Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

841 рядки
35 KiB

  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. %% -------------------------------------------------------------------
  4. %%
  5. %% rebar: Erlang Build Tools
  6. %%
  7. %% Copyright (c) 2009, 2010 Dave Smith (dizzyd@dizzyd.com)
  8. %%
  9. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  10. %% of this software and associated documentation files (the "Software"), to deal
  11. %% in the Software without restriction, including without limitation the rights
  12. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. %% copies of the Software, and to permit persons to whom the Software is
  14. %% furnished to do so, subject to the following conditions:
  15. %%
  16. %% The above copyright notice and this permission notice shall be included in
  17. %% all copies or substantial portions of the Software.
  18. %%
  19. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. %% THE SOFTWARE.
  26. %% -------------------------------------------------------------------
  27. -module(rebar_erlc_compiler).
  28. -export([compile/1, compile/2, compile/3,
  29. compile_dir/3, compile_dir/4,
  30. compile_dirs/5,
  31. clean/1]).
  32. -include("rebar.hrl").
  33. -include_lib("stdlib/include/erl_compile.hrl").
  34. -define(ERLCINFO_VSN, 2).
  35. -define(ERLCINFO_FILE, "erlcinfo").
  36. -type erlc_info_v() :: {digraph:vertex(), term()} | 'false'.
  37. -type erlc_info_e() :: {digraph:vertex(), digraph:vertex()}.
  38. -type erlc_info() :: {list(erlc_info_v()), list(erlc_info_e()), list(string())}.
  39. -record(erlcinfo, {
  40. vsn = ?ERLCINFO_VSN :: pos_integer(),
  41. info = {[], [], []} :: erlc_info()
  42. }).
  43. -type compile_opts() :: [compile_opt()].
  44. -type compile_opt() :: {recursive, boolean()}.
  45. -define(DEFAULT_OUTDIR, "ebin").
  46. -define(RE_PREFIX, "^(?!\\._)").
  47. %% ===================================================================
  48. %% Public API
  49. %% ===================================================================
  50. %% Supported configuration variables:
  51. %%
  52. %% * erl_opts - Erlang list of options passed to compile:file/2
  53. %% It is also possible to specify platform specific
  54. %% options by specifying a pair or a triplet where the
  55. %% first string is a regex that is checked against the
  56. %% string
  57. %%
  58. %% OtpRelease ++ "-" ++ SysArch ++ "-" ++ Words.
  59. %%
  60. %% where
  61. %%
  62. %% OtpRelease = erlang:system_info(otp_release).
  63. %% SysArch = erlang:system_info(system_architecture).
  64. %% Words = integer_to_list(8 *
  65. %% erlang:system_info({wordsize, external})).
  66. %%
  67. %% E.g. to define HAVE_SENDFILE only on systems with
  68. %% sendfile(), to define BACKLOG on Linux/FreeBSD as 128,
  69. %% and to define 'old_inets' for R13 OTP release do:
  70. %%
  71. %% {erl_opts, [{platform_define,
  72. %% "(linux|solaris|freebsd|darwin)",
  73. %% 'HAVE_SENDFILE'},
  74. %% {platform_define, "(linux|freebsd)",
  75. %% 'BACKLOG', 128},
  76. %% {platform_define, "R13",
  77. %% 'old_inets'}]}.
  78. %%
  79. %% @equiv compile(AppInfo, [])
  80. -spec compile(rebar_app_info:t()) -> ok.
  81. compile(AppInfo) when element(1, AppInfo) == app_info_t ->
  82. compile(AppInfo, []).
  83. %% @doc compile an individual application.
  84. -spec compile(rebar_app_info:t(), compile_opts()) -> ok.
  85. compile(AppInfo, CompileOpts) when element(1, AppInfo) == app_info_t ->
  86. warn_deprecated(),
  87. Dir = rebar_utils:to_list(rebar_app_info:out_dir(AppInfo)),
  88. RebarOpts = rebar_app_info:opts(AppInfo),
  89. SrcOpts = [check_last_mod,
  90. {recursive, dir_recursive(RebarOpts, "src", CompileOpts)}],
  91. MibsOpts = [check_last_mod,
  92. {recursive, dir_recursive(RebarOpts, "mibs", CompileOpts)}],
  93. rebar_base_compiler:run(RebarOpts,
  94. check_files([filename:join(Dir, File)
  95. || File <- rebar_opts:get(RebarOpts, xrl_first_files, [])]),
  96. filename:join(Dir, "src"), ".xrl", filename:join(Dir, "src"), ".erl",
  97. fun compile_xrl/3, SrcOpts),
  98. rebar_base_compiler:run(RebarOpts,
  99. check_files([filename:join(Dir, File)
  100. || File <- rebar_opts:get(RebarOpts, yrl_first_files, [])]),
  101. filename:join(Dir, "src"), ".yrl", filename:join(Dir, "src"), ".erl",
  102. fun compile_yrl/3, SrcOpts),
  103. rebar_base_compiler:run(RebarOpts,
  104. check_files([filename:join(Dir, File)
  105. || File <- rebar_opts:get(RebarOpts, mib_first_files, [])]),
  106. filename:join(Dir, "mibs"), ".mib", filename:join([Dir, "priv", "mibs"]), ".bin",
  107. compile_mib(AppInfo), MibsOpts),
  108. SrcDirs = lists:map(fun(SrcDir) -> filename:join(Dir, SrcDir) end,
  109. rebar_dir:src_dirs(RebarOpts, ["src"])),
  110. OutDir = filename:join(Dir, outdir(RebarOpts)),
  111. compile_dirs(RebarOpts, Dir, SrcDirs, OutDir, CompileOpts),
  112. ExtraDirs = rebar_dir:extra_src_dirs(RebarOpts),
  113. F = fun(D) ->
  114. case ec_file:is_dir(filename:join([Dir, D])) of
  115. true -> compile_dirs(RebarOpts, Dir, [D], D, CompileOpts);
  116. false -> ok
  117. end
  118. end,
  119. lists:foreach(F, lists:map(fun(SrcDir) -> filename:join(Dir, SrcDir) end, ExtraDirs)).
  120. %% @hidden
  121. %% these are kept for backwards compatibility but they're bad functions with
  122. %% bad interfaces you probably shouldn't use
  123. %% State/RebarOpts have to have src_dirs set and BaseDir must be the parent
  124. %% directory of those src_dirs
  125. -spec compile(rebar_dict() | rebar_state:t(), file:name(), file:name()) -> ok.
  126. compile(State, BaseDir, OutDir) when element(1, State) == state_t ->
  127. compile(rebar_state:opts(State), BaseDir, OutDir, [{recursive, false}]);
  128. compile(RebarOpts, BaseDir, OutDir) ->
  129. compile(RebarOpts, BaseDir, OutDir, [{recursive, false}]).
  130. %% @hidden
  131. -spec compile(rebar_dict() | rebar_state:t(), file:name(), file:name(), compile_opts()) -> ok.
  132. compile(State, BaseDir, OutDir, CompileOpts) when element(1, State) == state_t ->
  133. compile(rebar_state:opts(State), BaseDir, OutDir, CompileOpts);
  134. compile(RebarOpts, BaseDir, OutDir, CompileOpts) ->
  135. warn_deprecated(),
  136. SrcDirs = lists:map(fun(SrcDir) -> filename:join(BaseDir, SrcDir) end,
  137. rebar_dir:src_dirs(RebarOpts, ["src"])),
  138. compile_dirs(RebarOpts, BaseDir, SrcDirs, OutDir, CompileOpts),
  139. ExtraDirs = rebar_dir:extra_src_dirs(RebarOpts),
  140. F = fun(D) ->
  141. case ec_file:is_dir(filename:join([BaseDir, D])) of
  142. true -> compile_dirs(RebarOpts, BaseDir, [D], D, CompileOpts);
  143. false -> ok
  144. end
  145. end,
  146. lists:foreach(F, lists:map(fun(SrcDir) -> filename:join(BaseDir, SrcDir) end, ExtraDirs)).
  147. %% @equiv compile_dirs(Context, BaseDir, [Dir], Dir, [{recursive, false}])
  148. -spec compile_dir(rebar_dict() | rebar_state:t(), file:name(), file:name()) -> ok.
  149. compile_dir(State, BaseDir, Dir) when element(1, State) == state_t ->
  150. compile_dir(rebar_state:opts(State), BaseDir, Dir, [{recursive, false}]);
  151. compile_dir(RebarOpts, BaseDir, Dir) ->
  152. compile_dir(RebarOpts, BaseDir, Dir, [{recursive, false}]).
  153. %% @equiv compile_dirs(Context, BaseDir, [Dir], Dir, Opts)
  154. -spec compile_dir(rebar_dict() | rebar_state:t(), file:name(), file:name(), compile_opts()) -> ok.
  155. compile_dir(State, BaseDir, Dir, Opts) when element(1, State) == state_t ->
  156. compile_dirs(rebar_state:opts(State), BaseDir, [Dir], Dir, Opts);
  157. compile_dir(RebarOpts, BaseDir, Dir, Opts) ->
  158. compile_dirs(RebarOpts, BaseDir, [Dir], Dir, Opts).
  159. %% @doc compile a list of directories with the given opts.
  160. -spec compile_dirs(rebar_dict() | rebar_state:t(),
  161. file:filename(),
  162. [file:filename()],
  163. file:filename(),
  164. compile_opts()) -> ok.
  165. compile_dirs(State, BaseDir, Dirs, OutDir, CompileOpts) when element(1, State) == state_t ->
  166. compile_dirs(rebar_state:opts(State), BaseDir, Dirs, OutDir, CompileOpts);
  167. compile_dirs(RebarOpts, BaseDir, SrcDirs, OutDir, CompileOpts) ->
  168. ErlOpts = rebar_opts:erl_opts(RebarOpts),
  169. ?DEBUG("erlopts ~p", [ErlOpts]),
  170. AllErlFiles = gather_src(RebarOpts, BaseDir, SrcDirs, CompileOpts),
  171. ?DEBUG("files to compile ~p", [AllErlFiles]),
  172. %% Make sure that outdir is on the path
  173. ok = filelib:ensure_dir(filename:join(OutDir, "dummy.beam")),
  174. true = code:add_patha(filename:absname(OutDir)),
  175. G = init_erlcinfo(include_abs_dirs(ErlOpts, BaseDir), AllErlFiles, BaseDir, OutDir),
  176. {ParseTransforms, Rest} = split_source_files(AllErlFiles, ErlOpts),
  177. NeededErlFiles = case needed_files(G, ErlOpts, RebarOpts, BaseDir, OutDir, ParseTransforms) of
  178. [] -> needed_files(G, ErlOpts, RebarOpts, BaseDir, OutDir, Rest);
  179. %% at least one parse transform in the opts needs updating, so recompile all
  180. _ -> AllErlFiles
  181. end,
  182. {ErlFirstFiles, ErlOptsFirst} = erl_first_files(RebarOpts, ErlOpts, BaseDir, NeededErlFiles),
  183. {DepErls, OtherErls} = lists:partition(
  184. fun(Source) -> digraph:in_degree(G, Source) > 0 end,
  185. [File || File <- NeededErlFiles, not lists:member(File, ErlFirstFiles)]),
  186. SubGraph = digraph_utils:subgraph(G, DepErls),
  187. DepErlsOrdered = digraph_utils:topsort(SubGraph),
  188. FirstErls = ErlFirstFiles ++ lists:reverse(DepErlsOrdered),
  189. try
  190. rebar_base_compiler:run(
  191. RebarOpts, FirstErls, OtherErls,
  192. fun(S, C) ->
  193. ErlOpts1 = case lists:member(S, ErlFirstFiles) of
  194. true -> ErlOptsFirst;
  195. false -> ErlOpts
  196. end,
  197. internal_erl_compile(C, BaseDir, S, OutDir, ErlOpts1, RebarOpts)
  198. end)
  199. after
  200. true = digraph:delete(SubGraph),
  201. true = digraph:delete(G)
  202. end,
  203. ok.
  204. %% @doc remove compiled artifacts from an AppDir.
  205. -spec clean(rebar_app_info:t()) -> 'ok'.
  206. clean(AppInfo) ->
  207. AppDir = rebar_app_info:out_dir(AppInfo),
  208. MibFiles = rebar_utils:find_files(filename:join([AppDir, "mibs"]), ?RE_PREFIX".*\\.mib\$"),
  209. MIBs = [filename:rootname(filename:basename(MIB)) || MIB <- MibFiles],
  210. rebar_file_utils:delete_each(
  211. [filename:join([AppDir, "include",MIB++".hrl"]) || MIB <- MIBs]),
  212. ok = rebar_file_utils:rm_rf(filename:join([AppDir, "priv/mibs/*.bin"])),
  213. YrlFiles = rebar_utils:find_files(filename:join([AppDir, "src"]), ?RE_PREFIX".*\\.[x|y]rl\$"),
  214. rebar_file_utils:delete_each(
  215. [rebar_utils:to_list(re:replace(F, "\\.[x|y]rl$", ".erl", [unicode]))
  216. || F <- YrlFiles]),
  217. BinDirs = ["ebin"|rebar_dir:extra_src_dirs(rebar_app_info:opts(AppInfo))],
  218. ok = clean_dirs(AppDir, BinDirs),
  219. %% Delete the build graph, if any
  220. rebar_file_utils:rm_rf(erlcinfo_file(AppDir)).
  221. clean_dirs(_AppDir, []) -> ok;
  222. clean_dirs(AppDir, [Dir|Rest]) ->
  223. ok = rebar_file_utils:rm_rf(filename:join([AppDir, Dir, "*.beam"])),
  224. %% Erlang compilation is recursive, so it's possible that we have a nested
  225. %% directory structure in ebin with .beam files within. As such, we want
  226. %% to scan whatever is left in the app's out_dir directory for sub-dirs which
  227. %% satisfy our criteria.
  228. BeamFiles = rebar_utils:find_files(filename:join([AppDir, Dir]), ?RE_PREFIX".*\\.beam\$"),
  229. rebar_file_utils:delete_each(BeamFiles),
  230. lists:foreach(fun(D) -> delete_dir(D, dirs(D)) end, dirs(filename:join([AppDir, Dir]))),
  231. clean_dirs(AppDir, Rest).
  232. %% ===================================================================
  233. %% Internal functions
  234. %% ===================================================================
  235. gather_src(Opts, BaseDir, Dirs, CompileOpts) ->
  236. gather_src(Opts, filename:split(BaseDir), Dirs, [], CompileOpts).
  237. gather_src(_Opts, _BaseDirParts, [], Srcs, _CompileOpts) -> Srcs;
  238. gather_src(Opts, BaseDirParts, [Dir|Rest], Srcs, CompileOpts) ->
  239. DirParts = filename:split(Dir),
  240. RelDir = case lists:prefix(BaseDirParts,DirParts) of
  241. true ->
  242. case lists:nthtail(length(BaseDirParts),DirParts) of
  243. [] -> ".";
  244. RestParts -> filename:join(RestParts)
  245. end;
  246. false -> Dir
  247. end,
  248. DirRecursive = dir_recursive(Opts, RelDir, CompileOpts),
  249. gather_src(Opts, BaseDirParts, Rest, Srcs ++ rebar_utils:find_files(Dir, ?RE_PREFIX".*\\.erl\$", DirRecursive), CompileOpts).
  250. %% Get files which need to be compiled first, i.e. those specified in erl_first_files
  251. %% and parse_transform options. Also produce specific erl_opts for these first
  252. %% files, so that yet to be compiled parse transformations are excluded from it.
  253. erl_first_files(Opts, ErlOpts, Dir, NeededErlFiles) ->
  254. ErlFirstFilesConf = rebar_opts:get(Opts, erl_first_files, []),
  255. valid_erl_first_conf(ErlFirstFilesConf),
  256. NeededSrcDirs = lists:usort(lists:map(fun filename:dirname/1, NeededErlFiles)),
  257. %% NOTE: order of files here is important!
  258. ErlFirstFiles =
  259. [filename:join(Dir, File) || File <- ErlFirstFilesConf,
  260. lists:member(filename:join(Dir, File), NeededErlFiles)],
  261. {ParseTransforms, ParseTransformsErls} =
  262. lists:unzip(lists:flatmap(
  263. fun(PT) ->
  264. PTerls = [filename:join(D, module_to_erl(PT)) || D <- NeededSrcDirs],
  265. [{PT, PTerl} || PTerl <- PTerls, lists:member(PTerl, NeededErlFiles)]
  266. end, proplists:get_all_values(parse_transform, ErlOpts))),
  267. ErlOptsFirst = lists:filter(fun({parse_transform, PT}) ->
  268. not lists:member(PT, ParseTransforms);
  269. (_) ->
  270. true
  271. end, ErlOpts),
  272. {ErlFirstFiles ++ ParseTransformsErls, ErlOptsFirst}.
  273. split_source_files(SourceFiles, ErlOpts) ->
  274. ParseTransforms = proplists:get_all_values(parse_transform, ErlOpts),
  275. lists:partition(fun(Source) ->
  276. lists:member(filename_to_atom(Source), ParseTransforms)
  277. end, SourceFiles).
  278. filename_to_atom(F) -> list_to_atom(filename:rootname(filename:basename(F))).
  279. %% Get subset of SourceFiles which need to be recompiled, respecting
  280. %% dependencies induced by given graph G.
  281. needed_files(G, ErlOpts, RebarOpts, Dir, OutDir, SourceFiles) ->
  282. lists:filter(fun(Source) ->
  283. TargetBase = target_base(OutDir, Source),
  284. Target = TargetBase ++ ".beam",
  285. PrivIncludes = [{i, filename:join(Dir, Src)}
  286. || Src <- rebar_dir:all_src_dirs(RebarOpts, ["src"], [])],
  287. AllOpts = [{outdir, filename:dirname(Target)}
  288. ,{i, filename:join(Dir, "include")}
  289. ,{i, Dir}] ++ PrivIncludes ++ ErlOpts,
  290. %% necessary for erlang:function_exported/3 to work as expected
  291. %% called here for clarity as it's required by both opts_changed/2
  292. %% and erl_compiler_opts_set/0
  293. _ = code:ensure_loaded(compile),
  294. digraph:vertex(G, Source) > {Source, filelib:last_modified(Target)}
  295. orelse opts_changed(AllOpts, TargetBase)
  296. orelse erl_compiler_opts_set()
  297. end, SourceFiles).
  298. maybe_rm_beam_and_edge(G, OutDir, Source) ->
  299. %% This is NOT a double check it is the only check that the source file is actually gone
  300. case filelib:is_regular(Source) of
  301. true ->
  302. %% Actually exists, don't delete
  303. false;
  304. false ->
  305. Target = target_base(OutDir, Source) ++ ".beam",
  306. ?DEBUG("Source ~ts is gone, deleting previous beam file if it exists ~ts", [Source, Target]),
  307. file:delete(Target),
  308. digraph:del_vertex(G, Source),
  309. true
  310. end.
  311. opts_changed(NewOpts, Target) ->
  312. TotalOpts = case erlang:function_exported(compile, env_compiler_options, 0) of
  313. true -> NewOpts ++ compile:env_compiler_options();
  314. false -> NewOpts
  315. end,
  316. case compile_info(Target) of
  317. {ok, Opts} -> lists:any(fun effects_code_generation/1, lists:usort(TotalOpts) -- lists:usort(Opts));
  318. _ -> true
  319. end.
  320. effects_code_generation(Option) ->
  321. case Option of
  322. beam -> false;
  323. report_warnings -> false;
  324. report_errors -> false;
  325. return_errors-> false;
  326. return_warnings-> false;
  327. report -> false;
  328. warnings_as_errors -> false;
  329. binary -> false;
  330. verbose -> false;
  331. {cwd,_} -> false;
  332. {outdir, _} -> false;
  333. _ -> true
  334. end.
  335. compile_info(Target) ->
  336. case beam_lib:chunks(Target, [compile_info]) of
  337. {ok, {_mod, Chunks}} ->
  338. CompileInfo = proplists:get_value(compile_info, Chunks, []),
  339. {ok, proplists:get_value(options, CompileInfo, [])};
  340. {error, beam_lib, Reason} ->
  341. ?WARN("Couldn't read debug info from ~p for reason: ~p", [Target, Reason]),
  342. {error, Reason}
  343. end.
  344. erl_compiler_opts_set() ->
  345. EnvSet = case os:getenv("ERL_COMPILER_OPTIONS") of
  346. false -> false;
  347. _ -> true
  348. end,
  349. %% return false if changed env opts would have been caught in opts_changed/2
  350. EnvSet andalso not erlang:function_exported(compile, env_compiler_options, 0).
  351. erlcinfo_file(Dir) ->
  352. filename:join(rebar_dir:local_cache_dir(Dir), ?ERLCINFO_FILE).
  353. %% Get dependency graph of given Erls files and their dependencies (header files,
  354. %% parse transforms, behaviours etc.) located in their directories or given
  355. %% InclDirs. Note that last modification times stored in vertices already respect
  356. %% dependencies induced by given graph G.
  357. init_erlcinfo(InclDirs, Erls, Dir, OutDir) ->
  358. G = digraph:new([acyclic]),
  359. try restore_erlcinfo(G, InclDirs, Dir)
  360. catch
  361. _:_ ->
  362. ?WARN("Failed to restore ~ts file. Discarding it.~n", [erlcinfo_file(Dir)]),
  363. file:delete(erlcinfo_file(Dir))
  364. end,
  365. Dirs = source_and_include_dirs(InclDirs, Erls),
  366. %% A source file may have been renamed or deleted. Remove it from the graph
  367. %% and remove any beam file for that source if it exists.
  368. Modified = maybe_rm_beams_and_edges(G, OutDir, Erls),
  369. Modified1 = lists:foldl(update_erlcinfo_fun(G, Dirs), Modified, Erls),
  370. if Modified1 -> store_erlcinfo(G, InclDirs, Dir); not Modified1 -> ok end,
  371. G.
  372. maybe_rm_beams_and_edges(G, Dir, Files) ->
  373. Vertices = digraph:vertices(G),
  374. case lists:filter(fun(File) ->
  375. case filename:extension(File) =:= ".erl" of
  376. true ->
  377. maybe_rm_beam_and_edge(G, Dir, File);
  378. false ->
  379. false
  380. end
  381. end, lists:sort(Vertices) -- lists:sort(Files)) of
  382. [] ->
  383. false;
  384. _ ->
  385. true
  386. end.
  387. source_and_include_dirs(InclDirs, Erls) ->
  388. SourceDirs = lists:map(fun filename:dirname/1, Erls),
  389. lists:usort(InclDirs ++ SourceDirs).
  390. update_erlcinfo(G, Dirs, Source) ->
  391. case digraph:vertex(G, Source) of
  392. {_, LastUpdated} ->
  393. case filelib:last_modified(Source) of
  394. 0 ->
  395. %% The file doesn't exist anymore,
  396. %% erase it from the graph.
  397. %% All the edges will be erased automatically.
  398. digraph:del_vertex(G, Source),
  399. modified;
  400. LastModified when LastUpdated < LastModified ->
  401. modify_erlcinfo(G, Source, LastModified, filename:dirname(Source), Dirs);
  402. _ ->
  403. Modified = lists:foldl(
  404. update_erlcinfo_fun(G, Dirs),
  405. false, digraph:out_neighbours(G, Source)),
  406. MaxModified = update_max_modified_deps(G, Source),
  407. case Modified orelse MaxModified > LastUpdated of
  408. true -> modified;
  409. false -> unmodified
  410. end
  411. end;
  412. false ->
  413. modify_erlcinfo(G, Source, filelib:last_modified(Source), filename:dirname(Source), Dirs)
  414. end.
  415. update_erlcinfo_fun(G, Dirs) ->
  416. fun(Erl, Modified) ->
  417. case update_erlcinfo(G, Dirs, Erl) of
  418. modified -> true;
  419. unmodified -> Modified
  420. end
  421. end.
  422. update_max_modified_deps(G, Source) ->
  423. MaxModified = lists:max(lists:map(
  424. fun(File) -> {_, MaxModified} = digraph:vertex(G, File), MaxModified end,
  425. [Source|digraph:out_neighbours(G, Source)])),
  426. digraph:add_vertex(G, Source, MaxModified),
  427. MaxModified.
  428. modify_erlcinfo(G, Source, LastModified, Dir, Dirs) ->
  429. {ok, Fd} = file:open(Source, [read]),
  430. Incls = parse_attrs(Fd, [], Dir),
  431. AbsIncls = expand_file_names(Incls, Dirs),
  432. ok = file:close(Fd),
  433. digraph:add_vertex(G, Source, LastModified),
  434. digraph:del_edges(G, digraph:out_edges(G, Source)),
  435. lists:foreach(
  436. fun(Incl) ->
  437. update_erlcinfo(G, Dirs, Incl),
  438. digraph:add_edge(G, Source, Incl)
  439. end, AbsIncls),
  440. modified.
  441. restore_erlcinfo(G, InclDirs, Dir) ->
  442. case file:read_file(erlcinfo_file(Dir)) of
  443. {ok, Data} ->
  444. % Since externally passed InclDirs can influence erlcinfo graph (see
  445. % modify_erlcinfo), we have to check here that they didn't change.
  446. #erlcinfo{vsn=?ERLCINFO_VSN, info={Vs, Es, InclDirs}} =
  447. binary_to_term(Data),
  448. lists:foreach(
  449. fun({V, LastUpdated}) ->
  450. digraph:add_vertex(G, V, LastUpdated)
  451. end, Vs),
  452. lists:foreach(
  453. fun({_, V1, V2, _}) ->
  454. digraph:add_edge(G, V1, V2)
  455. end, Es);
  456. {error, _} ->
  457. ok
  458. end.
  459. store_erlcinfo(G, InclDirs, Dir) ->
  460. Vs = lists:map(fun(V) -> digraph:vertex(G, V) end, digraph:vertices(G)),
  461. Es = lists:map(fun(E) -> digraph:edge(G, E) end, digraph:edges(G)),
  462. File = erlcinfo_file(Dir),
  463. ok = filelib:ensure_dir(File),
  464. Data = term_to_binary(#erlcinfo{info={Vs, Es, InclDirs}}, [{compressed, 2}]),
  465. file:write_file(File, Data).
  466. %% NOTE: If, for example, one of the entries in Files, refers to
  467. %% gen_server.erl, that entry will be dropped. It is dropped because
  468. %% such an entry usually refers to the beam file, and we don't pass a
  469. %% list of OTP src dirs for finding gen_server.erl's full path. Also,
  470. %% if gen_server.erl was modified, it's not rebar's task to compile a
  471. %% new version of the beam file. Therefore, it's reasonable to drop
  472. %% such entries. Also see process_attr(behaviour, Form, Includes).
  473. -spec expand_file_names([file:filename()],
  474. [file:filename()]) -> [file:filename()].
  475. expand_file_names(Files, Dirs) ->
  476. %% We check if Files exist by itself or within the directories
  477. %% listed in Dirs.
  478. %% Return the list of files matched.
  479. lists:flatmap(
  480. fun(Incl) ->
  481. case filelib:is_regular(Incl) of
  482. true ->
  483. [Incl];
  484. false ->
  485. lists:flatmap(
  486. fun(Dir) ->
  487. FullPath = filename:join(Dir, Incl),
  488. case filelib:is_regular(FullPath) of
  489. true ->
  490. [FullPath];
  491. false ->
  492. []
  493. end
  494. end, Dirs)
  495. end
  496. end, Files).
  497. -spec internal_erl_compile(rebar_dict(), file:filename(), file:filename(),
  498. file:filename(), list(), rebar_dict()) ->
  499. ok | {ok, any()} | {error, any(), any()}.
  500. internal_erl_compile(Opts, Dir, Module, OutDir, ErlOpts, RebarOpts) ->
  501. Target = target_base(OutDir, Module) ++ ".beam",
  502. ok = filelib:ensure_dir(Target),
  503. PrivIncludes = [{i, filename:join(Dir, Src)}
  504. || Src <- rebar_dir:all_src_dirs(RebarOpts, ["src"], [])],
  505. AllOpts = [{outdir, filename:dirname(Target)}] ++ ErlOpts ++ PrivIncludes ++
  506. [{i, filename:join(Dir, "include")}, {i, Dir}, return],
  507. case compile:file(Module, AllOpts) of
  508. {ok, _Mod} ->
  509. ok;
  510. {ok, _Mod, Ws} ->
  511. FormattedWs = format_error_sources(Ws, Opts),
  512. rebar_base_compiler:ok_tuple(Module, FormattedWs);
  513. {error, Es, Ws} ->
  514. error_tuple(Module, Es, Ws, AllOpts, Opts)
  515. end.
  516. error_tuple(Module, Es, Ws, AllOpts, Opts) ->
  517. FormattedEs = format_error_sources(Es, Opts),
  518. FormattedWs = format_error_sources(Ws, Opts),
  519. rebar_base_compiler:error_tuple(Module, FormattedEs, FormattedWs, AllOpts).
  520. format_error_sources(Es, Opts) ->
  521. [{rebar_base_compiler:format_error_source(Src, Opts), Desc}
  522. || {Src, Desc} <- Es].
  523. target_base(OutDir, Source) ->
  524. filename:join(OutDir, filename:basename(Source, ".erl")).
  525. -spec compile_mib(rebar_app_info:t()) ->
  526. fun((file:filename(), file:filename(), rebar_dict()) -> 'ok').
  527. compile_mib(AppInfo) ->
  528. fun(Source, Target, Opts) ->
  529. Dir = filename:dirname(Target),
  530. Mib = filename:rootname(Target),
  531. HrlFilename = Mib ++ ".hrl",
  532. AppInclude = filename:join([rebar_app_info:dir(AppInfo), "include"]),
  533. ok = filelib:ensure_dir(Target),
  534. ok = filelib:ensure_dir(filename:join([AppInclude, "dummy.hrl"])),
  535. AllOpts = [{outdir, Dir}
  536. ,{i, [Dir]}] ++
  537. rebar_opts:get(Opts, mib_opts, []),
  538. case snmpc:compile(Source, AllOpts) of
  539. {ok, _} ->
  540. MibToHrlOpts =
  541. case proplists:get_value(verbosity, AllOpts, undefined) of
  542. undefined ->
  543. #options{specific = [],
  544. cwd = rebar_dir:get_cwd()};
  545. Verbosity ->
  546. #options{specific = [{verbosity, Verbosity}],
  547. cwd = rebar_dir:get_cwd()}
  548. end,
  549. ok = snmpc:mib_to_hrl(Mib, Mib, MibToHrlOpts),
  550. rebar_file_utils:mv(HrlFilename, AppInclude),
  551. ok;
  552. {error, compilation_failed} ->
  553. ?FAIL
  554. end
  555. end.
  556. -spec compile_xrl(file:filename(), file:filename(),
  557. rebar_dict()) -> 'ok'.
  558. compile_xrl(Source, Target, Opts) ->
  559. AllOpts = [{scannerfile, Target} | rebar_opts:get(Opts, xrl_opts, [])],
  560. compile_xrl_yrl(Opts, Source, Target, AllOpts, leex).
  561. -spec compile_yrl(file:filename(), file:filename(),
  562. rebar_dict()) -> 'ok'.
  563. compile_yrl(Source, Target, Opts) ->
  564. AllOpts = [{parserfile, Target} | rebar_opts:get(Opts, yrl_opts, [])],
  565. compile_xrl_yrl(Opts, Source, Target, AllOpts, yecc).
  566. -spec compile_xrl_yrl(rebar_dict(), file:filename(),
  567. file:filename(), list(), module()) -> 'ok'.
  568. compile_xrl_yrl(_Opts, Source, Target, AllOpts, Mod) ->
  569. %% FIX ME: should be the outdir or something
  570. Dir = filename:dirname(filename:dirname(Target)),
  571. AllOpts1 = [{includefile, filename:join(Dir, I)} || {includefile, I} <- AllOpts,
  572. filename:pathtype(I) =:= relative],
  573. case needs_compile(Source, Target) of
  574. true ->
  575. case Mod:file(Source, AllOpts1 ++ [{return, true}]) of
  576. {ok, _} ->
  577. ok;
  578. {ok, _Mod, Ws} ->
  579. rebar_base_compiler:ok_tuple(Source, Ws);
  580. {error, Es, Ws} ->
  581. rebar_base_compiler:error_tuple(Source,
  582. Es, Ws, AllOpts1)
  583. end;
  584. false ->
  585. skipped
  586. end.
  587. needs_compile(Source, Target) ->
  588. filelib:last_modified(Source) > filelib:last_modified(Target).
  589. -spec dirs(file:filename()) -> [file:filename()].
  590. dirs(Dir) ->
  591. [F || F <- filelib:wildcard(filename:join([Dir, "*"])), filelib:is_dir(F)].
  592. -spec delete_dir(file:filename(), [string()]) -> 'ok' | {'error', atom()}.
  593. delete_dir(Dir, []) ->
  594. file:del_dir(Dir);
  595. delete_dir(Dir, Subdirs) ->
  596. lists:foreach(fun(D) -> delete_dir(D, dirs(D)) end, Subdirs),
  597. file:del_dir(Dir).
  598. parse_attrs(Fd, Includes, Dir) ->
  599. case io:parse_erl_form(Fd, "") of
  600. {ok, Form, _Line} ->
  601. case erl_syntax:type(Form) of
  602. attribute ->
  603. NewIncludes = process_attr(Form, Includes, Dir),
  604. parse_attrs(Fd, NewIncludes, Dir);
  605. _ ->
  606. parse_attrs(Fd, Includes, Dir)
  607. end;
  608. {eof, _} ->
  609. Includes;
  610. _Err ->
  611. parse_attrs(Fd, Includes, Dir)
  612. end.
  613. process_attr(Form, Includes, Dir) ->
  614. AttrName = erl_syntax:atom_value(erl_syntax:attribute_name(Form)),
  615. process_attr(AttrName, Form, Includes, Dir).
  616. process_attr(import, Form, Includes, _Dir) ->
  617. case erl_syntax_lib:analyze_import_attribute(Form) of
  618. {Mod, _Funs} ->
  619. [module_to_erl(Mod)|Includes];
  620. Mod ->
  621. [module_to_erl(Mod)|Includes]
  622. end;
  623. process_attr(file, Form, Includes, _Dir) ->
  624. {File, _} = erl_syntax_lib:analyze_file_attribute(Form),
  625. [File|Includes];
  626. process_attr(include, Form, Includes, _Dir) ->
  627. [FileNode] = erl_syntax:attribute_arguments(Form),
  628. File = erl_syntax:string_value(FileNode),
  629. [File|Includes];
  630. process_attr(include_lib, Form, Includes, Dir) ->
  631. [FileNode] = erl_syntax:attribute_arguments(Form),
  632. RawFile = erl_syntax:string_value(FileNode),
  633. maybe_expand_include_lib_path(RawFile, Dir) ++ Includes;
  634. process_attr(behavior, Form, Includes, _Dir) ->
  635. process_attr(behaviour, Form, Includes, _Dir);
  636. process_attr(behaviour, Form, Includes, _Dir) ->
  637. [FileNode] = erl_syntax:attribute_arguments(Form),
  638. File = module_to_erl(erl_syntax:atom_value(FileNode)),
  639. [File|Includes];
  640. process_attr(compile, Form, Includes, _Dir) ->
  641. [Arg] = erl_syntax:attribute_arguments(Form),
  642. case erl_syntax:concrete(Arg) of
  643. {parse_transform, Mod} ->
  644. [module_to_erl(Mod)|Includes];
  645. {core_transform, Mod} ->
  646. [module_to_erl(Mod)|Includes];
  647. L when is_list(L) ->
  648. lists:foldl(
  649. fun({parse_transform, Mod}, Acc) ->
  650. [module_to_erl(Mod)|Acc];
  651. ({core_transform, Mod}, Acc) ->
  652. [module_to_erl(Mod)|Acc];
  653. (_, Acc) ->
  654. Acc
  655. end, Includes, L);
  656. _ ->
  657. Includes
  658. end;
  659. process_attr(_, _Form, Includes, _Dir) ->
  660. Includes.
  661. module_to_erl(Mod) ->
  662. atom_to_list(Mod) ++ ".erl".
  663. %% Given a path like "stdlib/include/erl_compile.hrl", return
  664. %% "OTP_INSTALL_DIR/lib/erlang/lib/stdlib-x.y.z/include/erl_compile.hrl".
  665. %% Usually a simple [Lib, SubDir, File1] = filename:split(File) should
  666. %% work, but to not crash when an unusual include_lib path is used,
  667. %% utilize more elaborate logic.
  668. maybe_expand_include_lib_path(File, Dir) ->
  669. File1 = filename:basename(File),
  670. case filename:split(filename:dirname(File)) of
  671. [_] ->
  672. warn_and_find_path(File, Dir);
  673. [Lib | SubDir] ->
  674. case code:lib_dir(list_to_atom(Lib), list_to_atom(filename:join(SubDir))) of
  675. {error, bad_name} ->
  676. warn_and_find_path(File, Dir);
  677. AppDir ->
  678. [filename:join(AppDir, File1)]
  679. end
  680. end.
  681. %% The use of -include_lib was probably incorrect by the user but lets try to make it work.
  682. %% We search in the outdir and outdir/../include to see if the header exists.
  683. warn_and_find_path(File, Dir) ->
  684. SrcHeader = filename:join(Dir, File),
  685. case filelib:is_regular(SrcHeader) of
  686. true ->
  687. [SrcHeader];
  688. false ->
  689. IncludeDir = filename:join(rebar_utils:droplast(filename:split(Dir))++["include"]),
  690. IncludeHeader = filename:join(IncludeDir, File),
  691. case filelib:is_regular(IncludeHeader) of
  692. true ->
  693. [filename:join(IncludeDir, File)];
  694. false ->
  695. []
  696. end
  697. end.
  698. %%
  699. %% Ensure all files in a list are present and abort if one is missing
  700. %%
  701. -spec check_files([file:filename()]) -> [file:filename()].
  702. check_files(FileList) ->
  703. [check_file(F) || F <- FileList].
  704. check_file(File) ->
  705. case filelib:is_regular(File) of
  706. false -> ?ABORT("File ~p is missing, aborting\n", [File]);
  707. true -> File
  708. end.
  709. outdir(RebarOpts) ->
  710. ErlOpts = rebar_opts:erl_opts(RebarOpts),
  711. proplists:get_value(outdir, ErlOpts, ?DEFAULT_OUTDIR).
  712. include_abs_dirs(ErlOpts, BaseDir) ->
  713. ErlOptIncludes = proplists:get_all_values(i, ErlOpts),
  714. InclDirs = lists:map(fun(Incl) -> filename:absname(Incl) end, ErlOptIncludes),
  715. [filename:join([BaseDir, "include"])|InclDirs].
  716. dir_recursive(Opts, Dir, CompileOpts) when is_list(CompileOpts) ->
  717. case proplists:get_value(recursive,CompileOpts) of
  718. undefined -> rebar_dir:recursive(Opts, Dir);
  719. Recursive -> Recursive
  720. end.
  721. valid_erl_first_conf(FileList) ->
  722. Strs = filter_file_list(FileList),
  723. case rebar_utils:is_list_of_strings(Strs) of
  724. true -> true;
  725. false -> ?ABORT("An invalid file list (~p) was provided as part of your erl_first_files directive",
  726. [FileList])
  727. end.
  728. filter_file_list(FileList) ->
  729. Atoms = lists:filter( fun(X) -> is_atom(X) end, FileList),
  730. case Atoms of
  731. [] ->
  732. FileList;
  733. _ ->
  734. atoms_in_erl_first_files_warning(Atoms),
  735. lists:filter( fun(X) -> not(is_atom(X)) end, FileList)
  736. end.
  737. atoms_in_erl_first_files_warning(Atoms) ->
  738. W = "You have provided atoms as file entries in erl_first_files; "
  739. "erl_first_files only expects lists of filenames as strings. "
  740. "The following modules (~p) may not work as expected and it is advised "
  741. "that you change these entires to string format "
  742. "(e.g., \"src/module.erl\") ",
  743. ?WARN(W, [Atoms]).
  744. warn_deprecated() ->
  745. case get({deprecate_warn, ?MODULE}) of
  746. undefined ->
  747. ?WARN("Calling deprecated ~p compiler module. This module has been "
  748. "replaced by rebar_compiler and rebar_compiler_erl, but will "
  749. "remain available.", [?MODULE]),
  750. put({deprecate_warn, ?MODULE}, true),
  751. ok;
  752. _ ->
  753. ok
  754. end.