Current section

Files

Jump to
fennec_precompile lib mix tasks fennec.precompile.ex
Raw

lib/mix/tasks/fennec.precompile.ex

defmodule Mix.Tasks.Fennec.Precompile do
@moduledoc """
Download and use precompiled NIFs safely with checksums.
Fennec Precompile is a tool for library maintainers that use `:elixir_make`
and wish to ship precompiled binaries. This tool aims to be a drop-in
replacement for `:elixir_make`.
It helps by removing the need to have the C/C++ compiler and other dependencies
installed in the user's machine.
Check the [README.md](https://github.com/cocoa-xu/fennec_precompile/blob/main/README.md) for details.
## Example
defmodule MyNative do
use FennecPrecompile,
base_url: "https://github.com/me/my_project/releases/download/v0.1.0",
version: "0.1.0"
end
## Options
* `:base_url` - A valid URL that is used as base path for the NIF file.
* `:version` - The version of precompiled assets (it is part of the NIF filename).
* `:targets` - A list of targets [supported by
Zig](https://ziglang.org/learn/overview/#support-table) for which
precompiled assets are avilable. By default the following targets are
configured:
- `x86_64-macos`
- `x86_64-linux-gnu`
- `x86_64-linux-musl`
- `x86_64-windows-gnu`
- `aarch64-macos`
- `aarch64-linux-gnu`
- `aarch64-linux-musl`
- `riscv64-linux-musl`
"""
use Mix.Task
require Logger
@impl true
def run(args) do
saved_cwd = File.cwd!()
cache_dir = System.get_env("FENNEC_CACHE_DIR", nil)
if cache_dir do
System.put_env("FENNEC_CACHE_DIR", cache_dir)
end
cache_dir = FennecPrecompile.cache_dir("")
targets = compile_targets()
do_fennec_precompile(args, targets, saved_cwd, cache_dir)
make_priv_dir(:clean)
with {:ok, target} <- FennecPrecompile.target(targets) do
tar_filename = "#{target}.tar.gz"
cached_tar_gz = Path.join([cache_dir, tar_filename])
FennecPrecompile.restore_nif_file(cached_tar_gz)
end
Mix.Project.build_structure()
:ok
end
defp compile_targets() do
targets = System.get_env("FENNEC_TARGETS")
if targets do
String.split(targets, ",", trim: true)
else
FennecPrecompile.Config.default_targets()
end
end
defp do_fennec_precompile(args, targets, saved_cwd, cache_dir) do
saved_cc = System.get_env("CC") || ""
saved_cxx = System.get_env("CXX") || ""
saved_cpp = System.get_env("CPP") || ""
checksums = fennec_precompile(args, targets, cache_dir)
FennecPrecompile.write_checksum!(checksums)
File.cd!(saved_cwd)
System.put_env("CC", saved_cc)
System.put_env("CXX", saved_cxx)
System.put_env("CPP", saved_cpp)
end
defp fennec_precompile(args, targets, cache_dir) do
Enum.reduce(targets, [], fn target, checksums ->
Logger.debug("Current compiling target: #{target}")
make_priv_dir(:clean)
{cc, cxx} =
case {:os.type(), target} do
{{:unix, :darwin}, "x86_64-macos" <> _} ->
{"gcc -arch x86_64", "g++ -arch x86_64"}
{{:unix, :darwin}, "aarch64-macos" <> _} ->
{"gcc -arch arm64", "g++ -arch arm64"}
_ ->
{"zig cc -target #{target}", "zig c++ -target #{target}"}
end
System.put_env("CC", cc)
System.put_env("CXX", cxx)
System.put_env("CPP", cxx)
Mix.Tasks.Compile.ElixirMake.run(args)
{archive_full_path, archive_tar_gz} = create_precompiled_archive(target, cache_dir)
{:ok, algo, checksum} = FennecPrecompile.compute_checksum(archive_full_path, :sha256)
[%{path: archive_tar_gz, checksum_algo: algo, checksum: checksum} | checksums]
end)
end
defp create_precompiled_archive(target, cache_dir) do
saved_cwd = File.cwd!()
app_priv = app_priv()
File.cd!(app_priv)
nif_version = FennecPrecompile.current_nif_version()
app = Mix.Project.config()[:app]
version = Mix.Project.config()[:version]
archive_filename = "#{to_string(app)}-nif-#{nif_version}-#{target}-#{version}"
archive_tar_gz = "#{archive_filename}.tar.gz"
archive_full_path = Path.expand(Path.join([cache_dir, archive_tar_gz]))
Logger.debug("Creating precompiled archive: #{archive_full_path}")
System.cmd("tar", ["-czf", archive_full_path, "."])
File.cd!(saved_cwd)
{archive_full_path, archive_tar_gz}
end
defp app_priv() do
app_priv(Mix.Project.config())
end
defp app_priv(config) do
config
|> Mix.Project.app_path()
|> Path.join("priv")
end
defp make_priv_dir(:clean) do
File.rm_rf(app_priv())
make_priv_dir()
end
defp make_priv_dir() do
File.mkdir_p!(app_priv())
end
end