add support for optional dependencies

update docs to explain how optional dependencies are handled
This commit is contained in:
John McQuah 2023-03-10 10:03:11 -05:00 committed by John McQuah
parent 8bc31c0745
commit 38bb3aa21a
16 changed files with 278 additions and 95 deletions

View File

@ -1,3 +1,8 @@
* 10.03.2023 John McQuah
- make it possible to consider optional dependencies when updating
- bump version of the cache file format, to warn users about the
'Packager' field being replaced by 'Optional'
* 5.16 26.06.2008 Johannes Winkelmann
- don't loop forever when version= contains unsupported $() tokens
- gcc 4.3 fixes (thanks Fredrik)

15
README
View File

@ -11,8 +11,8 @@ process.
Installing:
-----------
Just install the crux port from
- http://www.hta-bi.bfh.ch/~winkj/files/crux/prt-get.prt.gz
Download an iso of the latest CRUX release for a precompiled package.
Otherwise, build from the sources in this repository.
prt-get has to know where to look for ports. You can specify those in
/etc/prt-get.conf, one directory per line. Note that the order
@ -20,15 +20,16 @@ matters, when the same port is in multiple direcories
(e.g. /usr/ports/contrib/mutt and /usr/ports/local/mutt), the one
found _first_ will be used.
Feel free to contact me in case of problems (jw@tks6.net)
Feel free to file a bug report on the Gitea issue tracker:
https://git.crux.nu:82/farkuhar/prt-get/issues/
more information
----------------
See the man pages for prt-get(8), prt-get.conf(5) and prt-cache for more
information. There is also a user manual in the doc directory (as a
tex file). This document should also be available as PDF from
http://www.hta-bi.bfh.ch/~winkj/files/crux/manual.pdf
information. The CRUX homepage also offers a (somewhat dated) User Manual
and Quick Start guide:
https://crux.nu/doc/prt-get%20-%20User%20Manual.html
https://crux.nu/doc/prt-get%20-%20Quick%20start.html
"it must be user error" (thinkgeek)
-----------------------------------

2
TODO
View File

@ -10,7 +10,7 @@
CONSIDER:
- default formats for printf and dup
- add update-footprint, update-md5sum commands (patch in trac)
- add update-footprint, update-signature commands
- sysup
- allow injecting of new (uninstalled) dependencies
- allow to prohibit recompilation of packages when a dependency failed

View File

@ -48,6 +48,9 @@ preferhigher yes
# use regexps for searching (default no)
useregex yes
# consider soft dependencies when sorting the update/sysup targets
softdeps no
.fi
.LP
@ -90,6 +93,17 @@ if set to yes, prt-get will interpret search and filter patterns in
list, listinst, printf, search, dsearch and fsearch as regular
expressions. This will be the default in prt-get 0.6.
.B softdeps
if set to yes, during an update operation prt-get will perform a greedy search
of the dependency graph, visiting any \fBinstalled\fP optional dependency of
the ports specified on the command line and recursing until either a leaf node is
found (port that doesn't depend on anything else) or a cycle is created. If a
cycle is \fBnot\fP created by following a soft dependency, then this dependency
relationship is added to the list of edges, and topological sort of the
resulting graph can proceed as usual. The default is \fBsoftdeps no\fP, which
means prt-get will only consider hard dependencies when walking the directed
graph.
.LP
.B makecommand
.B addcommand
@ -155,6 +169,6 @@ problem :-)
.SH "AUTHORS"
Johannes Winkelmann <jw@tks6.net>
Johannes Winkelmann <jw@tks6.net>, John McQuah <jmcquah at disroot dot org>
.SH "SEE ALSO"
prt-get(8), pkgmk(8) pkgadd(8), ports(8)

View File

@ -41,3 +41,6 @@ logfile /var/log/pkgbuild/%n.log
### use regexp search
# useregex no # (yes|no)
### consider optional dependencies when sorting the update/sysup targets
# softdeps no # (yes|no)

View File

@ -29,6 +29,7 @@ ArgParser::ArgParser( int argc, char** argv )
m_noStdConfig( false ),
m_writeLog( false ),
m_nodeps( false ),
m_softdeps( false ),
m_all( false ),
m_printPath( false ),
m_execPreInstall( false ),
@ -182,6 +183,8 @@ bool ArgParser::parse()
m_useCache = true;
} else if ( s == "--nodeps" ) {
m_nodeps = true;
} else if ( s == "--softdeps" ) {
m_softdeps = true;
} else if ( s == "--all" ) {
m_all = true;
} else if ( s == "--path" ) {
@ -388,6 +391,14 @@ bool ArgParser::nodeps() const
return m_nodeps;
}
/*!
\return whether there was a --softdeps argument
*/
bool ArgParser::followSoftdeps() const
{
return m_softdeps;
}
/*!
\return whether there was a --all argument
*/

View File

@ -60,6 +60,7 @@ public:
bool recursive() const;
bool printTree() const;
bool depSort() const;
bool followSoftdeps() const;
const string& alternateConfigFile() const;
const string& pkgmkArgs() const;
@ -99,6 +100,7 @@ private:
bool m_writeLog;
bool m_nodeps;
bool m_softdeps;
bool m_all;
bool m_printPath;

View File

@ -34,6 +34,7 @@ Configuration::Configuration( const std::string& configFile,
m_runScripts( false ),
m_preferHigher( false ),
m_useRegex( false ),
m_followSoftdeps( false ),
m_makeCommand( "" ), m_addCommand( "" ),
m_removeCommand( "" ), m_runscriptCommand( "" )
{
@ -188,6 +189,14 @@ void Configuration::parseLine(const string& line, bool prepend)
if ( s == "yes" ) {
m_useRegex = true;
}
} else if ( startsWithNoCase( s, "softdeps" ) ) {
s = stripWhiteSpace( s.replace( 0, 8, "" ) );
if ( s == "yes" ) {
m_followSoftdeps = true;
}
if ( s == "no" ) {
m_followSoftdeps = false;
}
} else if ( startsWithNoCase( s, "makecommand" ) ) {
m_makeCommand = stripWhiteSpace( s.replace( 0, 11, "" ) );
} else if ( startsWithNoCase( s, "addcommand" ) ) {
@ -234,4 +243,8 @@ bool Configuration::useRegex() const
return m_useRegex;
}
bool Configuration::followSoftdeps() const
{
return m_followSoftdeps;
}

View File

@ -43,6 +43,7 @@ public:
bool runScripts() const;
bool preferHigher() const;
bool useRegex() const;
bool followSoftdeps() const;
void addConfig(const std::string& line,
bool configSet,
@ -72,6 +73,7 @@ private:
bool m_runScripts;
bool m_preferHigher;
bool m_useRegex;
bool m_followSoftdeps;
std::string m_makeCommand;
std::string m_addCommand;

View File

@ -467,12 +467,13 @@ bool InstallTransaction::calculateDependencies()
return false;
}
vector<string> treeWalk;
list< pair<string, const Package*> >::const_iterator it =
m_packages.begin();
for ( ; it != m_packages.end(); ++it ) {
const Package* package = it->second;
if ( package ) {
checkDependecies( package );
checkDependencies( false, package );
}
}
list<int> indexList;
@ -491,14 +492,17 @@ bool InstallTransaction::calculateDependencies()
/*!
recursive method to calculate dependencies
\param package package for which we want to calculate dependencies
\param depends index if the package \a package depends on (-1 for none)
\param greedy (=true if any soft dependencies took us to the current node)
\param package, package for which we want to calculate dependencies
\param depends, index of the package \a package depends on (-1 for none)
*/
void InstallTransaction::checkDependecies( const Package* package,
void InstallTransaction::checkDependencies( bool greedy,
const Package* package,
int depends )
{
int index = -1;
bool newPackage = true;
for ( unsigned int i = 0; i < m_depList.size(); ++i ){
if ( m_depList[i] == package->name() ) {
index = i;
@ -507,17 +511,28 @@ void InstallTransaction::checkDependecies( const Package* package,
}
}
if ( index == -1 ) {
index = m_depList.size();
if (( not greedy ) or ( m_pkgDB->isInstalled(package->name(),false) )) {
m_depList.push_back( package->name() );
}
}
if ( depends != -1 ) {
m_resolver.addDependency( index, depends );
} else {
// did we already visit the current node when walking the tree?
// return to the caller if such a cycle is detected
for ( unsigned int i = 0; i < treeWalk.size(); ++i) {
if ( treeWalk[i] == package->name() ) {
return;
}
}
treeWalk.push_back(package->name());
if ( depends == -1 ) {
// this just adds index to the dependency resolver
m_resolver.addDependency( index, index );
} else {
m_resolver.addDependency( index, depends );
}
if ( newPackage ) {
@ -534,15 +549,41 @@ void InstallTransaction::checkDependecies( const Package* package,
}
const Package* p = m_repo->getPackage( dep );
if ( p ) {
checkDependecies( p, index );
checkDependencies( greedy, p, index );
} else {
m_missingPackages.
push_back( make_pair( dep, package->name() ) );
}
}
}
}
if ( (m_config->followSoftdeps()) and (!package->optionals().empty()) ) {
list<string> softDeps;
split( package->optionals(), ',', softDeps );
list<string>::iterator it = softDeps.begin();
for ( ; it != softDeps.end(); ++it ) {
string softdep = *it;
if ( !softdep.empty() ) {
string::size_type pos = softdep.find_last_of( '/' );
if ( pos != string::npos && (pos+1) < softdep.length() ) {
softdep = softdep.substr( pos + 1 );
}
if ( m_pkgDB->isInstalled(softdep, true) ) {
const Package* p = m_repo->getPackage( softdep );
if ( p ) {
checkDependencies( true, p, index );
} else {
m_missingPackages.
push_back( make_pair( softdep, package->name() ) );
}
}
}
}
}
}
// reset the tree traversal history
treeWalk.pop_back();
}

View File

@ -103,7 +103,7 @@ public:
private:
bool calculateDependencies();
void checkDependecies( const Package* package, int depends=-1 );
void checkDependencies( bool greedy, const Package* package, int depends=-1 );
InstallResult installPackage( const Package* package,
const ArgParser* parser,
@ -136,6 +136,7 @@ private:
list<string> m_depNameList;
vector<string> m_depList;
vector<string> treeWalk;
// packages requested to be installed not found in the ports tree
list< pair<string, string> > m_missingPackages;

View File

@ -44,7 +44,7 @@ Package::Package( const string& name,
const string& description,
const string& dependencies,
const string& url,
const string& packager,
const string& optionals,
const string& maintainer,
const string& hasReadme,
const string& hasPreInstall,
@ -53,7 +53,7 @@ Package::Package( const string& name,
{
m_data = new PackageData( name, path, version, release,
description, dependencies, url,
packager, maintainer, hasReadme,
optionals, maintainer, hasReadme,
hasPreInstall, hasPostInstall );
}
@ -110,11 +110,11 @@ const string& Package::url() const
return m_data->url;
}
/*! \return the packager of this package */
const string& Package::packager() const
/*! \return the optional dependencies of this package */
const string& Package::optionals() const
{
load();
return m_data->packager;
return m_data->optionals;
}
/*! \return the maintainer of this package */
const string& Package::maintainer() const
@ -202,15 +202,36 @@ void Package::load() const
if ( startsWithNoCase( line, "desc" ) ) {
m_data->description =
stripWhiteSpace( getValue( line, ':' ) );
} else if ( startsWithNoCase( line, "pack" ) ) {
m_data->packager =
stripWhiteSpace( getValue( line, ':' ) );
} else if ( startsWithNoCase( line, "maint" ) ) {
m_data->maintainer =
stripWhiteSpace( getValue( line, ':' ) );
} else if ( startsWithNoCase( line, "url" ) ) {
m_data->url = stripWhiteSpace( getValue( line, ':' ) );
} else if ( startsWithNoCase( line, "optional" ) or
startsWithNoCase( line, "nice to have" ) ) {
string softdeps = stripWhiteSpace( getValue( line, ':' ) );
StringHelper::replaceAll( softdeps, " ", "," );
StringHelper::replaceAll( softdeps, ",,", "," );
// TODO: decide which one to use
#if 0
// remove commented out packages
list<string> softDepList = StringHelper::split( softdeps, ',' );
list<string>::iterator it = deps.begin();
for ( ; it != softDepList.end(); ++it ) {
if ( (*it)[0] == '#' ) {
cerr << "Commented dep: " << *it << endl;
} else {
if ( it != softDepsList.begin() ) {
m_data->optionals += ",";
}
m_data->optionals += *it;
}
}
#else
m_data->optionals = softdeps;
#endif
} else if ( startsWithNoCase( line, "dep" ) ) {
string depends = stripWhiteSpace( getValue( line, ':' ) );
@ -265,6 +286,11 @@ void Package::setDependencies( const std::string& dependencies )
m_data->depends = dependencies;
}
void Package::setOptionals( const std::string& optionals )
{
m_data->optionals = optionals;
}
PackageData::PackageData( const string& name_,
@ -274,7 +300,7 @@ PackageData::PackageData( const string& name_,
const string& description_,
const string& dependencies_,
const string& url_,
const string& packager_,
const string& optionals_,
const string& maintainer_,
const string& hasReadme_,
const string& hasPreInstall_,
@ -286,7 +312,7 @@ PackageData::PackageData( const string& name_,
description( description_ ),
depends( dependencies_ ),
url( url_ ),
packager( packager_ ),
optionals( optionals_ ),
maintainer( maintainer_ )
{

View File

@ -35,7 +35,7 @@ public:
const std::string& description,
const std::string& dependencies,
const std::string& url,
const std::string& packager,
const std::string& optionals,
const std::string& maintainer,
const std::string& hasReadme,
const std::string& hasPreInstall,
@ -50,7 +50,7 @@ public:
const std::string& description() const;
const std::string& dependencies() const;
const std::string& url() const;
const std::string& packager() const;
const std::string& optionals() const;
const std::string& maintainer() const;
const bool hasReadme() const;
const bool hasPreInstall() const;
@ -59,6 +59,7 @@ public:
std::string versionReleaseString() const;
void setDependencies( const std::string& dependencies );
void setOptionals( const std::string& optionals );
private:
@ -82,7 +83,7 @@ struct PackageData
const std::string& description_="",
const std::string& dependencies_="",
const std::string& url_="",
const std::string& packager="",
const std::string& optionals_="",
const std::string& maintainer="",
const std::string& hasReadme_="",
const std::string& hasPreInstall_="",
@ -95,7 +96,7 @@ struct PackageData
std::string description;
std::string depends;
std::string url;
std::string packager;
std::string optionals;
std::string maintainer;
std::string versionReleaseString;

View File

@ -73,6 +73,7 @@ PrtGet::PrtGet( const ArgParser* parser )
readConfig();
m_useRegex = m_config->useRegex() || m_parser->useRegex();
m_followSoftdeps = m_config->followSoftdeps() || m_parser->followSoftdeps();
}
/*! destruct PrtGet object */
@ -152,6 +153,8 @@ void PrtGet::printUsage()
<< "depend on 'port'"
<< endl;
cout << " where opt can be:" << endl;
cout << " --softdeps consider optional dependencies too"
<< endl;
cout << " --all list all dependent packages, not "
<< "only installed" << endl;
cout << " --recursive print recursive listing" << endl;
@ -205,16 +208,12 @@ void PrtGet::printUsage()
cout << " sysup [opt] update all outdated ports"
<< endl;
cout << " where opt can be:" << endl;
cout << " --nodeps don't sort by dependencies"
cout << " --nodeps don't sort by dependencies"
<< endl;
cout << " --test test mode" << endl;
cout << " --log write log file"<< endl;
cout << " --prefer-higher prefer higher installed "
<< "versions over lower ones in ports tree"
<< endl;
cout << " --strict-diff override prefer higher "
<< "configuration setting"
cout << " --softdeps consider optional dependencies when sorting"
<< endl;
cout << " --test test mode" << endl;
cout << " --log write log file"<< endl;
cout << " lock <port1 port2...> lock current version "
<< "of packages"
@ -395,16 +394,15 @@ void PrtGet::printInfo()
if ( !p->url().empty() ) {
cout << "URL: " << p->url() << endl;
}
if ( !p->packager().empty() ) {
cout << "Packager: " << p->packager() << endl;
}
if ( !p->maintainer().empty() ) {
cout << "Maintainer: " << p->maintainer() << endl;
}
if ( !p->dependencies().empty() ) {
cout << "Dependencies: " << p->dependencies() << endl;
}
if ( !p->optionals().empty() ) {
cout << "Optional: " << p->optionals() << endl;
}
// TODO: don't hardcode file names
string filesString = "";
@ -765,7 +763,11 @@ void PrtGet::printDepends( bool simpleListing )
}
} else {
if ( deps.size() > 0 ) {
cout << "-- dependencies ([i] = installed)" << endl;
cout << "-- dependencies (";
if ( m_followSoftdeps ) {
cout << "including optionals, ";
}
cout << "[i] = installed)" << endl;
list<string>::const_iterator it = deps.begin();
bool isAlias;
@ -1268,7 +1270,7 @@ void PrtGet::printf()
StringHelper::replaceAll( output, "%r", p->release() );
StringHelper::replaceAll( output, "%d", p->description() );
StringHelper::replaceAll( output, "%e", p->dependencies() );
StringHelper::replaceAll( output, "%P", p->packager() );
StringHelper::replaceAll( output, "%P", p->optionals() );
StringHelper::replaceAll( output, "%M", p->maintainer() );
StringHelper::replaceAll( output, "\\t", "\t" );
@ -1281,7 +1283,7 @@ void PrtGet::printf()
StringHelper::replaceAll( sortkey, "%r", p->release() );
StringHelper::replaceAll( sortkey, "%d", p->description() );
StringHelper::replaceAll( sortkey, "%e", p->dependencies() );
StringHelper::replaceAll( sortkey, "%P", p->packager() );
StringHelper::replaceAll( sortkey, "%P", p->optionals() );
StringHelper::replaceAll( sortkey, "%M", p->maintainer() );
string isInst = "no";
@ -1371,6 +1373,7 @@ void PrtGet::printDependent()
initRepo();
string arg = *(m_parser->otherArgs().begin());
vector<string> treeWalk;
if (m_parser->printTree()) {
cout << arg << endl;
@ -1385,6 +1388,14 @@ void PrtGet::printDependent(const string& dep, int level)
map<string, Package*>::const_iterator it = m_repo->packages().begin();
static map<string, bool> shownMap;
/* cycle detection --- code duplicated from checkDependencies */
for ( unsigned int i=0; i != treeWalk.size() ; ++i ) {
if ( treeWalk[i] == dep ) {
return;
}
}
treeWalk.push_back(dep);
set<const Package*> dependent;
for ( ; it != m_repo->packages().end(); ++it ) {
@ -1400,6 +1411,17 @@ void PrtGet::printDependent(const string& dep, int level)
dependent.insert( p );
}
}
if ( p && m_pkgDB->isInstalled(p->name(),false) && m_followSoftdeps
&& p->optionals().find( dep ) != string::npos ) {
list<string> tokens;
StringHelper::split( p->optionals(), ',', tokens );
list<string>::iterator it = find( tokens.begin(),
tokens.end(),
dep );
if ( it != tokens.end() ) {
dependent.insert( p );
}
}
}
// - there are two modes, tree and non-tree recursive mode; in
@ -1446,6 +1468,8 @@ void PrtGet::printDependent(const string& dep, int level)
}
}
}
treeWalk.pop_back();
}
void PrtGet::listOrphans()
@ -1464,6 +1488,15 @@ void PrtGet::listOrphans()
for (; lit != tokens.end(); ++lit) {
required[*lit] = true;
}
if (m_followSoftdeps) {
StringHelper::split( p->optionals(), ',', tokens );
lit = tokens.begin();
for (; lit != tokens.end(); ++lit) {
if ( m_pkgDB->isInstalled(*lit,false) ) {
required[*lit] = true;
}
}
}
}
}
@ -1988,69 +2021,97 @@ void PrtGet::printDependTree()
cerr << "Package '" << arg << "' not found" << endl;
m_returnValue = PG_GENERAL_ERROR;
return;
}
}
if (p->dependencies().length() > 0) {
cout << "-- dependencies ([i] = installed";
if ( (p->dependencies().length() > 0) or
(m_followSoftdeps && p->optionals().length() > 0) ) {
cout << "-- dependencies (";
if ( m_followSoftdeps ) {
cout << "[s] soft, ";
}
cout << "[i] hard, [ ] not installed";
if (!m_parser->all()) {
cout << ", '-->' = seen before";
cout << ", '-->' already shown";
}
cout << ")" << endl;
if ( m_pkgDB->isInstalled( *it ) ) {
cout << "[i] ";
} else {
cout << "[ ] ";
}
cout << p->name() << endl;
printDepsLevel(2, p);
printDepsLevel(2, p, false);
}
}
void PrtGet::printDepsLevel(int indent, const Package* package)
void PrtGet::printDepsLevel(int indent, const Package* package, bool greedy)
{
static map<string, bool> shownMap;
string installStatus;
bool isAlias = false;
string aliasName = "";
/* cycle detection -- code duplicated from checkDependencies */
for (unsigned int i=0; i<treeWalk.size(); ++i) {
if (treeWalk[i] == package->name()) {
return;
}
}
treeWalk.push_back(package->name());
if ( m_pkgDB->isInstalled( package->name(), true, &isAlias, &aliasName ) ) {
installStatus = ( greedy ) ? "[s] " : "[i] ";
cout << installStatus;
} else {
cout << "[ ] ";
}
for (int i = 0; i < indent; ++i) {
cout << " ";
}
cout << package->name();
if (isAlias) {
cout << " (provided by " << aliasName << ")";
}
map<string, bool>::iterator shownIt = shownMap.find(package->name());
if (shownIt != shownMap.end()) {
cout << " -->" << endl;
treeWalk.pop_back();
return;
} else {
cout << endl;
}
list<string> deps;
StringHelper::split(package->dependencies(), ',', deps);
list<string>::iterator it = deps.begin();
bool isAlias = false;
string aliasName = "";
for (; it != deps.end(); ++it) {
if ( m_pkgDB->isInstalled( *it, true, &isAlias, &aliasName ) ) {
cout << "[i] ";
} else {
cout << "[ ] ";
}
for (int i = 0; i < indent; ++i) {
cout << " ";
}
cout << *it;
if (isAlias) {
cout << " (provided by " << aliasName << ")";
}
const Package* p = m_repo->getPackage( *it );
if (p) {
if (p->dependencies().length() > 0) {
map<string, bool>::iterator shownIt = shownMap.find(*it);
if (shownIt != shownMap.end()) {
cout << " -->" << endl;;
} else {
cout << endl;
printDepsLevel(indent+2, p);
if (!m_parser->all()) {
shownMap[*it] = true;
}
}
} else {
cout << endl;
}
printDepsLevel(indent+2, p, greedy);
if (!m_parser->all()) {
shownMap[*it] = true;
}
} else {
cout << " (not found in ports tree)" << endl;
}
}
}
if ( m_followSoftdeps ) {
list<string> softDeps;
StringHelper::split(package->optionals(), ',', softDeps);
list<string>::iterator it = softDeps.begin();
for (; it != softDeps.end(); ++it) {
if ( m_pkgDB->isInstalled(*it, true) ) {
const Package* p = m_repo->getPackage( *it );
if (p) {
printDepsLevel(indent+2,p,true);
if (!m_parser->all()) {
shownMap[*it] = true;
}
} else {
cout << " (not found in ports tree)" << endl;
}
}
}
}
// reset the tree traversal history
treeWalk.pop_back();
}
void PrtGet::dumpConfig()

View File

@ -97,7 +97,7 @@ public:
protected:
void printDepsLevel(int indent, const Package* package);
void printDepsLevel(int indent, const Package* package, bool greedy);
void printDependent(const std::string& dep, int level);
@ -138,6 +138,8 @@ protected:
int m_returnValue;
bool m_useRegex;
bool m_followSoftdeps;
vector<string> treeWalk;
/*! Name of default configuration file */
static const string CONF_FILE;

View File

@ -264,7 +264,7 @@ Repository::initFromCache( const string& cacheFile )
// FIELDS:
// name, path, version, release,
// description, dependencies, url,
// packager, maintainer, hasReadme;
// optionals, maintainer, hasReadme;
// hasPreInstall, hasPostInstall
const int fieldCount = 12;
string fields[fieldCount];
@ -351,7 +351,7 @@ Repository::WriteResult Repository::writeCache( const string& cacheFile )
p->description().c_str(),
p->dependencies().c_str(),
p->url().c_str(),
p->packager().c_str(),
p->optionals().c_str(),
p->maintainer().c_str(),
hasReadme, hasPreInstall, hasPostInstall );
}