Skip to content

Commit e55e995

Browse files
authored
Support ports on precompiled and use consistent option naming (#64)
1 parent 2f30adf commit e55e995

File tree

6 files changed

+66
-40
lines changed

6 files changed

+66
-40
lines changed

PRECOMPILATION_GUIDE.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def deps do
2626
end
2727
```
2828

29-
Then add `:elixir_make` to the `compilers` list, and set `CCPrecompile` as the value for `make_precompiler`.
29+
Then add `:elixir_make` to the `compilers` list, and set the type (`:nif` or `:port`) and `CCPrecompile` as the value for `:make_precompiler`.
3030

3131
```elixir
3232
@version "0.1.0"
@@ -35,20 +35,20 @@ def project do
3535
# ...
3636
compilers: [:elixir_make] ++ Mix.compilers(),
3737
# elixir_make specific config
38-
make_precompiler: CCPrecompiler,
39-
make_precompiled_url: "https://github.com/cocoa-xu/cc_precompiler_example/releases/download/v#{@version}/@{artefact_filename}",
40-
make_nif_filename: "nif",
38+
make_precompiler: {:nif, CCPrecompiler},
39+
make_precompiler_url: "https://github.com/cocoa-xu/cc_precompiler_example/releases/download/v#{@version}/@{artefact_filename}",
40+
make_precompiler_filename: "nif",
4141
make_precompiler_priv_paths: ["nif.*"]
4242
# ...
4343
]
4444
end
4545
```
4646

47-
Another required field is `make_precompiled_url`. It is a URL template to the artefact file.
47+
Another required field is `make_precompiler_url`. It is a URL template to the artefact file.
4848

4949
`@{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`.
5050

51-
Note that there is an optional config key for elixir_make, `make_nif_filename`. If the name (file extension does not count) of the shared library is different from your app's name, then `make_nif_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_nif_filename` should be set as `"nif"`.
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"`.
5252

5353
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,
5454

lib/elixir_make/artefact.ex

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,13 @@ defmodule ElixirMake.Artefact do
6666
end
6767

6868
defp archive_filename(config, target) do
69-
"#{config[:app]}-nif-#{:erlang.system_info(:nif_version)}-#{target}-#{config[:version]}.tar.gz"
69+
case config[:make_precompiler] do
70+
{:nif, _} ->
71+
"#{config[:app]}-nif-#{:erlang.system_info(:nif_version)}-#{target}-#{config[:version]}.tar.gz"
72+
73+
{type, _} ->
74+
"#{config[:app]}-#{type}-#{target}-#{config[:version]}.tar.gz"
75+
end
7076
end
7177

7278
@doc """
@@ -138,12 +144,12 @@ defmodule ElixirMake.Artefact do
138144
@doc """
139145
Returns all available target-url pairs available.
140146
"""
141-
def available_nif_urls(config, precompiler) do
147+
def available_target_urls(config, precompiler) do
142148
targets = precompiler.all_supported_targets(:fetch)
143149

144150
url_template =
145-
config[:make_precompiled_url] ||
146-
Mix.raise("`make_precompiled_url` is not specified in `project`")
151+
config[:make_precompiler_url] ||
152+
Mix.raise("`make_precompiler_url` is not specified in `project`")
147153

148154
Enum.map(targets, fn target ->
149155
archive_filename = archive_filename(config, target)
@@ -154,10 +160,10 @@ defmodule ElixirMake.Artefact do
154160
@doc """
155161
Returns the url for the current target.
156162
"""
157-
def current_target_nif_url(config, precompiler) do
163+
def current_target_url(config, precompiler) do
158164
case precompiler.current_target() do
159165
{:ok, current_target} ->
160-
available_urls = available_nif_urls(config, precompiler)
166+
available_urls = available_target_urls(config, precompiler)
161167

162168
case List.keyfind(available_urls, current_target, 0) do
163169
{^current_target, download_url} ->

lib/mix/tasks/compile.elixir_make.ex

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,19 @@ defmodule Mix.Tasks.Compile.ElixirMake do
5656
5757
The following options configure precompilation:
5858
59-
* `:make_precompiler` - the precompiled module to use. Defaults to none.
60-
61-
* `:make_precompiled_url` - the download URL template. Defaults to none.
59+
* `:make_precompiler` - a two-element tuple with the precompiled type
60+
and module to use. The precompile type is either `:nif` or `:port`
61+
and then the precompilation module. If the type is a `:nif`, it looks
62+
for a DDL or a shared object as precompilation target given by
63+
`:make_precompiler_filename` and the current NIF version is part of
64+
the precompiled archive. If `:port`, it looks for an executable with
65+
`:make_precompiler_filename`.
66+
67+
* `:make_precompiler_url` - the download URL template. Defaults to none.
6268
Required when `make_precompiler` is set.
6369
64-
* `:make_nif_filename` - the filename of the compiled NIF without extension.
65-
Defaults to the app name.
70+
* `:make_precompiler_filename` - the filename of the compiled artefact
71+
without its extension. Defaults to the app name.
6672
6773
* `:make_force_build` - if build should be forced even if precompiled artefacts
6874
are available. Defaults to true if the app has a `-dev` version flag.
@@ -118,7 +124,7 @@ defmodule Mix.Tasks.Compile.ElixirMake do
118124
app = config[:app]
119125
version = config[:version]
120126
force_build = pre_release?(version) or Keyword.get(config, :make_force_build, false)
121-
precompiler = config[:make_precompiler]
127+
{precompiler_type, precompiler} = config[:make_precompiler] || {nil, nil}
122128

123129
cond do
124130
precompiler == nil ->
@@ -128,19 +134,24 @@ defmodule Mix.Tasks.Compile.ElixirMake do
128134
precompiler.build_native(args)
129135

130136
true ->
131-
nif_filename = config[:make_nif_filename] || "#{app}"
132-
app_priv = Path.join(Mix.Project.app_path(config), "priv")
133-
134-
load_path =
135-
case :os.type() do
136-
{:win32, _} -> Path.join(app_priv, "#{nif_filename}.dll")
137-
_ -> Path.join(app_priv, "#{nif_filename}.so")
137+
rootname = config[:make_precompiler_filename] || "#{app}"
138+
139+
extname =
140+
case {precompiler_type, :os.type()} do
141+
{:nif, {:win32, _}} -> ".dll"
142+
{:nif, _} -> ".so"
143+
{:port, {:win32, _}} -> ".exe"
144+
{:port, _} -> ""
145+
{_, _} -> raise_unknown_precompiler_type(precompiler_type)
138146
end
139147

148+
app_priv = Path.join(Mix.Project.app_path(config), "priv")
149+
load_path = Path.join(app_priv, rootname <> extname)
150+
140151
with false <- File.exists?(load_path),
141-
{:error, precomp_error} <- download_or_reuse_nif(config, precompiler, app_priv) do
152+
{:error, message} <- download_or_reuse_nif(config, precompiler, app_priv) do
142153
Mix.shell().error("""
143-
Error happened while installing #{app} from precompiled binary: #{precomp_error}.
154+
Error happened while installing #{app} from precompiled binary: #{message}.
144155
145156
Attempting to compile #{app} from source...\
146157
""")
@@ -152,6 +163,10 @@ defmodule Mix.Tasks.Compile.ElixirMake do
152163
end
153164
end
154165

166+
defp raise_unknown_precompiler_type(precompiler_type) do
167+
Mix.raise("Unknown precompiler type: #{inspect(precompiler_type)} (expected :nif or :port)")
168+
end
169+
155170
# This is called by Elixir when `mix clean` runs
156171
# and `:elixir_make` is in the list of compilers.
157172
@doc false
@@ -171,7 +186,7 @@ defmodule Mix.Tasks.Compile.ElixirMake do
171186
end
172187

173188
defp download_or_reuse_nif(config, precompiler, app_priv) do
174-
case Artefact.current_target_nif_url(config, precompiler) do
189+
case Artefact.current_target_url(config, precompiler) do
175190
{:ok, target, url} ->
176191
archived_fullpath = Artefact.archive_path(config, target)
177192

lib/mix/tasks/elixir_make.checksum.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ defmodule Mix.Tasks.ElixirMake.Checksum do
3232
def run(flags) when is_list(flags) do
3333
config = Mix.Project.config()
3434

35-
precompiler =
35+
{_, precompiler} =
3636
config[:make_precompiler] ||
3737
Mix.raise(
3838
":make_precompiler project configuration is required when using elixir_make.checksum"
@@ -43,10 +43,10 @@ defmodule Mix.Tasks.ElixirMake.Checksum do
4343
urls =
4444
cond do
4545
Keyword.get(options, :all) ->
46-
Artefact.available_nif_urls(config, precompiler)
46+
Artefact.available_target_urls(config, precompiler)
4747

4848
Keyword.get(options, :only_local) ->
49-
case Artefact.current_target_nif_url(config, precompiler) do
49+
case Artefact.current_target_url(config, precompiler) do
5050
{:ok, target, url} -> [{target, url}]
5151
{:error, error} -> Mix.raise(error)
5252
end

lib/mix/tasks/elixir_make.precompile.ex

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ defmodule Mix.Tasks.ElixirMake.Precompile do
44
@moduledoc """
55
Precompiles the given project for all targets.
66
7-
This is only supported if `make_precompiler` is specified.
7+
This task must only be used by package creators who want to ship the
8+
precompiled NIFs. This task is often used on CI to precompile
9+
for different targets.
10+
11+
This is only supported if `:make_precompiler` is specified
12+
in your project configuration.
813
"""
914

1015
alias ElixirMake.Artefact
@@ -16,15 +21,15 @@ defmodule Mix.Tasks.ElixirMake.Precompile do
1621
config = Mix.Project.config()
1722
paths = config[:make_precompiler_priv_paths] || ["."]
1823

19-
try do
20-
precompiler =
21-
config[:make_precompiler] ||
22-
Mix.raise(
23-
":make_precompiler project configuration is required when using elixir_make.precompile"
24-
)
24+
{_, precompiler} =
25+
config[:make_precompiler] ||
26+
Mix.raise(
27+
":make_precompiler project configuration is required when using elixir_make.precompile"
28+
)
2529

26-
targets = precompiler.all_supported_targets(:compile)
30+
targets = precompiler.all_supported_targets(:compile)
2731

32+
try do
2833
precompiled_artefacts =
2934
Enum.map(targets, fn target ->
3035
case precompiler.precompile(args, target) do

test/mix/tasks/compile.make_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ defmodule Mix.Tasks.Compile.ElixirMakeTest do
318318
build_file = "build_file"
319319

320320
precompile_config = [
321-
make_precompiler: MyApp.Precompiler,
321+
make_precompiler: {:nif, MyApp.Precompiler},
322322
make_precompiler_priv_paths: ["include_this*.txt", "symlink_to_lib", "lib", build_file],
323323
make_force_build: true
324324
]

0 commit comments

Comments
 (0)