From 0e4d06919b7c075275b22edfc031f90b7f421e7a Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 4 Sep 2015 14:24:15 +0200 Subject: Implement rep-create (create repository) command --- bpkg/rep-create.cxx | 261 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 bpkg/rep-create.cxx (limited to 'bpkg/rep-create.cxx') diff --git a/bpkg/rep-create.cxx b/bpkg/rep-create.cxx new file mode 100644 index 0000000..4ee2bab --- /dev/null +++ b/bpkg/rep-create.cxx @@ -0,0 +1,261 @@ +// file : bpkg/rep-create.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include // pair, move() +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace bpkg +{ + using package_key = pair; + using package_data = pair; + using package_map = map; + + static void + collect (package_map& map, const dir_path& d, const dir_path& root) + { + tracer trace ("collect"); + + for (const dir_entry& de: dir_iterator (d)) + { + path p (de.path ()); + + // Ignore entries that start with a dot (think .git/). + // + if (p.string ().front () == '.') + { + level3 ([&]{trace << "skipping '" << p << "' in " << d;}); + continue; + } + + switch (de.type ()) // Follow symlinks. + { + case entry_type::directory: + { + collect (map, path_cast (d / p), root); + continue; + } + case entry_type::regular: + break; + default: + fail << "unexpected entry '" << p << "' in directory " << d; + } + + // Ignore well-known top-level files. + // + if (d == root) + { + if (p == path ("repositories") || + p == path ("packages")) + continue; + } + + path fp (d / p); + + // Figure out the package directory. Strip the top-level extension + // and, as a special case, if the second-level extension is .tar, + // strip that as well (e.g., .tar.bz2). + // + p = p.base (); + if (const char* e = p.extension ()) + { + if (e == string ("tar")) + p = p.base (); + } + + level4 ([&]{trace << "found package " << p << " in " << fp;}); + + // Extract the manifest. + // + path mf (p / path ("manifest")); + + const char* args[] { + "tar", + "-xOf", // -O/--to-stdout -- extract to STDOUT. + fp.string ().c_str (), + mf.string ().c_str (), + nullptr}; + + if (verb) + print_process (args); + + try + { + process pr (args, 0, -1); // Open pipe to stdout. + + try + { + ifdstream is (pr.in_ofd); + is.exceptions (ifdstream::badbit | ifdstream::failbit); + + manifest_parser mp (is, mf.string ()); + package_manifest m (mp); + + // Verify package archive/directory is -. + // + { + path ep (m.name + "-" + m.version.string ()); + + if (p != ep) + fail << "package archive/directory name mismatch in " << fp << + info << "extracted from archive '" << p << "'" << + info << "expected from manifest '" << ep << "'"; + } + + // Add package archive location relative to the repository root. + // + m.location = fp.leaf (root); + + package_key k (m.name, m.version); // Argument evaluation order. + auto r (map.emplace (move (k), package_data (fp, move (m)))); + + // Diagnose duplicates. + // + if (!r.second) + { + const package_manifest& m (r.first->second.second); + + fail << "duplicate package " << m.name << " " << m.version << + info << "first package archive is " << r.first->second.first << + info << "second package archive is " << fp; + } + } + // Ignore these exceptions if the child process exited with + // an error status since that's the source of the failure. + // + catch (const manifest_parsing& e) + { + if (pr.wait ()) + fail (e.name, e.line, e.column) << e.description << + info << "package archive " << fp; + } + catch (const ifdstream::failure&) + { + if (pr.wait ()) + fail << "unable to extract " << mf << " from " << fp; + } + + if (!pr.wait ()) + { + // While it is reasonable to assuming the child process issued + // diagnostics, tar, specifically, doesn't mention the archive + // name. + // + fail << fp << " does not appear to be a bpkg package"; + } + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + } + + void + rep_create (const rep_create_options&, cli::scanner& args) + try + { + tracer trace ("rep_create"); + + dir_path d (args.more () ? args.next () : "."); + if (d.empty ()) + throw invalid_path (d.string ()); + + level4 ([&]{trace << "creating repository in " << d;}); + + // Load the 'repositories' file to make sure it is there and + // is valid. + // + path rf (d / path ("repositories")); + + if (!file_exists (rf)) + fail << "file " << rf << " does not exist"; + + try + { + ifstream ifs; + ifs.exceptions (ofstream::badbit | ofstream::failbit); + ifs.open (rf.string ()); + + manifest_parser mp (ifs, rf.string ()); + repository_manifests ms (mp); + level3 ([&]{trace << ms.size () - 1 << " prerequisite repository(s)";}); + } + catch (const manifest_parsing& e) + { + fail (e.name, e.line, e.column) << e.description; + } + catch (const ifstream::failure&) + { + fail << "unable to read from " << rf; + } + + // While we could have serialized as we go along, the order of + // packages will be pretty much random and not reproducible. By + // collecting all the manifests in a map we get a sorted list. + // + package_map pm; + collect (pm, d, d); + + // Serialize. + // + path p (d / path ("packages")); + + try + { + ofstream ofs; + ofs.exceptions (ofstream::badbit | ofstream::failbit); + ofs.open (p.string ()); + + manifest_serializer s (ofs, p.string ()); + + for (const auto& p: pm) + { + const package_manifest& m (p.second.second); + + text << "adding " << m.name << " " << m.version; + m.serialize (s); + } + + s.next ("", ""); // The end. + } + catch (const manifest_serialization& e) + { + fail << "unable to save manifest: " << e.description; + } + catch (const ifdstream::failure&) + { + fail << "unable to write to " << p; + } + + text << pm.size () << " package(s)"; + } + catch (const invalid_path& e) + { + fail << "invalid path: '" << e.path () << "'"; + } +} -- cgit v1.1