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.

505 line
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 年之前
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, 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. Edges = [{V1,V2} || E <- digraph:edges(G),
  226. {_,V1,V2,_} <- [digraph:edge(G, E)]],
  227. AppPaths = prepare_app_paths(AppDefs),
  228. compile_order(Edges, AppPaths, #{}).
  229. -dialyzer({no_opaque, maybe_store/5}). % optimized digraph usage breaks opacity
  230. %% @doc Store the DAG on disk if it was dirty
  231. maybe_store(G, Dir, Compiler, Label, CritMeta) ->
  232. case is_dirty(G) of
  233. true ->
  234. clear_dirty(G),
  235. File = dag_file(Dir, Compiler, Label),
  236. store_dag(G, File, CritMeta);
  237. false ->
  238. ok
  239. end.
  240. %% Get rid of the live state for the digraph; leave disk stuff in place.
  241. terminate(G) ->
  242. true = digraph:delete(G).
  243. store_artifact(G, Source, Target, Meta) ->
  244. mark_dirty(G),
  245. digraph:add_vertex(G, Target, {artifact, Meta}),
  246. digraph:add_edge(G, Target, Source, artifact).
  247. %%%%%%%%%%%%%%%
  248. %%% PRIVATE %%%
  249. %%%%%%%%%%%%%%%
  250. %% @private generate the name for the DAG based on the compiler module and
  251. %% a custom label, both of which are used to prevent various compiler runs
  252. %% from clobbering each other. The label `undefined' is kept for a default
  253. %% run of the compiler, to keep in line with previous versions of the file.
  254. dag_file(Dir, CompilerMod, undefined) ->
  255. filename:join([rebar_dir:local_cache_dir(Dir), CompilerMod,
  256. ?DAG_ROOT ++ ?DAG_EXT]);
  257. dag_file(Dir, CompilerMod, Label) ->
  258. filename:join([rebar_dir:local_cache_dir(Dir), CompilerMod,
  259. ?DAG_ROOT ++ "_" ++ Label ++ ?DAG_EXT]).
  260. -dialyzer({no_opaque, restore_dag/3}). % optimized digraph usage breaks opacity
  261. restore_dag(G, File, CritMeta) ->
  262. case file:read_file(File) of
  263. {ok, Data} ->
  264. %% The CritMeta value is checked and if it doesn't match, we fail
  265. %% the whole restore operation.
  266. #dag{vsn=?DAG_VSN, meta = CritMeta, vtab = VTab,
  267. etab = ETab, ntab = NTab} = binary_to_term(Data),
  268. {digraph, VT, ET, NT, false} = G,
  269. true = ets:insert_new(VT, VTab),
  270. true = ets:insert_new(ET, ETab),
  271. true = ets:delete_all_objects(NT),
  272. true = ets:insert(NT, NTab),
  273. ok;
  274. {error, _Err} ->
  275. ok
  276. end.
  277. -dialyzer([{no_opaque, store_dag/3}, {no_return, store_dag/3}]). % optimized digraph usage breaks opacity
  278. store_dag(G, File, CritMeta) ->
  279. ok = filelib:ensure_dir(File),
  280. {digraph, VT, ET, NT, false} = G,
  281. Data = term_to_binary(#dag{meta = CritMeta, vtab = ets:tab2list(VT),
  282. etab = ets:tab2list(ET), ntab = ets:select(NT, [{'_',[],['$_']}])}, [{compressed, 2}]),
  283. file:write_file(File, Data).
  284. %% Drop a file from the digraph if it doesn't exist, and if so,
  285. %% delete its related build artifact
  286. maybe_rm_artifact_and_edge(G, OutDir, SrcExt, Ext, Source) ->
  287. %% This is NOT a double check it is the only check that the source file is actually gone
  288. case filelib:is_regular(Source) of
  289. true ->
  290. %% Actually exists, don't delete
  291. false;
  292. false ->
  293. Edges = digraph:in_edges(G, Source),
  294. Targets = [V1 || Edge <- Edges,
  295. {_E, V1, _V2, artifact} <- [digraph:edge(G, Edge)]],
  296. case Targets of
  297. [] ->
  298. Target = target(OutDir, Source, SrcExt, Ext),
  299. ?DIAGNOSTIC("Source ~ts is gone, deleting previous ~ts file if it exists ~ts", [Source, Ext, Target]),
  300. file:delete(Target);
  301. [_|_] ->
  302. lists:foreach(fun(Target) ->
  303. ?DIAGNOSTIC("Source ~ts is gone, deleting artifact ~ts "
  304. "if it exists", [Source, Target]),
  305. file:delete(Target)
  306. end, Targets)
  307. end,
  308. digraph:del_vertex(G, Source),
  309. mark_dirty(G),
  310. true
  311. end.
  312. maybe_rm_vertex(G, Source) ->
  313. case filelib:is_regular(Source) of
  314. true ->
  315. exists;
  316. false ->
  317. digraph:del_vertex(G, Source),
  318. mark_dirty(G)
  319. end.
  320. %% Add dependencies of a given file to the DAG. If the file is not found yet,
  321. %% mark its timestamp to 0, which means we have no info on it.
  322. %% Source files will be covered at a later point in their own scan, and
  323. %% non-source files are going to be covered by `populate_deps/3'.
  324. prepopulate_deps(Compiler, InDirs, Source, DepOpts, Control) ->
  325. {Worker, _MRef} = spawn_monitor(
  326. fun () ->
  327. SourceDir = filename:dirname(Source),
  328. AbsIncls = case erlang:function_exported(Compiler, dependencies, 4) of
  329. false ->
  330. Compiler:dependencies(Source, SourceDir, InDirs);
  331. true ->
  332. Compiler:dependencies(Source, SourceDir, InDirs, DepOpts)
  333. end,
  334. Control ! {deps, self(), AbsIncls}
  335. end
  336. ),
  337. Worker.
  338. %% check that a dep file is up to date
  339. refresh_dep(_G, {artifact, _}) ->
  340. %% ignore artifacts
  341. ok;
  342. refresh_dep(G, {File, LastUpdated}) ->
  343. case filelib:last_modified(File) of
  344. 0 ->
  345. %% Gone! Erase from the graph
  346. digraph:del_vertex(G, File),
  347. mark_dirty(G);
  348. LastModified when LastUpdated < LastModified ->
  349. digraph:add_vertex(G, File, LastModified),
  350. mark_dirty(G);
  351. _ ->
  352. %% unchanged
  353. ok
  354. end.
  355. %% Do the actual propagation of all files; the files are expected to be
  356. %% in a topological order such that we don't need to go more than a level
  357. %% deep in what we search.
  358. propagate_stamps(_G, []) ->
  359. ok;
  360. propagate_stamps(G, [File|Files]) ->
  361. Stamps = [Stamp
  362. || F <- digraph:out_neighbours(G, File),
  363. {_, Stamp} <- [digraph:vertex(G, F)],
  364. is_tuple(Stamp) andalso element(1, Stamp) =/= artifact],
  365. case Stamps of
  366. [] ->
  367. ok;
  368. _ ->
  369. Max = lists:max(Stamps),
  370. case digraph:vertex(G, File) of
  371. {_, {artifact, _}} ->
  372. ok;
  373. {_, Smaller} when Smaller < Max ->
  374. digraph:add_vertex(G, File, Max);
  375. _ ->
  376. ok
  377. end
  378. end,
  379. propagate_stamps(G, Files).
  380. %% Do the actual reversal; be aware that only working from the edges
  381. %% may omit files, so we have to add all non-dependant apps manually
  382. %% to make sure we don't drop em. Since they have no deps, they're
  383. %% safer to put first (and compile first)
  384. compile_order([], AppPaths, AppDeps) ->
  385. %% use a digraph so we don't reimplement topsort by hand.
  386. G = digraph:new([acyclic]), % ignore cycles and hope it works
  387. Tups = maps:keys(AppDeps),
  388. {Va,Vb} = lists:unzip(Tups),
  389. [digraph:add_vertex(G, V) || V <- Va],
  390. [digraph:add_vertex(G, V) || V <- Vb],
  391. [digraph:add_edge(G, V1, V2) || {V1, V2} <- Tups],
  392. Sorted = lists:reverse(digraph_utils:topsort(G)),
  393. digraph:delete(G),
  394. Standalone = [Name || {_, Name} <- AppPaths] -- Sorted,
  395. Standalone ++ Sorted;
  396. compile_order([{P1,P2}|T], AppPaths, AppDeps) ->
  397. %% Assume most dependencies are between files of the same app
  398. %% so ask to see if it's the same before doing a deeper check:
  399. case find_app(P1, AppPaths) of
  400. not_found -> % system lib probably! not in the repo
  401. compile_order(T, AppPaths, AppDeps);
  402. {P1App, P1Path} ->
  403. case find_cached_app(P2, {P1App, P1Path}, AppPaths) of
  404. {P2App, _} when P2App =/= P1App ->
  405. compile_order(T, AppPaths, AppDeps#{{P1App,P2App} => true});
  406. _ ->
  407. compile_order(T, AppPaths, AppDeps)
  408. end
  409. end.
  410. %% Swap app name with paths in the order, and sort there; this lets us
  411. %% bail out early in a search where a file won't be found.
  412. prepare_app_paths(AppPaths) ->
  413. lists:sort([{filename:split(Path), Name} || {Name, Path} <- AppPaths]).
  414. %% Look for the app to which the path belongs; needed to
  415. %% go from an edge between files in the DAG to building
  416. %% app-related orderings
  417. find_app(Path, AppPaths) ->
  418. find_app_(filename:split(Path), AppPaths).
  419. %% A cached search for the app to which a path belongs;
  420. %% the assumption is that sorted edges and common relationships
  421. %% are going to be between local files within an app most
  422. %% of the time; so we first look for the same path as a
  423. %% prior match to avoid searching _all_ potential candidates.
  424. %% If it doesn't work, go for the normal search.
  425. find_cached_app(Path, {Name, AppPath}, AppPaths) ->
  426. Split = filename:split(Path),
  427. case find_app_(Split, [{AppPath, Name}]) of
  428. not_found -> find_app_(Split, AppPaths);
  429. LastEntry -> LastEntry
  430. end.
  431. %% Do the actual recursive search
  432. find_app_(_Path, []) ->
  433. not_found;
  434. find_app_(Path, [{AppPath, AppName}|Rest]) ->
  435. case lists:prefix(AppPath, Path) of
  436. true ->
  437. {AppName, AppPath};
  438. false when AppPath > Path ->
  439. not_found;
  440. false ->
  441. find_app_(Path, Rest)
  442. end.
  443. %% @private Return what should be the base name of an erl file, relocated to the
  444. %% target directory. For example:
  445. %% target_base("ebin/", "src/my_module.erl", ".erl", ".beam") -> "ebin/my_module.beam"
  446. target(OutDir, Source, SrcExt, Ext) ->
  447. filename:join(OutDir, filename:basename(Source, SrcExt) ++ Ext).
  448. %% Mark the digraph as having been modified, which is required to
  449. %% save its updated form on disk after the compiling run.
  450. %% This uses a magic vertex to carry the dirty state. This is less
  451. %% than ideal because listing vertices may expect filenames and
  452. %% instead there's going to be one trick atom through it.
  453. mark_dirty(G) ->
  454. digraph:add_vertex(G, '$r3_dirty_bit', true),
  455. ok.
  456. %% Check whether the digraph has been modified and is considered dirty.
  457. is_dirty(G) ->
  458. case digraph:vertex(G, '$r3_dirty_bit') of
  459. {_, Bool} -> Bool;
  460. false -> false
  461. end.
  462. %% Remove the dirty status. Because the saving of a digraph on disk saves all
  463. %% vertices, clear the flag before serializing it.
  464. clear_dirty(G) ->
  465. digraph:del_vertex(G, '$r3_dirty_bit').