aboutsummaryrefslogtreecommitdiff
path: root/bpkg
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-09-16 07:16:06 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-09-16 07:16:06 +0200
commit236ad71b105365bedf9d28a5606616fb9aed3168 (patch)
tree3e9c4943c6ea854ed1c931033f7f62916585cd81 /bpkg
parentfbe0716682ad4fd64df670978785db372cbe2ed2 (diff)
Implement pkg-unpack command
Diffstat (limited to 'bpkg')
-rw-r--r--bpkg/bpkg-options.cli7
-rw-r--r--bpkg/bpkg.cxx13
-rw-r--r--bpkg/buildfile2
-rw-r--r--bpkg/package22
-rw-r--r--bpkg/package.xml2
-rw-r--r--bpkg/pkg-fetch.cxx20
-rw-r--r--bpkg/pkg-unpack17
-rw-r--r--bpkg/pkg-unpack-options.cli44
-rw-r--r--bpkg/pkg-unpack.cxx205
-rw-r--r--bpkg/pkg-verify6
-rw-r--r--bpkg/pkg-verify.cxx57
11 files changed, 385 insertions, 10 deletions
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
+ {
+ "<pkg>",
+ "Unpack package archive.",
+ ""
+ };
+
bool cfg-create
{
"[<conf>]",
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 <bpkg/help>
#include <bpkg/pkg-verify>
#include <bpkg/pkg-fetch>
+#include <bpkg/pkg-unpack>
#include <bpkg/cfg-create>
#include <bpkg/rep-create>
@@ -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<pkg_unpack_options> (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<string>;
+ using optional_path = optional<path>;
+ using optional_dir_path = optional<dir_path>;
+
#pragma db map type(path) as(string) \
to((?).string ()) from(bpkg::path (?))
- using optional_path = optional<path>;
- using optional_string = optional<string>;
-
#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<path> 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<dir_path> 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 @@
<column name="state" type="TEXT" null="true"/>
<column name="archive" type="TEXT" null="true"/>
<column name="archive_purge" type="INTEGER" null="true"/>
+ <column name="source" type="TEXT" null="true"/>
+ <column name="source_purge" type="INTEGER" null="true"/>
<primary-key>
<column name="name"/>
</primary-key>
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<package> p = db.find<package> (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<package> p (new package {
+ move (m.name),
+ move (m.version),
+ state::fetched,
+ move (a),
+ purge,
+ optional<dir_path> (), // 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 <bpkg/types>
+#include <bpkg/pkg-unpack-options>
+
+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 <bpkg/common-options.cli>;
+
+/*
+"\section=1"
+"\name=bpkg-pkg-unpack"
+
+"\h{SYNOPSIS}
+
+bpkg pkg-unpack [<options>] <pkg>|(-e <dir>)"
+
+"\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 (".")
+ {
+ "<dir>",
+ "Assume configuration is in <dir> 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 <bpkg/pkg-unpack>
+
+#include <memory> // shared_ptr
+
+#include <butl/process>
+
+#include <bpkg/manifest>
+
+#include <bpkg/types>
+#include <bpkg/package>
+#include <bpkg/package-odb>
+#include <bpkg/utility>
+#include <bpkg/database>
+#include <bpkg/pkg-verify>
+#include <bpkg/diagnostics>
+
+using namespace std;
+using namespace butl;
+
+namespace bpkg
+{
+ static shared_ptr<package>
+ 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<package> p = db.find<package> (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<package> p (new package {
+ move (m.name),
+ move (m.version),
+ state::unpacked,
+ optional<path> (), // No archive
+ false, // Don't purge archive.
+ move (ad),
+ false}); // Don't purge source.
+
+ db.persist (p);
+ t.commit ();
+
+ return p;
+ }
+
+ static shared_ptr<package>
+ pkg_unpack (database& db, const dir_path& c, const string& name)
+ {
+ tracer trace ("pkg_unpack(pkg)");
+
+ transaction t (db.begin ());
+ shared_ptr<package> p (db.find<package> (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<package> 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 <bpkg/pkg-verify>
+#include <fstream>
+
#include <butl/process>
#include <butl/fdstream>
@@ -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 <name>-<version>.
+ //
+ 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)
{