aboutsummaryrefslogtreecommitdiff
path: root/bpkg/pkg-verify.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/pkg-verify.cxx')
-rw-r--r--bpkg/pkg-verify.cxx435
1 files changed, 389 insertions, 46 deletions
diff --git a/bpkg/pkg-verify.cxx b/bpkg/pkg-verify.cxx
index 38b4d68..d48c5b7 100644
--- a/bpkg/pkg-verify.cxx
+++ b/bpkg/pkg-verify.cxx
@@ -5,11 +5,12 @@
#include <iostream> // cout
-#include <libbutl/manifest-parser.mxx>
-#include <libbutl/manifest-serializer.mxx>
+#include <libbutl/manifest-parser.hxx>
+#include <libbutl/manifest-serializer.hxx>
#include <bpkg/archive.hxx>
#include <bpkg/diagnostics.hxx>
+#include <bpkg/satisfaction.hxx>
#include <bpkg/manifest-utility.hxx>
using namespace std;
@@ -17,28 +18,171 @@ using namespace butl;
namespace bpkg
{
+ pkg_verify_result
+ pkg_verify (const common_options& co,
+ manifest_parser& p,
+ bool it,
+ const path& what,
+ int diag_level)
+ {
+ manifest_name_value nv (p.next ());
+
+ // Make sure this is the start and we support the version.
+ //
+ if (!nv.name.empty ())
+ throw manifest_parsing (p.name (), nv.name_line, nv.name_column,
+ "start of package manifest expected");
+
+ if (nv.value != "1")
+ throw manifest_parsing (p.name (), nv.value_line, nv.value_column,
+ "unsupported format version");
+
+ pkg_verify_result r;
+
+ // For the depends name, parse the value and if it contains the build2 or
+ // bpkg constraints, verify that they are satisfied, if requested.
+ //
+ // Note that if the semantics of the depends value changes we may be
+ // unable to parse some of them before we get to build2 or bpkg and issue
+ // the user-friendly diagnostics. So we are going to ignore such depends
+ // values. But that means that if the user made a mistake in build2/bpkg
+ // then we will skip them as well. This, however, is not a problem since
+ // the pre-parsed result will then be re-parsed (e.g., by the
+ // package_manifest() constructor) which will diagnose any mistakes.
+ //
+ for (nv = p.next (); !nv.empty (); nv = p.next ())
+ {
+ if (nv.name == "depends")
+ try
+ {
+ // Note that we don't have the dependent package name here (unless we
+ // bother to retrieve it from the manifest in advance). This may cause
+ // parsing of a dependency alternative to fail while verifying the
+ // reflect clause (see dependency_alternative for details). That is,
+ // however, OK since we don't expect any clauses for the build2 and
+ // bpkg constraints and we just ignore failures for other depends
+ // values (see above).
+ //
+ dependency_alternatives das (nv.value, package_name ());
+
+ if (das.buildtime)
+ {
+ for (dependency_alternative& da: das)
+ {
+ for (dependency& d: da)
+ {
+ const package_name& dn (d.name);
+
+ if (dn != "build2" && dn != "bpkg")
+ continue;
+
+ // Even if the toolchain build-time dependencies are requested
+ // to be ignored let's make sure they are well-formed, i.e. they
+ // are the only dependencies in the respective depends values.
+ //
+ if (da.size () != 1)
+ {
+ if (diag_level != 0)
+ error (p.name (), nv.value_line, nv.value_column)
+ << "multiple names in " << dn << " dependency";
+
+ throw failed ();
+ }
+
+ if (das.size () != 1)
+ {
+ if (diag_level != 0)
+ error (p.name (), nv.value_line, nv.value_column)
+ << "alternatives in " << dn << " dependency";
+
+ throw failed ();
+ }
+
+ if (dn == "build2")
+ {
+ if (!it && d.constraint && !satisfy_build2 (co, d))
+ {
+ if (diag_level != 0)
+ {
+ diag_record dr (error);
+ dr << "unable to satisfy constraint (" << d << ")";
+
+ if (!what.empty ())
+ dr << " for package " << what;
+
+ dr << info << "available build2 version is "
+ << build2_version;
+ }
+
+ throw failed ();
+ }
+
+ r.build2_dependency = move (d);
+ }
+ else
+ {
+ if (!it && d.constraint && !satisfy_bpkg (co, d))
+ {
+ if (diag_level != 0)
+ {
+ diag_record dr (error);
+ dr << "unable to satisfy constraint (" << d << ")";
+
+ if (!what.empty ())
+ dr << " for package " << what;
+
+ dr << info << "available bpkg version is "
+ << bpkg_version;
+ }
+
+ throw failed ();
+ }
+
+ r.bpkg_dependency = move (d);
+ }
+ }
+ }
+ }
+ }
+ catch (const manifest_parsing&) {} // Ignore
+
+ r.push_back (move (nv));
+ }
+
+ // Make sure this is the end.
+ //
+ nv = p.next ();
+ if (!nv.empty ())
+ throw manifest_parsing (p.name (), nv.name_line, nv.name_column,
+ "single package manifest expected");
+
+ return r;
+ }
+
package_manifest
pkg_verify (const common_options& co,
const path& af,
bool iu,
+ bool it,
bool ev,
+ bool lb,
bool cd,
- bool diag)
+ int diag_level)
try
{
dir_path pd (package_dir (af));
path mf (pd / manifest_file);
- // If diag is false, we need to make tar not print any diagnostics. There
- // doesn't seem to be an option to suppress this and the only way is to
- // redirect stderr to something like /dev/null.
+ // If the diag level is less than 2, we need to make tar not print any
+ // diagnostics. There doesn't seem to be an option to suppress this and
+ // the only way is to redirect stderr to something like /dev/null.
//
// If things go badly for tar and it starts spitting errors instead of the
// manifest, the manifest parser will fail. But that's ok since we assume
// that the child error is always the reason for the manifest parsing
// failure.
//
- pair<process, process> pr (start_extract (co, af, mf, diag));
+ pair<process, process> pr (start_extract (co, af, mf, diag_level == 2));
auto wait = [&pr] () {return pr.second.wait () && pr.first.wait ();};
@@ -46,18 +190,23 @@ namespace bpkg
{
ifdstream is (move (pr.second.in_ofd), fdstream_mode::skip);
manifest_parser mp (is, mf.string ());
- package_manifest m (mp, iu, cd);
+
+ package_manifest m (mp.name (),
+ pkg_verify (co, mp, it, af, diag_level),
+ iu,
+ cd);
+
is.close ();
if (wait ())
{
// Verify package archive/directory is <name>-<version>.
//
- dir_path ed (m.name.string () + "-" + m.version.string ());
+ dir_path ed (m.name.string () + '-' + m.version.string ());
if (pd != ed)
{
- if (diag)
+ if (diag_level != 0)
error << "package archive/directory name mismatch in " << af <<
info << "extracted from archive '" << pd << "'" <<
info << "expected from manifest '" << ed << "'";
@@ -65,30 +214,163 @@ namespace bpkg
throw failed ();
}
- // Expand the *-file manifest values, if requested.
+ // If requested, expand file-referencing package manifest values.
//
- if (ev)
+ if (ev || lb)
{
m.load_files (
- [&pd, &co, &af, diag] (const string& n, const path& p)
+ [ev, &pd, &co, &af, diag_level]
+ (const string& n, const path& p) -> optional<string>
{
- path f (pd / p);
- string s (extract (co, af, f, diag));
+ bool bf (n == "build-file");
- if (s.empty ())
+ // Always expand the build-file values.
+ //
+ if (ev || bf)
{
- if (diag)
- error << n << " manifest value in package archive "
- << af << " references empty file " << f;
+ path f (pd / p);
+ string s (extract (co, af, f, diag_level != 0));
- throw failed ();
- }
+ if (s.empty () && !bf)
+ {
+ if (diag_level != 0)
+ error << n << " manifest value in package archive "
+ << af << " references empty file " << f;
- return s;
+ throw failed ();
+ }
+
+ return s;
+ }
+ else
+ return nullopt;
},
iu);
}
+ // Load the bootstrap, root, and config/*.build buildfiles into the
+ // respective *-build values, if requested and are not already
+ // specified in the manifest.
+ //
+ // Note that we don't verify that the files are not empty.
+ //
+ if (lb)
+ {
+ paths ps (archive_contents (co, af, diag_level != 0));
+
+ auto contains = [&ps] (const path& p)
+ {
+ return find (ps.begin (), ps.end (), p) != ps.end ();
+ };
+
+ auto extract_buildfiles = [&m, &co, &af, &ps, diag_level, &contains]
+ (const path& b,
+ const path& r,
+ const dir_path& c,
+ const string& ext)
+ {
+ if (!m.bootstrap_build)
+ m.bootstrap_build = extract (co, af, b, diag_level != 0);
+
+ if (!m.root_build && contains (r))
+ m.root_build = extract (co, af, r, diag_level != 0);
+
+ // Extract build/config/*.build files.
+ //
+ if (m.root_build)
+ {
+ vector<buildfile>& bs (m.buildfiles);
+ size_t n (bs.size ());
+
+ for (const path& ap: ps)
+ {
+ if (!ap.to_directory () && ap.sub (c))
+ {
+ path p (ap.leaf (c));
+ const char* e (p.extension_cstring ());
+
+ // Only consider immediate sub-entries of the config/
+ // subdirectory.
+ //
+ if (e != nullptr && ext == e && p.simple ())
+ {
+ path f (c.leaf () / p.base ()); // Relative to build/.
+
+ if (find_if (bs.begin (), bs.end (),
+ [&f] (const auto& v) {return v.path == f;}) ==
+ bs.end ())
+ {
+ bs.emplace_back (move (f),
+ extract (co, af, ap, diag_level != 0));
+ }
+ }
+ }
+ }
+
+ // To produce a stable result sort the appended *-build values.
+ //
+ if (bs.size () != n)
+ {
+ sort (bs.begin () + n, bs.end (),
+ [] (const auto& x, const auto& y)
+ {
+ return x.path < y.path;
+ });
+ }
+ }
+ };
+
+ // Set the manifest's alt_naming flag to the deduced value if absent
+ // and verify that it matches otherwise.
+ //
+ auto alt_naming = [&m, diag_level, &af] (bool v)
+ {
+ if (!m.alt_naming)
+ {
+ m.alt_naming = v;
+ }
+ else if (*m.alt_naming != v)
+ {
+ if (diag_level != 0)
+ error << "buildfile naming scheme mismatch between manifest "
+ << "and package archive " << af;
+
+ throw failed ();
+ }
+ };
+
+ // Check the alternative bootstrap file first since it is more
+ // specific.
+ //
+ path bf;
+ if (contains (bf = pd / alt_bootstrap_file))
+ {
+ alt_naming (true);
+
+ extract_buildfiles (bf,
+ pd / alt_root_file,
+ pd / alt_config_dir,
+ alt_build_ext);
+ }
+ else if (contains (bf = pd / std_bootstrap_file))
+ {
+ alt_naming (false);
+
+ extract_buildfiles (bf,
+ pd / std_root_file,
+ pd / std_config_dir,
+ std_build_ext);
+ }
+ else
+ {
+ if (diag_level != 0)
+ error << "unable to find bootstrap.build file in package "
+ << "archive " << af;
+
+ throw failed ();
+ }
+ }
+
return m;
}
@@ -101,7 +383,7 @@ namespace bpkg
{
if (wait ())
{
- if (diag)
+ if (diag_level != 0)
error (e.name, e.line, e.column) << e.description <<
info << "package archive " << af;
@@ -112,7 +394,7 @@ namespace bpkg
{
if (wait ())
{
- if (diag)
+ if (diag_level != 0)
error << "unable to extract " << mf << " from " << af;
throw failed ();
@@ -128,10 +410,10 @@ namespace bpkg
// diagnostics, tar, specifically, doesn't mention the archive
// name.
//
- if (diag)
+ if (diag_level == 2)
error << af << " does not appear to be a bpkg package";
- throw failed ();
+ throw not_package ();
}
catch (const process_error& e)
{
@@ -142,10 +424,13 @@ namespace bpkg
}
package_manifest
- pkg_verify (const dir_path& d,
+ pkg_verify (const common_options& co,
+ const dir_path& d,
bool iu,
+ bool it,
+ bool lb,
const function<package_manifest::translate_function>& tf,
- bool diag)
+ int diag_level)
{
// Parse the manifest.
//
@@ -153,45 +438,100 @@ namespace bpkg
if (!exists (mf))
{
- if (diag)
+ if (diag_level == 2)
error << "no manifest file in package directory " << d;
- throw failed ();
+ throw not_package ();
}
try
{
ifdstream ifs (mf);
manifest_parser mp (ifs, mf.string ());
- package_manifest m (mp, tf, iu);
+
+ package_manifest m (mp.name (),
+ pkg_verify (co, mp, it, d, diag_level),
+ tf,
+ iu);
+
+ // Load the bootstrap, root, and config/*.build buildfiles into the
+ // respective *-build values, if requested and if they are not already
+ // specified in the manifest. But first expand the build-file manifest
+ // values into the respective *-build values.
+ //
+ // Note that we don't verify that the files are not empty.
+ //
+ if (lb)
+ {
+ m.load_files (
+ [&d, &mf, diag_level]
+ (const string& n, const path& p) -> optional<string>
+ {
+ // Only expand the build-file values.
+ //
+ if (n == "build-file")
+ {
+ path f (d / p);
+
+ try
+ {
+ ifdstream is (f);
+ return is.read_text ();
+ }
+ catch (const io_error& e)
+ {
+ if (diag_level != 0)
+ error << "unable to read from " << f << " referenced by "
+ << n << " manifest value in " << mf << ": " << e;
+
+ throw failed ();
+ }
+ }
+ else
+ return nullopt;
+ },
+ iu);
+
+ try
+ {
+ load_package_buildfiles (m, d);
+ }
+ catch (const runtime_error& e)
+ {
+ if (diag_level != 0)
+ error << e;
+
+ throw failed ();
+ }
+ }
// We used to verify package directory is <name>-<version> but it is
// not clear why we should enforce it in this case (i.e., the user
// provides us with a package directory).
//
- // dir_path ed (m.name + "-" + m.version.string ());
+ // 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 << "'";
+ // if (diag_level != 0)
+ // error << "invalid package directory name '" << d.leaf () << "'" <<
+ // info << "expected from manifest '" << ed << "'";
//
- // throw failed ();
+ // throw failed ();
// }
return m;
}
catch (const manifest_parsing& e)
{
- if (diag)
+ if (diag_level != 0)
error (e.name, e.line, e.column) << e.description;
throw failed ();
}
catch (const io_error& e)
{
- if (diag)
+ if (diag_level != 0)
error << "unable to read from " << mf << ": " << e;
throw failed ();
@@ -219,12 +559,15 @@ namespace bpkg
//
try
{
- package_manifest m (pkg_verify (o,
- a,
- o.ignore_unknown (),
- o.deep () /* expand_values */,
- o.deep () /* complete_depends */,
- !o.silent ()));
+ package_manifest m (
+ pkg_verify (o,
+ a,
+ o.ignore_unknown (),
+ o.ignore_unknown () /* ignore_toolchain */,
+ o.deep () /* expand_values */,
+ o.deep () /* load_buildfiles */,
+ o.deep () /* complete_values */,
+ o.silent () ? 0 : 2));
if (o.manifest ())
{
@@ -247,9 +590,9 @@ namespace bpkg
return 0;
}
- catch (const failed&)
+ catch (const failed& e)
{
- return 1;
+ return e.code;
}
}
}