Skip to content

Commit 4c9970f

Browse files
authored
[precompilation] add support for multiple NIF versions (#71)
1 parent 1052b7f commit 4c9970f

File tree

5 files changed

+79
-16
lines changed

5 files changed

+79
-16
lines changed

PRECOMPILATION_GUIDE.md

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,17 @@ def project do
3535
# ...
3636
compilers: [:elixir_make] ++ Mix.compilers(),
3737
# elixir_make specific config
38+
# required
3839
make_precompiler: {:nif, CCPrecompiler},
3940
make_precompiler_url: "https://github.com/cocoa-xu/cc_precompiler_example/releases/download/v#{@version}/@{artefact_filename}",
41+
42+
# optional
4043
make_precompiler_filename: "nif",
41-
make_precompiler_priv_paths: ["nif.*"]
44+
make_precompiler_priv_paths: ["nif.*"],
45+
make_precompiler_nif_versions: [
46+
versions: ["2.14", "2.15", "2.16"],
47+
availability: &target_available_for_nif_version?/2
48+
]
4249
# ...
4350
]
4451
end
@@ -48,9 +55,12 @@ Another required field is `make_precompiler_url`. It is a URL template to the ar
4855

4956
`@{artefact_filename}` in the URL template string will be replaced by corresponding artefact filenames when fetching them. For example, `cc_precompiler_example-nif-2.16-x86_64-linux-gnu-0.1.0.tar.gz`.
5057

51-
Note that there is an optional config key for elixir_make, `make_precompiler_filename`. If the name (file extension does not count) of the shared library is different from your app's name, then `make_precompiler_filename` should be set. For example, if the app name is `"cc_precompiler_example"` while the name shared library is `"nif.so"` (or `"nif.dll"` on windows), then `make_precompiler_filename` should be set as `"nif"`.
58+
#### `make_precompiler_filename` (optional config key)
59+
60+
The first optional config key for elixir_make is `make_precompiler_filename`. If the name (file extension does not count) of the shared library is different from your app's name, then `make_precompiler_filename` should be set. For example, if the app name is `"cc_precompiler_example"` while the name shared library is `"nif.so"` (or `"nif.dll"` on windows), then `make_precompiler_filename` should be set as `"nif"`.
5261

53-
Another optional config key is `make_precompiler_priv_paths`. For example, say the `priv` directory is organised as follows in Linux, macOS and Windows respectively,
62+
#### `make_precompiler_priv_paths` (optional config key)
63+
The second optional config key is `make_precompiler_priv_paths`. For example, say the `priv` directory is organised as follows in Linux, macOS and Windows respectively,
5464

5565
```
5666
# Linux
@@ -93,6 +103,30 @@ Of course, wildcards (`?`, `**`, `*`) are supported when specifiying files. For
93103

94104
Directory structures and symbolic links are preserved.
95105

106+
#### `make_precompiler_nif_versions` (optional config key)
107+
108+
The third optional config key is `make_precompiler_nif_versions`. The default value is
109+
110+
```elixir
111+
[versions: ["#{:erlang.system_info(:nif_version)}"]]
112+
```
113+
114+
If you'd like to aim for an older NIF version, say `2.15` for Erlang/OTP 23 and 24, then you need to setup CI correspondingly and set the value of this key to `[versions: ["2.15", "2.16"]]`. This optional key will only be checked when downloading precompiled artefacts.
115+
116+
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`.
117+
118+
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.
119+
120+
```elixir
121+
defp target_available_for_nif_version?(target, nif_version) do
122+
if String.contains?(target, "windows") do
123+
nif_version == "2.16"
124+
else
125+
true
126+
end
127+
end
128+
```
129+
96130
### (Optional) Customise Precompilation Targets
97131

98132
To override the default configuration, please set the `cc_precompile` key in `project`. For example,

lib/elixir_make/artefact.ex

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,14 @@ defmodule ElixirMake.Artefact do
6161
@doc """
6262
Returns the full path to the precompiled archive.
6363
"""
64-
def archive_path(config, target) do
65-
Path.join(cache_dir(), archive_filename(config, target))
64+
def archive_path(config, target, nif_version) do
65+
Path.join(cache_dir(), archive_filename(config, target, nif_version))
6666
end
6767

68-
defp archive_filename(config, target) do
68+
defp archive_filename(config, target, nif_version) do
6969
case config[:make_precompiler] do
7070
{:nif, _} ->
71-
"#{config[:app]}-nif-#{:erlang.system_info(:nif_version)}-#{target}-#{config[:version]}.tar.gz"
71+
"#{config[:app]}-nif-#{nif_version}-#{target}-#{config[:version]}.tar.gz"
7272

7373
{type, _} ->
7474
"#{config[:app]}-#{type}-#{target}-#{config[:version]}.tar.gz"
@@ -151,17 +151,43 @@ defmodule ElixirMake.Artefact do
151151
config[:make_precompiler_url] ||
152152
Mix.raise("`make_precompiler_url` is not specified in `project`")
153153

154-
Enum.map(targets, fn target ->
155-
archive_filename = archive_filename(config, target)
156-
{target, String.replace(url_template, "@{artefact_filename}", archive_filename)}
154+
nif_versions =
155+
config[:make_precompiler_nif_versions] ||
156+
[versions: ["#{:erlang.system_info(:nif_version)}"]]
157+
158+
Enum.reduce(targets, [], fn target, archives ->
159+
archive_filenames =
160+
Enum.reduce(nif_versions[:versions], [], fn nif_version, acc ->
161+
availability = nif_versions[:availability]
162+
163+
available? =
164+
if is_function(availability, 2) do
165+
availability.(target, nif_version)
166+
else
167+
true
168+
end
169+
170+
if available? do
171+
archive_filename = archive_filename(config, target, nif_version)
172+
173+
[
174+
{target, String.replace(url_template, "@{artefact_filename}", archive_filename)}
175+
| acc
176+
]
177+
else
178+
acc
179+
end
180+
end)
181+
182+
archive_filenames ++ archives
157183
end)
158184
end
159185

160186
@doc """
161187
Returns the url for the current target.
162188
"""
163-
def current_target_url(config, precompiler) do
164-
case precompiler.current_target() do
189+
def current_target_url(config, precompiler, nif_version) do
190+
case precompiler.current_target(nif_version) do
165191
{:ok, current_target} ->
166192
available_urls = available_target_urls(config, precompiler)
167193

lib/mix/tasks/compile.elixir_make.ex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,12 @@ defmodule Mix.Tasks.Compile.ElixirMake do
205205
end
206206

207207
defp download_or_reuse_nif(config, precompiler, app_priv) do
208-
case Artefact.current_target_url(config, precompiler) do
208+
# should we allow this value to be overwritten by an env var?
209+
nif_version = :erlang.system_info(:nif_version)
210+
211+
case Artefact.current_target_url(config, precompiler, nif_version) do
209212
{:ok, target, url} ->
210-
archived_fullpath = Artefact.archive_path(config, target)
213+
archived_fullpath = Artefact.archive_path(config, target, nif_version)
211214

212215
unless File.exists?(archived_fullpath) do
213216
Mix.shell().info("Downloading precompiled NIF to #{archived_fullpath}")

lib/mix/tasks/elixir_make.checksum.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ defmodule Mix.Tasks.ElixirMake.Checksum do
4646
Artefact.available_target_urls(config, precompiler)
4747

4848
Keyword.get(options, :only_local) ->
49-
case Artefact.current_target_url(config, precompiler) do
49+
case Artefact.current_target_url(config, precompiler, :erlang.system_info(:nif_version)) do
5050
{:ok, target, url} ->
5151
[{target, url}]
5252

lib/mix/tasks/elixir_make.precompile.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ defmodule Mix.Tasks.ElixirMake.Precompile do
6565
end
6666

6767
defp create_precompiled_archive(config, target, paths) do
68-
archive_path = Artefact.archive_path(config, target)
68+
archive_path = Artefact.archive_path(config, target, :erlang.system_info(:nif_version))
6969

7070
Mix.shell().info("Creating precompiled archive: #{archive_path}")
7171
Mix.shell().info("Paths to archive from priv directory: #{inspect(paths)}")

0 commit comments

Comments
 (0)