Skip to content

Commit 051306b

Browse files
committed
Reapply "Add support for headers and dynamic base URL (#65) (#77)"
This reverts commit bd858bd.
1 parent 773a3cc commit 051306b

File tree

3 files changed

+341
-148
lines changed

3 files changed

+341
-148
lines changed

lib/rustler_precompiled.ex

Lines changed: 70 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,20 @@ defmodule RustlerPrecompiled do
2424
2525
* `:crate` - The name of Rust crate if different from the `:otp_app`. This is optional.
2626
27-
* `:base_url` - A valid URL that is used as base path for the NIF file.
27+
* `:base_url` - Location where to find the NIFs from. This should be one of the following:
28+
29+
* A URL to a directory containing the NIFs. The name of the NIF will be appended to it
30+
and a GET request will be made. Works well with public GitHub releases.
31+
32+
* A tuple of `{URL, headers}`. The headers should be a list of key-value pairs.
33+
This is useful when the NIFs are hosted in a private server.
34+
35+
* A tuple of `{module, function}` where the `function` is an atom representing the function
36+
name in that module. It's expected a function of arity 1, where the NIF file name is given,
37+
and it should return a URL or a tuple of `{URL, headers}`.
38+
This should be used for all cases not covered by the above.
39+
For example when multiple requests have to be made, like when using a private GitHub release
40+
through the GitHub API, or when the URLs don't resemble a simple directory.
2841
2942
* `:version` - The version of precompiled assets (it is part of the NIF filename).
3043
@@ -262,13 +275,19 @@ defmodule RustlerPrecompiled do
262275

263276
@native_dir "priv/native"
264277

278+
@doc deprecated: "Use available_nifs/1 instead"
279+
def available_nif_urls(nif_module) when is_atom(nif_module) do
280+
available_nifs(nif_module)
281+
|> Enum.map(fn {_lib_name, {url, _headers}} -> url end)
282+
end
283+
265284
@doc """
266-
Returns URLs for NIFs based on its module name.
285+
Returns URLs for NIFs based on its module name as a list of tuples: `[{lib_name, {url, headers}}]`.
267286
268287
The module name is the one that defined the NIF and this information
269288
is stored in a metadata file.
270289
"""
271-
def available_nif_urls(nif_module) when is_atom(nif_module) do
290+
def available_nifs(nif_module) when is_atom(nif_module) do
272291
nif_module
273292
|> metadata_file()
274293
|> read_map_from_file()
@@ -286,6 +305,13 @@ defmodule RustlerPrecompiled do
286305

287306
@doc false
288307
def nif_urls_from_metadata(metadata) when is_map(metadata) do
308+
with {:ok, nifs} <- nifs_from_metadata(metadata) do
309+
{:ok, Enum.map(nifs, fn {_lib_name, {url, _headers}} -> url end)}
310+
end
311+
end
312+
313+
@doc false
314+
def nifs_from_metadata(metadata) when is_map(metadata) do
289315
case metadata do
290316
%{
291317
targets: targets,
@@ -320,22 +346,27 @@ defmodule RustlerPrecompiled do
320346
variants = Map.fetch!(variants, target_triple)
321347

322348
for variant <- variants do
323-
tar_gz_file_url(
324-
base_url,
325-
lib_name_with_ext(target_triple, lib_name <> "--" <> Atom.to_string(variant))
326-
)
349+
lib_name = lib_name_with_ext(target_triple, lib_name <> "--" <> Atom.to_string(variant))
350+
{lib_name, tar_gz_file_url(base_url, lib_name)}
327351
end
328352
end
329353

330354
defp maybe_variants_tar_gz_urls(_, _, _, _), do: []
331355

356+
@doc deprecated: "Use current_target_nifs/1 instead"
357+
def current_target_nif_urls(nif_module) when is_atom(nif_module) do
358+
nif_module
359+
|> current_target_nifs()
360+
|> Enum.map(fn {_lib_name, {url, _headers}} -> url end)
361+
end
362+
332363
@doc """
333-
Returns the file URLs to be downloaded for current target.
364+
Returns the file URLs to be downloaded for current target as a list of tuples: `[{lib_name, {url, headers}}]`.
334365
335366
It is in the plural because a target may have some variants for it.
336367
It receives the NIF module.
337368
"""
338-
def current_target_nif_urls(nif_module) when is_atom(nif_module) do
369+
def current_target_nifs(nif_module) when is_atom(nif_module) do
339370
metadata =
340371
nif_module
341372
|> metadata_file()
@@ -362,9 +393,10 @@ defmodule RustlerPrecompiled do
362393

363394
defp tar_gz_urls(base_url, basename, version, nif_version, target_triple, variants) do
364395
lib_name = lib_name(basename, version, nif_version, target_triple)
396+
lib_name_with_ext = lib_name_with_ext(target_triple, lib_name)
365397

366398
[
367-
tar_gz_file_url(base_url, lib_name_with_ext(target_triple, lib_name))
399+
{lib_name_with_ext, tar_gz_file_url(base_url, lib_name_with_ext(target_triple, lib_name))}
368400
| maybe_variants_tar_gz_urls(variants, base_url, target_triple, lib_name)
369401
]
370402
end
@@ -616,7 +648,7 @@ defmodule RustlerPrecompiled do
616648

617649
# `cache_base_dir` is a "private" option used only in tests.
618650
cache_dir = cache_dir(config.base_cache_dir, "precompiled_nifs")
619-
cached_tar_gz = Path.join(cache_dir, "#{file_name}.tar.gz")
651+
cached_tar_gz = Path.join(cache_dir, file_name)
620652

621653
{:ok,
622654
Map.merge(basic_metadata, %{
@@ -841,21 +873,34 @@ defmodule RustlerPrecompiled do
841873
"so"
842874
end
843875

844-
"#{lib_name}.#{ext}"
876+
"#{lib_name}.#{ext}.tar.gz"
845877
end
846878

847-
defp tar_gz_file_url(base_url, file_name) do
879+
defp tar_gz_file_url({module, function_name}, file_name)
880+
when is_atom(module) and is_atom(function_name) do
881+
apply(module, function_name, [file_name])
882+
end
883+
884+
defp tar_gz_file_url({base_url, request_headers}, file_name) do
848885
uri = URI.parse(base_url)
849886

850887
uri =
851888
Map.update!(uri, :path, fn path ->
852-
Path.join(path || "", "#{file_name}.tar.gz")
889+
Path.join(path || "", file_name)
853890
end)
854891

855-
to_string(uri)
892+
{to_string(uri), request_headers}
893+
end
894+
895+
defp tar_gz_file_url(base_url, file_name) do
896+
tar_gz_file_url({base_url, []}, file_name)
897+
end
898+
899+
defp download_nif_artifact(url) when is_binary(url) do
900+
download_nif_artifact({url, []})
856901
end
857902

858-
defp download_nif_artifact(url) do
903+
defp download_nif_artifact({url, request_headers}) do
859904
url = String.to_charlist(url)
860905
Logger.debug("Downloading NIF from #{url}")
861906

@@ -896,7 +941,10 @@ defmodule RustlerPrecompiled do
896941

897942
options = [body_format: :binary]
898943

899-
case :httpc.request(:get, {url, []}, http_options, options) do
944+
request_headers =
945+
Enum.map(request_headers, fn {k, v} when is_binary(k) -> {String.to_charlist(k), v} end)
946+
947+
case :httpc.request(:get, {url, request_headers}, http_options, options) do
900948
{:ok, {{_, 200, _}, _headers, body}} ->
901949
{:ok, body}
902950

@@ -913,16 +961,17 @@ defmodule RustlerPrecompiled do
913961
attempts = max_retries(options)
914962

915963
download_results =
916-
for url <- urls, do: {url, with_retry(fn -> download_nif_artifact(url) end, attempts)}
964+
for {lib_name, url} <- urls,
965+
do: {lib_name, with_retry(fn -> download_nif_artifact(url) end, attempts)}
917966

918967
cache_dir = cache_dir("precompiled_nifs")
919968
:ok = File.mkdir_p(cache_dir)
920969

921970
Enum.flat_map(download_results, fn result ->
922-
with {:download, {url, download_result}} <- {:download, result},
971+
with {:download, {lib_name, download_result}} <- {:download, result},
923972
{:download_result, {:ok, body}} <- {:download_result, download_result},
924973
hash <- :crypto.hash(@checksum_algo, body),
925-
path <- Path.join(cache_dir, basename_from_url(url)),
974+
path <- Path.join(cache_dir, lib_name),
926975
{:file, :ok} <- {:file, File.write(path, body)} do
927976
checksum = Base.encode16(hash, case: :lower)
928977

@@ -932,7 +981,7 @@ defmodule RustlerPrecompiled do
932981

933982
[
934983
%{
935-
url: url,
984+
lib_name: lib_name,
936985
path: path,
937986
checksum: checksum,
938987
checksum_algo: @checksum_algo
@@ -986,14 +1035,6 @@ defmodule RustlerPrecompiled do
9861035
end)
9871036
end
9881037

989-
defp basename_from_url(url) do
990-
uri = URI.parse(url)
991-
992-
uri.path
993-
|> String.split("/")
994-
|> List.last()
995-
end
996-
9971038
defp read_map_from_file(file) do
9981039
with {:ok, contents} <- File.read(file),
9991040
{%{} = contents, _} <- Code.eval_string(contents) do

lib/rustler_precompiled/config.ex

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,34 @@ defmodule RustlerPrecompiled.Config do
8383

8484
defp validate_base_url!(nil), do: raise_for_nil_field_value(:base_url)
8585

86-
defp validate_base_url!(base_url) do
86+
defp validate_base_url!(base_url) when is_binary(base_url) do
87+
validate_base_url!({base_url, []})
88+
end
89+
90+
defp validate_base_url!({base_url, headers}) when is_binary(base_url) and is_list(headers) do
8791
case :uri_string.parse(base_url) do
8892
%{} ->
89-
base_url
93+
if Enum.all?(headers, &match?({key, value} when is_binary(key) and is_binary(value), &1)) do
94+
{base_url, headers}
95+
else
96+
raise "`:base_url` headers for `RustlerPrecompiled` must be a list of `{binary(),binary()}`"
97+
end
9098

9199
{:error, :invalid_uri, error} ->
92100
raise "`:base_url` for `RustlerPrecompiled` is invalid: #{inspect(to_string(error))}"
93101
end
94102
end
95103

104+
defp validate_base_url!({module, function}) when is_atom(module) and is_atom(function) do
105+
Code.ensure_compiled!(module)
106+
107+
if Kernel.function_exported?(module, function, 1) do
108+
{module, function}
109+
else
110+
raise "`:base_url` for `RustlerPrecompiled` is a function that does not exist: `#{inspect(module)}.#{function}/1`"
111+
end
112+
end
113+
96114
defp validate_list!(nil, option, _valid_values), do: raise_for_nil_field_value(option)
97115

98116
defp validate_list!([_ | _] = values, option, valid_values) do

0 commit comments

Comments
 (0)