diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a18a4e..6b99cfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,15 @@ Full module documentation: [hexdocs.pm/mob_dev](https://hexdocs.pm/mob_dev). --- +## [0.5.17] + +### Fixed +- **iOS simulator deploy now boots from a clean `mix mob.deploy --native`** (was device-only). Three gaps made the sim deploy incomplete vs the device path, so the sim crashed on boot even though the device worked: + - The Elixir-distribution apps `elixir`/`logger` were staged only under `lib//ebin`, which the sim's `mob_beam.m` doesn't add to the code path — boot failed at `ensure_all_started(:elixir)` with "elixir.app not found". They're now flattened into the flat BEAMS_DIR alongside `eex` (which already needed this), where the path resolves. + - `priv/` was only partially staged (`repo/migrations`), so `Application.app_dir(:, "priv/cacerts.pem")` was `:enoent` and `Mob.Certs.load_cacerts!` crashed the boot. The whole `priv/` is now rsynced into the flat dir (cacerts, `mix`/`hex` ebins, vendored static, …), matching the device release. + - `Paths.sim_runtime_dir/0` fell back to `/tmp/otp-ios-sim` for zig-based projects (no `ios/build.sh`), but the runtime is synced to `~/.mob/runtime/ios-sim` — so the launcher and staging disagreed. It now recognizes `ios/build.zig` and returns the default runtime dir. + Verified: a clean `mob.deploy --native` to an iPhone 11 Pro Max sim boots Io, Phoenix endpoint up, embedded Livebook home renders — no manual runtime fixups. + ## [0.5.16] ### Fixed diff --git a/lib/mob_dev/native_build.ex b/lib/mob_dev/native_build.ex index 9c8965e..d69ab92 100644 --- a/lib/mob_dev/native_build.ex +++ b/lib/mob_dev/native_build.ex @@ -1988,15 +1988,22 @@ defmodule MobDev.NativeBuild do end defp copy_priv_repo_assets(otp_root, app_module) do - src = "priv/repo/migrations" - - if File.dir?(src) do - IO.puts(" === Copying priv/repo assets") - dst = Path.join([otp_root, app_module, "priv/repo/migrations"]) + # Copy the WHOLE priv/ into BEAMS_DIR/priv (not just repo/migrations), so + # Application.app_dir(:, "priv/...") resolves on device/sim — e.g. + # priv/cacerts.pem (Mob.Certs.load_cacerts!), priv/mix + priv/hex ebins + # (on-device Mix.install), a vendored lib's priv/ (Livebook's static), etc. + # Mirrors the device release's full-priv rsync; without it the sim boot + # crashed at Mob.Certs.load_cacerts! with :enoent on priv/cacerts.pem. + if File.dir?("priv") do + IO.puts(" === Copying priv/ (full)") + dst = Path.join([otp_root, app_module, "priv"]) File.mkdir_p!(dst) + chmod_writable(dst) + + {_, status} = + System.cmd("rsync", ["-a", "--no-perms", "priv/", "#{dst}/"], stderr_to_stdout: true) - Path.wildcard("#{src}/*.exs") - |> Enum.each(&File.cp!(&1, Path.join(dst, Path.basename(&1)))) + if status != 0, do: raise("rsync priv/ -> #{dst} failed") end :ok @@ -2027,20 +2034,27 @@ defmodule MobDev.NativeBuild do end defp copy_eex_stdlib_to_app(elixir_lib, otp_root, app_module) do - # EEx is part of Elixir but mob_beam.m doesn't add it to the code path. - # Drop it into BEAMS_DIR (flat) so code:where_is_file("eex.app") resolves - # — Ecto's startup needs it. - IO.puts(" === Copying EEx stdlib") + # The iOS sim's mob_beam.m doesn't add lib//ebin to the code path, so + # the Elixir-distribution apps that copy_elixir_stdlib_to_otp drops under + # lib/ (elixir, logger) — plus eex — are invisible there: boot fails at + # `ensure_all_started(:elixir)` with "elixir.app not found". Drop their .app + # + beams into BEAMS_DIR (flat), which IS on the path, so they resolve. + # (eex was already needed for Ecto's startup; elixir/logger are needed for + # the sim to boot at all. Harmless on device, which also has them in lib/.) + IO.puts(" === Copying Elixir-distribution apps (elixir, logger, eex) to BEAMS_DIR") dst = Path.join(otp_root, app_module) File.mkdir_p!(dst) - src_ebin = Path.join([elixir_lib, "eex", "ebin"]) - if File.dir?(src_ebin) do - Path.wildcard("#{src_ebin}/*.beam") - |> Enum.each(&File.cp!(&1, Path.join(dst, Path.basename(&1)))) + for app <- ~w(elixir logger eex) do + src_ebin = Path.join([elixir_lib, app, "ebin"]) + + if File.dir?(src_ebin) do + Path.wildcard("#{src_ebin}/*.beam") + |> Enum.each(&File.cp!(&1, Path.join(dst, Path.basename(&1)))) - app_file = Path.join(src_ebin, "eex.app") - if File.exists?(app_file), do: File.cp!(app_file, Path.join(dst, "eex.app")) + app_file = Path.join(src_ebin, "#{app}.app") + if File.exists?(app_file), do: File.cp!(app_file, Path.join(dst, "#{app}.app")) + end end :ok diff --git a/lib/mob_dev/paths.ex b/lib/mob_dev/paths.ex index 6927ca7..9b1c883 100644 --- a/lib/mob_dev/paths.ex +++ b/lib/mob_dev/paths.ex @@ -39,11 +39,24 @@ defmodule MobDev.Paths do build_sh_aware?(project_dir) -> default_runtime_dir() + build_zig?(project_dir) -> + # Zig-based iOS builds (ios/build.zig, no ios/build.sh) sync the runtime + # to default_runtime_dir() in sync_otp_runtime_sim. The launcher must + # agree, or the sim looks in /tmp/otp-ios-sim and boots a non-existent + # runtime ("elixir.app not found"). Match the staging path. + default_runtime_dir() + true -> legacy_tmp_path() end end + @doc false + @spec build_zig?(String.t()) :: boolean() + def build_zig?(project_dir) do + File.exists?(Path.join([project_dir, "ios", "build.zig"])) + end + @doc """ The new default runtime path — under `~/.mob/runtime/` so `mix mob.cache` can list and clear it the same way it handles the OTP cache. diff --git a/mix.exs b/mix.exs index c3d6e9f..e00273b 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule MobDev.MixProject do def project do [ app: :mob_dev, - version: "0.5.16", + version: "0.5.17", elixir: "~> 1.19", description: "Development tooling for the Mob mobile framework", source_url: "https://github.com/genericjam/mob_dev", diff --git a/test/mob_dev/paths_test.exs b/test/mob_dev/paths_test.exs index dad36df..5f13861 100644 --- a/test/mob_dev/paths_test.exs +++ b/test/mob_dev/paths_test.exs @@ -79,5 +79,12 @@ defmodule MobDev.PathsTest do File.write!(Path.join([project, "ios", "build.sh"]), "echo old\n") assert Paths.sim_runtime_dir(project_dir: project) == Paths.legacy_tmp_path() end + + test "new default for zig-based iOS projects (ios/build.zig, no build.sh)", + %{project: project} do + System.delete_env("MOB_SIM_RUNTIME_DIR") + File.write!(Path.join([project, "ios", "build.zig"]), "// zig build\n") + assert Paths.sim_runtime_dir(project_dir: project) == Paths.default_runtime_dir() + end end end