{"id":36967929,"url":"https://github.com/malt3/hermetic-launcher","last_synced_at":"2026-01-30T02:35:37.553Z","repository":{"id":328375785,"uuid":"1114557227","full_name":"malt3/hermetic-launcher","owner":"malt3","description":"Minimal, deterministic Bazel launcher stubs for all platforms. Create tiny native binaries (10-68KB) from templates with embedded arguments and runfiles resolution. Built with Rust.","archived":false,"fork":false,"pushed_at":"2025-12-22T13:54:00.000Z","size":164,"stargazers_count":11,"open_issues_count":2,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-13T14:13:45.277Z","etag":null,"topics":["bazel","build-tools","cross-platform","deterministic","launcher","native","runfiles","rust"],"latest_commit_sha":null,"homepage":"https://registry.bazel.build/modules/hermetic_launcher","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/malt3.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-11T14:44:43.000Z","updated_at":"2025-12-31T13:47:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/malt3/hermetic-launcher","commit_stats":null,"previous_names":["malt3/runfiles-stub","malt3/hermetic-launcher"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/malt3/hermetic-launcher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/malt3%2Fhermetic-launcher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/malt3%2Fhermetic-launcher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/malt3%2Fhermetic-launcher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/malt3%2Fhermetic-launcher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/malt3","download_url":"https://codeload.github.com/malt3/hermetic-launcher/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/malt3%2Fhermetic-launcher/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28398988,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bazel","build-tools","cross-platform","deterministic","launcher","native","runfiles","rust"],"created_at":"2026-01-13T20:58:40.165Z","updated_at":"2026-01-13T20:58:40.743Z","avatar_url":"https://github.com/malt3.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hermetic Launcher\n\nA minimal, cross-platform Bazel runfiles stub runner that replaces shell scripts with tiny native binaries.\n\n## Why This Exists\n\n**Problem**: Many Bazel rules create shell script wrappers to invoke tools with their runfiles dependencies. Shell scripts aren't cross-platform—bash scripts don't work on Windows, batch files don't work on Unix.\n\n**Solution**: This project provides tiny native binaries (10-68KB) that:\n- Work on **Linux, macOS, and Windows**\n- Resolve Bazel runfiles paths\n- Forward arguments to the actual tool\n- Can be **\"cross-compiled\"** (finalized) from any build platform for any target platform\n\n## Primary Use Case: Bazel Rules\n\nInstead of generating platform-specific shell scripts like this:\n\n```bash\n#!/bin/bash\n# Generated wrapper script - Linux/macOS only because of shebang!\nexec $RUNFILES_DIR/my_workspace/bin/tool \"$@\"\n```\n\nCreate a launcher binary for the target platform:\n\n```bash\n./finalize-stub \\\n  --template runfiles-stub-x86_64-linux \\\n  --transform 0 \\\n  --output my_tool \\\n  -- \\\n  my_workspace/bin/tool\n\n# Runtime: works with runfiles\nRUNFILES_DIR=/path/to/runfiles ./my_tool --flag arg1 arg2\n```\n\nThis enables Bazel rules to create tiny, platform-agnostic entrypoints that work identically on Linux, macOS, and Windows.\n\n## Features\n\n- **Cross-platform**: Linux (x86_64, aarch64), macOS (x86_64, aarch64), Windows (x86_64)\n- **True cross-compilation**: Finalize launcher for **any target platform** from **any build platform**\n  - Build on Linux → create Windows/macOS launcher\n  - Build on macOS → create Linux/Windows launcher\n  - Build on Windows → create Linux/macOS launcher\n- **Deterministic**: Same inputs always produce identical output, regardless of build platform\n- **Tiny binaries**: 10-68KB depending on platform\n- **Runtime arguments**: Forward `$@` to the wrapped tool\n- **No dependencies**: Fully static on Linux, minimal dependencies on macOS/Windows\n\n## Quick Start\n\n### Download Pre-built Binaries\n\n```bash\n# Download from GitHub releases\nVERSION=\u003cversion\u003e # could be \"binaries-20251216\"\nwget https://github.com/malt3/hermetic-launcher/releases/download/${VERSION}/runfiles-stub-x86_64-linux\nwget https://github.com/malt3/hermetic-launcher/releases/download/${VERSION}/finalize-stub-x86_64-linux\nchmod +x runfiles-stub-x86_64-linux\nchmod +x finalize-stub-x86_64-linux\n```\n\n### Create a Stub\n\n```bash\n# Finalize a stub that wraps /bin/echo\n./finalize-stub-x86_64-linux \\\n  --template runfiles-stub-x86_64-linux \\\n  --transform 0 \\\n  --output my_echo \\\n  -- \\\n  _main/echo\n\n# Create a manifest (This maps the runfile _main/echo to /bin/echo)\n# Note: you can either use a runfiles manifest or a runfiles directory.\ncat \u003e manifest.txt \u003c\u003c 'EOF'\n_main/echo /bin/echo\nEOF\n\n# Run it - embedded args + runtime args\nRUNFILES_MANIFEST_FILE=manifest.txt ./my_echo \"Hello from embedded!\" arg1 arg2\n# Output: Hello from embedded! arg1 arg2\n```\n\nThe stub:\n1. Resolves `_main/echo` to `/bin/echo` through runfiles (because `--transform=0`)\n2. Appends runtime arguments (`arg1 arg2`)\n3. Executes: `/bin/echo \"Hello from embedded!\" arg1 arg2`\n\n## How It Works\n\n### Two-Step Process\n\n```\n┌──────────────────┐\n│  Template Binary │  Generic stub for a platform (10-68KB)\n│ (runfiles-stub)  │  Contains placeholder sections\n└────────┬─────────┘\n         │\n         │ finalize-stub patches placeholders with:\n         │  - Number of arguments\n         │  - Which args to transform (bitmask)\n         │  - Actual argument values\n         │\n         ▼\n┌──────────────────┐\n│ Finalized Binary │  Ready-to-use stub (same size)\n│   (my_tool)      │  Embedded args + accepts runtime args\n└────────┬─────────┘\n         │\n         │ At runtime:\n         │  1. Checks for RUNFILES_DIR, RUNFILES_MANIFEST_FILE, \u003cexecutable\u003e.runfiles_manifest, or \u003cexecutable\u003e.runfiles\n         │  2. Resolves selected embedded args through runfiles\n         │  3. Appends runtime $@ arguments\n         │  4. Executes target with all args\n         │\n         ▼\n    Target Program\n```\n\n### Cross-Platform Finalization\n\nThe finalizer works on any platform to create launchers for any platform:\n\n```bash\n# On Linux, create launcher for the target platform of choice\n./finalize-stub-x86_64-linux --template runfiles-stub-x86_64-linux --output stub-linux -- /bin/tool\n./finalize-stub-x86_64-linux --template runfiles-stub-x86_64-macos --output stub-macos -- /bin/tool\n./finalize-stub-x86_64-linux --template runfiles-stub-x86_64-windows.exe --output stub.exe -- 'C:\\Windows\\System32\\cmd.exe'\n\n# The finalizer just patches bytes - no platform-specific logic needed!\n```\n\nThis is crucial for Bazel: your **exec platform** (where the build runs) can create stubs for any **target platform** (where the output runs).\n\n## Supported Platforms\n\n| Platform | Architectures | Template Size | Notes |\n|----------|--------------|---------------|-------|\n| **Linux** | x86_64, aarch64 | 10-68KB | Fully static, no dependencies |\n| **macOS** | x86_64, aarch64 | 13-49KB | Links with libSystem |\n| **Windows** | x86_64 | 22KB | Links with kernel32.dll, shell32.dll |\n\n**Finalizers** (the tool that patches templates):\n- Linux: x86_64, aarch64 (static musl binaries)\n- macOS: x86_64, aarch64\n- Windows: x86_64\n\n## Usage\n\n### Basic Usage\n\n```bash\n# Syntax\nfinalize-stub --template \u003ctemplate\u003e [OPTIONS] -- \u003carg0\u003e [arg1 ...]\n\n# No transforms (default - all arguments are literal)\nfinalize-stub --template template --output my_tool -- my_workspace/bin/tool data/input.txt\n\n# Transform only specific arguments (repeated flags)\nfinalize-stub --template template --transform 0 --transform 2 --output my_tool -- /bin/tool --flag data/file\n\n# Transform only specific arguments (comma-separated)\nfinalize-stub --template template --transform 0,2 --output my_tool -- /bin/tool --flag data/file\n#                                             ^^^                       ^^^        ^^^^    ^^^\n#                                          arg0,arg2                 transform   literal transform\n```\n\n### Options\n\n```\n--template \u003cPATH\u003e           Path to template runfiles-stub binary (required)\n\n--transform \u003cN\u003e             Mark argument N for runfiles resolution (0-9)\n                            Can be repeated for multiple arguments (--transform 0 --transform 2)\n                            or comma-separated (--transform 0,2)\n                            Default: no arguments are transformed\n\n--export-runfiles-env       Export runfiles environment variables to child process\n                            Values: true (default) or false\n                            When true: RUNFILES_DIR, RUNFILES_MANIFEST_FILE, and JAVA_RUNFILES\n                            are set in the child process based on discovered runfiles\n                            When false: child process inherits environment unchanged\n\n--output \u003cPATH\u003e             Output file path (default: stdout)\n\n--                          Separates flags from positional arguments (recommended)\n```\n\n### Runtime Arguments\n\nFinalized stubs forward runtime arguments to the target:\n\n```bash\n# Create stub with embedded args\nfinalize-stub --template template --transform 0 --output stub -- /bin/grep \"pattern\"\n\n# Run with additional args - they're forwarded as argv\n./stub file1.txt file2.txt\n# Executes: /bin/grep \"pattern\" file1.txt file2.txt\n```\n\nThis is like bash `$@` - embedded args come first, runtime args are appended.\n\n### Runfiles Environment\n\nStubs discover runfiles through environment variables or automatic detection:\n\n```bash\n# Manifest-based (file maps runfiles paths to absolute paths)\nRUNFILES_MANIFEST_FILE=/path/to/manifest.txt ./stub\n\n# Directory-based (simple directory layout)\nRUNFILES_DIR=/path/to/runfiles ./stub\n\n# Automatic fallback (discovers \u003cstub\u003e.runfiles/ directory)\n./stub  # Looks for ./stub.runfiles/ automatically\n```\n\n#### Environment Variable Export\n\nBy default (`--export-runfiles-env=true`), stubs export runfiles environment variables to the child process:\n\n```bash\n# Create stub with export enabled (default)\nfinalize-stub --template template --transform 0 --output stub -- tool\n\n# Child process receives:\n#   RUNFILES_MANIFEST_FILE (if manifest-based)\n#   RUNFILES_DIR (if directory-based or fallback)\n#   JAVA_RUNFILES (same as RUNFILES_DIR)\n```\n\nThis allows child processes to use Bazel's runfiles libraries without manual environment setup.\n\nTo disable export and keep the environment unchanged:\n\n```bash\n# Create stub with export disabled\nfinalize-stub --template template --export-runfiles-env=false --output stub -- tool\n\n# Child process inherits parent environment unchanged\n```\n\n## Building from Source\n\n### Prerequisites\n\n- Rust toolchain (stable)\n- For cross-compilation: platform toolchains (mingw-w64 for Windows, etc.)\n\n### Build All Binaries\n\n```bash\n# Linux templates\ncd runfiles-stub\ncargo build --release --target x86_64-unknown-linux-gnu\ncargo build --release --target aarch64-unknown-linux-gnu\n\n# macOS templates\ncargo build --release --target x86_64-apple-darwin\ncargo build --release --target aarch64-apple-darwin\n\n# Windows template\ncargo build --release --target x86_64-pc-windows-gnu\n\n# Finalizers\ncd ../finalize-stub\ncargo build --release --target x86_64-unknown-linux-musl\ncargo build --release --target aarch64-unknown-linux-musl\ncargo build --release --target x86_64-apple-darwin\ncargo build --release --target aarch64-apple-darwin\ncargo build --release --target x86_64-pc-windows-gnu\n```\n\nSee `.github/workflows/release.yml` for the complete build matrix.\n\n### Running Integration Tests\n\nThe `integration-tests/` directory contains a comprehensive test suite:\n\n```bash\n# Build the test binaries\ncd integration-tests\ncargo build --release\n\n# Run tests (requires template and finalizer binaries)\n./target/release/test-runner \\\n  --template ../runfiles-stub/target/release/runfiles-stub \\\n  --finalizer ../finalize-stub/target/release/finalize-stub \\\n  --test-binaries ./target/release\n```\n\n## Architecture Details\n\n### Platform Implementations\n\n| Platform | Entry Point | API | Process Execution | Path Separator |\n|----------|-------------|-----|-------------------|----------------|\n| **Linux** | Custom `_start` | Raw syscalls (no libc) | `execve` syscall | `/` |\n| **macOS** | Standard `main` | libSystem functions | `execve` function | `/` |\n| **Windows** | Standard `main` | Win32 API (UTF-16) | `CreateProcessW` | `\\` |\n\n### Runfiles Path Handling\n\n**Input** (embedded arguments): Always Unix-style forward slashes\n```\nmy_workspace/bin/tool\ndata/input.txt\n```\n\n**Output** (after runfiles resolution): Platform-native\n```\nLinux/macOS:  /absolute/path/to/tool\nWindows:      C:\\absolute\\path\\to\\tool\n```\n\nThe Windows implementation automatically converts `/` to `\\`.\n\n### Binary Size Breakdown\n\nSizes vary by platform due to different linking requirements:\n\n- **x86_64 Linux**: ~10KB (fully static, no libc)\n- **aarch64 Linux**: ~67KB (static, larger due to alignment and number of instructions)\n- **x86_64 macOS**: ~13KB (links libSystem)\n- **aarch64 macOS**: ~49KB (links libSystem, ARM64)\n- **x86_64 Windows**: ~22KB (links kernel32.dll)\n\n## Use Cases for Bazel Rules\n\n### 1. Tool Wrappers\n\nCreate consistent wrappers for tools that need runfiles.\n\n```python\nload(\"@hermetic_launcher//launcher:launcher_binary.bzl\", \"launcher_binary\")\n\n# Simple wrapper that invokes a tool with arguments\nlauncher_binary(\n    name = \"buildozer_version_command\",\n    entrypoint = \"@buildozer\",\n    embedded_args = [\"--version\"],\n)\n\n# Wrapper with data dependencies and path resolution\nlauncher_binary(\n    name = \"hash_file\",\n    entrypoint = \"@openssl\",\n    embedded_args = [\n        \"dgst\",\n        \"-sha256\",\n        \"$(rlocationpath :file_to_hash.txt)\",  # Auto-detected for transformation\n    ],\n    data = [\":file_to_hash.txt\"],\n)\n```\n\n**Key features:**\n- **Automatic path transformation**: Arguments matching `$(rlocationpath ...)` are automatically resolved through runfiles\n- **Runtime argument forwarding**: Additional arguments passed at runtime are appended\n  ```bash\n  bazel run //:hash_file -- --some-extra-flag\n  # Executes: openssl dgst -sha256 /resolved/path/to/file.txt --some-extra-flag\n  ```\n- **Cross-platform**: The same BUILD file works on Linux, macOS, and Windows\n\n### 2. Test Runners\n\nWrap test executables with their data dependencies:\n\n```python\nlauncher_binary(\n    name = \"integration_test\",\n    entrypoint = \"//test:runner\",\n    embedded_args = [\n        \"$(rlocationpath //test/data:fixtures.json)\",\n        \"--verbose\",\n    ],\n    data = [\"//test/data:fixtures.json\"],\n)\n```\n\n### 3. Interpreted Language Binaries\n\nCreate native binaries for interpreted languages like Python, Ruby, or Node.js.\nIn those cases, you tend to have an interpreter binary that is not aware of runfiles and is generic for every binary of that language and a script that is executed on startup.\nNeither are good to be used as the \"executable\" file of a Bazel \"*_binary\" rule, so hermetic-launcher can be used to invoke the interpreter with a path to the script entrypoint.\n\n```python\n# Python script wrapped as a native binary\nlauncher_binary(\n    name = \"my_python_tool\",\n    entrypoint = \"@python_3_11//:python\",\n    embedded_args = [\n        \"$(rlocationpath //tools:script.py)\",\n    ],\n    data = [\"//tools:script.py\"],\n    visibility = [\"//visibility:public\"],\n)\n```\n\nThis creates a native executable that:\n1. Resolves the Python interpreter through runfiles\n2. Resolves the script path through runfiles\n3. Executes: `python /resolved/path/to/script.py`\n4. Works identically on Linux, macOS, and Windows\n\nThe launcher is tiny (10-68KB) regardless of the script size.\n\n## Advanced Usage for Rule Authors\n\nFor rule authors who need more control than `launcher_binary` provides, the `launcher` struct in `@hermetic_launcher//launcher:lib.bzl` offers a lower-level API to construct custom launchers within your own rule implementations.\n\n### When to Use the Launcher Struct\n\nUse the `launcher` struct when you need to:\n- Dynamically determine arguments based on rule context\n- Integrate launcher generation into complex custom rules\n- Build launchers with `cfg = \"exec\"` for tools that run during the build\n- Have fine-grained control over which arguments are transformed\n\nUse `launcher_binary` when you can express your launcher declaratively with static attributes.\n\n### API Reference\n\n```python\nload(\"@hermetic_launcher//launcher:lib.bzl\", \"launcher\")\n```\n\nThe `launcher` struct provides these functions:\n\n#### `launcher.args_from_entrypoint(executable_file)`\n\nInitialize embedded args from an entrypoint executable file. Returns `(embedded_args, transformed_args)` with the entrypoint as the first argument.\n\n```python\nembedded_args, transformed_args = launcher.args_from_entrypoint(\n    executable_file = ctx.executable.tool,\n)\n# Returns: ([\"_main/path/to/tool\"], [\"0\"])\n```\n\n#### `launcher.append_runfile(file, embedded_args, transformed_args)`\n\nAdd a file that needs runfiles path resolution at runtime. Automatically marks it for transformation.\n\n```python\nembedded_args, transformed_args = launcher.append_runfile(\n    file = ctx.file.config,\n    embedded_args = embedded_args,\n    transformed_args = transformed_args,\n)\n```\n\n#### `launcher.append_embedded_arg(arg, embedded_args, transformed_args)`\n\nAdd a literal argument that will NOT be transformed through runfiles.\n\n```python\nembedded_args, transformed_args = launcher.append_embedded_arg(\n    arg = \"--verbose\",\n    embedded_args = embedded_args,\n    transformed_args = transformed_args,\n)\n```\n\n#### `launcher.append_raw_transformed_arg(arg, embedded_args, transformed_args)`\n\nAdd an argument that WILL be transformed through runfiles, but without converting a File object first.\n\n```python\nembedded_args, transformed_args = launcher.append_raw_transformed_arg(\n    arg = \"my_workspace/data/file.txt\",\n    embedded_args = embedded_args,\n    transformed_args = transformed_args,\n)\n```\n\n#### `launcher.compile_stub(ctx, embedded_args, transformed_args, output_file, cfg, template_exec_group)`\n\nCompile the final launcher stub binary.\n\n```python\nlauncher.compile_stub(\n    ctx = ctx,\n    embedded_args = embedded_args,\n    transformed_args = transformed_args,\n    output_file = exe,\n    cfg = \"target\",  # or \"exec\" for build-time tools\n    template_exec_group = None,  # or exec group name for exec cfg\n)\n```\n\n**Parameters:**\n- `cfg`: `\"target\"` (default) for binaries that run on the target platform, or `\"exec\"` for tools that run during the build\n- `template_exec_group`: Name of the exec group when using `cfg = \"exec\"` (e.g., `\"host\"`)\n\n#### `launcher.to_rlocation_path(file)`\n\nConvert a File object to its rlocation path string.\n\n```python\npath = launcher.to_rlocation_path(ctx.file.data)\n# Returns: \"_main/path/to/data\" or \"external_repo/path/to/data\"\n```\n\n### Required Toolchains\n\nWhen using the `launcher` struct in your rules, declare the appropriate toolchains:\n\n```python\n# For target platform launchers (cfg = \"target\")\ntoolchains = [\n    launcher.finalizer_toolchain_type,\n    launcher.template_toolchain_type,\n]\n\n# For exec platform launchers (cfg = \"exec\")\ntoolchains = [\n    launcher.finalizer_toolchain_type,\n    launcher.template_exec_toolchain_type,\n]\n```\n\n## FAQ\n\n**Q: Why not just use shell scripts?**\nA: Shell scripts aren't cross-platform. Bash doesn't work on Windows, batch files don't work on Unix. Native launchers work everywhere.\n\n**Q: How is this different from other Bazel runfiles libraries?**\nA: This creates standalone binaries, not library code. The launcher is your program's entry point.\n\n**Q: Can I use this outside Bazel?**\nA: Yes! As long as you set `RUNFILES_DIR`, `RUNFILES_MANIFEST_FILE`, or you create `\u003cexecutable\u003e.runfiles`, launchers work anywhere.\n\n**Q: Why are the binaries different sizes?**\nA: Platform differences. Linux can be fully static (smaller), macOS requires libSystem, Windows needs DLLs, ARM architectures need more instructions than x86 and have stricter alignment requirements.\n\n**Q: Is the finalizer deterministic?**\nA: Yes! The same inputs produce byte-identical outputs regardless of which platform you run the finalizer on. This is tested in CI.\n\n## License\n\nMIT License - See [LICENSE](LICENSE) file for details.\n\n## Contributing\n\nContributions welcome! This project demonstrates:\n- Cross-platform `no_std` Rust development\n- Platform-specific system call interfaces\n- Binary template patching techniques\n- Bazel runfiles protocol implementation\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmalt3%2Fhermetic-launcher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmalt3%2Fhermetic-launcher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmalt3%2Fhermetic-launcher/lists"}