From 8166e873c0cad830001ff84a15450cd9a7958c19 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 15 Feb 2023 11:39:38 +0200 Subject: WIP --- bpkg/buildfile | 3 + bpkg/pkg-bindist.cli | 45 +++++++- bpkg/pkg-bindist.cxx | 30 +++--- bpkg/system-package-manager-debian.cxx | 189 +++++++++++++++++++++++++++++++-- bpkg/system-package-manager-debian.hxx | 15 +-- bpkg/system-package-manager-fedora.cxx | 7 +- bpkg/system-package-manager-fedora.hxx | 7 +- bpkg/system-package-manager.hxx | 14 +-- doc/cli.sh | 12 ++- 9 files changed, 275 insertions(+), 47 deletions(-) diff --git a/bpkg/buildfile b/bpkg/buildfile index 1531b9a..3ba9ea6 100644 --- a/bpkg/buildfile +++ b/bpkg/buildfile @@ -215,6 +215,9 @@ if $cli.configured cli.cxx{pkg-build-options}: cli.options += --class-doc \ bpkg::pkg_build_pkg_options=exclude-base --generate-modifier + cli.cxx{pkg-bindist-options}: cli.options += --class-doc \ +bpkg::pkg_bindist_debian_options=exclude-base + # Avoid generating CLI runtime and empty inline file for help topics. # cli.cxx{repository-signing repository-types argument-grouping \ diff --git a/bpkg/pkg-bindist.cli b/bpkg/pkg-bindist.cli index ad743d9..7dd5832 100644 --- a/bpkg/pkg-bindist.cli +++ b/bpkg/pkg-bindist.cli @@ -48,11 +48,50 @@ namespace bpkg " } - // @@ Have section for each package manager with options? + // Place distribution-specific options into separate classes in case one day + // we want to only pass their own options to each implementation. + // + class pkg_bindist_debian_options + { + "\h|PKG-BINDIST DEBIAN OPTIONS|" + + string --debian-section + { + "", + "Alternative \cb{Section} field value in the \cb{control} file for + the main binary package. The default is either \cb{libs} or \cb{devel}, + depending on the package type." + } + + string --debian-priority + { + "", + "Alternative \cb{Priority} field value in the \cb{control} file. The + default is \cb{optional}." + } + + string --debian-maintainer + { + "", + "Alternative \cb{Maintainer} field value in the \cb{control} file. The + default is the \cb{package-email} value from package \cb{manifest}." + } + + string --debian-architecture + { + "", + "Alternative \cb{Architecture} field value in the \cb{control} file for + the main binary package. The default is \cb{any}." + } + }; - class pkg_bindist_options: configuration_options + // NOTE: remember to add the corresponding `--class-doc ...=exclude-base` + // (both in bpkg/ and doc/) if adding a new base class. + // + class pkg_bindist_options: configuration_options, + pkg_bindist_debian_options { - "\h|PKG-BINDIST OPTIONS|" + "\h|PKG-BINDIST COMMON OPTIONS|" string --distribution { diff --git a/bpkg/pkg-bindist.cxx b/bpkg/pkg-bindist.cxx index 289f34b..ac774b1 100644 --- a/bpkg/pkg-bindist.cxx +++ b/bpkg/pkg-bindist.cxx @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -269,6 +270,20 @@ namespace bpkg t.commit (); + // 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 ().first); + + 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;})); + // Note that we shouldn't need to install anything or use sudo. // unique_ptr spm ( @@ -286,20 +301,10 @@ namespace bpkg // @@ TODO: pass/handle --private. - // Note that we certain move the arguments to allow the implementation to - // rearrange things if/as convenient. - // - spm->generate (move (pkgs), - move (deps), - move (vars), - out, - rec); - + spm->generate (pkgs, deps, vars, pm, out, rec); - // @@ TODO: need to save name/version (or change the output, maybe - // to something returned by spm? + // @@ TODO: change the output, maybe to something returned by spm? // -#if 0 if (verb && !o.no_result ()) { const selected_package& p (*pkgs.front ().first); @@ -307,7 +312,6 @@ namespace bpkg text << "generated " << spm->os_release.name_id << " package for " << p.name << '/' << p.version; } -#endif return 0; } diff --git a/bpkg/system-package-manager-debian.cxx b/bpkg/system-package-manager-debian.cxx index 88bc6df..0670e24 100644 --- a/bpkg/system-package-manager-debian.cxx +++ b/bpkg/system-package-manager-debian.cxx @@ -1830,9 +1830,10 @@ namespace bpkg // system and install all the packages directly from their bpkg locations. // void system_package_manager_debian:: - generate (packages&& pkgs, - packages&& deps, - strings&&, + generate (const packages& pkgs, + const packages& deps, + const strings&, + const package_manifest& pm, const dir_path& out, optional) { @@ -1845,7 +1846,13 @@ namespace bpkg const shared_ptr& sp (pkgs.front ().first); const package_name& pn (sp->name); const version& pv (sp->version); - package_status s (map_package (pn, pv, pkgs.front ().second)); + + // @@ TODO: need to factor this to system_package_manager. + // + bool lib (pn.string ().compare (0, 3, "lib") == 0 && pn.string ().size () > 3); + + const available_packages& aps (pkgs.front ().second); + package_status st (map_package (pn, pv, aps)); vector sdeps; sdeps.reserve (deps.size ()); @@ -1898,12 +1905,12 @@ namespace bpkg }; diag_record dr (text); - print_status (dr, s); + print_status (dr, st); - for (const package_status& ds: sdeps) + for (const package_status& st: sdeps) { dr << "\n "; - print_status (dr, ds); + print_status (dr, st); } } @@ -1934,6 +1941,172 @@ namespace bpkg dir_path deb (src / dir_path ("debian")); mk_p (deb); + // The control file. + // + // See the "Control files and their fields" chapter in the Debian Policy + // Manual for details (for example, which fields are mandatory). + // + // Note that we try to do a reasonably thorough job (e.g., filling in + // sections, etc) with the view that this can be used as a starting point + // for manual packaging (and perhaps we could add a mode for this in the + // future). + // + path ctrl (deb / "control"); + try + { + ofdstream os (ctrl); + + // First comes the general (source package) stanza. + // + // Note that Priority semantics is not the same as our priority. Rather + // it should reflect the overall functionality of the package. Our + // priority is more appropriately mapped to urgency in the changelog. + // + // If this is not a library, then by default we assume its some kind of + // a development tool and use the devel section. + // + string section ( + ops_->debian_section_specified () ? ops_->debian_section () : + lib ? "libs" : + "devel"); + + string priority ( + ops_->debian_priority_specified () ? ops_->debian_priority () : + "optional"); + + string maintainer ( + ops_->debian_maintainer_specified () ? ops_->debian_maintainer () : + pm.package_email ? static_cast (*pm.package_email) : + pm.email ? static_cast (*pm.email) : + string ()); + + if (maintainer.empty ()) + fail << "unable to determine package maintainer from manifest" << + info << "specify explicitly with --debian-maintainer"; + + optional homepage (pm.package_url ? pm.package_url->string () : + pm.url ? pm.url->string () : + optional ()); + + os << "Source: " << pn.string () << '\n' + << "Section: " << section << '\n' + << "Priority: " << priority << '\n' + << "Maintainer: " << maintainer << '\n' + << "Standards-Version: " << "4.6.2" << '\n' + << "Build-Depends: " << "debhelper-compat (= 13)" << '\n' + << "Rules-Requires-Root: " << "no" << '\n'; + if (homepage) + os << "Homepage: " << *homepage << '\n'; + if (pm.src_url) + os << "Vcs-Browser: " << pm.src_url->string () << '\n'; + + // Then we have one or more binary package stanzas. + // + // Note that values from the source package (such as Section, Priority) + // are used as defaults for the binary packages. + // + // We cannot easily detect architecture-independent packages (think + // libbutl.bash) and providing an option feels like the best we can do. + // Note that the value `any` means architecture-dependent while `all` + // means architecture-independent. + // + // The Multi-Arch hint can be `same` or `foreign`. The former means that + // a separate copy of the package may be installed for each architecture + // (e.g., library) while the latter -- that a single copy may be used by + // all architectures (e.g., executable, -doc, -common). Not that for + // some murky reasons Multi-Arch:foreign needs to be explicitly + // specified for Architecture:all. + // + // The Description field is quite messy: it requires both the short + // description (our summary) as a first line and a long description (our + // description) as the following lines in the multiline format. + // Converting our description to the Debian format is not going to be + // easy: it can be arbitrarily long and may not even be plain text (it's + // commonly the contents of the README.md file). So for now we fake it + // with a description of the package component. + // + string arch (ops_->debian_architecture_specified () + ? ops_->debian_architecture () + : "any"); + + string march (arch == "all" || !lib ? "foreign" : "same"); + + os << '\n' + << "Package: " << st.main << '\n' + << "Architecture: " << arch << '\n' + << "Multi-Arch: " << march << '\n' + << "Description: " << pm.summary << '\n' + << " This package contains the runtime files." << '\n'; + + // @@ Depends: common (if any) + + if (!st.dev.empty ()) + { + // Feels like the architecture should be the same as for the main + // package. + // + os << '\n' + << "Package: " << st.dev << '\n' + << "Section: " << (lib ? "libdevel" : "devel") << '\n' + << "Architecture: " << arch << '\n' + << "Multi-Arch: " << march << '\n'; + if (!st.doc.empty ()) + os << "Suggests: " << st.doc << '\n'; + os << "Description: " << pm.summary << '\n' + << " This package contains the development files." << '\n'; + + // @@ Depends: main + } + + if (!st.doc.empty ()) + { + os << '\n' + << "Package: " << st.doc << '\n' + << "Section: " << "doc" << '\n' + << "Architecture: " << "all" << '\n' + << "Multi-Arch: " << "foreign" << '\n' + << "Description: " << pm.summary << '\n' + << " This package contains the documentation." << '\n'; + } + + if (!st.dbg.empty ()) + { + os << '\n' + << "Package: " << st.dbg << '\n' + << "Section: " << "debug" << '\n' + << "Priority: " << "extra" << '\n' + << "Architecture: " << arch << '\n' + << "Multi-Arch: " << march << '\n' + << "Description: " << pm.summary << '\n' + << " This package contains the debugging information." << '\n'; + + // @@ Depends: main + } + + if (!st.common.empty ()) + { + // Generally, this package is not necessarily architecture-independent + // (for example, it could contain something shared between multiple + // binary packages produced from the same source package rather than + // something shared between all the architectures of a binary + // package). But seeing that we always generate one binary package, + // for us it only makes sense as architecture-independent. + // + os << '\n' + << "Package: " << st.common << '\n' + << "Architecture: " << "all" << '\n' + << "Multi-Arch: " << "foreign" << '\n' + << "Description: " << pm.summary << '\n' + << " This package contains the architecture-independent files." << '\n'; + } + + os.close (); + } + catch (const io_error& e) + { + fail << "unable to write to " << ctrl << ": " << e; + } + // The rules makefile. Note that it must be executable. // path rules (deb / "rules"); @@ -1959,6 +2132,8 @@ namespace bpkg else if (verb >= 2) os << "export DH_VERBOSE=1\n"; + // @@ TODO: remember to override config.install.sudo. + os.close (); } catch (const io_error& e) diff --git a/bpkg/system-package-manager-debian.hxx b/bpkg/system-package-manager-debian.hxx index 8fccb55..3708faa 100644 --- a/bpkg/system-package-manager-debian.hxx +++ b/bpkg/system-package-manager-debian.hxx @@ -23,10 +23,10 @@ namespace bpkg // For background, a library in Debian is normally split up into several // packages: the shared library package (e.g., libfoo1 where 1 is the ABI // version), the development files package (e.g., libfoo-dev), the - // documentation files package (e.g., libfoo-doc), the debug symbols - // package (e.g., libfoo1-dbg), and the architecture-independent files - // (e.g., libfoo1-common). All the packages except -dev are optional - // and there is quite a bit of variability here. Here are a few examples: + // documentation files package (e.g., libfoo-doc), the debug symbols package + // (e.g., libfoo1-dbg), and the (usually) architecture-independent files + // (e.g., libfoo1-common). All the packages except -dev are optional and + // there is quite a bit of variability here. Here are a few examples: // // libsqlite3-0 libsqlite3-dev // @@ -130,9 +130,10 @@ namespace bpkg pkg_install (const vector&) override; virtual void - generate (packages&&, - packages&&, - strings&&, + generate (const packages&, + const packages&, + const strings&, + const package_manifest&, const dir_path&, optional) override; diff --git a/bpkg/system-package-manager-fedora.cxx b/bpkg/system-package-manager-fedora.cxx index 3178e4e..381ebfe 100644 --- a/bpkg/system-package-manager-fedora.cxx +++ b/bpkg/system-package-manager-fedora.cxx @@ -1783,9 +1783,10 @@ namespace bpkg } void system_package_manager_fedora:: - generate (packages&&, - packages&&, - strings&&, + generate (const packages&, + const packages&, + const strings&, + const package_manifest&, const dir_path&, optional) { diff --git a/bpkg/system-package-manager-fedora.hxx b/bpkg/system-package-manager-fedora.hxx index 7692822..df0e4f6 100644 --- a/bpkg/system-package-manager-fedora.hxx +++ b/bpkg/system-package-manager-fedora.hxx @@ -197,9 +197,10 @@ namespace bpkg pkg_install (const vector&) override; virtual void - generate (packages&&, - packages&&, - strings&&, + generate (const packages&, + const packages&, + const strings&, + const package_manifest&, const dir_path&, optional) override; diff --git a/bpkg/system-package-manager.hxx b/bpkg/system-package-manager.hxx index 91f9a49..ad41174 100644 --- a/bpkg/system-package-manager.hxx +++ b/bpkg/system-package-manager.hxx @@ -4,9 +4,6 @@ #ifndef BPKG_SYSTEM_PACKAGE_MANAGER_HXX #define BPKG_SYSTEM_PACKAGE_MANAGER_HXX -#include // version -#include - #include #include @@ -161,6 +158,10 @@ namespace bpkg // all the packages in deps. For non-system packages there is always a // single available package that corresponds to the selected package. // + // The passed package manifest corresponds to the first package in pkgs + // (normally used as a source of additional package metadata such as + // summary, emails, urls, etc). + // // See the pkg-bindist(1) man page and the pkg_bindist() function // implementation for background and details. // @@ -170,9 +171,10 @@ namespace bpkg enum class recursive_mode {auto_, full}; virtual void - generate (packages&& pkgs, - packages&& deps, - strings&& vars, + generate (const packages& pkgs, + const packages& deps, + const strings& vars, + const package_manifest&, const dir_path& out, optional) = 0; diff --git a/doc/cli.sh b/doc/cli.sh index dd3cb37..4f7ea18 100755 --- a/doc/cli.sh +++ b/doc/cli.sh @@ -78,14 +78,16 @@ compile "bpkg" $o --output-prefix "" --class-doc bpkg::commands=short --class-do compile "pkg-build" $o --class-doc bpkg::pkg_build_pkg_options=exclude-base +compile "pkg-bindist" $o --class-doc bpkg::pkg_bindist_debian_options=exclude-base + # NOTE: remember to update a similar list in buildfile and bpkg.cli as well as # the help topics sections in bpkg/buildfile and help.cxx. # -pages="cfg-create cfg-info cfg-link cfg-unlink help pkg-bindist pkg-clean \ -pkg-configure pkg-disfigure pkg-drop pkg-fetch pkg-checkout pkg-install \ -pkg-purge pkg-status pkg-test pkg-uninstall pkg-unpack pkg-update pkg-verify \ -rep-add rep-remove rep-list rep-create rep-fetch rep-info repository-signing \ -repository-types argument-grouping default-options-files" +pages="cfg-create cfg-info cfg-link cfg-unlink help pkg-clean pkg-configure \ +pkg-disfigure pkg-drop pkg-fetch pkg-checkout pkg-install pkg-purge pkg-status \ +pkg-test pkg-uninstall pkg-unpack pkg-update pkg-verify rep-add rep-remove \ +rep-list rep-create rep-fetch rep-info repository-signing repository-types \ +argument-grouping default-options-files" for p in $pages; do compile $p $o -- cgit v1.1