defmodule Mix.Tasks.Compile.Odu do @moduledoc false use Mix.Task.Compiler @recursive true @system_arch List.to_string(:erlang.system_info(:system_architecture)) @impl Mix.Task.Compiler def run(_args) do {os, arch} = platform_from_env() case {os, arch} do {os, arch} when is_binary(os) and is_binary(arch) -> build_odu(executable_path({os, arch}), [{"GOOS", os}, {"GOARCH", arch}]) {nil, nil} -> build_for_current_platform() end end def executable_name(platform \\ current_platform()) do case platform do {"windows", "amd64"} -> "odu_windows_amd64.exe" {"windows", "arm64"} -> "odu_windows_arm64.exe" {"linux", "amd64"} -> "odu_linux_amd64" {"linux", "arm64"} -> "odu_linux_arm64" {"darwin", "amd64"} -> "odu_darwin_amd64" {"darwin", "arm64"} -> "odu_darwin_arm64" # generic name for all other platform _ -> "odu" end end defp build_for_current_platform do platform = current_platform() cond do System.find_executable("go") -> build_odu(executable_path(platform), []) # check if pre-built binary exists for currect platform File.exists?(executable_path(platform)) -> :ok true -> compiler_error(""" ExCmd does not ship with `odu` shim for your operating system and arch. Please install `go` to build shim for you. System Architecture: #{@system_arch} """) end end defp build_odu(executable_path, env) do result = System.cmd( "go", ~w/build -ldflags -w -o #{executable_path}/, cd: "go_src", stderr_to_stdout: true, env: env ) case result do {_, 0} -> {:ok, []} {output, _exit_status} -> compiler_error(output) end end defp compiler_error(msg) do error = %Mix.Task.Compiler.Diagnostic{ compiler_name: "odu", details: nil, file: __ENV__.file, message: msg, position: nil, severity: :error } Mix.shell().error(error.message) {:error, [error]} end defp executable_path(platform) do Path.absname("priv") |> Path.join(executable_name(platform)) end defp current_platform do case current_target(:os.type()) do {:ok, {"x86_64", "linux", _}} -> {"linux", "amd64"} {:ok, {"aarch64", "linux", _}} -> {"linux", "arm64"} {:ok, {"x86_64", "apple", _}} -> {"darwin", "amd64"} {:ok, {"aarch64", "apple", _}} -> {"darwin", "arm64"} {:ok, {"x86_64", "windows", _}} -> {"windows", "amd64"} {:ok, {"aarch64", "windows", _}} -> {"windows", "arm64"} {:ok, {_, os_name, _}} -> {os_name, nil} end end defp platform_from_env do os = System.get_env("ODU_GOOS") arch = System.get_env("ODU_GOARCH") if os && arch do {os, arch} else {nil, nil} end end defp current_target({:win32, _}) do processor_architecture = String.downcase(String.trim(System.get_env("PROCESSOR_ARCHITECTURE"))) compiler = case :erlang.system_info(:c_compiler_used) do {:msc, _} -> "msvc" {:gnuc, _} -> "gnu" {other, _} -> Atom.to_string(other) end # https://docs.microsoft.com/en-gb/windows/win32/winprog64/wow64-implementation-details?redirectedfrom=MSDN case processor_architecture do "amd64" -> {:ok, {"x86_64", "windows", compiler}} "ia64" -> {:ok, {"ia64", "windows", compiler}} "arm64" -> {:ok, {"aarch64", "windows", compiler}} "x86" -> {:ok, {"x86", "windows", compiler}} end end defp current_target({:unix, _}) do # get current target triplet from `:erlang.system_info/1` system_architecture = to_string(:erlang.system_info(:system_architecture)) current = String.split(system_architecture, "-", trim: true) case length(current) do 4 -> {:ok, {Enum.at(current, 0), Enum.at(current, 2), Enum.at(current, 3)}} 3 -> case :os.type() do {:unix, :darwin} -> # could be something like aarch64-apple-darwin21.0.0 # but we don't really need the last 21.0.0 part # credo:disable-for-next-line if String.match?(Enum.at(current, 2), ~r/^darwin.*/) do {:ok, {Enum.at(current, 0), Enum.at(current, 1), "darwin"}} else {:ok, List.to_tuple(current)} end _ -> {:ok, List.to_tuple(current)} end _ -> {:error, "cannot decide current target"} end end end