Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ mix test --exclude integration # skip the device-dependent ones
- **Compile-time regex literals are unsafe** on Elixir 1.19 / OTP 28.0. Use
`Regex.compile!("...", "flags")` for runtime compilation. Already swept in
0.3.17 — don't reintroduce.
- **Hex packages omit repository-root dotfiles by default.** Code under `lib/`
must not compile-time read `.tool-versions` or another root-only file. Keep a
packaged authority in source, enforce exact lockstep with the root file in a
source test, and compile the unpacked Hex artifact in the regression suite.
- **`mix mob.deploy --device <id>`** resolves the id via discovery before
deciding which platform to build. The narrowing logic is in
`narrow_platforms_for_device/2` and is the single source of truth for both
Expand Down
14 changes: 3 additions & 11 deletions lib/mob_dev/toolchain.ex
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
defmodule MobDev.Toolchain do
@moduledoc false

@tool_versions_path Path.expand("../../.tool-versions", __DIR__)
@external_resource @tool_versions_path
@required_zig_version @tool_versions_path
|> File.read!()
|> String.split("\n")
|> Enum.find_value(fn line ->
case String.split(line) do
["zig", version] -> version
_ -> nil
end
end) || raise("zig is not pinned in #{@tool_versions_path}")
# This value must live in packaged source; the root toolchain file is not in
# Hex archives. The source test keeps both release authorities in lockstep.
@required_zig_version "0.17.0-dev.269+ebff43698"

@type zig_status ::
:missing
Expand Down
87 changes: 87 additions & 0 deletions test/mob_dev/toolchain_package_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
defmodule MobDev.ToolchainPackageTest do
use ExUnit.Case, async: false

test "packed Hex artifact compiles and retains exact Zig preflights" do
root = Path.expand("../..", __DIR__)

temp =
Path.join(
System.tmp_dir!(),
"mob_dev_toolchain_package_#{System.unique_integer([:positive])}"
)

package = Path.join(temp, "package")
build = Path.join(temp, "build")
on_exit(fn -> File.rm_rf!(temp) end)

{pack_output, pack_status} =
System.cmd("mix", ["hex.build", "--unpack", "--output", package],
cd: root,
stderr_to_stdout: true
)

assert pack_status == 0, pack_output
refute File.exists?(Path.join(package, ".tool-versions"))
File.cp!(Path.join(root, "mix.lock"), Path.join(package, "mix.lock"))
link_compiled_dependencies(root, build)

env = [
{"MIX_ENV", "prod"},
{"MIX_BUILD_PATH", build},
{"MIX_DEPS_PATH", Path.join(root, "deps")}
]

{compile_output, compile_status} =
System.cmd("mix", ["compile"],
cd: package,
env: env,
stderr_to_stdout: true
)

assert compile_status == 0, compile_output

probe = ~S'''
beam = :code.which(MobDev.Toolchain) |> List.to_string()
true = String.contains?(beam, "mob_dev_toolchain_package_")

version = MobDev.Toolchain.required_zig_version()
exact = MobDev.Toolchain.zig_status_from_result({version <> "\n", 0})
mismatch = MobDev.Toolchain.zig_status_from_result({"0.15.2\n", 0})
failed = MobDev.Toolchain.zig_status_from_result({"dyld failure\n", 127})

{:ok, "zig", ^version, nil} = Mix.Tasks.Mob.Doctor.__zig_check_result__(exact)
{:fail, "zig", _, _} = Mix.Tasks.Mob.Doctor.__zig_check_result__(mismatch)
{:fail, "zig", _, _} = Mix.Tasks.Mob.Doctor.__zig_check_result__(failed)

:run_zig = MobDev.NativeBuild.zig_build_plan(true, exact, false)
{:zig_required, ^mismatch} = MobDev.NativeBuild.zig_build_plan(true, mismatch, true)
{:zig_required, ^failed} = MobDev.NativeBuild.zig_build_plan(true, failed, true)
IO.puts("packed_toolchain_ok=#{version}")
'''

{probe_output, probe_status} =
System.cmd(
"mix",
["run", "--no-compile", "--no-start", "-e", probe],
cd: package,
env: env,
stderr_to_stdout: true
)

assert probe_status == 0, probe_output
assert probe_output =~ "packed_toolchain_ok=#{MobDev.Toolchain.required_zig_version()}"
end

defp link_compiled_dependencies(root, build) do
build_lib = Path.join(build, "lib")
File.mkdir_p!(build_lib)

root
|> Path.join("_build/test/lib/*")
|> Path.wildcard()
|> Enum.reject(&(Path.basename(&1) == "mob_dev"))
|> Enum.each(fn dependency ->
File.ln_s!(dependency, Path.join(build_lib, Path.basename(dependency)))
end)
end
end
12 changes: 11 additions & 1 deletion test/mob_dev/toolchain_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ defmodule MobDev.ToolchainTest do
test "required Zig version stays in lockstep with .tool-versions" do
tool_versions = File.read!(Path.expand("../../.tool-versions", __DIR__))

assert tool_versions =~ "zig #{Toolchain.required_zig_version()}"
pinned_zig =
tool_versions
|> String.split("\n")
|> Enum.find_value(fn line ->
case String.split(line) do
["zig", version] -> version
_ -> nil
end
end)

assert pinned_zig == Toolchain.required_zig_version()
end

test "accepts the exact required Zig version" do
Expand Down
Loading