diff --git a/Cargo.toml b/Cargo.toml index a858059..8e93a29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,7 @@ winres = "0.1" [profile.release] opt-level = 3 -lto = "fat" +lto = "thin" codegen-units = 1 panic = "abort" strip = true diff --git a/plugins/cagire-plugins/src/editor.rs b/plugins/cagire-plugins/src/editor.rs index 8289074..0c9e760 100644 --- a/plugins/cagire-plugins/src/editor.rs +++ b/plugins/cagire-plugins/src/editor.rs @@ -234,7 +234,7 @@ pub fn create_editor( // Read live snapshot from the audio thread let shared = editor.bridge.shared_state.load(); editor.snapshot = SequencerSnapshot::from(shared.as_ref()); - editor.app.playback.playing = editor.snapshot.playing; + editor.app.playback.playing = editor.playing.load(std::sync::atomic::Ordering::Relaxed); // Sync host tempo into LinkState so title bar shows real tempo if shared.tempo > 0.0 { diff --git a/scripts/__pycache__/build.cpython-314.pyc b/scripts/__pycache__/build.cpython-314.pyc index e3c1119..a4c4da3 100644 Binary files a/scripts/__pycache__/build.cpython-314.pyc and b/scripts/__pycache__/build.cpython-314.pyc differ diff --git a/scripts/build.py b/scripts/build.py index 796fff2..cac90a4 100755 --- a/scripts/build.py +++ b/scripts/build.py @@ -655,6 +655,55 @@ def _build_display( return Group(table, log_text) +def _prebuild_cross_images(root: Path, platforms: list[Platform]) -> None: + """Pre-build Docker images for cross-compiled targets in parallel.""" + cross_platforms = [p for p in platforms if p.cross] + if not cross_platforms: + return + + cross_toml = root / "Cross.toml" + if not cross_toml.exists(): + return + + with open(cross_toml, "rb") as f: + cross_config = tomllib.load(f) + + def build_image(p: Platform) -> None: + target_cfg = cross_config.get("target", {}).get(p.triple, {}) + dockerfile = target_cfg.get("dockerfile") + if not dockerfile: + return + tag = f"cross-custom-{p.triple}:local" + try: + subprocess.run( + ["docker", "build", "-t", tag, "-f", str(root / dockerfile), str(root)], + capture_output=True, timeout=600, + ) + except Exception: + pass # non-critical, cross will build if needed + + console.print("[dim]Pre-building cross-compilation Docker images...[/]") + with ThreadPoolExecutor(max_workers=len(cross_platforms)) as pool: + pool.map(build_image, cross_platforms) + + +def _build_native_sequential( + root: Path, + native_platforms: list[Platform], + config: BuildConfig, + completed: dict[str, PlatformResult], + start_times: dict[str, float], +) -> list[PlatformResult]: + """Build native platforms sequentially (they share the cargo target/ lock).""" + native_results = [] + for p in native_platforms: + start_times[p.alias] = time.monotonic() + r = build_platform(root, p, config) + completed[r.platform.alias] = r + native_results.append(r) + return native_results + + def run_builds( root: Path, platforms: list[Platform], config: BuildConfig, version: str, verbose: bool = False, ) -> list[PlatformResult]: @@ -665,6 +714,10 @@ def run_builds( for p in platforms: _update_phase(p.alias, "waiting", 0) + # Split into native (share cargo lock) and cross (independent Docker builds) + native_platforms = [p for p in platforms if not p.cross] + cross_platforms = [p for p in platforms if p.cross] + results: list[PlatformResult] = [] completed: dict[str, PlatformResult] = {} start_times: dict[str, float] = {p.alias: time.monotonic() for p in platforms} @@ -676,15 +729,31 @@ def run_builds( return _build_display(platforms, config, completed, start_times, log_max_lines) with Live(make_display(), console=console, refresh_per_second=4) as live: - with ThreadPoolExecutor(max_workers=len(platforms)) as pool: - futures = {pool.submit(build_platform, root, p, config): p for p in platforms} + # Native builds run sequentially in one thread (they contend on cargo lock). + # Cross builds run in parallel (each in its own Docker container). + with ThreadPoolExecutor(max_workers=max(len(cross_platforms) + 1, 1)) as pool: + futures = {} + + if native_platforms: + f = pool.submit( + _build_native_sequential, root, native_platforms, config, + completed, start_times, + ) + futures[f] = "native" + + for p in cross_platforms: + f = pool.submit(build_platform, root, p, config) + futures[f] = "cross" + pending = set(futures.keys()) while pending: done = {f for f in pending if f.done()} for f in done: - r = f.result() - completed[r.platform.alias] = r - results.append(r) + tag = futures[f] + if tag == "native": + results.extend(f.result()) + else: + results.append(f.result()) pending.discard(f) live.update(make_display()) if pending: @@ -936,6 +1005,8 @@ def main() -> None: check_prerequisites(platforms, config) + _prebuild_cross_images(root, platforms) + t0 = time.monotonic() results = run_builds(root, platforms, config, version, verbose=args.verbose) wall_time = time.monotonic() - t0