Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

430 řádky
17 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.
před 5 roky
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.
před 5 roky
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.
před 5 roky
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.
před 5 roky
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.
před 5 roky
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.
před 5 roky
  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]).
  7. -include("rebar.hrl").
  8. -define(DAG_VSN, 3).
  9. -define(DAG_ROOT, "source").
  10. -define(DAG_EXT, ".dag").
  11. -type dag_v() :: {digraph:vertex(), term()} | 'false'.
  12. -type dag_e() :: {digraph:vertex(), digraph:vertex()}.
  13. -type critical_meta() :: term(). % if this changes, the DAG is invalid
  14. -type dag_rec() :: {list(dag_v()), list(dag_e()), critical_meta()}.
  15. -type dag() :: digraph:graph().
  16. -record(dag, {vsn = ?DAG_VSN :: pos_integer(),
  17. info = {[], [], []} :: dag_rec()}).
  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).
  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. prune_source_files(G, SrcExt, ArtifactExt,
  53. lists:sort(AppPaths), lists:sort(Deleted))
  54. end.
  55. is_deleted_source(_G, _F, Extension, Extension, _ArtifactExt) ->
  56. %% source file
  57. true;
  58. is_deleted_source(_G, _F, Extension, _SrcExt, Extension) ->
  59. %% artifact file - skip
  60. false;
  61. is_deleted_source(G, F, _Extension, _SrcExt, _ArtifactExt) ->
  62. %% must be header file
  63. digraph:in_edges(G, F) == [] andalso maybe_rm_vertex(G, F),
  64. false.
  65. %% This can be implemented using smarter trie, but since the
  66. %% whole procedure is rare, don't bother with optimisations.
  67. %% AppDirs & Fs are sorted, and to check if File is outside of
  68. %% App, lists:prefix is checked. When the App with File in it
  69. %% exists, verify file is still there on disk.
  70. prune_source_files(_G, _SrcExt, _ArtifactExt, [], _) ->
  71. ok;
  72. prune_source_files(_G, _SrcExt, _ArtifactExt, _, []) ->
  73. ok;
  74. prune_source_files(G, SrcExt, ArtifactExt, [AppDir | AppTail], Fs) when is_atom(AppDir) ->
  75. %% dirty bit shenanigans
  76. prune_source_files(G, SrcExt, ArtifactExt, AppTail, Fs);
  77. prune_source_files(G, SrcExt, ArtifactExt, [{App, Out} | AppTail] = AppPaths, [File | FTail]) ->
  78. case lists:prefix(App, File) of
  79. true ->
  80. maybe_rm_artifact_and_edge(G, Out, SrcExt, ArtifactExt, File),
  81. prune_source_files(G, SrcExt, ArtifactExt, AppPaths, FTail);
  82. false when App < File ->
  83. prune_source_files(G, SrcExt, ArtifactExt, AppTail, [File|FTail]);
  84. false ->
  85. prune_source_files(G, SrcExt, ArtifactExt, AppPaths, FTail)
  86. end.
  87. %% @doc this function scans all the source files found and looks into
  88. %% all the `InDirs' for deps (other source files, or files that aren't source
  89. %% but still returned by the compiler module) that are related
  90. %% to them.
  91. populate_sources(_G, _Compiler, _InDirs, [], _DepOpts) ->
  92. ok;
  93. populate_sources(G, Compiler, InDirs, [Source|Erls], DepOpts) ->
  94. case digraph:vertex(G, Source) of
  95. {_, LastUpdated} ->
  96. case filelib:last_modified(Source) of
  97. 0 ->
  98. %% The File doesn't exist anymore, delete
  99. %% from the graph.
  100. digraph:del_vertex(G, Source),
  101. mark_dirty(G),
  102. populate_sources(G, Compiler, InDirs, Erls, DepOpts);
  103. LastModified when LastUpdated < LastModified ->
  104. digraph:add_vertex(G, Source, LastModified),
  105. prepopulate_deps(G, Compiler, InDirs, Source, DepOpts, old),
  106. mark_dirty(G);
  107. _ -> % unchanged
  108. ok
  109. end;
  110. false ->
  111. LastModified = filelib:last_modified(Source),
  112. digraph:add_vertex(G, Source, LastModified),
  113. prepopulate_deps(G, Compiler, InDirs, Source, DepOpts, new),
  114. mark_dirty(G)
  115. end,
  116. populate_sources(G, Compiler, InDirs, Erls, DepOpts).
  117. %% @doc Scan all files in the digraph that are seen as dependencies, but are
  118. %% neither source files nor artifacts (i.e. header files that don't produce
  119. %% artifacts of any kind).
  120. populate_deps(G, SourceExt, ArtifactExts) ->
  121. %% deps are files that are part of the digraph, but couldn't be scanned
  122. %% because they are neither source files (`SourceExt') nor mappings
  123. %% towards build artifacts (`ArtifactExts'); they will therefore never
  124. %% be handled otherwise and need to be re-scanned for accuracy, even
  125. %% if they are not being analyzed (we assume `Compiler:deps' did that
  126. %% in depth already, and improvements should be driven at that level)
  127. IgnoredExts = [SourceExt | ArtifactExts],
  128. Vertices = digraph:vertices(G),
  129. [refresh_dep(G, File)
  130. || File <- Vertices,
  131. Ext <- [filename:extension(File)],
  132. not lists:member(Ext, IgnoredExts)],
  133. ok.
  134. %% @doc Take the timestamps/diff changes and propagate them from a dep to the
  135. %% parent; given:
  136. %% A 0 -> B 1 -> C 3 -> D 2
  137. %% then we expect to get back:
  138. %% A 3 -> B 3 -> C 3 -> D 2
  139. %% This is going to be safe for the current run of regeneration, but also for the
  140. %% next one; unless any file in the chain has changed, the stamp won't move up
  141. %% and there won't be a reason to recompile.
  142. %% The obvious caveat to this one is that a file changing by restoring an old version
  143. %% won't be picked up, but this weakness already existed in terms of timestamps.
  144. propagate_stamps(G) ->
  145. case is_dirty(G) of
  146. false ->
  147. %% no change, no propagation to make
  148. ok;
  149. true ->
  150. %% we can use a topsort, start at the end of it (files with no deps)
  151. %% and update them all in order. By doing this, each file only needs to check
  152. %% for one level of out-neighbours to set itself to the right appropriate time.
  153. DepSort = lists:reverse(digraph_utils:topsort(G)),
  154. propagate_stamps(G, DepSort)
  155. end.
  156. %% @doc Return the reverse sorting order to get dep-free apps first.
  157. %% -- we would usually not need to consider the non-source files for the order to
  158. %% be complete, but using them doesn't hurt.
  159. compile_order(G, AppDefs) ->
  160. Edges = [{V1,V2} || E <- digraph:edges(G),
  161. {_,V1,V2,_} <- [digraph:edge(G, E)]],
  162. AppPaths = prepare_app_paths(AppDefs),
  163. compile_order(Edges, AppPaths, #{}).
  164. %% @doc Store the DAG on disk if it was dirty
  165. maybe_store(G, Dir, Compiler, Label, CritMeta) ->
  166. case is_dirty(G) of
  167. true ->
  168. clear_dirty(G),
  169. File = dag_file(Dir, Compiler, Label),
  170. store_dag(G, File, CritMeta);
  171. false ->
  172. ok
  173. end.
  174. %% Get rid of the live state for the digraph; leave disk stuff in place.
  175. terminate(G) ->
  176. true = digraph:delete(G).
  177. %%%%%%%%%%%%%%%
  178. %%% PRIVATE %%%
  179. %%%%%%%%%%%%%%%
  180. %% @private generate the name for the DAG based on the compiler module and
  181. %% a custom label, both of which are used to prevent various compiler runs
  182. %% from clobbering each other. The label `undefined' is kept for a default
  183. %% run of the compiler, to keep in line with previous versions of the file.
  184. dag_file(Dir, CompilerMod, undefined) ->
  185. filename:join([rebar_dir:local_cache_dir(Dir), CompilerMod,
  186. ?DAG_ROOT ++ ?DAG_EXT]);
  187. dag_file(Dir, CompilerMod, Label) ->
  188. filename:join([rebar_dir:local_cache_dir(Dir), CompilerMod,
  189. ?DAG_ROOT ++ "_" ++ Label ++ ?DAG_EXT]).
  190. restore_dag(G, File, CritMeta) ->
  191. case file:read_file(File) of
  192. {ok, Data} ->
  193. %% The CritMeta value is checked and if it doesn't match, we fail
  194. %% the whole restore operation.
  195. #dag{vsn=?DAG_VSN, info={Vs, Es, CritMeta}} = binary_to_term(Data),
  196. [digraph:add_vertex(G, V, LastUpdated) || {V, LastUpdated} <- Vs],
  197. [digraph:add_edge(G, V1, V2) || {_, V1, V2, _} <- Es],
  198. ok;
  199. {error, _Err} ->
  200. ok
  201. end.
  202. store_dag(G, File, CritMeta) ->
  203. ok = filelib:ensure_dir(File),
  204. Vs = lists:map(fun(V) -> digraph:vertex(G, V) end, digraph:vertices(G)),
  205. Es = lists:map(fun(E) -> digraph:edge(G, E) end, digraph:edges(G)),
  206. Data = term_to_binary(#dag{info={Vs, Es, CritMeta}}, [{compressed, 2}]),
  207. file:write_file(File, Data).
  208. %% Drop a file from the digraph if it doesn't exist, and if so,
  209. %% delete its related build artifact
  210. maybe_rm_artifact_and_edge(G, OutDir, SrcExt, Ext, Source) ->
  211. %% This is NOT a double check it is the only check that the source file is actually gone
  212. case filelib:is_regular(Source) of
  213. true ->
  214. %% Actually exists, don't delete
  215. false;
  216. false ->
  217. Edges = digraph:in_edges(G, Source),
  218. Targets = [V2 || Edge <- Edges,
  219. {_E, _V1, V2, artifact} <- [digraph:edge(G, Edge)]],
  220. case Targets of
  221. [] ->
  222. Target = target(OutDir, Source, SrcExt, Ext),
  223. ?DEBUG("Source ~ts is gone, deleting previous ~ts file if it exists ~ts", [Source, Ext, Target]),
  224. file:delete(Target);
  225. [_|_] ->
  226. lists:foreach(fun(Target) ->
  227. ?DEBUG("Source ~ts is gone, deleting artiface ~ts "
  228. "if it exists ~ts", [Source, Target]),
  229. file:delete(Target)
  230. end, Targets)
  231. end,
  232. digraph:del_vertex(G, Source),
  233. mark_dirty(G),
  234. true
  235. end.
  236. maybe_rm_vertex(G, Source) ->
  237. case filelib:is_regular(Source) of
  238. true ->
  239. exists;
  240. false ->
  241. digraph:del_vertex(G, Source),
  242. mark_dirty(G)
  243. end.
  244. %% Add dependencies of a given file to the DAG. If the file is not found yet,
  245. %% mark its timestamp to 0, which means we have no info on it.
  246. %% Source files will be covered at a later point in their own scan, and
  247. %% non-source files are going to be covered by `populate_deps/3'.
  248. prepopulate_deps(G, Compiler, InDirs, Source, DepOpts, Status) ->
  249. SourceDir = filename:dirname(Source),
  250. AbsIncls = case erlang:function_exported(Compiler, dependencies, 4) of
  251. false ->
  252. Compiler:dependencies(Source, SourceDir, InDirs);
  253. true ->
  254. Compiler:dependencies(Source, SourceDir, InDirs, DepOpts)
  255. end,
  256. %% the file hasn't been visited yet; set it to existing, but with
  257. %% a last modified value that's null so it gets updated to something new.
  258. [digraph:add_vertex(G, Src, 0) || Src <- AbsIncls,
  259. digraph:vertex(G, Src) =:= false],
  260. %% drop edges from deps that aren't included!
  261. [digraph:del_edge(G, Edge) || Status == old,
  262. Edge <- digraph:out_edges(G, Source),
  263. {_, _Src, Path, _Label} <- [digraph:edge(G, Edge)],
  264. not lists:member(Path, AbsIncls)],
  265. %% Add the rest
  266. [digraph:add_edge(G, Source, Incl) || Incl <- AbsIncls],
  267. ok.
  268. %% check that a dep file is up to date
  269. refresh_dep(G, File) ->
  270. {_, LastUpdated} = digraph:vertex(G, File),
  271. case filelib:last_modified(File) of
  272. 0 ->
  273. %% Gone! Erase from the graph
  274. digraph:del_vertex(G, File),
  275. mark_dirty(G);
  276. {artifact, _} ->
  277. %% ignore artifacts
  278. ok;
  279. LastModified when LastUpdated < LastModified ->
  280. digraph:add_vertex(G, File, LastModified),
  281. mark_dirty(G);
  282. _ ->
  283. %% unchanged
  284. ok
  285. end.
  286. %% Do the actual propagation of all files; the files are expected to be
  287. %% in a topological order such that we don't need to go more than a level
  288. %% deep in what we search.
  289. propagate_stamps(_G, []) ->
  290. ok;
  291. propagate_stamps(G, [File|Files]) ->
  292. Stamps = [Stamp
  293. || F <- digraph:out_neighbours(G, File),
  294. {_, Stamp} <- [digraph:vertex(G, F)],
  295. is_tuple(Stamp) andalso element(1, Stamp) =/= artifact],
  296. case Stamps of
  297. [] ->
  298. ok;
  299. _ ->
  300. Max = lists:max(Stamps),
  301. case digraph:vertex(G, File) of
  302. {_, {artifact, _}} ->
  303. ok;
  304. {_, Smaller} when Smaller < Max ->
  305. digraph:add_vertex(G, File, Max);
  306. _ ->
  307. ok
  308. end
  309. end,
  310. propagate_stamps(G, Files).
  311. %% Do the actual reversal; be aware that only working from the edges
  312. %% may omit files, so we have to add all non-dependant apps manually
  313. %% to make sure we don't drop em. Since they have no deps, they're
  314. %% safer to put first (and compile first)
  315. compile_order([], AppPaths, AppDeps) ->
  316. %% use a digraph so we don't reimplement topsort by hand.
  317. G = digraph:new([acyclic]), % ignore cycles and hope it works
  318. Tups = maps:keys(AppDeps),
  319. {Va,Vb} = lists:unzip(Tups),
  320. [digraph:add_vertex(G, V) || V <- Va],
  321. [digraph:add_vertex(G, V) || V <- Vb],
  322. [digraph:add_edge(G, V1, V2) || {V1, V2} <- Tups],
  323. Sorted = lists:reverse(digraph_utils:topsort(G)),
  324. digraph:delete(G),
  325. Standalone = [Name || {_, Name} <- AppPaths] -- Sorted,
  326. Standalone ++ Sorted;
  327. compile_order([{P1,P2}|T], AppPaths, AppDeps) ->
  328. %% Assume most dependencies are between files of the same app
  329. %% so ask to see if it's the same before doing a deeper check:
  330. case find_app(P1, AppPaths) of
  331. not_found -> % system lib probably! not in the repo
  332. compile_order(T, AppPaths, AppDeps);
  333. {P1App, P1Path} ->
  334. case find_cached_app(P2, {P1App, P1Path}, AppPaths) of
  335. {P2App, _} when P2App =/= P1App ->
  336. compile_order(T, AppPaths, AppDeps#{{P1App,P2App} => true});
  337. _ ->
  338. compile_order(T, AppPaths, AppDeps)
  339. end
  340. end.
  341. %% Swap app name with paths in the order, and sort there; this lets us
  342. %% bail out early in a search where a file won't be found.
  343. prepare_app_paths(AppPaths) ->
  344. lists:sort([{filename:split(Path), Name} || {Name, Path} <- AppPaths]).
  345. %% Look for the app to which the path belongs; needed to
  346. %% go from an edge between files in the DAG to building
  347. %% app-related orderings
  348. find_app(Path, AppPaths) ->
  349. find_app_(filename:split(Path), AppPaths).
  350. %% A cached search for the app to which a path belongs;
  351. %% the assumption is that sorted edges and common relationships
  352. %% are going to be between local files within an app most
  353. %% of the time; so we first look for the same path as a
  354. %% prior match to avoid searching _all_ potential candidates.
  355. %% If it doesn't work, go for the normal search.
  356. find_cached_app(Path, {Name, AppPath}, AppPaths) ->
  357. Split = filename:split(Path),
  358. case find_app_(Split, [{AppPath, Name}]) of
  359. not_found -> find_app_(Split, AppPaths);
  360. LastEntry -> LastEntry
  361. end.
  362. %% Do the actual recursive search
  363. find_app_(_Path, []) ->
  364. not_found;
  365. find_app_(Path, [{AppPath, AppName}|Rest]) ->
  366. case lists:prefix(AppPath, Path) of
  367. true ->
  368. {AppName, AppPath};
  369. false when AppPath > Path ->
  370. not_found;
  371. false ->
  372. find_app_(Path, Rest)
  373. end.
  374. %% @private Return what should be the base name of an erl file, relocated to the
  375. %% target directory. For example:
  376. %% target_base("ebin/", "src/my_module.erl", ".erl", ".beam") -> "ebin/my_module.beam"
  377. target(OutDir, Source, SrcExt, Ext) ->
  378. filename:join(OutDir, filename:basename(Source, SrcExt) ++ Ext).
  379. %% Mark the digraph as having been modified, which is required to
  380. %% save its updated form on disk after the compiling run.
  381. %% This uses a magic vertex to carry the dirty state. This is less
  382. %% than ideal because listing vertices may expect filenames and
  383. %% instead there's going to be one trick atom through it.
  384. mark_dirty(G) ->
  385. digraph:add_vertex(G, '$r3_dirty_bit', true),
  386. ok.
  387. %% Check whether the digraph has been modified and is considered dirty.
  388. is_dirty(G) ->
  389. case digraph:vertex(G, '$r3_dirty_bit') of
  390. {_, Bool} -> Bool;
  391. false -> false
  392. end.
  393. %% Remove the dirty status. Because the saving of a digraph on disk saves all
  394. %% vertices, clear the flag before serializing it.
  395. clear_dirty(G) ->
  396. digraph:del_vertex(G, '$r3_dirty_bit').