mirror of
https://github.com/feigeCode/navop.git
synced 2026-09-21 08:01:22 +00:00
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.
328 lines
13 KiB
Python
Executable File
328 lines
13 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Point Navop at a local `gpui-pre` snapshot instead of the crates.io one.
|
|
|
|
`gpui-pre-*` is a crates.io republication of Zed's gpui crates. gpui-component
|
|
cuts a fresh snapshot every week with its own `script/bump-gpui.ts`, which
|
|
copies the crates out of a Zed checkout, renames them (`gpui` -> `gpui-pre`,
|
|
`util` -> `gpui-pre-util`, ...) and rewrites them into a standalone workspace.
|
|
|
|
Navop needs the dynamic-texture API (`DynamicTexture`,
|
|
`Window::update_dynamic_texture`, ...) which only exists on Zed's
|
|
`dynamic-texture` branch, so this script runs that same pipeline against a
|
|
local Zed checkout and then rewrites the generated `[patch.crates-io]` block in
|
|
Navop's root `Cargo.toml` to point at the staged crates.
|
|
|
|
Why a path patch rather than a git patch: the staged snapshots carry the
|
|
`gpui-pre-*` package names and a version that satisfies Navop's `^0.3.1`
|
|
requirement, so `[patch.crates-io]` accepts them. A Zed checkout on its own
|
|
declares `gpui`/`util`/... at Zed's own versions and would never match.
|
|
|
|
The staged workspace has to live outside every Cargo workspace. Cargo resolves
|
|
`workspace = true` against the nearest ancestor manifest that owns the package
|
|
as a member, and a snapshot left under `gpui-component/target/` gets attributed
|
|
to gpui-component's workspace, which has no `accesskit`/`gpui_pre_*` entries to
|
|
inherit from. The pipeline's own verification hits the same problem and works
|
|
around it the same way: copy the staging somewhere neutral before pointing a
|
|
manifest at it. `<gpui-component>/../.gpui-pre/workspace` is that place — it sits
|
|
next to the two checkouts, inside no workspace and no repository.
|
|
|
|
Usage:
|
|
script/patch-local-gpui-pre.py [options]
|
|
|
|
Options:
|
|
--zed PATH Zed checkout to snapshot (default: the `zed` checkout
|
|
next to the Navop and gpui-component checkouts)
|
|
--component PATH gpui-component checkout that owns the staging pipeline
|
|
(default: the `gpui-component` checkout next to Navop)
|
|
--patch-dir PATH Where to put the copy the patch points at (default:
|
|
`<gpui-component>/../.gpui-pre`)
|
|
--version VERSION Version to stamp on the staged crates; must satisfy the
|
|
`gpui-pre` requirement in Cargo.toml (default: 0.3.99)
|
|
--no-stage Reuse the workspace already staged, only refresh the
|
|
copy and the patch block
|
|
--update-lock Run `cargo update` for the patched crates so Cargo.lock
|
|
moves onto the snapshot (without it, Cargo keeps the
|
|
published version and ignores the patch)
|
|
--remove Delete the patch block and stop
|
|
-h, --help Show this help
|
|
|
|
Run it again whenever the local Zed branch changes: the patch paths stay the
|
|
same, only the staged sources are rebuilt.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
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 <<<"
|
|
|
|
DEFAULT_VERSION = "0.3.99"
|
|
PUBLISH_PREFIX = "gpui-pre"
|
|
|
|
|
|
class Error(Exception):
|
|
"""A user-facing failure, printed without a traceback."""
|
|
|
|
|
|
def run(cmd: list[str], cwd: Path | None = None) -> None:
|
|
print(f"$ {' '.join(cmd)}")
|
|
result = subprocess.run(cmd, cwd=cwd)
|
|
if result.returncode != 0:
|
|
raise Error(f"command failed with exit code {result.returncode}: {' '.join(cmd)}")
|
|
|
|
|
|
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)}")
|
|
return result.stdout
|
|
|
|
|
|
def sibling(root: Path, name: str, marker: str, flag: str) -> Path:
|
|
"""Find a checkout next to the Navop root, at any of the nesting depths.
|
|
|
|
Navop is normally checked out as `<workspace>/navop`, but the gpui-kit
|
|
migration lives in `<workspace>/navop/.worktrees/gpui-kit`, one level
|
|
deeper, so the sibling is two, three or four levels up.
|
|
"""
|
|
for depth in range(1, 5):
|
|
candidate = root
|
|
for _ in range(depth):
|
|
candidate = candidate.parent
|
|
candidate = candidate / name
|
|
if (candidate / marker).exists():
|
|
return candidate.resolve()
|
|
raise Error(
|
|
f"could not find a {name} checkout next to {root}; pass {flag} PATH"
|
|
)
|
|
|
|
|
|
def find_bun() -> str:
|
|
"""Locate the Bun runtime that runs the staging pipeline."""
|
|
found = shutil.which("bun")
|
|
if found:
|
|
return found
|
|
# A `npm install bun` under $HOME is a common fallback; it installs a shim
|
|
# at ~/node_modules/.bin/bun.
|
|
fallback = Path.home() / "node_modules" / ".bin" / "bun"
|
|
if fallback.is_file():
|
|
return str(fallback)
|
|
raise Error(
|
|
"bun is not installed; install it with `npm install -g bun` (or "
|
|
"`brew install oven-sh/bun/bun`) and re-run"
|
|
)
|
|
|
|
|
|
def stage(component: Path, zed: Path, version: str) -> Path:
|
|
"""Run gpui-component's snapshot pipeline into its `target/gpui-pre`."""
|
|
script = component / "script" / "bump-gpui.ts"
|
|
if not script.is_file():
|
|
raise Error(f"{script} not found; is {component} a gpui-component checkout?")
|
|
if not (zed / "Cargo.toml").is_file():
|
|
raise Error(f"{zed} does not look like a Zed checkout (no Cargo.toml)")
|
|
run(
|
|
[
|
|
find_bun(),
|
|
str(script),
|
|
version,
|
|
"--zed",
|
|
str(zed),
|
|
"--stage-only",
|
|
],
|
|
cwd=component,
|
|
)
|
|
workspace = component / "target" / "gpui-pre" / "workspace"
|
|
if not (workspace / "Cargo.toml").is_file():
|
|
raise Error(f"the pipeline did not write {workspace}")
|
|
return workspace
|
|
|
|
|
|
def mirror(staged: Path, patch_dir: Path) -> Path:
|
|
"""Copy the staged workspace somewhere that belongs to no workspace.
|
|
|
|
The staging under `gpui-component/target/` cannot be patched directly: Cargo
|
|
would resolve its `workspace = true` keys against gpui-component's
|
|
workspace, which knows nothing about `accesskit` or the `gpui-pre-*`
|
|
packages themselves. A copy outside every checkout resolves against the
|
|
staged root, as intended. `target/` is left behind — it is build output,
|
|
not part of the snapshot.
|
|
"""
|
|
destination = patch_dir / "workspace"
|
|
if destination.exists():
|
|
shutil.rmtree(destination)
|
|
shutil.copytree(staged, destination, ignore=shutil.ignore_patterns("target"))
|
|
return destination
|
|
|
|
|
|
def staged_crates(workspace: Path) -> list[tuple[str, Path]]:
|
|
"""Read the staged workspace's dependency table for published name -> dir.
|
|
|
|
The pipeline lists every republished crate as
|
|
`<alias> = { path = "...", package = "gpui-pre-...", version = "=..." }`,
|
|
which is exactly the mapping the patch needs.
|
|
"""
|
|
manifest = (workspace / "Cargo.toml").read_text()
|
|
section = re.search(
|
|
r"^\[workspace\.dependencies\]\s*$(.*?)(?=^\[|\Z)",
|
|
manifest,
|
|
re.MULTILINE | re.DOTALL,
|
|
)
|
|
if section is None:
|
|
raise Error(f"{workspace / 'Cargo.toml'} has no [workspace.dependencies]")
|
|
crates: list[tuple[str, Path]] = []
|
|
for line in section.group(1).splitlines():
|
|
if re.match(r"^\s*[\w-]+\s*=\s*\{", line) is None:
|
|
continue
|
|
package = re.search(r'\bpackage\s*=\s*"([^"]+)"', line)
|
|
path = re.search(r'\bpath\s*=\s*"([^"]+)"', line)
|
|
if package is None or path is None:
|
|
continue
|
|
name = package.group(1)
|
|
if not name.startswith(PUBLISH_PREFIX):
|
|
continue
|
|
crates.append((name, (workspace / path.group(1)).resolve()))
|
|
if not crates:
|
|
raise Error(f"no {PUBLISH_PREFIX}-* crates found in {workspace / 'Cargo.toml'}")
|
|
return sorted(set(crates))
|
|
|
|
|
|
def patch_block(manifest_dir: Path, crates: list[tuple[str, Path]]) -> str:
|
|
width = max(len(name) for name, _ in crates)
|
|
lines = [
|
|
BEGIN_MARKER,
|
|
"#",
|
|
"# LOCAL ONLY: the published gpui-pre-* snapshot predates Zed's",
|
|
"# dynamic-texture API. This redirects every gpui-pre-* crate at the local",
|
|
"# Zed checkout that has it. Re-run script/patch-local-gpui-pre.py after",
|
|
"# changing that branch; `--remove` deletes the block.",
|
|
"[patch.crates-io]",
|
|
]
|
|
lines += [
|
|
f'{name.ljust(width)} = {{ path = "{os.path.relpath(path, manifest_dir)}" }}'
|
|
for name, path in crates
|
|
]
|
|
lines.append(END_MARKER)
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def rewrite_manifest(root: Path, block: str | None) -> None:
|
|
manifest = root / "Cargo.toml"
|
|
text = manifest.read_text()
|
|
pattern = re.compile(
|
|
rf"^{re.escape(BEGIN_MARKER)}$.*?^{re.escape(END_MARKER)}\n?",
|
|
re.MULTILINE | re.DOTALL,
|
|
)
|
|
if block is None:
|
|
if pattern.search(text) is None:
|
|
print(f"{manifest} has no local gpui-pre patch; nothing to remove")
|
|
return
|
|
manifest.write_text(pattern.sub("", text).rstrip() + "\n")
|
|
print(f"removed the local gpui-pre patch from {manifest}")
|
|
return
|
|
if pattern.search(text) is not None:
|
|
manifest.write_text(pattern.sub(block, text))
|
|
else:
|
|
manifest.write_text(text.rstrip() + "\n\n" + block)
|
|
print(f"wrote {len(block.splitlines()) - 6} patch entries to {manifest}")
|
|
|
|
|
|
def graph_crates(
|
|
root: Path, crates: list[tuple[str, Path]]
|
|
) -> tuple[list[str], list[str]]:
|
|
"""Split the patched crates into those Navop resolves and those it does not.
|
|
|
|
`cargo update -p` fails on a package that is not in the graph, and Cargo
|
|
reports a patch that resolved to nothing as unused, so both lists are worth
|
|
knowing. `cargo metadata` is the only authoritative answer: Cargo.lock keeps
|
|
stale entries for packages the graph has dropped.
|
|
"""
|
|
metadata = json.loads(
|
|
run_capture(["cargo", "metadata", "--format-version", "1"], cwd=root)
|
|
)
|
|
resolved = {package["name"] for package in metadata["packages"]}
|
|
used = [name for name, _ in crates if name in resolved]
|
|
unused = [name for name, _ in crates if name not in resolved]
|
|
return used, unused
|
|
|
|
|
|
def main(argv: list[str]) -> int:
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
)
|
|
parser.add_argument("--zed")
|
|
parser.add_argument("--component")
|
|
parser.add_argument("--patch-dir")
|
|
parser.add_argument("--version", default=DEFAULT_VERSION)
|
|
parser.add_argument("--no-stage", action="store_true")
|
|
parser.add_argument("--update-lock", action="store_true")
|
|
parser.add_argument("--remove", action="store_true")
|
|
args = parser.parse_args(argv)
|
|
|
|
root = Path(__file__).resolve().parent.parent
|
|
if args.remove:
|
|
rewrite_manifest(root, None)
|
|
return 0
|
|
|
|
component = (
|
|
Path(args.component).expanduser().resolve()
|
|
if args.component
|
|
else sibling(root, "gpui-component", "script/bump-gpui.ts", "--component")
|
|
)
|
|
zed = (
|
|
Path(args.zed).expanduser().resolve()
|
|
if args.zed
|
|
else sibling(root, "zed", "crates/gpui/Cargo.toml", "--zed")
|
|
)
|
|
patch_dir = (
|
|
Path(args.patch_dir).expanduser().resolve()
|
|
if args.patch_dir
|
|
else component.parent / ".gpui-pre"
|
|
)
|
|
|
|
staged = component / "target" / "gpui-pre" / "workspace"
|
|
if not args.no_stage:
|
|
staged = stage(component, zed, args.version)
|
|
elif not (staged / "Cargo.toml").is_file():
|
|
raise Error(f"--no-stage was given but {staged} is empty")
|
|
|
|
workspace = mirror(staged, patch_dir)
|
|
crates = staged_crates(workspace)
|
|
rewrite_manifest(root, patch_block(root, crates))
|
|
print(f"snapshot from {zed}")
|
|
print(f"staged workspace: {staged}")
|
|
print(f"patched workspace: {workspace}")
|
|
print(f"{len(crates)} crates patched: {', '.join(name for name, _ in crates)}")
|
|
|
|
# Cargo keeps a locked version when it still satisfies the requirement, so
|
|
# a patch alone does not move Cargo.lock onto the snapshot.
|
|
used, unused = graph_crates(root, crates)
|
|
if unused:
|
|
print(
|
|
"not reached by Navop's graph, so Cargo will report them as unused "
|
|
f"patches: {', '.join(unused)}"
|
|
)
|
|
if args.update_lock:
|
|
packages = [arg for name in used for arg in ("-p", name)]
|
|
run(["cargo", "update", *packages], cwd=root)
|
|
else:
|
|
print("run this to move Cargo.lock onto the snapshot, then rebuild:")
|
|
print(" cargo update " + " ".join(f"-p {name}" for name in used))
|
|
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)
|