173 lines
5.1 KiB
Elixir
173 lines
5.1 KiB
Elixir
|
|
defmodule Mix.Tasks.ElixirMake.Checksum do
|
||
|
|
@shortdoc "Fetch precompiled NIFs and build the checksums"
|
||
|
|
|
||
|
|
@moduledoc """
|
||
|
|
A task responsible for downloading the precompiled NIFs for a given module.
|
||
|
|
|
||
|
|
This task must only be used by package creators who want to ship the
|
||
|
|
precompiled NIFs. The goal is to download the precompiled packages and
|
||
|
|
generate a checksum to check-in alongside the project in the the Hex repository.
|
||
|
|
This is done by passing the `--all` flag.
|
||
|
|
|
||
|
|
You can also use the `--only-local` flag to download only the precompiled
|
||
|
|
package for use during development.
|
||
|
|
|
||
|
|
You can use the `--ignore-unavailable` flag to ignore any NIFs that are not available.
|
||
|
|
This is useful when you are developing a new NIF that does not support all platforms.
|
||
|
|
|
||
|
|
This task also accept the `--print` flag to print the checksums.
|
||
|
|
"""
|
||
|
|
|
||
|
|
use Mix.Task
|
||
|
|
alias ElixirMake.Artefact
|
||
|
|
|
||
|
|
@recursive true
|
||
|
|
|
||
|
|
@switches [
|
||
|
|
all: :boolean,
|
||
|
|
only_local: :boolean,
|
||
|
|
print: :boolean,
|
||
|
|
ignore_unavailable: :boolean
|
||
|
|
]
|
||
|
|
|
||
|
|
@impl true
|
||
|
|
def run(flags) when is_list(flags) do
|
||
|
|
if function_exported?(Mix, :ensure_application!, 1) do
|
||
|
|
Mix.ensure_application!(:inets)
|
||
|
|
Mix.ensure_application!(:ssl)
|
||
|
|
Mix.ensure_application!(:crypto)
|
||
|
|
end
|
||
|
|
|
||
|
|
config = Mix.Project.config()
|
||
|
|
|
||
|
|
{_, precompiler} =
|
||
|
|
config[:make_precompiler] ||
|
||
|
|
Mix.raise(
|
||
|
|
":make_precompiler project configuration is required when using elixir_make.checksum"
|
||
|
|
)
|
||
|
|
|
||
|
|
{options, _args} = OptionParser.parse!(flags, strict: @switches)
|
||
|
|
|
||
|
|
urls =
|
||
|
|
cond do
|
||
|
|
Keyword.get(options, :all) ->
|
||
|
|
Artefact.available_target_urls(config, precompiler)
|
||
|
|
|
||
|
|
Keyword.get(options, :only_local) ->
|
||
|
|
current_nif_version = "#{:erlang.system_info(:nif_version)}"
|
||
|
|
|
||
|
|
case Artefact.current_target_url(config, precompiler, current_nif_version) do
|
||
|
|
{:ok, target, nif_version_to_use, url} ->
|
||
|
|
[{{target, nif_version_to_use}, url}]
|
||
|
|
|
||
|
|
{:error, {:unavailable_target, current_target, error}} ->
|
||
|
|
recover =
|
||
|
|
if function_exported?(precompiler, :unavailable_target, 1) do
|
||
|
|
precompiler.unavailable_target(current_target)
|
||
|
|
else
|
||
|
|
:compile
|
||
|
|
end
|
||
|
|
|
||
|
|
case recover do
|
||
|
|
:compile ->
|
||
|
|
Mix.raise(error)
|
||
|
|
|
||
|
|
:ignore ->
|
||
|
|
[]
|
||
|
|
end
|
||
|
|
|
||
|
|
{:error, error} ->
|
||
|
|
Mix.raise(error)
|
||
|
|
end
|
||
|
|
|
||
|
|
true ->
|
||
|
|
Mix.raise("you need to specify either \"--all\" or \"--only-local\" flags")
|
||
|
|
end
|
||
|
|
|
||
|
|
artefacts = download_and_checksum_all(config, urls, options)
|
||
|
|
|
||
|
|
if Keyword.get(options, :print, false) do
|
||
|
|
artefacts
|
||
|
|
|> Enum.map(fn %Artefact{basename: basename, checksum: checksum} -> {basename, checksum} end)
|
||
|
|
|> Enum.sort()
|
||
|
|
|> Enum.map_join("\n", fn {file, checksum} -> "#{checksum} #{file}" end)
|
||
|
|
|> IO.puts()
|
||
|
|
end
|
||
|
|
|
||
|
|
Artefact.write_checksums!(artefacts)
|
||
|
|
end
|
||
|
|
|
||
|
|
defp download_and_checksum_all(config, urls, options) do
|
||
|
|
ignore_unavailable? = Keyword.get(options, :ignore_unavailable, false)
|
||
|
|
|
||
|
|
tasks =
|
||
|
|
Task.async_stream(
|
||
|
|
urls,
|
||
|
|
fn {{_target, _nif_version}, url} ->
|
||
|
|
checksum_algo = Artefact.checksum_algo()
|
||
|
|
checksum_file_url = "#{url}.#{Atom.to_string(checksum_algo)}"
|
||
|
|
artifact_checksum = Artefact.download(config, checksum_file_url)
|
||
|
|
|
||
|
|
with {:ok, body} <- artifact_checksum,
|
||
|
|
[checksum, basename] <- String.split(body, " ", trim: true) do
|
||
|
|
{:checksum, url,
|
||
|
|
%Artefact{
|
||
|
|
basename: String.trim(basename),
|
||
|
|
checksum: checksum,
|
||
|
|
checksum_algo: checksum_algo
|
||
|
|
}}
|
||
|
|
else
|
||
|
|
_ -> {:download, url, Artefact.download(config, url)}
|
||
|
|
end
|
||
|
|
end,
|
||
|
|
timeout: :infinity,
|
||
|
|
ordered: false
|
||
|
|
)
|
||
|
|
|
||
|
|
cache_dir = Artefact.cache_dir()
|
||
|
|
|
||
|
|
Enum.flat_map(tasks, fn
|
||
|
|
{:ok, {:checksum, _url, artefact}} ->
|
||
|
|
Mix.shell().info(
|
||
|
|
"NIF checksum file with checksum #{artefact.checksum} (#{artefact.checksum_algo})"
|
||
|
|
)
|
||
|
|
|
||
|
|
[artefact]
|
||
|
|
|
||
|
|
{:ok, {:download, url, download}} ->
|
||
|
|
case download do
|
||
|
|
{:ok, body} ->
|
||
|
|
basename = basename_from_url(url)
|
||
|
|
path = Path.join(cache_dir, basename)
|
||
|
|
File.write!(path, body)
|
||
|
|
artefact = Artefact.checksum(basename, body)
|
||
|
|
|
||
|
|
Mix.shell().info(
|
||
|
|
"NIF cached at #{path} with checksum #{artefact.checksum} (#{artefact.checksum_algo})"
|
||
|
|
)
|
||
|
|
|
||
|
|
[artefact]
|
||
|
|
|
||
|
|
result ->
|
||
|
|
if ignore_unavailable? do
|
||
|
|
msg = "Skipped unavailable NIF artifact. Reason: #{inspect(result)}"
|
||
|
|
Mix.shell().info(msg)
|
||
|
|
else
|
||
|
|
msg = "Could not finish the download of NIF artifacts. Reason: #{inspect(result)}"
|
||
|
|
Mix.shell().error(msg)
|
||
|
|
end
|
||
|
|
|
||
|
|
[]
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
defp basename_from_url(url) do
|
||
|
|
uri = URI.parse(url)
|
||
|
|
|
||
|
|
uri.path
|
||
|
|
|> String.split("/")
|
||
|
|
|> List.last()
|
||
|
|
end
|
||
|
|
end
|