defmodule Mix.Tasks.Evision.Fetch 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 Mix.Tasks.Compile.EvisionPrecompiled, as: Precompile require Logger @switches [ all: :boolean, only_local: :boolean, print: :boolean, ignore_unavailable: :boolean, dry_run: :boolean ] @impl true def run([]) do raise "the module name and a flag is expected. Use \"--all\" or \"--only-local\" flags" end @impl true def run(flags) when is_list(flags) do {options, _args, _invalid} = OptionParser.parse(flags, strict: @switches) urls = cond do Keyword.get(options, :all) -> {_, urls} = Enum.map_reduce(Precompile.get_available_nif_versions(), [], fn nif_version, urls -> {nil, [Precompile.available_nif_urls(nif_version) | urls]} end) List.flatten(urls) Keyword.get(options, :only_local) -> [Precompile.current_target_nif_url(Precompile.get_nif_version())] true -> raise "you need to specify either \"--all\" or \"--only-local\" flags" end checksum_algo = Precompile.checksum_algo() checksum_urls = Enum.map(urls, fn url -> "#{url}.#{Atom.to_string(checksum_algo)}" end) if Keyword.get(options, :dry_run) do Enum.each(checksum_urls, &IO.puts/1) else result = Task.async_stream(checksum_urls, fn url -> filename = basename_from_url(url) filename = String.replace(filename, ".#{Atom.to_string(checksum_algo)}", "") try do {:ok, checksum_content} = Precompile.download!(url) [checksum, _] = String.split(checksum_content, " ", trim: true) Logger.info("downloaded: url=#{url}, file=#{filename}, checksum[#{Atom.to_string(checksum_algo)}]=#{checksum}") %{ url: String.replace(url, ".#{Atom.to_string(checksum_algo)}", ""), path: filename, checksum: checksum, checksum_algo: checksum_algo } rescue _ -> Logger.error("download fucked: url=#{url}, file=#{filename}") end end, timeout: :infinity ) result = Enum.map(result, fn {:ok, r} -> r end) if Keyword.get(options, :print) do result |> Enum.map(fn map -> {Path.basename(Map.fetch!(map, :path)), Map.fetch!(map, :checksum)} end) |> Enum.sort() |> Enum.map_join("\n", fn {file, checksum} -> "#{checksum} #{file}" end) |> IO.puts() end write_checksum!(result) end end defp write_checksum!(result, app \\ Mix.Project.config()[:app]) do {elixir_file, erlang_file} = Precompile.checksum_file(app) pairs = for %{path: path, checksum: checksum, checksum_algo: algo} <- result, into: %{} do basename = Path.basename(path) checksum = "#{algo}:#{checksum}" {basename, checksum} end lines = for {filename, checksum} <- Enum.sort(pairs) do ~s( "#{filename}" => #{inspect(checksum, limit: :infinity)},\n) end File.write!(elixir_file, ["%{\n", lines, "}\n"]) lines = for {filename, checksum} <- Enum.sort(pairs) do " \"#{filename}\" => #{inspect(checksum, limit: :infinity)}" end lines = Enum.join(lines, ",\n") File.write!(erlang_file, [ """ -module(checksum). -export([checksum/0]). checksum() -> \#{ """, lines, "\n }.\n"]) end defp basename_from_url(url) do uri = URI.parse(url) uri.path |> String.split("/") |> List.last() end end