From ceb8d922d6cadefd835278d20184348b1b1dbf98 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Mon, 20 Mar 2023 21:50:15 +0300 Subject: Don't generate main binary package for header-only libraries on Debian and Fedora --- bpkg/pkg-build.cxx | 2 +- bpkg/system-package-manager-archive.cxx | 7 +- bpkg/system-package-manager-debian.cxx | 459 +++++++++++++++++++++-------- bpkg/system-package-manager-debian.hxx | 4 +- bpkg/system-package-manager-fedora.cxx | 506 ++++++++++++++++++++++---------- bpkg/system-package-manager-fedora.hxx | 4 + doc/manual.cli | 31 +- 7 files changed, 722 insertions(+), 291 deletions(-) diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx index f44ba15..cc69b58 100644 --- a/bpkg/pkg-build.cxx +++ b/bpkg/pkg-build.cxx @@ -1755,7 +1755,7 @@ namespace bpkg { diag_record dr (fail); - dr << "no installed " << (o.sys_install () ? " or available " : "") + dr << "no installed " << (o.sys_install () ? "or available " : "") << "system package for " << nm; if (!o.sys_install ()) diff --git a/bpkg/system-package-manager-archive.cxx b/bpkg/system-package-manager-archive.cxx index 0df4580..b8df860 100644 --- a/bpkg/system-package-manager-archive.cxx +++ b/bpkg/system-package-manager-archive.cxx @@ -313,6 +313,11 @@ namespace bpkg const package_name& pn (sp->name); const version& pv (sp->version); + // Use version without iteration in paths, etc. + // + string pvs (pv.string (false /* ignore_revision */, + true /* ignore_iteration */)); + bool lib (pt == "lib"); bool priv (ops->private_ ()); // Private installation. @@ -497,7 +502,7 @@ 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 () + '-' + pv.string ()); + string base (pn.string () + '-' + pvs); if (ops->archive_build_meta_specified ()) { diff --git a/bpkg/system-package-manager-debian.cxx b/bpkg/system-package-manager-debian.cxx index c956aa9..e0b2bce 100644 --- a/bpkg/system-package-manager-debian.cxx +++ b/bpkg/system-package-manager-debian.cxx @@ -916,6 +916,8 @@ namespace bpkg optional system_package_manager_debian:: status (const package_name& pn, const available_packages& aps) { + tracer trace ("system_package_manager_debian::status"); + // For now we ignore -doc and -dbg package components (but we may want to // have options controlling this later). Note also that we assume -common // is pulled automatically by the main package so we ignore it as well @@ -968,6 +970,12 @@ namespace bpkg // Keep the main package name empty as an indication that it is to // be discovered. // + // @@ It seems that quite often the header-only library -dev package + // name doesn't start with 'lib'. Here are some randomly chosen + // packages: libeigen3-dev, libmdds-dev, rapidjson-dev, etl-dev, + // seqan-dev, catch2. Should we implement the fallback similar to + // the Fedora implementation? Maybe one day. + // candidates.push_back (package_status ("", n + "-dev")); } else @@ -1014,8 +1022,11 @@ namespace bpkg } // Guess unknown main package given the -dev package and its version. + // Failed that, assume the package to be a binless library and leave the + // main member of the package_status object empty. // - auto guess_main = [this, &pn] (package_status& s, const string& ver) + auto guess_main = [this, &pn, &trace] (package_status& s, + const string& ver) { string depends (apt_cache_show (s.dev, ver)); @@ -1023,11 +1034,8 @@ namespace bpkg if (s.main.empty ()) { - fail << "unable to guess main " << os_release.name_id - << " package for " << s.dev << ' ' << ver << - info << s.dev << " Depends value: " << depends << - info << "consider specifying explicit mapping in " << pn - << " package manifest"; + l4 ([&]{trace << "unable to guess main package for " << s.dev << ' ' + << ver << ", Depends value: " << depends;}); } }; @@ -1102,9 +1110,13 @@ namespace bpkg continue; guess_main (ps, dev.installed_version); - pps.emplace (pps.begin (), ps.main); - ps.package_policies_main++; - apt_cache_policy (pps, 1); + + if (!ps.main.empty ()) // Not a binless library? + { + pps.emplace (pps.begin (), ps.main); + ps.package_policies_main++; + apt_cache_policy (pps, 1); + } } optional s (status (pps, ps.package_policies_main)); @@ -1112,7 +1124,7 @@ namespace bpkg if (!s || *s != package_status::installed) continue; - const package_policy& main (pps.front ()); + const package_policy& main (pps.front ()); // Main/dev. ps.status = *s; ps.system_name = main.name; @@ -1128,17 +1140,20 @@ namespace bpkg { dr << fail << "multiple installed " << os_release.name_id << " packages for " << pn << - info << "candidate: " << r->main << " " << r->system_version; + info << "candidate: " << r->system_name << ' ' << r->system_version; } - dr << info << "candidate: " << ps.main << " " << ps.system_version; + dr << info << "candidate: " << ps.system_name << ' ' + << ps.system_version; } if (!dr.empty ()) dr << info << "consider specifying the desired version manually"; } - // Next look for available versions if we are allowed to install. + // Next look for available versions if we are allowed to install. Indicate + // the non-installable candidates by setting both their main and -dev + // package names to empty strings. // if (!r && install_) { @@ -1171,25 +1186,37 @@ namespace bpkg // Note that this time we use the candidate version. // if (dev.candidate_version.empty ()) - continue; // Not installable. + { + // Not installable. + // + ps.dev.clear (); + continue; + } guess_main (ps, dev.candidate_version); - pps.emplace (pps.begin (), ps.main); - ps.package_policies_main++; - apt_cache_policy (pps, 1); + + if (!ps.main.empty ()) // Not a binless library? + { + pps.emplace (pps.begin (), ps.main); + ps.package_policies_main++; + apt_cache_policy (pps, 1); + } } optional s (status (pps, ps.package_policies_main)); if (!s) { - ps.main.clear (); // Not installable. + // Not installable. + // + ps.main.clear (); + ps.dev.clear (); continue; } assert (*s != package_status::installed); // Sanity check. - const package_policy& main (pps.front ()); + const package_policy& main (pps.front ()); // Main/dev. // Note that if we are installing something for this main package, // then we always go for the candidate version even though it may @@ -1227,13 +1254,13 @@ namespace bpkg dr << fail << "multiple partially installed " << os_release.name_id << " packages for " << pn; - dr << info << "candidate: " << r->main << " " << r->system_version - << ", missing components:"; + dr << info << "candidate: " << r->system_name << ' ' + << r->system_version << ", missing components:"; print_missing (*r); } - dr << info << "candidate: " << ps.main << " " << ps.system_version - << ", missing components:"; + dr << info << "candidate: " << ps.system_name << ' ' + << ps.system_version << ", missing components:"; print_missing (ps); } @@ -1248,7 +1275,7 @@ namespace bpkg for (package_status& ps: candidates) { - if (ps.main.empty ()) + if (ps.main.empty () && ps.dev.empty ()) // Not installable? continue; assert (ps.status == package_status::not_installed); // Sanity check. @@ -1263,10 +1290,12 @@ namespace bpkg { dr << fail << "multiple available " << os_release.name_id << " packages for " << pn << - info << "candidate: " << r->main << " " << r->system_version; + info << "candidate: " << r->system_name << ' ' + << r->system_version; } - dr << info << "candidate: " << ps.main << " " << ps.system_version; + dr << info << "candidate: " << ps.system_name << ' ' + << ps.system_version; } if (!dr.empty ()) @@ -1938,6 +1967,12 @@ namespace bpkg const package_name& pn (sp->name); const version& pv (sp->version); + // Use version without iteration in paths, etc (`#` breaks dpkg + // machinery). + // + string pvs (pv.string (false /* ignore_revision */, + true /* ignore_iteration */)); + const available_packages& aps (pkgs.front ().available); bool lib (pt == "lib"); @@ -1967,6 +2002,8 @@ namespace bpkg // As a first step, figure out the system names and version of the package // we are generating and all the dependencies, diagnosing anything fishy. + // If the main package is not present for a dependency, then set the main + // package name to an empty string. // // Note that there should be no duplicate dependencies and we can sidestep // the status cache. @@ -1983,6 +2020,9 @@ namespace bpkg package_status s; if (sp->substate == package_substate::system) { + // Note that for a system dependency the main package name is already + // empty if it is not present in the distribution. + // optional os (status (sp->name, aps)); if (!os || os->status != package_status::installed) @@ -2007,35 +2047,28 @@ namespace bpkg s = move (*os); } else + { s = map_package (sp->name, sp->version, aps, build_metadata); - sdeps.push_back (move (s)); - } + // Set the main package name to an empty string if we wouldn't be + // generating the main package for this dependency (binless library + // without the -common package). + // + assert (aps.size () == 1); - if (verb >= 3) - { - auto print_status = [] (diag_record& dr, const package_status& s) - { - dr << s.main - << (s.dev.empty () ? "" : " ") << s.dev - << (s.doc.empty () ? "" : " ") << s.doc - << (s.dbg.empty () ? "" : " ") << s.dbg - << (s.common.empty () ? "" : " ") << s.common - << ' ' << s.system_version; - }; + const optional& t (aps.front ().first->type); - { - diag_record dr (trace); - dr << "package: "; - print_status (dr, st); - } + if (s.common.empty () && + package_manifest::effective_type (t, sp->name) == "lib") + { + strings sos (package_manifest::effective_type_sub_options (t)); - for (const package_status& st: sdeps) - { - diag_record dr (trace); - dr << "dependency: "; - print_status (dr, st); + if (find (sos.begin (), sos.end (), "binless") != sos.end ()) + s.main.clear (); + } } + + sdeps.push_back (move (s)); } if (!st.dbg.empty ()) @@ -2135,7 +2168,139 @@ namespace bpkg if (p.second.target != nullptr) dr << " -> " << p.second.target->first; // Symlink. else - dr << " " << p.second.mode; + dr << ' ' << p.second.mode; + } + } + + // Installed entry directories for sorting out which files belong where. + // + // Let's tighten things up and only look in / (if specified) to + // make sure there is nothing stray. + // + string pd (priv ? pn.string () + '/' : ""); + + // NOTE: keep consistent with the config.install.* values above. + // + dir_path bindir ("/usr/bin/"); + dir_path sbindir ("/usr/sbin/"); + dir_path etcdir ("/etc/"); + dir_path incdir ("/usr/include/" + pd); + dir_path incarchdir ("/usr/include/$(DEB_HOST_MULTIARCH)/" + pd); + dir_path libdir ("/usr/lib/$(DEB_HOST_MULTIARCH)/" + pd); + dir_path pkgdir (libdir / dir_path ("pkgconfig")); + dir_path sharedir ("/usr/share/" + pd); + dir_path docdir ("/usr/share/doc/" + pd); + dir_path mandir ("/usr/share/man/"); + + // As an optimization, don't generate the main and -dbgsym packages for a + // binless library unless it also specifies the -common package. + // + // If this is a binless library, then verify that it doesn't install any + // executable, library, or configuration files. Also verify that it has + // the -dev package. + // + bool binless (false); + + if (lib) + { + assert (aps.size () == 1); + + const shared_ptr& ap (aps.front ().first); + strings sos (package_manifest::effective_type_sub_options (ap->type)); + + if (find (sos.begin (), sos.end (), "binless") != sos.end ()) + { + // Verify installed files. + // + auto bad_install = [&pn, &pv] (const string& w) + { + fail << "binless library " << pn << ' ' << pv << " installs " << w; + }; + + auto verify_not_installed = [&ies, &bad_install] (const dir_path& d) + { + auto p (ies.find_sub (d)); + if (p.first != p.second) + bad_install (p.first->first.string ()); + }; + + verify_not_installed (bindir); + verify_not_installed (sbindir); + + // It would probably be better not to fail here but generate the main + // package instead (as we do if the -common package is also being + // generated). Then, however, it would not be easy to detect if a + // dependency has the main package or not (see sdeps initialization + // for details). + // + verify_not_installed (etcdir); + + for (auto p (ies.find_sub (libdir)); p.first != p.second; ++p.first) + { + const path& f (p.first->first); + + if (!f.sub (pkgdir)) + bad_install (f.string ()); + } + + // Verify packages. + // + if (st.dev.empty ()) + fail << "binless library " << pn << ' ' << pv << " doesn't have " + << os_release.name_id << " -dev package"; + + binless = true; + } + } + + bool gen_main (!binless || !st.common.empty ()); + + // If we don't generate the main package (and thus the -common package), + // then fail if there are any data files installed. It would probably be + // better not to fail but generate the main package instead in this + // case. Then, however, it would not be easy to detect if a dependency has + // the main package or not. + // + if (!gen_main) + { + for (auto p (ies.find_sub (sharedir)); p.first != p.second; ++p.first) + { + const path& f (p.first->first); + + if (!f.sub (docdir) && !f.sub (mandir)) + { + fail << "binless library " << pn << ' ' << pv << " installs " << f << + info << "consider specifying -common package in explicit " + << os_release.name_id << " name mapping in package manifest"; + } + } + } + + if (verb >= 3) + { + auto print_status = [] (diag_record& dr, + const package_status& s, + const string& main) + { + dr << (main.empty () ? "" : " ") << main + << (s.dev.empty () ? "" : " ") << s.dev + << (s.doc.empty () ? "" : " ") << s.doc + << (s.dbg.empty () ? "" : " ") << s.dbg + << (s.common.empty () ? "" : " ") << s.common + << ' ' << s.system_version; + }; + + { + diag_record dr (trace); + dr << "package:"; + print_status (dr, st, gen_main ? st.main : empty_string); + } + + for (const package_status& st: sdeps) + { + diag_record dr (trace); + dr << "dependency:"; + print_status (dr, st, st.main); } } @@ -2159,7 +2324,7 @@ namespace bpkg // Normally the source directory is called - // (e.g., as unpacked from the source archive). // - dir_path src (out / dir_path (pn.string () + '-' + pv.string ())); + dir_path src (out / dir_path (pn.string () + '-' + pvs)); dir_path deb (src / dir_path ("debian")); mk_p (deb); @@ -2289,17 +2454,23 @@ namespace bpkg string march (arch == "all" || !lib ? "foreign" : "same"); + if (gen_main) { string depends; - if (!st.common.empty ()) - depends = st.common + " (= ${binary:Version})"; - - for (const package_status& st: sdeps) + auto add_depends = [&depends] (const string& v) { if (!depends.empty ()) depends += ", "; + depends += v; + }; + + if (!st.common.empty ()) + add_depends (st.common + " (= ${binary:Version})"); + + for (const package_status& st: sdeps) + { // Note that the constraints will include build metadata (e.g., // ~debian10). While it may be tempting to strip it, we cannot since // the order is inverse. We could just make it empty `~`, though @@ -2307,7 +2478,8 @@ namespace bpkg // issues. Also note that the build metadata is part of the revision // so we could strip the whole thing. // - depends += st.main + " (>= " + st.system_version + ')'; + if (!st.main.empty ()) + add_depends (st.main + " (>= " + st.system_version + ')'); } if (ops_->debian_main_langdep_specified ()) @@ -2353,7 +2525,15 @@ namespace bpkg if (!st.dev.empty ()) { - string depends (st.main + " (= ${binary:Version})"); + string depends (gen_main ? st.main + " (= ${binary:Version})" : ""); + + auto add_depends = [&depends] (const string& v) + { + if (!depends.empty ()) + depends += ", "; + + depends += v; + }; for (const package_status& st: sdeps) { @@ -2362,14 +2542,14 @@ namespace bpkg // under-specify. // if (!st.dev.empty ()) - depends += ", " + st.dev + " (>= " + st.system_version + ')'; + add_depends (st.dev + " (>= " + st.system_version + ')'); } if (ops_->debian_dev_langdep_specified ()) { if (!ops_->debian_dev_langdep ().empty ()) { - depends += ", " + ops_->debian_dev_langdep (); + add_depends (ops_->debian_dev_langdep ()); } } else @@ -2389,13 +2569,13 @@ namespace bpkg // C++ (better to over- than under-specify). // bool cc (lang ("cc", true)); - if (cc || (cc = lang ("c++", true))) depends += ", libstdc++-dev"; - if (cc || (cc = lang ("c", true))) depends += ", libc-dev"; + if (cc || (cc = lang ("c++", true))) add_depends ("libstdc++-dev"); + if (cc || (cc = lang ("c", true))) add_depends ("libc-dev"); } if (!ops_->debian_dev_extradep ().empty ()) { - depends += ", " + ops_->debian_dev_extradep (); + add_depends (ops_->debian_dev_extradep ()); } // Feels like the architecture should be the same as for the main @@ -2427,7 +2607,7 @@ namespace bpkg // Keep this in case we want to support it in the "starting point" mode. // - if (!st.dbg.empty ()) + if (!st.dbg.empty () && !binless) { string depends (st.main + " (= ${binary:Version})"); @@ -2510,7 +2690,7 @@ namespace bpkg // first and last lines with blank lines. // os << '\n' - << " * New bpkg package release " << pv.string () << '.' << '\n' + << " * New bpkg package release " << pvs << '.' << '\n' << '\n'; // The last line is the "maintainer signoff" and has the following @@ -2724,36 +2904,52 @@ namespace bpkg os << "# *FLAGS (CFLAGS, CXXFLAGS, etc)" << '\n' << "#" << '\n' - << "export DEB_BUILD_MAINT_OPTIONS :=" << mo << '\n' + << "export DEB_BUILD_MAINT_OPTIONS :=" << mo << '\n' << "include /usr/share/dpkg/buildflags.mk" << '\n' << '\n'; - // Fixup -ffile-prefix-map option (if specified) which is used to - // strip source file path prefix in debug information (besides other - // places). By default it points to the source directory. We change it - // to point to the bpkg configuration directory. Note that this won't - // work for external packages with source out of configuration (e.g., - // managed by bdep). - // - if (lang_c || lang_cc) + if (!binless) { - // @@ TODO: OBJCFLAGS. + // Fixup -ffile-prefix-map option (if specified) which is used to + // strip source file path prefix in debug information (besides other + // places). By default it points to the source directory. We change + // it to point to the bpkg configuration directory. Note that this + // won't work for external packages with source out of configuration + // (e.g., managed by bdep). + // + if (lang_c || lang_cc) + { + // @@ TODO: OBJCFLAGS. - os << "CFLAGS := $(patsubst -ffile-prefix-map=%,-ffile-prefix-map=" - << cfg_dir.string () << "=.,$(CFLAGS))" << '\n' - << '\n'; - } + os << "CFLAGS := $(patsubst -ffile-prefix-map=%,-ffile-prefix-map=" + << cfg_dir.string () << "=.,$(CFLAGS))" << '\n' + << '\n'; + } - if (lang_cxx || lang_cc) - { - // @@ TODO: OBJCXXFLAGS. + if (lang_cxx || lang_cc) + { + // @@ TODO: OBJCXXFLAGS. - os << "CXXFLAGS := $(patsubst -ffile-prefix-map=%,-ffile-prefix-map=" - << cfg_dir.string () << "=.,$(CXXFLAGS))" << '\n' - << '\n'; + os << "CXXFLAGS := $(patsubst -ffile-prefix-map=%,-ffile-prefix-map=" + << cfg_dir.string () << "=.,$(CXXFLAGS))" << '\n' + << '\n'; + } } } + // For a binless library the -dbgsym package is not supposed to be + // generated. Thus, we disable its automatic generation by adding the + // noautodbgsym flag to the DEB_BUILD_OPTIONS variable. + // + // This doesn't seem to be necessary (probably because there is no + // .so/.a). + // +#if 0 + if (binless) + os << "export DEB_BUILD_OPTIONS += noautodbgsym" << '\n' + << '\n'; +#endif + // The debian/tmp/ subdirectory appears to be the canonical destination // directory (see dh_auto_install(1) for details). // @@ -3011,7 +3207,7 @@ namespace bpkg } }; - open (main, st.main); + open (main, gen_main ? st.main : empty_string); open (dev, st.dev); open (doc, st.doc); open (dbg, st.dbg); @@ -3040,37 +3236,24 @@ namespace bpkg os.second << s << '\n'; }; - // Let's tighten things up and only look in / (if specified) to - // make sure there is nothing stray. - // - string pd (priv ? pn.string () + '/' : ""); - - // NOTE: keep consistent with the config.install.* values above. - // - dir_path bindir ("/usr/bin/"); - dir_path sbindir ("/usr/sbin/"); - dir_path etcdir ("/etc/"); - dir_path incdir ("/usr/include/" + pd); - dir_path incarchdir ("/usr/include/$(DEB_HOST_MULTIARCH)/" + pd); - dir_path libdir ("/usr/lib/$(DEB_HOST_MULTIARCH)/" + pd); - dir_path pkgdir (libdir / dir_path ("pkgconfig")); - dir_path sharedir ("/usr/share/" + pd); - dir_path docdir ("/usr/share/doc/" + pd); - dir_path mandir ("/usr/share/man/"); - // The main package contains everything that doesn't go to another // packages. // - if (ies.contains_sub (bindir)) add (main, bindir / "*"); - if (ies.contains_sub (sbindir)) add (main, sbindir / "*"); + if (gen_main) + { + if (ies.contains_sub (bindir)) add (main, bindir / "*"); + if (ies.contains_sub (sbindir)) add (main, sbindir / "*"); - // This could potentially go to -common but it could also be target- - // specific, who knows. So let's keep it in main for now. - // - if (ies.contains_sub (etcdir)) add (main, etcdir / "*"); + // This could potentially go to -common but it could also be target- + // specific, who knows. So let's keep it in main for now. + // + if (ies.contains_sub (etcdir)) add (main, etcdir / "*"); + } if (!is_open (dev)) { + assert (gen_main); // Shouldn't be here otherwise. + if (ies.contains_sub (incdir)) add (main, incdir / "*"); if (ies.contains_sub (incarchdir)) add (main, incarchdir / "*"); if (ies.contains_sub (libdir)) add (main, libdir / "*"); @@ -3125,6 +3308,8 @@ namespace bpkg if (l.simple ()) { + assert (gen_main); // Shouldn't be here otherwise. + string e (l.extension ()); const string& n (l.string ()); @@ -3141,6 +3326,11 @@ namespace bpkg // dir_path d (libdir / dir_path (*l.begin ())); + // Can only be a subdirectory of pkgdir/ if the main package is + // not being generated. + // + assert (d == pkgdir || gen_main); + add (d == pkgdir ? dev : main, d / "*"); // Skip all the other entries in this subdirectory (in the prefix @@ -3156,31 +3346,34 @@ namespace bpkg // below. So we have to list all the top-level entries in usr/share/ // that are not doc/ or man/. // - for (auto p (ies.find_sub (sharedir)); p.first != p.second; ) + if (gen_main) { - const path& f ((p.first++)->first); + for (auto p (ies.find_sub (sharedir)); p.first != p.second; ) + { + const path& f ((p.first++)->first); - if (f.sub (docdir) || f.sub (mandir)) - continue; + if (f.sub (docdir) || f.sub (mandir)) + continue; - path l (f.leaf (sharedir)); + path l (f.leaf (sharedir)); - if (l.simple ()) - add (is_open (com) ? com : main, sharedir / l); - else - { - // Let's keep things tidy and use a wildcard rather than listing all - // the entries in subdirectories verbatim. - // - dir_path d (sharedir / dir_path (*l.begin ())); + if (l.simple ()) + add (is_open (com) ? com : main, sharedir / l); + else + { + // Let's keep things tidy and use a wildcard rather than listing + // all the entries in subdirectories verbatim. + // + dir_path d (sharedir / dir_path (*l.begin ())); - add (is_open (com) ? com : main, d / "*"); + add (is_open (com) ? com : main, d / "*"); - // Skip all the other entries in this subdirectory (in the prefix - // map they will all be in a contiguous range). - // - while (p.first != p.second && p.first->first.sub (d)) - ++p.first; + // Skip all the other entries in this subdirectory (in the prefix + // map they will all be in a contiguous range). + // + while (p.first != p.second && p.first->first.sub (d)) + ++p.first; + } } } @@ -3195,6 +3388,11 @@ namespace bpkg is_open (dev) ? dev : main); + // We can only add doc files to the main or -common packages if we + // generate the main package. + // + assert ((&os != &main && &os != &com) || gen_main); + if (ies.contains_sub (docdir)) add (os, docdir / "*"); if (ies.contains_sub (mandir)) add (os, mandir / "*"); } @@ -3339,8 +3537,9 @@ namespace bpkg // const string& ver (st.system_version); - add (st.main + '_' + ver + '_' + arch + ".deb"); - add (st.main + "-dbgsym_" + ver + '_' + arch + ".deb", true); + if (gen_main) add (st.main + '_' + ver + '_' + arch + ".deb"); + if (!binless) add (st.main + "-dbgsym_" + ver + '_' + arch + ".deb", true); + if (!st.dev.empty ()) add (st.dev + '_' + ver + '_' + arch + ".deb"); if (!st.doc.empty ()) add (st.doc + '_' + ver + "_all.deb"); if (!st.common.empty ()) add (st.common + '_' + ver + "_all.deb"); diff --git a/bpkg/system-package-manager-debian.hxx b/bpkg/system-package-manager-debian.hxx index 08a2d02..eb8b214 100644 --- a/bpkg/system-package-manager-debian.hxx +++ b/bpkg/system-package-manager-debian.hxx @@ -37,7 +37,9 @@ namespace bpkg // libcurl3-gnutls libcurl4-gnutls-dev libcurl4-doc (yes, 3 and 4) // // Note that while most library package names in Debian start with lib (per - // the policy), there are exceptions (e.g., zlib1g zlib1g-dev). + // the policy), there are exceptions (e.g., zlib1g zlib1g-dev). The + // header-only library package names may or may not start with lib and end + // with -dev (e.g., libeigen3-dev, rapidjson-dev, catch2). // // Also note that manual -dbg packages are obsolete in favor of automatic // -dbgsym packages from Debian 9. So while we support -dbg for consumption, diff --git a/bpkg/system-package-manager-fedora.cxx b/bpkg/system-package-manager-fedora.cxx index 9c75618..4d12360 100644 --- a/bpkg/system-package-manager-fedora.cxx +++ b/bpkg/system-package-manager-fedora.cxx @@ -1121,6 +1121,8 @@ namespace bpkg optional system_package_manager_fedora:: status (const package_name& pn, const available_packages& aps) { + tracer trace ("system_package_manager_fedora::status"); + // For now we ignore -doc and -debug* package components (but we may want // to have options controlling this later). Note also that we assume // -common is pulled automatically by the base package so we ignore it as @@ -1246,12 +1248,13 @@ namespace bpkg } // Guess unknown main package given the -devel package, its version, and - // architecture. + // architecture. Failed that, assume the package to be a binless library + // and leave the main member of the package_status object empty. // - auto guess_main = [this, &pn] (package_status& s, - const string& ver, - const string& qarch, - bool installed) + auto guess_main = [this, &pn, &trace] (package_status& s, + const string& ver, + const string& qarch, + bool installed) { vector> depends ( dnf_repoquery_requires (s.devel, ver, qarch, installed)); @@ -1260,19 +1263,21 @@ namespace bpkg if (s.main.empty ()) { - diag_record dr (fail); - dr << "unable to guess main " << os_release.name_id - << " package for " << s.devel << ' ' << ver << - info << "depends on"; + if (verb >= 4) + { + diag_record dr (trace); + dr << "unable to guess main package for " << s.devel << ' ' << ver; + if (!depends.empty ()) + { + dr << ", depends on"; - for (auto b (depends.begin ()), i (b); i != depends.end (); ++i) - { - dr << (i == b ? " " : ", ") << i->first << ' ' << i->second; + for (auto b (depends.begin ()), i (b); i != depends.end (); ++i) + dr << (i == b ? " " : ", ") << i->first << ' ' << i->second; + } + else + dr << ", has no dependencies"; } - - dr << info << "consider specifying explicit mapping in " << pn - << " package manifest"; } }; @@ -1403,9 +1408,12 @@ namespace bpkg devel.installed_arch, true /* installed */); - pis.emplace (pis.begin (), ps.main); - ps.package_infos_main++; - dnf_list (pis, 1); + if (!ps.main.empty ()) // Not a binless library? + { + pis.emplace (pis.begin (), ps.main); + ps.package_infos_main++; + dnf_list (pis, 1); + } } optional s (status (pis, ps.package_infos_main)); @@ -1413,7 +1421,7 @@ namespace bpkg if (!s || *s != package_status::installed) continue; - const package_info& main (pis.front ()); + const package_info& main (pis.front ()); // Main/devel. ps.status = *s; ps.system_name = main.name; @@ -1429,17 +1437,20 @@ namespace bpkg { dr << fail << "multiple installed " << os_release.name_id << " packages for " << pn << - info << "candidate: " << r->main << " " << r->system_version; + info << "candidate: " << r->system_name << ' ' << r->system_version; } - dr << info << "candidate: " << ps.main << " " << ps.system_version; + dr << info << "candidate: " << ps.system_name << ' ' + << ps.system_version; } if (!dr.empty ()) dr << info << "consider specifying the desired version manually"; } - // Next look for available versions if we are allowed to install. + // Next look for available versions if we are allowed to install. Indicate + // the non-installable candidates by setting both their main and -devel + // package names to empty strings. // if (!r && install_) { @@ -1476,11 +1487,13 @@ namespace bpkg // if (mp.candidate_version.empty ()) { + string& main (!ps.main.empty () ? ps.main : ps.devel); + if (!fp.candidate_version.empty ()) { // Use the fallback. // - (ps.main.empty () ? ps.devel : ps.main) = move (ps.fallback); + main = move (ps.fallback); mp = move (fp); } else @@ -1493,7 +1506,7 @@ namespace bpkg // Main/devel package is not installable. // - ps.main.clear (); + main.clear (); continue; } } @@ -1514,29 +1527,40 @@ namespace bpkg // Note that this time we use the candidate version. // if (devel.candidate_version.empty ()) - continue; // Not installable. + { + // Not installable. + // + ps.devel.clear (); + continue; + } guess_main (ps, devel.candidate_version, devel.candidate_arch, devel.candidate_version == devel.installed_version); - pis.emplace (pis.begin (), ps.main); - ps.package_infos_main++; - dnf_list (pis, 1); + if (!ps.main.empty ()) // Not a binless library? + { + pis.emplace (pis.begin (), ps.main); + ps.package_infos_main++; + dnf_list (pis, 1); + } } optional s (status (pis, ps.package_infos_main)); if (!s) { - ps.main.clear (); // Not installable. + // Not installable. + // + ps.main.clear (); + ps.devel.clear (); continue; } assert (*s != package_status::installed); // Sanity check. - const package_info& main (pis.front ()); + const package_info& main (pis.front ()); // Main/devel. // Note that if we are installing something for this main package, // then we always go for the candidate version even though it may @@ -1575,13 +1599,13 @@ namespace bpkg dr << fail << "multiple partially installed " << os_release.name_id << " packages for " << pn; - dr << info << "candidate: " << r->main << " " << r->system_version - << ", missing components:"; + dr << info << "candidate: " << r->system_name << ' ' + << r->system_version << ", missing components:"; print_missing (*r); } - dr << info << "candidate: " << ps.main << " " << ps.system_version - << ", missing components:"; + dr << info << "candidate: " << ps.system_name << ' ' + << ps.system_version << ", missing components:"; print_missing (ps); } @@ -1596,7 +1620,7 @@ namespace bpkg for (package_status& ps: candidates) { - if (ps.main.empty ()) + if (ps.main.empty () && ps.devel.empty ()) // Not installable? continue; assert (ps.status == package_status::not_installed); // Sanity check. @@ -1611,10 +1635,12 @@ namespace bpkg { dr << fail << "multiple available " << os_release.name_id << " packages for " << pn << - info << "candidate: " << r->main << " " << r->system_version; + info << "candidate: " << r->system_name << ' ' + << r->system_version; } - dr << info << "candidate: " << ps.main << " " << ps.system_version; + dr << info << "candidate: " << ps.system_name << ' ' + << ps.system_version; } if (!dr.empty ()) @@ -2292,6 +2318,11 @@ namespace bpkg const package_name& pn (sp->name); const version& pv (sp->version); + // Use version without iteration in paths, etc. + // + string pvs (pv.string (false /* ignore_revision */, + true /* ignore_iteration */)); + const available_packages& aps (pkgs.front ().available); bool lib (pt == "lib"); @@ -2321,6 +2352,8 @@ namespace bpkg // As a first step, figure out the system names and version of the package // we are generating and all the dependencies, diagnosing anything fishy. + // If the main package is not present for a dependency, then set the main + // package name to an empty string. // // Note that there should be no duplicate dependencies and we can sidestep // the status cache. @@ -2337,6 +2370,9 @@ namespace bpkg package_status s; if (sp->substate == package_substate::system) { + // Note that for a system dependency the main package name is already + // empty if it is not present in the distribution. + // optional os (status (sp->name, aps)); if (!os || os->status != package_status::installed) @@ -2374,37 +2410,28 @@ namespace bpkg v.resize (p); } else + { s = map_package (sp->name, sp->version, aps); - sdeps.push_back (move (s)); - } + // Set the main package name to an empty string if we wouldn't be + // generating the main package for this dependency (binless library + // without the -common sub-package). + // + assert (aps.size () == 1); - if (verb >= 3) - { - auto print_status = [] (diag_record& dr, const package_status& s) - { - dr << s.main - << (s.devel.empty () ? "" : " ") << s.devel - << (s.static_.empty () ? "" : " ") << s.static_ - << (s.doc.empty () ? "" : " ") << s.doc - << (s.debuginfo.empty () ? "" : " ") << s.debuginfo - << (s.debugsource.empty () ? "" : " ") << s.debugsource - << (s.common.empty () ? "" : " ") << s.common - << ' ' << s.system_version; - }; + const optional& t (aps.front ().first->type); - { - diag_record dr (trace); - dr << "package: "; - print_status (dr, st); - } + if (s.common.empty () && + package_manifest::effective_type (t, sp->name) == "lib") + { + strings sos (package_manifest::effective_type_sub_options (t)); - for (const package_status& st: sdeps) - { - diag_record dr (trace); - dr << "dependency: "; - print_status (dr, st); + if (find (sos.begin (), sos.end (), "binless") != sos.end ()) + s.main.clear (); + } } + + sdeps.push_back (move (s)); } // We only allow the standard -debug* sub-package names. @@ -2412,12 +2439,12 @@ namespace bpkg if (!st.debuginfo.empty () && st.debuginfo != st.main + "-debuginfo") fail << "generation of -debuginfo packages with custom names not " << "supported" << - info << "use " << st.main + "-debuginfo name instead"; + info << "use " << st.main << "-debuginfo name instead"; if (!st.debugsource.empty () && st.debuginfo != st.main + "-debugsource") fail << "generation of -debugsource packages with custom names not " << "supported" << - info << "use " << st.main + "-debugsource name instead"; + info << "use " << st.main << "-debugsource name instead"; // Prepare the common extra options that need to be passed to both // rpmbuild and rpm. @@ -2559,8 +2586,8 @@ namespace bpkg // strings expansions; - // These are used for sorting out the installed files into the %files - // sections of the sub-packages. + // Installed entry directories for sorting out the installed files into + // the %files sections of the sub-packages. // dir_path bindir; dir_path sbindir; @@ -2760,7 +2787,126 @@ namespace bpkg if (p.second.target != nullptr) dr << " -> " << p.second.target->first; // Symlink. else - dr << " " << p.second.mode; + dr << ' ' << p.second.mode; + } + } + + // As an optimization, don't generate the main and -debug* packages for a + // binless library unless it also specifies the -common sub-package. + // + // If this is a binless library, then verify that it doesn't install any + // executable, library, or configuration files. Also verify that it has + // the -devel sub-package but doesn't specify the -static sub-package. + // + bool binless (false); + + if (lib) + { + assert (aps.size () == 1); + + const shared_ptr& ap (aps.front ().first); + strings sos (package_manifest::effective_type_sub_options (ap->type)); + + if (find (sos.begin (), sos.end (), "binless") != sos.end ()) + { + // Verify installed files. + // + auto bad_install = [&pn, &pv] (const string& w) + { + fail << "binless library " << pn << ' ' << pv << " installs " << w; + }; + + auto verify_not_installed = [&ies, &bad_install] (const dir_path& d) + { + auto p (ies.find_sub (d)); + if (p.first != p.second) + bad_install (p.first->first.string ()); + }; + + verify_not_installed (bindir); + verify_not_installed (sbindir); + verify_not_installed (libexecdir); + + // It would probably be better not to fail here but generate the main + // package instead (as we do if the -common sub-package is also being + // generated). Then, however, it would not be easy to detect if a + // dependency has the main package or not (see sdeps initialization + // for details). + // + verify_not_installed (confdir); + + for (auto p (ies.find_sub (libdir)); p.first != p.second; ++p.first) + { + const path& f (p.first->first); + + if (!f.sub (pkgdir)) + bad_install (f.string ()); + } + + // Verify sub-packages. + // + if (st.devel.empty ()) + fail << "binless library " << pn << ' ' << pv << " doesn't have " + << os_release.name_id << " -devel package"; + + if (!st.static_.empty ()) + fail << "binless library " << pn << ' ' << pv << " has " + << os_release.name_id << ' ' << st.static_ << " package"; + + binless = true; + } + } + + bool gen_main (!binless || !st.common.empty ()); + + // If we don't generate the main package (and thus the -common + // sub-package), then fail if there are any data files installed. It would + // probably be better not to fail but generate the main package instead in + // this case. Then, however, it would not be easy to detect if a + // dependency has the main package or not. + // + if (!gen_main) + { + for (auto p (ies.find_sub (sharedir)); p.first != p.second; ++p.first) + { + const path& f (p.first->first); + + if (!f.sub (docdir) && !f.sub (mandir) && !f.sub (licensedir)) + { + fail << "binless library " << pn << ' ' << pv << " installs " << f << + info << "consider specifying -common package in explicit " + << os_release.name_id << " name mapping in package manifest"; + } + } + } + + if (verb >= 3) + { + auto print_status = [] (diag_record& dr, + const package_status& s, + const string& main) + { + dr << (main.empty () ? "" : " ") << main + << (s.devel.empty () ? "" : " ") << s.devel + << (s.static_.empty () ? "" : " ") << s.static_ + << (s.doc.empty () ? "" : " ") << s.doc + << (s.debuginfo.empty () ? "" : " ") << s.debuginfo + << (s.debugsource.empty () ? "" : " ") << s.debugsource + << (s.common.empty () ? "" : " ") << s.common + << ' ' << s.system_version; + }; + + { + diag_record dr (trace); + dr << "package:"; + print_status (dr, st, gen_main ? st.main : empty_string); + } + + for (const package_status& s: sdeps) + { + diag_record dr (trace); + dr << "dependency:"; + print_status (dr, s, s.main); } } @@ -3025,10 +3171,11 @@ namespace bpkg // (it's commonly the contents of the README.md file). // // We will disable automatic dependency discovery for all sub-packages - // using the `AutoReqProv: no` directive. + // using the `AutoReqProv: no` directive since we have an accurate set + // and some of them may not be system packages. // - // The main package. + // The common information and the main package. // { os << "Name: " << st.main << '\n' @@ -3045,9 +3192,6 @@ namespace bpkg if (!packager.empty ()) os << "Packager: " << packager << '\n'; - if (!build_arch.empty ()) - os << "BuildArch: " << build_arch << '\n'; - #if 0 os << "#Source: https://pkg.cppget.org/1/???/" << pm.effective_project () << '/' << sp->name << '-' @@ -3059,32 +3203,54 @@ namespace bpkg os << '\n' << "%global evr %{?epoch:%{epoch}:}%{version}-%{release}" << '\n'; - os << '\n' - << "# " << st.main << '\n' - << "#" << '\n' - << "AutoReqProv: no" << '\n'; - - // Requires directives. - // + if (gen_main) { - bool first (true); - if (!st.common.empty ()) - add_requires (first, st.common + " = %{evr}"); + os << '\n' + << "# " << st.main << '\n' + << "#" << '\n'; - for (const package_status& s: sdeps) - add_requires (first, s.main + " >= " + s.system_version); + if (!build_arch.empty ()) + os << "BuildArch: " << build_arch << '\n'; - add_lang_requires (first, - "" /* suffix */, - ops_->fedora_main_langreq ()); + os << "AutoReqProv: no" << '\n'; - if (ops_->fedora_main_extrareq_specified ()) - add_requires_list (first, ops_->fedora_main_extrareq ()); + // Requires directives. + // + { + bool first (true); + if (!st.common.empty ()) + add_requires (first, st.common + " = %{evr}"); + + for (const package_status& s: sdeps) + { + if (!s.main.empty ()) + add_requires (first, s.main + " >= " + s.system_version); + } + + add_lang_requires (first, + "" /* suffix */, + ops_->fedora_main_langreq ()); + + if (ops_->fedora_main_extrareq_specified ()) + add_requires_list (first, ops_->fedora_main_extrareq ()); + } } - os << '\n' - << "%description" << '\n' - << "This package contains the runtime files." << '\n'; + // Note that we need to add the %description directive regardless if + // the main package is being generated or not. + // + if (!binless) + { + os << '\n' + << "%description" << '\n' + << "This package contains the runtime files." << '\n'; + } + else + { + os << '\n' + << "%description" << '\n' + << "This package contains the development files." << '\n'; + } } // The -devel sub-package. @@ -3113,7 +3279,8 @@ namespace bpkg // Dependency on the main package. // - add_requires (first, "%{name}" + isa + " = %{evr}"); + if (gen_main) + add_requires (first, "%{name}" + isa + " = %{evr}"); for (const package_status& s: sdeps) { @@ -3205,6 +3372,9 @@ namespace bpkg // use. Thus, add dependency on the -devel or main sub-package, if // not being generated. // + // Note that if there is no -devel package, then this cannot be a + // binless library and thus the main package is being generated. + // add_requires ( first, (!st.devel.empty () ? st.devel : "%{name}") + isa + " = %{evr}"); @@ -3329,37 +3499,46 @@ namespace bpkg // Also note that we disable generating the -debugsource sub-packages // (see the generate() description above for the reasoning). // - os << "%undefine _debugsource_packages" << '\n'; - - // Append the -ffile-prefix-map option (if specified) which is used to - // strip source file path prefix in debug information (besides other - // places). By default it is not used, presumably since we disable - // generating the -debugsource sub-packages. We change it to point to - // the bpkg configuration directory. Note that this won't work for - // external packages with source out of configuration (e.g., managed - // by bdep). - // - // @@ Supposedly this code won't be necessary when we add support for - // -debugsource sub-packages. - // - // Note that adding this option may also result in notification - // messages for probably all the source files as following: - // - // cpio: libfoo-1.2.3/foo.cxx: Cannot stat: No such file or directory + // For a binless library no -debug* packages are supposed to be + // generated. Thus, we just drop their definitions by redefining the + // %{debug_package} macro as an empty string. // - // This bloats the rpmbuild output but doesn't seem to break - // anything. - // - if (ops_->fedora_buildflags () != "ignore") + if (!binless) { - if (lang_c || lang_cc) - os << "%global build_cflags %{?build_cflags} -ffile-prefix-map=" - << cfg_dir.string () << "=." << '\n'; + os << "%undefine _debugsource_packages" << '\n'; + + // Append the -ffile-prefix-map option (if specified) which is used + // to strip source file path prefix in debug information (besides + // other places). By default it is not used, presumably since we + // disable generating the -debugsource sub-packages. We change it to + // point to the bpkg configuration directory. Note that this won't + // work for external packages with source out of configuration + // (e.g., managed by bdep). + // + // @@ Supposedly this code won't be necessary when we add support for + // -debugsource sub-packages. + // + // Note that adding this option may also result in notification + // messages for probably all the source files as following: + // + // cpio: libfoo-1.2.3/foo.cxx: Cannot stat: No such file or directory + // + // This bloats the rpmbuild output but doesn't seem to break + // anything. + // + if (ops_->fedora_buildflags () != "ignore") + { + if (lang_c || lang_cc) + os << "%global build_cflags %{?build_cflags} -ffile-prefix-map=" + << cfg_dir.string () << "=." << '\n'; - if (lang_cxx || lang_cc) - os << "%global build_cxxflags %{?build_cxxflags} -ffile-prefix-map=" - << cfg_dir.string () << "=." << '\n'; + if (lang_cxx || lang_cc) + os << "%global build_cxxflags %{?build_cxxflags} -ffile-prefix-map=" + << cfg_dir.string () << "=." << '\n'; + } } + else + os << "%global debug_package %{nil}" << '\n'; // Common arguments for build2 commands. // @@ -3372,8 +3551,8 @@ namespace bpkg cstrings verb_args; string verb_arg; map_verb_b (*ops_, verb_b::normal, verb_args, verb_arg); - os << '\n' - << "%global build2 " << search_b (*ops_).effect_string (); + os << '\n' + << "%global build2 " << search_b (*ops_).effect_string (); for (const char* o: verb_args) os << ' ' << o; for (const string& o: ops_->build_option ()) os << ' ' << o; @@ -3581,25 +3760,28 @@ namespace bpkg // The main package contains everything that doesn't go to another // packages. // - if (ies.contains_sub (bindir)) main += "%{_bindir}/*\n"; - if (ies.contains_sub (sbindir)) main += "%{_sbindir}/*\n"; + if (gen_main) + { + if (ies.contains_sub (bindir)) main += "%{_bindir}/*\n"; + if (ies.contains_sub (sbindir)) main += "%{_sbindir}/*\n"; - if (ies.contains_sub (libexecdir)) - main += "%{_libexecdir}/" + (priv ? pd : "*") + '\n'; + if (ies.contains_sub (libexecdir)) + main += "%{_libexecdir}/" + (priv ? pd : "*") + '\n'; - // This could potentially go to -common but it could also be target- - // specific, who knows. So let's keep it in main for now. - // - // Let's also specify that the confdir/ sub-entries are non-replacable - // configuration files. This, in particular, means that if edited they - // will not be replaced/removed on the package upgrade or - // uninstallation (see RPM Packaging Guide for more details on the - // %config(noreplace) directive). Also note that the binary package - // configuration files can later be queried by the user via the `rpm - // --query --configfiles` command. - // - if (ies.contains_sub (confdir)) - main += "%config(noreplace) %{_sysconfdir}/*\n"; + // This could potentially go to -common but it could also be target- + // specific, who knows. So let's keep it in main for now. + // + // Let's also specify that the confdir/ sub-entries are + // non-replacable configuration files. This, in particular, means + // that if edited they will not be replaced/removed on the package + // upgrade or uninstallation (see RPM Packaging Guide for more + // details on the %config(noreplace) directive). Also note that the + // binary package configuration files can later be queried by the + // user via the `rpm --query --configfiles` command. + // + if (ies.contains_sub (confdir)) + main += "%config(noreplace) %{_sysconfdir}/*\n"; + } if (ies.contains_sub (incdir)) (!st.devel.empty () ? devel : main) += @@ -3607,6 +3789,8 @@ namespace bpkg if (st.devel.empty () && st.static_.empty ()) { + assert (gen_main); // Shouldn't be here otherwise. + if (ies.contains_sub (libdir)) main += "%{_libdir}/" + (priv ? pd : "*") + '\n'; } @@ -3751,17 +3935,20 @@ namespace bpkg else { fs = &devel; - *fs += "%{_libdir}/" + pd + sd.string () + '/'; + *fs += "%{_libdir}/" + pd + sd.string () + (priv ? "/" : "/*"); } } else *fs += "%{_libdir}/" + pd + sd.string () + '/'; - // In the case of the directory (has the trailing slash) skip - // all the other entries in this subdirectory (in the prefix map - // they will all be in a contiguous range). + // In the case of the directory (has the trailing slash) or + // wildcard (has the trailing asterisk) skip all the other + // entries in this subdirectory (in the prefix map they will all + // be in a contiguous range). // - if (fs->back () == '/') + char c (fs->back ()); + + if (c == '/' || c == '*') { while (p.first != p.second && p.first->first.sub (d)) ++p.first; @@ -3770,6 +3957,10 @@ namespace bpkg *fs += '\n'; } + // We can only add files to the main package if we generate it. + // + assert (fs != &main || gen_main); + // Update the index of a sub-package which should own // libdir//. // @@ -3783,13 +3974,15 @@ namespace bpkg *owners[*private_owner] += "%dir %{_libdir}/" + pd + '\n'; if (pkgconfig_owner) - *owners[*pkgconfig_owner] += "%dir %{_libdir}/" + pd + "pkgconfig/" + '\n'; + *owners[*pkgconfig_owner] += + "%dir %{_libdir}/" + pd + "pkgconfig/" + '\n'; } // We cannot just do usr/share/* since it will clash with doc/, man/, // and licenses/ below. So we have to list all the top-level entries // in usr/share/ that are not doc/, man/, or licenses/. // + if (gen_main) { // Note that if / is specified, then we also need to // establish ownership of the sharedir// directory (similar @@ -3819,7 +4012,7 @@ namespace bpkg // dir_path sd (*l.begin ()); - fs += "%{_datadir}/" + pd + sd.string () + '\n'; + fs += "%{_datadir}/" + pd + sd.string () + '/' + '\n'; // Skip all the other entries in this subdirectory (in the prefix // map they will all be in a contiguous range). @@ -3853,6 +4046,11 @@ namespace bpkg !st.devel.empty () ? devel : main); + // We can only add doc files to the main or -common packages if we + // generate the main package. + // + assert ((&fs != &main && &fs != &common) || gen_main); + // Let's specify that the docdir/ sub-entries are documentation // files. Note that the binary package documentation files can later // be queried by the user via the `rpm --query --docfiles` command. @@ -3877,12 +4075,15 @@ namespace bpkg // the user via the `rpm --query --licensefiles` command. // if (ies.contains_sub (licensedir)) - main += "%license %{_licensedir}/" + (priv ? pd : "*") + '\n'; + (gen_main ? main : devel) += + "%license %{_licensedir}/" + (priv ? pd : "*") + '\n'; // Finally, write the %files sections. // if (!main.empty ()) { + assert (gen_main); // Shouldn't be here otherwise. + os << '\n' << "# " << st.main << " files." << '\n' << "#" << '\n' @@ -3970,7 +4171,7 @@ namespace bpkg os << sys_version.epoch << ':'; os << sys_version.version << '-' << sys_version.release << '\n' - << "- New bpkg package release " << pv.string () << '.' << '\n'; + << "- New bpkg package release " << pvs << '.' << '\n'; } os.close (); @@ -4118,7 +4319,8 @@ namespace bpkg return np++; }; - add_package (st.main, package_arch); + if (gen_main) + add_package (st.main, package_arch); if (!st.devel.empty ()) add_package (st.devel, package_arch); @@ -4132,7 +4334,9 @@ namespace bpkg if (!st.common.empty ()) add_package (st.common, "noarch"); - size_t di (add_package (st.main + "-debuginfo", arch)); + optional di (!binless + ? add_package (st.main + "-debuginfo", arch) + : optional ()); // Strip the trailing newline since rpm adds one. // @@ -4161,7 +4365,7 @@ namespace bpkg // if (exists (p)) r.push_back (move (p)); - else if (i != di) // Not a -debuginfo sub-package? + else if (!di || i != *di) // Not a -debuginfo sub-package? fail << "expected output file " << p << " does not exist"; } catch (const invalid_path& e) diff --git a/bpkg/system-package-manager-fedora.hxx b/bpkg/system-package-manager-fedora.hxx index 609df48..672b2a1 100644 --- a/bpkg/system-package-manager-fedora.hxx +++ b/bpkg/system-package-manager-fedora.hxx @@ -50,6 +50,10 @@ namespace bpkg // // zlib zlib-devel zlib-static // + // catch-devel + // + // eigen3-devel eigen3-doc + // // xerces-c xerces-c-devel xerces-c-doc // // libsigc++20 libsigc++20-devel libsigc++20-doc diff --git a/doc/manual.cli b/doc/manual.cli index 29e5535..a7c1336 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -1137,7 +1137,7 @@ information. [type]: [language]: - = + = [,] = [=impl] \ @@ -1148,7 +1148,14 @@ the type is not specified, then if the package name starts with \c{lib}, then it is assumed to be \c{lib} and \c{exe} otherwise (see \l{#package-name Package Name} for details). Other package types may be added in the future and code that does not recognize a certain package type should treat it as -\c{other}. +\c{other}. The type name can be followed by a comma-separated list of +sub-options. Currently, the only recognized sub-option is \c{binless} which +applies to the \c{lib} type indicating a header-only (or equivalent) library. +For example: + +\ +type: lib,binless +\ The package language must be in the lower case, for example, \c{c}, \c{c++}, \c{rust}, \c{bash}. If the language is not specified, then if the package name @@ -3014,9 +3021,11 @@ libcurl3-gnutls libcurl4-gnutls-dev libcurl4-doc \ Note that while most library package names in Debian start with \c{lib} (per -the policy), there are exceptions (e.g., \c{zlib1g} \c{zlib1g-dev}). Also note -that manual \c{-dbg} packages are obsolete in favor of automatic \c{-dbgsym} -packages from Debian 9. +the policy), there are exceptions (e.g., \c{zlib1g} \c{zlib1g-dev}). The +header-only library package names may or may not start with \c{lib} and end +with \c{-dev} (e.g., \c{libeigen3-dev}, \c{rapidjson-dev}, \c{catch2}). Also +note that manual \c{-dbg} packages are obsolete in favor of automatic +\c{-dbgsym} packages from Debian 9. For executable packages there is normally no \c{-dev} packages but \c{-dbg}, \c{-doc}, and \c{-common} are plausible. @@ -3072,7 +3081,9 @@ are also used to derive the package names for production except here we have the option to specify alternative non-native package names using the special \c{debian_0-name} (or alike) value. If only the \c{-dev} package is specified, then the main package name is derived from that by removing the \c{-dev} -suffix. +suffix. Note that regardless of whether the main package name is specified or +not, the \l{bpkg-pkg-bindist(1)} command may omit generating the main package +for a binless library. The generated binary package version can be specified with the \c{debian-version} (or alike) manifest value. If it's not specified, then the @@ -3240,6 +3251,10 @@ libpq libpq-devel zlib zlib-devel zlib-static +catch-devel + +eigen3-devel eigen3-doc + xerces-c xerces-c-devel xerces-c-doc libsigc++20 libsigc++20-devel libsigc++20-doc @@ -3324,7 +3339,9 @@ are also used to derive the package names for production except here we have the option to specify alternative non-native package names using the special \c{fedora_0-name} (or alike) value. If only the \c{-devel} package is specified, then the main package name is derived from that by removing the -\c{-devel} suffix. +\c{-devel} suffix. Note that regardless of whether the main package name is +specified or not, the \l{bpkg-pkg-bindist(1)} command may omit generating the +main package for a binless library. The generated binary package version can be specified with the \c{fedora-version} (or alike) manifest value. If it's not specified, then the -- cgit v1.1