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

376 lines
15 KiB

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