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.

248 regels
9.4 KiB

10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
  1. -module(rebar_prv_compile).
  2. -behaviour(provider).
  3. -export([init/1,
  4. do/1,
  5. format_error/1]).
  6. -export([compile/2,
  7. compile/3]).
  8. -include_lib("providers/include/providers.hrl").
  9. -include("rebar.hrl").
  10. -define(PROVIDER, compile).
  11. -define(DEPS, [lock]).
  12. %% ===================================================================
  13. %% Public API
  14. %% ===================================================================
  15. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  16. init(State) ->
  17. State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER},
  18. {module, ?MODULE},
  19. {bare, true},
  20. {deps, ?DEPS},
  21. {example, "rebar3 compile"},
  22. {short_desc, "Compile apps .app.src and .erl files."},
  23. {desc, "Compile apps .app.src and .erl files."},
  24. {opts, []}])),
  25. {ok, State1}.
  26. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  27. do(State) ->
  28. DepsPaths = rebar_state:code_paths(State, all_deps),
  29. PluginDepsPaths = rebar_state:code_paths(State, all_plugin_deps),
  30. rebar_utils:remove_from_code_path(PluginDepsPaths),
  31. code:add_pathsa(DepsPaths),
  32. ProjectApps = rebar_state:project_apps(State),
  33. Providers = rebar_state:providers(State),
  34. Deps = rebar_state:deps_to_build(State),
  35. Cwd = rebar_state:dir(State),
  36. build_apps(State, Providers, Deps),
  37. {ok, ProjectApps1} = rebar_digraph:compile_order(ProjectApps),
  38. %% Run top level hooks *before* project apps compiled but *after* deps are
  39. rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State),
  40. ProjectApps2 = build_apps(State, Providers, ProjectApps1),
  41. State2 = rebar_state:project_apps(State, ProjectApps2),
  42. %% projects with structures like /apps/foo,/apps/bar,/test
  43. build_extra_dirs(State, ProjectApps2),
  44. State3 = update_code_paths(State2, ProjectApps2, DepsPaths),
  45. rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State2),
  46. case rebar_state:has_all_artifacts(State3) of
  47. {false, File} ->
  48. throw(?PRV_ERROR({missing_artifact, File}));
  49. true ->
  50. true
  51. end,
  52. rebar_utils:cleanup_code_path(rebar_state:code_paths(State3, default)
  53. ++ rebar_state:code_paths(State, all_plugin_deps)),
  54. {ok, State3}.
  55. -spec format_error(any()) -> iolist().
  56. format_error({missing_artifact, File}) ->
  57. io_lib:format("Missing artifact ~s", [File]);
  58. format_error(Reason) ->
  59. io_lib:format("~p", [Reason]).
  60. build_apps(State, Providers, Apps) ->
  61. [build_app(State, Providers, AppInfo) || AppInfo <- Apps].
  62. build_app(State, Providers, AppInfo) ->
  63. AppDir = rebar_app_info:dir(AppInfo),
  64. OutDir = rebar_app_info:out_dir(AppInfo),
  65. copy_app_dirs(AppInfo, AppDir, OutDir),
  66. compile(State, Providers, AppInfo).
  67. build_extra_dirs(State, Apps) ->
  68. BaseDir = rebar_state:dir(State),
  69. F = fun(App) -> rebar_app_info:dir(App) == BaseDir end,
  70. %% check that this app hasn't already been dealt with
  71. case lists:any(F, Apps) of
  72. false ->
  73. ProjOpts = rebar_state:opts(State),
  74. Extras = rebar_dir:extra_src_dirs(ProjOpts, []),
  75. [build_extra_dir(State, Dir) || Dir <- Extras];
  76. true -> ok
  77. end.
  78. build_extra_dir(_State, []) -> ok;
  79. build_extra_dir(State, Dir) ->
  80. case ec_file:is_dir(filename:join([rebar_state:dir(State), Dir])) of
  81. true ->
  82. BaseDir = filename:join([rebar_dir:base_dir(State), "extras"]),
  83. OutDir = filename:join([BaseDir, Dir]),
  84. filelib:ensure_dir(filename:join([OutDir, "dummy.beam"])),
  85. copy(rebar_state:dir(State), BaseDir, Dir),
  86. rebar_erlc_compiler:compile_dir(State, BaseDir, OutDir);
  87. false -> ok
  88. end.
  89. compile(State, AppInfo) ->
  90. compile(State, rebar_state:providers(State), AppInfo).
  91. compile(State, Providers, AppInfo) ->
  92. ?INFO("Compiling ~s", [rebar_app_info:name(AppInfo)]),
  93. AppDir = rebar_app_info:dir(AppInfo),
  94. AppInfo1 = rebar_hooks:run_all_hooks(AppDir, pre, ?PROVIDER, Providers, AppInfo, State),
  95. rebar_erlc_compiler:compile(AppInfo1),
  96. case rebar_otp_app:compile(State, AppInfo1) of
  97. {ok, AppInfo2} ->
  98. AppInfo3 = rebar_hooks:run_all_hooks(AppDir, post, ?PROVIDER, Providers, AppInfo2, State),
  99. has_all_artifacts(AppInfo3),
  100. AppInfo3;
  101. Error ->
  102. throw(Error)
  103. end.
  104. %% ===================================================================
  105. %% Internal functions
  106. %% ===================================================================
  107. update_code_paths(State, ProjectApps, DepsPaths) ->
  108. ProjAppsPaths = paths_for_apps(ProjectApps),
  109. ExtrasPaths = paths_for_extras(State, ProjectApps),
  110. rebar_state:code_paths(State, all_deps, DepsPaths ++ ProjAppsPaths ++ ExtrasPaths).
  111. paths_for_apps(Apps) -> paths_for_apps(Apps, []).
  112. paths_for_apps([], Acc) -> Acc;
  113. paths_for_apps([App|Rest], Acc) ->
  114. {_SrcDirs, ExtraDirs} = resolve_src_dirs(rebar_app_info:opts(App)),
  115. Paths = [filename:join([rebar_app_info:out_dir(App), Dir]) || Dir <- ["ebin"|ExtraDirs]],
  116. FilteredPaths = lists:filter(fun ec_file:is_dir/1, Paths),
  117. paths_for_apps(Rest, Acc ++ FilteredPaths).
  118. paths_for_extras(State, Apps) ->
  119. F = fun(App) -> rebar_app_info:dir(App) == rebar_state:dir(State) end,
  120. %% check that this app hasn't already been dealt with
  121. case lists:any(F, Apps) of
  122. false -> paths_for_extras(State);
  123. true -> []
  124. end.
  125. paths_for_extras(State) ->
  126. {_SrcDirs, ExtraDirs} = resolve_src_dirs(rebar_state:opts(State)),
  127. Paths = [filename:join([rebar_dir:base_dir(State), "extras", Dir]) || Dir <- ExtraDirs],
  128. lists:filter(fun ec_file:is_dir/1, Paths).
  129. has_all_artifacts(AppInfo1) ->
  130. case rebar_app_info:has_all_artifacts(AppInfo1) of
  131. {false, File} ->
  132. throw(?PRV_ERROR({missing_artifact, File}));
  133. true ->
  134. true
  135. end.
  136. copy_app_dirs(AppInfo, OldAppDir, AppDir) ->
  137. case ec_cnv:to_binary(filename:absname(OldAppDir)) =/=
  138. ec_cnv:to_binary(filename:absname(AppDir)) of
  139. true ->
  140. EbinDir = filename:join([OldAppDir, "ebin"]),
  141. %% copy all files from ebin if it exists
  142. case filelib:is_dir(EbinDir) of
  143. true ->
  144. OutEbin = filename:join([AppDir, "ebin"]),
  145. filelib:ensure_dir(filename:join([OutEbin, "dummy.beam"])),
  146. rebar_file_utils:cp_r(filelib:wildcard(filename:join([EbinDir, "*"])), OutEbin);
  147. false ->
  148. ok
  149. end,
  150. filelib:ensure_dir(filename:join([AppDir, "dummy"])),
  151. %% link or copy mibs if it exists
  152. case filelib:is_dir(filename:join([OldAppDir, "mibs"])) of
  153. true ->
  154. %% If mibs exist it means we must ensure priv exists.
  155. %% mibs files are compiled to priv/mibs/
  156. filelib:ensure_dir(filename:join([OldAppDir, "priv", "dummy"])),
  157. symlink_or_copy(OldAppDir, AppDir, "mibs");
  158. false ->
  159. ok
  160. end,
  161. {SrcDirs, ExtraDirs} = resolve_src_dirs(rebar_app_info:opts(AppInfo)),
  162. %% link to src_dirs to be adjacent to ebin is needed for R15 use of cover/xref
  163. [symlink_or_copy(OldAppDir, AppDir, Dir) || Dir <- ["priv", "include"] ++ SrcDirs],
  164. %% copy all extra_src_dirs as they build into themselves and linking means they
  165. %% are shared across profiles
  166. [copy(OldAppDir, AppDir, Dir) || Dir <- ExtraDirs];
  167. false ->
  168. ok
  169. end.
  170. symlink_or_copy(OldAppDir, AppDir, Dir) ->
  171. Source = filename:join([OldAppDir, Dir]),
  172. Target = filename:join([AppDir, Dir]),
  173. rebar_file_utils:symlink_or_copy(Source, Target).
  174. copy(OldAppDir, AppDir, Dir) ->
  175. Source = filename:join([OldAppDir, Dir]),
  176. Target = filename:join([AppDir, Dir]),
  177. case ec_file:is_dir(Source) of
  178. true -> copy(Source, Target);
  179. false -> ok
  180. end.
  181. %% TODO: use ec_file:copy/2 to do this, it preserves timestamps and
  182. %% may prevent recompilation of files in extra dirs
  183. copy(Source, Target) ->
  184. %% important to do this so no files are copied onto themselves
  185. %% which truncates them to zero length on some platforms
  186. ok = delete_if_symlink(Target),
  187. ok = filelib:ensure_dir(filename:join([Target, "dummy.beam"])),
  188. {ok, Files} = rebar_utils:list_dir(Source),
  189. case [filename:join([Source, F]) || F <- Files] of
  190. [] -> ok;
  191. Paths -> rebar_file_utils:cp_r(Paths, Target)
  192. end.
  193. delete_if_symlink(Path) ->
  194. case ec_file:is_symlink(Path) of
  195. true -> file:delete(Path);
  196. false -> ok
  197. end.
  198. resolve_src_dirs(Opts) ->
  199. SrcDirs = rebar_dir:src_dirs(Opts, ["src"]),
  200. ExtraDirs = rebar_dir:extra_src_dirs(Opts, []),
  201. normalize_src_dirs(SrcDirs, ExtraDirs).
  202. %% remove duplicates and make sure no directories that exist
  203. %% in src_dirs also exist in extra_src_dirs
  204. normalize_src_dirs(SrcDirs, ExtraDirs) ->
  205. S = lists:usort(SrcDirs),
  206. E = lists:usort(ExtraDirs),
  207. {S, lists:subtract(E, S)}.