@@ -35,6 +35,24 @@ defmodule Mix.Tasks.Deps.Compile do
35
35
recompiled without propagating those changes upstream. To ensure
36
36
`b` is included in the compilation step, pass `--include-children`.
37
37
38
+ ## Compiling dependencies across multiple OS processes
39
+
40
+ If you set the environment variable `MIX_OS_DEPS_COMPILE_PARTITION_COUNT`
41
+ to a number greater than 1, Mix will start multiple operating system
42
+ processes to compile your dependencies concurrently.
43
+
44
+ While Mix and Rebar compile all files within a given project in parallel,
45
+ enabling this environment variable can still yield useful gains in several
46
+ cases, such as when compiling dependencies with native code, dependencies
47
+ that must download assets, or dependencies where the compilation time is not
48
+ evenly distributed (for example, one file takes much longer to compile than
49
+ all others).
50
+
51
+ While most configuration in Mix is done via command line flags, this particular
52
+ environment variable exists because the best number will vary per machine
53
+ (and often per project too). The environment variable also makes it more accessible
54
+ to enable concurrent compilation in CI and also during `Mix.install/2` commands.
55
+
38
56
## Command line options
39
57
40
58
* `--force` - force compilation of deps
@@ -57,7 +75,6 @@ defmodule Mix.Tasks.Deps.Compile do
57
75
end
58
76
59
77
Mix.Project . get! ( )
60
-
61
78
config = Mix.Project . config ( )
62
79
63
80
Mix.Project . with_build_lock ( config , fn ->
@@ -75,86 +92,82 @@ defmodule Mix.Tasks.Deps.Compile do
75
92
76
93
@ doc false
77
94
def compile ( deps , options \\ [ ] ) do
78
- shell = Mix . shell ( )
79
- config = Mix.Project . deps_config ( )
80
95
Mix.Task . run ( "deps.precompile" )
96
+ force? = Keyword . get ( options , :force , false )
81
97
82
- compiled =
98
+ deps =
83
99
deps
84
100
|> reject_umbrella_children ( options )
85
101
|> reject_local_deps ( options )
86
- |> Enum . map ( fn % Mix.Dep { app: app , status: status , opts: opts , scm: scm } = dep ->
87
- check_unavailable! ( app , scm , status )
88
- maybe_clean ( dep , options )
89
102
90
- compiled? =
91
- cond do
92
- not is_nil ( opts [ :compile ] ) ->
93
- do_compile ( dep , config )
103
+ count = System . get_env ( "MIX_OS_DEPS_COMPILE_PARTITION_COUNT" , "0" ) |> String . to_integer ( )
94
104
95
- Mix.Dep . mix? ( dep ) ->
96
- do_mix ( dep , config )
105
+ compiled? =
106
+ if count > 1 and length ( deps ) > 1 do
107
+ Mix . shell ( ) . info ( "mix deps.compile running across #{ count } OS processes" )
108
+ Mix.Tasks.Deps.Partition . server ( deps , count , force? )
109
+ else
110
+ config = Mix.Project . deps_config ( )
111
+ true in Enum . map ( deps , & compile_single ( & 1 , force? , config ) )
112
+ end
97
113
98
- Mix.Dep . make? ( dep ) ->
99
- do_make ( dep , config )
114
+ if compiled? , do: Mix.Task . run ( "will_recompile" ) , else: :ok
115
+ end
100
116
101
- dep . manager == :rebar3 ->
102
- do_rebar3 ( dep , config )
117
+ @ doc false
118
+ def compile_single ( % Mix.Dep { } = dep , force? , config ) do
119
+ % { app: app , status: status , opts: opts , scm: scm } = dep
120
+ check_unavailable! ( app , scm , status )
103
121
104
- true ->
105
- shell . error (
106
- "Could not compile #{ inspect ( app ) } , no \" mix.exs \" , \" rebar.config \" or \" Makefile \" " <>
107
- "(pass :compile as an option to customize compilation, set it to \" false \" to do nothing)"
108
- )
122
+ # If a dependency was marked as fetched or with an out of date lock
123
+ # or missing the app file, we always compile it from scratch.
124
+ if force? or Mix.Dep . compilable? ( dep ) do
125
+ File . rm_rf! ( Path . join ( [ Mix.Project . build_path ( ) , "lib" , Atom . to_string ( dep . app ) ] ) )
126
+ end
109
127
110
- false
111
- end
128
+ compiled? =
129
+ cond do
130
+ not is_nil ( opts [ :compile ] ) ->
131
+ do_compile ( dep , config )
112
132
113
- if compiled? do
114
- build_path = Mix.Project . build_path ( config )
133
+ Mix.Dep . mix? ( dep ) ->
134
+ do_mix ( dep , config )
115
135
116
- lazy_message = fn ->
117
- info = % {
118
- app: dep . app ,
119
- scm: dep . scm ,
120
- manager: dep . manager ,
121
- os_pid: System . pid ( )
122
- }
136
+ Mix.Dep . make? ( dep ) ->
137
+ do_make ( dep , config )
123
138
124
- { :dep_compiled , info }
125
- end
139
+ dep . manager == :rebar3 ->
140
+ do_rebar3 ( dep , config )
126
141
127
- Mix.Sync.PubSub . broadcast ( build_path , lazy_message )
128
- end
142
+ true ->
143
+ Mix . shell ( ) . error (
144
+ "Could not compile #{ inspect ( app ) } , no \" mix.exs\" , \" rebar.config\" or \" Makefile\" " <>
145
+ "(pass :compile as an option to customize compilation, set it to \" false\" to do nothing)"
146
+ )
129
147
130
- # We should touch fetchable dependencies even if they
131
- # did not compile otherwise they will always be marked
132
- # as stale, even when there is nothing to do.
133
- fetchable? = touch_fetchable ( scm , opts [ :build ] )
148
+ false
149
+ end
134
150
135
- compiled? and fetchable?
151
+ if compiled? do
152
+ config
153
+ |> Mix.Project . build_path ( )
154
+ |> Mix.Sync.PubSub . broadcast ( fn ->
155
+ info = % {
156
+ app: dep . app ,
157
+ scm: dep . scm ,
158
+ manager: dep . manager ,
159
+ os_pid: System . pid ( )
160
+ }
161
+
162
+ { :dep_compiled , info }
136
163
end )
137
-
138
- if true in compiled , do: Mix.Task . run ( "will_recompile" ) , else: :ok
139
- end
140
-
141
- defp maybe_clean ( dep , opts ) do
142
- # If a dependency was marked as fetched or with an out of date lock
143
- # or missing the app file, we always compile it from scratch.
144
- if Keyword . get ( opts , :force , false ) or Mix.Dep . compilable? ( dep ) do
145
- File . rm_rf! ( Path . join ( [ Mix.Project . build_path ( ) , "lib" , Atom . to_string ( dep . app ) ] ) )
146
164
end
147
- end
148
165
149
- defp touch_fetchable ( scm , path ) do
150
- if scm . fetchable? ( ) do
151
- path = Path . join ( path , ".mix" )
152
- File . mkdir_p! ( path )
153
- File . touch! ( Path . join ( path , "compile.fetch" ) )
154
- true
155
- else
156
- false
157
- end
166
+ # We should touch fetchable dependencies even if they
167
+ # did not compile otherwise they will always be marked
168
+ # as stale, even when there is nothing to do.
169
+ fetchable? = touch_fetchable ( scm , opts [ :build ] )
170
+ compiled? and fetchable?
158
171
end
159
172
160
173
defp check_unavailable! ( app , scm , { :unavailable , path } ) do
@@ -176,6 +189,17 @@ defmodule Mix.Tasks.Deps.Compile do
176
189
:ok
177
190
end
178
191
192
+ defp touch_fetchable ( scm , path ) do
193
+ if scm . fetchable? ( ) do
194
+ path = Path . join ( path , ".mix" )
195
+ File . mkdir_p! ( path )
196
+ File . touch! ( Path . join ( path , "compile.fetch" ) )
197
+ true
198
+ else
199
+ false
200
+ end
201
+ end
202
+
179
203
defp do_mix ( dep , _config ) do
180
204
Mix.Dep . in_dependency ( dep , fn _ ->
181
205
config = Mix.Project . config ( )
0 commit comments