diff options
Diffstat (limited to 'bpkg/manifest-utility.cxx')
-rw-r--r-- | bpkg/manifest-utility.cxx | 447 |
1 files changed, 385 insertions, 62 deletions
diff --git a/bpkg/manifest-utility.cxx b/bpkg/manifest-utility.cxx index 159b2c3..afcb1f7 100644 --- a/bpkg/manifest-utility.cxx +++ b/bpkg/manifest-utility.cxx @@ -3,10 +3,11 @@ #include <bpkg/manifest-utility.hxx> +#include <sstream> #include <cstring> // strcspn() -#include <libbutl/b.mxx> -#include <libbutl/sha256.mxx> +#include <libbutl/b.hxx> +#include <libbutl/filesystem.hxx> // dir_iterator #include <bpkg/package.hxx> // wildcard_version #include <bpkg/diagnostics.hxx> @@ -22,6 +23,45 @@ namespace bpkg const path signature_file ("signature.manifest"); const path manifest_file ("manifest"); + vector<package_info> + package_b_info (const common_options& o, + const dir_paths& ds, + b_info_flags fl) + { + path b (name_b (o)); + + vector<package_info> r; + try + { + b_info (r, + ds, + fl, + verb, + [] (const char* const args[], size_t n) + { + if (verb >= 2) + print_process (args, n); + }, + b, + exec_dir, + o.build_option ()); + return r; + } + catch (const b_error& e) + { + if (e.normal ()) + throw failed (); // Assume the build2 process issued diagnostics. + + diag_record dr (fail); + dr << "unable to parse project "; + if (r.size () < ds.size ()) dr << ds[r.size ()] << ' '; + dr << "info: " << e << + info << "produced by '" << b << "'; use --build to override" << endf; + + return vector<package_info> (); // Work around GCC 13.2.1 segfault. + } + } + package_scheme parse_package_scheme (const char*& s) { @@ -40,36 +80,21 @@ namespace bpkg package_name parse_package_name (const char* s, bool allow_version) { - if (!allow_version) - try - { - return package_name (s); - } - catch (const invalid_argument& e) - { - fail << "invalid package name '" << s << "': " << e; - } - - // Calculate the package name length as a length of the prefix that - // doesn't contain spaces, slashes and the version constraint starting - // characters. Note that none of them are valid package name characters. - // - size_t n (strcspn (s, " /=<>([~^")); - try { - return package_name (string (s, n)); + return extract_package_name (s, allow_version); } catch (const invalid_argument& e) { - fail << "invalid package name in '" << s << "': " << e << endf; + fail << "invalid package name " << (allow_version ? "in " : "") + << "'" << s << "': " << e << endf; } } version parse_package_version (const char* s, bool allow_wildcard, - bool fold_zero_revision) + version::flags fl) { using traits = string::traits_type; @@ -83,15 +108,7 @@ namespace bpkg try { - version r (p, fold_zero_revision); - - if (r.release && r.release->empty ()) - throw invalid_argument ("earliest version"); - - if (r.compare (wildcard_version, true /* ignore_revision */) == 0) - throw invalid_argument ("stub version"); - - return r; + return extract_package_version (s, fl); } catch (const invalid_argument& e) { @@ -106,7 +123,7 @@ namespace bpkg optional<version_constraint> parse_package_version_constraint (const char* s, bool allow_wildcard, - bool fold_zero_revision, + version::flags fl, bool version_only) { // Calculate the version specification position as a length of the prefix @@ -118,16 +135,20 @@ namespace bpkg if (s[n] == '\0') // No version (constraint) is specified? return nullopt; - const char* v (s + n); // Constraint or version including '/'. + const char* v (s + n); // Constraint or version including leading '/'. + + if (version_only && v[0] != '/') + fail << "exact package version expected instead of version constraint " + << "in '" << s << "'"; - // If only the version is allowed or the package name is followed by '/' - // then fallback to the version parsing. + // If the package name is followed by '/' then fallback to the version + // parsing. // - if (version_only || v[0] == '/') + if (v[0] == '/') try { return version_constraint ( - parse_package_version (s, allow_wildcard, fold_zero_revision)); + parse_package_version (s, allow_wildcard, fl)); } catch (const invalid_argument& e) { @@ -295,42 +316,344 @@ namespace bpkg } } - optional<version> - package_version (const common_options& o, const dir_path& d) + package_version_infos + package_versions (const common_options& o, + const dir_paths& ds, + b_info_flags fl) { - path b (name_b (o)); + vector<b_project_info> pis (package_b_info (o, ds, fl)); - try - { - b_project_info pi ( - b_info (d, - verb, - [] (const char* const args[], size_t n) - { - if (verb >= 2) - print_process (args, n); - }, - b, - exec_dir, - o.build_option ())); - - optional<version> r; + package_version_infos r; + r.reserve (pis.size ()); + for (const b_project_info& pi: pis) + { // An empty version indicates that the version module is not enabled for // the project. // - if (!pi.version.empty ()) - r = version (pi.version.string ()); + optional<version> v (!pi.version.empty () + ? version (pi.version.string ()) + : optional<version> ()); - return r; + r.push_back (package_version_info {move (v), move (pi)}); } - catch (const b_error& e) + + return r; + } + + string + package_checksum (const common_options& o, + const dir_path& d, + const package_info* pi) + { + path f (d / manifest_file); + + try { - if (e.normal ()) - throw failed (); // Assume the build2 process issued diagnostics. + ifdstream is (f, fdopen_mode::binary); + sha256 cs (is); - fail << "unable to parse project " << d << " info: " << e << - info << "produced by '" << b << "'; use --build to override" << endf; + const vector<package_info::subproject>& sps ( + pi != nullptr + ? pi->subprojects + : package_b_info (o, d, b_info_flags::subprojects).subprojects); + + for (const package_info::subproject& sp: sps) + cs.append (sp.path.string ()); + + return cs.string (); + } + catch (const io_error& e) + { + fail << "unable to read from " << f << ": " << e << endf; + } + } + + // Return the sorted list of *.build files (first) which are present in the + // package's build/config/ subdirectory (or their alternatives) together + // with the *-build manifest value names they correspond to (second). Skip + // files which are already present in the specified buildfile/path + // lists. Note: throws system_error on filesystem errors. + // + static vector<pair<path, path>> + find_buildfiles (const dir_path& config, + const string& ext, + const vector<buildfile>& bs, + const vector<path>& bps) + { + vector<pair<path, path>> r; + + for (const dir_entry& de: dir_iterator (config, dir_iterator::no_follow)) + { + if (de.type () == entry_type::regular) + { + const path& p (de.path ()); + const char* e (p.extension_cstring ()); + + if (e != nullptr && ext == e) + { + path f (config.leaf () / p.base ()); // Relative to build/. + + if (find_if (bs.begin (), bs.end (), + [&f] (const auto& v) {return v.path == f;}) == + bs.end () && + find (bps.begin (), bps.end (), f) == bps.end ()) + { + r.emplace_back (config / p, move (f)); + } + } + } + } + + sort (r.begin (), r.end (), + [] (const auto& x, const auto& y) {return x.second < y.second;}); + + return r; + } + + string + package_buildfiles_checksum (const optional<string>& bb, + const optional<string>& rb, + const vector<buildfile>& bs, + const dir_path& d, + const vector<path>& bps, + optional<bool> an) + { + if (d.empty ()) + { + assert (bb); + + sha256 cs (*bb); + + if (rb) + cs.append (*rb); + + for (const buildfile& b: bs) + cs.append (b.content); + + return cs.string (); + } + + auto checksum = [&bb, &rb, &bs, &bps] (const path& b, + const path& r, + const dir_path& c, + const string& e) + { + sha256 cs; + + auto append_file = [&cs] (const path& f) + { + try + { + // Open the buildfile in the text mode and hash the NULL character + // at the end to calculate the checksum over files consistently with + // calculating it over the *-build manifest values. + // + ifdstream ifs (f); + cs.append (ifs); + cs.append ('\0'); + } + catch (const io_error& e) + { + fail << "unable to read from " << f << ": " << e; + } + }; + + if (bb) + cs.append (*bb); + else + append_file (b); + + bool root (true); + + if (rb) + cs.append (*rb); + else if (exists (r)) + append_file (r); + else + root = false; + + for (const buildfile& b: bs) + cs.append (b.content); + + if (!bps.empty ()) + { + dir_path bd (b.directory ()); + + for (const path& p: bps) + { + path f (bd / p); + f += '.' + e; + + append_file (f); + } + } + + if (root && exists (c)) + try + { + for (auto& f: find_buildfiles (c, e, bs, bps)) + append_file (f.first); + } + catch (const system_error& e) + { + fail << "unable to scan directory " << c << ": " << e; + } + + return string (cs.string ()); + }; + + // Verify that the deduced naming scheme matches the specified one and + // fail if that's not the case. + // + auto verify = [an, &d] (bool alt_naming) + { + assert (an); + + if (*an != alt_naming) + fail << "buildfile naming scheme mismatch between manifest and " + << "package directory " << d; + }; + + // Check the alternative bootstrap file first since it is more specific. + // + path bf; + if (exists (bf = d / alt_bootstrap_file)) + { + if (an) + verify (true /* alt_naming */); + + return checksum (bf, + d / alt_root_file, + d / alt_config_dir, + alt_build_ext); + } + else if (exists (bf = d / std_bootstrap_file)) + { + if (an) + verify (false /* alt_naming */); + + return checksum (bf, + d / std_root_file, + d / std_config_dir, + std_build_ext); + } + else + fail << "unable to find bootstrap.build file in package directory " + << d << endf; + } + + void + load_package_buildfiles (package_manifest& m, const dir_path& d, bool erp) + { + assert (m.buildfile_paths.empty ()); // build-file values must be expanded. + + auto load_buildfiles = [&m, &d, erp] (const path& b, + const path& r, + const dir_path& c, + const string& ext) + { + auto diag_path = [&d, erp] (const path& p) + { + return !erp ? p : p.leaf (d); + }; + + auto load = [&diag_path] (const path& f) + { + try + { + ifdstream ifs (f); + string r (ifs.read_text ()); + ifs.close (); + return r; + } + catch (const io_error& e) + { + // Sanitize the exception description. + // + ostringstream os; + os << "unable to read from " << diag_path (f) << ": " << e; + throw runtime_error (os.str ()); + } + }; + + if (!m.bootstrap_build) + m.bootstrap_build = load (b); + + if (!m.root_build && exists (r)) + m.root_build = load (r); + + if (m.root_build && exists (c)) + try + { + for (auto& f: find_buildfiles (c, + ext, + m.buildfiles, + m.buildfile_paths)) + { + m.buildfiles.emplace_back (move (f.second), load (f.first)); + } + } + catch (const system_error& e) + { + // Sanitize the exception description. + // + ostringstream os; + os << "unable to scan directory " << diag_path (c) << ": " << e; + throw runtime_error (os.str ()); + } + }; + + // Set the manifest's alt_naming flag to the deduced value if absent and + // verify that it matches otherwise. + // + auto alt_naming = [&m, &d, erp] (bool v) + { + if (!m.alt_naming) + { + m.alt_naming = v; + } + else if (*m.alt_naming != v) + { + string e ("buildfile naming scheme mismatch between manifest and " + "package directory"); + + if (!erp) + e += ' ' + d.string (); + + throw runtime_error (e); + } + }; + + // Check the alternative bootstrap file first since it is more specific. + // + path bf; + if (exists (bf = d / alt_bootstrap_file)) + { + alt_naming (true); + + load_buildfiles (bf, + d / alt_root_file, + d / alt_config_dir, + alt_build_ext); + } + else if (exists (bf = d / std_bootstrap_file)) + { + alt_naming (false); + + load_buildfiles (bf, + d / std_root_file, + d / std_config_dir, + std_build_ext); + } + else + { + string e ("unable to find bootstrap.build file in package directory"); + + if (!erp) + e += ' ' + d.string (); + + throw runtime_error (e); } } } |