@@ -35,6 +35,24 @@ defmodule Mix.Tasks.Deps.Compile do
3535 recompiled without propagating those changes upstream. To ensure
3636 `b` is included in the compilation step, pass `--include-children`.
3737
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+
3856 ## Command line options
3957
4058 * `--force` - force compilation of deps
@@ -57,7 +75,6 @@ defmodule Mix.Tasks.Deps.Compile do
5775 end
5876
5977 Mix.Project . get! ( )
60-
6178 config = Mix.Project . config ( )
6279
6380 Mix.Project . with_build_lock ( config , fn ->
@@ -75,86 +92,82 @@ defmodule Mix.Tasks.Deps.Compile do
7592
7693 @ doc false
7794 def compile ( deps , options \\ [ ] ) do
78- shell = Mix . shell ( )
79- config = Mix.Project . deps_config ( )
8095 Mix.Task . run ( "deps.precompile" )
96+ force? = Keyword . get ( options , :force , false )
8197
82- compiled =
98+ deps =
8399 deps
84100 |> reject_umbrella_children ( options )
85101 |> 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 )
89102
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 ( )
94104
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
97113
98- Mix.Dep . make? ( dep ) ->
99- do_make ( dep , config )
114+ if compiled? , do: Mix.Task . run ( "will_recompile" ) , else: :ok
115+ end
100116
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 )
103121
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
109127
110- false
111- end
128+ compiled? =
129+ cond do
130+ not is_nil ( opts [ :compile ] ) ->
131+ do_compile ( dep , config )
112132
113- if compiled? do
114- build_path = Mix.Project . build_path ( config )
133+ Mix.Dep . mix? ( dep ) ->
134+ do_mix ( dep , config )
115135
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 )
123138
124- { :dep_compiled , info }
125- end
139+ dep . manager == :rebar3 ->
140+ do_rebar3 ( dep , config )
126141
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+ )
129147
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
134150
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 }
136163 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 ) ] ) )
146164 end
147- end
148165
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?
158171 end
159172
160173 defp check_unavailable! ( app , scm , { :unavailable , path } ) do
@@ -176,6 +189,17 @@ defmodule Mix.Tasks.Deps.Compile do
176189 :ok
177190 end
178191
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+
179203 defp do_mix ( dep , _config ) do
180204 Mix.Dep . in_dependency ( dep , fn _ ->
181205 config = Mix.Project . config ( )
0 commit comments