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.

486 lines
20 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 years ago
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 years ago
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 years ago
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 years ago
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 years ago
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 years ago
5 years ago
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 years ago
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 years ago
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 years ago
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 years ago
  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, status/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 Quickly validate whether a DAG exists by validating its file name,
  36. %% version, and CritMeta data, without attempting to actually build it.
  37. -spec status(file:filename_all(), atom(), string() | undefined, critical_meta()) ->
  38. valid | bad_format | bad_vsn | bad_meta | not_found.
  39. status(Dir, Compiler, Label, CritMeta) ->
  40. File = dag_file(Dir, Compiler, Label),
  41. case file:read_file(File) of
  42. {ok, Data} ->
  43. %% The CritMeta value is checked and if it doesn't match, we
  44. %% consider things invalid. Same for the version.
  45. try binary_to_term(Data) of
  46. #dag{vsn = ?DAG_VSN, meta = CritMeta} -> valid;
  47. #dag{vsn = ?DAG_VSN} -> bad_meta;
  48. #dag{meta = CritMeta} -> bad_vsn;
  49. _ -> bad_format
  50. catch
  51. _:_ ->
  52. bad_format
  53. end;
  54. {error, _Err} ->
  55. not_found
  56. end.
  57. %% @doc Clear up inactive (deleted) source files from a given project.
  58. %% The file must be in one of the directories that may contain source files
  59. %% for an OTP application; source files found in the DAG `G' that lie outside
  60. %% of these directories may be used in other circumstances (i.e. options affecting
  61. %% visibility, extra_src_dirs).
  62. %% Prune out files that have no corresponding sources
  63. prune(G, SrcExt, ArtifactExt, Sources, AppPaths) ->
  64. %% Collect source files that may have been removed. These files:
  65. %% * are not in Sources
  66. %% * have SrcExt
  67. %% In the process, prune header files - those don't have ArtifactExt
  68. %% extension - using side effect in is_deleted_source/5.
  69. case [Del || Del <- (digraph:vertices(G) -- Sources),
  70. is_deleted_source(G, Del, filename:extension(Del), SrcExt, ArtifactExt)] of
  71. [] ->
  72. ok; %% short circuit without sorting AppPaths
  73. Deleted ->
  74. SafeAppPaths = safe_dirs(AppPaths),
  75. OutFiles = filter_prefix(G, lists:sort(SafeAppPaths), lists:sort(Deleted)),
  76. [maybe_rm_artifact_and_edge(G, Out, SrcExt, ArtifactExt, File)
  77. || {File, Out} <- OutFiles],
  78. ok
  79. end.
  80. %% Some app paths may be prefixes of one another; for example,
  81. %% `/some/app/directory' may be seen as a prefix
  82. %% of `/some/app/directory_trick' and cause pruning outside
  83. %% of the proper scopes.
  84. safe_dirs(AppPaths) ->
  85. [{safe_dir(AppDir), Path} || {AppDir, Path} <- AppPaths].
  86. safe_dir([]) -> "/";
  87. safe_dir("/") -> "/";
  88. safe_dir([H|T]) -> [H|safe_dir(T)].
  89. is_deleted_source(_G, _F, Extension, Extension, _ArtifactExt) ->
  90. %% source file
  91. true;
  92. is_deleted_source(_G, _F, Extension, _SrcExt, Extension) ->
  93. %% artifact file - skip
  94. false;
  95. is_deleted_source(G, F, _Extension, _SrcExt, _ArtifactExt) ->
  96. %% must be header file or artifact
  97. digraph:in_edges(G, F) == [] andalso maybe_rm_vertex(G, F),
  98. false.
  99. %% This can be implemented using smarter trie, but since the
  100. %% whole procedure is rare, don't bother with optimisations.
  101. %% AppDirs & Fs are sorted, and to check if File is outside of
  102. %% App, lists:prefix is checked. When the App with File in it
  103. %% exists, verify file is still there on disk.
  104. filter_prefix(_G, [], _) ->
  105. [];
  106. filter_prefix(_G, _, []) ->
  107. [];
  108. filter_prefix(G, Apps, [F|Fs]) when is_atom(F) ->
  109. %% dirty bit shenanigans
  110. filter_prefix(G, Apps, Fs);
  111. filter_prefix(G, [{App, Out} | AppTail] = AppPaths, [File | FTail]) ->
  112. case lists:prefix(App, File) of
  113. true ->
  114. [{File, Out} | filter_prefix(G, AppPaths, FTail)];
  115. false when App < File ->
  116. filter_prefix(G, AppTail, [File|FTail]);
  117. false ->
  118. filter_prefix(G, AppPaths, FTail)
  119. end.
  120. finalise_populate_sources(_G, _InDirs, Waiting) when Waiting =:= #{} ->
  121. ok;
  122. finalise_populate_sources(G, InDirs, Waiting) ->
  123. %% wait for all deps to complete
  124. receive
  125. {deps, Pid, AbsIncls} ->
  126. {Status, Source} = maps:get(Pid, Waiting),
  127. %% the file hasn't been visited yet; set it to existing, but with
  128. %% a last modified value that's null so it gets updated to something new.
  129. [digraph:add_vertex(G, Src, 0) || Src <- AbsIncls,
  130. digraph:vertex(G, Src) =:= false],
  131. %% drop edges from deps that aren't included!
  132. [digraph:del_edge(G, Edge) || Status == old,
  133. Edge <- digraph:out_edges(G, Source),
  134. {_, _Src, Path, _Label} <- [digraph:edge(G, Edge)],
  135. not lists:member(Path, AbsIncls)],
  136. %% Add the rest
  137. [digraph:add_edge(G, Source, Incl) || Incl <- AbsIncls],
  138. %% mark the digraph dirty when there is any change in
  139. %% dependencies, for any application in the project
  140. mark_dirty(G),
  141. finalise_populate_sources(G, InDirs, Waiting);
  142. {'DOWN', _MRef, process, Pid, normal} ->
  143. finalise_populate_sources(G, InDirs, maps:remove(Pid, Waiting));
  144. {'DOWN', _MRef, process, Pid, Reason} ->
  145. {_Status, Source} = maps:get(Pid, Waiting),
  146. ?ERROR("Failed to get dependencies for ~s~n~p", [Source, Reason]),
  147. ?ABORT
  148. end.
  149. %% @doc this function scans all the source files found and looks into
  150. %% all the `InDirs' for deps (other source files, or files that aren't source
  151. %% but still returned by the compiler module) that are related
  152. %% to them.
  153. populate_sources(G, Compiler, InDirs, Sources, DepOpts) ->
  154. populate_sources(G, Compiler, InDirs, Sources, DepOpts, #{}).
  155. populate_sources(G, _Compiler, InDirs, [], _DepOpts, Waiting) ->
  156. finalise_populate_sources(G, InDirs, Waiting);
  157. populate_sources(G, Compiler, InDirs, [Source|Erls], DepOpts, Waiting) ->
  158. case digraph:vertex(G, Source) of
  159. {_, LastUpdated} ->
  160. case filelib:last_modified(Source) of
  161. 0 ->
  162. %% The File doesn't exist anymore, delete
  163. %% from the graph.
  164. digraph:del_vertex(G, Source),
  165. mark_dirty(G),
  166. populate_sources(G, Compiler, InDirs, Erls, DepOpts, Waiting);
  167. LastModified when LastUpdated < LastModified ->
  168. digraph:add_vertex(G, Source, LastModified),
  169. Worker = prepopulate_deps(Compiler, InDirs, Source, DepOpts, self()),
  170. populate_sources(G, Compiler, InDirs, Erls, DepOpts, Waiting#{Worker => {old, Source}});
  171. _ -> % unchanged
  172. populate_sources(G, Compiler, InDirs, Erls, DepOpts, Waiting)
  173. end;
  174. false ->
  175. LastModified = filelib:last_modified(Source),
  176. digraph:add_vertex(G, Source, LastModified),
  177. Worker = prepopulate_deps(Compiler, InDirs, Source, DepOpts, self()),
  178. populate_sources(G, Compiler, InDirs, Erls, DepOpts, Waiting#{Worker => {new, Source}})
  179. end.
  180. %% @doc Scan all files in the digraph that are seen as dependencies, but are
  181. %% neither source files nor artifacts (i.e. header files that don't produce
  182. %% artifacts of any kind).
  183. populate_deps(G, SourceExt, ArtifactExts) ->
  184. %% deps are files that are part of the digraph, but couldn't be scanned
  185. %% because they are neither source files (`SourceExt') nor mappings
  186. %% towards build artifacts (`ArtifactExts'); they will therefore never
  187. %% be handled otherwise and need to be re-scanned for accuracy, even
  188. %% if they are not being analyzed (we assume `Compiler:deps' did that
  189. %% in depth already, and improvements should be driven at that level)
  190. IgnoredExts = [SourceExt | ArtifactExts],
  191. Vertices = digraph:vertices(G),
  192. [refresh_dep(G, digraph:vertex(G, File))
  193. || File <- Vertices,
  194. Ext <- [filename:extension(File)],
  195. not lists:member(Ext, IgnoredExts)],
  196. ok.
  197. %% @doc Take the timestamps/diff changes and propagate them from a dep to the
  198. %% parent; given:
  199. %% A 0 -> B 1 -> C 3 -> D 2
  200. %% then we expect to get back:
  201. %% A 3 -> B 3 -> C 3 -> D 2
  202. %% This is going to be safe for the current run of regeneration, but also for the
  203. %% next one; unless any file in the chain has changed, the stamp won't move up
  204. %% and there won't be a reason to recompile.
  205. %% The obvious caveat to this one is that a file changing by restoring an old version
  206. %% won't be picked up, but this weakness already existed in terms of timestamps.
  207. propagate_stamps(G) ->
  208. case is_dirty(G) of
  209. false ->
  210. %% no change, no propagation to make
  211. ok;
  212. true ->
  213. %% we can use a topsort, start at the end of it (files with no deps)
  214. %% and update them all in order. By doing this, each file only needs to check
  215. %% for one level of out-neighbours to set itself to the right appropriate time.
  216. DepSort = lists:reverse(digraph_utils:topsort(G)),
  217. propagate_stamps(G, DepSort)
  218. end.
  219. %% @doc Return the reverse sorting order to get dep-free apps first.
  220. %% -- we would usually not need to consider the non-source files for the order to
  221. %% be complete, but using them doesn't hurt.
  222. compile_order(_, AppDefs) when length(AppDefs) =< 1 ->
  223. [Name || {Name, _Path} <- AppDefs];
  224. compile_order(G, AppDefs) ->
  225. %% Build a digraph for following topo-sort, and populate
  226. %% FileToApp map as a side effect for caching
  227. AppDAG = digraph:new([acyclic]), % ignore cycles and hope it works
  228. lists:foldl(
  229. fun(E, FileToApp) ->
  230. case digraph:edge(G, E) of
  231. {_, _, _, artifact} ->
  232. %% skip artifacts, they don't affect compile order
  233. FileToApp;
  234. {_, V1, V2, _} ->
  235. %% V1 depends on V2, and we know V1 is in our repo,
  236. %% otherwise this edge would've never appeared
  237. {App, FTA1} = find_and_cache(V1, AppDefs, FileToApp),
  238. case find_and_cache(V2, AppDefs, FTA1) of
  239. {[], FTA2} ->
  240. %% dependency on a file outside of the repo
  241. FTA2;
  242. {App, FTA2} ->
  243. %% dependency within the same app
  244. FTA2;
  245. {AnotherApp, FTA2} ->
  246. %% actual dependency
  247. %% unfortunately digraph has non-functional API depending on side effects
  248. digraph:add_vertex(AppDAG, App), %% ignore errors for duplicate inserts
  249. digraph:add_vertex(AppDAG, AnotherApp),
  250. digraph:add_edge(AppDAG, App, AnotherApp),
  251. FTA2
  252. end
  253. end
  254. end, #{}, digraph:edges(G)),
  255. Sorted = lists:reverse(digraph_utils:topsort(AppDAG)),
  256. digraph:delete(AppDAG),
  257. Standalone = [Name || {Name, _} <- AppDefs] -- Sorted,
  258. Standalone ++ Sorted.
  259. -dialyzer({no_opaque, maybe_store/5}). % optimized digraph usage breaks opacity
  260. %% @doc Store the DAG on disk if it was dirty
  261. maybe_store(G, Dir, Compiler, Label, CritMeta) ->
  262. case is_dirty(G) of
  263. true ->
  264. clear_dirty(G),
  265. File = dag_file(Dir, Compiler, Label),
  266. store_dag(G, File, CritMeta);
  267. false ->
  268. ok
  269. end.
  270. %% Get rid of the live state for the digraph; leave disk stuff in place.
  271. terminate(G) ->
  272. true = digraph:delete(G).
  273. store_artifact(G, Source, Target, Meta) ->
  274. mark_dirty(G),
  275. digraph:add_vertex(G, Target, {artifact, Meta}),
  276. digraph:add_edge(G, Target, Source, artifact).
  277. %%%%%%%%%%%%%%%
  278. %%% PRIVATE %%%
  279. %%%%%%%%%%%%%%%
  280. %% @private generate the name for the DAG based on the compiler module and
  281. %% a custom label, both of which are used to prevent various compiler runs
  282. %% from clobbering each other. The label `undefined' is kept for a default
  283. %% run of the compiler, to keep in line with previous versions of the file.
  284. dag_file(Dir, CompilerMod, undefined) ->
  285. filename:join([rebar_dir:local_cache_dir(Dir), CompilerMod,
  286. ?DAG_ROOT ++ ?DAG_EXT]);
  287. dag_file(Dir, CompilerMod, Label) ->
  288. filename:join([rebar_dir:local_cache_dir(Dir), CompilerMod,
  289. ?DAG_ROOT ++ "_" ++ Label ++ ?DAG_EXT]).
  290. -dialyzer({no_opaque, restore_dag/3}). % optimized digraph usage breaks opacity
  291. restore_dag(G, File, CritMeta) ->
  292. case file:read_file(File) of
  293. {ok, Data} ->
  294. %% The CritMeta value is checked and if it doesn't match, we fail
  295. %% the whole restore operation.
  296. #dag{vsn=?DAG_VSN, meta = CritMeta, vtab = VTab,
  297. etab = ETab, ntab = NTab} = binary_to_term(Data),
  298. {digraph, VT, ET, NT, false} = G,
  299. true = ets:insert_new(VT, VTab),
  300. true = ets:insert_new(ET, ETab),
  301. true = ets:delete_all_objects(NT),
  302. true = ets:insert(NT, NTab),
  303. ok;
  304. {error, _Err} ->
  305. ok
  306. end.
  307. -dialyzer([{no_opaque, store_dag/3}, {no_return, store_dag/3}]). % optimized digraph usage breaks opacity
  308. store_dag(G, File, CritMeta) ->
  309. ok = filelib:ensure_dir(File),
  310. {digraph, VT, ET, NT, false} = G,
  311. Data = term_to_binary(#dag{meta = CritMeta, vtab = ets:tab2list(VT),
  312. etab = ets:tab2list(ET), ntab = ets:select(NT, [{'_',[],['$_']}])}, [{compressed, 2}]),
  313. file:write_file(File, Data).
  314. %% Drop a file from the digraph if it doesn't exist, and if so,
  315. %% delete its related build artifact
  316. maybe_rm_artifact_and_edge(G, OutDir, SrcExt, Ext, Source) ->
  317. %% This is NOT a double check it is the only check that the source file is actually gone
  318. case filelib:is_regular(Source) of
  319. true ->
  320. %% Actually exists, don't delete
  321. false;
  322. false ->
  323. Edges = digraph:in_edges(G, Source),
  324. Targets = [V1 || Edge <- Edges,
  325. {_E, V1, _V2, artifact} <- [digraph:edge(G, Edge)]],
  326. case Targets of
  327. [] ->
  328. Target = target(OutDir, Source, SrcExt, Ext),
  329. ?DIAGNOSTIC("Source ~ts is gone, deleting previous ~ts file if it exists ~ts", [Source, Ext, Target]),
  330. file:delete(Target);
  331. [_|_] ->
  332. lists:foreach(fun(Target) ->
  333. ?DIAGNOSTIC("Source ~ts is gone, deleting artifact ~ts "
  334. "if it exists", [Source, Target]),
  335. file:delete(Target)
  336. end, Targets)
  337. end,
  338. digraph:del_vertex(G, Source),
  339. mark_dirty(G),
  340. true
  341. end.
  342. maybe_rm_vertex(G, Source) ->
  343. case filelib:is_regular(Source) of
  344. true ->
  345. exists;
  346. false ->
  347. digraph:del_vertex(G, Source),
  348. mark_dirty(G)
  349. end.
  350. %% Add dependencies of a given file to the DAG. If the file is not found yet,
  351. %% mark its timestamp to 0, which means we have no info on it.
  352. %% Source files will be covered at a later point in their own scan, and
  353. %% non-source files are going to be covered by `populate_deps/3'.
  354. prepopulate_deps(Compiler, InDirs, Source, DepOpts, Control) ->
  355. {Worker, _MRef} = spawn_monitor(
  356. fun () ->
  357. SourceDir = filename:dirname(Source),
  358. AbsIncls = case erlang:function_exported(Compiler, dependencies, 4) of
  359. false ->
  360. Compiler:dependencies(Source, SourceDir, InDirs);
  361. true ->
  362. Compiler:dependencies(Source, SourceDir, InDirs, DepOpts)
  363. end,
  364. Control ! {deps, self(), AbsIncls}
  365. end
  366. ),
  367. Worker.
  368. %% check that a dep file is up to date
  369. refresh_dep(_G, {artifact, _}) ->
  370. %% ignore artifacts
  371. ok;
  372. refresh_dep(G, {File, LastUpdated}) ->
  373. case filelib:last_modified(File) of
  374. 0 ->
  375. %% Gone! Erase from the graph
  376. digraph:del_vertex(G, File),
  377. mark_dirty(G);
  378. LastModified when LastUpdated < LastModified ->
  379. digraph:add_vertex(G, File, LastModified),
  380. mark_dirty(G);
  381. _ ->
  382. %% unchanged
  383. ok
  384. end.
  385. %% Do the actual propagation of all files; the files are expected to be
  386. %% in a topological order such that we don't need to go more than a level
  387. %% deep in what we search.
  388. propagate_stamps(_G, []) ->
  389. ok;
  390. propagate_stamps(G, [File|Files]) ->
  391. Stamps = [Stamp
  392. || F <- digraph:out_neighbours(G, File),
  393. {_, Stamp} <- [digraph:vertex(G, F)],
  394. is_tuple(Stamp) andalso element(1, Stamp) =/= artifact],
  395. case Stamps of
  396. [] ->
  397. ok;
  398. _ ->
  399. Max = lists:max(Stamps),
  400. case digraph:vertex(G, File) of
  401. {_, {artifact, _}} ->
  402. ok;
  403. {_, Smaller} when Smaller < Max ->
  404. digraph:add_vertex(G, File, Max);
  405. _ ->
  406. ok
  407. end
  408. end,
  409. propagate_stamps(G, Files).
  410. find_and_cache(File, AppDefs, FileToApp) ->
  411. case maps:find(File, FileToApp) of
  412. error ->
  413. App = find(File, AppDefs),
  414. {App, FileToApp#{File => App}};
  415. {ok, App} ->
  416. {App, FileToApp}
  417. end.
  418. find(_File, []) ->
  419. [];
  420. find(File, [{App, Dir} | Tail]) ->
  421. case lists:prefix(Dir, File) of
  422. true ->
  423. App;
  424. false ->
  425. find(File, Tail)
  426. end.
  427. %% @private Return what should be the base name of an erl file, relocated to the
  428. %% target directory. For example:
  429. %% target_base("ebin/", "src/my_module.erl", ".erl", ".beam") -> "ebin/my_module.beam"
  430. target(OutDir, Source, SrcExt, Ext) ->
  431. filename:join(OutDir, filename:basename(Source, SrcExt) ++ Ext).
  432. %% Mark the digraph as having been modified, which is required to
  433. %% save its updated form on disk after the compiling run.
  434. %% This uses a magic vertex to carry the dirty state. This is less
  435. %% than ideal because listing vertices may expect filenames and
  436. %% instead there's going to be one trick atom through it.
  437. mark_dirty(G) ->
  438. digraph:add_vertex(G, '$r3_dirty_bit', true),
  439. ok.
  440. %% Check whether the digraph has been modified and is considered dirty.
  441. is_dirty(G) ->
  442. case digraph:vertex(G, '$r3_dirty_bit') of
  443. {_, Bool} -> Bool;
  444. false -> false
  445. end.
  446. %% Remove the dirty status. Because the saving of a digraph on disk saves all
  447. %% vertices, clear the flag before serializing it.
  448. clear_dirty(G) ->
  449. digraph:del_vertex(G, '$r3_dirty_bit').