From 71c246c7856eafe369d673aec4ecbf7568b50a9f Mon Sep 17 00:00:00 2001
From: Jan Tojnar <jtojnar@gmail.com>
Date: Sun, 20 Sep 2020 10:29:34 +0200
Subject: [PATCH] maintainers/scripts/update.nix: Run update scripts from the
 worktree

`update.nix` extracts `passthru.updateScript` attributes in the main repo
and when they are relative paths (e.g. `./update.sh`), Nix will resolve them
to absolute paths in the main repo.

Update scripts can use $(dirname $0) to get the location of files they
should update but that would point to the main repo.
We want them to modify the appropriate git worktree instead
so we replace the prefix accordingly.

`git rev-parse --show-toplevel` will resolve symlinks but, fortunately,
Nix will do that as well, so the path will match:

https://github.com/NixOS/nixpkgs/pull/98304#issuecomment-695761754
---
 maintainers/scripts/update.py | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/maintainers/scripts/update.py b/maintainers/scripts/update.py
index 74e36eaf80c6..8cc2bcbd67c9 100644
--- a/maintainers/scripts/update.py
+++ b/maintainers/scripts/update.py
@@ -5,6 +5,7 @@ import asyncio
 import contextlib
 import json
 import os
+import re
 import subprocess
 import sys
 import tempfile
@@ -30,16 +31,22 @@ async def check_subprocess(*args, **kwargs):
 
     return process
 
-async def run_update_script(merge_lock: asyncio.Lock, temp_dir: Optional[Tuple[str, str]], package: Dict, keep_going: bool):
+async def run_update_script(nixpkgs_root: str, merge_lock: asyncio.Lock, temp_dir: Optional[Tuple[str, str]], package: Dict, keep_going: bool):
     worktree: Optional[str] = None
 
+    update_script_command = package['updateScript']
+
     if temp_dir is not None:
         worktree, _branch = temp_dir
 
+        # Update scripts can use $(dirname $0) to get their location but we want to run
+        # their clones in the git worktree, not in the main nixpkgs repo.
+        update_script_command = map(lambda arg: re.sub(r'^{0}'.format(re.escape(nixpkgs_root)), worktree, arg), update_script_command)
+
     eprint(f" - {package['name']}: UPDATING ...")
 
     try:
-        update_process = await check_subprocess('env', f"UPDATE_NIX_ATTR_PATH={package['attrPath']}", *package['updateScript'], stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=worktree)
+        update_process = await check_subprocess('env', f"UPDATE_NIX_ATTR_PATH={package['attrPath']}", *update_script_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=worktree)
         update_info = await update_process.stdout.read()
 
         await merge_changes(merge_lock, package, update_info, temp_dir)
@@ -125,7 +132,7 @@ async def merge_changes(merge_lock: asyncio.Lock, package: Dict, update_info: st
     else:
         eprint(f" - {package['name']}: DONE.")
 
-async def updater(temp_dir: Optional[Tuple[str, str]], merge_lock: asyncio.Lock, packages_to_update: asyncio.Queue[Optional[Dict]], keep_going: bool, commit: bool):
+async def updater(nixpkgs_root: str, temp_dir: Optional[Tuple[str, str]], merge_lock: asyncio.Lock, packages_to_update: asyncio.Queue[Optional[Dict]], keep_going: bool, commit: bool):
     while True:
         package = await packages_to_update.get()
         if package is None:
@@ -135,7 +142,7 @@ async def updater(temp_dir: Optional[Tuple[str, str]], merge_lock: asyncio.Lock,
         if not ('commit' in package['supportedFeatures'] or 'attrPath' in package):
             temp_dir = None
 
-        await run_update_script(merge_lock, temp_dir, package, keep_going)
+        await run_update_script(nixpkgs_root, merge_lock, temp_dir, package, keep_going)
 
 async def start_updates(max_workers: int, keep_going: bool, commit: bool, packages: List[Dict]):
     merge_lock = asyncio.Lock()
@@ -147,6 +154,9 @@ async def start_updates(max_workers: int, keep_going: bool, commit: bool, packag
         # Do not create more workers than there are packages.
         num_workers = min(max_workers, len(packages))
 
+        nixpkgs_root_process = await check_subprocess('git', 'rev-parse', '--show-toplevel', stdout=asyncio.subprocess.PIPE)
+        nixpkgs_root = (await nixpkgs_root_process.stdout.read()).decode('utf-8').strip()
+
         # Set up temporary directories when using auto-commit.
         for i in range(num_workers):
             temp_dir = stack.enter_context(make_worktree()) if commit else None
@@ -163,7 +173,7 @@ async def start_updates(max_workers: int, keep_going: bool, commit: bool, packag
 
         # Prepare updater workers for each temp_dir directory.
         # At most `num_workers` instances of `run_update_script` will be running at one time.
-        updaters = asyncio.gather(*[updater(temp_dir, merge_lock, packages_to_update, keep_going, commit) for temp_dir in temp_dirs])
+        updaters = asyncio.gather(*[updater(nixpkgs_root, temp_dir, merge_lock, packages_to_update, keep_going, commit) for temp_dir in temp_dirs])
 
         try:
             # Start updater workers.