@@ -8,46 +8,76 @@ defmodule Rustler.Compiler do
8
8
config = Config . from ( otp_app , config , opts )
9
9
10
10
if ! config . skip_compilation? do
11
- crate_full_path = Path . expand ( config . path , File . cwd! ( ) )
12
-
13
11
File . mkdir_p! ( priv_dir ( ) )
14
12
15
13
Mix . shell ( ) . info ( "Compiling crate #{ config . crate } in #{ config . mode } mode (#{ config . path } )" )
14
+ do_compile ( config )
15
+ Mix.Project . build_structure ( )
16
+
17
+ # See #326: Ensure that the libraries are copied into the correct subdirectory
18
+ # in `_build`.
19
+ missing = Enum . reject ( expected_files ( config ) , & File . exists? / 1 )
20
+
21
+ if missing != [ ] do
22
+ Mix . shell ( ) . info ( "Expected files do not exist: #{ inspect ( missing ) } , rebuilding" )
23
+
24
+ do_compile ( config , true )
25
+ Mix.Project . build_structure ( )
26
+ end
27
+
28
+ missing = Enum . reject ( expected_files ( config ) , & File . exists? / 1 )
29
+
30
+ if missing != [ ] do
31
+ raise "Expected files do still not exist: #{ inspect ( missing ) } \n Please verify your configuration"
32
+ end
33
+ end
16
34
17
- [ cmd | args ] =
18
- make_base_command ( config . cargo )
19
- |> make_no_default_features_flag ( config . default_features )
20
- |> make_features_flag ( config . features )
21
- |> make_target_flag ( config . target )
22
- |> make_build_mode_flag ( config . mode )
35
+ config
36
+ end
23
37
24
- ensure_platform_requirements! ( crate_full_path , config , :os . type ( ) )
38
+ defp do_compile ( config , recompile \\ false ) do
39
+ [ cmd | args ] =
40
+ rustc ( config . cargo )
41
+ |> build_flags ( config )
42
+ |> make_build_mode_flag ( config . mode )
43
+
44
+ args =
45
+ if recompile do
46
+ # HACK: Cargo does not allow forcing a recompilation, but we need the
47
+ # compiler-artifact messages, so force a recompilation via a "config
48
+ # change"
49
+ args ++ [ "--cfg" , "build_#{ System . system_time ( :millisecond ) } " ]
50
+ else
51
+ args
52
+ end
25
53
26
- compile_result =
27
- System . cmd ( cmd , args ,
28
- cd: crate_full_path ,
29
- stderr_to_stdout: true ,
30
- env: [ { "CARGO_TARGET_DIR" , config . target_dir } | config . env ] ,
31
- into: IO . stream ( :stdio , :line )
32
- )
54
+ compile_result =
55
+ System . cmd ( cmd , args , env: config . env , into: % Rustler.BuildResults { } , lines: 1024 )
33
56
57
+ artifacts =
34
58
case compile_result do
35
- { _ , 0 } -> :ok
59
+ { build_results , 0 } -> build_results . artifacts
36
60
{ _ , code } -> raise "Rust NIF compile error (rustc exit code #{ code } )"
37
61
end
38
62
39
- handle_artifacts ( crate_full_path , config )
40
- # See #326: Ensure that the libraries are copied into the correct subdirectory
41
- # in `_build`.
42
- Mix.Project . build_structure ( )
43
- end
63
+ handle_artifacts ( artifacts , config )
64
+ end
44
65
45
- config
66
+ defp build_flags ( args , config ) do
67
+ args
68
+ |> make_package_flag ( config . crate )
69
+ |> make_no_default_features_flag ( config . default_features )
70
+ |> make_features_flag ( config . features )
71
+ |> make_target_flag ( config . target )
46
72
end
47
73
48
- defp make_base_command ( :system ) , do: [ "cargo" , "rustc" ]
49
- defp make_base_command ( { :system , channel } ) , do: [ "cargo" , channel , "rustc" ]
50
- defp make_base_command ( { :bin , path } ) , do: [ path , "rustc" ]
74
+ defp rustc ( cargo ) do
75
+ make_base_command ( cargo ) ++ [ "rustc" , "--message-format=json-render-diagnostics" , "--quiet" ]
76
+ end
77
+
78
+ defp make_base_command ( :system ) , do: [ "cargo" ]
79
+ defp make_base_command ( { :system , channel } ) , do: [ "cargo" , channel ]
80
+ defp make_base_command ( { :bin , path } ) , do: [ path ]
51
81
52
82
defp make_base_command ( { :rustup , version } ) do
53
83
if Rustup . version ( ) == :none do
@@ -58,10 +88,10 @@ defmodule Rustler.Compiler do
58
88
throw_error ( { :rust_version_not_installed , version } )
59
89
end
60
90
61
- [ "rustup" , "run" , version , "cargo" , "rustc" ]
91
+ [ "rustup" , "run" , version , "cargo" ]
62
92
end
63
93
64
- defp ensure_platform_requirements! ( _ , _ , _ ) , do: :ok
94
+ defp make_package_flag ( args , crate ) , do: args ++ [ "-p" , " #{ crate } " ]
65
95
66
96
defp make_no_default_features_flag ( args , true ) , do: args
67
97
defp make_no_default_features_flag ( args , false ) , do: args ++ [ "--no-default-features" ]
@@ -99,71 +129,54 @@ defmodule Rustler.Compiler do
99
129
end
100
130
end
101
131
102
- defp handle_artifacts ( path , config ) do
103
- toml = toml_data ( path )
132
+ defp handle_artifacts ( artifacts , config ) do
104
133
target = config . target
105
- names = get_name ( toml , :lib ) ++ get_name ( toml , :bin )
106
134
107
- output_dir =
108
- if is_binary ( target ) do
109
- Path . join ( [ target , Atom . to_string ( config . mode ) ] )
110
- else
111
- Atom . to_string ( config . mode )
112
- end
135
+ if artifacts == [ ] do
136
+ throw_error ( :no_artifacts )
137
+ end
113
138
114
- Enum . each ( names , fn { name , type } ->
115
- { src_file , dst_file } = make_file_names ( name , type , target )
116
- compiled_lib = Path . join ( [ config . target_dir , output_dir , src_file ] )
117
- destination_lib = Path . join ( priv_dir ( ) , dst_file )
118
-
119
- # If the file exists already, we delete it first. This is to ensure that another
120
- # process, which might have the library dynamically linked in, does not generate
121
- # a segfault. By deleting it first, we ensure that the copy operation below does
122
- # not write into the existing file.
123
- File . rm ( destination_lib )
124
- File . cp! ( compiled_lib , destination_lib )
139
+ Enum . each ( artifacts , fn artifact = % Rustler.BuildArtifact { } ->
140
+ if artifact . kind == :lib or artifact . kind == :bin do
141
+ dst_file = output_file ( artifact . name , artifact . kind , target )
142
+ destination_lib = Path . join ( priv_dir ( ) , dst_file )
143
+
144
+ # If the file exists already, we delete it first. This is to ensure that another
145
+ # process, which might have the library dynamically linked in, does not generate
146
+ # a segfault. By deleting it first, we ensure that the copy operation below does
147
+ # not write into the existing file.
148
+ Mix . shell ( ) . info ( "Copying #{ artifact . filename } to #{ destination_lib } " )
149
+ File . rm ( destination_lib )
150
+ File . cp! ( artifact . filename , destination_lib )
151
+ end
125
152
end )
126
153
end
127
154
128
- defp get_name ( toml , section ) do
129
- case toml [ to_string ( section ) ] do
130
- nil -> [ ]
131
- values when is_map ( values ) -> [ { values [ "name" ] , section } ]
132
- values when is_list ( values ) -> Enum . map ( values , & { & 1 [ "name" ] , section } )
133
- end
134
- end
155
+ defp expected_files ( config ) do
156
+ lib = if config . lib , do: [ output_file ( config . crate , :lib , config . target ) ] , else: [ ]
157
+ bins = for bin <- config . binaries , do: output_file ( bin , :bin , config . target )
135
158
136
- defp get_suffix ( target , :lib ) do
137
- case get_target_os_type ( target ) do
138
- { :win32 , _ } -> "dll"
139
- { :unix , :darwin } -> "dylib"
140
- { :unix , _ } -> "so"
159
+ for filename <- lib ++ bins do
160
+ Path . join ( priv_dir ( ) , filename )
141
161
end
142
162
end
143
163
144
- defp get_suffix ( target , :bin ) do
145
- case get_target_os_type ( target ) do
146
- { :win32 , _ } -> "exe"
147
- { :unix , _ } -> ""
148
- end
164
+ defp output_file ( base_name , kind , target ) do
165
+ "#{ base_name } #{ suffix ( kind , target ) } "
149
166
end
150
167
151
- defp make_file_names ( base_name , :lib , target ) do
152
- suffix = get_suffix ( target , :lib )
153
-
168
+ defp suffix ( :lib , target ) do
154
169
case get_target_os_type ( target ) do
155
- { :win32 , _ } -> { " #{ base_name } . #{ suffix } " , "lib #{ base_name } . dll"}
156
- { :unix , :darwin } -> { "lib #{ base_name } . #{ suffix } " , "lib #{ base_name } . so"}
157
- { :unix , _ } -> { "lib #{ base_name } . #{ suffix } " , "lib #{ base_name } . so"}
170
+ { :win32 , _ } -> ". dll"
171
+ { :unix , :darwin } -> ". so"
172
+ { :unix , _ } -> ". so"
158
173
end
159
174
end
160
175
161
- defp make_file_names ( base_name , :bin , target ) do
162
- suffix = get_suffix ( target , :bin )
163
-
176
+ defp suffix ( :bin , target ) do
164
177
case get_target_os_type ( target ) do
165
- { :win32 , _ } -> { " #{ base_name } . #{ suffix } " , " #{ base_name } . exe"}
166
- { :unix , _ } -> { base_name , base_name }
178
+ { :win32 , _ } -> ". exe"
179
+ { :unix , _ } -> ""
167
180
end
168
181
end
169
182
@@ -172,19 +185,5 @@ defmodule Rustler.Compiler do
172
185
raise "Compilation error"
173
186
end
174
187
175
- defp toml_data ( path ) do
176
- if ! File . dir? ( path ) do
177
- throw_error ( { :nonexistent_crate_directory , path } )
178
- end
179
-
180
- case File . read ( "#{ path } /Cargo.toml" ) do
181
- { :error , :enoent } ->
182
- throw_error ( { :cargo_toml_not_found , path } )
183
-
184
- { :ok , text } ->
185
- Toml . decode! ( text )
186
- end
187
- end
188
-
189
188
defp priv_dir , do: "priv/native"
190
189
end
0 commit comments