aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2023-04-10 14:38:29 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2023-04-10 14:38:29 +0200
commit089893e5b5f139d6bfe8c0817b639c9290a6a551 (patch)
tree7816aa3f85dd3c54b05a769e086e0b836c286cd7
parenta65b527f93f2ce0c0023618baf1384b83915a54b (diff)
Add --archive-split option to pkg-bindist command
It allows to split the installation into multiple archives using the config.install.filter functionality.
-rw-r--r--bpkg/pkg-bindist.cli29
-rw-r--r--bpkg/system-package-manager-archive.cxx389
2 files changed, 206 insertions, 212 deletions
diff --git a/bpkg/pkg-bindist.cli b/bpkg/pkg-bindist.cli
index 4192867..65fbeae 100644
--- a/bpkg/pkg-bindist.cli
+++ b/bpkg/pkg-bindist.cli
@@ -637,7 +637,8 @@ namespace bpkg
Another mechanism that can useful when generating archive packages is the
ability to filter the files being installed. This, for example, can be
used to create binary packages that don't contain any development-related
- files. See \l{b#install-filter Installation Filtering} for details.
+ files. See \l{b#install-filter Installation Filtering} for details. See
+ also the \cb{--archive-split} option.
The installation archive package can be generated for a target other than
the host by specifying the target triplet with the \cb{--architecture}
@@ -749,6 +750,17 @@ namespace bpkg
is still overridden with the \cb{--archive-install-root} option value
if specified."
}
+
+ std::map<string, string> --archive-split
+ {
+ "<key>=<filt>",
+ "Split the installation into multiple binary packages. Specifically,
+ for each <key>=<filt> pair, perform the \cb{install} operation with
+ \c{\b{config.install.filter=}\i{filt}} and package the resulting files
+ as \ci{package-key-version-build_metadata} omitting the \ci{-key} part
+ if <key> is empty. Note that wildcard patterns in <filt> must be
+ quoted. See \l{b#install-filter Installation Filtering} for background."
+ }
};
"
@@ -850,13 +862,14 @@ namespace bpkg
\cb{static.rpm}, \cb{doc.rpm}, \cb{common.rpm}, and \cb{debuginfo.rpm}; see
\l{bpkg#bindist-mapping-fedora-produce Fedora Package Mapping for
Production} for background. For the \cb{archive} distribution this is the
- archive type (\cb{--archive-type}), for example, \cb{tar.xz} or \cb{zip}.
-
- The \cb{package::system_version} and \cb{file::system_name} members are
- absent if not applicable to the distribution (for example, \cb{archive}).
- The \cb{file::system_name} member is also absent if the file is not a
- binary package (for example, \cb{.changes} and \cb{.buildid} files in the
- \cb{debian} distribution).
+ archive type (\cb{--archive-type}), for example, \cb{tar.xz} or \cb{zip},
+ potentially prefixed with \ci{key} if the \cb{--archive-split}
+ functionality is used, for example, \cb{dev.tar.xz}.
+
+ The \cb{package::system_version} and/or \cb{file::system_name} members are
+ absent if not applicable to the distribution. The \cb{file::system_name}
+ member is also absent if the file is not a binary package (for example,
+ \cb{.changes} and \cb{.buildid} files in the \cb{debian} distribution).
"
// NOTE: remember to add the corresponding `--class-doc ...=exclude-base`
diff --git a/bpkg/system-package-manager-archive.cxx b/bpkg/system-package-manager-archive.cxx
index e0f0ad2..1278330 100644
--- a/bpkg/system-package-manager-archive.cxx
+++ b/bpkg/system-package-manager-archive.cxx
@@ -504,145 +504,161 @@ namespace bpkg
// libhello-1.2.3-x86_64-windows10-msvc17.4
// libhello-1.2.3-x86_64-debian11-gcc12-rust1.62
//
- string base (pn.string () + '-' + pvs);
+ bool md_s (ops->archive_build_meta_specified ());
+ const string& md (ops->archive_build_meta ());
+
+ bool md_f (false);
+ bool md_b (false);
+ if (md_s && !md.empty ())
{
- bool md_s (ops->archive_build_meta_specified ());
- const string& md (ops->archive_build_meta ());
+ md_f = md.front () == '+';
+ md_b = md.back () == '+';
- bool md_f (false);
- bool md_b (false);
- if (md_s && !md.empty ())
- {
- md_f = md.front () == '+';
- md_b = md.back () == '+';
+ if (md_f && md_b) // Note: covers just `+`.
+ fail << "invalid build metadata '" << md << "'";
+ }
- if (md_f && md_b) // Note: covers just `+`.
- fail << "invalid build metadata '" << md << "'";
- }
+ vector<reference_wrapper<const pair<const string, string>>> langrt;
+ if (!md_s || md_f || md_b)
+ {
+ // First collect the interface languages and then add implementation.
+ // This way if different languages map to the same runtimes (e.g., C and
+ // C++ mapped to gcc12), then we will always prefer the interface
+ // version over the implementation (which could be different, for
+ // example, libstdc++6 vs libstdc++-12-dev; but it's not clear how this
+ // will be specified, won't they end up with different names as opposed
+ // to gcc6 and gcc12 -- still fuzzy/unclear).
+ //
+ // @@ We will need to split id and version to be able to pick the
+ // highest version.
+ //
+ // @@ Maybe we should just do "soft" version like in <distribution>?
+ //
+ // Note that we allow multiple values for the same language to support
+ // cases like --archive-lang cc=gcc12 --archive-lang cc=g++12.
+ //
- if (md_s && !(md_f || md_b))
- {
- if (!md.empty ())
- base += '-' + md;
- }
- else
+ auto find = [] (const std::multimap<string, string>& m, const string& n)
{
- if (md_b)
+ auto p (m.equal_range (n));
+
+ if (p.first == p.second)
{
- base += '-';
- base.append (md, 0, md.size () - 1);
+ // If no mapping for c/c++, fallback to cc.
+ //
+ if (n == "c" || n == "c++")
+ p = m.equal_range ("cc");
}
- if (!ops->archive_no_cpu ())
- base += '-' + target.cpu;
-
- if (!ops->archive_no_os ())
- base += '-' + os_release.name_id + os_release.version_id;
+ return p;
+ };
- // First collect the interface languages and then add implementation.
- // This way if different languages map to the same runtimes (e.g., C
- // and C++ mapped to gcc12), then we will always prefer the interface
- // version over the implementation (which could be different, for
- // example, libstdc++6 vs libstdc++-12-dev; but it's not clear how
- // this will be specified, won't they end up with different names as
- // opposed to gcc6 and gcc12 -- still fuzzy/unclear).
- //
- // @@ We will need to split id and version to be able to pick the
- // highest version.
- //
- // @@ Maybe we should just do "soft" version like in <distribution>?
- //
- // Note that we allow multiple values for the same language to support
- // cases like --archive-lang cc=gcc12 --archive-lang cc=g++12.
+ auto add = [&langrt] (const pair<const string, string>& p)
+ {
+ // Suppress duplicates.
//
- vector<reference_wrapper<const pair<const string, string>>> langrt;
-
- auto find = [] (const std::multimap<string, string>& m,
- const string& n)
+ if (find_if (langrt.begin (), langrt.end (),
+ [&p] (const pair<const string, string>& x)
+ {
+ // @@ TODO: keep highest version.
+ return p.second == x.second;
+ }) == langrt.end ())
{
- auto p (m.equal_range (n));
+ langrt.push_back (p);
+ }
+ };
- if (p.first == p.second)
- {
- // If no mapping for c/c++, fallback to cc.
- //
- if (n == "c" || n == "c++")
- p = m.equal_range ("cc");
- }
+ auto& implm (ops->archive_lang_impl ());
- return p;
- };
+ // The interface/implementation distinction is only relevant to
+ // libraries. For everything else we treat all the languages as
+ // implementation.
+ //
+ if (lib)
+ {
+ auto& intfm (ops->archive_lang ());
- auto add = [&langrt] (const pair<const string, string>& p)
+ for (const language& l: langs)
{
- // Suppress duplicates.
- //
- if (find_if (langrt.begin (), langrt.end (),
- [&p] (const pair<const string, string>& x)
- {
- // @@ TODO: keep highest version.
+ if (l.impl)
+ continue;
- return p.second == x.second;
- }) == langrt.end ())
- {
- langrt.push_back (p);
- }
- };
+ auto p (find (intfm, l.name));
- auto& implm (ops->archive_lang_impl ());
+ if (p.first == p.second)
+ p = find (implm, l.name);
- // The interface/implementation distinction is only relevant to
- // libraries. For everything else we treat all the languages as
- // implementation.
- //
- if (lib)
- {
- auto& intfm (ops->archive_lang ());
+ if (p.first == p.second)
+ fail << "no runtime mapping for language " << l.name <<
+ info << "consider specifying with --archive-lang[-impl]" <<
+ info << "or alternatively specify --archive-build-meta";
- for (const language& l: langs)
+ for (auto i (p.first); i != p.second; ++i)
{
- if (l.impl)
- continue;
+ if (i->second.empty ())
+ continue; // Unimportant.
+
+ add (*i);
+ }
+ }
+ }
- auto p (find (intfm, l.name));
+ for (const language& l: langs)
+ {
+ if (lib && !l.impl)
+ continue;
- if (p.first == p.second)
- p = find (implm, l.name);
+ auto p (find (implm, l.name));
- if (p.first == p.second)
- fail << "no runtime mapping for language " << l.name <<
- info << "consider specifying with --archive-lang[-impl]" <<
- info << "or alternatively specify --archive-build-meta";
+ if (p.first == p.second)
+ continue; // Unimportant.
- for (auto i (p.first); i != p.second; ++i)
- {
- if (i->second.empty ())
- continue; // Unimportant.
+ for (auto i (p.first); i != p.second; ++i)
+ {
+ if (i->second.empty ())
+ continue; // Unimportant.
- add (*i);
- }
- }
+ add (*i);
}
+ }
+ }
- for (const language& l: langs)
- {
- if (lib && !l.impl)
- continue;
+ // If there is no split, reduce to empty key and empty filter.
+ //
+ binary_files r;
+ for (const pair<const string, string>& kf:
+ ops->archive_split_specified ()
+ ? ops->archive_split ()
+ : std::map<string, string> {{string (), string ()}})
+ {
+ string sys_name (pn.string ());
- auto p (find (implm, l.name));
+ if (!kf.first.empty ())
+ sys_name += '-' + kf.first;
- if (p.first == p.second)
- continue; // Unimportant.
+ string base (sys_name);
- for (auto i (p.first); i != p.second; ++i)
- {
- if (i->second.empty ())
- continue; // Unimportant.
+ base += '-' + pvs;
- add (*i);
- }
+ if (md_s && !(md_f || md_b))
+ {
+ if (!md.empty ())
+ base += '-' + md;
+ }
+ else
+ {
+ if (md_b)
+ {
+ base += '-';
+ base.append (md, 0, md.size () - 1);
}
+ if (!ops->archive_no_cpu ())
+ base += '-' + target.cpu;
+
+ if (!ops->archive_no_os ())
+ base += '-' + os_release.name_id + os_release.version_id;
+
for (const pair<const string, string>& p: langrt)
base += '-' + p.second;
@@ -652,105 +668,65 @@ namespace bpkg
base.append (md, 1, md.size () - 1);
}
}
- }
- dir_path dst (out / dir_path (base));
- mk_p (dst);
+ dir_path dst (out / dir_path (base));
+ mk_p (dst);
- // Update and install.
- //
- // In a sense, this is a special version of pkg-install.
- //
- {
- strings dirs;
- for (const package& p: pkgs)
- dirs.push_back (p.out_root.representation ());
-
- run_b (*ops,
- verb_b::normal,
- (ops->jobs_specified ()
- ? strings ({"--jobs", to_string (ops->jobs ())})
- : strings ()),
- "config.install.chroot='" + dst.representation () + '\'',
- (ovr_install ? "config.install.sudo=[null]" : nullptr),
- config,
- "!config.install.scope=" + scope,
- "install:",
- dirs);
-
- // @@ TODO: call install.json? Or manifest-install.json. Place in data/
- // (would need support in build2 to use install.* values)?
+ // Install.
//
-#if 0
- args.push_back ("!config.install.manifest=-");
-#endif
- }
-
- // @@ TODO: metadata manifest.
- //
- // @@ TODO: add homepage, maintainer to manifest?
- // @@ TODO: also add dependencies?
- // @@ TODO: also add timestamp, priority like in Debian?
- // @@ TODO: also add licenses like in Debian?
- // @@ TODO: include languages in manifest (both intf and impl)?
- //
-#if 0
- string homepage (pm.package_url ? pm.package_url->string () :
- pm.url ? pm.url->string () :
- string ());
-
- string maintainer;
- if ( const email* e = (pm.package_email ? &*pm.package_email :
- pm.email ? &*pm.email :
- nullptr))
- {
- // In certain places (e.g., changelog), Debian expect this to be in the
- // `John Doe <john@example.org>` form while we often specify just the
- // email address (e.g., to the mailing list). Try to detect such a case
- // and complete it to the desired format.
+ // In a sense, this is a special version of pkg-install.
//
- if (e->find (' ') == string::npos && e->find ('@') != string::npos)
{
- // Try to use comment as name, if any.
+ strings dirs;
+ for (const package& p: pkgs)
+ dirs.push_back (p.out_root.representation ());
+
+ string filter;
+ if (!kf.second.empty ())
+ filter = "config.install.filter=" + kf.second;
+
+ run_b (*ops,
+ verb_b::normal,
+ (ops->jobs_specified ()
+ ? strings ({"--jobs", to_string (ops->jobs ())})
+ : strings ()),
+ "config.install.chroot='" + dst.representation () + '\'',
+ (ovr_install ? "config.install.sudo=[null]" : nullptr),
+ (!filter.empty () ? filter.c_str () : nullptr),
+ config,
+ "!config.install.scope=" + scope,
+ "install:",
+ dirs);
+
+ // @@ TODO: call install.json? Or manifest-install.json. Place in
+ // data/ (would need support in build2 to use install.* values)?
//
- if (!e->comment.empty ())
- maintainer = e->comment;
- else
- maintainer = pn.string () + " package maintainer";
-
- maintainer += " <" + *e + '>';
- }
- else
- maintainer = *e;
- }
+#if 0
+ args.push_back ("!config.install.manifest=-");
#endif
+ }
- if (ops->archive_prepare_only ())
- {
- if (verb >= 1)
- text << "prepared " << dst;
-
- return binary_files {};
- }
+ if (ops->archive_prepare_only ())
+ {
+ if (verb >= 1)
+ text << "prepared " << dst;
- // Create the archive.
- //
- // Should the default archive type be based on host or target? I guess
- // that depends on where the result will be unpacked, and it feels like
- // target is more likely.
- //
- // @@ What about the ownerhip of the resulting file in the archive?
- // We don't do anything for source archives, not sure why we should
- // do something here.
- //
- binary_files r;
- {
- const strings& ts (
- ops->archive_type_specified ()
- ? ops->archive_type ()
- : strings {target.class_ == "windows" ? "zip" : "tar.xz"});
+ continue;
+ }
- for (string t: ts)
+ // Create the archive.
+ //
+ // Should the default archive type be based on host or target? I guess
+ // that depends on where the result will be unpacked, and it feels like
+ // target is more likely.
+ //
+ // @@ What about the ownerhip of the resulting file in the archive?
+ // We don't do anything for source archives, not sure why we should
+ // do something here.
+ //
+ for (string t: (ops->archive_type_specified ()
+ ? ops->archive_type ()
+ : strings {target.class_ == "windows" ? "zip" : "tar.xz"}))
{
// Help the user out if the extension is specified with the leading
// dot.
@@ -758,18 +734,23 @@ namespace bpkg
if (t.size () > 1 && t.front () == '.')
t.erase (0, 1);
- // Using archive type as file type seems appropriate.
- //
path f (archive (out, base, t));
- r.push_back (binary_file {move (t), move (f), "" /* system_name */});
+
+ // Using archive type as file type seems appropriate. Add key before
+ // the archive type, if any.
+ //
+ if (!kf.first.empty ())
+ t = kf.first + '.' + t;
+
+ r.push_back (binary_file {move (t), move (f), sys_name});
}
- }
- // Cleanup intermediate files unless requested not to.
- //
- if (!ops->keep_output ())
- {
- rm_r (dst);
+ // Cleanup intermediate files unless requested not to.
+ //
+ if (!ops->keep_output ())
+ {
+ rm_r (dst);
+ }
}
return r;