defmodule ElixirMake.Precompiler do
@moduledoc """
The behaviour for precompiler modules.
"""
require Logger
@typedoc """
Target triplet.
"""
@type target :: String.t()
@doc """
This callback should return a list of triplets ("arch-os-abi") for all supported targets
of the given operation.
For the `:compile` operation, `all_supported_targets` should return a list of targets that
the current host is capable of (cross-)compiling to.
For the `:fetch` operation, `all_supported_targets` should return the full list of targets.
For example, GitHub Actions provides Linux, macOS and Windows CI hosts, when `operation` is
`:compile`, the precompiler might return `["x86_64-linux-gnu"]` if it is running in the Linux
CI environment, while returning `["x86_64-apple-darwin", "aarch64-apple-darwin"]` on macOS,
or `["amd64-windows", "x86-windows"]` on Windows platform.
When `operation` is `:fetch`, the precompiler should return the full list. The full list for
the above example should be:
[
"x86_64-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"amd64-windows",
"x86-windows"
]
This allows the precompiler to do the compilation work in multilple hosts and gather all the
artefacts later with `mix elixir_make.checksum --all`.
"""
@callback all_supported_targets(operation :: :compile | :fetch) :: [target]
@doc """
This callback should return the target triplet for current node.
"""
@callback current_target() :: {:ok, target} | {:error, String.t()}
@doc """
This callback will be invoked when the user executes the `mix compile`
(or `mix compile.elixir_make`) command.
The precompiler should then compile the NIF library "natively". Note that
it is possible for the precompiler module to pick up other environment variables
like `TARGET_ARCH=aarch64` and adjust compile arguments correspondingly.
"""
@callback build_native(OptionParser.argv()) ::
{Mix.Task.Compiler.status(), [Mix.Task.Compiler.Diagnostic.t()]}
@doc """
This callback should precompile the library to the given target(s).
Returns `:ok` if the requested target has successfully compiled.
"""
@callback precompile(OptionParser.argv(), target) :: :ok | {:error, String.t()}
@doc """
Optional post actions to run after each precompilation target is archived.
It will be called when a target is precompiled and archived successfully.
For example, actions can be deleting all target-specific files.
"""
@callback post_precompile_target(target) :: :ok
@doc """
Optional post actions to run after all precompilation tasks are done.
It will only be called at the end of the `mix elixir_make.precompile` command.
For example, actions can be archiving all precompiled artefacts and uploading
the archive file to an object storage server.
"""
@callback post_precompile() :: :ok
@doc """
Optional recover actions when the current target is unavailable.
There are two reasons that the current target might be unavailable:
when the library only has precompiled binaries for some platforms,
and it either
- needs to be compiled on other platforms.
The callback should return `:compile` for this case.
- is intended to function as `noop` on other platforms.
The callback should return `:ignore` for this case.
Defaults to `:compile` if this callback is not implemented.
"""
@callback unavailable_target(String.t()) :: :compile | :ignore
@optional_callbacks post_precompile: 0, unavailable_target: 1, post_precompile_target: 1
@doc """
Invoke the regular Mix toolchain compilation.
"""
def mix_compile(args) do
ElixirMake.Compiler.compile(args)
end
end