erlang_check_file.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. #!/usr/bin/env escript
  2. main([File]) ->
  3. Dir = get_root(filename:dirname(File)),
  4. Defs = [strong_validation,
  5. warn_export_all,
  6. warn_export_vars,
  7. warn_shadow_vars,
  8. warn_obsolete_guard,
  9. warn_unused_import,
  10. report,
  11. brief,
  12. {i, Dir ++ "/include"}],
  13. %% `rebar.config` is looked for,
  14. %% but it is not necessarily the one in the project root.
  15. %% I.e. it may be one deeper in the project file hierarchy.
  16. Profile = which_compile_opts_profile(filename:absname(File)),
  17. CompileOpts = case which_build_tool(Dir, Profile) of
  18. {rebar, RebarFile} ->
  19. %% `rebar.config` might contain relative paths.
  20. %% They are relative to the file! Not to the project root.
  21. %% rebar specific begin
  22. rebar_opts(RebarFile);
  23. %% rebar specific end
  24. {erlangmk, ErlangMkDir} ->
  25. %% Erlang.mk specific begin
  26. erlangmk_opts(ErlangMkDir, Profile);
  27. %% Erlang.mk specific end
  28. undefined ->
  29. fallback_opts()
  30. end,
  31. code:add_patha(filename:absname("ebin")),
  32. %% `compile:file/2` requires the `{i, Path}` to be relative
  33. %% to CWD - no surprise here.
  34. compile:file(File, Defs ++ translate_paths(Dir, CompileOpts));
  35. main(_) ->
  36. io:format("Usage: ~s <file>~n", [escript:script_name()]),
  37. halt(1).
  38. which_compile_opts_profile(File) ->
  39. case filename:basename(filename:dirname(File)) of
  40. "test" -> test;
  41. _ -> normal
  42. end.
  43. which_build_tool(Dir, Profile) ->
  44. %% rebar specific begin
  45. RebarFile = rebar_file(Dir, Profile),
  46. %% rebar specific end
  47. case filelib:is_file(RebarFile) of
  48. true ->
  49. {rebar, RebarFile};
  50. false ->
  51. %% Erlang.mk specific begin
  52. ErlangMk = erlangmk_file(Dir),
  53. %% Erlang.mk specific end
  54. case filelib:is_file(ErlangMk) of
  55. true -> {erlangmk, Dir};
  56. false -> undefined
  57. end
  58. end.
  59. rebar_file(Dir, normal) -> filename:join(Dir, "rebar.config");
  60. rebar_file(Dir, test) ->
  61. TestConfig = filename:join(Dir, "rebar.test.config"),
  62. case filelib:is_file(TestConfig) of
  63. true ->
  64. TestConfig;
  65. false ->
  66. %% If we can't find "rebar.test.config" try falling back:
  67. rebar_file(Dir, normal)
  68. end.
  69. erlangmk_file(Dir) -> filename:join(Dir, "erlang.mk").
  70. rebar_opts(RebarFile) ->
  71. Dir = get_root(filename:dirname(RebarFile)),
  72. case file:consult(RebarFile) of
  73. {ok, Terms} ->
  74. %% Add deps for a rebar (version < 3) project
  75. RebarLibDirs = proplists:get_value(lib_dirs, Terms, []),
  76. lists:foreach(
  77. fun(LibDir) ->
  78. code:add_pathsa(filelib:wildcard(LibDir ++ "/*/ebin"))
  79. end, RebarLibDirs),
  80. RebarDepsDir = proplists:get_value(deps_dir, Terms, "deps"),
  81. code:add_pathsa(filelib:wildcard(RebarDepsDir ++ "/*/ebin")),
  82. %% Add deps for rebar 3
  83. code:add_pathsa(filelib:wildcard(Dir ++ "/_build/default/lib/*/ebin")),
  84. %% Add include dependencies
  85. IncludeDeps = [{i, IPath} || IPath <- filelib:wildcard(Dir ++ "/_build/default/lib/*")] ++
  86. [{i, filename:join(Dir, RebarDepsDir)}, %% rebar 2 dependencies
  87. {i, filename:join(Dir, "apps")}], %% rebar 3 multi-apps
  88. proplists:get_value(erl_opts, Terms, []) ++ IncludeDeps;
  89. {error, _} when RebarFile == "rebar.config" ->
  90. fallback_opts();
  91. {error, _} ->
  92. rebar_opts("rebar.config")
  93. end.
  94. erlangmk_opts(BaseDir, Profile) ->
  95. Make =
  96. case os:getenv("MAKE") of
  97. false ->
  98. case os:find_executable("gmake") of
  99. false -> "make";
  100. Path -> Path
  101. end;
  102. Cmd ->
  103. case (lists:member($/, Cmd) orelse lists:member($\\, Cmd)) of
  104. true -> Cmd;
  105. false -> os:find_executable(Cmd)
  106. end
  107. end,
  108. ERLC_OPTS_Target =
  109. case Profile of
  110. normal -> "show-ERLC_OPTS";
  111. test -> "show-TEST_ERLC_OPTS"
  112. end,
  113. Args = [
  114. "--no-print-directory",
  115. "-C", BaseDir,
  116. "show-ERL_LIBS",
  117. ERLC_OPTS_Target
  118. ],
  119. try
  120. Port = erlang:open_port({spawn_executable, Make}, [
  121. {args, Args},
  122. exit_status, use_stdio, stderr_to_stdout]),
  123. case erlangmk_port_receive_loop(Port, "", BaseDir) of
  124. {error, _} ->
  125. fallback_opts();
  126. {ok, {ErlLibs, ErlcOpts}} ->
  127. [code:add_pathsa(filelib:wildcard(
  128. filename:join([ErlLib, "*", "ebin"])))
  129. || ErlLib <- ErlLibs],
  130. ErlcOpts
  131. end
  132. catch
  133. error:_ ->
  134. fallback_opts()
  135. end.
  136. erlangmk_port_receive_loop(Port, Stdout, BaseDir) ->
  137. receive
  138. {Port, {exit_status, 0}} ->
  139. erlangmk_format_opts(Stdout, BaseDir);
  140. {Port, {exit_status, _}} ->
  141. {error, {erlangmk, make_target_failure}};
  142. {Port, {data, Out}} ->
  143. erlangmk_port_receive_loop(Port, Stdout ++ Out, BaseDir)
  144. end.
  145. erlangmk_format_opts(Stdout, BaseDir) ->
  146. case string:tokens(Stdout, "\n") of
  147. [ErlLibsLine | ErlcOptsLines] ->
  148. ErlLibs = erlangmk_format_erl_libs(ErlLibsLine),
  149. ErlcOpts = erlangmk_format_erlc_opts(ErlcOptsLines, BaseDir),
  150. {ok, {ErlLibs, ErlcOpts}};
  151. _ ->
  152. {error, {erlangmk, incorrect_output}}
  153. end.
  154. erlangmk_format_erl_libs(ErlLibsLine) ->
  155. case os:type() of
  156. {win32, _} -> string:tokens(ErlLibsLine, ";");
  157. _ -> string:tokens(ErlLibsLine, ":")
  158. end.
  159. erlangmk_format_erlc_opts(ErlcOptsLines, BaseDir) ->
  160. erlangmk_format_erlc_opts(ErlcOptsLines, [], BaseDir).
  161. erlangmk_format_erlc_opts(["+" ++ Option | Rest], Opts, BaseDir) ->
  162. case make_term(Option) of
  163. {error, _} -> erlangmk_format_erlc_opts(Rest, Opts, BaseDir);
  164. Opt -> erlangmk_format_erlc_opts(Rest, [Opt | Opts], BaseDir)
  165. end;
  166. erlangmk_format_erlc_opts(["-I" ++ Opt | Rest], Opts, BaseDir)
  167. when Opt =/= "" ->
  168. erlangmk_format_erlc_opts(["-I", Opt | Rest], Opts, BaseDir);
  169. erlangmk_format_erlc_opts(["-I", [C | _] = Dir | Rest], Opts, BaseDir)
  170. when C =/= $- andalso C =/= $+ ->
  171. AbsDir = filename:absname(Dir, BaseDir),
  172. erlangmk_format_erlc_opts(Rest, [{i, AbsDir} | Opts], BaseDir);
  173. erlangmk_format_erlc_opts(["-W" ++ Warn | Rest], Opts, BaseDir)
  174. when Warn =/= "" ->
  175. erlangmk_format_erlc_opts(["-W", Warn | Rest], Opts, BaseDir);
  176. erlangmk_format_erlc_opts(["-W", Warn | Rest], Opts, BaseDir) ->
  177. case Warn of
  178. "all" ->
  179. erlangmk_format_erlc_opts(Rest, [{warn_format, 999} | Opts],
  180. BaseDir);
  181. "error" ->
  182. erlangmk_format_erlc_opts(Rest, [warnings_as_errors | Opts],
  183. BaseDir);
  184. "" ->
  185. erlangmk_format_erlc_opts(Rest, [{warn_format, 1} | Opts],
  186. BaseDir);
  187. _ ->
  188. try list_to_integer(Warn) of
  189. Level ->
  190. erlangmk_format_erlc_opts(Rest,
  191. [{warn_format, Level} | Opts], BaseDir)
  192. catch
  193. error:badarg ->
  194. erlangmk_format_erlc_opts(Rest, Opts, BaseDir)
  195. end
  196. end;
  197. erlangmk_format_erlc_opts(["-D" ++ Opt | Rest], Opts, BaseDir)
  198. when Opt =/= "" ->
  199. erlangmk_format_erlc_opts(["-D", Opt | Rest], Opts, BaseDir);
  200. erlangmk_format_erlc_opts(["-D", [C | _] = Val0 | Rest], Opts, BaseDir)
  201. when C =/= $- andalso C =/= $+ ->
  202. {Key0, Val1} = split_at_equals(Val0, []),
  203. Key = list_to_atom(Key0),
  204. case Val1 of
  205. [] ->
  206. erlangmk_format_erlc_opts(Rest, [{d, Key} | Opts], BaseDir);
  207. Val2 ->
  208. case make_term(Val2) of
  209. {error, _} ->
  210. erlangmk_format_erlc_opts(Rest, Opts, BaseDir);
  211. Val ->
  212. erlangmk_format_erlc_opts(Rest, [{d, Key, Val} | Opts], BaseDir)
  213. end
  214. end;
  215. erlangmk_format_erlc_opts([PathFlag, [_ | _] = Dir | Rest], Opts, BaseDir)
  216. when PathFlag =:= "-pa" orelse PathFlag =:= "-pz" ->
  217. AbsDir = filename:absname(Dir, BaseDir),
  218. case PathFlag of
  219. "-pa" -> code:add_patha(AbsDir);
  220. "-pz" -> code:add_pathz(AbsDir)
  221. end,
  222. erlangmk_format_erlc_opts(Rest, Opts, BaseDir);
  223. erlangmk_format_erlc_opts([_ | Rest], Opts, BaseDir) ->
  224. erlangmk_format_erlc_opts(Rest, Opts, BaseDir);
  225. erlangmk_format_erlc_opts([], Opts, _) ->
  226. lists:reverse(Opts).
  227. %% Function imported from erl_compile.erl from Erlang 19.1.
  228. make_term(Str) ->
  229. case erl_scan:string(Str) of
  230. {ok, Tokens, _} ->
  231. case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
  232. {ok, Term} -> Term;
  233. {error, Reason} -> {error, Reason}
  234. end;
  235. {error, Reason, _} ->
  236. {error, Reason}
  237. end.
  238. %% Function imported from erl_compile.erl from Erlang 19.1.
  239. split_at_equals([$=|T], Acc) ->
  240. {lists:reverse(Acc),T};
  241. split_at_equals([H|T], Acc) ->
  242. split_at_equals(T, [H|Acc]);
  243. split_at_equals([], Acc) ->
  244. {lists:reverse(Acc),[]}.
  245. fallback_opts() ->
  246. code:add_pathsa(filelib:wildcard("deps/*/ebin")),
  247. code:add_pathsa(nested_app_ebins()),
  248. [
  249. { i, filename:absname("apps") }, { i, filename:absname("deps") } | [ { i, filename:absname(Path) } || Path <- filelib:wildcard("deps/*/apps")]
  250. ].
  251. nested_app_ebins() ->
  252. DetectedAppSrcFiles = filelib:wildcard("deps/*/apps/**/*.app.src"),
  253. [apps_dir_from_src(AppSrcFile)||AppSrcFile<-DetectedAppSrcFiles].
  254. apps_dir_from_src(SrcFile) ->
  255. SrcDir = filename:dirname(SrcFile),
  256. filename:join(SrcDir, "../../ebin").
  257. %% Find the root directory of the project
  258. get_root(Dir) ->
  259. Path = filename:split(filename:absname(Dir)),
  260. filename:join(get_root(lists:reverse(Path), Path)).
  261. get_root([], Path) ->
  262. Path;
  263. %% Strip off /apps/<appname>/src from the end of the path
  264. %% (rebar 3 multi-app project)
  265. get_root(["src", _Appname, "apps" | Tail], _Path) ->
  266. lists:reverse(Tail);
  267. %% Strip off /src or /test from the end of the path
  268. %% (single-app project)
  269. get_root(["src" | Tail], _Path) ->
  270. lists:reverse(Tail);
  271. get_root(["test" | Tail], _Path) ->
  272. lists:reverse(Tail);
  273. get_root([_ | Tail], Path) ->
  274. get_root(Tail, Path).
  275. translate_paths(Dir, RebarOpts) ->
  276. [ translate_path(Dir, Opt) || Opt <- RebarOpts ].
  277. translate_path(Dir, {i, Path}) ->
  278. case Path of
  279. %% absolute
  280. "/" ++ _ -> {i, Path};
  281. %% relative -> make absolute taking rebar.config location into account
  282. _ -> {i, filename:join([Dir, Path])}
  283. end;
  284. translate_path(_, Other) -> Other.