From eec7e0184d06e369137686d910cde4c6fbd231cf Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 13 Nov 2024 18:24:32 +0200 Subject: Add support for --recursive [?]= for pkg-bindist --- bpkg/pkg-bindist.cli | 12 +- bpkg/pkg-bindist.cxx | 251 ++++++++++++++++++++++++++------ bpkg/system-package-manager-archive.cxx | 2 +- bpkg/system-package-manager-archive.hxx | 1 + bpkg/system-package-manager-debian.cxx | 2 +- bpkg/system-package-manager-debian.hxx | 1 + bpkg/system-package-manager-fedora.cxx | 2 +- bpkg/system-package-manager-fedora.hxx | 1 + bpkg/system-package-manager.hxx | 4 + 9 files changed, 229 insertions(+), 47 deletions(-) diff --git a/bpkg/pkg-bindist.cli b/bpkg/pkg-bindist.cli index 1401723..ca8de31 100644 --- a/bpkg/pkg-bindist.cli +++ b/bpkg/pkg-bindist.cli @@ -94,7 +94,7 @@ namespace bpkg If unspecified, the host architecture is used." } - string --recursive = "none" + strings --recursive { "", "Bundle or generate dependencies of the specified packages. The @@ -112,7 +112,15 @@ namespace bpkg dependency package is installed explicitly and completely, as if they were specified as additional package on the command line. The \cb{separate} mode is equivalent to invoking the \cb{pkg-bindist} - command on each dependency package. See also the \cb{--private} option." + command on each dependency package. See also the \cb{--private} option. + + The value can also be prefixed with the package name in the + \c{[\b{?}]\i{pkg}\b{=}\i{mode}} form in order to specify the mode on + the per-package basis. Specifically, if the package name starts with + \cb{?}, then the mode applies to the package itself (which only + makes sense for dependencies) and otherwise \- only to this package's + dependencies, recursively (until and unless overridden with another + package-specific mode)." } bool --private diff --git a/bpkg/pkg-bindist.cxx b/bpkg/pkg-bindist.cxx index 4639746..2bcf28d 100644 --- a/bpkg/pkg-bindist.cxx +++ b/bpkg/pkg-bindist.cxx @@ -3,6 +3,7 @@ #include +#include #include #include // cout @@ -129,21 +130,58 @@ namespace bpkg } } + enum class recursive_mode {auto_, full, separate}; + + // Package-specific recursive mode overrides. + // + struct package_recursive + { + // --recursive = + // + // If present, overrides the recursive mode for collecting dependencies of + // this package (inner absent means `none`). + // + optional> dependencies; + + // --recursive ?= + // + // If present, this dependency is collected in this mode rather than in + // the mode(s) its dependents collect their dependencies (inner absent + // means `none`). + // + optional> self; + }; + + using package_recursive_map = map; + // Collect dependencies of the specified package, potentially recursively. - // System dependencies go to deps, non-system -- to pkgs, which could be the - // same as deps or NULL, depending on the desired semantics (see the call - // site for details). Find available packages for pkgs and deps and merge - // languages. + // + // Specifically, in the non-recursive mode or in the `separate` recursive + // mode we want all the immediate (system and non-) dependencies in deps. + // Otherwise, if the recursive mode is `full`, then we want all the + // transitive non-system dependencies in pkgs. In both recursive modes we + // also want all the transitive system dependencies in deps. + // + // Or, to put it another way, the system dependencies and those collected + // non-recursively or in the `separate` recursive mode go to the deps + // list. The dependencies collected in the `full` recursive mode go to pkgs + // list. All other dependencies (collected in the `auto` recursive mode) are + // not saved to any of the lists. + // + // Find available packages for pkgs and deps and merge languages. Also save + // the effective recursive modes to package_rec_map (so that the mode from + // the first encounter of the package is used in subsequent). // static void collect_dependencies (const common_options& co, database& db, - packages* pkgs, + packages& pkgs, packages& deps, const string& type, small_vector& langs, const selected_package& p, - bool recursive) + optional rec, + package_recursive_map& package_rec_map) { for (const auto& pr: p.prerequisites) { @@ -173,7 +211,44 @@ namespace bpkg assert (d->state == package_state::configured); bool sys (d->substate == package_substate::system); - packages* ps (sys ? &deps : pkgs); + + // Deduce/save the effective recursive modes for the dependency. + // + // Note: don't change after being saved from the command line + // (--recursive [?]=) or via the first encountered dependent. + // + optional drec; + optional srec; + + if (!sys) + { + package_recursive& pr (package_rec_map[d->name]); + + if (!pr.dependencies) + pr.dependencies = rec; + + if (!pr.self) + pr.self = rec; + + drec = *pr.dependencies; + srec = *pr.self; + } + + // Note 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. + // + packages* ps (!srec || *srec == recursive_mode::separate + ? &deps + : *srec == recursive_mode::full ? &pkgs : nullptr); + + // Collect the package dependencies recursively, if requested, unless + // the package is collected in the separate mode in which case its + // dependencies will be collected later, when its own binary package is + // generated. + // + bool recursive (drec.has_value () && + (!srec || *srec != recursive_mode::separate)); // Skip duplicates. // @@ -185,13 +260,13 @@ namespace bpkg { const selected_package& p (*d); - if (ps != nullptr || (recursive && !sys)) + if (ps != nullptr || recursive) { available_packages aps (find_available_packages (co, db, d)); // Load and merge languages. // - if (recursive && !sys) + if (recursive) { const shared_ptr& ap (aps.front ().first); db.load (*ap, ap->languages_section); @@ -208,8 +283,11 @@ namespace bpkg } } - if (recursive && !sys) - collect_dependencies (co, db, pkgs, deps, type, langs, p, recursive); + if (recursive) + collect_dependencies (co, + db, + pkgs, deps, type, langs, p, + drec, package_rec_map); } } } @@ -222,26 +300,73 @@ namespace bpkg dir_path c (o.directory ()); l4 ([&]{trace << "configuration: " << c;}); - // Verify options. + // Parse and verify options. // - enum class recursive_mode {auto_, full, separate}; + map package_rec_map; optional rec; { diag_record dr; - if (o.recursive_specified ()) + for (const string& m: o.recursive ()) { - const string& m (o.recursive ()); - if (m == "auto") rec = recursive_mode::auto_; else if (m == "full") rec = recursive_mode::full; else if (m == "separate") rec = recursive_mode::separate; - else if (m != "none") + else if (m == "none") rec = nullopt; + else + { + size_t n (m.find ('=')); + + if (n != string::npos) + { + string pm (m, n + 1, m.size () - n - 1); + + optional> prec; + + if (pm == "auto") prec = recursive_mode::auto_; + else if (pm == "full") prec = recursive_mode::full; + else if (pm == "separate") prec = recursive_mode::separate; + else if (pm == "none") prec = optional (); + + if (prec) + { + string p (m, 0, n); + bool dependency (p[0] == '?'); + + if (dependency) + p.erase (0, 1); + + try + { + package_recursive& pr ( + package_rec_map[project_name (move (p))]); + + (dependency ? pr.self : pr.dependencies) = prec; + } + catch (const invalid_argument& e) + { + dr << fail << "invalid package name '" << p + << "' in --recursive mode '" << m << "': " << e; + break; + } + + continue; // Proceed to the next --recursive option. + } + + // Fall through. + } + dr << fail << "unknown --recursive mode '" << m << "'"; + break; + } } - if (o.private_ ()) + // Verify the --private/--recursive options consistency for the simple + // case (no --recursive [?]=). Otherwise, just ignore + // --private if the dependencies are not bundled. + // + if (o.private_ () && package_rec_map.empty ()) { if (!rec) { @@ -249,7 +374,7 @@ namespace bpkg } else if (*rec == recursive_mode::separate) { - dr << fail << "--private specified without --recursive=separate"; + dr << fail << "--private specified with --recursive=separate"; } } @@ -364,9 +489,10 @@ namespace bpkg bool dependent_config (false); auto generate = [&o, &vars, - rec, &spm, + &package_rec_map, &spm, &c, &db, &dependent_config] (const vector& pns, + optional rec, bool first) -> result { // Resolve package names to selected packages and verify they are all @@ -441,28 +567,31 @@ namespace bpkg pkgs.push_back ( package {move (p), move (aps), r.effective_out_root (db.config)}); - // 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. + // Deduce the effective recursive mode for collecting dependencies. // + optional drec; + { + auto i (package_rec_map.find (n)); + + if (i != package_rec_map.end ()) + { + const package_recursive& pr (i->second); + drec = pr.dependencies ? *pr.dependencies : rec; + } + else + drec = rec; + } + collect_dependencies ( o, db, - (!rec || *rec == recursive_mode::separate - ? &deps - : *rec == recursive_mode::full ? &pkgs : nullptr), + pkgs, deps, type, langs, r, - rec.has_value ()); + drec, + package_rec_map); } // Load the package manifest (source of extra metadata). This should be @@ -483,6 +612,15 @@ namespace bpkg if (rec && *rec != recursive_mode::separate) recursive_full = (*rec == recursive_mode::full); + // Only enable private installation subdirectory functionality if the + // dependencies are bundled with the dependent (see the --private option + // for details). + // + bool priv (o.private_ () && + rec && + (*rec == recursive_mode::full || + *rec == recursive_mode::auto_)); + // 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. @@ -494,6 +632,7 @@ namespace bpkg pm, type, langs, recursive_full, + priv, first)); return result {move (r), move (deps), move (pkgs.front ().selected)}; @@ -504,8 +643,9 @@ namespace bpkg // Generate packages for dependencies, recursively, suppressing // duplicates. Note: recursive lambda. // - auto generate_deps = [&generate, &rs] (const packages& deps, - const auto& generate_deps) -> void + auto generate_deps = [&package_rec_map, &generate, &rs] + (const packages& deps, + const auto& generate_deps) -> void { for (const package& d: deps) { @@ -528,19 +668,42 @@ namespace bpkg if (verb >= 1) text << "generating package for dependency " << p->name; - rs.push_back (generate ({p->name}, false /* first */)); - generate_deps (rs.back ().deps, generate_deps); + // The effective recursive modes for the dependency. + // + optional drec; + optional srec; + { + auto i (package_rec_map.find (p->name)); + + // Must have been saved by collect_dependencies(). + // + assert (i != package_rec_map.end () && + i->second.self && + i->second.dependencies); + + drec = *i->second.dependencies; + srec = *i->second.self; + } + + // See collect_dependencies() for details. + // + assert (!srec || *srec == recursive_mode::separate); + + if (srec) + { + rs.push_back (generate ({p->name}, drec, false /* first */)); + generate_deps (rs.back ().deps, generate_deps); + } } }; // Generate top-level package(s). // - rs.push_back (generate (pns, true /* first */)); + rs.push_back (generate (pns, rec, true /* first */)); // Generate dependencies, if requested. // - if (rec && rec == recursive_mode::separate) - generate_deps (rs.back ().deps, generate_deps); + generate_deps (rs.back ().deps, generate_deps); t.commit (); @@ -641,7 +804,11 @@ namespace bpkg } s.end_object (); // os_release - member ("recursive", o.recursive (), "none"); + if (rec) + member ("recursive", *rec == recursive_mode::auto_ ? "auto" : + *rec == recursive_mode::full ? "full" : + "separate"); + if (o.private_ ()) s.member ("private", true); if (dependent_config) s.member ("dependent_config", true); diff --git a/bpkg/system-package-manager-archive.cxx b/bpkg/system-package-manager-archive.cxx index d46e6d6..43d77f9 100644 --- a/bpkg/system-package-manager-archive.cxx +++ b/bpkg/system-package-manager-archive.cxx @@ -307,6 +307,7 @@ namespace bpkg const string& pt, const small_vector& langs, optional recursive_full, + bool priv, bool first) -> binary_files { tracer trace ("system_package_manager_archive::generate"); @@ -331,7 +332,6 @@ namespace bpkg true /* ignore_iteration */)); bool lib (pt == "lib"); - bool priv (ops->private_ ()); // Private installation. // Return true if this package uses the specified language, only as // interface language if intf_only is true. diff --git a/bpkg/system-package-manager-archive.hxx b/bpkg/system-package-manager-archive.hxx index 01c4a2a..e4ec302 100644 --- a/bpkg/system-package-manager-archive.hxx +++ b/bpkg/system-package-manager-archive.hxx @@ -26,6 +26,7 @@ namespace bpkg const string&, const small_vector&, optional, + bool, bool) override; virtual optional diff --git a/bpkg/system-package-manager-debian.cxx b/bpkg/system-package-manager-debian.cxx index b772352..256df28 100644 --- a/bpkg/system-package-manager-debian.cxx +++ b/bpkg/system-package-manager-debian.cxx @@ -1986,6 +1986,7 @@ namespace bpkg const string& pt, const small_vector& langs, optional recursive_full, + bool priv, bool first) -> binary_files { tracer trace ("system_package_manager_debian::generate"); @@ -2017,7 +2018,6 @@ namespace bpkg const available_packages& aps (pkgs.front ().available); bool lib (pt == "lib"); - bool priv (ops_->private_ ()); // Private installation. // For now we only know how to handle libraries with C-common interface // languages. But we allow other implementation languages. diff --git a/bpkg/system-package-manager-debian.hxx b/bpkg/system-package-manager-debian.hxx index 336f7a7..97ad825 100644 --- a/bpkg/system-package-manager-debian.hxx +++ b/bpkg/system-package-manager-debian.hxx @@ -144,6 +144,7 @@ namespace bpkg const string&, const small_vector&, optional, + bool, bool) override; public: diff --git a/bpkg/system-package-manager-fedora.cxx b/bpkg/system-package-manager-fedora.cxx index b4e9e10..ba67f82 100644 --- a/bpkg/system-package-manager-fedora.cxx +++ b/bpkg/system-package-manager-fedora.cxx @@ -2537,6 +2537,7 @@ namespace bpkg const string& pt, const small_vector& langs, optional recursive_full, + bool priv, bool /* first */) -> binary_files { tracer trace ("system_package_manager_fedora::generate"); @@ -2555,7 +2556,6 @@ namespace bpkg const available_packages& aps (pkgs.front ().available); bool lib (pt == "lib"); - bool priv (ops_->private_ ()); // Private installation. // For now we only know how to handle libraries with C-common interface // languages. But we allow other implementation languages. diff --git a/bpkg/system-package-manager-fedora.hxx b/bpkg/system-package-manager-fedora.hxx index 986d203..48e6cfd 100644 --- a/bpkg/system-package-manager-fedora.hxx +++ b/bpkg/system-package-manager-fedora.hxx @@ -212,6 +212,7 @@ namespace bpkg const string&, const small_vector&, optional, + bool, bool) override; public: diff --git a/bpkg/system-package-manager.hxx b/bpkg/system-package-manager.hxx index 7f5af7d..9c0cd58 100644 --- a/bpkg/system-package-manager.hxx +++ b/bpkg/system-package-manager.hxx @@ -185,6 +185,9 @@ namespace bpkg // If the result is empty, assume the prepare-only mode (or similar) with // appropriate result diagnostics having been already issued. // + // If the private argument is true, then enable the private installation + // subdirectory functionality (see the --private option for details). + // // Note that this function may be called multiple times in the // --recursive=separate mode. In this case the first argument indicates // whether this is the first call (can be used, for example, to adjust the @@ -220,6 +223,7 @@ namespace bpkg const string& type, const small_vector&, optional recursive_full, + bool private_, bool first) = 0; public: -- cgit v1.1