lib/steps/build/pack_and_build.ex

defmodule Burrito.Steps.Build.PackAndBuild do
  alias Burrito.Builder.Context
  alias Burrito.Builder.Log
  alias Burrito.Builder.Step
  alias Burrito.Builder.Target

  @behaviour Step

  @impl Step
  def execute(%Context{} = context) do
    options = context.mix_release.options[:burrito] || []
    release_name = Atom.to_string(context.mix_release.name)
    build_triplet = Target.make_triplet(context.target)

    plugin_path = maybe_get_plugin_path(options[:plugin])

    zig_build_args = ["-Dtarget=#{build_triplet}"]

    create_metadata_file(context.self_dir, zig_build_args, context.mix_release)

    # TODO: Why do we need to do this???
    # This is to bypass a VERY strange bug inside Linux containers...
    # If we don't do this, the archiver will fail to see all the files inside the lib directory
    # This is still under investigation, but touching a file inside the directory seems to force the
    # File system to suddenly "wake up" to all the files inside it.
    Path.join(context.work_dir, ["/lib", "/.burrito"]) |> File.touch!()

    build_env =
      [
        {"__BURRITO_IS_PROD", is_prod(context.target)},
        {"__BURRITO_RELEASE_PATH", context.work_dir},
        {"__BURRITO_RELEASE_NAME", release_name},
        {"__BURRITO_PLUGIN_PATH", plugin_path}
      ] ++ context.extra_build_env

    Log.info(:step, "Zig build env: #{inspect(build_env)}")

    build_result =
      System.cmd("zig", ["build"] ++ zig_build_args,
        cd: context.self_dir,
        env: build_env,
        into: IO.stream()
      )

    if !options[:no_clean] do
      clean_build(context.self_dir)
    end

    case build_result do
      {_, 0} ->
        context

      _ ->
        Log.error(
          :step,
          "Burrito failed to wrap up your app! Check the logs for more information."
        )

        raise "Wrapper build failed"
    end
  end

  defp maybe_get_plugin_path(nil), do: nil

  defp maybe_get_plugin_path(plugin_path) do
    Path.join(File.cwd!(), [plugin_path])
  end

  defp create_metadata_file(self_path, args, release) do
    Log.info(:step, "Generating wrapper metadata file...")

    {zig_version_string, 0} = System.cmd("zig", ["version"], cd: self_path)

    metadata_map = %{
      app_name: Atom.to_string(release.name),
      zig_version: zig_version_string |> String.trim(),
      zig_build_arguments: args,
      app_version: release.version,
      options: inspect(release.options),
      erts_version: release.erts_version |> to_string()
    }

    encoded = Jason.encode!(metadata_map)

    Path.join(self_path, ["src/", "_metadata.json"]) |> File.write!(encoded)
  end

  defp is_prod(%Target{debug?: debug?}) do
    cond do
      debug? -> "0"
      Mix.env() == :prod -> "1"
      true -> "0"
    end
  end

  defp clean_build(self_path) do
    Log.info(:step, "Cleaning up...")

    cache = Path.join(self_path, "zig-cache")
    out = Path.join(self_path, "zig-out")
    payload = Path.join(self_path, "payload.foilz")
    compressed_payload = Path.join(self_path, ["src/", "payload.foilz.xz"])
    musl_runtime = Path.join(self_path, ["src/", "musl-runtime.so"])
    metadata = Path.join(self_path, ["src/", "_metadata.json"])

    File.rmdir(cache)
    File.rmdir(out)
    File.rm(payload)
    File.rm(compressed_payload)
    File.rm(musl_runtime)
    File.rm(metadata)

    :ok
  end
end