Sync chromium-luci revision when rolling

Updates scripts/roll_chromium_deps.py to also sync the chromium-luci
revision in //infra/config/global/PACKAGE.star. This helps prevent
the shared Starlark code from getting out of date.

Also drive-by fixes a few minor issues that were found elsewhere in the
script while working on it.

Bug: 452840620
Change-Id: Ida3261ad31cd501b889d4ba6760c71684ad03ba7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/284480
Commit-Queue: Brian Sheedy <bsheedy@google.com>
Reviewed-by: Jonathan Lee <jonathanjlee@google.com>
Auto-Submit: Brian Sheedy <bsheedy@google.com>
diff --git a/scripts/roll_chromium_deps.py b/scripts/roll_chromium_deps.py
index 3e510ca..283cb25 100755
--- a/scripts/roll_chromium_deps.py
+++ b/scripts/roll_chromium_deps.py
@@ -25,7 +25,11 @@
 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""Rolls DEPS entries shared with Chromium."""
+"""Rolls dependencies shared with Chromium.
+
+This is mostly DEPS entries, but some additional dependencies elsewhere in the
+repo are also synced.
+"""
 
 import abc
 import argparse
@@ -35,6 +39,7 @@
 import logging
 import pathlib
 import posixpath
+import re
 import shlex
 import subprocess
 import sys
@@ -567,7 +572,7 @@
             raise RuntimeError(
                 f'Unable to find Dawn CIPD entry {dawn_name}. Was it removed?')
         if chromium_name not in chromium_deps['deps']:
-            raise RuntimeErrror(
+            raise RuntimeError(
                 f'Unable to find Chromium CIPD entry {chromium_name}. Was it '
                 f'removed?')
 
@@ -820,7 +825,7 @@
         A list of strings containing lines to append to the commit message.
     """
     command_lines = [
-        'DEPS and submodule changes generated by running:',
+        'DEPS, submodule, and //infra/config changes generated by running:',
     ]
     script = pathlib.Path(__file__).resolve()
     script = script.relative_to(DAWN_ROOT)
@@ -828,7 +833,7 @@
     command_lines.append(f'  {shlex.join(relative_command)}')
     command_lines.extend([
         'Test specs were then regenerated by syncing and running:',
-        f'  infra/specs/generate_test_spec_json.py',
+        '  infra/specs/generate_test_spec_json.py',
     ])
     return command_lines
 
@@ -877,7 +882,7 @@
     """
     return _generate_section_for_entry_type(
         ChangedVariable, changed_entries,
-        'No explicitly synced GN variables in this roll',
+        'No explicitly synced GN variables changed in this roll',
         'Explicitly synced GN variables:')
 
 
@@ -956,6 +961,62 @@
     subprocess.run(cmd, check=True)
 
 
+def _sync_chromium_luci_revision(revision: str) -> None:
+    """Syncs the chromium-luci revision used by //infra/config.
+
+    Args:
+        revision: The Chromium revision to read the chromium-luci revision at.
+    """
+    chromium_package_contents = _read_remote_chromium_file(
+        'infra/config/PACKAGE.star', revision)
+    # Regex breakdown:
+    # 1. (name\s*=\s*"@chromium-luci".*?revision\s*=\s*")
+    #   Group 1: Context prefix to ensure that we're matching the chromium-luci
+    #   entry.
+    # 2. ([a-fA-F0-9]+)
+    #   Group 2: The hexadecimal revision to extract/replace.
+    # 3. (?=")
+    #   Positive lookahead: Asserts that a " follows, but does not match/consume
+    #   it.
+    revision_pattern = re.compile(
+        r'(name\s*=\s*"@chromium-luci".*?revision\s*=\s*")'
+        r'([a-fA-F0-9]+)'
+        r'(?=")', re.DOTALL)
+    match = revision_pattern.search(chromium_package_contents)
+    if not match:
+        raise RuntimeError(
+            "Unable to extract chromium-luci revision from Chromium's "
+            '//infra/config/PACKAGE.star')
+    chromium_luci_revision = match.group(2)
+
+    infra_path = DAWN_ROOT / 'infra' / 'config' / 'global'
+    dawn_package = infra_path / 'PACKAGE.star'
+    with open(dawn_package, encoding='utf-8') as infile:
+        dawn_package_contents = infile.read()
+
+    # Replace the match with Group 1 (matched content before the revision) and
+    # the new revision itself.
+    dawn_package_contents = revision_pattern.sub(
+        rf'\g<1>{chromium_luci_revision}', dawn_package_contents)
+    with open(dawn_package, 'w', encoding='utf-8') as outfile:
+        outfile.write(dawn_package_contents)
+
+    # We are able to generate files now unlike with
+    # infra/specs/generate_test_spec_json.py because this does not require a
+    # `gclient sync` to pull in updated dependencies.
+    subprocess.check_call(
+        ['lucicfg', 'generate',
+         str(infra_path / 'main.star')],
+        stdout=subprocess.DEVNULL,
+        stderr=subprocess.DEVNULL)
+
+    # Automatically add any changed files to be consistent with DEPS
+    # modifications.
+    subprocess.check_call(['git', 'add', str(infra_path)],
+                          stdout=subprocess.DEVNULL,
+                          stderr=subprocess.DEVNULL)
+
+
 def _parse_args() -> argparse.Namespace:
     """Parses and returns command line arguments."""
     parser = argparse.ArgumentParser('Roll DEPS entries shared with Chromium.')
@@ -1022,12 +1083,13 @@
         _create_roll_branch()
 
     _apply_changed_deps(changed_entries)
+    _sync_chromium_luci_revision(revision_range.new_revision)
 
     if args.autoroll:
         _amend_commit(commit_message)
     else:
         if _is_tree_clean():
-            logging.info('No DEPS changes detected, skipping commit')
+            logging.info('No changes detected, skipping commit')
         else:
             _commit(commit_message)
             logging.info('Changes committed locally and are ready for upload')