From 09490cd2433e3c37a0d85220b4f12f01e6740d46 Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Fri, 22 Feb 2019 22:26:56 +0000 Subject: [PATCH] support for package dependencies --- Makefile | 3 +- doc/zpm-pkgdeps.8 | 77 +++++++++++++++++++++++++++ doc/zpm.8 | 5 +- lib/jsw/jsw_atree.c | 23 ++++++-- lib/jsw/jsw_atree.h | 2 + schema/main.sql | 8 +-- src/search.c | 124 ++++++++++++++++++++++++++++++++++++++++---- t/basics.t | 14 ++++- zpm-gc | 8 +-- zpm-pkgdeps | 71 +++++++++++++++++++++++++ zpm-update | 2 + zpm.h | 1 + 12 files changed, 314 insertions(+), 24 deletions(-) create mode 100644 doc/zpm-pkgdeps.8 create mode 100755 zpm-pkgdeps diff --git a/Makefile b/Makefile index 9691276..802360b 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,8 @@ ZPKGBIN=zpm-addfile zpm-extract zpm-init zpm-vercmp zpm-stat zpm-hash \ SCRIPTS=zpm zpm-install zpm-merge zpm-list zpm-test zpm-log \ zpm-contents zpm-uninstall zpm-pathmod zpm-rmpackage zpm-newpackage \ - zpm-pkg zpm-pkgfile zpm-gc zpm-repo zpm-update zpm-confgit + zpm-pkg zpm-pkgfile zpm-gc zpm-repo zpm-update zpm-confgit \ + zpm-pkgdeps MANPAGES=$(shell ls doc/*.8) #MANPAGES=doc/zpm.8 $(addprefix doc/zpm-, list.8 contents.8 hash.8 quote.8 pathmod.8 note.8 vercmp.8 repo.8 gc.8) diff --git a/doc/zpm-pkgdeps.8 b/doc/zpm-pkgdeps.8 new file mode 100644 index 0000000..cdda376 --- /dev/null +++ b/doc/zpm-pkgdeps.8 @@ -0,0 +1,77 @@ +.TH zpm-pkgdeps 8 2019-02-22 "ZPM 0.3" +.SH NAME +zpm-pkgdeps \- run pkgdeps +.SH SYNOPSIS + f) pkgfile="$OPTARG" ;; + s) setlist="$setlist $OPTARG"; clearlist=1 ;; + a) add="$add $OPTARG"; ;; + r) remove="$remove $OPTARG" ;; + q) quiet=1 ;; + c) clearlist=1 ;; +.B zpm pkgdeps +[ +.BI \-f " pkgfile" +] +[ +.B \-qc +] +[ +.BI \-s " dep" +] +[ +.BI \-a " dep" +] +[ +.BI \-r " dep" +] +.RI [ package ...] +.SH DESCRIPTION +\fBzpm-pkgdeps\fR +manipulates and prints the set of package dependencies of a package. +The non-argument \fIpackage\fR is a full or partial package id, +and will be looked as if by zpm-findpkg. A package dependency +is a full or partial package id, which is taken as a minimum package +version. After any updates are made, the (new) set of dependencies +is printed to stdout on one line. +.PP +Actions to set, add, or remove dependencies are done in the order +of clearing the set, adding dependencies, and then removing dependencies. +It is not an error if a dependency to remove is not present. +.PP +Dependencies are package ids, and thus can't have whitespace or illegal +characters. Illegal characters are not checked for, and whitespace +will be interpreted as separating dependencies. +.SH OPTIONS +.TP +.BI \-f package +specify the package file to find packages in +.TP +.B \-q +Suppress printing the set of package deps. +.TP +.B \-c +Clear the set of dependencies. +.TP +.BI \-s " dep" +Add a dependency to the set of dependencies to set. Implies \-c. +.TP +.BI \-a " dep" +Add a dependency to the package. +.TP +.BI \-r " dep" +Remove a dependency from the package. +.SH EXAMPLES +.TP +.B zpm pkgdeps vim +List dependencies of the vim package. +.SH EXIT STATUS +0 on success non zero on failure +.SH FILES +/var/lib/zpm/local.db +.SH ENVIRONMENT +ZPMDB +.SH AUTHOR +Nathan Wagner +.SH SEE ALSO +.BR zpm (8) +.BR zpm-findpkg (8) diff --git a/doc/zpm.8 b/doc/zpm.8 index ae923db..1ad54f8 100644 --- a/doc/zpm.8 +++ b/doc/zpm.8 @@ -1,4 +1,4 @@ -.TH zpm 8 2019-02-15 "ZPM 0.3" +.TH zpm 8 2019-02-22 "ZPM 0.3" .SH NAME zpm \- the ultimate package manager .SH SYNOPSIS @@ -92,6 +92,9 @@ from http://git.tukaani.org/xz.git .TP SSL root certificates taken from http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt +.TP +JSW data structures code. +Adapted from JSW's data structures code at http://www.eternallyconfuzzled.com/ .PP All other code written by Nathan Wagner and also placed in the public domain. .SH LICENSE diff --git a/lib/jsw/jsw_atree.c b/lib/jsw/jsw_atree.c index 82c24ca..d73e22a 100644 --- a/lib/jsw/jsw_atree.c +++ b/lib/jsw/jsw_atree.c @@ -1,3 +1,4 @@ +#define _POSIX_C_SOURCE 200809L /* Andersson tree library @@ -66,6 +67,10 @@ struct jsw_atrav { } \ } while(0) +int jsw_afind_strcmp(const void *a, const void *b) { + return strcmp(a, b); +} + static jsw_anode_t *new_node ( jsw_atree_t *tree, void *data ) { jsw_anode_t *rn = malloc ( sizeof *rn ); @@ -80,6 +85,10 @@ static jsw_anode_t *new_node ( jsw_atree_t *tree, void *data ) return rn; } +jsw_atree_t *jsw_anew_str(void) { + return jsw_anew(jsw_afind_strcmp, (dup_f)strdup, (rel_f)free); +} + jsw_atree_t *jsw_anew ( cmp_f cmp, dup_f dup, rel_f rel ) { jsw_atree_t *rt = malloc ( sizeof *rt ); @@ -108,10 +117,16 @@ jsw_atree_t *jsw_anew ( cmp_f cmp, dup_f dup, rel_f rel ) return rt; } -void jsw_adelete ( jsw_atree_t *tree ) -{ - jsw_anode_t *it = tree->root; - jsw_anode_t *save; +void jsw_adelete(jsw_atree_t *tree) { + jsw_anode_t *it; + jsw_anode_t *save; + + if (!tree) { + return; + } + + it = tree->root; + /* Destruction by rotation */ while ( it != tree->nil ) { diff --git a/lib/jsw/jsw_atree.h b/lib/jsw/jsw_atree.h index 7274ba0..6dccf07 100644 --- a/lib/jsw/jsw_atree.h +++ b/lib/jsw/jsw_atree.h @@ -39,11 +39,13 @@ jsw_atree_t *jsw_anew ( void *(dup)(void *), void (*rel)(void *) ); +jsw_atree_t *jsw_anew_str(void); void jsw_adelete ( jsw_atree_t *tree ); void *jsw_afind ( jsw_atree_t *tree, void *data ); int jsw_ainsert ( jsw_atree_t *tree, void *data ); int jsw_aerase ( jsw_atree_t *tree, void *data ); size_t jsw_asize ( jsw_atree_t *tree ); +int jsw_afind_strcmp(const void *a, const void *b); /* Traversal functions */ jsw_atrav_t *jsw_atnew ( void ); diff --git a/schema/main.sql b/schema/main.sql index b067550..85546d9 100644 --- a/schema/main.sql +++ b/schema/main.sql @@ -325,13 +325,15 @@ select printf('%s-%s-%s', package, version, release) as pkgid, * from scripts ; --- package dependencies: table of package, dependency, dep type (package, soname) +-- package dependencies: table of package, dependency, dep type (package, + -- soname) +-- how to specify min/max/exact create table packagedeps ( package text, version text, release integer, - requires text, -- package, can be partial - primary key (package,version,release,package), + requires text, -- package, can be partial, minimum + primary key (package,version,release,requires), foreign key (package,version,release) references packages (package,version,release) on delete cascade on update cascade ); diff --git a/src/search.c b/src/search.c index fd8d793..1943f3b 100644 --- a/src/search.c +++ b/src/search.c @@ -62,7 +62,7 @@ char *pathcat(char *dir, char *path) { return cat; } -char *checkfile(char *pkgstr, char *path) { +char *checkfile(char *pkgstr, char *path, int orgreater) { struct zpm pkgfile; char *pkgid = 0; @@ -70,7 +70,11 @@ char *checkfile(char *pkgstr, char *path) { return NULL; } - pkgid = zpm_findpkg(&pkgfile, pkgstr, NULL); + if (orgreater) { + pkgid = zpm_findpkg_range(&pkgfile, pkgstr, 0, 0, 0); + } else { + pkgid = zpm_findpkg(&pkgfile, pkgstr, NULL); + } zpm_close(&pkgfile); return pkgid; @@ -146,6 +150,9 @@ int find_globs(struct search_ctl *opt) { return 1; } +/* + * find a package and packagefile that supplies a library + */ int find_lib(char *soname, struct search_ctl *opt, struct pkgloc *pkg) { char *latest = 0; char *installed = 0, *found = 0; @@ -193,6 +200,69 @@ int find_lib(char *soname, struct search_ctl *opt, struct pkgloc *pkg) { return 0; } +struct pkgloc *find_atleast_package(char *pkgstr, struct search_ctl *opt, int stopifinstalled) { + char *latest = 0; + char *installed = 0, *found = 0; + char *pkgfile = 0; + struct pkgloc *pkg = 0; + int rv; + unsigned int i; + + if (opt->localdb) { + installed = zpm_findpkg_range(opt->zpmdb, pkgstr, 0, "status = 'installed'", 0); + if (installed) { + latest = installed; + pkgfile = opt->zpmdb->path; + if (stopifinstalled) { + pkg = malloc(sizeof *pkg); + pkg->id = strdup(latest); + pkg->file = pkgfile; + pkg->installed = 1; + return pkg; + } + } + if (!opt->onlylocalinstalled) { + found = zpm_findpkg_range(opt->zpmdb, pkgstr, 0, NULL, 0); + if (found) { + rv = zpm_vercmp(found, latest); + if (rv == 1) { + latest = found; + pkgfile = opt->zpmdb->path; + } + } + } + } + + for (i = 0; i < opt->repos.gl_pathc; i++) { + if (!opt->matchallpkgfile + && !strstr(opt->repos.gl_pathv[i], pkgstr) + && !strstr(opt->repos.gl_pathv[i], ".repo") + ) { + continue; + } + found = checkfile(pkgstr, opt->repos.gl_pathv[i], 1); + if (found) { + rv = zpm_vercmp(found, latest); + if (rv == 1) { + latest = found; + pkgfile = strdup(opt->repos.gl_pathv[i]); + } + } + } + + if (latest && pkgfile) { + pkg = malloc(sizeof *pkg); + pkg->id = strdup(latest); + pkg->file = pkgfile; + pkg->installed = 0; + if (installed) { + pkg->installed = !strcmp(latest, installed); + } + } + + return pkg; +} + struct pkgloc *find_package(char *pkgstr, struct search_ctl *opt) { char *latest = 0; char *installed = 0, *found = 0; @@ -232,7 +302,7 @@ struct pkgloc *find_package(char *pkgstr, struct search_ctl *opt) { ) { continue; } - found = checkfile(pkgstr, opt->repos.gl_pathv[i]); + found = checkfile(pkgstr, opt->repos.gl_pathv[i], 0); if (found) { rv = zpm_vercmp(found, latest); if (rv == 1) { @@ -272,6 +342,8 @@ with pkglibs as (\ where PL.pkgid = %Q\ "; +char *pkgdeps = "select requires from packagedeps_pkgid where pkgid = %Q"; + static int afind_strcmp(const void *a, const void *b) { return strcmp(a, b); } @@ -283,13 +355,13 @@ void checklibs(struct search_ctl *opts, /* checked_libs is a list of checked sonames * checked is a list of checked packages - * needed is list of libs for package + * needed is list of libs for package, or list of package deps */ jsw_atree_t *needed = 0, *checked, *checked_libs; - int libs; + int libs, count; jsw_atrav_t *i; - char *soname; + char *soname, *package; checked = jsw_anew(afind_strcmp, (dup_f)strdup, (rel_f)free); checked_libs = jsw_anew(afind_strcmp, (dup_f)strdup, (rel_f)free); @@ -328,14 +400,45 @@ void checklibs(struct search_ctl *opts, exit(EXIT_FAILURE); } - /* get the libraries needed by this package */ + /* get the libraries and packages needed by this package */ if (!zpm_open(&src, pkgfile)) { fprintf(stderr, "can't zpm open %s\n", pkgfile); break; } - if (needed) jsw_adelete(needed); - needed = jsw_anew(afind_strcmp, (dup_f)strdup, (rel_f)free); + jsw_adelete(needed); + needed = jsw_anew_str(); + count = zpm_packages_needed(&src, pkgid, needed); + if (count) { + struct pkgloc *pkg = 0; + i = jsw_atnew(); + + if (pkg) { + free(pkg->file); + free(pkg->id); + free(pkg); + } + + for (package = jsw_atfirst(i, needed); package; package = jsw_atnext(i)) { + pkg = find_atleast_package(package, opts, 1); + if (!pkg || pkg->installed) { + continue; + } + /* look for a package at least that version */ + /* if package is installed, or already + * handled, skip. Otherwise, add it + * to the list of installed + */ + } + if (pkg) { + free(pkg->file); + free(pkg->id); + free(pkg); + } + } + + jsw_adelete(needed); + needed = jsw_anew_str(); libs = zpm_libraries_needed(&src, pkgid, needed); zpm_close(&src); @@ -349,7 +452,8 @@ void checklibs(struct search_ctl *opts, int found; struct pkgloc pkginfo; - /* if it's in checked_libs, we've already looked at this one */ + /* if it's in checked_libs, we've already looked at + * this one */ if (jsw_afind(checked_libs, soname)) { if (opts->verbose > 1) { fprintf(stderr, "already checked %s\n", soname); diff --git a/t/basics.t b/t/basics.t index 34aef28..3f926f5 100755 --- a/t/basics.t +++ b/t/basics.t @@ -2,7 +2,7 @@ . tap.sh -plan 15 +plan 20 PF=test.db @@ -36,6 +36,18 @@ okstreq "$val" 'baz' newpackage set description val=$(zpm pkg -f $PF valset url) okstreq "$val" 'quux' newpackage set url +# package dependencies +deps=$(zpm pkgdeps -f $PF valset) +okstreq "$deps" '' new package empty deps +deps=$(zpm pkgdeps -f $PF -s foo valset) +okstreq "$deps" 'foo' pkgdeps set and print +deps=$(zpm pkgdeps -f $PF -c valset) +okstreq "$deps" '' pkgdeps clear +deps=$(zpm pkgdeps -f $PF -q -s foo valset) +okstreq "$deps" '' pkgdeps set and quiet +deps=$(zpm pkgdeps -f $PF valset) +okstreq "$deps" 'foo' pkgdeps set + cd .. rm -rf tmp finish diff --git a/zpm-gc b/zpm-gc index d2d49fc..2559f0d 100755 --- a/zpm-gc +++ b/zpm-gc @@ -82,12 +82,12 @@ export ZPMDB # remove failed packages for pkg in $(zpm list -s failed); do zpm rmpackage -m 'gc removed failed' "$pkg" - echo removed $pkg + echo removed failed $pkg done for pkg in $(zpm list -s dryrun); do zpm rmpackage -m 'gc removed dryrun' "$pkg" - echo removed $pkg + echo removed dryrun $pkg done # remove incomplete packages @@ -95,7 +95,7 @@ done if [ $remove_incomplete_packages -ne 0 ]; then for pkg in $(zpm list -F 'hash is null'); do zpm rmpackage -m 'gc removed incomplete' $pkg - echo removed $pkg + echo removed incomplete $pkg done fi @@ -104,7 +104,7 @@ fi if [ $remove_old_packages -ne 0 ]; then zpm shell $ZPMDB "select printf('%s-%s-%s',package,version,release) from package_age where age > $retain_old" | while read pkg; do zpm rmpackage -m 'gc removed old package' $pkg - echo removed $pkg + echo removed old package $pkg done fi diff --git a/zpm-pkgdeps b/zpm-pkgdeps new file mode 100755 index 0000000..b1ea0ce --- /dev/null +++ b/zpm-pkgdeps @@ -0,0 +1,71 @@ +#!/bin/sh + +add= +remove= +setlist= +quiet=0 +clearlist=0 + +while getopts :f:s:a:r:qc opt; do + case $opt in + f) pkgfile="$OPTARG" ;; + s) setlist="$setlist $OPTARG"; clearlist=1 ;; + a) add="$add $OPTARG"; ;; + r) remove="$remove $OPTARG" ;; + q) quiet=1 ;; + c) clearlist=1 ;; + esac +done +shift $(( OPTIND - 1)) + +: ${pkgfile:=${ZPMDB:-/var/lib/zpm/local.db}} + +if [ -z "$pkgfile" ]; then + echo "must specify package file" + exit 1 +fi + +pkgid=$(zpm findpkg -f $pkgfile "$1") +if [ -z "$pkgid" ]; then + echo "cannot find pkgid for $1" + exit 1 +fi + +eval $(zpm parse -E "$pkgid") +package=$(zpm quote -q "$name") +version=$(zpm quote -q "$version") +pkgselector="package = $package and version = $version and release = $release" +listdeps="select requires from packagedeps where %s;\n" +adddep='insert or ignore into packagedeps (package,version,release,requires) values (%s,%s,%d,%s);\n' +rmdep='delete from packagedeps where %s and requires = %s;\n' +cleardeps='delete from packagedeps where %s;\n' + +{ + printf ".bail on\n"; + printf "begin;\n"; +if [ $clearlist -eq 1 ]; then + printf "$cleardeps" "$pkgselector" +fi +if [ -n "$setlist" ]; then + for dep in $setlist; do + qdep=$(zpm quote -q "$dep") + printf "$adddep" "$package" "$version" "$release" "$qdep" + done +fi +if [ -n "$add" ]; then + for dep in $add; do + qdep=$(zpm quote -q "$dep") + printf "$adddep" "$package" "$version" "$release" "$qdep" + done +fi +if [ -n "$remove" ]; then + for dep in $remove; do + qdep=$(zpm quote -q "$dep") + printf "$rmdep" "$pkgselector" "$qdep" + done +fi +if [ $quiet -eq 0 ]; then + printf "$listdeps" "$pkgselector" +fi + printf "commit;\n"; +} | zpm shell $pkgfile diff --git a/zpm-update b/zpm-update index 5ee5008..e1accc8 100755 --- a/zpm-update +++ b/zpm-update @@ -202,6 +202,8 @@ fi if [ $ignorelibdeps -eq 1 ]; then merge=$(zpm search -iIO $search) + # TODO search for libs anyway and put a note for non-installed + # library deps else merge=$(zpm search -iIO -l $search) fi diff --git a/zpm.h b/zpm.h index 157eb37..5eddd56 100644 --- a/zpm.h +++ b/zpm.h @@ -92,6 +92,7 @@ char *zpm_findpkg_range(struct zpm *zpm, char *minpkg, char *maxpkg, char *where int zpm_findhash(struct zpm *zpm, char *find, char *dest); char *zpm_findlib(struct zpm *zpm, char *soname, char *where); int zpm_libraries_needed(struct zpm *zpm, char *pkgid, jsw_atree_t *list); +int zpm_packages_needed(struct zpm *zpm, char *pkgid, jsw_atree_t *list); int zpm_quote(char *value, char *dest, size_t n); struct zpm_file { -- 2.40.0