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