diff options
-rw-r--r-- | clean/clean.cxx | 4 | ||||
-rw-r--r-- | libbrep/build-extra.sql | 18 | ||||
-rw-r--r-- | libbrep/build-package.hxx | 42 | ||||
-rw-r--r-- | libbrep/package.cxx | 5 | ||||
-rw-r--r-- | libbrep/package.hxx | 29 | ||||
-rw-r--r-- | libbrep/package.xml | 38 | ||||
-rw-r--r-- | load/load.cxx | 1 | ||||
-rw-r--r-- | mod/build-config.cxx | 27 | ||||
-rw-r--r-- | mod/build-config.hxx | 15 | ||||
-rw-r--r-- | mod/mod-build-task.cxx | 157 | ||||
-rw-r--r-- | mod/mod-builds.cxx | 190 | ||||
-rw-r--r-- | mod/mod-package-version-details.cxx | 71 | ||||
-rw-r--r-- | tests/load/1/math/libexp-1~1.2+1.tar.gz | bin | 315 -> 360 bytes | |||
-rw-r--r-- | tests/load/1/math/packages | 4 | ||||
-rw-r--r-- | tests/load/driver.cxx | 32 |
15 files changed, 497 insertions, 136 deletions
diff --git a/clean/clean.cxx b/clean/clean.cxx index f6d7eff..bec297b 100644 --- a/clean/clean.cxx +++ b/clean/clean.cxx @@ -200,8 +200,8 @@ try package_name = b.package_name; package_versions.clear (); - for (auto& v: pkg_prep_query.execute ()) - package_versions.emplace (move (v.version)); + for (auto& p: pkg_prep_query.execute ()) + package_versions.emplace (move (p.version)); } cleanup = package_versions.find (b.package_version) == diff --git a/libbrep/build-extra.sql b/libbrep/build-extra.sql index 6a222a7..4da75e5 100644 --- a/libbrep/build-extra.sql +++ b/libbrep/build-extra.sql @@ -3,6 +3,8 @@ -- file for details. -- +DROP FOREIGN TABLE IF EXISTS build_package_constraints; + DROP FOREIGN TABLE IF EXISTS build_package; DROP FOREIGN TABLE IF EXISTS build_repository; @@ -29,3 +31,19 @@ CREATE FOREIGN TABLE build_package ( version_release TEXT NULL, internal_repository TEXT NULL) SERVER package_server OPTIONS (table_name 'package'); + +-- The foreign table for the build_package object constraints member (that is +-- of a container type). +-- +-- +CREATE FOREIGN TABLE build_package_constraints ( + name TEXT NOT NULL, + version_epoch INTEGER NOT NULL, + version_canonical_upstream TEXT NOT NULL, + version_canonical_release TEXT NOT NULL COLLATE "C", + version_revision INTEGER NOT NULL, + index BIGINT NOT NULL, + exclusion BOOLEAN NOT NULL, + config TEXT NOT NULL, + target TEXT NULL) +SERVER package_server OPTIONS (table_name 'package_build_constraints'); diff --git a/libbrep/build-package.hxx b/libbrep/build-package.hxx index 8bc703a..324303d 100644 --- a/libbrep/build-package.hxx +++ b/libbrep/build-package.hxx @@ -23,7 +23,7 @@ namespace brep // The mapping is established in build-extra.sql. We also explicitly mark // non-primary key foreign-mapped members in the source object. // - // Foreign object that is mapped to the subset of repository object. + // Foreign object that is mapped to a subset of repository object. // #pragma db object table("build_repository") pointer(shared_ptr) readonly class build_repository @@ -46,7 +46,18 @@ namespace brep build_repository () = default; }; - // Foreign object that is mapped to the subset of package object. + // "Foreign" value type that is mapped to a subset of the build_constraint + // value type (see libbpkg/manifest.hxx for details). + // + #pragma db value + struct build_constraint_subset + { + bool exclusion; + string config; + optional<string> target; + }; + + // Foreign object that is mapped to a subset of package object. // #pragma db object table("build_package") pointer(shared_ptr) readonly class build_package @@ -56,10 +67,16 @@ namespace brep upstream_version version; lazy_shared_ptr<build_repository> internal_repository; + // Mapped to a subset of the package object build_constraints member + // using the PostgreSQL foreign table mechanism. + // + vector<build_constraint_subset> constraints; + // Database mapping. // #pragma db member(id) id column("") #pragma db member(version) set(this.version.init (this.id.version, (?))) + #pragma db member(constraints) id_column("") value_column("") private: friend class odb::access; @@ -102,6 +119,27 @@ namespace brep // #pragma db member(result) column("count(" + build_package::id.name + ")") }; + + // Packages that have the build constraints. Note that only buildable + // (internal and non-stub) packages can have such constraints, so there is + // no need for additional checks. + // + #pragma db view \ + table("build_package_constraints" = "c") \ + object(build_package = package inner: \ + "c.exclusion AND " \ + "c.name = " + package::id.name + "AND" + \ + "c.version_epoch = " + package::id.version.epoch + "AND" + \ + "c.version_canonical_upstream = " + \ + package::id.version.canonical_upstream + "AND" + \ + "c.version_canonical_release = " + \ + package::id.version.canonical_release + "AND" + \ + "c.version_revision = " + package::id.version.revision) \ + query(distinct) + struct build_constrained_package + { + shared_ptr<build_package> package; + }; } #endif // LIBBREP_BUILD_PACKAGE_HXX diff --git a/libbrep/package.cxx b/libbrep/package.cxx index 20be387..e14b15a 100644 --- a/libbrep/package.cxx +++ b/libbrep/package.cxx @@ -62,6 +62,7 @@ namespace brep optional<email_type> be, dependencies_type dp, requirements_type rq, + build_constraints_type bc, optional<path> lc, optional<string> sh, shared_ptr<repository_type> rp) @@ -80,6 +81,10 @@ namespace brep build_email (move (be)), dependencies (move (dp)), requirements (move (rq)), + build_constraints ( + version.compare (wildcard_version, true) != 0 + ? move (bc) + : build_constraints_type ()), internal_repository (move (rp)), location (move (lc)), sha256sum (move (sh)) diff --git a/libbrep/package.hxx b/libbrep/package.hxx index 5a159ae..d3e3e00 100644 --- a/libbrep/package.hxx +++ b/libbrep/package.hxx @@ -9,6 +9,7 @@ #include <chrono> #include <odb/core.hxx> +#include <odb/section.hxx> #include <odb/nested-container.hxx> #include <libbrep/types.hxx> @@ -18,9 +19,9 @@ // Used by the data migration entries. // -#define LIBBREP_PACKAGE_SCHEMA_VERSION_BASE 4 +#define LIBBREP_PACKAGE_SCHEMA_VERSION_BASE 5 -#pragma db model version(LIBBREP_PACKAGE_SCHEMA_VERSION_BASE, 4, closed) +#pragma db model version(LIBBREP_PACKAGE_SCHEMA_VERSION_BASE, 5, open) namespace brep { @@ -164,6 +165,13 @@ namespace brep #pragma db value(requirement_alternatives) definition + // build_constraints + // + using bpkg::build_constraint; + using build_constraints = vector<build_constraint>; + + #pragma db value(build_constraint) definition + #pragma db value class certificate { @@ -285,8 +293,10 @@ namespace brep using email_type = brep::email; using dependencies_type = brep::dependencies; using requirements_type = brep::requirements; + using build_constraints_type = brep::build_constraints; - // Create internal package object. + // Create internal package object. Note that for stubs the build + // constraints are meaningless, and so not saved. // package (string name, version_type, @@ -303,6 +313,7 @@ namespace brep optional<email_type> build_email, dependencies_type, requirements_type, + build_constraints_type, optional<path> location, optional<string> sha256sum, shared_ptr<repository_type>); @@ -337,6 +348,9 @@ namespace brep dependencies_type dependencies; requirements_type requirements; + build_constraints_type build_constraints; // Note: foreign-mapped in build. + odb::section build_section; + // Note that it is foreign-mapped in build. // lazy_shared_ptr<repository_type> internal_repository; @@ -413,9 +427,16 @@ namespace brep set(odb::nested_set (this.requirements, std::move (?))) \ id_column("") key_column("") value_column("id") + // build_constraints + // + #pragma db member(build_constraints) id_column("") value_column("") \ + section(build_section) + + #pragma db member(build_section) load(lazy) update(always) + // other_repositories // - #pragma db member(other_repositories) \ + #pragma db member(other_repositories) \ id_column("") value_column("repository") value_not_null // search_index diff --git a/libbrep/package.xml b/libbrep/package.xml index 657c2fc..0947d6f 100644 --- a/libbrep/package.xml +++ b/libbrep/package.xml @@ -1,5 +1,5 @@ <changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" schema-name="package" version="1"> - <model version="4"> + <model version="5"> <table name="repository" kind="object"> <column name="name" type="TEXT" null="false"/> <column name="location" type="TEXT" null="false"/> @@ -374,6 +374,42 @@ <column name="version_revision"/> </index> </table> + <table name="package_build_constraints" kind="container"> + <column name="name" type="TEXT" null="false"/> + <column name="version_epoch" type="INTEGER" null="false"/> + <column name="version_canonical_upstream" type="TEXT" null="false"/> + <column name="version_canonical_release" type="TEXT" null="false" options="COLLATE "C""/> + <column name="version_revision" type="INTEGER" null="false"/> + <column name="index" type="BIGINT" null="false"/> + <column name="exclusion" type="BOOLEAN" null="false"/> + <column name="config" type="TEXT" null="false"/> + <column name="target" type="TEXT" null="true"/> + <column name="comment" type="TEXT" null="false"/> + <foreign-key name="object_id_fk" on-delete="CASCADE"> + <column name="name"/> + <column name="version_epoch"/> + <column name="version_canonical_upstream"/> + <column name="version_canonical_release"/> + <column name="version_revision"/> + <references table="package"> + <column name="name"/> + <column name="version_epoch"/> + <column name="version_canonical_upstream"/> + <column name="version_canonical_release"/> + <column name="version_revision"/> + </references> + </foreign-key> + <index name="package_build_constraints_object_id_i"> + <column name="name"/> + <column name="version_epoch"/> + <column name="version_canonical_upstream"/> + <column name="version_canonical_release"/> + <column name="version_revision"/> + </index> + <index name="package_build_constraints_index_i"> + <column name="index"/> + </index> + </table> <table name="package_other_repositories" kind="container"> <column name="name" type="TEXT" null="false"/> <column name="version_epoch" type="INTEGER" null="false"/> diff --git a/load/load.cxx b/load/load.cxx index bab35ec..860aeb0 100644 --- a/load/load.cxx +++ b/load/load.cxx @@ -410,6 +410,7 @@ load_packages (const shared_ptr<repository>& rp, database& db) move (pm.build_email), move (ds), move (pm.requirements), + move (pm.build_constraints), move (pm.location), move (pm.sha256sum), rp); diff --git a/mod/build-config.cxx b/mod/build-config.cxx index 6b59e54..fed2ee9 100644 --- a/mod/build-config.cxx +++ b/mod/build-config.cxx @@ -146,4 +146,31 @@ namespace brep "&cf=" + mime_url_encode (b.configuration) + "&tc=" + b.toolchain_version.string () + "&reason="; } + + bool + match (const string& config_pattern, const optional<string>& target_pattern, + const build_config& c) + { + return path_match (config_pattern, c.name) && + (!target_pattern || + (c.target && path_match (*target_pattern, c.target->string ()))); + } + + bool + exclude (const build_package& p, const build_config& c) + { + for (const auto& bc: p.constraints) + { + if (!bc.exclusion && match (bc.config, bc.target, c)) + return false; + } + + for (const auto& bc: p.constraints) + { + if (bc.exclusion && match (bc.config, bc.target, c)) + return true; + } + + return false; + } } diff --git a/mod/build-config.hxx b/mod/build-config.hxx index b49819d..3c89ceb 100644 --- a/mod/build-config.hxx +++ b/mod/build-config.hxx @@ -13,9 +13,12 @@ #include <libbrep/utility.hxx> #include <libbrep/build.hxx> +#include <libbrep/build-package.hxx> #include <mod/options.hxx> +// Various build-related state and utilities. +// namespace brep { // Return pointer to the shared build configurations instance, creating one @@ -48,6 +51,18 @@ namespace brep // string force_rebuild_url (const string& host, const dir_path& root, const build&); + + // Match a build configuration against the name and target patterns. + // + bool + match (const string& config_pattern, + const optional<string>& target_pattern, + const bbot::build_config&); + + // Return true if a package excludes the specified build configuration. + // + bool + exclude (const build_package&, const bbot::build_config&); } #endif // MOD_BUILD_CONFIG_HXX diff --git a/mod/mod-build-task.cxx b/mod/mod-build-task.cxx index 1a7b272..771b1ee 100644 --- a/mod/mod-build-task.cxx +++ b/mod/mod-build-task.cxx @@ -442,80 +442,94 @@ handle (request& rq, response& rs) if (!configs.empty ()) { - config_machine& cm (configs.begin ()->second); - machine_header_manifest& mh (*cm.machine); - build_id bid (move (id), cm.config->name, toolchain_version); - shared_ptr<build> b (build_db_->find<build> (bid)); - optional<string> cl (challenge ()); - - // If build configuration doesn't exist then create the new one and - // persist. Otherwise put it into the building state, refresh the - // timestamp and update. + // Find the first build configuration that is not excluded by the + // package. // - if (b == nullptr) - { - b = make_shared<build> (move (bid.package.name), - move (bp.version), - move (bid.configuration), - move (tqm.toolchain_name), - move (toolchain_version), - move (agent_fp), - move (cl), - mh.name, - move (mh.summary), - cm.config->target); - - build_db_->persist (b); - } - else + shared_ptr<build_package> p (build_db_->load<build_package> (id)); + + auto i (configs.begin ()); + auto e (configs.end ()); + for (; i != e && exclude (*p, *i->second.config); ++i) ; + + if (i != e) { - // The package configuration is in the building state, and there - // are no results. + config_machine& cm (i->second); + machine_header_manifest& mh (*cm.machine); + build_id bid (move (id), cm.config->name, toolchain_version); + shared_ptr<build> b (build_db_->find<build> (bid)); + optional<string> cl (challenge ()); + + // If build configuration doesn't exist then create the new one and + // persist. Otherwise put it into the building state, refresh the + // timestamp and update. // - // Note that in both cases we keep the status intact to be able to - // compare it with the final one in the result request handling in - // order to decide if to send the notification email. The same is - // true for the forced flag (in the sense that we don't set the - // force state to unforced). - // - // Load the section to assert the above statement. - // - build_db_->load (*b, b->results_section); + if (b == nullptr) + { + b = make_shared<build> (move (bid.package.name), + move (bp.version), + move (bid.configuration), + move (tqm.toolchain_name), + move (toolchain_version), + move (agent_fp), + move (cl), + mh.name, + move (mh.summary), + cm.config->target); + + build_db_->persist (b); + } + else + { + // The package configuration is in the building state, and there + // are no results. + // + // Note that in both cases we keep the status intact to be able + // to compare it with the final one in the result request + // handling in order to decide if to send the notification email. + // The same is true for the forced flag (in the sense that we + // don't set the force state to unforced). + // + // Load the section to assert the above statement. + // + build_db_->load (*b, b->results_section); - assert (b->state == build_state::building && b->results.empty ()); + assert (b->state == build_state::building && + b->results.empty ()); - b->state = build_state::building; + b->state = build_state::building; - // Switch the force state not to reissue the task after the forced - // rebuild timeout. Note that the result handler will still - // recognize that the rebuild was forced. - // - if (b->force == force_state::forcing) - b->force = force_state::forced; - - b->toolchain_name = move (tqm.toolchain_name); - b->agent_fingerprint = move (agent_fp); - b->agent_challenge = move (cl); - b->machine = mh.name; - b->machine_summary = move (mh.summary); - b->target = cm.config->target; - b->timestamp = timestamp::clock::now (); - - build_db_->update (b); - } + // Switch the force state not to reissue the task after the + // forced rebuild timeout. Note that the result handler will + // still recognize that the rebuild was forced. + // + if (b->force == force_state::forcing) + b->force = force_state::forced; - // Finally, prepare the task response manifest. - // - shared_ptr<build_package> p ( - build_db_->load<build_package> (b->id.package)); + b->toolchain_name = move (tqm.toolchain_name); + b->agent_fingerprint = move (agent_fp); + b->agent_challenge = move (cl); + b->machine = mh.name; + b->machine_summary = move (mh.summary); + b->target = cm.config->target; + b->timestamp = timestamp::clock::now (); - p->internal_repository.load (); + build_db_->update (b); + } - tsm = task (move (b), move (p), cm); + // Finally, prepare the task response manifest. + // + // We iterate over buildable packages. + // + assert (p->internal_repository != nullptr); + + p->internal_repository.load (); + + tsm = task (move (b), move (p), cm); + } } // If the task response manifest is prepared, then bail out from the - // package loop, commit transactions and respond. + // package loop, commit the transaction and respond. // if (!tsm.session.empty ()) break; @@ -556,10 +570,10 @@ handle (request& rq, response& rs) // Pick the first package configuration from the ordered list. // - // Note that the configurations may not match the required criteria - // anymore (as we have committed the database transactions that were - // used to collect this data) so we recheck. If we find one that matches - // then put it into the building state, refresh the timestamp and + // Note that the configurations and packages may not match the required + // criteria anymore (as we have committed the database transactions that + // were used to collect this data) so we recheck. If we find one that + // matches then put it into the building state, refresh the timestamp and // update. Note that we don't amend the status and the force state to // have them available in the result request handling (see above). // @@ -582,25 +596,28 @@ handle (request& rq, response& rs) // assert (i != cfg_machines.end ()); const config_machine& cm (i->second); - const machine_header_manifest& mh (*cm.machine); - // Load the package (if still present). + // Rebuild the package if still present, is buildable and doesn't + // exclude the configuration. // shared_ptr<build_package> p ( build_db_->find<build_package> (b->id.package)); - if (p != nullptr) + if (p != nullptr && p->internal_repository != nullptr && + !exclude (*p, *cm.config)) { assert (b->status); b->state = build_state::building; - b->machine = mh.name; // Can't move from, as may need them on the next iteration. // b->agent_fingerprint = agent_fp; b->agent_challenge = cl; b->toolchain_name = tqm.toolchain_name; + + const machine_header_manifest& mh (*cm.machine); + b->machine = mh.name; b->machine_summary = mh.summary; b->target = cm.config->target; diff --git a/mod/mod-builds.cxx b/mod/mod-builds.cxx index 439bd05..c7a308e 100644 --- a/mod/mod-builds.cxx +++ b/mod/mod-builds.cxx @@ -98,7 +98,9 @@ build_query (const brep::cstrings& configs, const brep::params::builds& params) using query = query<T>; using qb = typename query::build; - query q (qb::id.configuration.in_range (configs.begin (), configs.end ())); + query q (!configs.empty () + ? qb::id.configuration.in_range (configs.begin (), configs.end ()) + : query (true)); // Note that there is no error reported if the filter parameters parsing // fails. Instead, it is considered that no package builds match such a @@ -199,13 +201,12 @@ build_query (const brep::cstrings& configs, const brep::params::builds& params) return q; } -template <typename T> +template <typename T, typename P = typename query<T>::build_package> static inline query<T> package_query (const brep::params::builds& params) { using namespace brep; using query = query<T>; - using qp = typename query::build_package; query q (true); @@ -217,12 +218,12 @@ package_query (const brep::params::builds& params) // Package name. // if (!params.name ().empty ()) - q = q && qp::id.name.like (transform (params.name ())); + q = q && P::id.name.like (transform (params.name ())); // Package version. // if (!params.version ().empty () && params.version () != "*") - q = q && compare_version_eq (qp::id.version, + q = q && compare_version_eq (P::id.version, version (params.version ()), // May throw. true); } @@ -234,6 +235,20 @@ package_query (const brep::params::builds& params) return q; } +template <typename T, typename ID> +static inline query<T> +package_id_eq (const ID& x, const brep::package_id& y) +{ + using query = query<T>; + const auto& qv (x.version); + + return x.name == query::_ref (y.name) && + qv.epoch == query::_ref (y.version.epoch) && + qv.canonical_upstream == query::_ref (y.version.canonical_upstream) && + qv.canonical_release == query::_ref (y.version.canonical_release) && + qv.revision == query::_ref (y.version.revision); +} + static const vector<pair<string, string>> build_results ({ {"unbuilt", "<unbuilt>"}, {"*", "*"}, @@ -283,7 +298,7 @@ handle (request& rq, response& rs) // // This hack is required to avoid the "flash of unstyled content", which // happens due to the presence of the autofocus attribute in the input - // element of the search form. The problem appears in Firefox and has a + // element of the filter form. The problem appears in Firefox and has a // (4-year old, at the time of this writing) bug report: // // https://bugzilla.mozilla.org/show_bug.cgi?id=712130. @@ -491,17 +506,15 @@ handle (request& rq, response& rs) }; // Note that config_toolchains contains shallow references to the toolchain - // names and versions, and in particular to the selected ones (tc_name and - // tc_version). + // names and versions. // - string tc_name; - version tc_version; set<config_toolchain> config_toolchains; - { transaction t (build_db_->begin ()); toolchains = query_toolchains (); + string tc_name; + version tc_version; const string& tc (params.toolchain ()); if (tc != "*") @@ -527,6 +540,7 @@ handle (request& rq, response& rs) const string& pc (params.configuration ()); const string& tg (params.target ()); + vector<const build_config*> configs; for (const auto& c: *build_conf_) { @@ -537,11 +551,13 @@ handle (request& rq, response& rs) : tg == "*" || (c.target && path_match (tg, c.target->string ())))) { - if (tc != "*") // Filter by toolchain. - config_toolchains.insert ({c.name, tc_name, tc_version}); - else // Add all toolchains. + configs.push_back (&c); + + for (const auto& t: toolchains) { - for (const auto& t: toolchains) + // Filter by toolchain. + // + if (tc == "*" || (t.first == tc_name && t.second == tc_version)) config_toolchains.insert ({c.name, t.first, t.second}); } } @@ -551,6 +567,15 @@ handle (request& rq, response& rs) // difference between the maximum possible number of unbuilt // configurations and the number of existing package builds. // + // Note that we also need to deduct the package-excluded configurations + // count from the maximum possible number of unbuilt configurations. The + // only way to achieve this is to traverse through the build-constrained + // packages and match their constraints against our configurations. + // + // Also note that some existing builds can now be excluded by packages + // due to the build configuration target change. We should deduct such + // builds count from the number of existing package builds. + // size_t nmax (config_toolchains.size () * build_db_->query_value<buildable_package_count> ( package_query<buildable_package_count> (params))); @@ -558,6 +583,63 @@ handle (request& rq, response& rs) size_t ncur = build_db_->query_value<package_build_count> ( build_query<package_build_count> (*build_conf_names_, bld_params)); + // From now we will be using specific package name and version for each + // build database query. + // + bld_params.name ().clear (); + bld_params.version ().clear (); + + if (!config_toolchains.empty ()) + { + // Prepare the build count prepared query. + // + // For each package-excluded configuration we will query the number of + // existing builds. + // + using bld_query = query<package_build_count>; + using prep_bld_query = prepared_query<package_build_count>; + + package_id id; + string config; + + bld_query bq ( + package_id_eq<package_build_count> ( + bld_query::build::id.package, id) && + bld_query::build::id.configuration == bld_query::_ref (config) && + build_query<package_build_count> (cstrings (), bld_params)); + + prep_bld_query bld_prep_query ( + build_db_->prepare_query<package_build_count> ( + "mod-builds-build-count-query", bq)); + + size_t nt (tc == "*" ? toolchains.size () : 1); + + // The number of build-constrained packages can potentially be large, + // and we may implement some caching in the future. However, the + // caching will not be easy as the cached values depend on the filter + // form parameters. + // + using query = query<build_constrained_package>; + query q (package_query<build_constrained_package, query> (params)); + + for (const auto& p: build_db_->query<build_constrained_package> (q)) + { + const build_package& bp (*p.package); + id = bp.id; + + for (const auto& c: configs) + { + if (exclude (bp, *c)) + { + nmax -= nt; + + config = c->name; + ncur -= bld_prep_query.execute_value (); + } + } + } + } + assert (nmax >= ncur); count = nmax - ncur; @@ -620,36 +702,37 @@ handle (request& rq, response& rs) using bld_query = query<package_build>; using prep_bld_query = prepared_query<package_build>; - // We will use specific package name and version for each database query. - // - bld_params.name ().clear (); - bld_params.version ().clear (); - package_id id; - const auto& qv (bld_query::build::id.package.version); bld_query bq ( - bld_query::build::id.package.name == bld_query::_ref (id.name) && - - qv.epoch == bld_query::_ref (id.version.epoch) && - qv.canonical_upstream == - bld_query::_ref (id.version.canonical_upstream) && - qv.canonical_release == - bld_query::_ref (id.version.canonical_release) && - qv.revision == bld_query::_ref (id.version.revision) && - + package_id_eq<package_build> (bld_query::build::id.package, id) && build_query<package_build> (*build_conf_names_, bld_params)); prep_bld_query bld_prep_query ( conn->prepare_query<package_build> ("mod-builds-build-query", bq)); + // Prepare the build-constrained package prepared query. + // + // For each build-constrained package we will exclude the corresponding + // configurations from being printed. + // + using ctr_query = query<build_constrained_package>; + using prep_ctr_query = prepared_query<build_constrained_package>; + + ctr_query cq ( + package_id_eq<build_constrained_package> (ctr_query::id, id)); + + prep_ctr_query ctr_prep_query ( + conn->prepare_query<build_constrained_package> ( + "mod-builds-build-constrained-package-query", cq)); + size_t skip (page * page_configs); size_t print (page_configs); // Enclose the subsequent tables to be able to use nth-child CSS selector. // s << DIV; - for (bool prn (true); prn; ) + while (print != 0) { transaction t (conn->begin ()); @@ -657,27 +740,47 @@ handle (request& rq, response& rs) // auto packages (pkg_prep_query.execute ()); - if ((prn = !packages.empty ())) + if (packages.empty ()) + print = 0; + else { offset += packages.size (); // Iterate over packages and print unbuilt configurations. Skip the // appropriate number of them first (for page number greater than one). // - for (auto& pv: packages) + for (auto& p: packages) { - id = move (pv.id); + id = move (p.id); - // Make a copy for this package. + // Copy configuration/toolchain combinations for this package, + // skipping explicitly excluded configurations. // - auto unbuilt_configs (config_toolchains); + set<config_toolchain> unbuilt_configs; + { + build_constrained_package p; + if (ctr_prep_query.execute_one (p)) + { + const build_package& bp (*p.package); + for (const auto& ct: config_toolchains) + { + auto i (build_conf_map_->find (ct.configuration.c_str ())); + assert (i != build_conf_map_->end ()); + + if (!exclude (bp, *i->second)) + unbuilt_configs.insert (ct); + } + } + else + unbuilt_configs = config_toolchains; + } // Iterate through the package configuration builds and erase them // from the unbuilt configurations set. // for (const auto& pb: bld_prep_query.execute ()) { - build& b (*pb.build); + const build& b (*pb.build); unbuilt_configs.erase ({ b.id.configuration, b.toolchain_name, b.toolchain_version}); @@ -693,12 +796,6 @@ handle (request& rq, response& rs) continue; } - if (print-- == 0) - { - prn = false; - break; - } - auto i (build_conf_map_->find (ct.configuration.c_str ())); assert (i != build_conf_map_->end ()); @@ -707,7 +804,7 @@ handle (request& rq, response& rs) s << TABLE(CLASS="proplist build") << TBODY << TR_NAME (id.name, string (), root) - << TR_VERSION (id.name, pv.version, root) + << TR_VERSION (id.name, p.version, root) << TR_VALUE ("toolchain", string (ct.toolchain_name) + '-' + ct.toolchain_version.string ()) @@ -715,9 +812,12 @@ handle (request& rq, response& rs) << TR_VALUE ("target", tg ? tg->string () : "<default>") << ~TBODY << ~TABLE; + + if (--print == 0) // Bail out the configuration loop. + break; } - if (!prn) + if (print == 0) // Bail out the package loop. break; } } diff --git a/mod/mod-package-version-details.cxx b/mod/mod-package-version-details.cxx index fd8d9a1..4da13ab 100644 --- a/mod/mod-package-version-details.cxx +++ b/mod/mod-package-version-details.cxx @@ -10,6 +10,8 @@ #include <odb/database.hxx> #include <odb/transaction.hxx> +#include <libbutl/utility.hxx> // alpha(), ucase(), lcase() + #include <web/xhtml.hxx> #include <web/module.hxx> #include <web/mime-url-encoding.hxx> @@ -23,6 +25,7 @@ #include <mod/options.hxx> using namespace std; +using namespace butl; using namespace odb::core; using namespace brep::cli; @@ -280,6 +283,14 @@ handle (request& rq, response& rs) << ~TABLE; } + // Don't display the page builds section for stub packages. + // + bool builds (build_db_ != nullptr && + ver.compare (wildcard_version, true) != 0); + + if (builds) + package_db_->load (*pkg, pkg->build_section); + t.commit (); const auto& rm (pkg->requirements); @@ -325,9 +336,7 @@ handle (request& rq, response& rs) << ~TABLE; } - // Don't display the section for stub packages. - // - if (build_db_ != nullptr && ver.compare (wildcard_version, true) != 0) + if (builds) { s << H3 << "Builds" << ~H3 << DIV(ID="builds"); @@ -335,6 +344,8 @@ handle (request& rq, response& rs) timestamp now (timestamp::clock::now ()); transaction t (build_db_->begin ()); + // Print built package configurations. + // using query = query<build>; for (auto& b: build_db_->query<build> ( @@ -368,6 +379,60 @@ handle (request& rq, response& rs) << ~TABLE; } + // Print configurations that are excluded by the package. + // + auto excluded = [&pkg] (const bbot::build_config& c, string& reason) + { + for (const auto& bc: pkg->build_constraints) + { + if (!bc.exclusion && match (bc.config, bc.target, c)) + return false; + } + + for (const auto& bc: pkg->build_constraints) + { + if (bc.exclusion && match (bc.config, bc.target, c)) + { + // Save the first sentence of the exclusion comment, lower-case the + // first letter if the beginning looks like a word (the second + // character is the lower-case letter or space). + // + reason = bc.comment.substr (0, bc.comment.find ('.')); + + char c; + size_t n (reason.size ()); + if (n > 0 && alpha (c = reason[0]) && c == ucase (c) && + (n == 1 || + (alpha (c = reason[1]) && c == lcase (c)) || + c == ' ')) + reason[0] = lcase (reason[0]); + + return true; + } + } + + return false; + }; + + for (const auto& c: *build_conf_) + { + string reason; + if (excluded (c, reason)) + { + s << TABLE(CLASS="proplist build") + << TBODY + << TR_VALUE ("config", + c.name + " / " + + (c.target ? c.target->string () : "<default>")) + << TR_VALUE ("result", + !reason.empty () + ? "excluded (" + reason + ')' + : "excluded") + << ~TBODY + << ~TABLE; + } + } + t.commit (); s << ~DIV; diff --git a/tests/load/1/math/libexp-1~1.2+1.tar.gz b/tests/load/1/math/libexp-1~1.2+1.tar.gz Binary files differindex 1e1807f..1d15a59 100644 --- a/tests/load/1/math/libexp-1~1.2+1.tar.gz +++ b/tests/load/1/math/libexp-1~1.2+1.tar.gz diff --git a/tests/load/1/math/packages b/tests/load/1/math/packages index 22a3167..a796d31 100644 --- a/tests/load/1/math/packages +++ b/tests/load/1/math/packages @@ -12,8 +12,10 @@ email: users@exp.com build-email: builds@exp.com depends: libmisc depends: libpq >= 9.0.0 +build-include: linux* +build-exclude: *; Only supported on Linux. location: libexp-1~1.2+1.tar.gz -sha256sum: 96add9edada45f4ceee18b3ec344ca3c4fc1473d9aad22a13e97d7728a439087 +sha256sum: 0a7414d06ad26d49dad203deaf3841f3df97f1fe27c5bf190c1c20dfeb7f84e0 : name: libfoo version: 1.0 diff --git a/tests/load/driver.cxx b/tests/load/driver.cxx index a907be1..2c8f4b6 100644 --- a/tests/load/driver.cxx +++ b/tests/load/driver.cxx @@ -46,6 +46,16 @@ check_external (const package& p) p.requirements.empty () && !p.sha256sum; } +namespace bpkg +{ + static bool + operator== (const build_constraint& x, const build_constraint& y) + { + return x.exclusion == y.exclusion && x.config == y.config && + x.target == y.target && x.comment == y.comment; + } +} + int main (int argc, char* argv[]) { @@ -237,13 +247,11 @@ main (int argc, char* argv[]) assert (fpv2->dependencies[0].size () == 1); assert (fpv2->dependencies[1].size () == 1); - auto dep ( - [&db](const char* n, - const optional<dependency_constraint>& c) -> dependency - { - return { - lazy_shared_ptr<package> (db, package_id (n, version ())), c}; - }); + auto dep = [&db] ( + const char* n, const optional<dependency_constraint>& c) -> dependency + { + return {lazy_shared_ptr<package> (db, package_id (n, version ())), c}; + }; assert (fpv2->dependencies[0][0] == dep ( @@ -635,9 +643,17 @@ main (int argc, char* argv[]) assert (epv->requirements.empty ()); + db.load (*epv, epv->build_section); + + assert ( + epv->build_constraints == + build_constraints ({ + build_constraint (false, "linux*", nullopt, ""), + build_constraint (true, "*", nullopt, "Only supported on Linux.")})); + assert (check_location (epv)); assert (epv->sha256sum && *epv->sha256sum == - "96add9edada45f4ceee18b3ec344ca3c4fc1473d9aad22a13e97d7728a439087"); + "0a7414d06ad26d49dad203deaf3841f3df97f1fe27c5bf190c1c20dfeb7f84e0"); // Verify libpq package version. // |