Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deprecate availability key in nif_versions #81

Merged
merged 3 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 25 additions & 22 deletions PRECOMPILATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ def project do
make_precompiler_filename: "nif",
make_precompiler_priv_paths: ["nif.*"],
make_precompiler_nif_versions: [
versions: ["2.14", "2.15", "2.16"],
availability: &target_available_for_nif_version?/2
versions: ["2.14", "2.15", "2.16"]
]
# ...
]
Expand Down Expand Up @@ -113,22 +112,40 @@ The default value for `make_precompiler_nif_versions` is
[versions: ["#{:erlang.system_info(:nif_version)}"]]
```

There're three sub-keys for `make_precompiler_nif_versions`:
There're two sub-keys for `make_precompiler_nif_versions`:

- `versions`
- `fallback_version`
- `availability`

##### `versions` sub-key

The `versions` sub-key is a list of NIF versions that the precompiled artefacts are available for:
The `versions` sub-key is either a list of NIF versions of a function that returns a list of NIF versions that the precompiled artefacts are available for:

```elixir
make_precompiler_nif_versions: [
versions: ["2.15", "2.16"]
]
```

The above example tells `:elixir_make` that all targets have precompiled artefacts for NIF version `2.15` and `2.16`.

For some platforms maybe we only have precompiled artefacts after a certain NIF version, say for x86_64 Windows we have precompiled artefacts available when NIF version >= `2.16` while other platforms have precompiled artefacts available from NIF version >= `2.15`.

In such case we can inform `:elixir_make` that Windows targets don't have precompiled artefacts available except for NIF version `2.16` by passing a function to the `availability` sub-key. This function should accept two arguments, `target` and `nif_version`, and returns a boolean value indicating whether the precompiled artefacts for the target and NIF version are available.

```elixir
make_precompiler_nif_versions: [
versions: fn opts ->
target = opts.target
if String.contains?(target, "windows") do
["2.16"]
else
["2.15", "2.16"]
end
end
]
```

The default behaviour is to use the exact NIF version that is available to the current target. If one is not available, it may fallback (see `fallback_version` next) to the highest matching major version prior to the current version. For example:

- if the current host is using Erlang/OTP 23 (NIF version `2.15`), `elixir_make` will use the precompiled artefacts for NIF version `2.15`;
Expand All @@ -139,25 +156,11 @@ If the current host is using Erlang/OTP with a new major Erlang NIF version (NIF

##### `fallback_version` sub-key

The behaviour when `elixir_make` cannot find the exact NIF version of the precompiled binary can be customized by setting the `fallback_version` sub-key. The value of the `fallback_version` sub-key should be a function that accepts three arguments, `target`, `current_nif_version` and `target_versions`. The `target` is the target triplet (or other name format, defined by the precompiler of your choice), `current_nif_version` is the NIF version on the current host, and `target_versions` is a list of NIF versions that are available to the target.

The `fallback_version` function should return either the NIF version that `elixir_make` should use from the `target_versions` list or the `current_nif_version`.
The behaviour when `elixir_make` cannot find the exact NIF version of the precompiled binary can be customized by setting the `fallback_version` sub-key.

##### `availability` sub-key
The value of the `fallback_version` sub-key should be a function that accepts one argument, `opts`, which is a map that include one key `target`. The `target` is the target triplet (or other naming format, defined by the precompiler of your choice).

For some platforms maybe we only have precompiled artefacts after a certain NIF version, say for x86_64 Windows we have precompiled artefacts available when NIF version >= `2.16` while other platforms have precompiled artefacts available from NIF version >= `2.15`.

In such case we can inform `:elixir_make` that Windows targets don't have precompiled artefacts available except for NIF version `2.16` by passing a function to the `availability` sub-key. This function should accept two arguments, `target` and `nif_version`, and returns a boolean value indicating whether the precompiled artefacts for the target and NIF version are available.

```elixir
defp target_available_for_nif_version?(target, nif_version) do
if String.contains?(target, "windows") do
nif_version == "2.16"
else
true
end
end
```
The `fallback_version` function should return the NIF version that is available and should be chosen for the current target.

### (Optional) Customise Precompilation Targets

Expand Down
50 changes: 23 additions & 27 deletions lib/elixir_make/artefact.ex
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,14 @@ defmodule ElixirMake.Artefact do
{String.to_integer(major), String.to_integer(minor)}
end

defp fallback_version(_current_target, current_nif_version, versions) do
defp fallback_version(opts) do
current_nif_version = "#{:erlang.system_info(:nif_version)}"
{major, minor} = nif_version_to_tuple(current_nif_version)

# Get all matching major versions, earlier than the current version
# and their distance. We want the closest (smallest distance).
candidates =
for version <- versions,
for version <- opts.versions,
{^major, candidate_minor} <- [nif_version_to_tuple(version)],
candidate_minor <= minor,
do: {minor - candidate_minor, version}
Expand All @@ -163,6 +164,16 @@ defmodule ElixirMake.Artefact do
end
end

defp get_versions_for_target(versions, current_target) do
case versions do
version_list when is_list(version_list) ->
version_list

version_func when is_function(version_func, 1) ->
version_func.(%{:target => current_target})
end
end

@doc """
Returns all available {{target, nif_version}, url} pairs available.
"""
Expand All @@ -179,31 +190,15 @@ defmodule ElixirMake.Artefact do
config[:make_precompiler_nif_versions] ||
[versions: [current_nif_version]]

versions = nif_versions[:versions]

Enum.reduce(targets, [], fn target, archives ->
archive_filenames =
Enum.reduce(versions, [], fn nif_version_for_target, acc ->
availability = nif_versions[:availability]

available? =
if is_function(availability, 2) do
availability.(target, nif_version_for_target)
else
true
end
versions = get_versions_for_target(nif_versions[:versions], target)

if available? do
archive_filename = archive_filename(config, target, nif_version_for_target)
archive_filenames =
Enum.map(versions, fn nif_version_for_target ->
archive_filename = archive_filename(config, target, nif_version_for_target)

[
{{target, nif_version_for_target},
String.replace(url_template, "@{artefact_filename}", archive_filename)}
| acc
]
else
acc
end
{{target, nif_version_for_target},
String.replace(url_template, "@{artefact_filename}", archive_filename)}
end)

archive_filenames ++ archives
Expand All @@ -220,14 +215,15 @@ defmodule ElixirMake.Artefact do
config[:make_precompiler_nif_versions] ||
[versions: []]

versions = nif_versions[:versions]
versions = get_versions_for_target(nif_versions[:versions], current_target)

nif_version_to_use =
if current_nif_version in versions do
current_nif_version
else
fallback_version = nif_versions[:fallback_version] || (&fallback_version/3)
fallback_version.(current_target, current_nif_version, versions)
fallback_version = nif_versions[:fallback_version] || (&fallback_version/1)
opts = %{:target => current_target, :versions => versions}
fallback_version.(opts)
end

available_urls = available_target_urls(config, precompiler)
Expand Down
Loading