From 7c0c92a5facf1f3eb9ab7a2beeb2b7ba6e19be1b Mon Sep 17 00:00:00 2001 From: Denbeigh Stevens Date: Tue, 16 Jun 2026 21:09:11 +0000 Subject: [PATCH 1/6] claude-generated: initial integration of rules_erlang relocatable refactor --- private/elixir_build.bzl | 34 +++++++++++----------- private/elixir_release.bzl | 10 ++----- private/elixir_source_build.bzl | 8 ++++-- private/elixir_sys_config.bzl | 18 ++++++++---- private/elixir_toolchain.bzl | 45 +++++++++++++++++++----------- private/eval_config.bzl | 15 ++++++---- private/protocol_consolidation.bzl | 13 +++++---- test/escript_test.sh | 15 +++++----- tools/elixir_vars.bzl | 23 +++++++++------ 9 files changed, 108 insertions(+), 73 deletions(-) diff --git a/private/elixir_build.bzl b/private/elixir_build.bzl index 8f06152..8e9f257 100644 --- a/private/elixir_build.bzl +++ b/private/elixir_build.bzl @@ -10,6 +10,10 @@ load( "@rules_erlang//private:erlang_build.bzl", "OtpInfo", ) +load( + "@rules_erlang//tools:erlang_toolchain.bzl", + "erlang_home", +) ElixirInfo = provider( doc = "A Home directory of a built Elixir", @@ -21,24 +25,20 @@ ElixirInfo = provider( ) def otp_install_snippet(otp_info): - """Generate shell snippet to extract OTP release tar, like maybe_install_erlang.""" - if otp_info.release_dir_tar == None: + """Shell snippet exporting ERL_ROOTDIR for a relocatable OTP, like maybe_install_erlang. + + Empty for an external/host erlang (erlang_home is an absolute path). Build-action + context only -- all callers here are run_shell build actions, so the release dir is + addressed by its (execroot-relative) path. + """ + if otp_info.release_dir == None: return "" - return """\ -mkdir -p $(dirname "{install_path}") -if mkdir "{install_path}"; then - tar --extract --no-same-owner \\ - --directory "{install_path}" \\ - --file {release_tar} -fi""".format( - release_tar = otp_info.release_dir_tar.path, - install_path = otp_info.install_path, - ) + return 'export ERL_ROOTDIR="$PWD/{}"'.format(otp_info.release_dir.path) def otp_runfiles(ctx, otp_info): """Build runfiles depset for OTP, like erlang_dirs.""" - if otp_info.release_dir_tar != None: - return ctx.runfiles([otp_info.release_dir_tar, otp_info.version_file]) + if otp_info.release_dir != None: + return ctx.runfiles([otp_info.release_dir, otp_info.version_file]) else: return ctx.runfiles([otp_info.version_file]) @@ -86,7 +86,7 @@ cp -r bin $ABS_RELEASE_DIR/ cp -r lib $ABS_RELEASE_DIR/ """.format( maybe_install_erlang = otp_install_snippet(otp_info), - erlang_home = otp_info.erlang_home, + erlang_home = erlang_home(otp_info), release_path = release_dir.path, source_files = " ".join([f.path for f in ctx.files.srcs]), first_source_file = ctx.files.srcs[0].path if ctx.files.srcs else "", @@ -111,7 +111,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} "{elixir_home}"/bin/iex --version > {version_file} """.format( maybe_install_erlang = otp_install_snippet(otp_info), - erlang_home = otp_info.erlang_home, + erlang_home = erlang_home(otp_info), elixir_home = release_dir.path, version_file = version_file.path, ), @@ -164,7 +164,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} "{elixir_home}"/bin/iex --version > {version_file} """.format( maybe_install_erlang = otp_install_snippet(otp_info), - erlang_home = otp_info.erlang_home, + erlang_home = erlang_home(otp_info), elixir_home = elixir_home, version_file = version_file.path, ), diff --git a/private/elixir_release.bzl b/private/elixir_release.bzl index 5966f40..9ca3ed2 100644 --- a/private/elixir_release.bzl +++ b/private/elixir_release.bzl @@ -213,13 +213,9 @@ else ABS_ELIXIR_HOME=$PWD/{elixir_home} fi -if [[ "{erlang_home}" == /* ]]; then - ABS_ERLANG_HOME="{erlang_home}" -else - ABS_ERLANG_HOME=$PWD/{erlang_home} -fi - -export PATH="$ABS_ELIXIR_HOME/bin:$ABS_ERLANG_HOME/bin:$PATH" +# erlang_home is either an absolute host path (external erlang) or the literal +# "$ERL_ROOTDIR" (relocatable OTP), already exported above by maybe_install_erlang. +export PATH="$ABS_ELIXIR_HOME/bin:{erlang_home}/bin:$PATH" # Create output directory mkdir -p "{release_dir}" diff --git a/private/elixir_source_build.bzl b/private/elixir_source_build.bzl index 6756cab..88f9813 100644 --- a/private/elixir_source_build.bzl +++ b/private/elixir_source_build.bzl @@ -2,6 +2,10 @@ load( "@rules_erlang//private:erlang_build.bzl", "OtpInfo", ) +load( + "@rules_erlang//tools:erlang_toolchain.bzl", + "erlang_home", +) load( ":elixir_build.bzl", "ElixirInfo", @@ -53,7 +57,7 @@ cp -r bin $ABS_RELEASE_DIR/ cp -r lib $ABS_RELEASE_DIR/ """.format( maybe_install_erlang = otp_install_snippet(otp_info), - erlang_home = otp_info.erlang_home, + erlang_home = erlang_home(otp_info), release_path = release_dir.path, source_files = " ".join([f.path for f in ctx.files.srcs]), first_source_file = ctx.files.srcs[0].path if ctx.files.srcs else "", @@ -78,7 +82,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} "{elixir_home}"/bin/iex --version > {version_file} """.format( maybe_install_erlang = otp_install_snippet(otp_info), - erlang_home = otp_info.erlang_home, + erlang_home = erlang_home(otp_info), elixir_home = release_dir.path, version_file = version_file.path, ), diff --git a/private/elixir_sys_config.bzl b/private/elixir_sys_config.bzl index d732b21..bb34952 100644 --- a/private/elixir_sys_config.bzl +++ b/private/elixir_sys_config.bzl @@ -24,6 +24,7 @@ extend this to also inject specs for these into `sys.config`. load("@rules_erlang//:erlang_app_info.bzl", "ErlangAppInfo") load("//:elixir_app_info.bzl", "ElixirAppInfo") +load(":elixir_toolchain.bzl", "erlang_dirs", "maybe_install_erlang") load(":release_info.bzl", "ReleaseInfo", "get_release_info") load( ":config_inference.bzl", @@ -99,9 +100,8 @@ def _elixir_sys_config_impl(ctx): # Get the sys_config_builder tool builder = ctx.executable._sys_config_builder - # Get the toolchain for Erlang paths - toolchain = ctx.toolchains["//:toolchain_type"] - erlang_toolchain = toolchain.otpinfo + # Get the Erlang/OTP dirs (handles relocatable OTP via $ERL_ROOTDIR) + (erlang_home, _, erlang_runfiles) = erlang_dirs(ctx) # Create a wrapper script that sets up the PATH for escript wrapper = ctx.actions.declare_file("{}_sys_config_wrapper.sh".format(ctx.attr.name)) @@ -109,6 +109,8 @@ def _elixir_sys_config_impl(ctx): wrapper_content = """#!/bin/bash set -euo pipefail +{erl_rootdir_setup} + # Set up Erlang/OTP paths export PATH="{erlang_home}/bin:$PATH" @@ -116,7 +118,8 @@ export PATH="{erlang_home}/bin:$PATH" RUNTIME_PATH=$(cat "{runtime_config_path_file}") exec "{tool_path}" "$@" --runtime-path "$RUNTIME_PATH" """.format( - erlang_home = erlang_toolchain.erlang_home, + erl_rootdir_setup = maybe_install_erlang(ctx), + erlang_home = erlang_home, runtime_config_path_file = runtime_config_path.path, tool_path = builder.path, ) @@ -124,13 +127,16 @@ exec "{tool_path}" "$@" --runtime-path "$RUNTIME_PATH" wrapper_content = """#!/bin/bash set -euo pipefail +{erl_rootdir_setup} + # Set up Erlang/OTP paths export PATH="{erlang_home}/bin:$PATH" # Run the escript with all arguments exec "{tool_path}" "$@" """.format( - erlang_home = erlang_toolchain.erlang_home, + erl_rootdir_setup = maybe_install_erlang(ctx), + erlang_home = erlang_home, tool_path = builder.path, ) @@ -154,7 +160,7 @@ exec "{tool_path}" "$@" ctx.actions.run( executable = wrapper, arguments = [args], - inputs = depset(inputs), + inputs = depset(inputs, transitive = [erlang_runfiles.files]), outputs = outputs, mnemonic = "SysConfig", progress_message = "Generating sys.config for {}".format(ctx.label), diff --git a/private/elixir_toolchain.bzl b/private/elixir_toolchain.bzl index 7138670..1650816 100644 --- a/private/elixir_toolchain.bzl +++ b/private/elixir_toolchain.bzl @@ -2,6 +2,10 @@ load( "@rules_erlang//private:erlang_build.bzl", "OtpInfo", ) +load( + "@rules_erlang//tools:erlang_toolchain.bzl", + "erlang_home", +) load( ":elixir_build.bzl", "ElixirInfo", @@ -30,16 +34,16 @@ def _build_info(ctx): def erlang_dirs(ctx): info = _build_info(ctx) - if info.release_dir_tar != None: + if info.release_dir != None: runfiles = ctx.runfiles([ - info.release_dir_tar, + info.release_dir, info.version_file, ]) else: runfiles = ctx.runfiles([ info.version_file, ]) - return (info.erlang_home, info.release_dir_tar, runfiles) + return (erlang_home(info), info.release_dir, runfiles) def elixir_dirs(ctx, short_path = False): info = ctx.toolchains["//:toolchain_type"].elixirinfo @@ -50,20 +54,29 @@ def elixir_dirs(ctx, short_path = False): return (p, ctx.runfiles([info.release_dir, info.version_file])) def maybe_install_erlang(ctx, short_path = False): + """Shell that exports ERL_ROOTDIR so a templated "$ERL_ROOTDIR"/bin/erl resolves. + + Pairs with erlang_home(): that emits the "$ERL_ROOTDIR" reference (for a + relocatable OTP install), this gives it a value. Empty for an external/host + erlang, which carries an absolute erlang_home and needs no setup. + + Pass short_path = True when the generated script runs from a runfiles tree + (an executable launched by `bazel run`/`bazel test`); leave it False (the + default) when the script runs inside a build action, where cwd is the + execroot. Mirrors erl_rootdir_setup() in + @rules_erlang//tools:erlang_toolchain.bzl. + """ info = _build_info(ctx) - release_dir_tar = info.release_dir_tar - if release_dir_tar == None: + release_dir = info.release_dir + if release_dir == None: return "" - else: + if short_path: return """\ -mkdir -p $(dirname "{install_path}") -if mkdir "{install_path}"; then - tar --extract \\ - --no-same-owner \\ - --directory "{install_path}" \\ - --file {release_tar} +if [ -n "${{TEST_SRCDIR:-}}" ]; then + export ERL_ROOTDIR="$TEST_SRCDIR/$TEST_WORKSPACE/{short_path}" +else + export ERL_ROOTDIR="$PWD/{short_path}" fi\ -""".format( - release_tar = release_dir_tar.short_path if short_path else release_dir_tar.path, - install_path = info.install_path, - ) +""".format(short_path = release_dir.short_path) + else: + return 'export ERL_ROOTDIR="$PWD/{}"'.format(release_dir.path) diff --git a/private/eval_config.bzl b/private/eval_config.bzl index d5d9dfd..2419374 100644 --- a/private/eval_config.bzl +++ b/private/eval_config.bzl @@ -6,13 +6,13 @@ from Elixir config/*.exs files and encoding them as EETF (Erlang External Term F load("//:elixir_app_info.bzl", "ElixirAppInfo") load("@rules_erlang//:util.bzl", "path_join") +load(":elixir_toolchain.bzl", "erlang_dirs", "maybe_install_erlang") def _eval_config_impl(ctx): """Evaluate all Elixir application configurations using the eval_config tool.""" - # Get the toolchain - toolchain = ctx.toolchains["//:toolchain_type"] - erlang_toolchain = toolchain.otpinfo + # Get the Erlang/OTP dirs (handles relocatable OTP via $ERL_ROOTDIR) + (erlang_home, _, erlang_runfiles) = erlang_dirs(ctx) # Determine output file name output_name = ctx.attr.output_name @@ -35,13 +35,16 @@ def _eval_config_impl(ctx): wrapper_content = """#!/bin/bash set -euo pipefail +{erl_rootdir_setup} + # Set up Erlang/OTP paths export PATH="{erlang_home}/bin:$PATH" # Run the escript with all arguments exec "{tool_path}" "$@" """.format( - erlang_home = erlang_toolchain.erlang_home, + erl_rootdir_setup = maybe_install_erlang(ctx), + erlang_home = erlang_home, tool_path = tool.path, ) @@ -93,9 +96,11 @@ exec "{tool_path}" "$@" if ctx.attr.no_debug: args.add("--no-debug") - # Collect inputs - config files, the tool, and any extra inputs + # Collect inputs - config files, the tool, any extra inputs, and the OTP + # release tree artifact (so $ERL_ROOTDIR/bin/erl is staged for relocatable OTP) inputs = depset( direct = ctx.files.config_files + ctx.files.extra_inputs + [tool], + transitive = [erlang_runfiles.files], ) # Determine outputs diff --git a/private/protocol_consolidation.bzl b/private/protocol_consolidation.bzl index ed13866..db425b9 100644 --- a/private/protocol_consolidation.bzl +++ b/private/protocol_consolidation.bzl @@ -6,6 +6,7 @@ at build time rather than runtime. This matches Mix's behavior during releases. load("@rules_erlang//:erlang_app_info.bzl", "ErlangAppInfo") load("//:elixir_app_info.bzl", "ElixirAppInfo") +load(":elixir_toolchain.bzl", "erlang_dirs", "maybe_install_erlang") def _elixir_protocol_consolidation_impl(ctx): """Consolidate protocols from Elixir applications.""" @@ -34,9 +35,8 @@ def _elixir_protocol_consolidation_impl(ctx): # Get the protocol_consolidator tool consolidator = ctx.executable._protocol_consolidator - # Get the toolchain for Erlang paths - toolchain = ctx.toolchains["//:toolchain_type"] - erlang_toolchain = toolchain.otpinfo + # Get the Erlang/OTP dirs (handles relocatable OTP via $ERL_ROOTDIR) + (erlang_home, _, erlang_runfiles) = erlang_dirs(ctx) # Create a wrapper script that sets up the PATH for escript wrapper = ctx.actions.declare_file("{}_consolidator_wrapper.sh".format(ctx.attr.name)) @@ -45,13 +45,16 @@ def _elixir_protocol_consolidation_impl(ctx): wrapper_content = """#!/bin/bash set -euo pipefail +{erl_rootdir_setup} + # Set up Erlang/OTP paths export PATH="{erlang_home}/bin:$PATH" # Run the protocol consolidator escript exec "{tool_path}" "$@" """.format( - erlang_home = erlang_toolchain.erlang_home, + erl_rootdir_setup = maybe_install_erlang(ctx), + erlang_home = erlang_home, tool_path = consolidator.path, ) @@ -73,7 +76,7 @@ exec "{tool_path}" "$@" ctx.actions.run( executable = wrapper, arguments = [args], - inputs = depset(beam_files + [consolidator]), + inputs = depset(beam_files + [consolidator], transitive = [erlang_runfiles.files]), outputs = [output_dir], mnemonic = "ConsolidateProtocols", progress_message = "Consolidating protocols for {}".format(ctx.label), diff --git a/test/escript_test.sh b/test/escript_test.sh index 70371d6..bf64d00 100755 --- a/test/escript_test.sh +++ b/test/escript_test.sh @@ -1,13 +1,14 @@ #! /usr/bin/env bash set -exo pipefail -if [[ -n "$ERLANG_RELEASE_TAR_SHORT_PATH" ]]; then - mkdir -p $(dirname "$OTP_INSTALL_PATH") - if mkdir "$OTP_INSTALL_PATH"; then - tar --extract \ - --no-same-owner \ - --directory "$OTP_INSTALL_PATH" \ - --file "$ERLANG_RELEASE_TAR_SHORT_PATH" +# Relocatable OTP: point ERL_ROOTDIR at the staged release tree artifact so the +# "$ERLANG_HOME" (= "$ERL_ROOTDIR") below resolves. Runfiles/sh_test context, so +# anchor on $TEST_SRCDIR/$TEST_WORKSPACE (or $PWD) using the short_path var. +if [[ -n "${ERLANG_RELEASE_DIR_SHORT_PATH:-}" ]]; then + if [[ -n "${TEST_SRCDIR:-}" ]]; then + export ERL_ROOTDIR="$TEST_SRCDIR/$TEST_WORKSPACE/$ERLANG_RELEASE_DIR_SHORT_PATH" + else + export ERL_ROOTDIR="$PWD/$ERLANG_RELEASE_DIR_SHORT_PATH" fi fi diff --git a/tools/elixir_vars.bzl b/tools/elixir_vars.bzl index 7ad248c..2f17fd6 100644 --- a/tools/elixir_vars.bzl +++ b/tools/elixir_vars.bzl @@ -1,3 +1,8 @@ +load( + "@rules_erlang//tools:erlang_toolchain.bzl", + "erlang_home", +) + ELIXIR_VARS_ENV_MAP = { "OTP_VERSION": "$(OTP_VERSION)", "OTP_VERSION_FILE_PATH": "$(OTP_VERSION_FILE_PATH)", @@ -8,10 +13,13 @@ ELIXIR_VARS_ENV_MAP = { "ELIXIR_VERSION_FILE_SHORT_PATH": "$(ELIXIR_VERSION_FILE_SHORT_PATH)", } +# OTP installs are now relocatable tree artifacts rather than a tar unpacked to a +# fixed path. OTP_INSTALL_PATH and ERLANG_RELEASE_TAR_* are gone; consumers wanting +# to run erl should `export ERL_ROOTDIR="$PWD/$(ERLANG_RELEASE_DIR_PATH)"` (or +# _SHORT_PATH in a runfiles context) before invoking "$(ERLANG_HOME)"/bin/erl. ELIXIR_VARS_ENV_MAP_INTERNAL = ELIXIR_VARS_ENV_MAP | { - "OTP_INSTALL_PATH": "$(OTP_INSTALL_PATH)", - "ERLANG_RELEASE_TAR_PATH": "$(ERLANG_RELEASE_TAR_PATH)", - "ERLANG_RELEASE_TAR_SHORT_PATH": "$(ERLANG_RELEASE_TAR_SHORT_PATH)", + "ERLANG_RELEASE_DIR_PATH": "$(ERLANG_RELEASE_DIR_PATH)", + "ERLANG_RELEASE_DIR_SHORT_PATH": "$(ERLANG_RELEASE_DIR_SHORT_PATH)", } def _impl(ctx): @@ -21,15 +29,14 @@ def _impl(ctx): "OTP_VERSION": otpinfo.version, "OTP_VERSION_FILE_PATH": otpinfo.version_file.path, "OTP_VERSION_FILE_SHORT_PATH": otpinfo.version_file.short_path, - "ERLANG_HOME": otpinfo.erlang_home, + "ERLANG_HOME": erlang_home(otpinfo), "ELIXIR_HOME": elixirinfo.elixir_home, "ELIXIR_VERSION_FILE_PATH": elixirinfo.version_file.path, "ELIXIR_VERSION_FILE_SHORT_PATH": elixirinfo.version_file.short_path, } - if otpinfo.release_dir_tar != None: - vars["OTP_INSTALL_PATH"] = otpinfo.install_path - vars["ERLANG_RELEASE_TAR_PATH"] = otpinfo.release_dir_tar.path - vars["ERLANG_RELEASE_TAR_SHORT_PATH"] = otpinfo.release_dir_tar.short_path + if otpinfo.release_dir != None: + vars["ERLANG_RELEASE_DIR_PATH"] = otpinfo.release_dir.path + vars["ERLANG_RELEASE_DIR_SHORT_PATH"] = otpinfo.release_dir.short_path return [ platform_common.TemplateVariableInfo(vars), From c02b7c0c76387926c298aaa07c33699cd963ccd0 Mon Sep 17 00:00:00 2001 From: Denbeigh Stevens Date: Tue, 16 Jun 2026 21:23:44 +0000 Subject: [PATCH 2/6] rename maybe_install_erlang -> erl_rootdir_setup --- private/elixir_build.bzl | 14 +++++++------- private/elixir_bytecode.bzl | 6 +++--- private/elixir_release.bzl | 8 ++++---- private/elixir_release_bundle.bzl | 6 +++--- private/elixir_source_build.bzl | 8 ++++---- private/elixir_sys_config.bzl | 6 +++--- private/elixir_toolchain.bzl | 2 +- private/eval_config.bzl | 4 ++-- private/ex_unit_test.bzl | 6 +++--- private/iex_eval.bzl | 6 +++--- private/mix_library.bzl | 6 +++--- private/mix_release.bzl | 6 +++--- private/mix_test.bzl | 6 +++--- private/protocol_consolidation.bzl | 4 ++-- 14 files changed, 44 insertions(+), 44 deletions(-) diff --git a/private/elixir_build.bzl b/private/elixir_build.bzl index 8e9f257..e792174 100644 --- a/private/elixir_build.bzl +++ b/private/elixir_build.bzl @@ -25,7 +25,7 @@ ElixirInfo = provider( ) def otp_install_snippet(otp_info): - """Shell snippet exporting ERL_ROOTDIR for a relocatable OTP, like maybe_install_erlang. + """Shell snippet exporting ERL_ROOTDIR for a relocatable OTP, like erl_rootdir_setup. Empty for an external/host erlang (erlang_home is an absolute path). Build-action context only -- all callers here are run_shell build actions, so the release dir is @@ -57,7 +57,7 @@ def _elixir_build_impl(ctx): outputs = [release_dir], command = """set -euo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} export PATH="{erlang_home}"/bin:${{PATH}} @@ -85,7 +85,7 @@ make cp -r bin $ABS_RELEASE_DIR/ cp -r lib $ABS_RELEASE_DIR/ """.format( - maybe_install_erlang = otp_install_snippet(otp_info), + erl_rootdir_setup = otp_install_snippet(otp_info), erlang_home = erlang_home(otp_info), release_path = release_dir.path, source_files = " ".join([f.path for f in ctx.files.srcs]), @@ -104,13 +104,13 @@ cp -r lib $ABS_RELEASE_DIR/ outputs = [version_file], command = """set -euo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} export PATH="{erlang_home}"/bin:${{PATH}} "{elixir_home}"/bin/iex --version > {version_file} """.format( - maybe_install_erlang = otp_install_snippet(otp_info), + erl_rootdir_setup = otp_install_snippet(otp_info), erlang_home = erlang_home(otp_info), elixir_home = release_dir.path, version_file = version_file.path, @@ -157,13 +157,13 @@ def _elixir_external_impl(ctx): outputs = [version_file], command = """set -euo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} export PATH="{erlang_home}"/bin:${{PATH}} "{elixir_home}"/bin/iex --version > {version_file} """.format( - maybe_install_erlang = otp_install_snippet(otp_info), + erl_rootdir_setup = otp_install_snippet(otp_info), erlang_home = erlang_home(otp_info), elixir_home = elixir_home, version_file = version_file.path, diff --git a/private/elixir_bytecode.bzl b/private/elixir_bytecode.bzl index 6d09fa6..edf93b7 100644 --- a/private/elixir_bytecode.bzl +++ b/private/elixir_bytecode.bzl @@ -6,7 +6,7 @@ load( "//private:elixir_toolchain.bzl", "elixir_dirs", "erlang_dirs", - "maybe_install_erlang", + "erl_rootdir_setup", ) def unique_dirnames(files): @@ -57,7 +57,7 @@ def _impl(ctx): script = """set -euo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} if [[ "{elixir_home}" == /* ]]; then ABS_ELIXIR_HOME="{elixir_home}" else @@ -82,7 +82,7 @@ ${{ABS_ELIXIR_HOME}}/bin/elixirc \\ {elixirc_opts} \\ {srcs} """.format( - maybe_install_erlang = maybe_install_erlang(ctx), + erl_rootdir_setup = erl_rootdir_setup(ctx), erlang_home = erlang_home, elixir_home = elixir_home, erl_libs_path = erl_libs_path, diff --git a/private/elixir_release.bzl b/private/elixir_release.bzl index 9ca3ed2..4cea1a5 100644 --- a/private/elixir_release.bzl +++ b/private/elixir_release.bzl @@ -94,7 +94,7 @@ load( "//private:elixir_toolchain.bzl", "elixir_dirs", "erlang_dirs", - "maybe_install_erlang", + "erl_rootdir_setup", ) def _elixir_release_impl(ctx): @@ -204,7 +204,7 @@ cp "{manifest_file}" "{release_dir}/{release_name}.manifest" script_content = """#!/bin/bash set -euo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} # Set up paths if [[ "{elixir_home}" == /* ]]; then @@ -214,7 +214,7 @@ else fi # erlang_home is either an absolute host path (external erlang) or the literal -# "$ERL_ROOTDIR" (relocatable OTP), already exported above by maybe_install_erlang. +# "$ERL_ROOTDIR" (relocatable OTP), already exported above by erl_rootdir_setup. export PATH="$ABS_ELIXIR_HOME/bin:{erlang_home}/bin:$PATH" # Create output directory @@ -246,7 +246,7 @@ ARGS+=("--version" "{release_version}") chmod -w "{release_dir}/{release_name}.script" chmod -w "{release_dir}/{release_name}.boot" """.format( - maybe_install_erlang = maybe_install_erlang(ctx), + erl_rootdir_setup = erl_rootdir_setup(ctx), elixir_home = elixir_home, erlang_home = erlang_home, processor = processor.path, diff --git a/private/elixir_release_bundle.bzl b/private/elixir_release_bundle.bzl index 2232b63..18682fb 100644 --- a/private/elixir_release_bundle.bzl +++ b/private/elixir_release_bundle.bzl @@ -91,7 +91,7 @@ load( "//private:elixir_toolchain.bzl", "elixir_dirs", "erlang_dirs", - "maybe_install_erlang", + "erl_rootdir_setup", ) def _impl(ctx): @@ -299,7 +299,7 @@ chmod +x "$BUNDLE_DIR/bin/$RELEASE_NAME" # Build the complete bundle creation script script = """set -euo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} BUNDLE_DIR="{bundle_dir}" MANIFEST_FILE="{manifest_file}" @@ -353,7 +353,7 @@ echo " $BUNDLE_DIR/bin/$RELEASE_NAME start # Start as daemon" echo " $BUNDLE_DIR/bin/$RELEASE_NAME console # Start with console" echo " $BUNDLE_DIR/bin/$RELEASE_NAME foreground # Start in foreground" """.format( - maybe_install_erlang = maybe_install_erlang(ctx), + erl_rootdir_setup = erl_rootdir_setup(ctx), bundle_dir = bundle_dir.path, manifest_file = erlang_release_info.manifest_file.path, release_name = release_name, diff --git a/private/elixir_source_build.bzl b/private/elixir_source_build.bzl index 88f9813..6bef82a 100644 --- a/private/elixir_source_build.bzl +++ b/private/elixir_source_build.bzl @@ -28,7 +28,7 @@ def _elixir_source_build_impl(ctx): outputs = [release_dir], command = """set -euo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} export PATH="{erlang_home}"/bin:${{PATH}} @@ -56,7 +56,7 @@ make cp -r bin $ABS_RELEASE_DIR/ cp -r lib $ABS_RELEASE_DIR/ """.format( - maybe_install_erlang = otp_install_snippet(otp_info), + erl_rootdir_setup = otp_install_snippet(otp_info), erlang_home = erlang_home(otp_info), release_path = release_dir.path, source_files = " ".join([f.path for f in ctx.files.srcs]), @@ -75,13 +75,13 @@ cp -r lib $ABS_RELEASE_DIR/ outputs = [version_file], command = """set -euo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} export PATH="{erlang_home}"/bin:${{PATH}} "{elixir_home}"/bin/iex --version > {version_file} """.format( - maybe_install_erlang = otp_install_snippet(otp_info), + erl_rootdir_setup = otp_install_snippet(otp_info), erlang_home = erlang_home(otp_info), elixir_home = release_dir.path, version_file = version_file.path, diff --git a/private/elixir_sys_config.bzl b/private/elixir_sys_config.bzl index bb34952..001745d 100644 --- a/private/elixir_sys_config.bzl +++ b/private/elixir_sys_config.bzl @@ -24,7 +24,7 @@ extend this to also inject specs for these into `sys.config`. load("@rules_erlang//:erlang_app_info.bzl", "ErlangAppInfo") load("//:elixir_app_info.bzl", "ElixirAppInfo") -load(":elixir_toolchain.bzl", "erlang_dirs", "maybe_install_erlang") +load(":elixir_toolchain.bzl", "erlang_dirs", "erl_rootdir_setup") load(":release_info.bzl", "ReleaseInfo", "get_release_info") load( ":config_inference.bzl", @@ -118,7 +118,7 @@ export PATH="{erlang_home}/bin:$PATH" RUNTIME_PATH=$(cat "{runtime_config_path_file}") exec "{tool_path}" "$@" --runtime-path "$RUNTIME_PATH" """.format( - erl_rootdir_setup = maybe_install_erlang(ctx), + erl_rootdir_setup = erl_rootdir_setup(ctx), erlang_home = erlang_home, runtime_config_path_file = runtime_config_path.path, tool_path = builder.path, @@ -135,7 +135,7 @@ export PATH="{erlang_home}/bin:$PATH" # Run the escript with all arguments exec "{tool_path}" "$@" """.format( - erl_rootdir_setup = maybe_install_erlang(ctx), + erl_rootdir_setup = erl_rootdir_setup(ctx), erlang_home = erlang_home, tool_path = builder.path, ) diff --git a/private/elixir_toolchain.bzl b/private/elixir_toolchain.bzl index 1650816..a04019c 100644 --- a/private/elixir_toolchain.bzl +++ b/private/elixir_toolchain.bzl @@ -53,7 +53,7 @@ def elixir_dirs(ctx, short_path = False): p = info.release_dir.short_path if short_path else info.release_dir.path return (p, ctx.runfiles([info.release_dir, info.version_file])) -def maybe_install_erlang(ctx, short_path = False): +def erl_rootdir_setup(ctx, short_path = False): """Shell that exports ERL_ROOTDIR so a templated "$ERL_ROOTDIR"/bin/erl resolves. Pairs with erlang_home(): that emits the "$ERL_ROOTDIR" reference (for a diff --git a/private/eval_config.bzl b/private/eval_config.bzl index 2419374..7efeefa 100644 --- a/private/eval_config.bzl +++ b/private/eval_config.bzl @@ -6,7 +6,7 @@ from Elixir config/*.exs files and encoding them as EETF (Erlang External Term F load("//:elixir_app_info.bzl", "ElixirAppInfo") load("@rules_erlang//:util.bzl", "path_join") -load(":elixir_toolchain.bzl", "erlang_dirs", "maybe_install_erlang") +load(":elixir_toolchain.bzl", "erlang_dirs", "erl_rootdir_setup") def _eval_config_impl(ctx): """Evaluate all Elixir application configurations using the eval_config tool.""" @@ -43,7 +43,7 @@ export PATH="{erlang_home}/bin:$PATH" # Run the escript with all arguments exec "{tool_path}" "$@" """.format( - erl_rootdir_setup = maybe_install_erlang(ctx), + erl_rootdir_setup = erl_rootdir_setup(ctx), erlang_home = erlang_home, tool_path = tool.path, ) diff --git a/private/ex_unit_test.bzl b/private/ex_unit_test.bzl index de90212..d174dd3 100644 --- a/private/ex_unit_test.bzl +++ b/private/ex_unit_test.bzl @@ -16,7 +16,7 @@ load( "//private:elixir_toolchain.bzl", "elixir_dirs", "erlang_dirs", - "maybe_install_erlang", + "erl_rootdir_setup", ) def _package_relative_path(ctx, p): @@ -62,7 +62,7 @@ def _impl(ctx): #!/usr/bin/env bash set -eo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} if [[ "{elixir_home}" == /* ]]; then ABS_ELIXIR_HOME="{elixir_home}" else @@ -91,7 +91,7 @@ tail -n 4 test.log | grep -E --silent "0 failure" tail -n 4 test.log | grep -E --silent "[0-9] test" rm test.log """.format( - maybe_install_erlang = maybe_install_erlang(ctx, short_path = True), + erl_rootdir_setup = erl_rootdir_setup(ctx, short_path = True), erlang_home = erlang_home, elixir_home = elixir_home, copy_srcs_and_data_commands = "\n".join(copy_srcs_and_data_commands), diff --git a/private/iex_eval.bzl b/private/iex_eval.bzl index 9833654..621c634 100644 --- a/private/iex_eval.bzl +++ b/private/iex_eval.bzl @@ -2,7 +2,7 @@ load( ":elixir_toolchain.bzl", "elixir_dirs", "erlang_dirs", - "maybe_install_erlang", + "erl_rootdir_setup", ) def _impl(ctx): @@ -15,7 +15,7 @@ def _impl(ctx): (elixir_home, elixir_runfiles) = elixir_dirs(ctx) script = """set -euo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} if [[ "{elixir_home}" == /* ]]; then ABS_ELIXIR_HOME="{elixir_home}" else @@ -26,7 +26,7 @@ export SRCS="{srcs}" export OUTS="{outs}" ${{ABS_ELIXIR_HOME}}/bin/iex --eval "$1" """.format( - maybe_install_erlang = maybe_install_erlang(ctx), + erl_rootdir_setup = erl_rootdir_setup(ctx), erlang_home = erlang_home, elixir_home = elixir_home, srcs = ctx.configuration.host_path_separator.join([src.path for src in ctx.files.srcs]), diff --git a/private/mix_library.bzl b/private/mix_library.bzl index 74696aa..5326cf9 100644 --- a/private/mix_library.bzl +++ b/private/mix_library.bzl @@ -6,7 +6,7 @@ load( "//private:elixir_toolchain.bzl", "elixir_dirs", "erlang_dirs", - "maybe_install_erlang", + "erl_rootdir_setup", ) load("//private:mix_info.bzl", "MixProjectInfo") load("//private:beam_transitions.bzl", "platform_independent_transition") @@ -104,7 +104,7 @@ def _mix_compile_impl(ctx): # that's just a way for elixir to expose and interface to erlang. script = """set -euo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} if [[ "{elixir_home}" == /* ]]; then ABS_ELIXIR_HOME="{elixir_home}" else @@ -170,7 +170,7 @@ mkdir -p "$ABS_OUT_DIR" cp _output/{mix_env}/lib/{app_name}/ebin/*.beam _output/{mix_env}/lib/{app_name}/ebin/*.app "$ABS_OUT_DIR/" """.format( - maybe_install_erlang = maybe_install_erlang(ctx), + erl_rootdir_setup = erl_rootdir_setup(ctx), app_name = ctx.attr.app_name, # app_file_out = app_file.path, erlang_home = erlang_home, diff --git a/private/mix_release.bzl b/private/mix_release.bzl index a94af9a..d7b9532 100644 --- a/private/mix_release.bzl +++ b/private/mix_release.bzl @@ -8,7 +8,7 @@ load( "//private:elixir_toolchain.bzl", "elixir_dirs", "erlang_dirs", - "maybe_install_erlang", + "erl_rootdir_setup", ) load("//private:mix_info.bzl", "MixProjectInfo") load(":release_info.bzl", "ReleaseInfo", "create_release_info") @@ -76,7 +76,7 @@ def _mix_release_impl(ctx): version_file = ctx.actions.declare_file("{}_version.txt".format(ctx.label.name)) script = """set -euo pipefail -{maybe_install_erlang} +{erl_rootdir_setup} if [[ "{elixir_home}" == /* ]]; then ABS_ELIXIR_HOME="{elixir_home}" else @@ -135,7 +135,7 @@ cd - mv $OUTPUT_DIR/{mix_env}/rel/{app_name} {output_file} awk '{{print $2}}' {output_file}/{app_name}/releases/start_erl.data | tr -d '\\n' > {version_file} """.format( - maybe_install_erlang = maybe_install_erlang(ctx), + erl_rootdir_setup = erl_rootdir_setup(ctx), elixir_home = elixir_home, erlang_home = erlang_home, erl_libs_path = erl_libs_path, diff --git a/private/mix_test.bzl b/private/mix_test.bzl index b33389a..2b9569d 100644 --- a/private/mix_test.bzl +++ b/private/mix_test.bzl @@ -5,7 +5,7 @@ load( "//private:elixir_toolchain.bzl", "elixir_dirs", "erlang_dirs", - "maybe_install_erlang", + "erl_rootdir_setup", ) load("//private:mix_info.bzl", "MixProjectInfo") @@ -65,7 +65,7 @@ def _mix_test_impl(ctx): #!/usr/bin/env bash set -eo pipefail -{install_erlang} +{erl_rootdir_setup} if [[ "{elixir_home}" == /* ]]; then ABS_ELIXIR_HOME="{elixir_home}" else @@ -111,7 +111,7 @@ MIX_ENV=test \\ ERL_LIBS="$DEPS_DIR" \\ ${{ABS_ELIXIR_HOME}}/bin/mix test --no-compile {no_start}--no-deps-check {test_paths} {mix_test_opts} """.format( - install_erlang = maybe_install_erlang(ctx, short_path = True), + erl_rootdir_setup = erl_rootdir_setup(ctx, short_path = True), erlang_home = erlang_home, elixir_home = elixir_home, package = package, diff --git a/private/protocol_consolidation.bzl b/private/protocol_consolidation.bzl index db425b9..d471f91 100644 --- a/private/protocol_consolidation.bzl +++ b/private/protocol_consolidation.bzl @@ -6,7 +6,7 @@ at build time rather than runtime. This matches Mix's behavior during releases. load("@rules_erlang//:erlang_app_info.bzl", "ErlangAppInfo") load("//:elixir_app_info.bzl", "ElixirAppInfo") -load(":elixir_toolchain.bzl", "erlang_dirs", "maybe_install_erlang") +load(":elixir_toolchain.bzl", "erlang_dirs", "erl_rootdir_setup") def _elixir_protocol_consolidation_impl(ctx): """Consolidate protocols from Elixir applications.""" @@ -53,7 +53,7 @@ export PATH="{erlang_home}/bin:$PATH" # Run the protocol consolidator escript exec "{tool_path}" "$@" """.format( - erl_rootdir_setup = maybe_install_erlang(ctx), + erl_rootdir_setup = erl_rootdir_setup(ctx), erlang_home = erlang_home, tool_path = consolidator.path, ) From 051a6931ca5984b08237ddaa1730f006f7fe4817 Mon Sep 17 00:00:00 2001 From: Denbeigh Stevens Date: Tue, 16 Jun 2026 21:34:31 +0000 Subject: [PATCH 3/6] delegate to upstream otp_rootdir_setup / otp_runfiles --- private/elixir_build.bzl | 26 ++++---------------- private/elixir_source_build.bzl | 8 +++--- private/elixir_toolchain.bzl | 43 ++++++--------------------------- 3 files changed, 16 insertions(+), 61 deletions(-) diff --git a/private/elixir_build.bzl b/private/elixir_build.bzl index e792174..d3ac6b5 100644 --- a/private/elixir_build.bzl +++ b/private/elixir_build.bzl @@ -13,6 +13,8 @@ load( load( "@rules_erlang//tools:erlang_toolchain.bzl", "erlang_home", + "otp_rootdir_setup", + "otp_runfiles", ) ElixirInfo = provider( @@ -24,24 +26,6 @@ ElixirInfo = provider( ], ) -def otp_install_snippet(otp_info): - """Shell snippet exporting ERL_ROOTDIR for a relocatable OTP, like erl_rootdir_setup. - - Empty for an external/host erlang (erlang_home is an absolute path). Build-action - context only -- all callers here are run_shell build actions, so the release dir is - addressed by its (execroot-relative) path. - """ - if otp_info.release_dir == None: - return "" - return 'export ERL_ROOTDIR="$PWD/{}"'.format(otp_info.release_dir.path) - -def otp_runfiles(ctx, otp_info): - """Build runfiles depset for OTP, like erlang_dirs.""" - if otp_info.release_dir != None: - return ctx.runfiles([otp_info.release_dir, otp_info.version_file]) - else: - return ctx.runfiles([otp_info.version_file]) - def _elixir_build_impl(ctx): otp_info = ctx.attr.otp[OtpInfo] release_dir = ctx.actions.declare_directory("elixir_release") @@ -85,7 +69,7 @@ make cp -r bin $ABS_RELEASE_DIR/ cp -r lib $ABS_RELEASE_DIR/ """.format( - erl_rootdir_setup = otp_install_snippet(otp_info), + erl_rootdir_setup = otp_rootdir_setup(otp_info), erlang_home = erlang_home(otp_info), release_path = release_dir.path, source_files = " ".join([f.path for f in ctx.files.srcs]), @@ -110,7 +94,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} "{elixir_home}"/bin/iex --version > {version_file} """.format( - erl_rootdir_setup = otp_install_snippet(otp_info), + erl_rootdir_setup = otp_rootdir_setup(otp_info), erlang_home = erlang_home(otp_info), elixir_home = release_dir.path, version_file = version_file.path, @@ -163,7 +147,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} "{elixir_home}"/bin/iex --version > {version_file} """.format( - erl_rootdir_setup = otp_install_snippet(otp_info), + erl_rootdir_setup = otp_rootdir_setup(otp_info), erlang_home = erlang_home(otp_info), elixir_home = elixir_home, version_file = version_file.path, diff --git a/private/elixir_source_build.bzl b/private/elixir_source_build.bzl index 6bef82a..a098ec2 100644 --- a/private/elixir_source_build.bzl +++ b/private/elixir_source_build.bzl @@ -5,12 +5,12 @@ load( load( "@rules_erlang//tools:erlang_toolchain.bzl", "erlang_home", + "otp_rootdir_setup", + "otp_runfiles", ) load( ":elixir_build.bzl", "ElixirInfo", - "otp_install_snippet", - "otp_runfiles", ) def _elixir_source_build_impl(ctx): @@ -56,7 +56,7 @@ make cp -r bin $ABS_RELEASE_DIR/ cp -r lib $ABS_RELEASE_DIR/ """.format( - erl_rootdir_setup = otp_install_snippet(otp_info), + erl_rootdir_setup = otp_rootdir_setup(otp_info), erlang_home = erlang_home(otp_info), release_path = release_dir.path, source_files = " ".join([f.path for f in ctx.files.srcs]), @@ -81,7 +81,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} "{elixir_home}"/bin/iex --version > {version_file} """.format( - erl_rootdir_setup = otp_install_snippet(otp_info), + erl_rootdir_setup = otp_rootdir_setup(otp_info), erlang_home = erlang_home(otp_info), elixir_home = release_dir.path, version_file = version_file.path, diff --git a/private/elixir_toolchain.bzl b/private/elixir_toolchain.bzl index a04019c..1670ec1 100644 --- a/private/elixir_toolchain.bzl +++ b/private/elixir_toolchain.bzl @@ -5,6 +5,8 @@ load( load( "@rules_erlang//tools:erlang_toolchain.bzl", "erlang_home", + "otp_rootdir_setup", + "otp_runfiles", ) load( ":elixir_build.bzl", @@ -34,16 +36,7 @@ def _build_info(ctx): def erlang_dirs(ctx): info = _build_info(ctx) - if info.release_dir != None: - runfiles = ctx.runfiles([ - info.release_dir, - info.version_file, - ]) - else: - runfiles = ctx.runfiles([ - info.version_file, - ]) - return (erlang_home(info), info.release_dir, runfiles) + return (erlang_home(info), info.release_dir, otp_runfiles(ctx, info)) def elixir_dirs(ctx, short_path = False): info = ctx.toolchains["//:toolchain_type"].elixirinfo @@ -54,29 +47,7 @@ def elixir_dirs(ctx, short_path = False): return (p, ctx.runfiles([info.release_dir, info.version_file])) def erl_rootdir_setup(ctx, short_path = False): - """Shell that exports ERL_ROOTDIR so a templated "$ERL_ROOTDIR"/bin/erl resolves. - - Pairs with erlang_home(): that emits the "$ERL_ROOTDIR" reference (for a - relocatable OTP install), this gives it a value. Empty for an external/host - erlang, which carries an absolute erlang_home and needs no setup. - - Pass short_path = True when the generated script runs from a runfiles tree - (an executable launched by `bazel run`/`bazel test`); leave it False (the - default) when the script runs inside a build action, where cwd is the - execroot. Mirrors erl_rootdir_setup() in - @rules_erlang//tools:erlang_toolchain.bzl. - """ - info = _build_info(ctx) - release_dir = info.release_dir - if release_dir == None: - return "" - if short_path: - return """\ -if [ -n "${{TEST_SRCDIR:-}}" ]; then - export ERL_ROOTDIR="$TEST_SRCDIR/$TEST_WORKSPACE/{short_path}" -else - export ERL_ROOTDIR="$PWD/{short_path}" -fi\ -""".format(short_path = release_dir.short_path) - else: - return 'export ERL_ROOTDIR="$PWD/{}"'.format(release_dir.path) + """ERL_ROOTDIR export for the elixir toolchain's OTP; delegates to the pure + otp_rootdir_setup in @rules_erlang//tools:erlang_toolchain.bzl. short_path=True + for a runfiles (bazel run/test) context, False for a build action.""" + return otp_rootdir_setup(_build_info(ctx), short_path) From a1a7baa0dfed44d0821ef2da117e78cac660e457 Mon Sep 17 00:00:00 2001 From: Denbeigh Stevens Date: Tue, 16 Jun 2026 21:35:01 +0000 Subject: [PATCH 4/6] erlang_dirs: fail on relocatable OTP for windows targets --- private/elixir_toolchain.bzl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/private/elixir_toolchain.bzl b/private/elixir_toolchain.bzl index 1670ec1..957c1ab 100644 --- a/private/elixir_toolchain.bzl +++ b/private/elixir_toolchain.bzl @@ -36,6 +36,15 @@ def _build_info(ctx): def erlang_dirs(ctx): info = _build_info(ctx) + + # erl.exe resolves its root from erl.ini, not $ERL_ROOTDIR, so a relocatable + # (release_dir) toolchain can't work on a Windows target -- fail loudly rather + # than emit a broken script. Use an external (host) erlang on Windows. + if getattr(ctx.attr, "is_windows", False) and info.release_dir != None: + fail("relocatable OTP toolchain (release_dir) is unsupported on Windows " + + "targets: erl.exe reads its root from erl.ini, not $ERL_ROOTDIR. " + + "Use an external (host) erlang toolchain for Windows.") + return (erlang_home(info), info.release_dir, otp_runfiles(ctx, info)) def elixir_dirs(ctx, short_path = False): From 70d8ccba691f22a026dc38b2c3b06748f1fc9c98 Mon Sep 17 00:00:00 2001 From: Denbeigh Stevens Date: Thu, 9 Jul 2026 18:41:38 +0000 Subject: [PATCH 5/6] dedupe escript wrapper --- private/elixir_sys_config.bzl | 60 +++++++----------------------- private/elixir_toolchain.bzl | 40 ++++++++++++++++++++ private/eval_config.bzl | 39 +++++-------------- private/protocol_consolidation.bzl | 39 +++++-------------- 4 files changed, 73 insertions(+), 105 deletions(-) diff --git a/private/elixir_sys_config.bzl b/private/elixir_sys_config.bzl index 001745d..db4092b 100644 --- a/private/elixir_sys_config.bzl +++ b/private/elixir_sys_config.bzl @@ -24,8 +24,6 @@ extend this to also inject specs for these into `sys.config`. load("@rules_erlang//:erlang_app_info.bzl", "ErlangAppInfo") load("//:elixir_app_info.bzl", "ElixirAppInfo") -load(":elixir_toolchain.bzl", "erlang_dirs", "erl_rootdir_setup") -load(":release_info.bzl", "ReleaseInfo", "get_release_info") load( ":config_inference.bzl", "infer_app_name", @@ -35,6 +33,8 @@ load( "parse_config_provider_options", "should_use_runtime_config", ) +load(":elixir_toolchain.bzl", "erlang_escript_wrapper") +load(":release_info.bzl", "ReleaseInfo", "get_release_info") def _elixir_sys_config_impl(ctx): """Generate a sys.config file with intelligent defaults and inference.""" @@ -100,50 +100,21 @@ def _elixir_sys_config_impl(ctx): # Get the sys_config_builder tool builder = ctx.executable._sys_config_builder - # Get the Erlang/OTP dirs (handles relocatable OTP via $ERL_ROOTDIR) - (erlang_home, _, erlang_runfiles) = erlang_dirs(ctx) - - # Create a wrapper script that sets up the PATH for escript - wrapper = ctx.actions.declare_file("{}_sys_config_wrapper.sh".format(ctx.attr.name)) + # Runtime-config builds read the runtime path from a file and forward it. + exec_line = None if has_runtime and runtime_config_path: - wrapper_content = """#!/bin/bash -set -euo pipefail - -{erl_rootdir_setup} - -# Set up Erlang/OTP paths -export PATH="{erlang_home}/bin:$PATH" - -# Read runtime config path from file and pass to tool -RUNTIME_PATH=$(cat "{runtime_config_path_file}") -exec "{tool_path}" "$@" --runtime-path "$RUNTIME_PATH" -""".format( - erl_rootdir_setup = erl_rootdir_setup(ctx), - erlang_home = erlang_home, - runtime_config_path_file = runtime_config_path.path, - tool_path = builder.path, - ) - else: - wrapper_content = """#!/bin/bash -set -euo pipefail - -{erl_rootdir_setup} - -# Set up Erlang/OTP paths -export PATH="{erlang_home}/bin:$PATH" - -# Run the escript with all arguments -exec "{tool_path}" "$@" -""".format( - erl_rootdir_setup = erl_rootdir_setup(ctx), - erlang_home = erlang_home, - tool_path = builder.path, + exec_line = 'RUNTIME_PATH=$(cat "{}")\nexec "{}" "$@" --runtime-path "$RUNTIME_PATH"'.format( + runtime_config_path.path, + builder.path, ) - ctx.actions.write( - output = wrapper, - content = wrapper_content, - is_executable = True, + # Wrapper puts the toolchain's Erlang/OTP on PATH; erlang_runfiles must be + # staged as action inputs (below) so $ERL_ROOTDIR/bin/erl exists. + (wrapper, erlang_runfiles) = erlang_escript_wrapper( + ctx, + "{}_sys_config_wrapper.sh".format(ctx.attr.name), + builder, + exec_line = exec_line, ) # Prepare inputs @@ -200,7 +171,6 @@ elixir_sys_config = rule( app_configs = [":config_prod"] # From eval_config """, ), - "runtime_configs": attr.label_list( allow_files = [".exs"], default = [], @@ -234,7 +204,6 @@ elixir_sys_config = rule( proper config files with eval_config instead. """, ), - "config_provider_options": attr.string_dict( default = {}, doc = """Advanced Config.Provider options. @@ -285,7 +254,6 @@ elixir_sys_config = rule( env = "staging" # Custom environment """, ), - "version": attr.string( doc = """Explicitly set the release version. diff --git a/private/elixir_toolchain.bzl b/private/elixir_toolchain.bzl index 957c1ab..f6a3fef 100644 --- a/private/elixir_toolchain.bzl +++ b/private/elixir_toolchain.bzl @@ -60,3 +60,43 @@ def erl_rootdir_setup(ctx, short_path = False): otp_rootdir_setup in @rules_erlang//tools:erlang_toolchain.bzl. short_path=True for a runfiles (bazel run/test) context, False for a build action.""" return otp_rootdir_setup(_build_info(ctx), short_path) + +def erlang_escript_wrapper(ctx, wrapper_name, tool, exec_line = None): + """Write a bash wrapper that puts the toolchain's Erlang/OTP on PATH and execs an escript. + + Handles relocatable OTP via $ERL_ROOTDIR, so the same wrapper works for + external and internal toolchains. + + Args: + ctx: the rule context. + wrapper_name: filename to declare for the generated wrapper script. + tool: the escript File to exec (its path is used in the default exec line). + exec_line: overrides the default `exec "" "$@"` (e.g. to append flags). + + Returns: + (wrapper_file, erlang_runfiles). Callers must feed erlang_runfiles.files + into the action's inputs so the OTP release tree is staged. + """ + (erlang_home, _, erlang_runfiles) = erlang_dirs(ctx) + if exec_line == None: + exec_line = 'exec "{}" "$@"'.format(tool.path) + wrapper = ctx.actions.declare_file(wrapper_name) + ctx.actions.write( + output = wrapper, + content = """#!/bin/bash +set -euo pipefail + +{erl_rootdir_setup} + +# Set up Erlang/OTP paths +export PATH="{erlang_home}/bin:$PATH" + +{exec_line} +""".format( + erl_rootdir_setup = erl_rootdir_setup(ctx), + erlang_home = erlang_home, + exec_line = exec_line, + ), + is_executable = True, + ) + return (wrapper, erlang_runfiles) diff --git a/private/eval_config.bzl b/private/eval_config.bzl index 7efeefa..c7e3309 100644 --- a/private/eval_config.bzl +++ b/private/eval_config.bzl @@ -4,16 +4,13 @@ This module provides a Bazel rule for evaluating ALL application configurations from Elixir config/*.exs files and encoding them as EETF (Erlang External Term Format). """ -load("//:elixir_app_info.bzl", "ElixirAppInfo") load("@rules_erlang//:util.bzl", "path_join") -load(":elixir_toolchain.bzl", "erlang_dirs", "erl_rootdir_setup") +load("//:elixir_app_info.bzl", "ElixirAppInfo") +load(":elixir_toolchain.bzl", "erlang_escript_wrapper") def _eval_config_impl(ctx): """Evaluate all Elixir application configurations using the eval_config tool.""" - # Get the Erlang/OTP dirs (handles relocatable OTP via $ERL_ROOTDIR) - (erlang_home, _, erlang_runfiles) = erlang_dirs(ctx) - # Determine output file name output_name = ctx.attr.output_name if not output_name: @@ -28,30 +25,12 @@ def _eval_config_impl(ctx): # Get the eval_config tool tool = ctx.executable._eval_config - # Create a wrapper script that sets up the PATH for escript - wrapper = ctx.actions.declare_file("{}_wrapper.sh".format(ctx.attr.name)) - - # Build the wrapper script content - wrapper_content = """#!/bin/bash -set -euo pipefail - -{erl_rootdir_setup} - -# Set up Erlang/OTP paths -export PATH="{erlang_home}/bin:$PATH" - -# Run the escript with all arguments -exec "{tool_path}" "$@" -""".format( - erl_rootdir_setup = erl_rootdir_setup(ctx), - erlang_home = erlang_home, - tool_path = tool.path, - ) - - ctx.actions.write( - output = wrapper, - content = wrapper_content, - is_executable = True, + # Wrapper puts the toolchain's Erlang/OTP on PATH; erlang_runfiles must be + # staged as action inputs (below) so $ERL_ROOTDIR/bin/erl exists. + (wrapper, erlang_runfiles) = erlang_escript_wrapper( + ctx, + "{}_wrapper.sh".format(ctx.attr.name), + tool, ) # Build arguments for eval_config tool @@ -186,4 +165,4 @@ eval_config = rule( ), }, provides = [DefaultInfo], -) \ No newline at end of file +) diff --git a/private/protocol_consolidation.bzl b/private/protocol_consolidation.bzl index d471f91..8d75333 100644 --- a/private/protocol_consolidation.bzl +++ b/private/protocol_consolidation.bzl @@ -6,7 +6,7 @@ at build time rather than runtime. This matches Mix's behavior during releases. load("@rules_erlang//:erlang_app_info.bzl", "ErlangAppInfo") load("//:elixir_app_info.bzl", "ElixirAppInfo") -load(":elixir_toolchain.bzl", "erlang_dirs", "erl_rootdir_setup") +load(":elixir_toolchain.bzl", "erlang_escript_wrapper") def _elixir_protocol_consolidation_impl(ctx): """Consolidate protocols from Elixir applications.""" @@ -18,8 +18,10 @@ def _elixir_protocol_consolidation_impl(ctx): for dep in ctx.attr.deps: if ErlangAppInfo in dep: app_info = dep[ErlangAppInfo] + # Collect all beam files beam_files.extend(app_info.beam) + # Extract ebin directories from beam file paths for beam_file in app_info.beam: ebin_dir = beam_file.dirname @@ -35,33 +37,12 @@ def _elixir_protocol_consolidation_impl(ctx): # Get the protocol_consolidator tool consolidator = ctx.executable._protocol_consolidator - # Get the Erlang/OTP dirs (handles relocatable OTP via $ERL_ROOTDIR) - (erlang_home, _, erlang_runfiles) = erlang_dirs(ctx) - - # Create a wrapper script that sets up the PATH for escript - wrapper = ctx.actions.declare_file("{}_consolidator_wrapper.sh".format(ctx.attr.name)) - - # Build the wrapper script content - wrapper_content = """#!/bin/bash -set -euo pipefail - -{erl_rootdir_setup} - -# Set up Erlang/OTP paths -export PATH="{erlang_home}/bin:$PATH" - -# Run the protocol consolidator escript -exec "{tool_path}" "$@" -""".format( - erl_rootdir_setup = erl_rootdir_setup(ctx), - erlang_home = erlang_home, - tool_path = consolidator.path, - ) - - ctx.actions.write( - output = wrapper, - content = wrapper_content, - is_executable = True, + # Wrapper puts the toolchain's Erlang/OTP on PATH; erlang_runfiles must be + # staged as action inputs (below) so $ERL_ROOTDIR/bin/erl exists. + (wrapper, erlang_runfiles) = erlang_escript_wrapper( + ctx, + "{}_consolidator_wrapper.sh".format(ctx.attr.name), + consolidator, ) # Build arguments for consolidator @@ -152,4 +133,4 @@ def consolidate_protocols_for_release(name, deps, **kwargs): name = "{}_dir".format(name), actual = name, visibility = ["//visibility:public"], - ) \ No newline at end of file + ) From 6c660095989d0615594b831abbdc280377d2cb9c Mon Sep 17 00:00:00 2001 From: Denbeigh Stevens Date: Thu, 9 Jul 2026 20:38:36 +0000 Subject: [PATCH 6/6] tidy comment --- tools/elixir_vars.bzl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/elixir_vars.bzl b/tools/elixir_vars.bzl index 2f17fd6..10612e7 100644 --- a/tools/elixir_vars.bzl +++ b/tools/elixir_vars.bzl @@ -13,10 +13,8 @@ ELIXIR_VARS_ENV_MAP = { "ELIXIR_VERSION_FILE_SHORT_PATH": "$(ELIXIR_VERSION_FILE_SHORT_PATH)", } -# OTP installs are now relocatable tree artifacts rather than a tar unpacked to a -# fixed path. OTP_INSTALL_PATH and ERLANG_RELEASE_TAR_* are gone; consumers wanting -# to run erl should `export ERL_ROOTDIR="$PWD/$(ERLANG_RELEASE_DIR_PATH)"` (or -# _SHORT_PATH in a runfiles context) before invoking "$(ERLANG_HOME)"/bin/erl. +# Consumers wanting to run erl should `export ERL_ROOTDIR="$PWD/$(ERLANG_RELEASE_DIR_PATH)"` +# (or _SHORT_PATH in a runfiles context) before invoking "$(ERLANG_HOME)"/bin/erl. ELIXIR_VARS_ENV_MAP_INTERNAL = ELIXIR_VARS_ENV_MAP | { "ERLANG_RELEASE_DIR_PATH": "$(ERLANG_RELEASE_DIR_PATH)", "ERLANG_RELEASE_DIR_SHORT_PATH": "$(ERLANG_RELEASE_DIR_SHORT_PATH)",