From: Zac Medico <zmedico@gentoo.org>
To: gentoo-portage-dev@lists.gentoo.org
Cc: Zac Medico <zmedico@gentoo.org>
Subject: [gentoo-portage-dev] [PATCH 2/2] bintree: Add REPO_REVISIONS to package index header
Date: Wed, 13 Mar 2024 21:42:33 -0700 [thread overview]
Message-ID: <20240314044233.1003525-3-zmedico@gentoo.org> (raw)
In-Reply-To: <20240314044233.1003525-1-zmedico@gentoo.org>
As a means for binhost clients to select source repo
revisions which are consistent with binhosts, inject
REPO_REVISIONS from a package into the index header,
using a history of synced revisions to guarantee
forward progress. This queries the relevant repos to
check if any new revisions have appeared in the
absence of a proper sync operation.
Bug: https://bugs.gentoo.org/924772
Signed-off-by: Zac Medico <zmedico@gentoo.org>
---
lib/portage/dbapi/bintree.py | 66 ++++++++++++++++++++-
lib/portage/tests/sync/test_sync_local.py | 71 +++++++++++++++++++----
2 files changed, 123 insertions(+), 14 deletions(-)
diff --git a/lib/portage/dbapi/bintree.py b/lib/portage/dbapi/bintree.py
index 7bc1f60f6d..fbf60e74eb 100644
--- a/lib/portage/dbapi/bintree.py
+++ b/lib/portage/dbapi/bintree.py
@@ -48,6 +48,7 @@ from portage.exception import (
from portage.localization import _
from portage.output import colorize
from portage.package.ebuild.profile_iuse import iter_iuse_vars
+from portage.sync.revision_history import get_repo_revision_history
from portage.util import ensure_dirs
from portage.util.file_copy import copyfile
from portage.util.futures import asyncio
@@ -62,6 +63,7 @@ from portage import _unicode_encode
import codecs
import errno
import io
+import json
import re
import stat
import subprocess
@@ -134,13 +136,19 @@ class bindbapi(fakedbapi):
"USE",
"_mtime_",
}
+ # Keys required only when initially adding a package.
+ self._init_aux_keys = {
+ "REPO_REVISIONS",
+ }
self._aux_cache = {}
self._aux_cache_slot_dict_cache = None
@property
def _aux_cache_slot_dict(self):
if self._aux_cache_slot_dict_cache is None:
- self._aux_cache_slot_dict_cache = slot_dict_class(self._aux_cache_keys)
+ self._aux_cache_slot_dict_cache = slot_dict_class(
+ chain(self._aux_cache_keys, self._init_aux_keys)
+ )
return self._aux_cache_slot_dict_cache
def __getstate__(self):
@@ -1791,6 +1799,10 @@ class binarytree:
pkgindex = self._new_pkgindex()
d = self._inject_file(pkgindex, cpv, full_path)
+ repo_revisions = d.get("REPO_REVISIONS")
+ if repo_revisions:
+ repo_revisions = json.loads(repo_revisions)
+ self._inject_repo_revisions(pkgindex.header, repo_revisions)
self._update_pkgindex_header(pkgindex.header)
self._pkgindex_write(pkgindex)
@@ -1872,7 +1884,7 @@ class binarytree:
@return: package metadata
"""
if keys is None:
- keys = self.dbapi._aux_cache_keys
+ keys = chain(self.dbapi._aux_cache_keys, self.dbapi._init_aux_keys)
metadata = self.dbapi._aux_cache_slot_dict()
else:
metadata = {}
@@ -1916,6 +1928,56 @@ class binarytree:
return metadata
+ def _inject_repo_revisions(self, header, repo_revisions):
+ """
+ Inject REPO_REVISIONS from a package into the index header,
+ using a history of synced revisions to guarantee forward
+ progress. This queries the relevant repos to check if any
+ new revisions have appeared in the absence of a proper sync
+ operation.
+
+ This does not expose REPO_REVISIONS that do not appear in
+ the sync history, since such revisions suggest that the
+ package was not built locally, and in this case its
+ REPO_REVISIONS are not intended to be exposed.
+ """
+ synced_repo_revisions = get_repo_revision_history(
+ self.settings["EROOT"],
+ [self.settings.repositories[repo_name] for repo_name in repo_revisions],
+ )
+ header_repo_revisions = (
+ json.loads(header["REPO_REVISIONS"]) if header.get("REPO_REVISIONS") else {}
+ )
+ for repo_name, repo_revision in repo_revisions.items():
+ rev_list = synced_repo_revisions.get(repo_name, [])
+ header_rev = header_repo_revisions.get(repo_name)
+ if not rev_list or header_rev in (repo_revision, rev_list[0]):
+ continue
+ try:
+ header_rev_index = (
+ None if header_rev is None else rev_list.index(header_rev)
+ )
+ except ValueError:
+ header_rev_index = None
+ try:
+ repo_revision_index = rev_list.index(repo_revision)
+ except ValueError:
+ repo_revision_index = None
+ if repo_revision_index is not None and (
+ header_rev_index is None or repo_revision_index < header_rev_index
+ ):
+ # There is forward progress when repo_revision is more recent
+ # than header_rev or header_rev was not found in the history.
+ # Do not expose repo_revision here if it does not appear in
+ # the history, since this suggests that the package was not
+ # built locally and in this case its REPO_REVISIONS are not
+ # intended to be exposed here.
+ header_repo_revisions[repo_name] = repo_revision
+ if header_repo_revisions:
+ header["REPO_REVISIONS"] = json.dumps(
+ header_repo_revisions, ensure_ascii=False, sort_keys=True
+ )
+
def _inject_file(self, pkgindex, cpv, filename):
"""
Add a package to internal data structures, and add an
diff --git a/lib/portage/tests/sync/test_sync_local.py b/lib/portage/tests/sync/test_sync_local.py
index 91649398de..7e6158ee45 100644
--- a/lib/portage/tests/sync/test_sync_local.py
+++ b/lib/portage/tests/sync/test_sync_local.py
@@ -380,6 +380,45 @@ class SyncLocalTestCase(TestCase):
(homedir, lambda: self.assertTrue(bool(get_revision_history()))),
)
+ def assert_latest_rev_in_packages_index(positive):
+ """
+ If we build a binary package then its REPO_REVISIONS should
+ propagate into $PKGDIR/Packages as long as it results in
+ forward progress according to the repo revision history.
+ """
+ revision_history = get_revision_history()
+ prefix = "REPO_REVISIONS:"
+ header_repo_revisions = None
+ try:
+ with open(
+ os.path.join(settings["PKGDIR"], "Packages"), encoding="utf8"
+ ) as f:
+ for line in f:
+ if line.startswith(prefix):
+ header_repo_revisions = line[len(prefix) :].strip()
+ break
+ except FileNotFoundError:
+ pass
+
+ if positive:
+ self.assertFalse(header_repo_revisions is None)
+
+ if header_repo_revisions is None:
+ header_repo_revisions = {}
+ else:
+ header_repo_revisions = json.loads(header_repo_revisions)
+
+ (self.assertEqual if positive else self.assertNotEqual)(
+ revision_history.get(repo.name, [False])[0],
+ header_repo_revisions.get(repo.name, None),
+ )
+
+ pkgindex_revisions_cmds = (
+ (homedir, lambda: assert_latest_rev_in_packages_index(False)),
+ (homedir, cmds["emerge"] + ("-B", "dev-libs/A")),
+ (homedir, lambda: assert_latest_rev_in_packages_index(True)),
+ )
+
def hg_init_global_config():
with open(os.path.join(homedir, ".hgrc"), "w") as f:
f.write(f"[ui]\nusername = {committer_name} <{committer_email}>\n")
@@ -447,18 +486,25 @@ class SyncLocalTestCase(TestCase):
pythonpath = ":" + pythonpath
pythonpath = PORTAGE_PYM_PATH + pythonpath
- env = {
- "PORTAGE_OVERRIDE_EPREFIX": eprefix,
- "DISTDIR": distdir,
- "GENTOO_COMMITTER_NAME": committer_name,
- "GENTOO_COMMITTER_EMAIL": committer_email,
- "HOME": homedir,
- "PATH": settings["PATH"],
- "PORTAGE_GRPNAME": os.environ["PORTAGE_GRPNAME"],
- "PORTAGE_USERNAME": os.environ["PORTAGE_USERNAME"],
- "PYTHONDONTWRITEBYTECODE": os.environ.get("PYTHONDONTWRITEBYTECODE", ""),
- "PYTHONPATH": pythonpath,
- }
+ env = settings.environ()
+ env.update(
+ {
+ "PORTAGE_OVERRIDE_EPREFIX": eprefix,
+ "DISTDIR": distdir,
+ "GENTOO_COMMITTER_NAME": committer_name,
+ "GENTOO_COMMITTER_EMAIL": committer_email,
+ "HOME": homedir,
+ "PORTAGE_INST_GID": str(os.getgid()),
+ "PORTAGE_INST_UID": str(os.getuid()),
+ "PORTAGE_GRPNAME": os.environ["PORTAGE_GRPNAME"],
+ "PORTAGE_USERNAME": os.environ["PORTAGE_USERNAME"],
+ "PYTHONDONTWRITEBYTECODE": os.environ.get(
+ "PYTHONDONTWRITEBYTECODE", ""
+ ),
+ "PYTHONPATH": pythonpath,
+ }
+ )
+
repos_set_conf("rsync")
if os.environ.get("SANDBOX_ON") == "1":
@@ -518,6 +564,7 @@ class SyncLocalTestCase(TestCase):
+ upstream_git_commit
+ sync_cmds
+ repo_revisions_cmds
+ + pkgindex_revisions_cmds
+ mercurial_tests
):
if hasattr(cmd, "__call__"):
--
2.41.0
prev parent reply other threads:[~2024-03-14 4:44 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-03-14 4:42 [gentoo-portage-dev] [PATCH 0/2] Add REPO_REVISIONS to package index header Zac Medico
2024-03-14 4:42 ` [gentoo-portage-dev] [PATCH 1/2] Add get_repo_revision_history function and repo_revisions file Zac Medico
2024-03-14 4:42 ` Zac Medico [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240314044233.1003525-3-zmedico@gentoo.org \
--to=zmedico@gentoo.org \
--cc=gentoo-portage-dev@lists.gentoo.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox