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.cxx329
1 files changed, 314 insertions, 15 deletions
diff --git a/bpkg/manifest-utility.cxx b/bpkg/manifest-utility.cxx
index 9b8cbca..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>
@@ -23,7 +24,9 @@ namespace bpkg
const path manifest_file ("manifest");
vector<package_info>
- package_b_info (const common_options& o, const dir_paths& ds, bool ext_mods)
+ package_b_info (const common_options& o,
+ const dir_paths& ds,
+ b_info_flags fl)
{
path b (name_b (o));
@@ -32,7 +35,7 @@ namespace bpkg
{
b_info (r,
ds,
- ext_mods,
+ fl,
verb,
[] (const char* const args[], size_t n)
{
@@ -54,6 +57,8 @@ namespace bpkg
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.
}
}
@@ -89,7 +94,7 @@ namespace bpkg
version
parse_package_version (const char* s,
bool allow_wildcard,
- bool fold_zero_revision)
+ version::flags fl)
{
using traits = string::traits_type;
@@ -103,7 +108,7 @@ namespace bpkg
try
{
- return extract_package_version (s, fold_zero_revision);
+ return extract_package_version (s, fl);
}
catch (const invalid_argument& e)
{
@@ -118,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
@@ -130,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)
{
@@ -308,9 +317,11 @@ namespace bpkg
}
package_version_infos
- package_versions (const common_options& o, const dir_paths& ds)
+ package_versions (const common_options& o,
+ const dir_paths& ds,
+ b_info_flags fl)
{
- vector<b_project_info> pis (package_b_info (o, ds, false /* ext_mods */));
+ vector<b_project_info> pis (package_b_info (o, ds, fl));
package_version_infos r;
r.reserve (pis.size ());
@@ -345,7 +356,7 @@ namespace bpkg
const vector<package_info::subproject>& sps (
pi != nullptr
? pi->subprojects
- : package_b_info (o, d, false /* ext_mods */).subprojects);
+ : package_b_info (o, d, b_info_flags::subprojects).subprojects);
for (const package_info::subproject& sp: sps)
cs.append (sp.path.string ());
@@ -357,4 +368,292 @@ namespace bpkg
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);
+ }
+ }
}