You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

478 line
19 KiB

Split up the compiler DAG This is another tricky commit towards replacing the current module analysis with EPP. The compiler DAG is now being shared across multiple applications being compiled, rather than a per-application basis, which promises to allow better ordering, parallelism, and more thorough invalidation of runtime dependencies when they are modified. This however required changes: - The compiler DAG is no longer private to `rebar_compiler`, and has been extracted to the `rebar_compiler_dag` module - The compiler DAG is now started by the `rebar_prv_compile` module, which oversees the calls to `rebar_compiler` for each OTP application - The compiler DAG has been refactored to use a "dirty flag" to know if it was modified, rather than just tracking modifications in a functional manner, since the scope change (going multi-app) makes it impossible to cleanly use the functional approach without much larger changes - The DAG used to be cached within each OTP application. This is no longer possible since it is shared. Instead the DAG is stored in the state's deps_dir, which allows to cleanly split caches between regular apps for the user's project and plugins - The DAG supported a "label" mode that was used to store distinct DAGs for extra_src_dir runs and regular modules; this label is now used (and extended to `rebar_prv_compile` internals) to distinguish between "compile runs", such as "project_apps", or just "apps" (deps). The label is optional (i.e. not used by plugins which have no such need) - The extra_src_dirs for each app is now compiled using the main app's DAG, but the run takes place later in the compilation process. This may need changing to detect and prevent dependencies from src_dirs into extra_src_dirs, but this should not technically be a problem for runtime anyway. - Reworked the support for extra_src_dirs that are at the root of an umbrella project (and therefore do not belong to any single app) to use the new structure, also as part of the project_apps DAG. All tests keep passing, and this puts us in a better place to use EPP with cross-app support in the near-future.
5 年之前
Split up the compiler DAG This is another tricky commit towards replacing the current module analysis with EPP. The compiler DAG is now being shared across multiple applications being compiled, rather than a per-application basis, which promises to allow better ordering, parallelism, and more thorough invalidation of runtime dependencies when they are modified. This however required changes: - The compiler DAG is no longer private to `rebar_compiler`, and has been extracted to the `rebar_compiler_dag` module - The compiler DAG is now started by the `rebar_prv_compile` module, which oversees the calls to `rebar_compiler` for each OTP application - The compiler DAG has been refactored to use a "dirty flag" to know if it was modified, rather than just tracking modifications in a functional manner, since the scope change (going multi-app) makes it impossible to cleanly use the functional approach without much larger changes - The DAG used to be cached within each OTP application. This is no longer possible since it is shared. Instead the DAG is stored in the state's deps_dir, which allows to cleanly split caches between regular apps for the user's project and plugins - The DAG supported a "label" mode that was used to store distinct DAGs for extra_src_dir runs and regular modules; this label is now used (and extended to `rebar_prv_compile` internals) to distinguish between "compile runs", such as "project_apps", or just "apps" (deps). The label is optional (i.e. not used by plugins which have no such need) - The extra_src_dirs for each app is now compiled using the main app's DAG, but the run takes place later in the compilation process. This may need changing to detect and prevent dependencies from src_dirs into extra_src_dirs, but this should not technically be a problem for runtime anyway. - Reworked the support for extra_src_dirs that are at the root of an umbrella project (and therefore do not belong to any single app) to use the new structure, also as part of the project_apps DAG. All tests keep passing, and this puts us in a better place to use EPP with cross-app support in the near-future.
5 年之前
Split up the compiler DAG This is another tricky commit towards replacing the current module analysis with EPP. The compiler DAG is now being shared across multiple applications being compiled, rather than a per-application basis, which promises to allow better ordering, parallelism, and more thorough invalidation of runtime dependencies when they are modified. This however required changes: - The compiler DAG is no longer private to `rebar_compiler`, and has been extracted to the `rebar_compiler_dag` module - The compiler DAG is now started by the `rebar_prv_compile` module, which oversees the calls to `rebar_compiler` for each OTP application - The compiler DAG has been refactored to use a "dirty flag" to know if it was modified, rather than just tracking modifications in a functional manner, since the scope change (going multi-app) makes it impossible to cleanly use the functional approach without much larger changes - The DAG used to be cached within each OTP application. This is no longer possible since it is shared. Instead the DAG is stored in the state's deps_dir, which allows to cleanly split caches between regular apps for the user's project and plugins - The DAG supported a "label" mode that was used to store distinct DAGs for extra_src_dir runs and regular modules; this label is now used (and extended to `rebar_prv_compile` internals) to distinguish between "compile runs", such as "project_apps", or just "apps" (deps). The label is optional (i.e. not used by plugins which have no such need) - The extra_src_dirs for each app is now compiled using the main app's DAG, but the run takes place later in the compilation process. This may need changing to detect and prevent dependencies from src_dirs into extra_src_dirs, but this should not technically be a problem for runtime anyway. - Reworked the support for extra_src_dirs that are at the root of an umbrella project (and therefore do not belong to any single app) to use the new structure, also as part of the project_apps DAG. All tests keep passing, and this puts us in a better place to use EPP with cross-app support in the near-future.
5 年之前
Split up the compiler DAG This is another tricky commit towards replacing the current module analysis with EPP. The compiler DAG is now being shared across multiple applications being compiled, rather than a per-application basis, which promises to allow better ordering, parallelism, and more thorough invalidation of runtime dependencies when they are modified. This however required changes: - The compiler DAG is no longer private to `rebar_compiler`, and has been extracted to the `rebar_compiler_dag` module - The compiler DAG is now started by the `rebar_prv_compile` module, which oversees the calls to `rebar_compiler` for each OTP application - The compiler DAG has been refactored to use a "dirty flag" to know if it was modified, rather than just tracking modifications in a functional manner, since the scope change (going multi-app) makes it impossible to cleanly use the functional approach without much larger changes - The DAG used to be cached within each OTP application. This is no longer possible since it is shared. Instead the DAG is stored in the state's deps_dir, which allows to cleanly split caches between regular apps for the user's project and plugins - The DAG supported a "label" mode that was used to store distinct DAGs for extra_src_dir runs and regular modules; this label is now used (and extended to `rebar_prv_compile` internals) to distinguish between "compile runs", such as "project_apps", or just "apps" (deps). The label is optional (i.e. not used by plugins which have no such need) - The extra_src_dirs for each app is now compiled using the main app's DAG, but the run takes place later in the compilation process. This may need changing to detect and prevent dependencies from src_dirs into extra_src_dirs, but this should not technically be a problem for runtime anyway. - Reworked the support for extra_src_dirs that are at the root of an umbrella project (and therefore do not belong to any single app) to use the new structure, also as part of the project_apps DAG. All tests keep passing, and this puts us in a better place to use EPP with cross-app support in the near-future.
5 年之前
Split up the compiler DAG This is another tricky commit towards replacing the current module analysis with EPP. The compiler DAG is now being shared across multiple applications being compiled, rather than a per-application basis, which promises to allow better ordering, parallelism, and more thorough invalidation of runtime dependencies when they are modified. This however required changes: - The compiler DAG is no longer private to `rebar_compiler`, and has been extracted to the `rebar_compiler_dag` module - The compiler DAG is now started by the `rebar_prv_compile` module, which oversees the calls to `rebar_compiler` for each OTP application - The compiler DAG has been refactored to use a "dirty flag" to know if it was modified, rather than just tracking modifications in a functional manner, since the scope change (going multi-app) makes it impossible to cleanly use the functional approach without much larger changes - The DAG used to be cached within each OTP application. This is no longer possible since it is shared. Instead the DAG is stored in the state's deps_dir, which allows to cleanly split caches between regular apps for the user's project and plugins - The DAG supported a "label" mode that was used to store distinct DAGs for extra_src_dir runs and regular modules; this label is now used (and extended to `rebar_prv_compile` internals) to distinguish between "compile runs", such as "project_apps", or just "apps" (deps). The label is optional (i.e. not used by plugins which have no such need) - The extra_src_dirs for each app is now compiled using the main app's DAG, but the run takes place later in the compilation process. This may need changing to detect and prevent dependencies from src_dirs into extra_src_dirs, but this should not technically be a problem for runtime anyway. - Reworked the support for extra_src_dirs that are at the root of an umbrella project (and therefore do not belong to any single app) to use the new structure, also as part of the project_apps DAG. All tests keep passing, and this puts us in a better place to use EPP with cross-app support in the near-future.
5 年之前
Split up the compiler DAG This is another tricky commit towards replacing the current module analysis with EPP. The compiler DAG is now being shared across multiple applications being compiled, rather than a per-application basis, which promises to allow better ordering, parallelism, and more thorough invalidation of runtime dependencies when they are modified. This however required changes: - The compiler DAG is no longer private to `rebar_compiler`, and has been extracted to the `rebar_compiler_dag` module - The compiler DAG is now started by the `rebar_prv_compile` module, which oversees the calls to `rebar_compiler` for each OTP application - The compiler DAG has been refactored to use a "dirty flag" to know if it was modified, rather than just tracking modifications in a functional manner, since the scope change (going multi-app) makes it impossible to cleanly use the functional approach without much larger changes - The DAG used to be cached within each OTP application. This is no longer possible since it is shared. Instead the DAG is stored in the state's deps_dir, which allows to cleanly split caches between regular apps for the user's project and plugins - The DAG supported a "label" mode that was used to store distinct DAGs for extra_src_dir runs and regular modules; this label is now used (and extended to `rebar_prv_compile` internals) to distinguish between "compile runs", such as "project_apps", or just "apps" (deps). The label is optional (i.e. not used by plugins which have no such need) - The extra_src_dirs for each app is now compiled using the main app's DAG, but the run takes place later in the compilation process. This may need changing to detect and prevent dependencies from src_dirs into extra_src_dirs, but this should not technically be a problem for runtime anyway. - Reworked the support for extra_src_dirs that are at the root of an umbrella project (and therefore do not belong to any single app) to use the new structure, also as part of the project_apps DAG. All tests keep passing, and this puts us in a better place to use EPP with cross-app support in the near-future.
5 年之前
5 年之前
Split up the compiler DAG This is another tricky commit towards replacing the current module analysis with EPP. The compiler DAG is now being shared across multiple applications being compiled, rather than a per-application basis, which promises to allow better ordering, parallelism, and more thorough invalidation of runtime dependencies when they are modified. This however required changes: - The compiler DAG is no longer private to `rebar_compiler`, and has been extracted to the `rebar_compiler_dag` module - The compiler DAG is now started by the `rebar_prv_compile` module, which oversees the calls to `rebar_compiler` for each OTP application - The compiler DAG has been refactored to use a "dirty flag" to know if it was modified, rather than just tracking modifications in a functional manner, since the scope change (going multi-app) makes it impossible to cleanly use the functional approach without much larger changes - The DAG used to be cached within each OTP application. This is no longer possible since it is shared. Instead the DAG is stored in the state's deps_dir, which allows to cleanly split caches between regular apps for the user's project and plugins - The DAG supported a "label" mode that was used to store distinct DAGs for extra_src_dir runs and regular modules; this label is now used (and extended to `rebar_prv_compile` internals) to distinguish between "compile runs", such as "project_apps", or just "apps" (deps). The label is optional (i.e. not used by plugins which have no such need) - The extra_src_dirs for each app is now compiled using the main app's DAG, but the run takes place later in the compilation process. This may need changing to detect and prevent dependencies from src_dirs into extra_src_dirs, but this should not technically be a problem for runtime anyway. - Reworked the support for extra_src_dirs that are at the root of an umbrella project (and therefore do not belong to any single app) to use the new structure, also as part of the project_apps DAG. All tests keep passing, and this puts us in a better place to use EPP with cross-app support in the near-future.
5 年之前
Split up the compiler DAG This is another tricky commit towards replacing the current module analysis with EPP. The compiler DAG is now being shared across multiple applications being compiled, rather than a per-application basis, which promises to allow better ordering, parallelism, and more thorough invalidation of runtime dependencies when they are modified. This however required changes: - The compiler DAG is no longer private to `rebar_compiler`, and has been extracted to the `rebar_compiler_dag` module - The compiler DAG is now started by the `rebar_prv_compile` module, which oversees the calls to `rebar_compiler` for each OTP application - The compiler DAG has been refactored to use a "dirty flag" to know if it was modified, rather than just tracking modifications in a functional manner, since the scope change (going multi-app) makes it impossible to cleanly use the functional approach without much larger changes - The DAG used to be cached within each OTP application. This is no longer possible since it is shared. Instead the DAG is stored in the state's deps_dir, which allows to cleanly split caches between regular apps for the user's project and plugins - The DAG supported a "label" mode that was used to store distinct DAGs for extra_src_dir runs and regular modules; this label is now used (and extended to `rebar_prv_compile` internals) to distinguish between "compile runs", such as "project_apps", or just "apps" (deps). The label is optional (i.e. not used by plugins which have no such need) - The extra_src_dirs for each app is now compiled using the main app's DAG, but the run takes place later in the compilation process. This may need changing to detect and prevent dependencies from src_dirs into extra_src_dirs, but this should not technically be a problem for runtime anyway. - Reworked the support for extra_src_dirs that are at the root of an umbrella project (and therefore do not belong to any single app) to use the new structure, also as part of the project_apps DAG. All tests keep passing, and this puts us in a better place to use EPP with cross-app support in the near-future.
5 年之前
Split up the compiler DAG This is another tricky commit towards replacing the current module analysis with EPP. The compiler DAG is now being shared across multiple applications being compiled, rather than a per-application basis, which promises to allow better ordering, parallelism, and more thorough invalidation of runtime dependencies when they are modified. This however required changes: - The compiler DAG is no longer private to `rebar_compiler`, and has been extracted to the `rebar_compiler_dag` module - The compiler DAG is now started by the `rebar_prv_compile` module, which oversees the calls to `rebar_compiler` for each OTP application - The compiler DAG has been refactored to use a "dirty flag" to know if it was modified, rather than just tracking modifications in a functional manner, since the scope change (going multi-app) makes it impossible to cleanly use the functional approach without much larger changes - The DAG used to be cached within each OTP application. This is no longer possible since it is shared. Instead the DAG is stored in the state's deps_dir, which allows to cleanly split caches between regular apps for the user's project and plugins - The DAG supported a "label" mode that was used to store distinct DAGs for extra_src_dir runs and regular modules; this label is now used (and extended to `rebar_prv_compile` internals) to distinguish between "compile runs", such as "project_apps", or just "apps" (deps). The label is optional (i.e. not used by plugins which have no such need) - The extra_src_dirs for each app is now compiled using the main app's DAG, but the run takes place later in the compilation process. This may need changing to detect and prevent dependencies from src_dirs into extra_src_dirs, but this should not technically be a problem for runtime anyway. - Reworked the support for extra_src_dirs that are at the root of an umbrella project (and therefore do not belong to any single app) to use the new structure, also as part of the project_apps DAG. All tests keep passing, and this puts us in a better place to use EPP with cross-app support in the near-future.
5 年之前
Split up the compiler DAG This is another tricky commit towards replacing the current module analysis with EPP. The compiler DAG is now being shared across multiple applications being compiled, rather than a per-application basis, which promises to allow better ordering, parallelism, and more thorough invalidation of runtime dependencies when they are modified. This however required changes: - The compiler DAG is no longer private to `rebar_compiler`, and has been extracted to the `rebar_compiler_dag` module - The compiler DAG is now started by the `rebar_prv_compile` module, which oversees the calls to `rebar_compiler` for each OTP application - The compiler DAG has been refactored to use a "dirty flag" to know if it was modified, rather than just tracking modifications in a functional manner, since the scope change (going multi-app) makes it impossible to cleanly use the functional approach without much larger changes - The DAG used to be cached within each OTP application. This is no longer possible since it is shared. Instead the DAG is stored in the state's deps_dir, which allows to cleanly split caches between regular apps for the user's project and plugins - The DAG supported a "label" mode that was used to store distinct DAGs for extra_src_dir runs and regular modules; this label is now used (and extended to `rebar_prv_compile` internals) to distinguish between "compile runs", such as "project_apps", or just "apps" (deps). The label is optional (i.e. not used by plugins which have no such need) - The extra_src_dirs for each app is now compiled using the main app's DAG, but the run takes place later in the compilation process. This may need changing to detect and prevent dependencies from src_dirs into extra_src_dirs, but this should not technically be a problem for runtime anyway. - Reworked the support for extra_src_dirs that are at the root of an umbrella project (and therefore do not belong to any single app) to use the new structure, also as part of the project_apps DAG. All tests keep passing, and this puts us in a better place to use EPP with cross-app support in the near-future.
5 年之前
  1. %%% Module handling the directed graph required for the analysis
  2. %%% of all top-level applications by the various compiler plugins.
  3. -module(rebar_compiler_dag).
  4. -export([init/4, maybe_store/5, terminate/1]).
  5. -export([prune/5, populate_sources/5, populate_deps/3, propagate_stamps/1,
  6. compile_order/2, store_artifact/4]).
  7. -include("rebar.hrl").
  8. -define(DAG_VSN, 4).
  9. -define(DAG_ROOT, "source").
  10. -define(DAG_EXT, ".dag").
  11. -type critical_meta() :: term().
  12. -record(dag, {vsn = ?DAG_VSN :: pos_integer(),
  13. meta :: critical_meta(),
  14. vtab :: notable | [tuple()],
  15. etab :: notable | [tuple()],
  16. ntab :: notable | [tuple()]}).
  17. -type dag() :: digraph:graph().
  18. %% @doc You should initialize one DAG per compiler module.
  19. %% `CritMeta' is any contextual information that, if it is found to change,
  20. %% must invalidate the DAG loaded from disk.
  21. -spec init(file:filename_all(), atom(), string() | undefined, critical_meta()) -> dag().
  22. init(Dir, Compiler, Label, CritMeta) ->
  23. G = digraph:new([acyclic]),
  24. File = dag_file(Dir, Compiler, Label),
  25. try
  26. restore_dag(G, File, CritMeta)
  27. catch
  28. _:_ ->
  29. %% Don't mark as dirty yet to avoid creating compiler DAG files for
  30. %% compilers that are actually never used.
  31. ?WARN("Failed to restore ~ts file. Discarding it.~n", [File]),
  32. file:delete(File)
  33. end,
  34. G.
  35. %% @doc Clear up inactive (deleted) source files from a given project.
  36. %% The file must be in one of the directories that may contain source files
  37. %% for an OTP application; source files found in the DAG `G' that lie outside
  38. %% of these directories may be used in other circumstances (i.e. options affecting
  39. %% visibility, extra_src_dirs).
  40. %% Prune out files that have no corresponding sources
  41. prune(G, SrcExt, ArtifactExt, Sources, AppPaths) ->
  42. %% Collect source files that may have been removed. These files:
  43. %% * are not in Sources
  44. %% * have SrcExt
  45. %% In the process, prune header files - those don't have ArtifactExt
  46. %% extension - using side effect in is_deleted_source/5.
  47. case [Del || Del <- (digraph:vertices(G) -- Sources),
  48. is_deleted_source(G, Del, filename:extension(Del), SrcExt, ArtifactExt)] of
  49. [] ->
  50. ok; %% short circuit without sorting AppPaths
  51. Deleted ->
  52. SafeAppPaths = safe_dirs(AppPaths),
  53. OutFiles = filter_prefix(G, lists:sort(SafeAppPaths), lists:sort(Deleted)),
  54. [maybe_rm_artifact_and_edge(G, Out, SrcExt, ArtifactExt, File)
  55. || {File, Out} <- OutFiles],
  56. ok
  57. end.
  58. %% Some app paths may be prefixes of one another; for example,
  59. %% `/some/app/directory' may be seen as a prefix
  60. %% of `/some/app/directory_trick' and cause pruning outside
  61. %% of the proper scopes.
  62. safe_dirs(AppPaths) ->
  63. [{safe_dir(AppDir), Path} || {AppDir, Path} <- AppPaths].
  64. safe_dir([]) -> "/";
  65. safe_dir("/") -> "/";
  66. safe_dir([H|T]) -> [H|safe_dir(T)].
  67. is_deleted_source(_G, _F, Extension, Extension, _ArtifactExt) ->
  68. %% source file
  69. true;
  70. is_deleted_source(_G, _F, Extension, _SrcExt, Extension) ->
  71. %% artifact file - skip
  72. false;
  73. is_deleted_source(G, F, _Extension, _SrcExt, _ArtifactExt) ->
  74. %% must be header file or artifact
  75. digraph:in_edges(G, F) == [] andalso maybe_rm_vertex(G, F),
  76. false.
  77. %% This can be implemented using smarter trie, but since the
  78. %% whole procedure is rare, don't bother with optimisations.
  79. %% AppDirs & Fs are sorted, and to check if File is outside of
  80. %% App, lists:prefix is checked. When the App with File in it
  81. %% exists, verify file is still there on disk.
  82. filter_prefix(_G, [], _) ->
  83. [];
  84. filter_prefix(_G, _, []) ->
  85. [];
  86. filter_prefix(G, Apps, [F|Fs]) when is_atom(F) ->
  87. %% dirty bit shenanigans
  88. filter_prefix(G, Apps, Fs);
  89. filter_prefix(G, [{App, Out} | AppTail] = AppPaths, [File | FTail]) ->
  90. case lists:prefix(App, File) of
  91. true ->
  92. [{File, Out} | filter_prefix(G, AppPaths, FTail)];
  93. false when App < File ->
  94. filter_prefix(G, AppTail, [File|FTail]);
  95. false ->
  96. filter_prefix(G, AppPaths, FTail)
  97. end.
  98. finalise_populate_sources(_G, _InDirs, Waiting) when Waiting =:= #{} ->
  99. ok;
  100. finalise_populate_sources(G, InDirs, Waiting) ->
  101. %% wait for all deps to complete
  102. receive
  103. {deps, Pid, AbsIncls} ->
  104. {Status, Source} = maps:get(Pid, Waiting),
  105. %% the file hasn't been visited yet; set it to existing, but with
  106. %% a last modified value that's null so it gets updated to something new.
  107. [digraph:add_vertex(G, Src, 0) || Src <- AbsIncls,
  108. digraph:vertex(G, Src) =:= false],
  109. %% drop edges from deps that aren't included!
  110. [digraph:del_edge(G, Edge) || Status == old,
  111. Edge <- digraph:out_edges(G, Source),
  112. {_, _Src, Path, _Label} <- [digraph:edge(G, Edge)],
  113. not lists:member(Path, AbsIncls)],
  114. %% Add the rest
  115. [digraph:add_edge(G, Source, Incl) || Incl <- AbsIncls],
  116. %% mark the digraph dirty when there is any change in
  117. %% dependencies, for any application in the project
  118. mark_dirty(G),
  119. finalise_populate_sources(G, InDirs, Waiting);
  120. {'DOWN', _MRef, process, Pid, normal} ->
  121. finalise_populate_sources(G, InDirs, maps:remove(Pid, Waiting));
  122. {'DOWN', _MRef, process, Pid, Reason} ->
  123. {_Status, Source} = maps:get(Pid, Waiting),
  124. ?ERROR("Failed to get dependencies for ~s~n~p", [Source, Reason]),
  125. ?ABORT
  126. end.
  127. %% @doc this function scans all the source files found and looks into
  128. %% all the `InDirs' for deps (other source files, or files that aren't source
  129. %% but still returned by the compiler module) that are related
  130. %% to them.
  131. populate_sources(G, Compiler, InDirs, Sources, DepOpts) ->
  132. populate_sources(G, Compiler, InDirs, Sources, DepOpts, #{}).
  133. populate_sources(G, _Compiler, InDirs, [], _DepOpts, Waiting) ->
  134. finalise_populate_sources(G, InDirs, Waiting);
  135. populate_sources(G, Compiler, InDirs, [Source|Erls], DepOpts, Waiting) ->
  136. case digraph:vertex(G, Source) of
  137. {_, LastUpdated} ->
  138. case filelib:last_modified(Source) of
  139. 0 ->
  140. %% The File doesn't exist anymore, delete
  141. %% from the graph.
  142. digraph:del_vertex(G, Source),
  143. mark_dirty(G),
  144. populate_sources(G, Compiler, InDirs, Erls, DepOpts, Waiting);
  145. LastModified when LastUpdated < LastModified ->
  146. digraph:add_vertex(G, Source, LastModified),
  147. Worker = prepopulate_deps(Compiler, InDirs, Source, DepOpts, self()),
  148. populate_sources(G, Compiler, InDirs, Erls, DepOpts, Waiting#{Worker => {old, Source}});
  149. _ -> % unchanged
  150. populate_sources(G, Compiler, InDirs, Erls, DepOpts, Waiting)
  151. end;
  152. false ->
  153. LastModified = filelib:last_modified(Source),
  154. digraph:add_vertex(G, Source, LastModified),
  155. Worker = prepopulate_deps(Compiler, InDirs, Source, DepOpts, self()),
  156. populate_sources(G, Compiler, InDirs, Erls, DepOpts, Waiting#{Worker => {new, Source}})
  157. end.
  158. %% @doc Scan all files in the digraph that are seen as dependencies, but are
  159. %% neither source files nor artifacts (i.e. header files that don't produce
  160. %% artifacts of any kind).
  161. populate_deps(G, SourceExt, ArtifactExts) ->
  162. %% deps are files that are part of the digraph, but couldn't be scanned
  163. %% because they are neither source files (`SourceExt') nor mappings
  164. %% towards build artifacts (`ArtifactExts'); they will therefore never
  165. %% be handled otherwise and need to be re-scanned for accuracy, even
  166. %% if they are not being analyzed (we assume `Compiler:deps' did that
  167. %% in depth already, and improvements should be driven at that level)
  168. IgnoredExts = [SourceExt | ArtifactExts],
  169. Vertices = digraph:vertices(G),
  170. [refresh_dep(G, digraph:vertex(G, File))
  171. || File <- Vertices,
  172. Ext <- [filename:extension(File)],
  173. not lists:member(Ext, IgnoredExts)],
  174. ok.
  175. %% @doc Take the timestamps/diff changes and propagate them from a dep to the
  176. %% parent; given:
  177. %% A 0 -> B 1 -> C 3 -> D 2
  178. %% then we expect to get back:
  179. %% A 3 -> B 3 -> C 3 -> D 2
  180. %% This is going to be safe for the current run of regeneration, but also for the
  181. %% next one; unless any file in the chain has changed, the stamp won't move up
  182. %% and there won't be a reason to recompile.
  183. %% The obvious caveat to this one is that a file changing by restoring an old version
  184. %% won't be picked up, but this weakness already existed in terms of timestamps.
  185. propagate_stamps(G) ->
  186. case is_dirty(G) of
  187. false ->
  188. %% no change, no propagation to make
  189. ok;
  190. true ->
  191. %% we can use a topsort, start at the end of it (files with no deps)
  192. %% and update them all in order. By doing this, each file only needs to check
  193. %% for one level of out-neighbours to set itself to the right appropriate time.
  194. DepSort = lists:reverse(digraph_utils:topsort(G)),
  195. propagate_stamps(G, DepSort)
  196. end.
  197. %% @doc Return the reverse sorting order to get dep-free apps first.
  198. %% -- we would usually not need to consider the non-source files for the order to
  199. %% be complete, but using them doesn't hurt.
  200. compile_order(_, AppDefs) when length(AppDefs) =< 1 ->
  201. [Name || {Name, _Path} <- AppDefs];
  202. compile_order(G, AppDefs) ->
  203. Edges = [{V1,V2} || E <- digraph:edges(G),
  204. {_,V1,V2,_} <- [digraph:edge(G, E)]],
  205. AppPaths = prepare_app_paths(AppDefs),
  206. compile_order(Edges, AppPaths, #{}).
  207. %% @doc Store the DAG on disk if it was dirty
  208. maybe_store(G, Dir, Compiler, Label, CritMeta) ->
  209. case is_dirty(G) of
  210. true ->
  211. clear_dirty(G),
  212. File = dag_file(Dir, Compiler, Label),
  213. store_dag(G, File, CritMeta);
  214. false ->
  215. ok
  216. end.
  217. %% Get rid of the live state for the digraph; leave disk stuff in place.
  218. terminate(G) ->
  219. true = digraph:delete(G).
  220. store_artifact(G, Source, Target, Meta) ->
  221. digraph:add_vertex(G, Target, {artifact, Meta}),
  222. digraph:add_edge(G, Target, Source, artifact).
  223. %%%%%%%%%%%%%%%
  224. %%% PRIVATE %%%
  225. %%%%%%%%%%%%%%%
  226. %% @private generate the name for the DAG based on the compiler module and
  227. %% a custom label, both of which are used to prevent various compiler runs
  228. %% from clobbering each other. The label `undefined' is kept for a default
  229. %% run of the compiler, to keep in line with previous versions of the file.
  230. dag_file(Dir, CompilerMod, undefined) ->
  231. filename:join([rebar_dir:local_cache_dir(Dir), CompilerMod,
  232. ?DAG_ROOT ++ ?DAG_EXT]);
  233. dag_file(Dir, CompilerMod, Label) ->
  234. filename:join([rebar_dir:local_cache_dir(Dir), CompilerMod,
  235. ?DAG_ROOT ++ "_" ++ Label ++ ?DAG_EXT]).
  236. restore_dag(G, File, CritMeta) ->
  237. case file:read_file(File) of
  238. {ok, Data} ->
  239. %% The CritMeta value is checked and if it doesn't match, we fail
  240. %% the whole restore operation.
  241. #dag{vsn=?DAG_VSN, meta = CritMeta, vtab = VTab,
  242. etab = ETab, ntab = NTab} = binary_to_term(Data),
  243. {digraph, VT, ET, NT, false} = G,
  244. true = ets:insert_new(VT, VTab),
  245. true = ets:insert_new(ET, ETab),
  246. true = ets:delete_all_objects(NT),
  247. true = ets:insert(NT, NTab),
  248. ok;
  249. {error, _Err} ->
  250. ok
  251. end.
  252. store_dag(G, File, CritMeta) ->
  253. ok = filelib:ensure_dir(File),
  254. {digraph, VT, ET, NT, false} = G,
  255. Data = term_to_binary(#dag{meta = CritMeta, vtab = ets:tab2list(VT),
  256. etab = ets:tab2list(ET), ntab = ets:select(NT, [{'_',[],['$_']}])}, [{compressed, 2}]),
  257. file:write_file(File, Data).
  258. %% Drop a file from the digraph if it doesn't exist, and if so,
  259. %% delete its related build artifact
  260. maybe_rm_artifact_and_edge(G, OutDir, SrcExt, Ext, Source) ->
  261. %% This is NOT a double check it is the only check that the source file is actually gone
  262. case filelib:is_regular(Source) of
  263. true ->
  264. %% Actually exists, don't delete
  265. false;
  266. false ->
  267. Edges = digraph:in_edges(G, Source),
  268. Targets = [V1 || Edge <- Edges,
  269. {_E, V1, _V2, artifact} <- [digraph:edge(G, Edge)]],
  270. case Targets of
  271. [] ->
  272. Target = target(OutDir, Source, SrcExt, Ext),
  273. ?DIAGNOSTIC("Source ~ts is gone, deleting previous ~ts file if it exists ~ts", [Source, Ext, Target]),
  274. file:delete(Target);
  275. [_|_] ->
  276. lists:foreach(fun(Target) ->
  277. ?DIAGNOSTIC("Source ~ts is gone, deleting artifact ~ts "
  278. "if it exists", [Source, Target]),
  279. file:delete(Target)
  280. end, Targets)
  281. end,
  282. digraph:del_vertex(G, Source),
  283. mark_dirty(G),
  284. true
  285. end.
  286. maybe_rm_vertex(G, Source) ->
  287. case filelib:is_regular(Source) of
  288. true ->
  289. exists;
  290. false ->
  291. digraph:del_vertex(G, Source),
  292. mark_dirty(G)
  293. end.
  294. %% Add dependencies of a given file to the DAG. If the file is not found yet,
  295. %% mark its timestamp to 0, which means we have no info on it.
  296. %% Source files will be covered at a later point in their own scan, and
  297. %% non-source files are going to be covered by `populate_deps/3'.
  298. prepopulate_deps(Compiler, InDirs, Source, DepOpts, Control) ->
  299. {Worker, _MRef} = spawn_monitor(
  300. fun () ->
  301. SourceDir = filename:dirname(Source),
  302. AbsIncls = case erlang:function_exported(Compiler, dependencies, 4) of
  303. false ->
  304. Compiler:dependencies(Source, SourceDir, InDirs);
  305. true ->
  306. Compiler:dependencies(Source, SourceDir, InDirs, DepOpts)
  307. end,
  308. Control ! {deps, self(), AbsIncls}
  309. end
  310. ),
  311. Worker.
  312. %% check that a dep file is up to date
  313. refresh_dep(_G, {artifact, _}) ->
  314. %% ignore artifacts
  315. ok;
  316. refresh_dep(G, {File, LastUpdated}) ->
  317. case filelib:last_modified(File) of
  318. 0 ->
  319. %% Gone! Erase from the graph
  320. digraph:del_vertex(G, File),
  321. mark_dirty(G);
  322. LastModified when LastUpdated < LastModified ->
  323. digraph:add_vertex(G, File, LastModified),
  324. mark_dirty(G);
  325. _ ->
  326. %% unchanged
  327. ok
  328. end.
  329. %% Do the actual propagation of all files; the files are expected to be
  330. %% in a topological order such that we don't need to go more than a level
  331. %% deep in what we search.
  332. propagate_stamps(_G, []) ->
  333. ok;
  334. propagate_stamps(G, [File|Files]) ->
  335. Stamps = [Stamp
  336. || F <- digraph:out_neighbours(G, File),
  337. {_, Stamp} <- [digraph:vertex(G, F)],
  338. is_tuple(Stamp) andalso element(1, Stamp) =/= artifact],
  339. case Stamps of
  340. [] ->
  341. ok;
  342. _ ->
  343. Max = lists:max(Stamps),
  344. case digraph:vertex(G, File) of
  345. {_, {artifact, _}} ->
  346. ok;
  347. {_, Smaller} when Smaller < Max ->
  348. digraph:add_vertex(G, File, Max);
  349. _ ->
  350. ok
  351. end
  352. end,
  353. propagate_stamps(G, Files).
  354. %% Do the actual reversal; be aware that only working from the edges
  355. %% may omit files, so we have to add all non-dependant apps manually
  356. %% to make sure we don't drop em. Since they have no deps, they're
  357. %% safer to put first (and compile first)
  358. compile_order([], AppPaths, AppDeps) ->
  359. %% use a digraph so we don't reimplement topsort by hand.
  360. G = digraph:new([acyclic]), % ignore cycles and hope it works
  361. Tups = maps:keys(AppDeps),
  362. {Va,Vb} = lists:unzip(Tups),
  363. [digraph:add_vertex(G, V) || V <- Va],
  364. [digraph:add_vertex(G, V) || V <- Vb],
  365. [digraph:add_edge(G, V1, V2) || {V1, V2} <- Tups],
  366. Sorted = lists:reverse(digraph_utils:topsort(G)),
  367. digraph:delete(G),
  368. Standalone = [Name || {_, Name} <- AppPaths] -- Sorted,
  369. Standalone ++ Sorted;
  370. compile_order([{P1,P2}|T], AppPaths, AppDeps) ->
  371. %% Assume most dependencies are between files of the same app
  372. %% so ask to see if it's the same before doing a deeper check:
  373. case find_app(P1, AppPaths) of
  374. not_found -> % system lib probably! not in the repo
  375. compile_order(T, AppPaths, AppDeps);
  376. {P1App, P1Path} ->
  377. case find_cached_app(P2, {P1App, P1Path}, AppPaths) of
  378. {P2App, _} when P2App =/= P1App ->
  379. compile_order(T, AppPaths, AppDeps#{{P1App,P2App} => true});
  380. _ ->
  381. compile_order(T, AppPaths, AppDeps)
  382. end
  383. end.
  384. %% Swap app name with paths in the order, and sort there; this lets us
  385. %% bail out early in a search where a file won't be found.
  386. prepare_app_paths(AppPaths) ->
  387. lists:sort([{filename:split(Path), Name} || {Name, Path} <- AppPaths]).
  388. %% Look for the app to which the path belongs; needed to
  389. %% go from an edge between files in the DAG to building
  390. %% app-related orderings
  391. find_app(Path, AppPaths) ->
  392. find_app_(filename:split(Path), AppPaths).
  393. %% A cached search for the app to which a path belongs;
  394. %% the assumption is that sorted edges and common relationships
  395. %% are going to be between local files within an app most
  396. %% of the time; so we first look for the same path as a
  397. %% prior match to avoid searching _all_ potential candidates.
  398. %% If it doesn't work, go for the normal search.
  399. find_cached_app(Path, {Name, AppPath}, AppPaths) ->
  400. Split = filename:split(Path),
  401. case find_app_(Split, [{AppPath, Name}]) of
  402. not_found -> find_app_(Split, AppPaths);
  403. LastEntry -> LastEntry
  404. end.
  405. %% Do the actual recursive search
  406. find_app_(_Path, []) ->
  407. not_found;
  408. find_app_(Path, [{AppPath, AppName}|Rest]) ->
  409. case lists:prefix(AppPath, Path) of
  410. true ->
  411. {AppName, AppPath};
  412. false when AppPath > Path ->
  413. not_found;
  414. false ->
  415. find_app_(Path, Rest)
  416. end.
  417. %% @private Return what should be the base name of an erl file, relocated to the
  418. %% target directory. For example:
  419. %% target_base("ebin/", "src/my_module.erl", ".erl", ".beam") -> "ebin/my_module.beam"
  420. target(OutDir, Source, SrcExt, Ext) ->
  421. filename:join(OutDir, filename:basename(Source, SrcExt) ++ Ext).
  422. %% Mark the digraph as having been modified, which is required to
  423. %% save its updated form on disk after the compiling run.
  424. %% This uses a magic vertex to carry the dirty state. This is less
  425. %% than ideal because listing vertices may expect filenames and
  426. %% instead there's going to be one trick atom through it.
  427. mark_dirty(G) ->
  428. digraph:add_vertex(G, '$r3_dirty_bit', true),
  429. ok.
  430. %% Check whether the digraph has been modified and is considered dirty.
  431. is_dirty(G) ->
  432. case digraph:vertex(G, '$r3_dirty_bit') of
  433. {_, Bool} -> Bool;
  434. false -> false
  435. end.
  436. %% Remove the dirty status. Because the saving of a digraph on disk saves all
  437. %% vertices, clear the flag before serializing it.
  438. clear_dirty(G) ->
  439. digraph:del_vertex(G, '$r3_dirty_bit').