const std = @import("std"); const llvm = @import("llvm_api.zig"); const c = llvm.c; /// One `#jni_main #jni_class("...")` declaration's Java-source emission. /// Populated by lowering and surfaced to the sx Android bundler in /// `library/modules/platform/bundle.sx` via `BuildConfig.jni_main_*`, /// which writes a `.java` file under `/java//.java`, /// compiles via `javac`, dexes via `d8`, and bundles the resulting /// `classes.dex` into the APK. pub const JniMainEmission = struct { /// foreign_path of the source decl (e.g. "co/swipelab/sxmain/SxApp"). /// Splits into package + class name for `/java//.java`. foreign_path: []const u8, /// Pre-rendered Java source bytes (from `jni_java_emit.emitJavaSource`). java_source: []const u8, }; pub const TargetConfig = struct { /// Target triple (e.g. "aarch64-apple-darwin"). Null = host default. triple: ?[*:0]const u8 = null, /// CPU name (e.g. "generic", "apple-m1"). Null = "generic". cpu: ?[*:0]const u8 = null, /// CPU features string (e.g. "+avx2"). Null = "". features: ?[*:0]const u8 = null, /// Optimization level. opt_level: OptLevel = .default, /// Library search paths (-L flags). lib_paths: []const []const u8 = &.{}, /// Framework search paths (-F flags). Apple-only. framework_paths: []const []const u8 = &.{}, /// Output path override. output_path: ?[]const u8 = null, /// Linker command (null = "cc" on Unix, "link.exe" on Windows). linker: ?[]const u8 = null, /// Sysroot for cross-compilation (passed as --sysroot to linker). sysroot: ?[]const u8 = null, /// Extra flags passed through to the linker (e.g. Emscripten -s flags). extra_link_flags: []const []const u8 = &.{}, /// Custom WASM shell template path (overrides the built-in template). wasm_shell_path: ?[]const u8 = null, /// Path to a `.app` bundle directory to produce (iOS/macOS). When set, the /// linker output is moved into the bundle alongside a generated Info.plist /// and ad-hoc signed for simulator runs. bundle_path: ?[]const u8 = null, /// CFBundleIdentifier for the bundle (e.g. "co.swipelab.sxhello"). /// Required when `bundle_path` is set. On Android, doubles as the /// AndroidManifest package="..." attribute. bundle_id: ?[]const u8 = null, /// Path to a `.apk` file to produce (Android). When set, the linked /// `.so` is wrapped into a debug-signed APK ready for `adb install`. apk_path: ?[]const u8 = null, /// Custom AndroidManifest.xml path. When null, a minimal NativeActivity /// manifest is generated from `bundle_id`. manifest_path: ?[]const u8 = null, /// Debug keystore for APK signing. Defaults to `~/.android/debug.keystore`. keystore_path: ?[]const u8 = null, /// Codesigning identity (e.g. `"Apple Development: Alex (TEAMID)"` or a /// SHA-1 fingerprint from `security find-identity -p codesigning`). /// When null, ad-hoc signs with `-` (sufficient for simulator, not device). codesign_identity: ?[]const u8 = null, /// Path to a `.mobileprovision` to embed as `embedded.mobileprovision`. /// Required for real-device builds. provisioning_profile: ?[]const u8 = null, /// Path to an entitlements plist. When null and `provisioning_profile` /// is set, the entitlements are auto-extracted from the profile. entitlements_path: ?[]const u8 = null, /// True when emitting an ahead-of-time binary (`sx build`), false for /// in-process JIT (`sx run`). Used by emit_llvm to gate code that only /// makes sense for a standalone executable — e.g. the macOS bundle /// `chdir` shouldn't run in JIT mode because it would mutate the host /// sx process's CWD. is_aot: bool = false, pub const OptLevel = enum { none, less, default, aggressive, pub fn toLLVM(self: OptLevel) c.LLVMCodeGenOptLevel { return switch (self) { .none => c.LLVMCodeGenLevelNone, .less => c.LLVMCodeGenLevelLess, .default => c.LLVMCodeGenLevelDefault, .aggressive => c.LLVMCodeGenLevelAggressive, }; } }; /// Check if target triple indicates aarch64/arm64 (runtime check, not comptime). pub fn isAarch64(self: TargetConfig) bool { return self.tripleHasPrefix("aarch64", "arm64"); } /// Check if target triple indicates x86_64/x86-64. pub fn isX86_64(self: TargetConfig) bool { return self.tripleHasPrefix("x86_64", "x86-64"); } /// Check if target triple indicates Windows (contains "windows" or "win32"). pub fn isWindows(self: TargetConfig) bool { return self.tripleContains("windows") or self.tripleContains("win32"); } /// Check if target triple indicates WebAssembly (wasm32 or wasm64). pub fn isWasm(self: TargetConfig) bool { return self.tripleHasPrefix("wasm32", "wasm64"); } /// Check if target triple indicates wasm32 specifically (4-byte pointers, i32 size_t). pub fn isWasm32(self: TargetConfig) bool { return self.tripleHasPrefix("wasm32", "wasm32"); } /// Check if target triple indicates wasm64 specifically (8-byte pointers, i64 size_t). pub fn isWasm64(self: TargetConfig) bool { return self.tripleHasPrefix("wasm64", "wasm64"); } /// Check if target triple indicates macOS/Darwin (does not match iOS). pub fn isMacOS(self: TargetConfig) bool { if (self.isIOS()) return false; return self.tripleContains("darwin") or self.tripleContains("macos"); } /// Check if target triple indicates iOS (device or simulator). pub fn isIOS(self: TargetConfig) bool { return self.tripleContains("-apple-ios"); } /// Check if target triple indicates the iOS Simulator. pub fn isIOSSimulator(self: TargetConfig) bool { return self.isIOS() and self.tripleContains("simulator"); } /// Check if target triple indicates a real iOS device (not Simulator). pub fn isIOSDevice(self: TargetConfig) bool { return self.isIOS() and !self.tripleContains("simulator"); } /// Check if target triple indicates Linux (NOT Android — Android uses /// "linux-android" too but isAndroid() must take precedence). pub fn isLinux(self: TargetConfig) bool { if (self.isAndroid()) return false; return self.tripleContains("linux"); } /// Check if target triple indicates Android (e.g. aarch64-linux-android21). pub fn isAndroid(self: TargetConfig) bool { return self.tripleContains("android"); } /// Check if target triple indicates Emscripten (contains "emscripten"). pub fn isEmscripten(self: TargetConfig) bool { return self.tripleContains("emscripten"); } fn tripleHasPrefix(self: TargetConfig, prefix1: []const u8, prefix2: []const u8) bool { if (self.triple) |t| { const span = std.mem.span(t); return std.mem.startsWith(u8, span, prefix1) or std.mem.startsWith(u8, span, prefix2); } const dt = c.LLVMGetDefaultTargetTriple(); defer c.LLVMDisposeMessage(dt); const span = std.mem.span(dt); return std.mem.startsWith(u8, span, prefix1) or std.mem.startsWith(u8, span, prefix2); } fn tripleContains(self: TargetConfig, needle: []const u8) bool { if (self.triple) |t| { return std.mem.indexOf(u8, std.mem.span(t), needle) != null; } const dt = c.LLVMGetDefaultTargetTriple(); defer c.LLVMDisposeMessage(dt); return std.mem.indexOf(u8, std.mem.span(dt), needle) != null; } pub fn getCpu(self: TargetConfig) [*:0]const u8 { return self.cpu orelse "generic"; } pub fn getFeatures(self: TargetConfig) [*:0]const u8 { return self.features orelse ""; } pub fn getLinker(self: TargetConfig) []const u8 { return self.linker orelse "cc"; } }; /// Execute a precompiled object file in-process using LLVM's ORC JIT. /// Takes ownership of obj_buf. Returns the exit code from main(). pub fn runJITFromObject(obj_buf: c.LLVMMemoryBufferRef) !u8 { // Create LLJIT with default builder (no custom TM needed — .o is precompiled) var jit: c.LLVMOrcLLJITRef = null; var err = c.LLVMOrcCreateLLJIT(&jit, null); if (err != null) { const msg = c.LLVMGetErrorMessage(err); defer c.LLVMDisposeErrorMessage(msg); std.debug.print("JIT error: {s}\n", .{std.mem.span(msg)}); return error.CompileError; } defer _ = c.LLVMOrcDisposeLLJIT(jit); // Add process symbols so JIT can find libc (printf, etc.) const jd = c.LLVMOrcLLJITGetMainJITDylib(jit); const prefix = c.LLVMOrcLLJITGetGlobalPrefix(jit); var gen: c.LLVMOrcDefinitionGeneratorRef = null; err = c.LLVMOrcCreateDynamicLibrarySearchGeneratorForProcess(&gen, prefix, null, null); if (err != null) { const msg = c.LLVMGetErrorMessage(err); defer c.LLVMDisposeErrorMessage(msg); std.debug.print("JIT symbol gen error: {s}\n", .{std.mem.span(msg)}); return error.CompileError; } c.LLVMOrcJITDylibAddGenerator(jd, gen); // Add precompiled object file (transfers ownership of obj_buf) err = c.LLVMOrcLLJITAddObjectFile(jit, jd, obj_buf); if (err != null) { const msg = c.LLVMGetErrorMessage(err); defer c.LLVMDisposeErrorMessage(msg); std.debug.print("JIT add object error: {s}\n", .{std.mem.span(msg)}); return error.CompileError; } // Look up the "main" function var main_addr: c.LLVMOrcExecutorAddress = 0; err = c.LLVMOrcLLJITLookup(jit, &main_addr, "main"); if (err != null) { const msg = c.LLVMGetErrorMessage(err); defer c.LLVMDisposeErrorMessage(msg); std.debug.print("JIT lookup error: {s}\n", .{std.mem.span(msg)}); return error.CompileError; } // Cast to function pointer and call const main_fn: *const fn () callconv(.c) i32 = @ptrFromInt(main_addr); const result = main_fn(); return if (result >= 0 and result <= 255) @intCast(result) else 1; } // Android APK bundling (createApk, compileJniMainSources, // buildAndroidManifest, buildJniMainManifest, ensureDebugKeystore, // libNameFromSoBasename + helpers) has moved to // `library/modules/platform/bundle.sx`. `src/main.zig` invokes it // post-link via the BuildOptions callback registered from sx code. // `--apk ` on the CLI is a transitional alias that feeds // `bundle_path` so the auto-fallback to `platform.bundle.bundle_main` // fires; programs that opt in via `set_post_link_callback` reach the // sx bundler directly. /// Discover the Android NDK root. Honors $ANDROID_NDK_HOME / $ANDROID_NDK_ROOT, /// otherwise picks the highest-versioned NDK under $HOME/Library/Android/sdk/ndk /// (the SDK Manager default install location on macOS). Caller owns the slice. pub fn discoverAndroidNdk(allocator: std.mem.Allocator, io: std.Io) ![]const u8 { if (std.c.getenv("ANDROID_NDK_HOME")) |env| { return try allocator.dupe(u8, std.mem.span(env)); } if (std.c.getenv("ANDROID_NDK_ROOT")) |env| { return try allocator.dupe(u8, std.mem.span(env)); } const home_env = std.c.getenv("HOME") orelse { std.debug.print("error: cannot locate Android NDK \u{2014} set $ANDROID_NDK_HOME\n", .{}); return error.NdkNotFound; }; const home = std.mem.span(home_env); const ndk_root = try std.fmt.allocPrint(allocator, "{s}/Library/Android/sdk/ndk", .{home}); var dir = std.Io.Dir.openDir(.cwd(), io, ndk_root, .{ .iterate = true }) catch { std.debug.print("error: no NDK at {s} \u{2014} install via Android Studio or set $ANDROID_NDK_HOME\n", .{ndk_root}); return error.NdkNotFound; }; defer dir.close(io); var best: ?[]const u8 = null; var it = dir.iterate(); while (it.next(io) catch null) |entry| { if (entry.kind != .directory) continue; if (best == null or std.mem.order(u8, entry.name, best.?) == .gt) { best = try allocator.dupe(u8, entry.name); } } const version = best orelse { std.debug.print("error: no NDK versions under {s}\n", .{ndk_root}); return error.NdkNotFound; }; return try std.fmt.allocPrint(allocator, "{s}/{s}", .{ ndk_root, version }); } /// Run `xcrun --sdk --show-sdk-path` and return the trimmed path. /// Caller owns the returned slice. pub fn discoverAppleSdk(allocator: std.mem.Allocator, io: std.Io, sdk_name: []const u8) ![]const u8 { const r = std.process.run(allocator, io, .{ .argv = &.{ "xcrun", "--sdk", sdk_name, "--show-sdk-path" }, }) catch |e| { std.debug.print("error: failed to run xcrun: {} \u{2014} install Xcode Command Line Tools (xcode-select --install)\n", .{e}); return error.SdkNotFound; }; defer allocator.free(r.stderr); errdefer allocator.free(r.stdout); if (r.term != .exited or r.term.exited != 0) { std.debug.print("error: xcrun --sdk {s} --show-sdk-path failed\n", .{sdk_name}); allocator.free(r.stdout); return error.SdkNotFound; } const trimmed = std.mem.trimEnd(u8, r.stdout, " \t\r\n"); const out = try allocator.dupe(u8, trimmed); allocator.free(r.stdout); return out; } pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, extra_objects: []const []const u8, output_bin: []const u8, libraries: []const []const u8, frameworks: []const []const u8, target_config: TargetConfig, has_jni_main: bool) !void { var argv = std.ArrayList([]const u8).empty; if (target_config.isIOS()) { // iOS: clang driver with -isysroot pointing at the iOS SDK. // -l libraries are generally wrong for iOS (Apple ships system code // as frameworks); user-declared #library still pass through. const linker = target_config.linker orelse "clang"; try argv.append(allocator, linker); if (target_config.triple) |t| { try argv.append(allocator, "-target"); try argv.append(allocator, std.mem.span(t)); } const sdk_path = if (target_config.sysroot) |sr| try allocator.dupe(u8, sr) else blk: { const sdk_name: []const u8 = if (target_config.isIOSSimulator()) "iphonesimulator" else "iphoneos"; break :blk try discoverAppleSdk(allocator, io, sdk_name); }; try argv.append(allocator, "-isysroot"); try argv.append(allocator, sdk_path); const min_flag: []const u8 = if (target_config.isIOSSimulator()) "-mios-simulator-version-min=14.0" else "-mios-version-min=14.0"; try argv.append(allocator, min_flag); // Embedded framework load path: bundle/Frameworks at runtime. try argv.append(allocator, "-Wl,-rpath,@executable_path/Frameworks"); try argv.append(allocator, output_obj); try argv.append(allocator, "-o"); try argv.append(allocator, output_bin); for (extra_objects) |eo| try argv.append(allocator, eo); for (target_config.lib_paths) |lp| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp})); } for (target_config.framework_paths) |fp| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-F{s}", .{fp})); } for (libraries) |lib| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib})); } for (frameworks) |fw| { try argv.append(allocator, "-framework"); try argv.append(allocator, fw); } for (target_config.extra_link_flags) |flag| { var it = std.mem.tokenizeScalar(u8, flag, ' '); while (it.next()) |part| try argv.append(allocator, part); } } else if (target_config.isAndroid()) { // Android: NDK clang. Produces a shared library (.so). // // Two entry shapes: // // - **#jni_main path (`has_jni_main = true`)** — the Java side // drives lifecycle (the bundled classes.dex declares an // Activity that overrides `onCreate` etc.). The .so just // provides JNI implementations bound at load time via the // `JNI_OnLoad` synthesized in slice R.3. No native_app_glue // is needed: there's no `ANativeActivity_onCreate` to host, // no `android_main` event loop to run. // // - **Legacy NativeActivity path (`has_jni_main = false`)** — // native_app_glue.c is compiled and linked alongside the sx // code; the glue owns `ANativeActivity_onCreate` and forwards // into the user's `android_main` on a worker thread. The // `-u ANativeActivity_onCreate` keeps the glue's symbol from // being stripped (nothing in our .o references it). // // The `libraries` parameter (collected from `#library` directives) // and `frameworks` parameter (Apple-only by definition) are // intentionally ignored here. On Android, users opt into specific // libs via `opts.add_link_flag("-l")` in their build.sx — // the platform-specific link surface should be expressed in build // options rather than auto-inherited from every imported module // (most of which assume Apple targets). const ndk_root = if (target_config.sysroot) |sr| try allocator.dupe(u8, sr) else try discoverAndroidNdk(allocator, io); const host_tag: []const u8 = if (@import("builtin").os.tag == .macos) "darwin-x86_64" else "linux-x86_64"; const clang = try std.fmt.allocPrint(allocator, "{s}/toolchains/llvm/prebuilt/{s}/bin/clang", .{ ndk_root, host_tag }); const glue_obj_opt: ?[]const u8 = if (has_jni_main) null else blk: { const glue_src = try std.fmt.allocPrint(allocator, "{s}/sources/android/native_app_glue/android_native_app_glue.c", .{ndk_root}); const glue_obj = try std.fmt.allocPrint(allocator, "{s}.glue.o", .{output_obj}); var glue_argv = std.ArrayList([]const u8).empty; try glue_argv.appendSlice(allocator, &.{ clang, "-c", "-fPIC" }); if (target_config.triple) |t| { try glue_argv.append(allocator, "-target"); try glue_argv.append(allocator, std.mem.span(t)); } try glue_argv.appendSlice(allocator, &.{ glue_src, "-o", glue_obj }); const glue_slice = try glue_argv.toOwnedSlice(allocator); var glue_child = std.process.spawn(io, .{ .argv = glue_slice }) catch return error.LinkError; const glue_term = glue_child.wait(io) catch return error.LinkError; if (glue_term != .exited or glue_term.exited != 0) return error.LinkError; break :blk glue_obj; }; try argv.append(allocator, clang); if (target_config.triple) |t| { try argv.append(allocator, "-target"); try argv.append(allocator, std.mem.span(t)); } try argv.append(allocator, "-shared"); try argv.append(allocator, "-fPIC"); if (!has_jni_main) { try argv.appendSlice(allocator, &.{ "-u", "ANativeActivity_onCreate" }); } try argv.append(allocator, output_obj); if (glue_obj_opt) |go| try argv.append(allocator, go); for (extra_objects) |eo| try argv.append(allocator, eo); try argv.append(allocator, "-o"); try argv.append(allocator, output_bin); for (target_config.lib_paths) |lp| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp})); } // Default libs available on every Android runtime; linker drops // unreferenced ones automatically. `#library` directives are // intentionally NOT auto-emitted here (most assume Apple targets); // users opt in per-target via `opts.add_link_flag("-l...")` in // their build.sx. try argv.appendSlice(allocator, &.{ "-llog", "-landroid", "-lEGL", "-lGLESv3", "-lm", "-ldl" }); for (target_config.extra_link_flags) |flag| { var it = std.mem.tokenizeScalar(u8, flag, ' '); while (it.next()) |part| try argv.append(allocator, part); } } else if (target_config.isEmscripten()) { // Emscripten: use emcc as the linker/driver const linker = target_config.linker orelse "emcc"; try argv.appendSlice(allocator, &.{ linker, output_obj, "-o", output_bin }); for (extra_objects) |eo| try argv.append(allocator, eo); if (target_config.sysroot) |sr| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "--sysroot={s}", .{sr})); } for (target_config.lib_paths) |lp| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp})); } // Skip -l flags for Emscripten: libraries like SDL3 are provided via // -sUSE_SDL=3, not -lSDL3. User provides everything via --lflags. // wasm64: automatically add -sMEMORY64 for the linker if (target_config.isWasm64()) { try argv.append(allocator, "-sMEMORY64"); } // HTML shell template: use custom path if set, otherwise write built-in template to temp file if (std.mem.endsWith(u8, output_bin, ".html")) { if (target_config.wasm_shell_path) |custom_shell| { try argv.appendSlice(allocator, &.{ "--shell-file", custom_shell }); } else { const shell_html = @embedFile("wasm_shell.html"); const shell_path = try std.fmt.allocPrint(allocator, "{s}.shell.html", .{output_obj}); std.Io.Dir.writeFile(.cwd(), io, .{ .sub_path = shell_path, .data = shell_html }) catch {}; try argv.appendSlice(allocator, &.{ "--shell-file", shell_path }); } } // Extra linker flags (e.g. -sUSE_SDL=3, -sUSE_WEBGL2=1, --preload-file assets) // Split space-separated flags into individual argv entries. for (target_config.extra_link_flags) |flag| { var it = std.mem.tokenizeScalar(u8, flag, ' '); while (it.next()) |part| { try argv.append(allocator, part); } } } else if (target_config.isWindows()) { // Windows: MSVC-style linker flags const linker = target_config.linker orelse "link.exe"; try argv.appendSlice(allocator, &.{ linker, output_obj }); for (extra_objects) |eo| try argv.append(allocator, eo); try argv.append(allocator, try std.fmt.allocPrint(allocator, "/OUT:{s}", .{output_bin})); for (target_config.lib_paths) |lp| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "/LIBPATH:{s}", .{lp})); } for (libraries) |lib| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "{s}.lib", .{lib})); } } else { // Unix: cc-style linker flags try argv.appendSlice(allocator, &.{ target_config.getLinker(), output_obj, "-o", output_bin }); for (extra_objects) |eo| try argv.append(allocator, eo); if (target_config.sysroot) |sr| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "--sysroot={s}", .{sr})); } // User-supplied library paths first for (target_config.lib_paths) |lp| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp})); } // Auto-detect host OS library paths when linking foreign libraries if (libraries.len > 0 and target_config.triple == null) { for (host_lib_paths) |path| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{path})); } } for (libraries) |lib| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib})); } // Frameworks: only meaningful on Apple targets; silently ignored elsewhere. if (target_config.isMacOS()) { for (frameworks) |fw| { try argv.append(allocator, "-framework"); try argv.append(allocator, fw); } } // Extra linker flags — split space-separated flags into individual argv entries. for (target_config.extra_link_flags) |flag| { var it = std.mem.tokenizeScalar(u8, flag, ' '); while (it.next()) |part| { try argv.append(allocator, part); } } } const argv_slice = try argv.toOwnedSlice(allocator); if (std.c.getenv("SX_DEBUG_LINK") != null) { std.debug.print("[sx] link argv:", .{}); for (argv_slice) |a| std.debug.print(" {s}", .{a}); std.debug.print("\n", .{}); } var child = std.process.spawn(io, .{ .argv = argv_slice, }) catch return error.LinkError; const result = child.wait(io) catch return error.LinkError; if (result != .exited) return error.LinkError; if (result.exited != 0) return error.LinkError; } // Apple .app bundling (createBundle, embedFramework, extractEntitlements, // buildInfoPlist, codesign) has moved to // `library/modules/platform/bundle.sx`. `src/main.zig` invokes it // post-link via the BuildOptions callback registered from sx code. /// After emcc produces HTML output, inject cache-busting hashes into the /// generated // Inject ?v=HASH into the src and prepend a Module.locateFile script. while (std.mem.indexOfPos(u8, html, pos, "src=\"")) |src_start| { const val_start = src_start + 5; // past src=" const val_end = std.mem.indexOfPos(u8, html, val_start, "\"") orelse break; const src_val = html[val_start..val_end]; if (!std.mem.endsWith(u8, src_val, ".js")) { // Not a .js src — skip past this attribute and keep searching pos = val_end + 1; continue; } // Find the opening < of this tag to inject locateFile before it const tag_start = if (std.mem.lastIndexOf(u8, html[pos..src_start], "<")) |off| pos + off else src_start; // Copy everything up to the tag start out.appendSlice(allocator, html[pos..tag_start]) catch return; // Inject Module.locateFile once, before the first .js script tag if (!injected_locateFile) { out.appendSlice(allocator, "\n") catch return; injected_locateFile = true; } // Copy tag up to the closing quote of src, inserting ?v=HASH out.appendSlice(allocator, html[tag_start..val_end]) catch return; out.appendSlice(allocator, "?v=") catch return; out.appendSlice(allocator, hash_hex) catch return; pos = val_end; } // Copy remaining HTML out.appendSlice(allocator, html[pos..]) catch return; const final = out.toOwnedSlice(allocator) catch return; std.Io.Dir.writeFile(.cwd(), io, .{ .sub_path = html_path, .data = final }) catch {}; } /// Common library paths for the host OS, computed at comptime. pub const host_lib_paths = blk: { const builtin = @import("builtin"); var paths: []const []const u8 = &.{}; if (builtin.os.tag == .macos) { if (builtin.cpu.arch == .aarch64) { // Apple Silicon Homebrew paths = &.{ "/opt/homebrew/lib", "/usr/local/lib" }; } else { // Intel Mac Homebrew paths = &.{"/usr/local/lib"}; } } else if (builtin.os.tag == .linux) { paths = &.{ "/usr/local/lib", "/usr/lib" }; } break :blk paths; };