From df91839a0f53b1bf266eb6b9ebabf2b587211731 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 21 Mar 2023 06:32:56 +0200 Subject: Add support for --recursive=separate pkg-bindist option mode --- bpkg/pkg-bindist.cxx | 314 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 215 insertions(+), 99 deletions(-) (limited to 'bpkg/pkg-bindist.cxx') diff --git a/bpkg/pkg-bindist.cxx b/bpkg/pkg-bindist.cxx index 8c2163b..cb29af1 100644 --- a/bpkg/pkg-bindist.cxx +++ b/bpkg/pkg-bindist.cxx @@ -3,6 +3,8 @@ #include +#include + #include #include #include @@ -18,7 +20,6 @@ namespace bpkg { using package = system_package_manager::package; using packages = system_package_manager::packages; - using recursive_mode = system_package_manager::recursive_mode; // Find the available package(s) for the specified selected package. // @@ -220,6 +221,8 @@ namespace bpkg // Verify options. // + enum class recursive_mode {auto_, full, separate}; + optional rec; { diag_record dr; @@ -228,13 +231,24 @@ namespace bpkg { const string& m (o.recursive ()); - if (m == "auto") rec = recursive_mode::auto_; - else if (m == "full") rec = recursive_mode::full; + if (m == "auto") rec = recursive_mode::auto_; + else if (m == "full") rec = recursive_mode::full; + else if (m == "separate") rec = recursive_mode::separate; else dr << fail << "unknown mode '" << m << "' specified with --recursive"; } - else if (o.private_ ()) - dr << fail << "--private specified without --recursive"; + + if (o.private_ ()) + { + if (!rec) + { + dr << fail << "--private specified without --recursive"; + } + else if (*rec == recursive_mode::separate) + { + dr << fail << "--private specified without --recursive=separate"; + } + } if (!dr.empty ()) dr << info << "run 'bpkg help pkg-bindist' for more information"; @@ -281,6 +295,25 @@ namespace bpkg info << "run 'bpkg help pkg-bindist' for more information"; } + // Note that we shouldn't need to install anything or use sudo. + // + unique_ptr spm ( + make_production_system_package_manager (o, + host_triplet, + o.distribution (), + o.architecture ())); + if (spm == nullptr) + { + fail << "no standard distribution package manager for this host " + << "or it is not yet supported" << + info << "consider specifying alternative distribution package " + << "manager with --distribution" << + info << "specify --distribution=archive to generate installation " + << "archive" << + info << "consider specifying --os-release-* if unable to correctly " + << "auto-detect host operating system"; + } + database db (c, trace, true /* pre_attach */); // Similar to pkg-install we disallow generating packages from the @@ -303,128 +336,211 @@ namespace bpkg // session ses; - // Resolve package names to selected packages and verify they are all - // configured. While at it collect their available packages and - // dependencies as well as figure out type and languages. + // Generate one binary package. // - packages pkgs, deps; - string type; - small_vector langs; - - for (const package_name& n: pns) + struct result + { + paths bins; + packages deps; + shared_ptr pkg; + }; + + auto generate = [&o, &vars, + rec, &spm, + &c, &db] (const vector& pns, + bool first) -> result { - shared_ptr p (db.find (n)); + // Resolve package names to selected packages and verify they are all + // configured. While at it collect their available packages and + // dependencies as well as figure out type and languages. + // + packages pkgs, deps; + string type; + small_vector langs; - if (p == nullptr) - fail << "package " << n << " does not exist in configuration " << c; + for (const package_name& n: pns) + { + shared_ptr p (db.find (n)); - if (p->state != package_state::configured) - fail << "package " << n << " is " << p->state << - info << "expected it to be configured"; + if (p == nullptr) + fail << "package " << n << " does not exist in configuration " << c; - if (p->substate == package_substate::system) - fail << "package " << n << " is configured as system"; + if (p->state != package_state::configured) + fail << "package " << n << " is " << p->state << + info << "expected it to be configured"; - // Load the available package for type/languages as well as the mapping - // information. - // - available_packages aps (find_available_packages (o, db, p)); - const shared_ptr& ap (aps.front ().first); - db.load (*ap, ap->languages_section); + if (p->substate == package_substate::system) + fail << "package " << n << " is configured as system"; - if (pkgs.empty ()) // First. - { - type = ap->effective_type (); - langs = ap->effective_languages (); - } - else - merge_languages (type, langs, *ap); + // Make sure there are no dependent configuration variables. The + // rationale here is that we most likely don't want to generate a + // binary package in a configuration that is specific to some + // dependents. + // + if (!o.allow_dependent_config ()) + { + for (const config_variable& v: p->config_variables) + { + switch (v.source) + { + case config_source::dependent: + { + fail << "configuration variable " << v.name << " is imposed " + << " by dependent package" << + info << "specify it as user configuration to allow" << + info << "or specify --allow-dependent-config" << endf; + } + case config_source::user: + case config_source::reflect: + break; + } + } + } + + // Load the available package for type/languages as well as the + // mapping information. + // + available_packages aps (find_available_packages (o, db, p)); + const shared_ptr& ap (aps.front ().first); + db.load (*ap, ap->languages_section); + + if (pkgs.empty ()) // First. + { + type = ap->effective_type (); + langs = ap->effective_languages (); + } + else + merge_languages (type, langs, *ap); - const selected_package& r (*p); - pkgs.push_back ( - package {move (p), move (aps), r.effective_out_root (db.config)}); + const selected_package& r (*p); + pkgs.push_back ( + package {move (p), move (aps), r.effective_out_root (db.config)}); - // If --recursive is not specified then we want all the immediate - // (system and non-) dependecies in deps. Otherwise, if the recursive - // mode is full, then we want all the transitive non-system dependecies - // in pkgs. In both recursive modes we also want all the transitive - // system dependecies in deps. + // If --recursive is not specified or specified with the seperate mode + // then we want all the immediate (system and non-) dependecies in + // deps. Otherwise, if the recursive mode is full, then we want all + // the transitive non-system dependecies in pkgs. In both recursive + // modes we also want all the transitive system dependecies in deps. + // + // Note also that in the auto recursive mode it's possible that some + // of the system dependencies are not really needed. But there is no + // way for us to detect this and it's better to over- than + // under-specify. + // + collect_dependencies ( + o, + db, + (!rec || *rec == recursive_mode::separate + ? &deps + : *rec == recursive_mode::full ? &pkgs : nullptr), + deps, + type, + langs, + r, + rec.has_value ()); + } + + // Load the package manifest (source of extra metadata). This should be + // always possible since the package is configured and is not system. // - // Note also that in the auto recursive mode it's possible that some of - // the system dependencies are not really needed. But there is no way - // for us to detect this and it's better to over- than under-specify. + const shared_ptr& sp (pkgs.front ().selected); + + package_manifest pm ( + pkg_verify (o, + sp->effective_src_root (db.config_orig), + true /* ignore_unknown */, + false /* ignore_toolchain */, + false /* load_buildfiles */, + // Copy potentially fixed up version from selected package. + [&sp] (version& v) {v = sp->version;})); + + optional recursive_full; + if (rec && *rec != recursive_mode::separate) + recursive_full = *rec != recursive_mode::full; + + // Note that we pass type from here in case one day we want to provide + // an option to specify/override it (along with languages). Note that + // there will probably be no way to override type for dependencies. // - collect_dependencies (o, - db, - (rec - ? *rec == recursive_mode::full ? &pkgs : nullptr - : &deps), - deps, - type, - langs, - r, - rec.has_value ()); - } + paths r (spm->generate (pkgs, + deps, + vars, + db.config, + pm, + type, langs, + recursive_full, + first)); + + return result {move (r), move (deps), move (pkgs.front ().selected)}; + }; + + list rs; // Note: list for reference stability. + + // Generate packages for dependencies, recursively, suppressing + // duplicates. Note: recursive lambda. + // + auto generate_deps = [&generate, &rs] (const packages& deps, + const auto& generate_deps) -> void + { + for (const package& d: deps) + { + const shared_ptr& p (d.selected); - t.commit (); + // Skip system dependencies. + // + if (p->substate == package_substate::system) + continue; - // Load the package manifest (source of extra metadata). This should be - // always possible since the package is configured and is not system. - // - const shared_ptr& sp (pkgs.front ().selected); + // Make sure we don't generate the same dependency multiple times. + // + if (find_if (rs.begin (), rs.end (), + [&p] (const result& r) + { + return r.pkg == p; + }) != rs.end ()) + continue; - package_manifest pm ( - pkg_verify (o, - sp->effective_src_root (db.config_orig), - true /* ignore_unknown */, - false /* ignore_toolchain */, - false /* load_buildfiles */, - // Copy potentially fixed up version from selected package. - [&sp] (version& v) {v = sp->version;})); + if (verb >= 1) + text << "generating package for dependency " << p->name; - // Note that we shouldn't need to install anything or use sudo. + rs.push_back (generate ({p->name}, false /* first */)); + generate_deps (rs.back ().deps, generate_deps); + } + }; + + // Generate top-level package(s). // - unique_ptr spm ( - make_production_system_package_manager (o, - host_triplet, - o.distribution (), - o.architecture ())); - if (spm == nullptr) - { - fail << "no standard distribution package manager for this host " - << "or it is not yet supported" << - info << "consider specifying alternative distribution package " - << "manager with --distribution" << - info << "specify --distribution=archive to generate installation " - << "archive" << - info << "consider specifying --os-release-* if unable to correctly " - << "auto-detect host operating system"; - } + rs.push_back (generate (pns, true /* first */)); - // Note that we pass type from here in case one day we want to provide an - // option to specify/override it (along with languages). Note that there - // will probably be no way to override type for dependencies. + // Generate dependencies, if requested. // - paths r (spm->generate (pkgs, deps, vars, db.config, pm, type, langs, rec)); + if (rec && rec == recursive_mode::separate) + generate_deps (rs.back ().deps, generate_deps); - if (r.empty ()) + t.commit (); + + if (rs.front ().bins.empty ()) return 0; // Assume prepare-only mode or similar. if (verb && !o.no_result ()) { - const selected_package& p (*pkgs.front ().selected); - - diag_record dr (text); - const string& d (o.distribution_specified () ? o.distribution () : spm->os_release.name_id); - dr << "generated " << d << " package for " << p.name << '/' << p.version - << ':'; + for (auto b (rs.begin ()), i (b); i != rs.end (); ++i) + { + const selected_package& p (*i->pkg); + + diag_record dr (text); - for (const path& p: r) - dr << "\n " << p; + dr << "generated " << d << " package for " + << (i != b ? "dependency " : "") + << p.name << '/' << p.version << ':'; + + for (const path& p: i->bins) + dr << "\n " << p; + } } return 0; -- cgit v1.1