From ac6df7a77682bf33b486d451c67ed9650bd9bc2f Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 16 Sep 2015 16:12:31 +0200 Subject: Implement pkg-status, pkg-purge commands; start ad-hoc test --- bpkg/bpkg-options.cli | 14 ++++ bpkg/bpkg.cxx | 26 +++++++ bpkg/buildfile | 4 + bpkg/package | 6 +- bpkg/pkg-purge | 17 +++++ bpkg/pkg-purge-options.cli | 52 +++++++++++++ bpkg/pkg-purge.cxx | 175 +++++++++++++++++++++++++++++++++++++++++++ bpkg/pkg-status | 17 +++++ bpkg/pkg-status-options.cli | 34 +++++++++ bpkg/pkg-status.cxx | 85 +++++++++++++++++++++ bpkg/test.sh | 177 ++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 605 insertions(+), 2 deletions(-) create mode 100644 bpkg/pkg-purge create mode 100644 bpkg/pkg-purge-options.cli create mode 100644 bpkg/pkg-purge.cxx create mode 100644 bpkg/pkg-status create mode 100644 bpkg/pkg-status-options.cli create mode 100644 bpkg/pkg-status.cxx create mode 100755 bpkg/test.sh (limited to 'bpkg') diff --git a/bpkg/bpkg-options.cli b/bpkg/bpkg-options.cli index 552a8ab..710b809 100644 --- a/bpkg/bpkg-options.cli +++ b/bpkg/bpkg-options.cli @@ -28,6 +28,13 @@ namespace bpkg "" }; + bool pkg-status + { + "", // []: 24 + "Print package status.", + "" + }; + bool pkg-fetch { " ", @@ -42,6 +49,13 @@ namespace bpkg "" }; + bool pkg-purge + { + "", + "Purge package.", + "" + }; + bool cfg-create { "[]", diff --git a/bpkg/bpkg.cxx b/bpkg/bpkg.cxx index 18d68c9..994f115 100644 --- a/bpkg/bpkg.cxx +++ b/bpkg/bpkg.cxx @@ -15,8 +15,10 @@ // #include #include +#include #include #include +#include #include #include @@ -150,6 +152,18 @@ try return 0; } + // pkg-status + // + if (cmd.pkg_status ()) + { + if (h) + help (ho, "pkg-status", pkg_status_options::print_usage); + else + pkg_status (parse (co, args), args); + + return 0; + } + // pkg-fetch // if (cmd.pkg_fetch ()) @@ -174,6 +188,18 @@ try return 0; } + // pkg-purge + // + if (cmd.pkg_purge ()) + { + if (h) + help (ho, "pkg-purge", pkg_purge_options::print_usage); + else + pkg_purge (parse (co, args), args); + + return 0; + } + // cfg-create // if (cmd.cfg_create ()) diff --git a/bpkg/buildfile b/bpkg/buildfile index b47417a..0d9742d 100644 --- a/bpkg/buildfile +++ b/bpkg/buildfile @@ -14,8 +14,10 @@ exe{bpkg}: cxx{package package-odb database diagnostics utility} \ cxx{bpkg} cli.cxx{bpkg-options} \ cxx{help} cli.cxx{help-options} \ cxx{pkg-verify} cli.cxx{pkg-verify-options} \ + cxx{pkg-status} cli.cxx{pkg-status-options} \ cxx{pkg-fetch} cli.cxx{pkg-fetch-options} \ cxx{pkg-unpack} cli.cxx{pkg-unpack-options} \ + cxx{pkg-purge} cli.cxx{pkg-purge-options} \ cxx{cfg-create} cli.cxx{cfg-create-options} \ cxx{rep-create} cli.cxx{rep-create-options} \ $libs @@ -37,7 +39,9 @@ cli.cxx{bpkg-options}: cli.options += --option-length 22 --short-usage cli.cxx{help-options}: cli{help-options} cli.cxx{pkg-verify-options}: cli{pkg-verify-options} +cli.cxx{pkg-status-options}: cli{pkg-status-options} cli.cxx{pkg-fetch-options}: cli{pkg-fetch-options} cli.cxx{pkg-unpack-options}: cli{pkg-unpack-options} +cli.cxx{pkg-purge-options}: cli{pkg-purge-options} cli.cxx{cfg-create-options}: cli{cfg-create-options} cli.cxx{rep-create-options}: cli{rep-create-options} diff --git a/bpkg/package b/bpkg/package index 0c46300..73c0b27 100644 --- a/bpkg/package +++ b/bpkg/package @@ -104,7 +104,8 @@ namespace bpkg // Path to the archive of this package, if any. If not absolute, // then it is relative to the configuration directory. The purge // flag indicates whether the archive should be removed when the - // packaged is purged. + // packaged is purged. If the archive is not present, it should + // be false. // optional archive; bool archive_purge; @@ -112,7 +113,8 @@ namespace bpkg // Path to the source directory of this package, if any. If not // absolute, then it is relative to the configuration directory. // The purge flag indicates whether the directory should be - // removed when the packaged is purged. + // removed when the packaged is purged. If the source directory + // is not present, it should be false. // optional source; bool source_purge; diff --git a/bpkg/pkg-purge b/bpkg/pkg-purge new file mode 100644 index 0000000..7ec365d --- /dev/null +++ b/bpkg/pkg-purge @@ -0,0 +1,17 @@ +// file : bpkg/pkg-purge -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_PKG_PURGE +#define BPKG_PKG_PURGE + +#include +#include + +namespace bpkg +{ + void + pkg_purge (const pkg_purge_options&, cli::scanner& args); +} + +#endif // BPKG_PKG_PURGE diff --git a/bpkg/pkg-purge-options.cli b/bpkg/pkg-purge-options.cli new file mode 100644 index 0000000..8d7d61b --- /dev/null +++ b/bpkg/pkg-purge-options.cli @@ -0,0 +1,52 @@ +// file : bpkg/pkg-purge-options.cli +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +include ; + +/* +"\section=1" +"\name=bpkg-pkg-purge" + +"\h{SYNOPSIS} + +bpkg pkg-purge [] " + +"\h{DESCRIPTION} + +The \cb{pkg-purge} command removes the package directory and archive +from the filesystem and removes the package from the configuration's +database. Only packages in the \cb{fetched} and \cb{unpacked} state +can be purged plus broken packages if the \cb{-f|--force} options is +specified (see this option's description for details on purging broken +packages). If the \cb{-k|--keep} option is specified, then the package +archive is not removed (see this option's description for details on +this mode)." +*/ + +namespace bpkg +{ + class pkg_purge_options: common_options + { + dir_path --directory|-d (".") + { + "", + "Assume configuration is in rather than in the current working + directory." + }; + + bool --keep|-k + { + "Keep the package archive. Note that in this mode the package is + still retained in the configuration's database in the \cb{fetched} + state." + }; + + bool --force|-f + { + "Purge a broken package. In this mode \cb{bpkg} will verify that + the package directory and archive no longer exist and will remove + the package from the configuration's database." + }; + }; +} diff --git a/bpkg/pkg-purge.cxx b/bpkg/pkg-purge.cxx new file mode 100644 index 0000000..8831b6d --- /dev/null +++ b/bpkg/pkg-purge.cxx @@ -0,0 +1,175 @@ +// file : bpkg/pkg-purge.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // shared_ptr + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace bpkg +{ + void + pkg_purge (const pkg_purge_options& o, cli::scanner& args) + { + tracer trace ("pkg_purge"); + + dir_path c (o.directory ()); + level4 ([&]{trace << "configuration: " << c;}); + + if (!args.more ()) + fail << "package name argument expected" << + info << "run 'bpkg help pkg-purge' for more information"; + + string n (args.next ()); + + database db (open (c)); + transaction t (db.begin ()); + + shared_ptr p (db.find (n)); + + if (p == nullptr) + fail << "package " << n << " does not exist in configuration " << c; + + // Make sure the package is in a state from which it can be purged. + // + switch (p->state) + { + case state::fetched: + { + // If we have --keep, then this is a no-op. We could have + // detected this and returned but we still want the normal + // diagnostics. So instead the logic below takes care of + // this situation. + // + break; + } + case state::unpacked: + { + if (o.keep () && !p->archive) + fail << "package " << n << " has no archive to keep"; + + break; + } + case state::broken: + { + if (!o.force ()) + fail << "broken package " << n << " can only be purged with --force"; + + if (o.keep ()) + fail << "cannot keep broken package " << n; + + break; + } + default: + { + fail << p->state << " package " << n << " cannot be purged"; + } + } + + // First clean up the package source directory. + // + if (p->source_purge) + { + dir_path d (*p->source); + if (d.relative ()) + d = c / d; + + if (p->state != state::broken) + { + try + { + if (exists (d)) // Don't complain if someone did our job for us. + rm_r (d); + + p->source = optional (); + p->source_purge = false; + } + catch (const failed&) + { + p->state = state::broken; + db.update (p); + t.commit (); + + if (verb) + text << "broke " << p->name << " " << p->version; + + throw; + } + } + else + { + // If we are broken, simply make sure the user cleaned things up + // manually. + // + if (exists (d)) + fail << "broken package " << n << " source directory still exists" << + info << "remove " << d << " manually then re-run pkg-purge"; + } + } + + // Now the archive. Pretty much the same code as above but for a file. + // + if (p->archive_purge && !o.keep ()) + { + path a (*p->archive); + if (a.relative ()) + a = c / a; + + if (p->state != state::broken) + { + try + { + if (exists (a)) + rm (a); + + p->archive = optional (); + p->archive_purge = false; + } + catch (const failed&) + { + p->state = state::broken; + db.update (p); + t.commit (); + + if (verb) + text << "broke " << p->name << " " << p->version; + + throw; + } + } + else + { + if (exists (a)) + fail << "broken package " << n << " archive still exists" << + info << "remove " << a << " manually then re-run pkg-purge"; + } + } + + if (o.keep ()) + { + if (p->state != state::fetched) // That no-op we were talking about. + { + p->state = state::fetched; + db.update (p); + } + } + else + db.erase (p); + + t.commit (); + + if (verb) + text << (o.keep () ? "keeping archive " : "purged ") + << p->name << " " << p->version; + } +} diff --git a/bpkg/pkg-status b/bpkg/pkg-status new file mode 100644 index 0000000..98bd014 --- /dev/null +++ b/bpkg/pkg-status @@ -0,0 +1,17 @@ +// file : bpkg/pkg-status -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_PKG_STATUS +#define BPKG_PKG_STATUS + +#include +#include + +namespace bpkg +{ + void + pkg_status (const pkg_status_options&, cli::scanner& args); +} + +#endif // BPKG_PKG_STATUS diff --git a/bpkg/pkg-status-options.cli b/bpkg/pkg-status-options.cli new file mode 100644 index 0000000..700f5fa --- /dev/null +++ b/bpkg/pkg-status-options.cli @@ -0,0 +1,34 @@ +// file : bpkg/pkg-status-options.cli +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +include ; + +/* +"\section=1" +"\name=bpkg-pkg-status" + +"\h{SYNOPSIS} + +bpkg pkg-status []" + +"\h{DESCRIPTION} + +The \cb{pkg-status} command prints the status of the specified +package or, if the argument is provided, package version. +Note that the status is written to \cb{STDOUT}, not \cb{STDERR}. +@@ TODO: output form and possible status values." +*/ + +namespace bpkg +{ + class pkg_status_options: common_options + { + dir_path --directory|-d (".") + { + "", + "Assume configuration is in rather than in the current working + directory." + }; + }; +} diff --git a/bpkg/pkg-status.cxx b/bpkg/pkg-status.cxx new file mode 100644 index 0000000..0d8c474 --- /dev/null +++ b/bpkg/pkg-status.cxx @@ -0,0 +1,85 @@ +// file : bpkg/pkg-status.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // cout + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace bpkg +{ + void + pkg_status (const pkg_status_options& o, cli::scanner& args) + { + tracer trace ("pkg_status"); + + dir_path c (o.directory ()); + level4 ([&]{trace << "configuration: " << c;}); + + if (!args.more ()) + fail << "package name argument expected" << + info << "run 'bpkg help pkg-status' for more information"; + + string n (args.next ()); + + version v; + if (args.more ()) + { + const char* s (args.next ()); + try + { + v = version (s); + } + catch (const invalid_argument& e) + { + fail << "invalid package version '" << s << "'"; + } + } + + database db (open (c)); + transaction t (db.begin ()); + + using query = odb::query; + query q (query::name == n); + + if (!v.empty ()) + { + q = q && (query::version.epoch == v.epoch () && + query::version.revision == v.revision () && + query::version.canonical_upstream == v.canonical_upstream ()); + } + + shared_ptr p (db.query_one (q)); + + if (p == nullptr) + { + // @@ TODO: This is where we search the packages available in + // the repositories and if found, print its status as 'available' + // plus a list of versions. + // + cout << "unknown"; + } + else + { + cout << p->state; + + // Also print the version of the package unless the user + // specified it. + // + if (v.empty ()) + cout << " " << p->version; + } + + cout << endl; + } +} diff --git a/bpkg/test.sh b/bpkg/test.sh new file mode 100755 index 0000000..d71609a --- /dev/null +++ b/bpkg/test.sh @@ -0,0 +1,177 @@ +#! /usr/bin/env bash + +trap 'exit 1' ERR + +bpkg="./bpkg $*" +#bpkg="valgrind -q ./bpkg $*" +cfg=/tmp/conf +pkg=libhello +ver=1.0.0 +pkga=../../hello/dist/$pkg-$ver.tar.bz2 +pkgd=../../hello/dist/$pkg-$ver + +function error () +{ + echo "$*" 1>&2 + exit 1 +} + +function test () +{ + local cmd=$1 + shift + + $bpkg $cmd -d $cfg $* + + if [ $? -ne 0 ]; then + error "failed: $bpkg $cmd -d $cfg $*" + fi +} + +function fail () +{ + local cmd=$1 + shift + + $bpkg $cmd -d $cfg $* + + if [ $? -eq 0 ]; then + error "succeeded: $bpkg $cmd -d $cfg $*" + fi + + return 0 +} + +# Verify package status. +# +function stat () +{ + local s=`$bpkg pkg-status -d $cfg $pkg $ver` + + if [ "$s" != "$1" ]; then + error "status: $s, expected: $1" + fi +} + +# Verify path is gone (no longer exists) +# +function gone () +{ + if [ -e "$1" ]; then + error "path $1 still exists" + fi +} + +## +## cfg-create +## + +test cfg-create --wipe config.cxx=g++-4.9 cxx config.install.root=/tmp/install +stat unknown + +## +## pkg-fetch +## + +# fetch existing archive +# +stat unknown +test pkg-fetch -e $pkga +stat fetched +test pkg-purge $pkg +stat unknown + + +## +## pkg-purge +## + +fail pkg-purge +fail pkg-purge $pkg + +# purge fetched +# +test pkg-fetch -e $pkga +test pkg-purge $pkg +stat unknown + +# --keep +# +test pkg-fetch -e $pkga +test pkg-purge -k $pkg +stat fetched +test pkg-purge $pkg + +# archive --purge +# +cp $pkga $cfg/ +test pkg-fetch -e -p $cfg/`basename $pkga` +test pkg-purge $pkg +stat unknown +gone $cfg/`basename $pkga` + +# no archive but --keep +# +test pkg-unpack -e $pkgd +fail pkg-purge --keep $pkg +stat unpacked +test pkg-purge $pkg + +# purge unpacked directory +# +test pkg-unpack -e $pkgd +test pkg-purge $pkg +stat unknown + +# purge unpacked archive +# +test pkg-fetch -e $pkga +test pkg-unpack $pkg +test pkg-purge $pkg +stat unknown +gone $cfg/`basename $pkgd` + +# purge unpacked archive but --keep +# +test pkg-fetch -e $pkga +test pkg-unpack $pkg +test pkg-purge --keep $pkg +stat fetched +gone $cfg/`basename $pkgd` +test pkg-purge $pkg + +# directory --purge +# +cp -r $pkgd $cfg/ +test pkg-unpack -e -p $cfg/`basename $pkgd` +test pkg-purge $pkg +stat unknown +gone $cfg/`basename $pkgd` + +# archive --purge +# +cp $pkga $cfg/ +test pkg-fetch -e -p $cfg/`basename $pkga` +test pkg-unpack $pkg +test pkg-purge $pkg +stat unknown +gone $cfg/`basename $pkgd` +gone $cfg/`basename $pkga` + +# broken +# +cp $pkga $cfg/ +test pkg-fetch -e -p $cfg/`basename $pkga` +test pkg-unpack $pkg +chmod 000 $cfg/`basename $pkgd` +fail pkg-purge $pkg +stat broken +fail pkg-purge $pkg # need --force +fail pkg-purge -f -k $pkg # can't keep broken +fail pkg-purge -f $pkg # directory still exists +chmod 755 $cfg/`basename $pkgd` +rm -r $cfg/`basename $pkgd` +fail pkg-purge -f $pkg # archive still exists +rm $cfg/`basename $pkga` +test pkg-purge -f $pkg +stat unknown -- cgit v1.1