11defmodule Mix.Compilers.Elixir do
22 @ moduledoc false
33
4- @ manifest_vsn 26
4+ @ manifest_vsn 27
55 @ checkpoint_vsn 2
66
77 import Record
@@ -46,9 +46,8 @@ defmodule Mix.Compilers.Elixir do
4646 all_paths = Mix.Utils . extract_files ( srcs , [ :ex ] )
4747
4848 { all_modules , all_sources , all_local_exports , old_parents , old_cache_key , old_deps_config ,
49- old_project_mtime ,
50- old_config_mtime } =
51- parse_manifest ( manifest , dest )
49+ old_project_mtime , old_config_mtime ,
50+ old_protocols_and_impls } = parse_manifest ( manifest , dest )
5251
5352 # Prepend ourselves early because of __mix_recompile__? checks
5453 # and also that, in case of nothing compiled, we already need
@@ -129,7 +128,7 @@ defmodule Mix.Compilers.Elixir do
129128 { false , stale , old_deps_config }
130129 end
131130
132- { stale_modules , stale_exports , all_local_exports } =
131+ { stale_modules , stale_exports , all_local_exports , protocols_and_impls } =
133132 stale_local_deps ( local_deps , manifest , stale , modified , all_local_exports )
134133
135134 prev_paths = Map . keys ( all_sources )
@@ -157,26 +156,41 @@ defmodule Mix.Compilers.Elixir do
157156 { sources , removed_modules } =
158157 update_stale_sources ( sources , stale , removed_modules , sources_stats )
159158
160- if stale != [ ] or stale_modules != % { } do
159+ consolidation_status =
160+ if Mix.Project . umbrella? ( ) do
161+ :off
162+ else
163+ Mix.Compilers.Protocol . status ( config_mtime > old_config_mtime , opts )
164+ end
165+
166+ if stale != [ ] or stale_modules != % { } or removed != [ ] or deps_changed? or
167+ consolidation_status == :force do
161168 path = opts [ :purge_consolidation_path_if_stale ]
162169
163170 if is_binary ( path ) and Code . delete_path ( path ) do
164171 purge_modules_in_path ( path )
165172 end
166173
167- Mix.Utils . compiling_n ( length ( stale ) , :ex )
174+ if stale != [ ] do
175+ Mix.Utils . compiling_n ( length ( stale ) , :ex )
176+ end
177+
168178 Mix.Project . ensure_structure ( )
169179
170180 # We don't want to cache this path as we will write to it
171181 true = Code . prepend_path ( dest )
172182 previous_opts = set_compiler_opts ( opts )
173183
184+ # Group consolidation information to pass to compiler
185+
174186 try do
175- state = { % { } , exports , sources , [ ] , modules , removed_modules }
187+ consolidation = { consolidation_status , old_protocols_and_impls , protocols_and_impls }
188+ state = { % { } , exports , sources , [ ] , modules , removed_modules , consolidation }
176189 compiler_loop ( stale , stale_modules , dest , timestamp , opts , state )
177190 else
178191 { :ok , % { runtime_warnings: runtime_warnings , compile_warnings: compile_warnings } , state } ->
179- { modules , _exports , sources , _changed , pending_modules , _stale_exports } = state
192+ { modules , _exports , sources , _changed , pending_modules , _stale_exports ,
193+ protocols_and_impls } = state
180194
181195 previous_warnings =
182196 if Keyword . get ( opts , :all_warnings , true ) ,
@@ -197,6 +211,7 @@ defmodule Mix.Compilers.Elixir do
197211 new_deps_config ,
198212 project_mtime ,
199213 config_mtime ,
214+ protocols_and_impls ,
200215 timestamp
201216 )
202217
@@ -208,12 +223,11 @@ defmodule Mix.Compilers.Elixir do
208223 end
209224
210225 Mix.Task.Compiler . notify_modules_compiled ( lazy_modules_diff )
211-
212226 unless_previous_warnings_as_errors ( previous_warnings , opts , { :ok , all_warnings } )
213227
214228 { :error , errors , % { runtime_warnings: r_warnings , compile_warnings: c_warnings } , state } ->
215229 # In case of errors, we show all previous warnings and all new ones.
216- { _ , _ , sources , _ , _ , _ } = state
230+ { _ , _ , sources , _ , _ , _ , _ } = state
217231 errors = Enum . map ( errors , & diagnostic / 1 )
218232 warnings = Enum . map ( r_warnings ++ c_warnings , & diagnostic / 1 )
219233 all_warnings = Keyword . get ( opts , :all_warnings , errors == [ ] )
@@ -222,40 +236,9 @@ defmodule Mix.Compilers.Elixir do
222236 Code . compiler_options ( previous_opts )
223237 end
224238 else
225- # We need to return ok if deps_changed? or stale_modules changed,
226- # even if no code was compiled, because we need to propagate the changed
227- # status to compile.protocols. This will be the case whenever:
228- #
229- # * the lock file or a config changes
230- # * any module in a path dependency changes
231- # * the mix.exs changes
232- # * the Erlang manifest updates (Erlang files are compiled)
233- #
234- # In the first case, we will consolidate from scratch. In the remaining, we
235- # will only compute the diff with current protocols. In fact, there is no
236- # need to reconsolidate if an Erlang file changes and it doesn't trigger
237- # any other change, but the diff check should be reasonably fast anyway.
238- status = if removed != [ ] or deps_changed? or stale_modules != % { } , do: :ok , else: :noop
239-
240- if status != :noop do
241- write_manifest (
242- manifest ,
243- modules ,
244- sources ,
245- all_local_exports ,
246- new_parents ,
247- new_cache_key ,
248- new_deps_config ,
249- project_mtime ,
250- config_mtime ,
251- timestamp
252- )
253- end
254-
255239 all_warnings = Keyword . get ( opts , :all_warnings , true )
256240 previous_warnings = previous_warnings ( sources , all_warnings )
257-
258- unless_previous_warnings_as_errors ( previous_warnings , opts , { status , previous_warnings } )
241+ unless_previous_warnings_as_errors ( previous_warnings , opts , { :noop , previous_warnings } )
259242 end
260243 end
261244
@@ -292,24 +275,6 @@ defmodule Mix.Compilers.Elixir do
292275 end )
293276 end
294277
295- @ doc """
296- Returns protocols and implementations for the given `manifest`.
297- """
298- def protocols_and_impls ( paths ) do
299- Enum . reduce ( paths , { % { } , % { } } , fn path , acc ->
300- { modules , _ } = read_manifest ( Path . join ( path , ".mix/compile.elixir" ) )
301-
302- Enum . reduce ( modules , acc , fn
303- { module , module ( kind: kind , timestamp: timestamp ) } , { protocols , impls } ->
304- case kind do
305- :protocol -> { Map . put ( protocols , module , timestamp ) , impls }
306- { :impl , protocol } -> { protocols , Map . put ( impls , module , protocol ) }
307- _ -> { protocols , impls }
308- end
309- end )
310- end )
311- end
312-
313278 @ doc """
314279 Reads the manifest for external consumption.
315280 """
@@ -328,7 +293,7 @@ defmodule Mix.Compilers.Elixir do
328293 Retrieves all diagnostics from the given manifest.
329294 """
330295 def diagnostics ( manifest , dest ) do
331- { _ , all_sources , _ , _ , _ , _ , _ , _ } = parse_manifest ( manifest , dest )
296+ { _ , all_sources , _ , _ , _ , _ , _ , _ , _ } = parse_manifest ( manifest , dest )
332297 previous_warnings ( all_sources , false )
333298 end
334299
@@ -619,8 +584,8 @@ defmodule Mix.Compilers.Elixir do
619584 for % { app: app , opts: opts } <- local_deps ,
620585 manifest = Path . join ( [ opts [ :build ] , ".mix" , base ] ) ,
621586 Mix.Utils . last_modified ( manifest ) > modified ,
622- reduce: { stale_modules , stale_modules , deps_exports } do
623- { modules , exports , deps_exports } ->
587+ reduce: { stale_modules , stale_modules , deps_exports , protocols_and_impls ( ) } do
588+ { modules , exports , deps_exports , protocols_and_impls } ->
624589 { manifest_modules , manifest_sources } = read_manifest ( manifest )
625590
626591 dep_modules =
@@ -673,7 +638,8 @@ defmodule Mix.Compilers.Elixir do
673638 modules = modules |> Map . merge ( dep_modules ) |> Map . merge ( removed )
674639 exports = Map . merge ( exports , removed )
675640 deps_exports = Map . put ( deps_exports , app , new_exports )
676- { modules , exports , deps_exports }
641+ protocols_and_impls = protocols_and_impls_from_modules ( modules , protocols_and_impls )
642+ { modules , exports , deps_exports , protocols_and_impls }
677643 end
678644 end
679645
@@ -879,7 +845,7 @@ defmodule Mix.Compilers.Elixir do
879845
880846 ## Manifest handling
881847
882- @ default_manifest { % { } , % { } , % { } , [ ] , nil , nil , 0 , 0 }
848+ @ default_manifest { % { } , % { } , % { } , [ ] , nil , nil , 0 , 0 , { % { } , % { } } }
883849
884850 # Similar to read_manifest, but for internal consumption and with data migration support.
885851 defp parse_manifest ( manifest , compile_path ) do
@@ -890,9 +856,9 @@ defmodule Mix.Compilers.Elixir do
890856 @ default_manifest
891857 else
892858 { @ manifest_vsn , modules , sources , local_exports , parent , cache_key , deps_config ,
893- project_mtime , config_mtime } ->
859+ project_mtime , config_mtime , protocols_and_impls } ->
894860 { modules , sources , local_exports , parent , cache_key , deps_config , project_mtime ,
895- config_mtime }
861+ config_mtime , protocols_and_impls }
896862
897863 # {vsn, %{module => record}, sources, ...} v22-?
898864 # {vsn, [module_record], sources, ...} v5-v21
@@ -946,6 +912,7 @@ defmodule Mix.Compilers.Elixir do
946912 deps_config ,
947913 project_mtime ,
948914 config_mtime ,
915+ protocols_and_impls ,
949916 timestamp
950917 ) do
951918 if modules == % { } and sources == % { } do
@@ -955,7 +922,7 @@ defmodule Mix.Compilers.Elixir do
955922
956923 term =
957924 { @ manifest_vsn , modules , sources , exports , parents , cache_key , deps_config , project_mtime ,
958- config_mtime }
925+ config_mtime , protocols_and_impls }
959926
960927 manifest_data = :erlang . term_to_binary ( term , [ :compressed ] )
961928 File . write! ( manifest , manifest_data )
@@ -1055,7 +1022,7 @@ defmodule Mix.Compilers.Elixir do
10551022 spawn_link ( fn ->
10561023 compile_opts = [
10571024 each_cycle: fn ->
1058- compiler_call ( parent , ref , { :each_cycle , stale_modules , dest , timestamp } )
1025+ compiler_call ( parent , ref , { :each_cycle , stale_modules , dest , timestamp , opts } )
10591026 end ,
10601027 each_file: fn file , lexical ->
10611028 compiler_call ( parent , ref , { :each_file , file , lexical , verbose } )
@@ -1092,8 +1059,8 @@ defmodule Mix.Compilers.Elixir do
10921059
10931060 defp compiler_loop ( ref , pid , state , cwd ) do
10941061 receive do
1095- { ^ ref , { :each_cycle , stale_modules , dest , timestamp } } ->
1096- { response , state } = each_cycle ( stale_modules , dest , timestamp , state )
1062+ { ^ ref , { :each_cycle , stale_modules , dest , timestamp , opts } } ->
1063+ { response , state } = each_cycle ( stale_modules , dest , timestamp , state , opts )
10971064 send ( pid , { ref , response } )
10981065 compiler_loop ( ref , pid , state , cwd )
10991066
@@ -1121,8 +1088,8 @@ defmodule Mix.Compilers.Elixir do
11211088 end
11221089 end
11231090
1124- defp each_cycle ( stale_modules , compile_path , timestamp , state ) do
1125- { modules , _exports , sources , changed , pending_modules , stale_exports } = state
1091+ defp each_cycle ( stale_modules , compile_path , timestamp , state , opts ) do
1092+ { modules , _exports , sources , changed , pending_modules , stale_exports , consolidation } = state
11261093
11271094 { pending_modules , exports , changed } =
11281095 update_stale_entries ( pending_modules , sources , changed , % { } , stale_exports , compile_path )
@@ -1165,7 +1132,8 @@ defmodule Mix.Compilers.Elixir do
11651132 runtime_paths =
11661133 Enum . map ( runtime_modules , & { & 1 , Path . join ( compile_path , Atom . to_string ( & 1 ) <> ".beam" ) } )
11671134
1168- state = { modules , exports , sources , [ ] , pending_modules , stale_exports }
1135+ protocols_and_impls = maybe_consolidate ( consolidation , modules , opts )
1136+ state = { modules , exports , sources , [ ] , pending_modules , stale_exports , protocols_and_impls }
11691137 { { :runtime , runtime_paths , [ ] } , state }
11701138 else
11711139 Mix.Utils . compiling_n ( length ( changed ) , :ex )
@@ -1192,7 +1160,7 @@ defmodule Mix.Compilers.Elixir do
11921160
11931161 defp each_file ( file , references , verbose , state , cwd ) do
11941162 { compile_references , export_references , runtime_references , compile_env } = references
1195- { modules , exports , sources , changed , pending_modules , stale_exports } = state
1163+ { modules , exports , sources , changed , pending_modules , stale_exports , consolidate } = state
11961164
11971165 file = Path . relative_to ( file , cwd )
11981166
@@ -1220,11 +1188,11 @@ defmodule Mix.Compilers.Elixir do
12201188 )
12211189
12221190 sources = Map . replace! ( sources , file , source )
1223- { modules , exports , sources , changed , pending_modules , stale_exports }
1191+ { modules , exports , sources , changed , pending_modules , stale_exports , consolidate }
12241192 end
12251193
12261194 defp each_module ( file , module , kind , external , new_export , state , timestamp , cwd ) do
1227- { modules , exports , sources , changed , pending_modules , stale_exports } = state
1195+ { modules , exports , sources , changed , pending_modules , stale_exports , consolidate } = state
12281196
12291197 file = Path . relative_to ( file , cwd )
12301198 external = process_external_resources ( external , cwd )
@@ -1277,7 +1245,7 @@ defmodule Mix.Compilers.Elixir do
12771245 % { } -> changed
12781246 end
12791247
1280- { modules , exports , sources , changed , pending_modules , stale_exports }
1248+ { modules , exports , sources , changed , pending_modules , stale_exports , consolidate }
12811249 end
12821250
12831251 defp detect_kind ( module ) do
@@ -1306,4 +1274,50 @@ defmodule Mix.Compilers.Elixir do
13061274 { Path . relative_to ( file , cwd ) , Mix.Utils . last_modified_and_size ( file ) , digest }
13071275 end
13081276 end
1277+
1278+ ## Consolidation
1279+
1280+ @ doc """
1281+ Returns protocols and implementations for the given `manifest`.
1282+ """
1283+ def protocols_and_impls_from_paths ( paths ) do
1284+ Enum . reduce ( paths , protocols_and_impls ( ) , fn path , acc ->
1285+ { modules , _ } = read_manifest ( Path . join ( path , ".mix/compile.elixir" ) )
1286+ protocols_and_impls_from_modules ( modules , acc )
1287+ end )
1288+ end
1289+
1290+ defp protocols_and_impls_from_modules ( modules , protocols_and_impls ) do
1291+ Enum . reduce ( modules , protocols_and_impls , fn
1292+ { module , module ( kind: kind , timestamp: timestamp ) } , { protocols , impls } ->
1293+ case kind do
1294+ :protocol -> { Map . put ( protocols , module , timestamp ) , impls }
1295+ { :impl , protocol } -> { protocols , Map . put ( impls , module , protocol ) }
1296+ _ -> { protocols , impls }
1297+ end
1298+ end )
1299+ end
1300+
1301+ defp protocols_and_impls ( ) , do: { % { } , % { } }
1302+
1303+ defp maybe_consolidate ( { :off , _ , _ } , _ , _ ) do
1304+ protocols_and_impls ( )
1305+ end
1306+
1307+ defp maybe_consolidate (
1308+ { on_or_force , old_protocols_and_impls , protocols_and_impls } ,
1309+ modules ,
1310+ opts
1311+ ) do
1312+ protocols_and_impls = protocols_and_impls_from_modules ( modules , protocols_and_impls )
1313+
1314+ Mix.Compilers.Protocol . compile (
1315+ on_or_force == :force ,
1316+ old_protocols_and_impls ,
1317+ protocols_and_impls ,
1318+ opts
1319+ )
1320+
1321+ protocols_and_impls
1322+ end
13091323end
0 commit comments