diff --git a/rust/src/lib.rs b/rust/src/lib.rs index fe9e479374..d132aa5e0d 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -426,6 +426,7 @@ pub mod ffi { fn get_checksum(&self, repo: Pin<&mut OstreeRepo>) -> Result; fn get_ostree_ref(&self) -> String; fn get_repo_packages(&self) -> &[RepoPackage]; + fn get_override_replace_query(&self) -> &[RepoPackage]; fn clear_repo_packages(&mut self); fn prettyprint_json_stdout(&self); fn print_deprecation_warnings(&self); diff --git a/rust/src/treefile.rs b/rust/src/treefile.rs index 04e1f4eaf6..86e19527fd 100644 --- a/rust/src/treefile.rs +++ b/rust/src/treefile.rs @@ -786,6 +786,13 @@ impl Treefile { self.parsed.repo_packages.as_deref().unwrap_or_default() } + pub(crate) fn get_override_replace_query(&self) -> &[RepoPackage] { + self.parsed + .override_replace_query + .as_deref() + .unwrap_or_default() + } + pub(crate) fn clear_repo_packages(&mut self) { self.parsed.repo_packages.take(); } @@ -1025,7 +1032,8 @@ impl TreefileExternals { Ok(()) } - fn assert_nonempty(&self) { + // Panic if there is externally referenced data. + fn assert_empty(&self) { // can't use the Default trick here because we can't auto-derive Eq because of `File` assert!(self.postprocess_script.is_none()); assert!(self.add_files.is_empty()); @@ -1203,16 +1211,18 @@ pub(crate) enum RpmdbBackend { /// cases. Everything else is specific to either case and so lives in their respective flattened /// field. #[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] pub(crate) struct TreeComposeConfig { #[serde(skip_serializing_if = "Option::is_none")] pub(crate) packages: Option>, #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "repo-packages")] pub(crate) repo_packages: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) modules: Option, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) cliwrap: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) override_replace_query: Option>, #[serde(flatten)] pub(crate) derive: DeriveConfigFields, @@ -1221,6 +1231,20 @@ pub(crate) struct TreeComposeConfig { pub(crate) base: BaseComposeConfigFields, } +impl std::ops::Deref for TreeComposeConfig { + type Target = BaseComposeConfigFields; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl std::ops::DerefMut for TreeComposeConfig { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.base + } +} + /// These fields are only useful when composing a new ostree commit. #[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] @@ -1504,7 +1528,7 @@ impl TreeComposeConfig { fn substitute_vars(mut self) -> Result { let mut substvars: collections::HashMap = collections::HashMap::new(); // Substitute ${basearch} and ${releasever} - if let Some(arch) = &self.base.basearch { + if let Some(arch) = &self.basearch { substvars.insert("basearch".to_string(), arch.clone()); } if let Some(releasever) = &self.base.releasever { @@ -1809,11 +1833,18 @@ pub(crate) mod tests { override-remove: - foo - bar + override-replace-query: + - repo: foo + packages: + - bar + - baz "}); let v = treefile.derive.override_remove.unwrap(); assert_eq!(v.len(), 2); assert_eq!(v[0], "foo"); assert_eq!(v[1], "bar"); + let q = &treefile.override_replace_query.unwrap()[0]; + assert_eq!(q.repo, "foo"); } #[test] @@ -2281,6 +2312,7 @@ pub(crate) fn treefile_new_compose( pub(crate) fn treefile_new_client(filename: &str, basearch: &str) -> CxxResult> { let r = treefile_new(filename, basearch, -1)?; r.error_if_base()?; + dbg!(&r); Ok(r) } @@ -2315,7 +2347,8 @@ pub(crate) fn treefile_new_client_from_etc(basearch: &str) -> CxxResult(old->pdata[0]); /* did we expect this nevra to replace a base pkg? */ - if (rpmostree_str_ptrarray_contains (replaced_nevras, nevra)) + if (rpmostree_str_ptrarray_contains (replaced_nevras, nevra) || + rpmostree_str_ptrarray_contains (replaced_pkgnames, dnf_package_get_name (pkg))) g_hash_table_insert (self->pkgs_to_replace, gv_nevra_from_pkg (pkg), gv_nevra_from_pkg (old_pkg)); else @@ -1601,7 +1603,9 @@ check_goal_solution (RpmOstreeContext *self, { g_autoptr(GVariant) nevra_v = g_variant_get_child_value (newv, 0); const char *nevra = g_variant_get_string (nevra_v, NULL); - if (!rpmostree_str_ptrarray_contains (replaced_nevras, nevra)) + const char *name = NULL; + g_variant_get_child (newv, 1, "&s", &name); + if (!(rpmostree_str_ptrarray_contains (replaced_nevras, nevra) || rpmostree_str_ptrarray_contains (replaced_pkgnames, name))) g_ptr_array_add (forbidden, g_strdup (nevra)); } @@ -1997,14 +2001,50 @@ rpmostree_context_prepare (RpmOstreeContext *self, } /* First, handle packages to remove */ - g_autoptr(GPtrArray) removed_pkgnames = g_ptr_array_new (); + g_autoptr(GPtrArray) removed_pkgnames = g_ptr_array_new_with_free_func (g_free); for (auto &pkgname_v: packages_override_remove) { const char *pkgname = pkgname_v.c_str(); if (!dnf_context_remove (dnfctx, pkgname, error)) return FALSE; - g_ptr_array_add (removed_pkgnames, (gpointer)pkgname); + g_ptr_array_add (removed_pkgnames, g_strdup(pkgname)); + } + + // Then, repo packages to remove and replace + g_autoptr(DnfPackageSet) pinned_pkgs = dnf_packageset_new (sack); + Map *pinned_pkgs_map = dnf_packageset_get_map (pinned_pkgs); + auto override_packages_query = self->treefile_rs->get_override_replace_query(); + for (auto & repo_pkg : override_packages_query) + { + auto repo = std::string(repo_pkg.get_repo()); + auto pkgs = repo_pkg.get_packages(); + for (auto & pkg_r : pkgs) + { + // We need to copy here because we only have an immutable reference to Rust + auto pkg = std::string(pkg_r); + const char *pkgname = pkg.c_str(); + if (!dnf_context_remove (dnfctx, pkgname, error)) + return FALSE; + // Then copy here *again*; TODO: switch this to a vec which owns the string + char *copied = g_strdup (pkgname); + g_ptr_array_add (replaced_pkgnames, copied); + + g_auto(HySubject) subject = hy_subject_create(pkg.c_str()); + /* this is basically like a slightly customized dnf_context_install() */ + HyNevra nevra = NULL; + hy_autoquery HyQuery query = + hy_subject_get_best_solution(subject, sack, NULL, &nevra, FALSE, TRUE, TRUE, TRUE, FALSE); + hy_query_filter (query, HY_PKG_REPONAME, HY_EQ, repo.c_str()); + g_autoptr(DnfPackageSet) pset = hy_query_run_set (query); + if (dnf_packageset_count (pset) == 0) + return glnx_throw (error, "No matches for '%s' in repo '%s'", pkg.c_str(), repo.c_str()); + g_auto(HySelector) selector = hy_selector_create (sack); + hy_selector_pkg_set (selector, pset); + if (!hy_goal_install_selector (goal, selector, error)) + return FALSE; + map_or (pinned_pkgs_map, dnf_packageset_get_map (pset)); + } } /* Then, handle local packages to install */ @@ -2017,8 +2057,6 @@ rpmostree_context_prepare (RpmOstreeContext *self, return FALSE; /* Now repo-packages */ - g_autoptr(DnfPackageSet) pinned_pkgs = dnf_packageset_new (sack); - Map *pinned_pkgs_map = dnf_packageset_get_map (pinned_pkgs); auto repo_pkgs = self->treefile_rs->get_repo_packages(); for (auto & repo_pkg : repo_pkgs) { @@ -2123,7 +2161,7 @@ rpmostree_context_prepare (RpmOstreeContext *self, auto task = rpmostreecxx::progress_begin_task("Resolving dependencies"); /* XXX: consider a --allow-uninstall switch? */ if (!dnf_goal_depsolve (goal, actions, error) || - !check_goal_solution (self, removed_pkgnames, replaced_nevras, error)) + !check_goal_solution (self, removed_pkgnames, replaced_pkgnames, replaced_nevras, error)) return FALSE; g_clear_pointer (&self->pkgs, (GDestroyNotify)g_ptr_array_unref); self->pkgs = dnf_goal_get_packages (goal,