25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

452 lines
18 KiB

  1. -module(rebar_compiler_erl).
  2. -behaviour(rebar_compiler).
  3. -export([context/1,
  4. needed_files/4,
  5. dependencies/3, dependencies/4,
  6. compile/4, compile_and_track/4,
  7. clean/2,
  8. format_error/1]).
  9. -include("rebar.hrl").
  10. -include_lib("providers/include/providers.hrl").
  11. context(AppInfo) ->
  12. EbinDir = rebar_app_info:ebin_dir(AppInfo),
  13. Mappings = [{".beam", EbinDir}],
  14. OutDir = rebar_app_info:dir(AppInfo),
  15. SrcDirs = rebar_dir:src_dirs(rebar_app_info:opts(AppInfo), ["src"]),
  16. ExistingSrcDirs = lists:filter(fun(D) ->
  17. ec_file:is_dir(filename:join(OutDir, D))
  18. end, SrcDirs),
  19. RebarOpts = rebar_app_info:opts(AppInfo),
  20. ErlOpts = rebar_opts:erl_opts(RebarOpts),
  21. ErlOptIncludes = proplists:get_all_values(i, ErlOpts),
  22. InclDirs = lists:map(fun(Incl) -> filename:absname(Incl) end, ErlOptIncludes),
  23. AbsIncl = [filename:join([OutDir, "include"]) | InclDirs],
  24. PTrans = proplists:get_all_values(parse_transform, ErlOpts),
  25. Macros = [case Tup of
  26. {d,Name} -> Name;
  27. {d,Name,Val} -> {Name,Val}
  28. end || Tup <- ErlOpts,
  29. is_tuple(Tup) andalso element(1,Tup) == d],
  30. #{src_dirs => ExistingSrcDirs,
  31. include_dirs => AbsIncl,
  32. src_ext => ".erl",
  33. out_mappings => Mappings,
  34. dependencies_opts => [{includes, AbsIncl}, {macros, Macros},
  35. {parse_transforms, PTrans}]}.
  36. needed_files(Graph, FoundFiles, _, AppInfo) ->
  37. OutDir = rebar_app_info:out_dir(AppInfo),
  38. Dir = rebar_app_info:dir(AppInfo),
  39. EbinDir = rebar_app_info:ebin_dir(AppInfo),
  40. RebarOpts = rebar_app_info:opts(AppInfo),
  41. ErlOpts = rebar_opts:erl_opts(RebarOpts),
  42. ?DEBUG("erlopts ~p", [ErlOpts]),
  43. ?DEBUG("files to compile ~p", [FoundFiles]),
  44. %% Make sure that the ebin dir is on the path
  45. ok = rebar_file_utils:ensure_dir(EbinDir),
  46. true = code:add_patha(filename:absname(EbinDir)),
  47. {ParseTransforms, Rest} = split_source_files(FoundFiles, ErlOpts),
  48. NeededErlFiles = case needed_files(Graph, ErlOpts, RebarOpts, OutDir, EbinDir, ParseTransforms) of
  49. [] ->
  50. needed_files(Graph, ErlOpts, RebarOpts, OutDir, EbinDir, Rest);
  51. _ ->
  52. %% at least one parse transform in the opts needs updating, so recompile all
  53. FoundFiles
  54. end,
  55. {ErlFirstFiles, ErlOptsFirst} = erl_first_files(RebarOpts, ErlOpts, Dir, NeededErlFiles),
  56. SubGraph = digraph_utils:subgraph(Graph, NeededErlFiles),
  57. DepErlsOrdered = digraph_utils:topsort(SubGraph),
  58. %% Break out the files required by other modules from those
  59. %% that none other depend of; the former must be sequentially
  60. %% built, the rest is parallelizable.
  61. OtherErls = lists:partition(
  62. fun(Erl) -> lists:any(
  63. fun(Edge) ->
  64. {_E, _V1, _V2, Kind} = digraph:edge(Graph, Edge),
  65. Kind =:= artifact
  66. end, digraph:in_edges(Graph, Erl)) end,
  67. lists:reverse([Dep || Dep <- DepErlsOrdered,
  68. not lists:member(Dep, ErlFirstFiles)])
  69. ),
  70. PrivIncludes = [{i, filename:join(OutDir, Src)}
  71. || Src <- rebar_dir:all_src_dirs(RebarOpts, ["src"], [])],
  72. AdditionalOpts = PrivIncludes ++ [{i, filename:join(OutDir, "include")}, {i, OutDir}, return],
  73. true = digraph:delete(SubGraph),
  74. {{ErlFirstFiles, ErlOptsFirst ++ AdditionalOpts},
  75. {OtherErls, ErlOpts ++ AdditionalOpts}}.
  76. dependencies(Source, SourceDir, Dirs) ->
  77. case file:open(Source, [read]) of
  78. {ok, Fd} ->
  79. Incls = parse_attrs(Fd, [], SourceDir),
  80. AbsIncls = expand_file_names(Incls, Dirs),
  81. ok = file:close(Fd),
  82. AbsIncls;
  83. {error, Reason} ->
  84. throw(?PRV_ERROR({cannot_read_file, Source, file:format_error(Reason)}))
  85. end.
  86. dependencies(Source, _SourceDir, Dirs, DepOpts) ->
  87. OptPTrans = proplists:get_value(parse_transforms, DepOpts, []),
  88. try rebar_compiler_epp:deps(Source, DepOpts) of
  89. #{include := AbsIncls,
  90. missing_include_file := _MissIncl,
  91. missing_include_lib := _MissInclLib,
  92. parse_transform := PTrans,
  93. behaviour := Behaviours} ->
  94. %% TODO: check for core transforms?
  95. {_MissIncl, _MissInclLib} =/= {[],[]} andalso
  96. ?DEBUG("Missing: ~p", [{_MissIncl, _MissInclLib}]),
  97. expand_file_names([module_to_erl(Mod) || Mod <- OptPTrans ++ PTrans], Dirs) ++
  98. expand_file_names([module_to_erl(Mod) || Mod <- Behaviours], Dirs) ++
  99. AbsIncls
  100. catch
  101. error:{badmatch, {error, Reason}} ->
  102. case file:format_error(Reason) of
  103. "unknown POSIX error" ->
  104. throw(?PRV_ERROR({cannot_read_file, Source, Reason}));
  105. ReadableReason ->
  106. throw(?PRV_ERROR({cannot_read_file, Source, ReadableReason}))
  107. end;
  108. error:Reason ->
  109. throw(?PRV_ERROR({cannot_read_file, Source, Reason}))
  110. end.
  111. compile(Source, [{_, OutDir}], Config, ErlOpts) ->
  112. case compile:file(Source, [{outdir, OutDir} | ErlOpts]) of
  113. {ok, _Mod} ->
  114. ok;
  115. {ok, _Mod, []} ->
  116. ok;
  117. {ok, _Mod, Ws} ->
  118. FormattedWs = format_error_sources(Ws, Config),
  119. rebar_compiler:ok_tuple(Source, FormattedWs);
  120. {error, Es, Ws} ->
  121. error_tuple(Source, Es, Ws, Config, ErlOpts);
  122. error ->
  123. error
  124. end.
  125. compile_and_track(Source, [{Ext, OutDir}], Config, ErlOpts) ->
  126. BuildOpts = [{outdir, OutDir} | ErlOpts],
  127. Target = target_base(OutDir, Source) ++ Ext,
  128. AllOpts = case erlang:function_exported(compile, env_compiler_options, 0) of
  129. true -> BuildOpts ++ compile:env_compiler_options();
  130. false -> BuildOpts
  131. end,
  132. case compile:file(Source, BuildOpts) of
  133. {ok, _Mod} ->
  134. {ok, [{Source, Target, AllOpts}]};
  135. {ok, _Mod, []} ->
  136. {ok, [{Source, Target, AllOpts}]};
  137. {ok, _Mod, Ws} ->
  138. FormattedWs = format_error_sources(Ws, Config),
  139. {ok, Warns} = rebar_compiler:ok_tuple(Source, FormattedWs),
  140. {ok, [{Source, Target, AllOpts}], Warns};
  141. {error, Es, Ws} ->
  142. error_tuple(Source, Es, Ws, Config, ErlOpts);
  143. error ->
  144. error
  145. end.
  146. clean(Files, AppInfo) ->
  147. EbinDir = rebar_app_info:ebin_dir(AppInfo),
  148. [begin
  149. Source = filename:basename(File, ".erl"),
  150. Target = target_base(EbinDir, Source) ++ ".beam",
  151. file:delete(Target)
  152. end || File <- Files].
  153. %%
  154. error_tuple(Module, Es, Ws, AllOpts, Opts) ->
  155. FormattedEs = format_error_sources(Es, AllOpts),
  156. FormattedWs = format_error_sources(Ws, AllOpts),
  157. rebar_compiler:error_tuple(Module, FormattedEs, FormattedWs, Opts).
  158. format_error_sources(Es, Opts) ->
  159. [{rebar_compiler:format_error_source(Src, Opts), Desc}
  160. || {Src, Desc} <- Es].
  161. %% Get files which need to be compiled first, i.e. those specified in erl_first_files
  162. %% and parse_transform options. Also produce specific erl_opts for these first
  163. %% files, so that yet to be compiled parse transformations are excluded from it.
  164. erl_first_files(Opts, ErlOpts, Dir, NeededErlFiles) ->
  165. ErlFirstFilesConf = rebar_opts:get(Opts, erl_first_files, []),
  166. valid_erl_first_conf(ErlFirstFilesConf),
  167. NeededSrcDirs = lists:usort(lists:map(fun filename:dirname/1, NeededErlFiles)),
  168. %% NOTE: order of files here is important!
  169. ErlFirstFiles =
  170. [filename:join(Dir, File) || File <- ErlFirstFilesConf,
  171. lists:member(filename:join(Dir, File), NeededErlFiles)],
  172. {ParseTransforms, ParseTransformsErls} =
  173. lists:unzip(lists:flatmap(
  174. fun(PT) ->
  175. PTerls = [filename:join(D, module_to_erl(PT)) || D <- NeededSrcDirs],
  176. [{PT, PTerl} || PTerl <- PTerls, lists:member(PTerl, NeededErlFiles)]
  177. end, proplists:get_all_values(parse_transform, ErlOpts))),
  178. ErlOptsFirst = lists:filter(fun({parse_transform, PT}) ->
  179. not lists:member(PT, ParseTransforms);
  180. (_) ->
  181. true
  182. end, ErlOpts),
  183. {ErlFirstFiles ++ ParseTransformsErls, ErlOptsFirst}.
  184. split_source_files(SourceFiles, ErlOpts) ->
  185. ParseTransforms = proplists:get_all_values(parse_transform, ErlOpts),
  186. lists:partition(fun(Source) ->
  187. lists:member(filename_to_atom(Source), ParseTransforms)
  188. end, SourceFiles).
  189. filename_to_atom(F) -> list_to_atom(filename:rootname(filename:basename(F))).
  190. %% Get subset of SourceFiles which need to be recompiled, respecting
  191. %% dependencies induced by given graph G.
  192. needed_files(Graph, ErlOpts, RebarOpts, Dir, OutDir, SourceFiles) ->
  193. lists:filter(fun(Source) ->
  194. TargetBase = target_base(OutDir, Source),
  195. Target = TargetBase ++ ".beam",
  196. PrivIncludes = [{i, filename:join(Dir, Src)}
  197. || Src <- rebar_dir:all_src_dirs(RebarOpts, ["src"], [])],
  198. AllOpts = [{outdir, filename:dirname(Target)}
  199. ,{i, filename:join(Dir, "include")}
  200. ,{i, Dir}] ++ PrivIncludes ++ ErlOpts,
  201. digraph:vertex(Graph, Source) > {Source, filelib:last_modified(Target)}
  202. orelse opts_changed(Graph, AllOpts, Target, TargetBase)
  203. orelse erl_compiler_opts_set()
  204. end, SourceFiles).
  205. target_base(OutDir, Source) ->
  206. filename:join(OutDir, filename:basename(Source, ".erl")).
  207. opts_changed(Graph, NewOpts, Target, TargetBase) ->
  208. TotalOpts = case erlang:function_exported(compile, env_compiler_options, 0) of
  209. true -> NewOpts ++ compile:env_compiler_options();
  210. false -> NewOpts
  211. end,
  212. TargetOpts = case digraph:vertex(Graph, Target) of
  213. {_Target, {artifact, Opts}} -> % tracked dep is found
  214. Opts;
  215. false -> % not found; might be a non-tracked DAG
  216. case compile_info(TargetBase) of
  217. {ok, Opts} -> Opts;
  218. _ -> []
  219. end
  220. end,
  221. lists:any(fun effects_code_generation/1,
  222. lists:usort(TotalOpts) -- lists:usort(TargetOpts)).
  223. effects_code_generation(Option) ->
  224. case Option of
  225. beam -> false;
  226. report_warnings -> false;
  227. report_errors -> false;
  228. return_errors-> false;
  229. return_warnings-> false;
  230. report -> false;
  231. warnings_as_errors -> false;
  232. binary -> false;
  233. verbose -> false;
  234. {cwd,_} -> false;
  235. {outdir, _} -> false;
  236. _ -> true
  237. end.
  238. compile_info(Target) ->
  239. case beam_lib:chunks(Target, [compile_info]) of
  240. {ok, {_mod, Chunks}} ->
  241. CompileInfo = proplists:get_value(compile_info, Chunks, []),
  242. {ok, proplists:get_value(options, CompileInfo, [])};
  243. {error, beam_lib, Reason} ->
  244. ?WARN("Couldn't read debug info from ~p for reason: ~p", [Target, Reason]),
  245. {error, Reason}
  246. end.
  247. erl_compiler_opts_set() ->
  248. EnvSet = case os:getenv("ERL_COMPILER_OPTIONS") of
  249. false -> false;
  250. _ -> true
  251. end,
  252. %% return false if changed env opts would have been caught in opts_changed/2
  253. EnvSet andalso not erlang:function_exported(compile, env_compiler_options, 0).
  254. valid_erl_first_conf(FileList) ->
  255. Strs = filter_file_list(FileList),
  256. case rebar_utils:is_list_of_strings(Strs) of
  257. true -> true;
  258. false -> ?ABORT("An invalid file list (~p) was provided as part of your erl_first_files directive",
  259. [FileList])
  260. end.
  261. filter_file_list(FileList) ->
  262. Atoms = lists:filter( fun(X) -> is_atom(X) end, FileList),
  263. case Atoms of
  264. [] ->
  265. FileList;
  266. _ ->
  267. atoms_in_erl_first_files_warning(Atoms),
  268. lists:filter( fun(X) -> not(is_atom(X)) end, FileList)
  269. end.
  270. atoms_in_erl_first_files_warning(Atoms) ->
  271. W = "You have provided atoms as file entries in erl_first_files; "
  272. "erl_first_files only expects lists of filenames as strings. "
  273. "The following modules (~p) may not work as expected and it is advised "
  274. "that you change these entires to string format "
  275. "(e.g., \"src/module.erl\") ",
  276. ?WARN(W, [Atoms]).
  277. module_to_erl(Mod) ->
  278. atom_to_list(Mod) ++ ".erl".
  279. parse_attrs(Fd, Includes, Dir) ->
  280. case io:parse_erl_form(Fd, "") of
  281. {ok, Form, _Line} ->
  282. case erl_syntax:type(Form) of
  283. attribute ->
  284. NewIncludes = process_attr(Form, Includes, Dir),
  285. parse_attrs(Fd, NewIncludes, Dir);
  286. _ ->
  287. parse_attrs(Fd, Includes, Dir)
  288. end;
  289. {eof, _} ->
  290. lists:usort(Includes);
  291. _Err ->
  292. parse_attrs(Fd, Includes, Dir)
  293. end.
  294. process_attr(Form, Includes, Dir) ->
  295. AttrName = erl_syntax:atom_value(erl_syntax:attribute_name(Form)),
  296. process_attr(AttrName, Form, Includes, Dir).
  297. process_attr(import, Form, Includes, _Dir) ->
  298. case erl_syntax_lib:analyze_import_attribute(Form) of
  299. {Mod, _Funs} ->
  300. [module_to_erl(Mod)|Includes];
  301. Mod ->
  302. [module_to_erl(Mod)|Includes]
  303. end;
  304. process_attr(file, Form, Includes, _Dir) ->
  305. {File, _} = erl_syntax_lib:analyze_file_attribute(Form),
  306. [File|Includes];
  307. process_attr(include, Form, Includes, _Dir) ->
  308. [FileNode] = erl_syntax:attribute_arguments(Form),
  309. File = erl_syntax:string_value(FileNode),
  310. [File|Includes];
  311. process_attr(include_lib, Form, Includes, Dir) ->
  312. [FileNode] = erl_syntax:attribute_arguments(Form),
  313. RawFile = erl_syntax:string_value(FileNode),
  314. maybe_expand_include_lib_path(RawFile, Dir) ++ Includes;
  315. process_attr(behavior, Form, Includes, _Dir) ->
  316. process_attr(behaviour, Form, Includes, _Dir);
  317. process_attr(behaviour, Form, Includes, _Dir) ->
  318. [FileNode] = erl_syntax:attribute_arguments(Form),
  319. File = module_to_erl(erl_syntax:atom_value(FileNode)),
  320. [File|Includes];
  321. process_attr(compile, Form, Includes, _Dir) ->
  322. [Arg] = erl_syntax:attribute_arguments(Form),
  323. case erl_syntax:concrete(Arg) of
  324. {parse_transform, Mod} ->
  325. [module_to_erl(Mod)|Includes];
  326. {core_transform, Mod} ->
  327. [module_to_erl(Mod)|Includes];
  328. L when is_list(L) ->
  329. lists:foldl(
  330. fun({parse_transform, Mod}, Acc) ->
  331. [module_to_erl(Mod)|Acc];
  332. ({core_transform, Mod}, Acc) ->
  333. [module_to_erl(Mod)|Acc];
  334. (_, Acc) ->
  335. Acc
  336. end, Includes, L);
  337. _ ->
  338. Includes
  339. end;
  340. process_attr(_, _Form, Includes, _Dir) ->
  341. Includes.
  342. %% NOTE: If, for example, one of the entries in Files, refers to
  343. %% gen_server.erl, that entry will be dropped. It is dropped because
  344. %% such an entry usually refers to the beam file, and we don't pass a
  345. %% list of OTP src dirs for finding gen_server.erl's full path. Also,
  346. %% if gen_server.erl was modified, it's not rebar's task to compile a
  347. %% new version of the beam file. Therefore, it's reasonable to drop
  348. %% such entries. Also see process_attr(behaviour, Form, Includes).
  349. -spec expand_file_names([file:filename()],
  350. [file:filename()]) -> [file:filename()].
  351. expand_file_names(Files, Dirs) ->
  352. %% We check if Files exist by itself or within the directories
  353. %% listed in Dirs.
  354. %% Return the list of files matched.
  355. lists:flatmap(
  356. fun(Incl) ->
  357. case filelib:is_regular(Incl) of
  358. true ->
  359. [Incl];
  360. false ->
  361. Res = rebar_utils:find_files_in_dirs(Dirs, [$^, Incl, $$], true),
  362. case Res of
  363. [] ->
  364. ?DEBUG("FILE ~p NOT FOUND", [Incl]),
  365. [];
  366. _ ->
  367. Res
  368. end
  369. end
  370. end, Files).
  371. %% Given a path like "stdlib/include/erl_compile.hrl", return
  372. %% "OTP_INSTALL_DIR/lib/erlang/lib/stdlib-x.y.z/include/erl_compile.hrl".
  373. %% Usually a simple [Lib, SubDir, File1] = filename:split(File) should
  374. %% work, but to not crash when an unusual include_lib path is used,
  375. %% utilize more elaborate logic.
  376. maybe_expand_include_lib_path(File, Dir) ->
  377. File1 = filename:basename(File),
  378. case filename:split(filename:dirname(File)) of
  379. [_] ->
  380. warn_and_find_path(File, Dir);
  381. [Lib | SubDir] ->
  382. case code:lib_dir(list_to_atom(Lib), list_to_atom(filename:join(SubDir))) of
  383. {error, bad_name} ->
  384. warn_and_find_path(File, Dir);
  385. AppDir ->
  386. [filename:join(AppDir, File1)]
  387. end
  388. end.
  389. %% The use of -include_lib was probably incorrect by the user but lets try to make it work.
  390. %% We search in the outdir and outdir/../include to see if the header exists.
  391. warn_and_find_path(File, Dir) ->
  392. SrcHeader = filename:join(Dir, File),
  393. case filelib:is_regular(SrcHeader) of
  394. true ->
  395. [SrcHeader];
  396. false ->
  397. IncludeDir = filename:join(rebar_utils:droplast(filename:split(Dir))++["include"]),
  398. IncludeHeader = filename:join(IncludeDir, File),
  399. case filelib:is_regular(IncludeHeader) of
  400. true ->
  401. [filename:join(IncludeDir, File)];
  402. false ->
  403. []
  404. end
  405. end.
  406. format_error({cannot_read_file, Source, Reason}) ->
  407. lists:flatten(io_lib:format("Cannot read file '~s': ~s", [Source, Reason]));
  408. format_error(Other) ->
  409. io_lib:format("~p", [Other]).