public inbox for gentoo-portage-dev@lists.gentoo.org
 help / color / mirror / Atom feed
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



      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