defmodule Burrito.Util.DefaultERTSResolver do
alias Burrito.Builder.Target
alias Burrito.Builder.Log
alias Burrito.Util
alias Burrito.Util.FileCache
alias Burrito.Util.ERTSResolver
alias Burrito.Util.ERTSUniversalMachineFetcher
@behaviour ERTSResolver
@impl ERTSResolver
@spec do_resolve(Burrito.Builder.Target.t()) :: Burrito.Builder.Target.t()
def do_resolve(%Target{erts_source: {:runtime, _}} = target) do
%Target{target | erts_source: {:runtime, version: Util.get_otp_version()}}
end
def do_resolve(%Target{erts_source: {:precompiled, version: otp_version}} = target)
when is_binary(otp_version) do
case ERTSUniversalMachineFetcher.fetch_version(
target.os,
target.qualifiers[:libc],
target.cpu,
otp_version
) do
%URI{} = location ->
%Target{target | erts_source: {:url, url: location}} |> do_resolve()
end
end
def do_resolve(%Target{erts_source: {:local, path: location}} = target) do
archive_data = File.read!(location)
unpacked_location = do_unpack(archive_data, target)
%Target{target | erts_source: {:local_unpacked, path: unpacked_location}}
end
def do_resolve(%Target{erts_source: {:url, url: location}} = target) do
url_string = URI.to_string(location)
archive_data = get_erts(url_string)
unpacked_location = do_unpack(archive_data, target)
%Target{target | erts_source: {:local_unpacked, path: unpacked_location}}
end
def do_resolve(%Target{erts_source: {:local_unpacked, path: _location}} = target) do
target
end
defp get_erts(tar_url) do
cache_key = :crypto.hash(:sha, tar_url) |> Base.encode16()
case FileCache.fetch(cache_key) do
{:hit, data} ->
Log.info(:step, "Found matching cached ERTS, using that")
data
_ ->
do_download(tar_url, cache_key)
end
end
defp do_unpack(data, %Target{} = target) when is_binary(data) do
# save the payload somewhere
random_id = :crypto.strong_rand_bytes(8) |> Base.encode16()
tar_dest_path = System.tmp_dir!() |> Path.join(["erts_#{random_id}"])
File.write!(tar_dest_path, data)
random_id = :crypto.strong_rand_bytes(8) |> Base.encode16()
extraction_path = System.tmp_dir!() |> Path.join(["unpacked_erts_#{random_id}"])
File.mkdir_p!(extraction_path)
# we use 7z to unpack windows setup files, otherwise we use tar
command =
case target.os do
:windows -> ~c"7z x #{tar_dest_path} -o#{extraction_path}/otp-windows/"
_ -> ~c"tar xzf #{tar_dest_path} -C #{extraction_path}"
end
:os.cmd(command)
File.rm!(tar_dest_path)
Log.info(:step, "Unpacked ERTS to: #{extraction_path}")
extraction_path
end
defp do_download(url, cache_key) do
{:ok, _} = Application.ensure_all_started(:req)
Log.info(:step, "Downloading file: #{url}")
resp = Req.get!(url, raw: true)
if resp.status != 200 do
raise "Failed to fetch #{url}! (Got #{resp.status}) Perhaps we haven't built a pre-compiled Erlang for this release yet? If this was a 404, please file an issue! Thanks!"
end
FileCache.put_if_not_exist(cache_key, resp.body)
resp.body
end
end