diff --git a/etc/pacman.conf.in b/etc/pacman.conf.in index d1ab736e..78710275 100644 --- a/etc/pacman.conf.in +++ b/etc/pacman.conf.in @@ -24,6 +24,7 @@ Architecture = auto # Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup #IgnorePkg = #IgnoreGroup = +#DependencyStrategy = Default #NoUpgrade = #NoExtract = diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h index e532f3de..8330926e 100644 --- a/lib/libalpm/alpm.h +++ b/lib/libalpm/alpm.h @@ -570,6 +570,22 @@ typedef enum _alpm_fileconflicttype_t { ALPM_FILECONFLICT_FILESYSTEM } alpm_fileconflicttype_t; +/** + * Dependency strategy type. + * The strategy changes the details of how dependency names are + * matched up against existing packages when requested. + */ +typedef enum _alpm_depstrategy_t { + /** Prefer packages with a name matching a dependency over + packages that provide that name, regardless of database + order. */ + ALPM_DEPSTRATEGY_DEFAULT = 1, + /** Prefer packages in higher priority databases over packages + in lower priority databases, regardless of whether their + names match the dependency or they just provide it. */ + ALPM_DEPSTRATEGY_STRICT +} alpm_depstrategy_t; + /** The basic dependency type. * * This type is used throughout libalpm, not just for dependencies @@ -644,7 +660,7 @@ alpm_list_t *alpm_checkdeps(alpm_handle_t *handle, alpm_list_t *pkglist, */ alpm_pkg_t *alpm_find_satisfier(alpm_list_t *pkgs, const char *depstring); -/** Find a package satisfying a specified dependency. +/** Find a package satisfying a specified dependency with the default strategy. * First look for a literal, going through each db one by one. Then look for * providers. The first satisfyer that belongs to an installed package is * returned. If no providers belong to an installed package then an @@ -659,6 +675,22 @@ alpm_pkg_t *alpm_find_satisfier(alpm_list_t *pkgs, const char *depstring); alpm_pkg_t *alpm_find_dbs_satisfier(alpm_handle_t *handle, alpm_list_t *dbs, const char *depstring); +/** Find a package satisfying a specified dependency. + * First look for a literal, going through each db one by one. Then look for + * providers. The first satisfyer that belongs to an installed package is + * returned. If no providers belong to an installed package then an + * alpm_question_select_provider_t is created to select the provider. + * The dependency can include versions with depmod operators. + * + * @param handle the context handle + * @param dbs an alpm_list_t* of alpm_db_t where the satisfyer will be searched + * @param depstrategy the dependency strategy to use + * @param depstring package or provision name, versioned or not + * @return a alpm_pkg_t* satisfying depstring + */ +alpm_pkg_t *alpm_find_dbs_satisfier_ex(alpm_handle_t *handle, + alpm_list_t *dbs, alpm_depstrategy_t depstrategy, const char *depstring); + /** Check the package conflicts in a database * * @param handle the context handle @@ -2811,7 +2843,7 @@ alpm_list_t *alpm_trans_get_remove(alpm_handle_t *handle); */ int alpm_trans_init(alpm_handle_t *handle, int flags); -/** Prepare a transaction. +/** Prepare a transaction with the default dependency strategy. * @param handle the context handle * @param data the address of an alpm_list where a list * of alpm_depmissing_t objects is dumped (conflicting packages) @@ -2819,6 +2851,15 @@ int alpm_trans_init(alpm_handle_t *handle, int flags); */ int alpm_trans_prepare(alpm_handle_t *handle, alpm_list_t **data); +/** Prepare a transaction specifying the dependency strategy. + * @param handle the context handle + * @param data the address of an alpm_list where a list + * of alpm_depmissing_t objects is dumped (conflicting packages) + * @param depstrategy the dependency resolution strategy to use. + * @return 0 on success, -1 on error (pm_errno is set accordingly) + */ +int alpm_trans_prepare_ex(alpm_handle_t *handle, alpm_depstrategy_t depstrategy, alpm_list_t **data); + /** Commit a transaction. * @param handle the context handle * @param data the address of an alpm_list where detailed description diff --git a/lib/libalpm/deps.c b/lib/libalpm/deps.c index 041e8988..7d3cc868 100644 --- a/lib/libalpm/deps.c +++ b/lib/libalpm/deps.c @@ -622,7 +622,9 @@ int _alpm_recursedeps(alpm_db_t *db, alpm_list_t **targs, int include_explicit) } /** - * helper function for resolvedeps: search for dep satisfier in dbs + * helper function for resolvedep: search for dep satisfier in dbs + * this version applies the "default" logic that puts name matching + * ahead of db ordering. * * @param handle the context handle * @param dep is the dependency to search for @@ -633,7 +635,7 @@ int _alpm_recursedeps(alpm_db_t *db, alpm_list_t **targs, int include_explicit) * packages. * @return the resolved package **/ -static alpm_pkg_t *resolvedep(alpm_handle_t *handle, alpm_depend_t *dep, +static alpm_pkg_t *resolvedep_default(alpm_handle_t *handle, alpm_depend_t *dep, alpm_list_t *dbs, alpm_list_t *excluding, int prompt) { alpm_list_t *i, *j; @@ -748,8 +750,169 @@ static alpm_pkg_t *resolvedep(alpm_handle_t *handle, alpm_depend_t *dep, return NULL; } +/** + * helper function for resolvedep: search for dep satisfier in dbs + * this version applies the "strict" logic that puts db order before + * exact matching. + * + * @param handle the context handle + * @param dep is the dependency to search for + * @param dbs are the databases to search + * @param excluding are the packages to exclude from the search + * @param prompt if true, ask an alpm_question_install_ignorepkg_t to decide + * if ignored packages should be installed; if false, skip ignored + * packages. + * @return the resolved package + **/ +static alpm_pkg_t *resolvedep_strict(alpm_handle_t *handle, alpm_depend_t *dep, + alpm_list_t *dbs, alpm_list_t *excluding, int prompt) +{ + alpm_list_t *i, *j; + int ignored = 0; + + alpm_list_t *providers = NULL; + int count; + + for(i = dbs; i; i = i->next) { + alpm_pkg_t *pkg; + alpm_db_t *db = i->data; + + if(!(db->usage & (ALPM_DB_USAGE_INSTALL|ALPM_DB_USAGE_UPGRADE))) { + continue; + } + + /* 1. literals */ + pkg = _alpm_db_get_pkgfromcache(db, dep->name); + if(pkg && _alpm_depcmp_literal(pkg, dep) + && !alpm_pkg_find(excluding, pkg->name)) { + if(alpm_pkg_should_ignore(handle, pkg)) { + alpm_question_install_ignorepkg_t question = { + .type = ALPM_QUESTION_INSTALL_IGNOREPKG, + .install = 0, + .pkg = pkg + }; + if(prompt) { + QUESTION(handle, &question); + } else { + _alpm_log(handle, ALPM_LOG_WARNING, _("ignoring package %s-%s\n"), + pkg->name, pkg->version); + } + if(!question.install) { + ignored = 1; + continue; + } + } + if(_alpm_db_get_pkgfromcache(handle->db_local, pkg->name)) { + alpm_list_free(providers); + return pkg; + } + providers = alpm_list_add(providers, pkg); + } + + /* 2. satisfiers (skip literals here) */ + for(j = _alpm_db_get_pkgcache(db); j; j = j->next) { + pkg = j->data; + if((pkg->name_hash != dep->name_hash || strcmp(pkg->name, dep->name) != 0) + && _alpm_depcmp_provides(dep, alpm_pkg_get_provides(pkg)) + && !alpm_pkg_find(excluding, pkg->name)) { + if(alpm_pkg_should_ignore(handle, pkg)) { + alpm_question_install_ignorepkg_t question = { + .type = ALPM_QUESTION_INSTALL_IGNOREPKG, + .install = 0, + .pkg = pkg + }; + if(prompt) { + QUESTION(handle, &question); + } else { + _alpm_log(handle, ALPM_LOG_WARNING, _("ignoring package %s-%s\n"), + pkg->name, pkg->version); + } + if(!question.install) { + ignored = 1; + continue; + } + } + _alpm_log(handle, ALPM_LOG_DEBUG, "provider found (%s provides %s)\n", + pkg->name, dep->name); + + /* provide is already installed so return early instead of prompting later */ + if(_alpm_db_get_pkgfromcache(handle->db_local, pkg->name)) { + alpm_list_free(providers); + return pkg; + } + + providers = alpm_list_add(providers, pkg); + /* keep looking for other providers in the all dbs */ + } + } + } + + count = alpm_list_count(providers); + if(count >= 1) { + alpm_question_select_provider_t question = { + .type = ALPM_QUESTION_SELECT_PROVIDER, + /* default to first provider if there is no QUESTION callback */ + .use_index = 0, + .providers = providers, + .depend = dep + }; + if(count > 1) { + /* if there is more than one provider, we ask the user */ + QUESTION(handle, &question); + } + if(question.use_index >= 0 && question.use_index < count) { + alpm_list_t *nth = alpm_list_nth(providers, question.use_index); + alpm_pkg_t *pkg = nth->data; + alpm_list_free(providers); + return pkg; + } + alpm_list_free(providers); + providers = NULL; + } + + if(ignored) { /* resolvedeps will override these */ + handle->pm_errno = ALPM_ERR_PKG_IGNORED; + } else { + handle->pm_errno = ALPM_ERR_PKG_NOT_FOUND; + } + return NULL; +} + +/** + * helper function for resolvedeps: search for dep satisfier in dbs + * + * @param handle the context handle + * @param dep is the dependency to search for + * @param depstrategy is the depstrategy to apply when searching + * @param dbs are the databases to search + * @param excluding are the packages to exclude from the search + * @param prompt if true, ask an alpm_question_install_ignorepkg_t to decide + * if ignored packages should be installed; if false, skip ignored + * packages. + * @return the resolved package + **/ +static alpm_pkg_t *resolvedep(alpm_handle_t *handle, alpm_depend_t *dep, + alpm_depstrategy_t depstrategy, alpm_list_t *dbs, alpm_list_t *excluding, int prompt) +{ + switch(depstrategy) { + case ALPM_DEPSTRATEGY_DEFAULT: + return resolvedep_default(handle, dep, dbs, excluding, prompt); + case ALPM_DEPSTRATEGY_STRICT: + return resolvedep_strict(handle, dep, dbs, excluding, prompt); + default: + /* We should never reach here */ + return NULL; + } +} + alpm_pkg_t SYMEXPORT *alpm_find_dbs_satisfier(alpm_handle_t *handle, alpm_list_t *dbs, const char *depstring) +{ + return alpm_find_dbs_satisfier_ex(handle, dbs, ALPM_DEPSTRATEGY_DEFAULT, depstring); +} + +alpm_pkg_t SYMEXPORT *alpm_find_dbs_satisfier_ex(alpm_handle_t *handle, + alpm_list_t *dbs, alpm_depstrategy_t depstrategy, const char *depstring) { alpm_depend_t *dep; alpm_pkg_t *pkg; @@ -759,7 +922,7 @@ alpm_pkg_t SYMEXPORT *alpm_find_dbs_satisfier(alpm_handle_t *handle, dep = alpm_dep_from_string(depstring); ASSERT(dep, return NULL); - pkg = resolvedep(handle, dep, dbs, NULL, 1); + pkg = resolvedep(handle, dep, depstrategy, dbs, NULL, 1); alpm_dep_free(dep); return pkg; } @@ -771,6 +934,8 @@ alpm_pkg_t SYMEXPORT *alpm_find_dbs_satisfier(alpm_handle_t *handle, * @param handle the context handle * @param localpkgs is the list of local packages * @param pkg is the package to resolve + * @param depstrategy is the strategy to follow (one of + ALPM_DEPSTRATEGY_DEFAULT, ALPM_DEPSTRATEGY_STRICT) * @param preferred packages to prefer when resolving * @param packages is a pointer to a list of packages which will be * searched first for any dependency packages needed to complete the @@ -786,8 +951,8 @@ alpm_pkg_t SYMEXPORT *alpm_find_dbs_satisfier(alpm_handle_t *handle, * unmodified by this function */ int _alpm_resolvedeps(alpm_handle_t *handle, alpm_list_t *localpkgs, - alpm_pkg_t *pkg, alpm_list_t *preferred, alpm_list_t **packages, - alpm_list_t *rem, alpm_list_t **data) + alpm_pkg_t *pkg, alpm_depstrategy_t depstrategy, alpm_list_t *preferred, + alpm_list_t **packages, alpm_list_t *rem, alpm_list_t **data) { int ret = 0; alpm_list_t *j; @@ -826,14 +991,14 @@ int _alpm_resolvedeps(alpm_handle_t *handle, alpm_list_t *localpkgs, alpm_pkg_t *spkg = find_dep_satisfier(preferred, missdep); if(!spkg) { /* find a satisfier package in the given repositories */ - spkg = resolvedep(handle, missdep, handle->dbs_sync, *packages, 0); + spkg = resolvedep(handle, missdep, depstrategy, handle->dbs_sync, *packages, 0); } - if(spkg && _alpm_resolvedeps(handle, localpkgs, spkg, preferred, packages, rem, data) == 0) { + if(spkg && _alpm_resolvedeps(handle, localpkgs, spkg, depstrategy, preferred, packages, rem, data) == 0) { _alpm_log(handle, ALPM_LOG_DEBUG, "pulling dependency %s (needed by %s)\n", spkg->name, pkg->name); alpm_depmissing_free(miss); - } else if(resolvedep(handle, missdep, (targ = alpm_list_add(NULL, handle->db_local)), rem, 0)) { + } else if(resolvedep(handle, missdep, depstrategy, (targ = alpm_list_add(NULL, handle->db_local)), rem, 0)) { alpm_depmissing_free(miss); } else { handle->pm_errno = ALPM_ERR_UNSATISFIED_DEPS; diff --git a/lib/libalpm/deps.h b/lib/libalpm/deps.h index 4bf6273f..34a8185f 100644 --- a/lib/libalpm/deps.h +++ b/lib/libalpm/deps.h @@ -32,8 +32,8 @@ alpm_list_t *_alpm_sortbydeps(alpm_handle_t *handle, alpm_list_t *targets, alpm_list_t *ignore, int reverse); int _alpm_recursedeps(alpm_db_t *db, alpm_list_t **targs, int include_explicit); int _alpm_resolvedeps(alpm_handle_t *handle, alpm_list_t *localpkgs, alpm_pkg_t *pkg, - alpm_list_t *preferred, alpm_list_t **packages, alpm_list_t *remove, - alpm_list_t **data); + alpm_depstrategy_t depstrategy, alpm_list_t *preferred, alpm_list_t **packages, + alpm_list_t *remove, alpm_list_t **data); int _alpm_depcmp_literal(alpm_pkg_t *pkg, alpm_depend_t *dep); int _alpm_depcmp_provides(alpm_depend_t *dep, alpm_list_t *provisions); int _alpm_depcmp(alpm_pkg_t *pkg, alpm_depend_t *dep); diff --git a/lib/libalpm/sync.c b/lib/libalpm/sync.c index cf436a84..fe89dbc4 100644 --- a/lib/libalpm/sync.c +++ b/lib/libalpm/sync.c @@ -362,7 +362,7 @@ finish: return ret; } -int _alpm_sync_prepare(alpm_handle_t *handle, alpm_list_t **data) +int _alpm_sync_prepare(alpm_handle_t *handle, alpm_depstrategy_t depstrategy, alpm_list_t **data) { alpm_list_t *i, *j; alpm_list_t *deps = NULL; @@ -424,7 +424,7 @@ int _alpm_sync_prepare(alpm_handle_t *handle, alpm_list_t **data) building up a list of packages which could not be resolved. */ for(i = trans->add; i; i = i->next) { alpm_pkg_t *pkg = i->data; - if(_alpm_resolvedeps(handle, localpkgs, pkg, trans->add, + if(_alpm_resolvedeps(handle, localpkgs, pkg, depstrategy, trans->add, &resolved, remove, data) == -1) { unresolvable = alpm_list_add(unresolvable, pkg); } diff --git a/lib/libalpm/sync.h b/lib/libalpm/sync.h index 26e57cdf..c0cefc6e 100644 --- a/lib/libalpm/sync.h +++ b/lib/libalpm/sync.h @@ -24,7 +24,7 @@ #include "alpm.h" -int _alpm_sync_prepare(alpm_handle_t *handle, alpm_list_t **data); +int _alpm_sync_prepare(alpm_handle_t *handle, alpm_depstrategy_t depstrategy, alpm_list_t **data); int _alpm_sync_load(alpm_handle_t *handle, alpm_list_t **data); int _alpm_sync_check(alpm_handle_t *handle, alpm_list_t **data); int _alpm_sync_commit(alpm_handle_t *handle); diff --git a/lib/libalpm/trans.c b/lib/libalpm/trans.c index 7048a059..d41115bf 100644 --- a/lib/libalpm/trans.c +++ b/lib/libalpm/trans.c @@ -108,6 +108,10 @@ static alpm_list_t *check_arch(alpm_handle_t *handle, alpm_list_t *pkgs) int SYMEXPORT alpm_trans_prepare(alpm_handle_t *handle, alpm_list_t **data) { + return alpm_trans_prepare_ex(handle, ALPM_DEPSTRATEGY_DEFAULT, data); +} + +int SYMEXPORT alpm_trans_prepare_ex(alpm_handle_t *handle, alpm_depstrategy_t depstrategy, alpm_list_t **data) { alpm_trans_t *trans; /* Sanity checks */ @@ -138,7 +142,7 @@ int SYMEXPORT alpm_trans_prepare(alpm_handle_t *handle, alpm_list_t **data) return -1; } } else { - if(_alpm_sync_prepare(handle, data) == -1) { + if(_alpm_sync_prepare(handle, depstrategy, data) == -1) { /* pm_errno is set by _alpm_sync_prepare() */ return -1; } diff --git a/src/pacman/conf.c b/src/pacman/conf.c index 8396d02d..8c519d84 100644 --- a/src/pacman/conf.c +++ b/src/pacman/conf.c @@ -115,6 +115,7 @@ config_t *config_new(void) newconfig->remotefilesiglevel = ALPM_SIG_USE_DEFAULT; } + newconfig->depstrategy = ALPM_DEPSTRATEGY_DEFAULT; /* by default use 1 download stream */ newconfig->parallel_downloads = 1; newconfig->colstr.colon = ":: "; @@ -745,6 +746,17 @@ static int _parse_options(const char *key, char *value, } config->parallel_downloads = number; + } else if(strcmp(key, "DependencyStrategy") == 0) { + if(strcmp(value, "Default") == 0) { + config->depstrategy = ALPM_DEPSTRATEGY_DEFAULT; + } else if(strcmp(value, "Strict") == 0) { + config->depstrategy = ALPM_DEPSTRATEGY_STRICT; + } else { + pm_printf(ALPM_LOG_ERROR, + _("config file %s, line %d: invalid value for '%s': '%s'\n"), + file, linenum, "DependencyStrategy", value); + return 1; + } } else { pm_printf(ALPM_LOG_WARNING, _("config file %s, line %d: directive '%s' in section '%s' not recognized.\n"), diff --git a/src/pacman/conf.h b/src/pacman/conf.h index 9b1beff0..c0fc884d 100644 --- a/src/pacman/conf.h +++ b/src/pacman/conf.h @@ -128,6 +128,7 @@ typedef struct __config_t { char *xfercommand; char **xfercommand_argv; size_t xfercommand_argc; + alpm_depstrategy_t depstrategy; /* our connection to libalpm */ alpm_handle_t *handle; diff --git a/src/pacman/remove.c b/src/pacman/remove.c index 98f8bcda..b584a4bc 100644 --- a/src/pacman/remove.c +++ b/src/pacman/remove.c @@ -107,7 +107,7 @@ int pacman_remove(alpm_list_t *targets) } /* Step 2: prepare the transaction based on its type, targets and flags */ - if(alpm_trans_prepare(config->handle, &data) == -1) { + if(alpm_trans_prepare_ex(config->handle, ALPM_DEPSTRATEGY_DEFAULT, &data) == -1) { alpm_errno_t err = alpm_errno(config->handle); pm_printf(ALPM_LOG_ERROR, _("failed to prepare transaction (%s)\n"), alpm_strerror(err)); diff --git a/src/pacman/sync.c b/src/pacman/sync.c index 4a01d402..ba5bb4c5 100644 --- a/src/pacman/sync.c +++ b/src/pacman/sync.c @@ -622,7 +622,8 @@ cleanup: static int process_targname(alpm_list_t *dblist, const char *targname, int error) { - alpm_pkg_t *pkg = alpm_find_dbs_satisfier(config->handle, dblist, targname); + alpm_pkg_t *pkg = alpm_find_dbs_satisfier_ex(config->handle, dblist, + config->depstrategy, targname); /* skip ignored packages when user says no */ if(alpm_errno(config->handle) == ALPM_ERR_PKG_IGNORED) { @@ -755,7 +756,7 @@ int sync_prepare_execute(void) int retval = 0; /* Step 2: "compute" the transaction based on targets and flags */ - if(alpm_trans_prepare(config->handle, &data) == -1) { + if(alpm_trans_prepare_ex(config->handle, config->depstrategy, &data) == -1) { alpm_errno_t err = alpm_errno(config->handle); pm_printf(ALPM_LOG_ERROR, _("failed to prepare transaction (%s)\n"), alpm_strerror(err));