From 236ad71b105365bedf9d28a5606616fb9aed3168 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 16 Sep 2015 07:16:06 +0200 Subject: Implement pkg-unpack command --- bpkg/bpkg-options.cli | 7 ++ bpkg/bpkg.cxx | 13 +++ bpkg/buildfile | 2 + bpkg/package | 22 ++++- bpkg/package.xml | 2 + bpkg/pkg-fetch.cxx | 20 +++-- bpkg/pkg-unpack | 17 ++++ bpkg/pkg-unpack-options.cli | 44 ++++++++++ bpkg/pkg-unpack.cxx | 205 ++++++++++++++++++++++++++++++++++++++++++++ bpkg/pkg-verify | 6 ++ bpkg/pkg-verify.cxx | 57 ++++++++++++ 11 files changed, 385 insertions(+), 10 deletions(-) create mode 100644 bpkg/pkg-unpack create mode 100644 bpkg/pkg-unpack-options.cli create mode 100644 bpkg/pkg-unpack.cxx diff --git a/bpkg/bpkg-options.cli b/bpkg/bpkg-options.cli index c3bfd24..552a8ab 100644 --- a/bpkg/bpkg-options.cli +++ b/bpkg/bpkg-options.cli @@ -35,6 +35,13 @@ namespace bpkg "" }; + bool pkg-unpack + { + "", + "Unpack package archive.", + "" + }; + bool cfg-create { "[]", diff --git a/bpkg/bpkg.cxx b/bpkg/bpkg.cxx index d1ee945..18d68c9 100644 --- a/bpkg/bpkg.cxx +++ b/bpkg/bpkg.cxx @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -161,6 +162,18 @@ try return 0; } + // pkg-unpack + // + if (cmd.pkg_unpack ()) + { + if (h) + help (ho, "pkg-unpack", pkg_unpack_options::print_usage); + else + pkg_unpack (parse (co, args), args); + + return 0; + } + // cfg-create // if (cmd.cfg_create ()) diff --git a/bpkg/buildfile b/bpkg/buildfile index 0d00a9f..b47417a 100644 --- a/bpkg/buildfile +++ b/bpkg/buildfile @@ -15,6 +15,7 @@ exe{bpkg}: cxx{package package-odb database diagnostics utility} \ cxx{help} cli.cxx{help-options} \ cxx{pkg-verify} cli.cxx{pkg-verify-options} \ cxx{pkg-fetch} cli.cxx{pkg-fetch-options} \ + cxx{pkg-unpack} cli.cxx{pkg-unpack-options} \ cxx{cfg-create} cli.cxx{cfg-create-options} \ cxx{rep-create} cli.cxx{rep-create-options} \ $libs @@ -37,5 +38,6 @@ cli.cxx{help-options}: cli{help-options} cli.cxx{pkg-verify-options}: cli{pkg-verify-options} cli.cxx{pkg-fetch-options}: cli{pkg-fetch-options} +cli.cxx{pkg-unpack-options}: cli{pkg-unpack-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 8c1e047..0c46300 100644 --- a/bpkg/package +++ b/bpkg/package @@ -37,16 +37,24 @@ namespace bpkg { // path // + using optional_string = optional; + using optional_path = optional; + using optional_dir_path = optional; + #pragma db map type(path) as(string) \ to((?).string ()) from(bpkg::path (?)) - using optional_path = optional; - using optional_string = optional; - #pragma db map type(optional_path) as(bpkg::optional_string) \ to((?) ? (?)->string () : bpkg::optional_string ()) \ from((?) ? bpkg::path (*(?)) : bpkg::optional_path ()) + #pragma db map type(dir_path) as(string) \ + to((?).string ()) from(bpkg::dir_path (?)) + + #pragma db map type(optional_dir_path) as(bpkg::optional_string) \ + to((?) ? (?)->string () : bpkg::optional_string ()) \ + from((?) ? bpkg::dir_path (*(?)) : bpkg::optional_dir_path ()) + // version // #pragma db map type(version) as(_version) \ @@ -101,6 +109,14 @@ namespace bpkg optional archive; bool archive_purge; + // 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. + // + optional source; + bool source_purge; + // Database mapping. // #pragma db member(name) id diff --git a/bpkg/package.xml b/bpkg/package.xml index 22f71e7..5140717 100644 --- a/bpkg/package.xml +++ b/bpkg/package.xml @@ -9,6 +9,8 @@ + + diff --git a/bpkg/pkg-fetch.cxx b/bpkg/pkg-fetch.cxx index 9d82446..7558862 100644 --- a/bpkg/pkg-fetch.cxx +++ b/bpkg/pkg-fetch.cxx @@ -88,11 +88,11 @@ namespace bpkg // if (shared_ptr p = db.find (n)) fail << "package " << n << " already exists in configuration " << d << - info << "existing version: " << p->version << ", state: " << p->state; + info << "version: " << p->version << ", state: " << p->state; // Make the archive and configuration paths absolute and normalized. // If the archive is inside the configuration, use the relative path. - // This way we can move configuration around. + // This way we can move the configuration around. // d.complete ().normalize (); a.complete ().normalize (); @@ -102,13 +102,19 @@ namespace bpkg // Add the package to the configuration. // - package p {move (m.name), - move (m.version), - state::fetched, - move (a), - purge}; + shared_ptr p (new package { + move (m.name), + move (m.version), + state::fetched, + move (a), + purge, + optional (), // No source directory yet. + false}); db.persist (p); t.commit (); + + if (verb) + text << "fetched " << p->name << " " << p->version; } } diff --git a/bpkg/pkg-unpack b/bpkg/pkg-unpack new file mode 100644 index 0000000..4a37c35 --- /dev/null +++ b/bpkg/pkg-unpack @@ -0,0 +1,17 @@ +// file : bpkg/pkg-unpack -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_PKG_UNPACK +#define BPKG_PKG_UNPACK + +#include +#include + +namespace bpkg +{ + void + pkg_unpack (const pkg_unpack_options&, cli::scanner& args); +} + +#endif // BPKG_PKG_UNPACK diff --git a/bpkg/pkg-unpack-options.cli b/bpkg/pkg-unpack-options.cli new file mode 100644 index 0000000..0fa77a3 --- /dev/null +++ b/bpkg/pkg-unpack-options.cli @@ -0,0 +1,44 @@ +// file : bpkg/pkg-unpack-options.cli +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +include ; + +/* +"\section=1" +"\name=bpkg-pkg-unpack" + +"\h{SYNOPSIS} + +bpkg pkg-unpack [] |(-e )" + +"\h{DESCRIPTION} + +The \cb{pkg-unpack} command unpacks the archive for the previously +fetched (\cb{pkg-fetch}) package. If the \cb{-e|--existing} option +is used, then instead of the package name, \cb{pkg-unpack} expects +a local path to the existing package source directory. In this case, +\cb{bpkg} will use the directory in place, without copying it to the +configuration or package cache directories. It will also not attempt +to remove this directory if the package is purged with the \cb{pkg-purge} +command." +*/ + +namespace bpkg +{ + class pkg_unpack_options: common_options + { + dir_path --directory|-d (".") + { + "", + "Assume configuration is in rather than in the current working + directory." + }; + + bool --existing|-e + { + "Treat the argument as an existing package directory path rather than + package name to unpack." + }; + }; +} diff --git a/bpkg/pkg-unpack.cxx b/bpkg/pkg-unpack.cxx new file mode 100644 index 0000000..8b08674 --- /dev/null +++ b/bpkg/pkg-unpack.cxx @@ -0,0 +1,205 @@ +// file : bpkg/pkg-unpack.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 +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace bpkg +{ + static shared_ptr + pkg_unpack (database& db, const dir_path& c, const dir_path& d) + { + tracer trace ("pkg_unpack(dir)"); + + if (!exists (d)) + fail << "package directory " << d << " does not exist"; + + // Verify the directory is a package and get its manifest. + // + package_manifest m (pkg_verify (d)); + level4 ([&]{trace << d << ": " << m.name << " " << m.version;}); + + const auto& n (m.name); + + transaction t (db.begin ()); + + // See if this package already exists in this configuration. + // + if (shared_ptr p = db.find (n)) + fail << "package " << n << " already exists in configuration " << c << + info << "version: " << p->version << ", state: " << p->state; + + // Make the package and configuration paths absolute and normalized. + // If the package is inside the configuration, use the relative path. + // This way we can move the configuration around. + // + dir_path ac (c), ad (d); + ac.complete ().normalize (); + ad.complete ().normalize (); + + if (ad.sub (ac)) + ad = ad.leaf (ac); + + // Add the package to the configuration. + // + shared_ptr p (new package { + move (m.name), + move (m.version), + state::unpacked, + optional (), // No archive + false, // Don't purge archive. + move (ad), + false}); // Don't purge source. + + db.persist (p); + t.commit (); + + return p; + } + + static shared_ptr + pkg_unpack (database& db, const dir_path& c, const string& name) + { + tracer trace ("pkg_unpack(pkg)"); + + transaction t (db.begin ()); + shared_ptr p (db.find (name)); + + if (p == nullptr) + fail << "package " << name << " does not exist in configuration " << c; + + if (p->state != state::fetched) + fail << "package " << name << " is already in " << p->state << " state"; + + level4 ([&]{trace << p->name << " " << p->version;}); + + assert (p->archive); // Should have archive in the fetched state. + + // If the archive path is not absolute, then it must be relative + // to the configuration. + // + path a (*p->archive); + if (a.relative ()) + a = c / a; + + level4 ([&]{trace << "archive: " << a;}); + + // Extract the package directory. Currently we always extract it + // into the configuration directory. But once we support package + // cache, this will need to change. + // + // Also, since we must have verified the archive during fetch, + // here we can just assume what the resulting directory will be. + // + dir_path d (c / dir_path (p->name + '-' + p->version.string ())); + + if (exists (d)) + fail << "package directory " << d << " already exists"; + + const char* args[] { + "tar", + "-C", c.string ().c_str (), // -C/--directory -- change to directory. + "-xf", + a.string ().c_str (), + nullptr}; + + if (verb >= 2) + print_process (args); + + // What should we do if tar or something after it fails? Cleaning + // up the package directory sounds like the right thing to do. + // + auto dg ( + make_exception_guard ( + [&d]() + { + if (exists (d)) + rm_r (d); + })); + + try + { + process pr (args); + + // While it is reasonable to assuming the child process issued + // diagnostics, tar, specifically, doesn't mention the archive + // name. + // + if (!pr.wait ()) + fail << "unable to extract package archive " << a; + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + + p->source = d.leaf (); // For now assuming to be in configuration. + p->source_purge = true; + + p->state = state::unpacked; + + db.update (p); + t.commit (); + + return p; + } + + void + pkg_unpack (const pkg_unpack_options& o, cli::scanner& args) + { + tracer trace ("pkg_unpack"); + + const dir_path& c (o.directory ()); + level4 ([&]{trace << "configuration: " << c;}); + + database db (open (c)); + + shared_ptr p; + + if (o.existing ()) + { + // The package directory case. + // + if (!args.more ()) + fail << "package directory argument expected" << + info << "run 'bpkg help pkg-unpack' for more information"; + + p = pkg_unpack (db, c, dir_path (args.next ())); + } + else + { + // The package name case. + // + if (!args.more ()) + fail << "package name argument expected" << + info << "run 'bpkg help pkg-unpack' for more information"; + + p = pkg_unpack (db, c, args.next ()); + } + + if (verb) + text << "unpacked " << p->name << " " << p->version; + } +} diff --git a/bpkg/pkg-verify b/bpkg/pkg-verify index e002bf3..8250579 100644 --- a/bpkg/pkg-verify +++ b/bpkg/pkg-verify @@ -22,6 +22,12 @@ namespace bpkg // package_manifest pkg_verify (const path& archive, bool diag = true); + + // Similar to the above but verifies that a source directory is + // a valid package. + // + package_manifest + pkg_verify (const dir_path& source, bool diag = true); } #endif // BPKG_PKG_VERIFY diff --git a/bpkg/pkg-verify.cxx b/bpkg/pkg-verify.cxx index a1aa64b..a7d215e 100644 --- a/bpkg/pkg-verify.cxx +++ b/bpkg/pkg-verify.cxx @@ -4,6 +4,8 @@ #include +#include + #include #include @@ -142,6 +144,61 @@ namespace bpkg } } + package_manifest + pkg_verify (const dir_path& d, bool diag) + { + // Parse the manifest. + // + path mf (d / path ("manifest")); + + if (!exists (mf)) + { + if (diag) + error << "no manifest file in package directory " << d; + + throw failed (); + } + + try + { + ifstream ifs; + ifs.exceptions (ifstream::badbit | ifstream::failbit); + ifs.open (mf.string ()); + + manifest_parser mp (ifs, mf.string ()); + package_manifest m (mp); + + // Verify package directory is -. + // + dir_path ed (m.name + "-" + m.version.string ()); + + if (d.leaf () != ed) + { + if (diag) + error << "invalid package directory name '" << d.leaf () << "'" << + info << "expected from manifest '" << ed << "'"; + + throw failed (); + } + + return m; + } + catch (const manifest_parsing& e) + { + if (diag) + error (e.name, e.line, e.column) << e.description; + + throw failed (); + } + catch (const ifstream::failure&) + { + if (diag) + error << "unable to read from " << mf; + + throw failed (); + } + } + void pkg_verify (const pkg_verify_options& o, cli::scanner& args) { -- cgit v1.1