libzypp
10.5.0
|
00001 /*---------------------------------------------------------------------\ 00002 | ____ _ __ __ ___ | 00003 | |__ / \ / / . \ . \ | 00004 | / / \ V /| _/ _/ | 00005 | / /__ | | | | | | | 00006 | /_____||_| |_| |_| | 00007 | | 00008 \---------------------------------------------------------------------*/ 00012 #include <iostream> 00013 00014 #include "zypp/base/Logger.h" 00015 #include "zypp/base/Gettext.h" 00016 #include "zypp/base/Exception.h" 00017 #include "zypp/base/Functional.h" 00018 #include "zypp/base/Collector.h" 00019 00020 #include "zypp/sat/detail/PoolImpl.h" 00021 #include "zypp/sat/Solvable.h" 00022 #include "zypp/sat/Pool.h" 00023 #include "zypp/sat/LookupAttr.h" 00024 00025 #include "zypp/Repository.h" 00026 #include "zypp/OnMediaLocation.h" 00027 #include "zypp/ZConfig.h" 00028 00029 using std::endl; 00030 00032 namespace zypp 00033 { 00034 00035 namespace sat 00036 { 00037 00038 namespace 00039 { 00040 void _doSplit( IdString & _ident, ResKind & _kind, IdString & _name ) 00041 { 00042 if ( ! _ident ) 00043 return; 00044 00045 ResKind explicitKind = Solvable::SplitIdent::explicitKind( _ident.c_str() ); 00046 // NOTE: kind package and srcpackage do not have namespaced ident! 00047 if ( ! explicitKind ) 00048 { 00049 _name = _ident; 00050 // No kind defaults to package 00051 if ( !_kind ) 00052 _kind = ResKind::package; 00053 if ( ! ( _kind == ResKind::package || _kind == ResKind::srcpackage ) ) 00054 _ident = IdString( str::form( "%s:%s", _kind.c_str(), _ident.c_str() ) ); 00055 } 00056 else 00057 { 00058 // strip kind spec from name 00059 _name = IdString( ::strchr( _ident.c_str(), ':' )+1 ); 00060 _kind = explicitKind; 00061 if ( _kind == ResKind::package || _kind == ResKind::srcpackage ) 00062 _ident = _name; 00063 } 00064 return; 00065 } 00066 } 00067 00068 Solvable::SplitIdent::SplitIdent( IdString ident_r ) 00069 : _ident( ident_r ) 00070 { _doSplit( _ident, _kind, _name ); } 00071 00072 Solvable::SplitIdent::SplitIdent( const char * ident_r ) 00073 : _ident( ident_r ) 00074 { _doSplit( _ident, _kind, _name ); } 00075 00076 Solvable::SplitIdent::SplitIdent( const std::string & ident_r ) 00077 : _ident( ident_r ) 00078 { _doSplit( _ident, _kind, _name ); } 00079 00080 Solvable::SplitIdent::SplitIdent( ResKind kind_r, IdString name_r ) 00081 : _ident( name_r ) 00082 , _kind( kind_r ) 00083 { _doSplit( _ident, _kind, _name ); } 00084 00085 Solvable::SplitIdent::SplitIdent( ResKind kind_r, const C_Str & name_r ) 00086 : _ident( name_r ) 00087 , _kind( kind_r ) 00088 { _doSplit( _ident, _kind, _name ); } 00089 00090 ResKind Solvable::SplitIdent::explicitKind( const char * ident_r ) 00091 { 00092 if ( ! ident_r ) 00093 return ResKind(); 00094 00095 const char * sep = ::strchr( ident_r, ':' ); 00096 if ( ! sep ) 00097 return ResKind(); 00098 00099 ResKind ret; 00100 if ( sep-ident_r >= 4 ) 00101 { 00102 switch ( ident_r[3] ) 00103 { 00104 #define OUTS(K,S) if ( !::strncmp( ident_r, ResKind::K.c_str(), S ) && ident_r[S] == ':' ) ret = ResKind::K 00105 // ----v 00106 case 'c': OUTS( patch, 5 ); break; 00107 case 'd': OUTS( product, 7 ); break; 00108 case 'k': OUTS( package, 7 ); break; 00109 case 'p': OUTS( srcpackage, 10 ); break; 00110 case 't': OUTS( pattern, 7 ); break; 00111 #undef OUTS 00112 } 00113 } 00114 return ret; 00115 } 00116 00118 00119 const Solvable Solvable::noSolvable; 00120 00122 00123 ::_Solvable * Solvable::get() const 00124 { return myPool().getSolvable( _id ); } 00125 00126 #define NO_SOLVABLE_RETURN( VAL ) \ 00127 ::_Solvable * _solvable( get() ); \ 00128 if ( ! _solvable ) return VAL 00129 00130 Solvable Solvable::nextInPool() const 00131 { return Solvable( myPool().getNextId( _id ) ); } 00132 00133 Solvable Solvable::nextInRepo() const 00134 { 00135 NO_SOLVABLE_RETURN( noSolvable ); 00136 for ( detail::SolvableIdType next = _id+1; next < unsigned(_solvable->repo->end); ++next ) 00137 { 00138 ::_Solvable * nextS( myPool().getSolvable( next ) ); 00139 if ( nextS && nextS->repo == _solvable->repo ) 00140 { 00141 return Solvable( next ); 00142 } 00143 } 00144 return noSolvable; 00145 } 00146 00147 Repository Solvable::repository() const 00148 { 00149 NO_SOLVABLE_RETURN( Repository::noRepository ); 00150 return Repository( _solvable->repo ); 00151 } 00152 00153 bool Solvable::isSystem() const 00154 { 00155 NO_SOLVABLE_RETURN( _id == detail::systemSolvableId ); 00156 return myPool().isSystemRepo( _solvable->repo ); 00157 } 00158 00159 bool Solvable::onSystemByUser() const 00160 { 00161 return isSystem() && myPool().isOnSystemByUser( ident() ); 00162 } 00163 00164 IdString Solvable::ident() const 00165 { 00166 NO_SOLVABLE_RETURN( IdString() ); 00167 return IdString( _solvable->name ); 00168 } 00169 00170 std::string Solvable::lookupStrAttribute( const SolvAttr & attr ) const 00171 { 00172 NO_SOLVABLE_RETURN( std::string() ); 00173 const char * s = ::solvable_lookup_str( _solvable, attr.id() ); 00174 return s ? s : std::string(); 00175 } 00176 00177 std::string Solvable::lookupStrAttribute( const SolvAttr & attr, const Locale & lang_r ) const 00178 { 00179 NO_SOLVABLE_RETURN( std::string() ); 00180 const char * s = 0; 00181 if ( lang_r == Locale::noCode ) 00182 { 00183 s = ::solvable_lookup_str_poollang( _solvable, attr.id() ); 00184 } 00185 else 00186 { 00187 for ( Locale l( lang_r ); l != Locale::noCode; l = l.fallback() ) 00188 if ( (s = ::solvable_lookup_str_lang( _solvable, attr.id(), l.code().c_str(), 0 )) ) 00189 return s; 00190 // here: no matching locale, so use default 00191 s = ::solvable_lookup_str_lang( _solvable, attr.id(), 0, 0 ); 00192 } 00193 return s ? s : std::string(); 00194 } 00195 00196 unsigned Solvable::lookupNumAttribute( const SolvAttr & attr ) const 00197 { 00198 NO_SOLVABLE_RETURN( 0 ); 00199 return ::solvable_lookup_num( _solvable, attr.id(), 0 ); 00200 } 00201 00202 bool Solvable::lookupBoolAttribute( const SolvAttr & attr ) const 00203 { 00204 NO_SOLVABLE_RETURN( false ); 00205 return ::solvable_lookup_bool( _solvable, attr.id() ); 00206 } 00207 00208 detail::IdType Solvable::lookupIdAttribute( const SolvAttr & attr ) const 00209 { 00210 NO_SOLVABLE_RETURN( detail::noId ); 00211 return ::solvable_lookup_id( _solvable, attr.id() ); 00212 } 00213 00214 CheckSum Solvable::lookupCheckSumAttribute( const SolvAttr & attr ) const 00215 { 00216 NO_SOLVABLE_RETURN( CheckSum() ); 00217 detail::IdType chksumtype = 0; 00218 const char * s = ::solvable_lookup_checksum( _solvable, attr.id(), &chksumtype ); 00219 if ( ! s ) 00220 return CheckSum(); 00221 switch ( chksumtype ) 00222 { 00223 case REPOKEY_TYPE_MD5: return CheckSum::md5( s ); 00224 case REPOKEY_TYPE_SHA1: return CheckSum::sha1( s ); 00225 case REPOKEY_TYPE_SHA256: return CheckSum::sha256( s ); 00226 } 00227 return CheckSum( std::string(), s ); // try to autodetect 00228 } 00229 00231 namespace 00232 { 00233 inline Pathname lookupDatadirIn( Repository repor_r ) 00234 { 00235 static const sat::SolvAttr susetagsDatadir( "susetags:datadir" ); 00236 Pathname ret; 00237 // First look for repo attribute "susetags:datadir". If not found, 00238 // look into the solvables as Code11 libsolv placed it there. 00239 sat::LookupRepoAttr datadir( susetagsDatadir, repor_r ); 00240 if ( ! datadir.empty() ) 00241 ret = datadir.begin().asString(); 00242 else 00243 { 00244 sat::LookupAttr datadir( susetagsDatadir, repor_r ); 00245 if ( ! datadir.empty() ) 00246 ret = datadir.begin().asString(); 00247 } 00248 return ret; 00249 } 00250 } 00252 00253 OnMediaLocation Solvable::lookupLocation() const 00254 { 00255 NO_SOLVABLE_RETURN( OnMediaLocation() ); 00256 // medianumber and path 00257 unsigned medianr; 00258 char * file = ::solvable_get_location( _solvable, &medianr ); 00259 if ( ! file ) 00260 return OnMediaLocation(); 00261 00262 OnMediaLocation ret; 00263 00264 Pathname path; 00265 switch ( repository().info().type().toEnum() ) 00266 { 00267 case repo::RepoType::NONE_e: 00268 { 00269 path = lookupDatadirIn( repository() ); 00270 if ( ! path.empty() ) 00271 repository().info().setProbedType( repo::RepoType::YAST2_e ); 00272 } 00273 break; 00274 00275 case repo::RepoType::YAST2_e: 00276 { 00277 path = lookupDatadirIn( repository() ); 00278 if ( path.empty() ) 00279 path = "suse"; 00280 } 00281 break; 00282 00283 default: 00284 break; 00285 } 00286 ret.setLocation ( path/file, medianr ); 00287 ret.setDownloadSize( ByteCount( lookupNumAttribute( SolvAttr::downloadsize ), ByteCount::K ) ); 00288 ret.setChecksum ( lookupCheckSumAttribute( SolvAttr::checksum ) ); 00289 // Not needed/available for solvables? 00290 //ret.setOpenSize ( ByteCount( lookupNumAttribute( SolvAttr::opensize ), ByteCount::K ) ); 00291 //ret.setOpenChecksum( lookupCheckSumAttribute( SolvAttr::openchecksum ) ); 00292 return ret; 00293 } 00294 00295 ResKind Solvable::kind() const 00296 { 00297 NO_SOLVABLE_RETURN( ResKind() ); 00298 // detect srcpackages by 'arch' 00299 switch ( _solvable->arch ) 00300 { 00301 case ARCH_SRC: 00302 case ARCH_NOSRC: 00303 return ResKind::srcpackage; 00304 break; 00305 } 00306 00307 const char * ident = IdString( _solvable->name ).c_str(); 00308 const char * sep = ::strchr( ident, ':' ); 00309 00310 // no ':' in package names (hopefully) 00311 if ( ! sep ) 00312 return ResKind::package; 00313 00314 // quick check for well known kinds 00315 if ( sep-ident >= 4 ) 00316 { 00317 switch ( ident[3] ) 00318 { 00319 #define OUTS(K,S) if ( !::strncmp( ident, ResKind::K.c_str(), S ) ) return ResKind::K 00320 // ----v 00321 case 'c': OUTS( patch, 5 ); break; 00322 case 'd': OUTS( product, 7 ); break; 00323 case 'k': OUTS( package, 7 ); break; 00324 case 'p': OUTS( srcpackage, 10 ); break; 00325 case 't': OUTS( pattern, 7 ); break; 00326 #undef OUTS 00327 } 00328 } 00329 00330 // an unknown kind 00331 return ResKind( std::string( ident, sep-ident ) ); 00332 } 00333 00334 bool Solvable::isKind( const ResKind & kind_r ) const 00335 { 00336 NO_SOLVABLE_RETURN( false ); 00337 00338 // detect srcpackages by 'arch' 00339 switch ( _solvable->arch ) 00340 { 00341 case ARCH_SRC: 00342 case ARCH_NOSRC: 00343 return( kind_r == ResKind::srcpackage ); 00344 break; 00345 } 00346 00347 // no ':' in package names (hopefully) 00348 const char * ident = IdString( _solvable->name ).c_str(); 00349 if ( kind_r == ResKind::package ) 00350 { 00351 return( ::strchr( ident, ':' ) == 0 ); 00352 } 00353 00354 // look for a 'kind:' prefix 00355 const char * kind = kind_r.c_str(); 00356 unsigned ksize = ::strlen( kind ); 00357 return( ::strncmp( ident, kind, ksize ) == 0 00358 && ident[ksize] == ':' ); 00359 } 00360 00361 std::string Solvable::name() const 00362 { 00363 NO_SOLVABLE_RETURN( std::string() ); 00364 const char * ident = IdString( _solvable->name ).c_str(); 00365 const char * sep = ::strchr( ident, ':' ); 00366 return( sep ? sep+1 : ident ); 00367 } 00368 00369 Edition Solvable::edition() const 00370 { 00371 NO_SOLVABLE_RETURN( Edition() ); 00372 return Edition( _solvable->evr ); 00373 } 00374 00375 Arch Solvable::arch() const 00376 { 00377 NO_SOLVABLE_RETURN( Arch_noarch ); //ArchId() ); 00378 switch ( _solvable->arch ) 00379 { 00380 case ARCH_SRC: 00381 case ARCH_NOSRC: 00382 return Arch_noarch; //ArchId( ARCH_NOARCH ); 00383 break; 00384 } 00385 return Arch( IdString(_solvable->arch).asString() ); 00386 //return ArchId( _solvable->arch ); 00387 } 00388 00389 bool Solvable::multiversionInstall() const 00390 { 00391 return myPool().isMultiversion( ident() ); 00392 } 00393 00394 IdString Solvable::vendor() const 00395 { 00396 NO_SOLVABLE_RETURN( IdString() ); 00397 return IdString( _solvable->vendor ); 00398 } 00399 00400 Capabilities Solvable::operator[]( Dep which_r ) const 00401 { 00402 switch( which_r.inSwitch() ) 00403 { 00404 case Dep::PROVIDES_e: return provides(); break; 00405 case Dep::REQUIRES_e: return requires(); break; 00406 case Dep::CONFLICTS_e: return conflicts(); break; 00407 case Dep::OBSOLETES_e: return obsoletes(); break; 00408 case Dep::RECOMMENDS_e: return recommends(); break; 00409 case Dep::SUGGESTS_e: return suggests(); break; 00410 case Dep::ENHANCES_e: return enhances(); break; 00411 case Dep::SUPPLEMENTS_e: return supplements(); break; 00412 case Dep::PREREQUIRES_e: return prerequires(); break; 00413 } 00414 return Capabilities(); 00415 } 00416 00417 inline Capabilities _getCapabilities( detail::IdType * idarraydata_r, ::Offset offs_r ) 00418 { 00419 return offs_r ? Capabilities( idarraydata_r + offs_r ) : Capabilities(); 00420 } 00421 Capabilities Solvable::provides() const 00422 { 00423 NO_SOLVABLE_RETURN( Capabilities() ); 00424 return _getCapabilities( _solvable->repo->idarraydata, _solvable->provides ); 00425 } 00426 Capabilities Solvable::requires() const 00427 { 00428 NO_SOLVABLE_RETURN( Capabilities() ); 00429 return _getCapabilities( _solvable->repo->idarraydata, _solvable->requires ); 00430 } 00431 Capabilities Solvable::conflicts() const 00432 { 00433 NO_SOLVABLE_RETURN( Capabilities() ); 00434 return _getCapabilities( _solvable->repo->idarraydata, _solvable->conflicts ); 00435 } 00436 Capabilities Solvable::obsoletes() const 00437 { 00438 NO_SOLVABLE_RETURN( Capabilities() ); 00439 return _getCapabilities( _solvable->repo->idarraydata, _solvable->obsoletes ); 00440 } 00441 Capabilities Solvable::recommends() const 00442 { 00443 NO_SOLVABLE_RETURN( Capabilities() ); 00444 return _getCapabilities( _solvable->repo->idarraydata, _solvable->recommends ); 00445 } 00446 Capabilities Solvable::suggests() const 00447 { 00448 NO_SOLVABLE_RETURN( Capabilities() ); 00449 return _getCapabilities( _solvable->repo->idarraydata, _solvable->suggests ); 00450 } 00451 Capabilities Solvable::enhances() const 00452 { 00453 NO_SOLVABLE_RETURN( Capabilities() ); 00454 return _getCapabilities( _solvable->repo->idarraydata, _solvable->enhances ); 00455 } 00456 Capabilities Solvable::supplements() const 00457 { 00458 NO_SOLVABLE_RETURN( Capabilities() ); 00459 return _getCapabilities( _solvable->repo->idarraydata, _solvable->supplements ); 00460 } 00461 Capabilities Solvable::prerequires() const 00462 { 00463 NO_SOLVABLE_RETURN( Capabilities() ); 00464 // prerequires are a subset of requires 00465 ::Offset offs = _solvable->requires; 00466 return offs ? Capabilities( _solvable->repo->idarraydata + offs, detail::solvablePrereqMarker ) 00467 : Capabilities(); 00468 } 00469 00470 CapabilitySet Solvable::providesNamespace( const std::string & namespace_r ) const 00471 { 00472 NO_SOLVABLE_RETURN( CapabilitySet() ); 00473 CapabilitySet ret; 00474 Capabilities caps( provides() ); 00475 for_( it, caps.begin(), caps.end() ) 00476 { 00477 CapDetail caprep( it->detail() ); 00478 if ( str::hasPrefix( caprep.name().c_str(), namespace_r ) && *(caprep.name().c_str()+namespace_r.size()) == '(' ) 00479 ret.insert( *it ); 00480 } 00481 return ret; 00482 } 00483 00484 CapabilitySet Solvable::valuesOfNamespace( const std::string & namespace_r ) const 00485 { 00486 NO_SOLVABLE_RETURN( CapabilitySet() ); 00487 CapabilitySet ret; 00488 Capabilities caps( provides() ); 00489 for_( it, caps.begin(), caps.end() ) 00490 { 00491 CapDetail caprep( it->detail() ); 00492 if ( str::hasPrefix( caprep.name().c_str(), namespace_r ) && *(caprep.name().c_str()+namespace_r.size()) == '(' ) 00493 { 00494 std::string value( caprep.name().c_str()+namespace_r.size()+1 ); 00495 value[value.size()-1] = '\0'; // erase the trailing ')' 00496 ret.insert( Capability( value, caprep.op(), caprep.ed() ) ); 00497 } 00498 } 00499 return ret; 00500 } 00501 00502 00503 std::string Solvable::asString() const 00504 { 00505 NO_SOLVABLE_RETURN( (_id == detail::systemSolvableId ? "systemSolvable" : "noSolvable") ); 00506 return str::form( "%s-%s.%s", 00507 IdString( _solvable->name ).c_str(), 00508 IdString( _solvable->evr ).c_str(), 00509 IdString( _solvable->arch ).c_str() ); 00510 } 00511 00512 bool Solvable::identical( Solvable rhs ) const 00513 { 00514 NO_SOLVABLE_RETURN( ! rhs.get() ); 00515 ::_Solvable * rhssolvable( rhs.get() ); 00516 return rhssolvable && ( _solvable == rhssolvable || ::solvable_identical( _solvable, rhssolvable ) ); 00517 } 00518 00520 namespace 00521 { 00522 00526 int invokeOnEachSupportedLocale( Capability cap_r, function<bool (const Locale &)> fnc_r ) 00527 { 00528 CapDetail detail( cap_r ); 00529 if ( detail.kind() == CapDetail::EXPRESSION ) 00530 { 00531 switch ( detail.capRel() ) 00532 { 00533 case CapDetail::CAP_AND: 00534 case CapDetail::CAP_OR: 00535 // expand 00536 { 00537 int res = invokeOnEachSupportedLocale( detail.lhs(), fnc_r ); 00538 if ( res < 0 ) 00539 return res; // negative on abort. 00540 int res2 = invokeOnEachSupportedLocale( detail.rhs(), fnc_r ); 00541 if ( res2 < 0 ) 00542 return -res + res2; // negative on abort. 00543 return res + res2; 00544 } 00545 break; 00546 00547 case CapDetail::CAP_NAMESPACE: 00548 if ( detail.lhs().id() == NAMESPACE_LANGUAGE ) 00549 { 00550 return ( !fnc_r || fnc_r( Locale( IdString(detail.rhs().id()) ) ) ) ? 1 : -1; // negative on abort. 00551 } 00552 break; 00553 00554 case CapDetail::REL_NONE: 00555 case CapDetail::CAP_WITH: 00556 case CapDetail::CAP_ARCH: 00557 break; // unwanted 00558 } 00559 } 00560 return 0; 00561 } 00562 00567 inline int invokeOnEachSupportedLocale( Capabilities cap_r, function<bool (const Locale &)> fnc_r ) 00568 { 00569 int cnt = 0; 00570 for_( cit, cap_r.begin(), cap_r.end() ) 00571 { 00572 int res = invokeOnEachSupportedLocale( *cit, fnc_r ); 00573 if ( res < 0 ) 00574 return -cnt + res; // negative on abort. 00575 cnt += res; 00576 } 00577 return cnt; 00578 } 00580 00581 // Functor returning false if a Locale is in the set. 00582 struct NoMatchIn 00583 { 00584 NoMatchIn( const LocaleSet & locales_r ) : _locales( locales_r ) {} 00585 00586 bool operator()( const Locale & locale_r ) const 00587 { 00588 return _locales.find( locale_r ) == _locales.end(); 00589 } 00590 00591 const LocaleSet & _locales; 00592 }; 00593 00594 } 00595 00596 bool Solvable::supportsLocales() const 00597 { 00598 // false_c stops on 1st Locale. 00599 return invokeOnEachSupportedLocale( supplements(), functor::false_c() ) < 0; 00600 } 00601 00602 bool Solvable::supportsLocale( const Locale & locale_r ) const 00603 { 00604 // not_equal_to stops on == Locale. 00605 return invokeOnEachSupportedLocale( supplements(), bind( std::not_equal_to<Locale>(), locale_r, _1 ) ) < 0; 00606 } 00607 00608 bool Solvable::supportsLocale( const LocaleSet & locales_r ) const 00609 { 00610 if ( locales_r.empty() ) 00611 return false; 00612 // NoMatchIn stops if Locale is included. 00613 return invokeOnEachSupportedLocale( supplements(), NoMatchIn(locales_r) ) < 0; 00614 } 00615 00616 bool Solvable::supportsRequestedLocales() const 00617 { return supportsLocale( myPool().getRequestedLocales() ); } 00618 00619 void Solvable::getSupportedLocales( LocaleSet & locales_r ) const 00620 { 00621 invokeOnEachSupportedLocale( supplements(), 00622 functor::Collector( std::inserter( locales_r, locales_r.begin() ) ) ); 00623 } 00624 00625 /****************************************************************** 00626 ** 00627 ** FUNCTION NAME : operator<< 00628 ** FUNCTION TYPE : std::ostream & 00629 */ 00630 std::ostream & operator<<( std::ostream & str, const Solvable & obj ) 00631 { 00632 if ( ! obj ) 00633 return str << (obj.isSystem() ? "systemSolvable" : "noSolvable" ); 00634 00635 return str << "(" << obj.id() << ")" 00636 << ( obj.isKind( ResKind::srcpackage ) ? "srcpackage:" : "" ) << obj.ident() 00637 << '-' << obj.edition() << '.' << obj.arch() << "(" 00638 << obj.repository().alias() << ")"; 00639 } 00640 00641 /****************************************************************** 00642 ** 00643 ** FUNCTION NAME : dumpOn 00644 ** FUNCTION TYPE : std::ostream & 00645 */ 00646 std::ostream & dumpOn( std::ostream & str, const Solvable & obj ) 00647 { 00648 str << obj; 00649 if ( obj ) 00650 { 00651 #define OUTS(X) if ( ! obj[Dep::X].empty() ) str << endl << " " #X " " << obj[Dep::X] 00652 OUTS(PROVIDES); 00653 OUTS(PREREQUIRES); 00654 OUTS(REQUIRES); 00655 OUTS(CONFLICTS); 00656 OUTS(OBSOLETES); 00657 OUTS(RECOMMENDS); 00658 OUTS(SUGGESTS); 00659 OUTS(ENHANCES); 00660 OUTS(SUPPLEMENTS); 00661 #undef OUTS 00662 } 00663 return str; 00664 } 00665 00667 } // namespace sat 00670 } // namespace zypp