aboutsummaryrefslogtreecommitdiff
path: root/bpkg/manifest-utility.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/manifest-utility.cxx')
-rw-r--r--bpkg/manifest-utility.cxx447
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);
}
}
}