@@ -66,6 +66,15 @@ defmodule RustlerPrecompiled do
66
66
* `:max_retries` - The maximum of retries before giving up. Defaults to `3`.
67
67
Retries can be disabled with `0`.
68
68
69
+ * `:variants` - A map with alternative versions of a given target. This is useful to
70
+ support specific versions of dependencies, such as an old glibc version, or to support
71
+ restrict CPU features, like AVX on x86_64.
72
+
73
+ The order of variants matters, because the first one that returns `true` is going to be
74
+ selected. Example:
75
+
76
+ %{"x86_64-unknown-linux-gnu" => [old_glibc: fn _config -> has_old_glibc?() end]}
77
+
69
78
In case "force build" is used, all options except `:base_url`, `:version`,
70
79
`:force_build`, `:nif_versions`, and `:targets` are going to be passed down to `Rustler`.
71
80
So if you need to configure the build, check the `Rustler` options.
@@ -180,7 +189,8 @@ defmodule RustlerPrecompiled do
180
189
:force_build ,
181
190
:targets ,
182
191
:nif_versions ,
183
- :max_retries
192
+ :max_retries ,
193
+ :variants
184
194
] )
185
195
186
196
{ :force_build , rustler_opts }
@@ -225,11 +235,23 @@ defmodule RustlerPrecompiled do
225
235
is stored in a metadata file.
226
236
"""
227
237
def available_nif_urls ( nif_module ) when is_atom ( nif_module ) do
228
- metadata =
229
- nif_module
230
- |> metadata_file ( )
231
- |> read_map_from_file ( )
238
+ nif_module
239
+ |> metadata_file ( )
240
+ |> read_map_from_file ( )
241
+ |> nif_urls_from_metadata ( )
242
+ |> case do
243
+ { :ok , urls } ->
244
+ urls
245
+
246
+ { :error , wrong_meta } ->
247
+ raise "metadata about current target for the module #{ inspect ( nif_module ) } is not available. " <>
248
+ "Please compile the project again with: `mix compile --force` " <>
249
+ "Metadata found: #{ inspect ( wrong_meta , limit: :infinity , pretty: true ) } "
250
+ end
251
+ end
232
252
253
+ @ doc false
254
+ def nif_urls_from_metadata ( metadata ) when is_map ( metadata ) do
233
255
case metadata do
234
256
% {
235
257
targets: targets ,
@@ -238,42 +260,73 @@ defmodule RustlerPrecompiled do
238
260
nif_versions: nif_versions ,
239
261
version: version
240
262
} ->
241
- for target_triple <- targets , nif_version <- nif_versions do
242
- target = "nif-#{ nif_version } -#{ target_triple } "
263
+ all_tar_gzs =
264
+ for target_triple <- targets , nif_version <- nif_versions do
265
+ target = "nif-#{ nif_version } -#{ target_triple } "
243
266
244
- # We need to build again the name because each arch is different.
245
- lib_name = "#{ lib_prefix ( target ) } #{ basename } -v#{ version } -#{ target } "
267
+ # We need to build again the name because each arch is different.
268
+ lib_name = "#{ lib_prefix ( target ) } #{ basename } -v#{ version } -#{ target } "
269
+ file_name = lib_name_with_ext ( target_triple , lib_name )
246
270
247
- tar_gz_file_url ( base_url , lib_name_with_ext ( target , lib_name ) )
248
- end
271
+ tar_gz_urls ( base_url , file_name , target_triple , metadata [ :variants ] )
272
+ end
249
273
250
- _ ->
251
- raise "metadata about current target for the module #{ inspect ( nif_module ) } is not available. " <>
252
- "Please compile the project again with: `mix compile --force`"
274
+ { :ok , List . flatten ( all_tar_gzs ) }
275
+
276
+ wrong_meta ->
277
+ { :error , wrong_meta }
253
278
end
254
279
end
255
280
281
+ defp maybe_variants_tar_gz_urls ( nil , _ , _ , _ ) , do: [ ]
282
+
283
+ defp maybe_variants_tar_gz_urls ( variants , base_url , target_triple , lib_name )
284
+ when is_map_key ( variants , target_triple ) do
285
+ variants = Map . fetch! ( variants , target_triple )
286
+
287
+ for variant <- variants do
288
+ tar_gz_file_url (
289
+ base_url ,
290
+ lib_name_with_ext ( target_triple , lib_name <> "--" <> Atom . to_string ( variant ) )
291
+ )
292
+ end
293
+ end
294
+
295
+ defp maybe_variants_tar_gz_urls ( _ , _ , _ , _ ) , do: [ ]
296
+
256
297
@ doc """
257
- Returns the file URL to be downloaded for current target.
298
+ Returns the file URLs to be downloaded for current target.
258
299
300
+ It is in the plural because a target may have some variants for it.
259
301
It receives the NIF module.
260
302
"""
261
- def current_target_nif_url ( nif_module ) when is_atom ( nif_module ) do
303
+ def current_target_nif_urls ( nif_module ) when is_atom ( nif_module ) do
262
304
metadata =
263
305
nif_module
264
306
|> metadata_file ( )
265
307
|> read_map_from_file ( )
266
308
267
309
case metadata do
268
310
% { base_url: base_url , file_name: file_name } ->
269
- tar_gz_file_url ( base_url , file_name )
311
+ target_triple = target_triple_from_nif_target ( metadata [ :target ] )
312
+
313
+ tar_gz_urls ( base_url , file_name , target_triple , metadata [ :variants ] )
270
314
271
315
_ ->
272
316
raise "metadata about current target for the module #{ inspect ( nif_module ) } is not available. " <>
273
317
"Please compile the project again with: `mix compile --force`"
274
318
end
275
319
end
276
320
321
+ defp tar_gz_urls ( base_url , file_name , target_triple , variants ) do
322
+ [ lib_name , _ ] = String . split ( file_name , "." , parts: 2 )
323
+
324
+ [
325
+ tar_gz_file_url ( base_url , file_name )
326
+ | maybe_variants_tar_gz_urls ( variants , base_url , target_triple , lib_name )
327
+ ]
328
+ end
329
+
277
330
@ doc """
278
331
Returns the target triple for download or compile and load.
279
332
@@ -501,14 +554,19 @@ defmodule RustlerPrecompiled do
501
554
crate: config . crate ,
502
555
otp_app: config . otp_app ,
503
556
targets: config . targets ,
557
+ variants: variants_for_metadata ( config . variants ) ,
504
558
nif_versions: config . nif_versions ,
505
559
version: config . version
506
560
}
507
561
508
562
case target ( target_config ( config . nif_versions ) , config . targets , config . nif_versions ) do
509
563
{ :ok , target } ->
510
564
basename = config . crate || config . otp_app
511
- lib_name = "#{ lib_prefix ( target ) } #{ basename } -v#{ config . version } -#{ target } "
565
+
566
+ target_triple = target_triple_from_nif_target ( target )
567
+
568
+ variant = variant_suffix ( target_triple , config )
569
+ lib_name = "#{ lib_prefix ( target ) } #{ basename } -v#{ config . version } -#{ target } #{ variant } "
512
570
513
571
file_name = lib_name_with_ext ( target , lib_name )
514
572
@@ -534,6 +592,38 @@ defmodule RustlerPrecompiled do
534
592
end
535
593
end
536
594
595
+ defp variants_for_metadata ( variants ) do
596
+ Map . new ( variants , fn { target , values } -> { target , Keyword . keys ( values ) } end )
597
+ end
598
+
599
+ # Extract the target without the nif-NIF-VERSION part
600
+ defp target_triple_from_nif_target ( nif_target ) do
601
+ [ "nif" , _version , triple ] = String . split ( nif_target , "-" , parts: 3 )
602
+ triple
603
+ end
604
+
605
+ defp variant_suffix ( target , % { variants: variants } = config ) when is_map_key ( variants , target ) do
606
+ variants = Map . fetch! ( variants , target )
607
+
608
+ callback = fn { _name , func } ->
609
+ if is_function ( func , 1 ) do
610
+ func . ( config )
611
+ else
612
+ func . ( )
613
+ end
614
+ end
615
+
616
+ case Enum . find ( variants , callback ) do
617
+ { name , _ } ->
618
+ "--" <> Atom . to_string ( name )
619
+
620
+ nil ->
621
+ ""
622
+ end
623
+ end
624
+
625
+ defp variant_suffix ( _ , _ ) , do: ""
626
+
537
627
# Perform the download or load of the precompiled NIF
538
628
# It will look in the "priv/native/otp_app" first, and if
539
629
# that file doesn't exist, it will try to fetch from cache.
0 commit comments