aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-09-16 16:12:31 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-09-16 16:12:31 +0200
commitac6df7a77682bf33b486d451c67ed9650bd9bc2f (patch)
tree144c60ce90390e0eb07f73843606daab61753758
parent829c0083b1684e7513e0bd1bb0a92f767ba83f77 (diff)
Implement pkg-status, pkg-purge commands; start ad-hoc test
-rw-r--r--bpkg/bpkg-options.cli14
-rw-r--r--bpkg/bpkg.cxx26
-rw-r--r--bpkg/buildfile4
-rw-r--r--bpkg/package6
-rw-r--r--bpkg/pkg-purge17
-rw-r--r--bpkg/pkg-purge-options.cli52
-rw-r--r--bpkg/pkg-purge.cxx175
-rw-r--r--bpkg/pkg-status17
-rw-r--r--bpkg/pkg-status-options.cli34
-rw-r--r--bpkg/pkg-status.cxx85
-rwxr-xr-xbpkg/test.sh177
11 files changed, 605 insertions, 2 deletions
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
+ {
+ "<pkg>", // [<ver>]: 24
+ "Print package status.",
+ ""
+ };
+
bool pkg-fetch
{
"<pkg> <ver>",
@@ -42,6 +49,13 @@ namespace bpkg
""
};
+ bool pkg-purge
+ {
+ "<pkg>",
+ "Purge package.",
+ ""
+ };
+
bool cfg-create
{
"[<conf>]",
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 <bpkg/help>
#include <bpkg/pkg-verify>
+#include <bpkg/pkg-status>
#include <bpkg/pkg-fetch>
#include <bpkg/pkg-unpack>
+#include <bpkg/pkg-purge>
#include <bpkg/cfg-create>
#include <bpkg/rep-create>
@@ -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<pkg_status_options> (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<pkg_purge_options> (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<path> 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<dir_path> 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 <bpkg/types>
+#include <bpkg/pkg-purge-options>
+
+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 <bpkg/common-options.cli>;
+
+/*
+"\section=1"
+"\name=bpkg-pkg-purge"
+
+"\h{SYNOPSIS}
+
+bpkg pkg-purge [<options>] <pkg>"
+
+"\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 (".")
+ {
+ "<dir>",
+ "Assume configuration is in <dir> 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 <bpkg/pkg-purge>
+
+#include <memory> // shared_ptr
+
+#include <bpkg/types>
+#include <bpkg/package>
+#include <bpkg/package-odb>
+#include <bpkg/utility>
+#include <bpkg/database>
+#include <bpkg/diagnostics>
+
+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<package> p (db.find<package> (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<dir_path> ();
+ 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<path> ();
+ 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 <bpkg/types>
+#include <bpkg/pkg-status-options>
+
+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 <bpkg/common-options.cli>;
+
+/*
+"\section=1"
+"\name=bpkg-pkg-status"
+
+"\h{SYNOPSIS}
+
+bpkg pkg-status <pkg> [<ver>]"
+
+"\h{DESCRIPTION}
+
+The \cb{pkg-status} command prints the status of the specified
+package or, if the <ver> 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 (".")
+ {
+ "<dir>",
+ "Assume configuration is in <dir> 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 <bpkg/pkg-status>
+
+#include <iostream> // cout
+
+#include <bpkg/types>
+#include <bpkg/package>
+#include <bpkg/package-odb>
+#include <bpkg/utility>
+#include <bpkg/database>
+#include <bpkg/diagnostics>
+
+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<package>;
+ 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<package> p (db.query_one<package> (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