Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

847 wiersze
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) -> lists:any(
  185. fun(Edge) ->
  186. {_E, _V1, _V2, Kind} = digraph:edge(G, Edge),
  187. Kind =/= artifact
  188. end,
  189. digraph:in_edges(G, Source))
  190. end,
  191. [File || File <- NeededErlFiles, not lists:member(File, ErlFirstFiles)]),
  192. SubGraph = digraph_utils:subgraph(G, DepErls),
  193. DepErlsOrdered = digraph_utils:topsort(SubGraph),
  194. FirstErls = ErlFirstFiles ++ lists:reverse(DepErlsOrdered),
  195. try
  196. rebar_base_compiler:run(
  197. RebarOpts, FirstErls, OtherErls,
  198. fun(S, C) ->
  199. ErlOpts1 = case lists:member(S, ErlFirstFiles) of
  200. true -> ErlOptsFirst;
  201. false -> ErlOpts
  202. end,
  203. internal_erl_compile(C, BaseDir, S, OutDir, ErlOpts1, RebarOpts)
  204. end)
  205. after
  206. true = digraph:delete(SubGraph),
  207. true = digraph:delete(G)
  208. end,
  209. ok.
  210. %% @doc remove compiled artifacts from an AppDir.
  211. -spec clean(rebar_app_info:t()) -> 'ok'.
  212. clean(AppInfo) ->
  213. AppDir = rebar_app_info:out_dir(AppInfo),
  214. MibFiles = rebar_utils:find_files(filename:join([AppDir, "mibs"]), ?RE_PREFIX".*\\.mib\$"),
  215. MIBs = [filename:rootname(filename:basename(MIB)) || MIB <- MibFiles],
  216. rebar_file_utils:delete_each(
  217. [filename:join([AppDir, "include",MIB++".hrl"]) || MIB <- MIBs]),
  218. ok = rebar_file_utils:rm_rf(filename:join([AppDir, "priv/mibs/*.bin"])),
  219. YrlFiles = rebar_utils:find_files(filename:join([AppDir, "src"]), ?RE_PREFIX".*\\.[x|y]rl\$"),
  220. rebar_file_utils:delete_each(
  221. [rebar_utils:to_list(re:replace(F, "\\.[x|y]rl$", ".erl", [unicode]))
  222. || F <- YrlFiles]),
  223. BinDirs = ["ebin"|rebar_dir:extra_src_dirs(rebar_app_info:opts(AppInfo))],
  224. ok = clean_dirs(AppDir, BinDirs),
  225. %% Delete the build graph, if any
  226. rebar_file_utils:rm_rf(erlcinfo_file(AppDir)).
  227. clean_dirs(_AppDir, []) -> ok;
  228. clean_dirs(AppDir, [Dir|Rest]) ->
  229. ok = rebar_file_utils:rm_rf(filename:join([AppDir, Dir, "*.beam"])),
  230. %% Erlang compilation is recursive, so it's possible that we have a nested
  231. %% directory structure in ebin with .beam files within. As such, we want
  232. %% to scan whatever is left in the app's out_dir directory for sub-dirs which
  233. %% satisfy our criteria.
  234. BeamFiles = rebar_utils:find_files(filename:join([AppDir, Dir]), ?RE_PREFIX".*\\.beam\$"),
  235. rebar_file_utils:delete_each(BeamFiles),
  236. lists:foreach(fun(D) -> delete_dir(D, dirs(D)) end, dirs(filename:join([AppDir, Dir]))),
  237. clean_dirs(AppDir, Rest).
  238. %% ===================================================================
  239. %% Internal functions
  240. %% ===================================================================
  241. gather_src(Opts, BaseDir, Dirs, CompileOpts) ->
  242. gather_src(Opts, filename:split(BaseDir), Dirs, [], CompileOpts).
  243. gather_src(_Opts, _BaseDirParts, [], Srcs, _CompileOpts) -> Srcs;
  244. gather_src(Opts, BaseDirParts, [Dir|Rest], Srcs, CompileOpts) ->
  245. DirParts = filename:split(Dir),
  246. RelDir = case lists:prefix(BaseDirParts,DirParts) of
  247. true ->
  248. case lists:nthtail(length(BaseDirParts),DirParts) of
  249. [] -> ".";
  250. RestParts -> filename:join(RestParts)
  251. end;
  252. false -> Dir
  253. end,
  254. DirRecursive = dir_recursive(Opts, RelDir, CompileOpts),
  255. gather_src(Opts, BaseDirParts, Rest, Srcs ++ rebar_utils:find_files(Dir, ?RE_PREFIX".*\\.erl\$", DirRecursive), CompileOpts).
  256. %% Get files which need to be compiled first, i.e. those specified in erl_first_files
  257. %% and parse_transform options. Also produce specific erl_opts for these first
  258. %% files, so that yet to be compiled parse transformations are excluded from it.
  259. erl_first_files(Opts, ErlOpts, Dir, NeededErlFiles) ->
  260. ErlFirstFilesConf = rebar_opts:get(Opts, erl_first_files, []),
  261. valid_erl_first_conf(ErlFirstFilesConf),
  262. NeededSrcDirs = lists:usort(lists:map(fun filename:dirname/1, NeededErlFiles)),
  263. %% NOTE: order of files here is important!
  264. ErlFirstFiles =
  265. [filename:join(Dir, File) || File <- ErlFirstFilesConf,
  266. lists:member(filename:join(Dir, File), NeededErlFiles)],
  267. {ParseTransforms, ParseTransformsErls} =
  268. lists:unzip(lists:flatmap(
  269. fun(PT) ->
  270. PTerls = [filename:join(D, module_to_erl(PT)) || D <- NeededSrcDirs],
  271. [{PT, PTerl} || PTerl <- PTerls, lists:member(PTerl, NeededErlFiles)]
  272. end, proplists:get_all_values(parse_transform, ErlOpts))),
  273. ErlOptsFirst = lists:filter(fun({parse_transform, PT}) ->
  274. not lists:member(PT, ParseTransforms);
  275. (_) ->
  276. true
  277. end, ErlOpts),
  278. {ErlFirstFiles ++ ParseTransformsErls, ErlOptsFirst}.
  279. split_source_files(SourceFiles, ErlOpts) ->
  280. ParseTransforms = proplists:get_all_values(parse_transform, ErlOpts),
  281. lists:partition(fun(Source) ->
  282. lists:member(filename_to_atom(Source), ParseTransforms)
  283. end, SourceFiles).
  284. filename_to_atom(F) -> list_to_atom(filename:rootname(filename:basename(F))).
  285. %% Get subset of SourceFiles which need to be recompiled, respecting
  286. %% dependencies induced by given graph G.
  287. needed_files(G, ErlOpts, RebarOpts, Dir, OutDir, SourceFiles) ->
  288. lists:filter(fun(Source) ->
  289. TargetBase = target_base(OutDir, Source),
  290. Target = TargetBase ++ ".beam",
  291. PrivIncludes = [{i, filename:join(Dir, Src)}
  292. || Src <- rebar_dir:all_src_dirs(RebarOpts, ["src"], [])],
  293. AllOpts = [{outdir, filename:dirname(Target)}
  294. ,{i, filename:join(Dir, "include")}
  295. ,{i, Dir}] ++ PrivIncludes ++ ErlOpts,
  296. %% necessary for erlang:function_exported/3 to work as expected
  297. %% called here for clarity as it's required by both opts_changed/2
  298. %% and erl_compiler_opts_set/0
  299. _ = code:ensure_loaded(compile),
  300. digraph:vertex(G, Source) > {Source, filelib:last_modified(Target)}
  301. orelse opts_changed(AllOpts, TargetBase)
  302. orelse erl_compiler_opts_set()
  303. end, SourceFiles).
  304. maybe_rm_beam_and_edge(G, OutDir, Source) ->
  305. %% This is NOT a double check it is the only check that the source file is actually gone
  306. case filelib:is_regular(Source) of
  307. true ->
  308. %% Actually exists, don't delete
  309. false;
  310. false ->
  311. Target = target_base(OutDir, Source) ++ ".beam",
  312. ?DEBUG("Source ~ts is gone, deleting previous beam file if it exists ~ts", [Source, Target]),
  313. file:delete(Target),
  314. digraph:del_vertex(G, Source),
  315. true
  316. end.
  317. opts_changed(NewOpts, Target) ->
  318. TotalOpts = case erlang:function_exported(compile, env_compiler_options, 0) of
  319. true -> NewOpts ++ compile:env_compiler_options();
  320. false -> NewOpts
  321. end,
  322. case compile_info(Target) of
  323. {ok, Opts} -> lists:any(fun effects_code_generation/1, lists:usort(TotalOpts) -- lists:usort(Opts));
  324. _ -> true
  325. end.
  326. effects_code_generation(Option) ->
  327. case Option of
  328. beam -> false;
  329. report_warnings -> false;
  330. report_errors -> false;
  331. return_errors-> false;
  332. return_warnings-> false;
  333. report -> false;
  334. warnings_as_errors -> false;
  335. binary -> false;
  336. verbose -> false;
  337. {cwd,_} -> false;
  338. {outdir, _} -> false;
  339. _ -> true
  340. end.
  341. compile_info(Target) ->
  342. case beam_lib:chunks(Target, [compile_info]) of
  343. {ok, {_mod, Chunks}} ->
  344. CompileInfo = proplists:get_value(compile_info, Chunks, []),
  345. {ok, proplists:get_value(options, CompileInfo, [])};
  346. {error, beam_lib, Reason} ->
  347. ?WARN("Couldn't read debug info from ~p for reason: ~p", [Target, Reason]),
  348. {error, Reason}
  349. end.
  350. erl_compiler_opts_set() ->
  351. EnvSet = case os:getenv("ERL_COMPILER_OPTIONS") of
  352. false -> false;
  353. _ -> true
  354. end,
  355. %% return false if changed env opts would have been caught in opts_changed/2
  356. EnvSet andalso not erlang:function_exported(compile, env_compiler_options, 0).
  357. erlcinfo_file(Dir) ->
  358. filename:join(rebar_dir:local_cache_dir(Dir), ?ERLCINFO_FILE).
  359. %% Get dependency graph of given Erls files and their dependencies (header files,
  360. %% parse transforms, behaviours etc.) located in their directories or given
  361. %% InclDirs. Note that last modification times stored in vertices already respect
  362. %% dependencies induced by given graph G.
  363. init_erlcinfo(InclDirs, Erls, Dir, OutDir) ->
  364. G = digraph:new([acyclic]),
  365. try restore_erlcinfo(G, InclDirs, Dir)
  366. catch
  367. _:_ ->
  368. ?WARN("Failed to restore ~ts file. Discarding it.~n", [erlcinfo_file(Dir)]),
  369. file:delete(erlcinfo_file(Dir))
  370. end,
  371. Dirs = source_and_include_dirs(InclDirs, Erls),
  372. %% A source file may have been renamed or deleted. Remove it from the graph
  373. %% and remove any beam file for that source if it exists.
  374. Modified = maybe_rm_beams_and_edges(G, OutDir, Erls),
  375. Modified1 = lists:foldl(update_erlcinfo_fun(G, Dirs), Modified, Erls),
  376. if Modified1 -> store_erlcinfo(G, InclDirs, Dir); not Modified1 -> ok end,
  377. G.
  378. maybe_rm_beams_and_edges(G, Dir, Files) ->
  379. Vertices = digraph:vertices(G),
  380. case lists:filter(fun(File) ->
  381. case filename:extension(File) =:= ".erl" of
  382. true ->
  383. maybe_rm_beam_and_edge(G, Dir, File);
  384. false ->
  385. false
  386. end
  387. end, lists:sort(Vertices) -- lists:sort(Files)) of
  388. [] ->
  389. false;
  390. _ ->
  391. true
  392. end.
  393. source_and_include_dirs(InclDirs, Erls) ->
  394. SourceDirs = lists:map(fun filename:dirname/1, Erls),
  395. lists:usort(InclDirs ++ SourceDirs).
  396. update_erlcinfo(G, Dirs, Source) ->
  397. case digraph:vertex(G, Source) of
  398. {_, LastUpdated} ->
  399. case filelib:last_modified(Source) of
  400. 0 ->
  401. %% The file doesn't exist anymore,
  402. %% erase it from the graph.
  403. %% All the edges will be erased automatically.
  404. digraph:del_vertex(G, Source),
  405. modified;
  406. LastModified when LastUpdated < LastModified ->
  407. modify_erlcinfo(G, Source, LastModified, filename:dirname(Source), Dirs);
  408. _ ->
  409. Modified = lists:foldl(
  410. update_erlcinfo_fun(G, Dirs),
  411. false, digraph:out_neighbours(G, Source)),
  412. MaxModified = update_max_modified_deps(G, Source),
  413. case Modified orelse MaxModified > LastUpdated of
  414. true -> modified;
  415. false -> unmodified
  416. end
  417. end;
  418. false ->
  419. modify_erlcinfo(G, Source, filelib:last_modified(Source), filename:dirname(Source), Dirs)
  420. end.
  421. update_erlcinfo_fun(G, Dirs) ->
  422. fun(Erl, Modified) ->
  423. case update_erlcinfo(G, Dirs, Erl) of
  424. modified -> true;
  425. unmodified -> Modified
  426. end
  427. end.
  428. update_max_modified_deps(G, Source) ->
  429. MaxModified = lists:max(lists:map(
  430. fun(File) -> {_, MaxModified} = digraph:vertex(G, File), MaxModified end,
  431. [Source|digraph:out_neighbours(G, Source)])),
  432. digraph:add_vertex(G, Source, MaxModified),
  433. MaxModified.
  434. modify_erlcinfo(G, Source, LastModified, Dir, Dirs) ->
  435. {ok, Fd} = file:open(Source, [read]),
  436. Incls = parse_attrs(Fd, [], Dir),
  437. AbsIncls = expand_file_names(Incls, Dirs),
  438. ok = file:close(Fd),
  439. digraph:add_vertex(G, Source, LastModified),
  440. digraph:del_edges(G, digraph:out_edges(G, Source)),
  441. lists:foreach(
  442. fun(Incl) ->
  443. update_erlcinfo(G, Dirs, Incl),
  444. digraph:add_edge(G, Source, Incl)
  445. end, AbsIncls),
  446. modified.
  447. restore_erlcinfo(G, InclDirs, Dir) ->
  448. case file:read_file(erlcinfo_file(Dir)) of
  449. {ok, Data} ->
  450. % Since externally passed InclDirs can influence erlcinfo graph (see
  451. % modify_erlcinfo), we have to check here that they didn't change.
  452. #erlcinfo{vsn=?ERLCINFO_VSN, info={Vs, Es, InclDirs}} =
  453. binary_to_term(Data),
  454. lists:foreach(
  455. fun({V, LastUpdated}) ->
  456. digraph:add_vertex(G, V, LastUpdated)
  457. end, Vs),
  458. lists:foreach(
  459. fun({_, V1, V2, _}) ->
  460. digraph:add_edge(G, V1, V2)
  461. end, Es);
  462. {error, _} ->
  463. ok
  464. end.
  465. store_erlcinfo(G, InclDirs, Dir) ->
  466. Vs = lists:map(fun(V) -> digraph:vertex(G, V) end, digraph:vertices(G)),
  467. Es = lists:map(fun(E) -> digraph:edge(G, E) end, digraph:edges(G)),
  468. File = erlcinfo_file(Dir),
  469. ok = filelib:ensure_dir(File),
  470. Data = term_to_binary(#erlcinfo{info={Vs, Es, InclDirs}}, [{compressed, 2}]),
  471. file:write_file(File, Data).
  472. %% NOTE: If, for example, one of the entries in Files, refers to
  473. %% gen_server.erl, that entry will be dropped. It is dropped because
  474. %% such an entry usually refers to the beam file, and we don't pass a
  475. %% list of OTP src dirs for finding gen_server.erl's full path. Also,
  476. %% if gen_server.erl was modified, it's not rebar's task to compile a
  477. %% new version of the beam file. Therefore, it's reasonable to drop
  478. %% such entries. Also see process_attr(behaviour, Form, Includes).
  479. -spec expand_file_names([file:filename()],
  480. [file:filename()]) -> [file:filename()].
  481. expand_file_names(Files, Dirs) ->
  482. %% We check if Files exist by itself or within the directories
  483. %% listed in Dirs.
  484. %% Return the list of files matched.
  485. lists:flatmap(
  486. fun(Incl) ->
  487. case filelib:is_regular(Incl) of
  488. true ->
  489. [Incl];
  490. false ->
  491. lists:flatmap(
  492. fun(Dir) ->
  493. FullPath = filename:join(Dir, Incl),
  494. case filelib:is_regular(FullPath) of
  495. true ->
  496. [FullPath];
  497. false ->
  498. []
  499. end
  500. end, Dirs)
  501. end
  502. end, Files).
  503. -spec internal_erl_compile(rebar_dict(), file:filename(), file:filename(),
  504. file:filename(), list(), rebar_dict()) ->
  505. ok | {ok, any()} | {error, any(), any()}.
  506. internal_erl_compile(Opts, Dir, Module, OutDir, ErlOpts, RebarOpts) ->
  507. Target = target_base(OutDir, Module) ++ ".beam",
  508. ok = filelib:ensure_dir(Target),
  509. PrivIncludes = [{i, filename:join(Dir, Src)}
  510. || Src <- rebar_dir:all_src_dirs(RebarOpts, ["src"], [])],
  511. AllOpts = [{outdir, filename:dirname(Target)}] ++ ErlOpts ++ PrivIncludes ++
  512. [{i, filename:join(Dir, "include")}, {i, Dir}, return],
  513. case compile:file(Module, AllOpts) of
  514. {ok, _Mod} ->
  515. ok;
  516. {ok, _Mod, Ws} ->
  517. FormattedWs = format_error_sources(Ws, Opts),
  518. rebar_base_compiler:ok_tuple(Module, FormattedWs);
  519. {error, Es, Ws} ->
  520. error_tuple(Module, Es, Ws, AllOpts, Opts)
  521. end.
  522. error_tuple(Module, Es, Ws, AllOpts, Opts) ->
  523. FormattedEs = format_error_sources(Es, Opts),
  524. FormattedWs = format_error_sources(Ws, Opts),
  525. rebar_base_compiler:error_tuple(Module, FormattedEs, FormattedWs, AllOpts).
  526. format_error_sources(Es, Opts) ->
  527. [{rebar_base_compiler:format_error_source(Src, Opts), Desc}
  528. || {Src, Desc} <- Es].
  529. target_base(OutDir, Source) ->
  530. filename:join(OutDir, filename:basename(Source, ".erl")).
  531. -spec compile_mib(rebar_app_info:t()) ->
  532. fun((file:filename(), file:filename(), rebar_dict()) -> 'ok').
  533. compile_mib(AppInfo) ->
  534. fun(Source, Target, Opts) ->
  535. Dir = filename:dirname(Target),
  536. Mib = filename:rootname(Target),
  537. HrlFilename = Mib ++ ".hrl",
  538. AppInclude = filename:join([rebar_app_info:dir(AppInfo), "include"]),
  539. ok = filelib:ensure_dir(Target),
  540. ok = filelib:ensure_dir(filename:join([AppInclude, "dummy.hrl"])),
  541. AllOpts = [{outdir, Dir}
  542. ,{i, [Dir]}] ++
  543. rebar_opts:get(Opts, mib_opts, []),
  544. case snmpc:compile(Source, AllOpts) of
  545. {ok, _} ->
  546. MibToHrlOpts =
  547. case proplists:get_value(verbosity, AllOpts, undefined) of
  548. undefined ->
  549. #options{specific = [],
  550. cwd = rebar_dir:get_cwd()};
  551. Verbosity ->
  552. #options{specific = [{verbosity, Verbosity}],
  553. cwd = rebar_dir:get_cwd()}
  554. end,
  555. ok = snmpc:mib_to_hrl(Mib, Mib, MibToHrlOpts),
  556. rebar_file_utils:mv(HrlFilename, AppInclude),
  557. ok;
  558. {error, compilation_failed} ->
  559. ?ABORT
  560. end
  561. end.
  562. -spec compile_xrl(file:filename(), file:filename(),
  563. rebar_dict()) -> 'ok'.
  564. compile_xrl(Source, Target, Opts) ->
  565. AllOpts = [{scannerfile, Target} | rebar_opts:get(Opts, xrl_opts, [])],
  566. compile_xrl_yrl(Opts, Source, Target, AllOpts, leex).
  567. -spec compile_yrl(file:filename(), file:filename(),
  568. rebar_dict()) -> 'ok'.
  569. compile_yrl(Source, Target, Opts) ->
  570. AllOpts = [{parserfile, Target} | rebar_opts:get(Opts, yrl_opts, [])],
  571. compile_xrl_yrl(Opts, Source, Target, AllOpts, yecc).
  572. -spec compile_xrl_yrl(rebar_dict(), file:filename(),
  573. file:filename(), list(), module()) -> 'ok'.
  574. compile_xrl_yrl(_Opts, Source, Target, AllOpts, Mod) ->
  575. %% FIX ME: should be the outdir or something
  576. Dir = filename:dirname(filename:dirname(Target)),
  577. AllOpts1 = [{includefile, filename:join(Dir, I)} || {includefile, I} <- AllOpts,
  578. filename:pathtype(I) =:= relative],
  579. case needs_compile(Source, Target) of
  580. true ->
  581. case Mod:file(Source, AllOpts1 ++ [{return, true}]) of
  582. {ok, _} ->
  583. ok;
  584. {ok, _Mod, Ws} ->
  585. rebar_base_compiler:ok_tuple(Source, Ws);
  586. {error, Es, Ws} ->
  587. rebar_base_compiler:error_tuple(Source,
  588. Es, Ws, AllOpts1)
  589. end;
  590. false ->
  591. skipped
  592. end.
  593. needs_compile(Source, Target) ->
  594. filelib:last_modified(Source) > filelib:last_modified(Target).
  595. -spec dirs(file:filename()) -> [file:filename()].
  596. dirs(Dir) ->
  597. [F || F <- filelib:wildcard(filename:join([Dir, "*"])), filelib:is_dir(F)].
  598. -spec delete_dir(file:filename(), [string()]) -> 'ok' | {'error', atom()}.
  599. delete_dir(Dir, []) ->
  600. file:del_dir(Dir);
  601. delete_dir(Dir, Subdirs) ->
  602. lists:foreach(fun(D) -> delete_dir(D, dirs(D)) end, Subdirs),
  603. file:del_dir(Dir).
  604. parse_attrs(Fd, Includes, Dir) ->
  605. case io:parse_erl_form(Fd, "") of
  606. {ok, Form, _Line} ->
  607. case erl_syntax:type(Form) of
  608. attribute ->
  609. NewIncludes = process_attr(Form, Includes, Dir),
  610. parse_attrs(Fd, NewIncludes, Dir);
  611. _ ->
  612. parse_attrs(Fd, Includes, Dir)
  613. end;
  614. {eof, _} ->
  615. Includes;
  616. _Err ->
  617. parse_attrs(Fd, Includes, Dir)
  618. end.
  619. process_attr(Form, Includes, Dir) ->
  620. AttrName = erl_syntax:atom_value(erl_syntax:attribute_name(Form)),
  621. process_attr(AttrName, Form, Includes, Dir).
  622. process_attr(import, Form, Includes, _Dir) ->
  623. case erl_syntax_lib:analyze_import_attribute(Form) of
  624. {Mod, _Funs} ->
  625. [module_to_erl(Mod)|Includes];
  626. Mod ->
  627. [module_to_erl(Mod)|Includes]
  628. end;
  629. process_attr(file, Form, Includes, _Dir) ->
  630. {File, _} = erl_syntax_lib:analyze_file_attribute(Form),
  631. [File|Includes];
  632. process_attr(include, Form, Includes, _Dir) ->
  633. [FileNode] = erl_syntax:attribute_arguments(Form),
  634. File = erl_syntax:string_value(FileNode),
  635. [File|Includes];
  636. process_attr(include_lib, Form, Includes, Dir) ->
  637. [FileNode] = erl_syntax:attribute_arguments(Form),
  638. RawFile = erl_syntax:string_value(FileNode),
  639. maybe_expand_include_lib_path(RawFile, Dir) ++ Includes;
  640. process_attr(behavior, Form, Includes, _Dir) ->
  641. process_attr(behaviour, Form, Includes, _Dir);
  642. process_attr(behaviour, Form, Includes, _Dir) ->
  643. [FileNode] = erl_syntax:attribute_arguments(Form),
  644. File = module_to_erl(erl_syntax:atom_value(FileNode)),
  645. [File|Includes];
  646. process_attr(compile, Form, Includes, _Dir) ->
  647. [Arg] = erl_syntax:attribute_arguments(Form),
  648. case erl_syntax:concrete(Arg) of
  649. {parse_transform, Mod} ->
  650. [module_to_erl(Mod)|Includes];
  651. {core_transform, Mod} ->
  652. [module_to_erl(Mod)|Includes];
  653. L when is_list(L) ->
  654. lists:foldl(
  655. fun({parse_transform, Mod}, Acc) ->
  656. [module_to_erl(Mod)|Acc];
  657. ({core_transform, Mod}, Acc) ->
  658. [module_to_erl(Mod)|Acc];
  659. (_, Acc) ->
  660. Acc
  661. end, Includes, L);
  662. _ ->
  663. Includes
  664. end;
  665. process_attr(_, _Form, Includes, _Dir) ->
  666. Includes.
  667. module_to_erl(Mod) ->
  668. atom_to_list(Mod) ++ ".erl".
  669. %% Given a path like "stdlib/include/erl_compile.hrl", return
  670. %% "OTP_INSTALL_DIR/lib/erlang/lib/stdlib-x.y.z/include/erl_compile.hrl".
  671. %% Usually a simple [Lib, SubDir, File1] = filename:split(File) should
  672. %% work, but to not crash when an unusual include_lib path is used,
  673. %% utilize more elaborate logic.
  674. maybe_expand_include_lib_path(File, Dir) ->
  675. File1 = filename:basename(File),
  676. case filename:split(filename:dirname(File)) of
  677. [_] ->
  678. warn_and_find_path(File, Dir);
  679. [Lib | SubDir] ->
  680. case code:lib_dir(list_to_atom(Lib), list_to_atom(filename:join(SubDir))) of
  681. {error, bad_name} ->
  682. warn_and_find_path(File, Dir);
  683. AppDir ->
  684. [filename:join(AppDir, File1)]
  685. end
  686. end.
  687. %% The use of -include_lib was probably incorrect by the user but lets try to make it work.
  688. %% We search in the outdir and outdir/../include to see if the header exists.
  689. warn_and_find_path(File, Dir) ->
  690. SrcHeader = filename:join(Dir, File),
  691. case filelib:is_regular(SrcHeader) of
  692. true ->
  693. [SrcHeader];
  694. false ->
  695. IncludeDir = filename:join(rebar_utils:droplast(filename:split(Dir))++["include"]),
  696. IncludeHeader = filename:join(IncludeDir, File),
  697. case filelib:is_regular(IncludeHeader) of
  698. true ->
  699. [filename:join(IncludeDir, File)];
  700. false ->
  701. []
  702. end
  703. end.
  704. %%
  705. %% Ensure all files in a list are present and abort if one is missing
  706. %%
  707. -spec check_files([file:filename()]) -> [file:filename()].
  708. check_files(FileList) ->
  709. [check_file(F) || F <- FileList].
  710. check_file(File) ->
  711. case filelib:is_regular(File) of
  712. false -> ?ABORT("File ~p is missing, aborting\n", [File]);
  713. true -> File
  714. end.
  715. outdir(RebarOpts) ->
  716. ErlOpts = rebar_opts:erl_opts(RebarOpts),
  717. proplists:get_value(outdir, ErlOpts, ?DEFAULT_OUTDIR).
  718. include_abs_dirs(ErlOpts, BaseDir) ->
  719. ErlOptIncludes = proplists:get_all_values(i, ErlOpts),
  720. InclDirs = lists:map(fun(Incl) -> filename:absname(Incl) end, ErlOptIncludes),
  721. [filename:join([BaseDir, "include"])|InclDirs].
  722. dir_recursive(Opts, Dir, CompileOpts) when is_list(CompileOpts) ->
  723. case proplists:get_value(recursive,CompileOpts) of
  724. undefined -> rebar_dir:recursive(Opts, Dir);
  725. Recursive -> Recursive
  726. end.
  727. valid_erl_first_conf(FileList) ->
  728. Strs = filter_file_list(FileList),
  729. case rebar_utils:is_list_of_strings(Strs) of
  730. true -> true;
  731. false -> ?ABORT("An invalid file list (~p) was provided as part of your erl_first_files directive",
  732. [FileList])
  733. end.
  734. filter_file_list(FileList) ->
  735. Atoms = lists:filter( fun(X) -> is_atom(X) end, FileList),
  736. case Atoms of
  737. [] ->
  738. FileList;
  739. _ ->
  740. atoms_in_erl_first_files_warning(Atoms),
  741. lists:filter( fun(X) -> not(is_atom(X)) end, FileList)
  742. end.
  743. atoms_in_erl_first_files_warning(Atoms) ->
  744. W = "You have provided atoms as file entries in erl_first_files; "
  745. "erl_first_files only expects lists of filenames as strings. "
  746. "The following modules (~p) may not work as expected and it is advised "
  747. "that you change these entires to string format "
  748. "(e.g., \"src/module.erl\") ",
  749. ?WARN(W, [Atoms]).
  750. warn_deprecated() ->
  751. case get({deprecate_warn, ?MODULE}) of
  752. undefined ->
  753. ?WARN("Calling deprecated ~p compiler module. This module has been "
  754. "replaced by rebar_compiler and rebar_compiler_erl, but will "
  755. "remain available.", [?MODULE]),
  756. put({deprecate_warn, ?MODULE}, true),
  757. ok;
  758. _ ->
  759. ok
  760. end.