Batamanta.EnvCleaner (batamanta v1.5.0)

Copy Markdown View Source

Provides environment isolation for build commands.

This module ensures that batamanta uses system Erlang/Elixir instead of version managers like asdf, mise, or kerl that can cause version mismatches between compile-time and runtime ERTS.

The Problem

When asdf/mise/kerl are active in the shell, they modify PATH to point to specific Erlang/Elixir versions. This causes:

  1. Build uses Erlang from asdf (e.g., 27.x)
  2. Runtime uses ERTS embedded by batamanta (e.g., 28.0)
  3. Binary compiled for 27.x fails on 28.x → "corrupt atom table"

The Solution

This module:

  1. Detects and filters out version manager paths from PATH
  2. Optionally uses the cached ERTS bin directory for build (ultimate consistency)
  3. Ensures build-time and runtime ERTS are exactly the same

Platform Support

Works on:

  • macOS (x86_64, aarch64)
  • Linux glibc (x86_64, aarch64)
  • Linux musl (x86_64, aarch64)
  • Windows (x86_64)

Summary

Functions

Returns the environment for running mix release / mix escript.build during the batamanta build phase.

Returns a clean environment map that excludes asdf/mise/kerl paths.

Returns a clean environment as a map.

Returns a clean environment as a list of tuples for System.cmd/3.

Returns a clean environment that uses the specified ERTS bin directory.

Returns the path to system Elixir executable, ignoring version managers.

Returns the path to system Erlang executable, ignoring version managers.

Returns the path to system mix executable, ignoring version managers.

Functions

build_env(erts_path)

@spec build_env(Path.t()) :: [{binary(), binary()}]

Returns the environment for running mix release / mix escript.build during the batamanta build phase.

Why not erts_env/1?

System.cmd with the env: option uses Port.open underneath, which replaces the child process environment entirely with the given list — it does not merge. erts_env/1 starts from base_environment/0, a whitelist of only 7 variables (HOME, USER, LANG, …). That strips elixir, mix, MIX_HOME, HEX_HOME, MIX_REBAR3, etc., making mix release fail with "elixir: No such file or directory".

What this function does

Starts from the full current process environment (System.get_env()), then applies surgical overrides:

  1. Prepends the downloaded ERTS bin/ to PATH so erlexec / erl from the bundled ERTS win over any asdf/mise/kerl shim.
  2. Sets ROOTDIR, BINDIR, ERL_ROOTDIR to the bundled ERTS so OTP locates its own libs from the right place.
  3. Clears ERL_FLAGS, ERL_AFLAGS, ERL_ZFLAGS so no stale shell flags bleed into the compilation BEAM.
  4. Clears ASDF_ERLANG_VERSION and MISE_ERLANG_VERSION so that even if asdf/mise shims are still on PATH (needed for elixir/mix), they forward erl calls to whichever erl is first on PATH — which is our bundled one.

elixir, mix, MIX_HOME, HEX_HOME, hex cache, rebar, and every other build tool remain accessible because the full env is inherited.

clean_env()

@spec clean_env() :: %{optional(binary()) => binary() | nil}

Returns a clean environment map that excludes asdf/mise/kerl paths.

This function:

  • Preserves essential system variables (HOME, USER, etc.)
  • Removes asdf/mise/kerl from PATH
  • Returns a map suitable for System.cmd/3

clean_env_map()

@spec clean_env_map() :: %{optional(binary()) => binary() | nil}

Returns a clean environment as a map.

clean_env_tuples()

@spec clean_env_tuples() :: [{binary(), binary() | nil}]

Returns a clean environment as a list of tuples for System.cmd/3.

erts_env(erts_path, include_mix \\ true)

@spec erts_env(Path.t(), boolean()) :: [{binary(), binary() | nil}]

Returns a clean environment that uses the specified ERTS bin directory.

This is the PREFERRED method for batamanta builds - it ensures the build uses exactly the same ERTS version that will be embedded in the final binary.

Parameters

  • erts_path: Path to the cached ERTS directory
  • include_mix: If true, also tries to find mix in the ERTS path

Returns

Environment list tuples with:

  • PATH prepended with ERTS bin
  • ERL_AFLAGS set for proper startup
  • All version manager paths removed

system_elixir_path()

@spec system_elixir_path() :: binary() | nil

Returns the path to system Elixir executable, ignoring version managers.

system_erlang_path()

@spec system_erlang_path() :: binary() | nil

Returns the path to system Erlang executable, ignoring version managers.

system_mix_path()

@spec system_mix_path() :: binary() | nil

Returns the path to system mix executable, ignoring version managers.