Files
navop/script/migrate-to-git-fork.py
user.email 6a1e7f6dd0 feat(remote_desktop_view): restore dynamic texture via a self-maintained gpui-pre fork
The dynamic-texture API only exists on our zed dynamic-texture branch, so
the published gpui-pre-* snapshot predates it. Instead of a path patch
that ties the build to one host, point the [patch.crates-io] block at a
git fork we own:

    feigeCode/gpui-pre, tag fork-0.3.99

The fork is cut from zed@cae01216cb with gpui-component's
script/bump-gpui.ts pipeline. Re-publish with
script/publish-gpui-pre-fork.py and move the patch with
script/migrate-to-git-fork.py; both live in script/.

With the API available again, revert the eda5371ae RenderImage fallback
in remote_desktop_view:

- surface.rs uses DynamicTexture directly; the backing framebuffer
  becomes the upload source for paint_dynamic_texture.
- render.rs paints dirty rects via update_dynamic_texture /
  paint_dynamic_texture instead of a whole-frame paint_image, and
  releases retired handles with drop_dynamic_texture.
- view.rs drops the placeholder handles; the contract tests assert the
  dynamic-texture path.

cargo check --workspace --locked: clean. cargo test -p
remote_desktop_view --lib: 241 passed.
2026-09-10 20:17:50 +08:00

230 lines
8.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Rewrite the local `gpui-pre` patch block from path to git dependencies.
`script/patch-local-gpui-pre.py` adds a `[patch.crates-io]` block to Navop's
root `Cargo.toml` so the upstream `gpui-pre-*` crates resolve to a directory
on the build host. That works for the dev who runs both scripts, but it ties
every other build host (CI, teammates, deploy machines) to the same
directory layout. The fix is to keep the patch block but switch each
`path = "..."` entry to `git = "..." + tag = "..."`, pointing at the fork
that `script/publish-gpui-pre-fork.py` populates.
The patch block stays because `gpui-pre` reaches Navop only transitively
through `gpui-component`; adding `gpui-pre` to Navop's own `[dependencies]`
would be unused and rejected. Patching crates.io from path or from git is
the only mechanism that lets a transitive dep be re-pointed without
touching the upstream crate's manifest.
This script only edits the existing block produced by the path-patch
script. Both scripts share the same `>>> local gpui-pre patch >>>` /
`<<< local gpui-pre patch <<<` markers, so they can be interleaved freely:
script/patch-local-gpui-pre.py # path patch (dev workstation)
script/migrate-to-git-fork.py --tag X # switch to git (CI, ship)
script/migrate-to-git-fork.py --path # switch back to path (dev)
Usage:
script/migrate-to-git-fork.py [options]
Options:
--fork-url URL Git URL of the fork. Required for `--git` (default).
--tag TAG The tag the fork exposes for the snapshot. Required
for `--git` (default).
--path Rewrite the block back to a local path patch. This
is the inverse of `--git`; the path defaults to the
`<component>/../.gpui-pre/workspace` layout produced
by the path-patch script.
--no-update-lock Print the change but do not run `cargo update`. The
lock will keep the old path-based entries until you
run it.
--dry-run Print what would be written, do not touch files.
-h, --help Show this help
The script refuses to write a git-fork block without a non-empty
`--fork-url` and `--tag`, and refuses to write a path block when the path
mirror does not exist. Both refusals are deliberate: a half-written patch
is worse than a working path patch, and "the path is wrong" is a runtime
error that only shows up on `cargo check` two minutes later.
"""
from __future__ import annotations
import argparse
import json
import re
import subprocess
import sys
from pathlib import Path
BEGIN_MARKER = "# >>> local gpui-pre patch (generated by script/patch-local-gpui-pre.py) >>>"
END_MARKER = "# <<< local gpui-pre patch <<<"
PUBLISH_PREFIX = "gpui-pre"
class Error(Exception):
"""A user-facing failure, printed without a traceback."""
def run_capture(cmd: list[str], cwd: Path | None = None) -> str:
result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True)
if result.returncode != 0:
raise Error(
f"command failed with exit code {result.returncode}: {' '.join(cmd)}\n"
f"{result.stderr}"
)
return result.stdout
def read_block(manifest: Path) -> tuple[str, int, int, str]:
"""Return (text, start_offset, end_offset, prefix_before_block)."""
text = manifest.read_text()
pattern = re.compile(
rf"^{re.escape(BEGIN_MARKER)}$.*?^{re.escape(END_MARKER)}\n?",
re.MULTILINE | re.DOTALL,
)
match = pattern.search(text)
if match is None:
raise Error(f"{manifest} has no local gpui-pre patch; run patch-local-gpui-pre.py first")
return match.group(0), match.start(), match.end(), text[: match.start()]
def parse_block_entries(block: str) -> list[str]:
"""Extract the patched crate names from an existing patch block.
Accepts both the path form (`{ path = "..." }`, written by the
path-patch script) and the git form (`{ git = "...", tag = "..." }`,
written by this script) so the two modes can be toggled repeatedly.
Only the crate names matter: for `--git` the path values are replaced
outright, and for `--path` the authoritative paths are recomputed by
`patch-local-gpui-pre.py`.
"""
names: list[str] = []
for line in block.splitlines():
entry = re.match(r"^\s*([\w-]+)\s*=\s*\{(.*)\}\s*$", line)
if entry is None:
continue
name, payload = entry.group(1), entry.group(2)
if not name.startswith(PUBLISH_PREFIX):
continue
if re.search(r'\b(path|git)\s*=\s*"', payload) is None:
continue
names.append(name)
if not names:
raise Error(
f"the patch block in this manifest has no {PUBLISH_PREFIX}-* entries"
)
return names
def build_git_block(names: list[str], fork_url: str, tag: str) -> str:
width = max(len(name) for name in names)
lines = [
BEGIN_MARKER,
"#",
"# SELF-MAINTAINED: gpui-pre-* is a git fork of the snapshot produced",
"# by script/patch-local-gpui-pre.py. The fork is owned by us, the tag",
"# below is bumped by script/publish-gpui-pre-fork.py. To return to a",
"# path patch (dev workstation only), re-run",
"# `script/migrate-to-git-fork.py --path`.",
"[patch.crates-io]",
]
for name in names:
lines.append(
f'{name.ljust(width)} = {{ git = "{fork_url}", tag = "{tag}" }}'
)
lines.append(END_MARKER)
return "\n".join(lines) + "\n"
def graph_crates(manifest: Path) -> list[str]:
"""Crates that Navop's graph resolves; only those can be `cargo update -p`."""
metadata = json.loads(
run_capture(["cargo", "metadata", "--format-version", "1"], cwd=manifest.parent)
)
return sorted(
{
package["name"]
for package in metadata["packages"]
if package["name"].startswith(PUBLISH_PREFIX)
}
)
def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("--fork-url")
parser.add_argument("--tag")
parser.add_argument("--path", action="store_true")
parser.add_argument("--no-update-lock", action="store_true")
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args(argv)
root = Path(__file__).resolve().parent.parent
manifest = root / "Cargo.toml"
if not manifest.is_file():
raise Error(f"{manifest} not found")
block, start, end, prefix = read_block(manifest)
names = parse_block_entries(block)
if args.path:
# The path-patch script is the authoritative writer of the path
# block: it recomputes the relative paths from its own mirror, so
# delegate instead of trying to reconstruct them here (a git block
# does not carry the path values at all). It leaves Cargo.lock
# alone unless told to update, which matches --no-update-lock.
patcher = root / "script" / "patch-local-gpui-pre.py"
if not args.dry_run:
run(
[
sys.executable,
str(patcher),
"--no-stage",
],
cwd=root,
)
else:
print(f"# would run {patcher} --no-stage")
print(f"# ({len(names)} crates currently in the git block)")
return 0
if not args.fork_url:
raise Error("--fork-url is required (or pass --path to revert)")
if not args.tag:
raise Error("--tag is required (or pass --path to revert)")
new_block = build_git_block(names, args.fork_url, args.tag)
kind = "git"
if args.dry_run:
print(f"# would replace {end - start} bytes in {manifest}:")
print("--- new block ---")
print(new_block)
return 0
manifest.write_text(prefix + new_block + manifest.read_text()[end:])
print(f"wrote {kind}-patch block to {manifest} ({len(names)} entries: {', '.join(names)})")
if args.no_update_lock:
print("--no-update-lock: Cargo.lock still resolves to the old source; run")
print(" cargo update " + " ".join(f"-p {n}" for n in graph_crates(manifest)))
return 0
reachable = graph_crates(manifest)
if not reachable:
print("no gpui-pre-* packages reached by Navop's graph; nothing to update")
return 0
packages = [arg for n in reachable for arg in ("-p", n)]
subprocess.run(["cargo", "update", *packages], cwd=root)
return 0
if __name__ == "__main__":
try:
sys.exit(main(sys.argv[1:]))
except Error as error:
print(f"error: {error}", file=sys.stderr)
sys.exit(1)