The Makepad Builder (tools/makepad_builder: the Windows download is makepad-builder.zip with the Builder's source; macOS and Linux run makepad.sh), the CI runner (tools/ci), cargo-makepad, the agents tool, the root workspace and the documents. Covers work 6f1e44649..2b1a41df4 outside platform, draw, widgets, libs, apps and examples: - wm: the Android super-app compiles tiles on the phone against the host engine - cargo-makepad: the super-app APK is packed by `android dyn-pack`, in Rust - widgets, storybook: the widget catalogue app, some eighty new widgets, a theme store with mixable style sheets, and the rule that a press belongs to - wm, widgets: the style tween starts with all eight weights, and the new style sheet takes the last number instead of macOS's - platform: the Android desk swipes at 120 Hz — offscreen render passes and framebuffers cached, SVG meshes kept, font families that complete - cef: a page's audio can be captured instead of played - Button: activate from the keyboard when focused (#1241) - libs/ai: exclude hub_ui and services from the AI workspace (#1242) - flow: the flow-ui app and the flowgraph canvas return to the workspace, with staged progress in libs/flow - svg: a stroke that bends tighter than its half width collapses its inner edge to the corner - the accent a name is written with stops deciding whether search finds it - macOS: keep the paint clock beating while the window is minimized (#1244) - Linux: only link libdrm for the direct backend (#1245) - cargo-makepad: find dependency crate dirs via cargo metadata (#1246) - app_main!: only ship the fonts an app declares (#1247) - ai stems: LaneDemixer, VocalsModel and the platform Stage origin/main needs - platform, widgets, wm: every Vulkan pass renders through a negative-height viewport again, so a Wayland or X11 window stands upright, and no capture - calendar: its own colour contrast is named explicitly, now that the theme store exports one under the same name through the widgets glob - widgets: the supersampled resolve and the map's shadow mask sample their textures as stored, like every other render texture - platform: every OpenGL texture pass stores top-left rows, custom cameras included, and culls with the winding its inverted projection gives - platform, wm, director: a hosted app's software frame arrives as a top-left texture and the window manager and the director carry no flip term at al - widgets, platform: the last twelve warnings go - The Builder compiles itself again from the Makepad tree Scope's release pins and removes the source snapshots nothing is built from any more - The regex engine accepts \b and \B, reports the earliest match so a scan can resume after it, and can decide word boundaries on ASCII so a code sear - workspace: Scope's settings carry the code map's tab width and the directories each prepared project hides from its map - AGENTS.md: a platform change made for one application needs the user's feedback and checks first, and app specifics never leak into the shared layer - wm: the desktop style menu lists each style once - cef, video, platform: what Stage's browser needs from the shared layers - macOS: re-arm the display links when a window leaves the Dock (#1251) - platform, wm: work builds again on iOS, tvOS and wasm, and the window manager is warning-free on Windows - ci: tools/ci, a Splash runtime that watches a branch and runs every ci.splash it finds - stitch: the crate says what the rest of the repository says about its license - ci: a run is one target dir and one cargo batch per target, and the wall draws - ci: the wall shows THIS run and is fit for an OLED - ci: untested is grey and nothing else - ci: warming fills the cache and blames nobody - Vulkan: stop printing the loader's startup narration by default (#1252) - ci: the terminal gets its pty helper, director is a desktop tool, and a refused input is asked again - platform: no warnings on tvOS and linux_direct, and a missing pty helper says so - apps: what the CI box found - ScrollBar: add `show_handle` for a view that scrolls without a grabbable bar (#1254) - cargo-makepad: keep the android SDK at a stable path (#1253) - ci: the window is a dashboard - Remove Flow, asset and VJ applications moved into Stage - ci: a stop or a restart is not a test result - ci: a quiet header, a build that visibly moves, and scripts that leave the machine alone - widgets: children a lookup discovers reach the dump, the snapshot and the flood searches - tests: the workspace suite, run as a whole for the first time, passes outside the example UI tests - platform: a hidden window wears no hands-off frame - examples: the UI tests pass, for the reasons they failed - ci: a library's warnings turn the workspace block yellow, and a desktop tool is not warmed for the web - libs: no warnings in the workspace check on any row - ci: the workspace script gives the wall back after warming - ci: a judgement is never lost to the way it was phrased, and every "retry" of the bridge is retried - tests: the last three failures of the CI box's night - Linux: fix window chrome button hovers and how maximized/fullscreen windows work (#1255) - ci: the header names the tip being tested - tools: makepad-screen is makepad-agents, and its binary is `agents` - gif: the crate's doc examples compile - ci: the tests are the platform's own, in release, in two minutes - Tooltip: position anchored tooltips in the same draw (#1256) - cef: a hosted browser survives its host's exit - ci: a card is as high as its content - tests: the three binaries over ten seconds in release come under it - widgets: let the host install script mods into every Splash isolate - widgets: a pooled test context forgets the last case's Escape claim - widgets: the pooled test context's resets replace the globals - wm: the shell menu says what it did, and the CI waits for that - platform: the storage module says how much a volume has free - git: the memory ledger can take what it is asked for - ci: a driven app takes every key once, and the storage module builds for the browser - RadioButton: fix its touch hover state and click-off behavior (#1258) - HtmlLink: fix its hover and pressed states (#1257) - RadioButton: take key focus on the click, not on the press (#1259) - script: re-entrant dispatch, thread index validation, Any-based handle downcasts, UTF-8 previews; regex: never_loop fix - script: port Octoscript WS1 — worklist equality with fuel/deadline/work bail, uncaught-error bail, allow_debug_output - script: port Octoscript parser/tokenizer/control-flow VM patches (ws2) - script: port Octoscript heap/string/array/object hardening onto upstream's allocation budget - script: port orphaned Octoscript VM hardening (execution caps, clear_type_methods, parser diagnostics) - script: keep silenced streaming evals running past uncaught errors - script: the Octoscript hardening keeps a Makepad host's semantics and its speed - fix(text): enable ttf-parser gvar-alloc for many-tuple variable fonts - feat(text): CoreText outline fallback for hvgl-only fonts (macOS) - draw text: the CoreText outline fallback is only ever resolved for a face whose outlines live solely in hvgl, and font-family diagnostics are the `f - mail: the phone layout lines up - clock: the phone layout fits both orientations - calendar, reminders, calculator, sheets: the phone layouts fit their space - platform, widgets, wm: a touch that is taken away is cancelled, never released - weather, notes, finance, photos, route: the phone layouts tidy up - files: the phone layout tidies up - wm: the phone shell's surfaces match the app and the grid - wm on Android: every app is its own process - clock: a cancelled release neither opens an alarm nor switches tab - platform, wm: hosted apps ask the WM for what they cannot do themselves - wm on Android: apps build on the phone - wm on Android: app transitions and gestures feel like the phone's - android: rotated Vulkan windows stop rebuilding their swapchain every frame, and hosted children get touch - vulkan (android): a window released on suspend is not released again - android: hosted children hand frames over with GPU fences and draw only when they have work - wm on Android: gestures and surfaces settle like the phone's - widgets: touch lists scroll with Android's physics - wm on Android: the shell follows the Pixel launcher's motion - wm on Android: Recents shows the apps over a receding home, and the home screen switches looks - wm on Android: the look switcher stays put, the iOS dock shows its icons, background apps follow a look change - task, cargo-makepad, widgets, audio: the task manager chooses its columns, graphs every process' network and disk traffic and installs itself as a D - agents: a user touching an app under test no longer stops the agent - flowgraph: ordered ports take many wires, sockets can override their icon, and the canvas embeds as a viewport - cef: captured frames and audio carry callback clocks and a navigation epoch - git: timings read a portable clock - ai: the hub's rig-fixture tests find the asset library without the asset client - ai llm: a Metal main buffer is filled in 32 MB chunks - diskmap: a scan lists each macOS directory in one getattrlistbulk call and classifies files without allocating or locking - files: the tile view's three projections are toolbar buttons - mail: the local view takes a theme-derived palette and line icons, search pages share records - director: agent lanes form a tree and Grok joins the usage bar - widgets, draw: skeuomorphic surfaces light each other through an optional relief buffer - widgets, platform: a texture can light the relief buffer, menus take colour chips, and a style reload recompiles changed shader functions - widgets: relief surfaces can travel like mechanical keys, and dark surfaces take less neighbour light - platform, widgets, audio_route, ai: window crossfades and whole-frame presents, caption controls that click, themed menus, a stereo-pair audio tap, - builder: the Builder TUI matches the new terminal design, Windows executables carry app icons, and every app has one - platform: Android builds without Vulkan compile again, and the font-selection test counts the hosted entry point - platform: the pipeline-skip repaint mark exists only on Apple, where Metal reads it - builder: the terminal scripts delete nothing and read top to bottom, and every Builder delete stays inside its own folder - files: the Files app deletes nothing and never overwrites - director: coding agents ask before acting unless you choose otherwise - sheets, score, git, home: saves cannot cut a file short or overwrite another one, and checkouts cannot write outside the repository - git: worktree status matches git on real repositories and can be cancelled - builder: one forward-only bar per setup component, green checks on finished setup rows, a black Builder window and a smaller Windows ZIP - builder: the GPU notice is remembered - cuda: builds link only the CUDA toolkit they are given, never a system install - builder: a refused email opens the editor again with what was typed and says why, and the app section is YOUR APPS - builder: a refused email says "Email not recognised" and keeps what was typed for fixing - platform (windows): every window keeps animating with several windows open - builder previews: long work gets a page of its own (steps as a checklist, the current one carrying its bar, footer working · ctrl+c stops), Account - builder: long work runs on its own page - builder: a first build resolves online, the Rust row and bars move when the work does, and the window fits the TUI - audio_route: one API on macOS, Windows and Linux, monitoring by default and processing only when asked, plus shaders that discard a value compile to - platform, widgets, ai: a restyle recompiles nothing it already has, shows only complete frames, and a window can own its caption - builder: Compile shows a real bar - builder: the compile line is the bar and n / total crates - builder: apps get the icon their package declares - builder: no Scope command - widgets: relief surfaces can swing their light toward a point - builder: a HEAD request is a HEAD - builder: the menu waits for a choice - builder: no menu flash after log-in - builder: wait out Windows security on fresh Rust - builder: the menu opens on the first of YOUR APPS - platform (windows): half-float RGBA textures have four channels - builder: CUDA whenever the machine has NVIDIA - audio_route example: print the samples that reached the processor each second - widgets, builder: the big widget families are features, all on by default, and the Builder takes none of them - platform (macos): the display link is kept until it is invalidated - ai hub: a local chat with no model says so once, plainly - builder: the Windows exe is built for size - windows: desktop apps open no console window, and still speak through pipes - ai: Claude Desktop drives any app with the F10 panel - Constrain Splash external I/O to the host service bridge (#1243) - PortalList: stop following the end when scrolling to an earlier item (#1261) - wm: starts on Windows - ci: a full disk empties the build output before the run, not every row after it - builder: an incremental compile's bar follows what really compiles - builder: the compile bar's total is the app's own crates - builder: menus wrap around - wm: children open no console windows on Windows - wm: the whole deck out of a Builder's source, compiled only when the person opens an app - builder: CUDA crates build in an installation whose path has a space - ai: Connect hands the .mcpb to Claude Desktop itself - wm: the clock and calendar know the date on Windows - platform (windows): a popup opened for the first time draws its rows - wm: opening an app rebuilds it when its binary is out of date; warm instances never build - builder: an app edited after its build shows as needing a compile - civil-time: one local wall clock for every app, and it knows the zone on Windows - platform + wm: a hosted child redraws at its tile's new size - platform (windows): a texture pass drawn before its window has a size is skipped, not a crash - draw: glass of a view that stopped drawing leaves the screen - calendar: the Calendars sheet is an opaque panel, and the wide layout does not float one over its sidebar - build: dev keeps line tables only, release is incremental without LTO, the parallel frontend is a documented local opt-in - cargo-makepad: a binary with several app_main! entry points bundles - script: #[derive(Script)] emits one helper call per field, and ScriptNew's default methods keep their bodies out of every type - widgets: the Widget lookup methods and with_script_vm_id are compiled once, not once per widget type - platform: studio-protocol no longer waits for script, so platform starts ~0.5 s earlier - ai-speech: makepad-ai-sfx (and with it ai-h3) is built only for IndexTTS - platform: an app's package dir, icons and bundle name rebuild only that app - script + wasm_bridge: no build script that reruns on every file - build: a private app cloned into apps/<name> joins this workspace, Stage first - build: the AI stack, csg, Scope and Source Library join the one workspace - builder: every app builds in the one Makepad workspace, one target directory per source snapshot - ai chat: the chat's backends are the CLI, MCP and cloud providers; the local model is the `localai` feature - apps: every app that hosts the chat has a `localai` feature for the local model, off unless the app's own job runs one - builder: time each install component and phase - builder: Build tools, Windows SDK, Rust and CUDA install side by side, each with its own bar - builder bootstrap: rustc, rust-std and cargo download and unpack side by side under one Rust bar - widgets: the library moves to makepad-widgets-core in widgets/core, and makepad-widgets becomes its front crate - widgets: the Window, widget tree and panel theme reach the tweaker, voice, AI slot and dock through hooks, not through their modules - widgets: every widget family is a crate of its own under widgets/families, and makepad-widgets links, re-exports and registers the ones its features - aichat, widgets: the chat reads the design feedback through the tweaker's hook, so linking the chat no longer needs the tweaker - apps, examples, libs: each crate builds only the widget families it uses, and the design overlay is each app's own default feature - builder: an app release builds without the app's development defaults, and the catalog names every feature a release ships with - widgets: the build scripts rerun on their own edits only, and makepad-widgets no longer reads MAKEPAD - livepipe: takes makepad-widgets without its default families, now that the AI stack is in the one workspace - builder: the catalog names the new localai defaults of route and ai-hub - feedback: Send feedback, a caption icon and a small panel that shows exactly what is sent - builder: an app it launches knows who the person is, for Send feedback - feedback: the panel's wording reads right and draws in any font - feedback: takes makepad-widgets without its default families - builder: clear build data deletes target/ itself and says why when it cannot - ai chat: a build without local AI starts on the first provider that answers here, not on "No model" - platform: MAKEPAD=gpusim builds again - cargo-makepad: a Windows desktop build links the app's icon into its own binary, not into RUSTFLAGS - thiserror: RUSTC_BOOTSTRAP no longer turns on the unstable generic member access API - builder: every end-user build uses rustc's parallel frontend, and falls back to one thread when it fails - builder: the Windows SDK downloads the 25 cabinets its MSIs name instead of all 149, no screen or log shows a \\?\ path, long status messages wrap, - builder: CUDA kernels compile in any install folder, and when they cannot the app is built without them - builder: makepad-builder.exe declares itself like a well-formed Windows program and stops a stalled build through a job object instead of taskkill - platform (macos): a contained panic no longer frees the windows under AppKit or leaves an app that ignores quit - builder: macOS and Linux run the Builder as one shell script, the same TUI as Windows, and nothing is compiled for it - builder + ai-cuda: the CUDA kernel progress shows in the Builder's Compile row, not scribbled over its screen - builder: an app built with CUDA starts on Windows - builder (windows): the downloaded exe is built large again - builder (unix): the email check takes real addresses - repo: no third-party comic archive, generated models or sample asset in the tree - remove big example media files - cargo_makepad: no stray KNMI radar frame in the Android Java sources - widgets: the empty parts of an app's caption bar drag the window again - ScrollBar: fade out when idle, like macOS overlay scrollers (#1264) - platform: every app builds the same `windows` crate - wm: in a Makepad Builder installation apps build through the Builder, however wm was started - platform, draw: web builds start again - platform: web text draws again - widgets: the keyboard reaches boxes and modals - widgets: a hover tooltip never sits under the mouse cursor - platform: clipboard_read, reading text or an image from the system clipboard - feedback: Send clipboard, and the dialog works from the keyboard - builder: the Windows Builder ships as source, runs in its own console, and apps compile with Rust's GNU toolchain or Microsoft's - platform: an app also finds its resource map in the builder folder beside it - win_resource: PNG decode and encode on makepad-fast-inflate instead of zune-png - builder: one folder layout everywhere, makepad-builder.zip with only the files a Windows build reads, a Rust download over parallel connections, and - builder (windows): the .bat runs from a folder with spaces and parentheses, and the ZIP always carries it with CRLF - builder: coding agents start from the macOS/Linux Builder, its compile bar has its end, the app rows say where each app is in a word or two, and Win - builder (unix): running curl | sh again opens the existing installation with its saved email - builder: log out from the Account row - builder: `makepad-builder build APP` compiles an app offline exactly as the Builder does Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
314 lines
16 KiB
Python
314 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""The Windows Builder as source: the ZIP makepad.nl/builder/windows serves.
|
|
|
|
python3 tools/makepad_builder/windows/package.py [--rev REV] --out makepad-builder.zip
|
|
|
|
The ZIP holds makepad-builder.bat and, under builder/source/, the source of
|
|
the Builder (a console program: its TUI runs in the .bat's own window) with
|
|
the few small crates of this repository it depends on and nothing from
|
|
crates.io. makepad-builder.bat downloads Rust's official GNU toolchain,
|
|
compiles that source and runs it; no program of ours ships in binary form.
|
|
Everything the Builder writes later goes in builder/ too, so the folder
|
|
itself only ever holds the .bat, makepad-builder.exe and the apps.
|
|
|
|
Takes the files of REV (default HEAD) of every package in `cargo tree -p
|
|
makepad-loader --target all`: what compiles (the manifest without its
|
|
dev-dependencies, benches, examples and tests; src/, build.rs, the files
|
|
the code includes) and the licences, nothing else. Path dependencies cargo
|
|
only reads (another platform's, optional ones) go in as their manifest with
|
|
empty targets. builder/source/Cargo.toml is a generated workspace naming
|
|
only the Builder, with a lock, so the build needs no network. The same REV
|
|
gives the same bytes. Also writes FILE.sha256.
|
|
"""
|
|
import argparse
|
|
import hashlib
|
|
import io
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
def git(repo, *args):
|
|
return subprocess.check_output(["git", "-C", str(repo), *args])
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("--rev", default="HEAD")
|
|
parser.add_argument("--out", required=True, type=Path)
|
|
parser.add_argument("--package", default="makepad-loader", help="another package's closure (tests of the GNU chain)")
|
|
parser.add_argument("--worktree", action="store_true", help="file contents from the working tree, not REV (tests of uncommitted changes)")
|
|
args = parser.parse_args()
|
|
repo = Path(__file__).resolve().parents[3]
|
|
commit = git(repo, "rev-parse", args.rev).decode().strip()
|
|
|
|
# The packages, from the working tree's manifests (they must match REV).
|
|
tree = subprocess.check_output(
|
|
["cargo", "tree", "-p", args.package, "--target", "all", "-e", "normal,build",
|
|
"--prefix", "none", "--no-dedupe", "--offline"],
|
|
cwd=repo, text=True)
|
|
dirs = set()
|
|
member = None
|
|
for line in tree.splitlines():
|
|
match = re.search(r"\((/[^)]*)\)", line)
|
|
if not match:
|
|
# A crates.io package of another platform (OpenHarmony's
|
|
# hilog-sys): resolved from Cargo.lock, never compiled here.
|
|
continue
|
|
dirs.add(os.path.relpath(match.group(1), repo))
|
|
if member is None:
|
|
member = os.path.relpath(match.group(1), repo)
|
|
manifests = [m for m in git(repo, "ls-tree", "-r", "--name-only", commit).decode().splitlines() if m.endswith("Cargo.toml")]
|
|
packages = {os.path.dirname(m) for m in manifests}
|
|
|
|
def owner(path):
|
|
d = os.path.dirname(path)
|
|
while d:
|
|
if d in packages:
|
|
return d
|
|
d = os.path.dirname(d)
|
|
return None
|
|
|
|
# Cargo reads the manifest of every path dependency, optional and other
|
|
# platforms' ones included, before it resolves features. Those it does
|
|
# not build go in as their manifest and empty target files.
|
|
import tomllib
|
|
|
|
def manifest(d):
|
|
if args.worktree:
|
|
return tomllib.loads((repo / d / "Cargo.toml").read_text())
|
|
return tomllib.loads(git(repo, "show", f"{commit}:{d}/Cargo.toml").decode())
|
|
|
|
# Dev-dependencies are dropped from the manifests below.
|
|
def path_deps(d, m):
|
|
out = []
|
|
tables = [m.get(k, {}) for k in ("dependencies", "build-dependencies")]
|
|
for spec in m.get("target", {}).values():
|
|
tables += [spec.get(k, {}) for k in ("dependencies", "build-dependencies")]
|
|
for table in tables:
|
|
for dep in table.values():
|
|
if isinstance(dep, dict) and "path" in dep:
|
|
out.append(os.path.normpath(os.path.join(d, dep["path"])))
|
|
return out
|
|
|
|
stubs = {}
|
|
todo = sorted(dirs)
|
|
seen = set()
|
|
while todo:
|
|
d = todo.pop()
|
|
if d in seen:
|
|
continue
|
|
seen.add(d)
|
|
m = manifest(d)
|
|
todo += [p for p in path_deps(d, m) if p in packages]
|
|
if d not in dirs:
|
|
targets = [m.get("lib", {}).get("path", "src/lib.rs")] if "lib" in m or not m.get("bin") else []
|
|
for kind in ("bin",):
|
|
targets += [t["path"] for t in m.get(kind, []) if "path" in t]
|
|
build = m.get("package", {}).get("build")
|
|
if build is None and subprocess.run(["git", "-C", str(repo), "cat-file", "-e", f"{commit}:{d}/build.rs"], stderr=subprocess.DEVNULL).returncode == 0:
|
|
build = "build.rs"
|
|
if isinstance(build, str):
|
|
targets.append(build)
|
|
stubs[d] = targets
|
|
|
|
def text_of(path, blob):
|
|
if args.worktree:
|
|
return (repo / path).read_text(errors="replace")
|
|
return git(repo, "cat-file", "blob", blob).decode(errors="replace")
|
|
|
|
# What compiles: src/ (less a test module only `cfg(test)` declares),
|
|
# build.rs, the manifest, and the files the code names (include_str!,
|
|
# include_bytes!, include!, a resources/ path); and the licences.
|
|
def wanted(path, package):
|
|
rel = os.path.relpath(path, package)
|
|
name = os.path.basename(rel)
|
|
if rel == "Cargo.toml" or rel == "build.rs" or rel.startswith("src/"):
|
|
return True
|
|
return "/" not in rel and re.match(r"(LICEN[CS]E|COPYING|NOTICE)", name, re.I) is not None
|
|
|
|
files = []
|
|
listing = git(repo, "ls-tree", "-r", "-z", "--full-tree", commit, "--", *sorted(dirs)).split(b"\0")
|
|
for entry in listing:
|
|
if not entry:
|
|
continue
|
|
meta, path = entry.decode().split("\t", 1)
|
|
mode, kind, blob = meta.split()
|
|
if kind != "blob" or owner(path) not in dirs:
|
|
continue
|
|
if not wanted(path, owner(path)):
|
|
continue
|
|
if "/tests/" in path or path.endswith(".wast") or re.match(r"widgets/fonts/[^/]+\.ttf$", path):
|
|
continue
|
|
if args.worktree and not (repo / path).is_file():
|
|
continue
|
|
files.append((path, blob, mode))
|
|
if args.worktree:
|
|
# New files not yet committed (a test of uncommitted work).
|
|
known = {path for path, _, _ in files}
|
|
extra = git(repo, "ls-files", "-z", "--others", "--exclude-standard", "--", *sorted(dirs)).decode().split("\0")
|
|
for path in extra:
|
|
if path and path not in known and owner(path) in dirs and "/tests/" not in path and wanted(path, owner(path)):
|
|
files.append((path, "worktree", "100644"))
|
|
# Files a build script names (its icon): dep-info does not list them.
|
|
build_resources = set()
|
|
# The files the code names, and no test module only tests compile.
|
|
everything = git(repo, "ls-tree", "-r", "-z", "--full-tree", "--name-only", commit, "--", *sorted(dirs)).decode().split("\0")
|
|
by_path = {path: (blob, mode) for path, blob, mode in files}
|
|
for path, blob, mode in list(files):
|
|
if not path.endswith(".rs"):
|
|
continue
|
|
text = text_of(path, blob)
|
|
package = owner(path)
|
|
for literal in re.findall(r'include(?:_str|_bytes)?!\(\s*"([^"]+)"', text) + re.findall(r'"((?:\.\./)*resources/[^"]+)"', text):
|
|
for base in (os.path.dirname(path), package):
|
|
named = os.path.normpath(os.path.join(base, literal))
|
|
if named in by_path or owner(named) != package:
|
|
continue
|
|
if named in everything or (args.worktree and (repo / named).is_file()):
|
|
blob_id = "worktree" if args.worktree else git(repo, "rev-parse", f"{commit}:{named}").decode().strip()
|
|
by_path[named] = (blob_id, "100644")
|
|
files.append((named, blob_id, "100644"))
|
|
if os.path.basename(path) == "build.rs" and named in by_path:
|
|
build_resources.add(named)
|
|
if re.search(r"#\[cfg\(test\)\]\s*mod tests;", text):
|
|
test = os.path.join(os.path.dirname(path), "tests.rs")
|
|
files = [f for f in files if f[0] != test]
|
|
for d, targets in stubs.items():
|
|
blob = git(repo, "rev-parse", f"{commit}:{d}/Cargo.toml").decode().strip()
|
|
files.append((f"{d}/Cargo.toml", blob, "100644"))
|
|
for target in targets:
|
|
files.append((os.path.normpath(os.path.join(d, target)), None, "100644")) # empty: never compiled
|
|
files.sort()
|
|
|
|
# Only the Builder is a member: a path dependency inside a workspace's
|
|
# directory would otherwise join it, and a member's every dependency,
|
|
# optional and dev ones included, must resolve.
|
|
others = sorted(d for d in set(dirs) | set(stubs) if d != member)
|
|
root = open(repo / "Cargo.toml").read()
|
|
def raw(path, blob):
|
|
if args.worktree and blob:
|
|
return (repo / path).read_bytes()
|
|
return git(repo, "cat-file", "blob", blob) if blob else b""
|
|
|
|
# Manifests of the packages that compile lose their dev-dependencies and
|
|
# bench, example and test targets (none of those files ship).
|
|
def content(path, blob):
|
|
data = raw(path, blob)
|
|
if os.path.basename(path) != "Cargo.toml" or os.path.dirname(path) not in dirs:
|
|
return data
|
|
out, skip = [], False
|
|
for line in data.decode().splitlines(keepends=True):
|
|
header = re.match(r"\s*\[\[?([^\]]+)\]", line)
|
|
if header:
|
|
table = header.group(1).strip()
|
|
skip = (table in ("bench", "example", "test")
|
|
or re.search(r"(^|\.)dev-dependencies(\.|$)", table) is not None)
|
|
if not skip:
|
|
out.append(line)
|
|
text = "".join(out)
|
|
# No auto-discovered targets from folders that are not there.
|
|
if "[package]" in text and "autobenches" not in text:
|
|
text = text.replace("[package]\n", "[package]\nautobenches = false\nautoexamples = false\nautotests = false\n", 1)
|
|
return text.encode()
|
|
|
|
# The first line names the exact source (makepad-builder.bat compiles
|
|
# again when it changes): the commit and a hash of every file's bytes.
|
|
def workspace_for(files):
|
|
tree = hashlib.sha256()
|
|
for path, blob, _ in files:
|
|
tree.update(path.encode() + b"\0" + content(path, blob) + b"\0")
|
|
return (
|
|
"# Generated by tools/makepad_builder/windows/package.py from makepad "
|
|
+ commit + " (source " + tree.hexdigest()[:16] + ").\n[workspace]\nmembers = [\"" + member + "\"]\nresolver = \"2\"\nexclude = [\n"
|
|
+ "".join(f" \"{d}\",\n" for d in others) + "]\n\n"
|
|
# The root's crates-io patches whose package is in this graph (a
|
|
# crate that names windows-link by version still gets the one here).
|
|
+ "".join(
|
|
("[patch.crates-io]\n" if i == 0 else "") + line + "\n"
|
|
for i, line in enumerate(
|
|
line for line in re.findall(r'^(\S+ = \{ path = "([^"]+)" \})$', root, re.M) and [
|
|
m.group(0) for m in re.finditer(r'^\S+ = \{ path = "([^"]+)" \}$', root.split("[patch.crates-io]", 1)[-1].split("\n[", 1)[0], re.M)
|
|
if os.path.normpath(m.group(1)) in dirs
|
|
] or []
|
|
)
|
|
)
|
|
+ "\n[profile.release]\nincremental = false\n"
|
|
)
|
|
|
|
workspace = workspace_for(files)
|
|
|
|
# The lock, from the generated workspace, so the build resolves nothing.
|
|
import tempfile
|
|
with tempfile.TemporaryDirectory() as scratch:
|
|
for d in sorted(dirs):
|
|
Path(scratch, d).mkdir(parents=True, exist_ok=True)
|
|
for path, blob, mode in files:
|
|
target = Path(scratch, path)
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
target.write_bytes(content(path, blob))
|
|
Path(scratch, "Cargo.toml").write_text(workspace)
|
|
# Start from the repository's lock (its versions are the ones this
|
|
# tree builds with); cargo trims it to this workspace. An optional
|
|
# dependency nothing enables and nothing has locked is then never
|
|
# looked for (unicode-bidi names a ../smallvec that does not exist).
|
|
if (repo / "Cargo.lock").is_file():
|
|
Path(scratch, "Cargo.lock").write_bytes((repo / "Cargo.lock").read_bytes())
|
|
subprocess.check_call(["cargo", "metadata", "--offline", "--format-version", "1"], cwd=scratch, stdout=subprocess.DEVNULL)
|
|
lock = Path(scratch, "Cargo.lock").read_bytes()
|
|
# Exactly the files a Windows build reads: compile it for Windows
|
|
# here (a check, with the build scripts run) and take every source
|
|
# file the compiler's dep-info names. Another platform's modules and
|
|
# the files only they include stay out. Needs the
|
|
# x86_64-pc-windows-gnu standard library (rustup target add).
|
|
subprocess.check_call(
|
|
["cargo", "check", "--offline", "--locked", "--quiet", "--release", "--target", "x86_64-pc-windows-gnu",
|
|
"-p", "makepad-loader", "--bin", "makepad-builder"],
|
|
cwd=scratch, env={**os.environ, "CARGO_TARGET_DIR": str(Path(scratch, ".target"))})
|
|
used = set()
|
|
real = os.path.realpath(scratch)
|
|
for dep_file in Path(scratch, ".target").rglob("*.d"):
|
|
for word in re.split(r"(?<!\\)\s+", dep_file.read_text()):
|
|
# Paths inside the workspace are relative to it.
|
|
word = word.rstrip(":").replace("\\ ", " ")
|
|
for prefix in (real + "/", scratch + "/"):
|
|
if word.startswith(prefix):
|
|
word = word[len(prefix):]
|
|
if word and not os.path.isabs(word):
|
|
used.add(os.path.normpath(word))
|
|
def ships(path):
|
|
name = os.path.basename(path)
|
|
return (path in used or name == "Cargo.toml" or name == "build.rs" or path in build_resources
|
|
or re.match(r"(LICEN[CS]E|COPYING|NOTICE)", name, re.I) is not None)
|
|
files = [f for f in files if ships(f[0])]
|
|
workspace = workspace_for(files)
|
|
|
|
bat = (Path(__file__).parent / "makepad-builder.bat").read_bytes()
|
|
if not args.worktree:
|
|
bat = git(repo, "show", f"{commit}:tools/makepad_builder/windows/makepad-builder.bat")
|
|
# cmd.exe needs CRLF: with bare LF its labels and goto misparse and the
|
|
# window closes at once. Git keeps the file with LF (eol=crlf applies to
|
|
# checkouts only), so the ZIP always gets CRLF, whatever the source had.
|
|
bat = bat.replace(b"\r\n", b"\n").replace(b"\n", b"\r\n")
|
|
assert b"\r\r" not in bat
|
|
data = io.BytesIO()
|
|
with zipfile.ZipFile(data, "w", zipfile.ZIP_DEFLATED, compresslevel=9) as z:
|
|
def add(name, content):
|
|
info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
|
|
info.compress_type = zipfile.ZIP_DEFLATED
|
|
info.external_attr = 0o644 << 16
|
|
z.writestr(info, content)
|
|
add("makepad-builder.bat", bat)
|
|
add("builder/source/Cargo.toml", workspace.encode())
|
|
add("builder/source/Cargo.lock", lock)
|
|
for path, blob, mode in files:
|
|
add("builder/source/" + path, content(path, blob))
|
|
args.out.write_bytes(data.getvalue())
|
|
digest = hashlib.sha256(data.getvalue()).hexdigest()
|
|
Path(str(args.out) + ".sha256").write_text(f"{digest} {args.out.name}\n")
|
|
print(f"{args.out}: {len(files)} files, {len(dirs)} packages built, {len(stubs)} manifests only, {len(data.getvalue()) // 1024} KB, sha256 {digest}, makepad {commit[:9]}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|