Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

347 rader
13 KiB

  1. %% -*- tab-width: 4;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 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_port_compiler).
  28. -export([compile/2,
  29. clean/2]).
  30. -include("rebar.hrl").
  31. %% ===================================================================
  32. %% Public API
  33. %% ===================================================================
  34. %% Supported configuration variables:
  35. %%
  36. %% * port_sources - Erlang list of files and/or wildcard strings to be compiled
  37. %%
  38. %% * port_envs - Erlang list of key/value pairs which will control the environment when
  39. %% running the compiler and linker. By default, the following variables
  40. %% are defined:
  41. %% CC - C compiler
  42. %% CXX - C++ compiler
  43. %% CFLAGS - C compiler
  44. %% CXXFLAGS - C++ compiler
  45. %% LDFLAGS - Link flags
  46. %% ERL_CFLAGS - default -I paths for erts and ei
  47. %% ERL_LDFLAGS - default -L and -lerl_interface -lei
  48. %% DRV_CFLAGS - flags that will be used for compiling the driver
  49. %% DRV_LDFLAGS - flags that will be used for linking the driver
  50. %%
  51. %% Note that if you wish to extend (vs. replace) these variables, you MUST
  52. %% include a shell-style reference in your definition. E.g. to extend CFLAGS,
  53. %% do something like:
  54. %%
  55. %% {port_envs, [{"CFLAGS", "$CFLAGS -MyOtherOptions"}]}
  56. %%
  57. %% It is also possible to specify platform specific options by specifying a triplet
  58. %% where the first string is a regex that is checked against erlang's system architecture
  59. %% string. E.g. to specify a CFLAG that only applies to x86_64 on linux do:
  60. %%
  61. %% {port_envs, [{"x86_64.*-linux", "CFLAGS", "$CFLAGS -X86Options"}]}
  62. %%
  63. %% * port_pre_script - Tuple which specifies a pre-compilation script to run, and a filename that
  64. %% exists as a result of the script running.
  65. %%
  66. %% * port_cleanup_script - String that specifies a script to run during cleanup. Use this to remove
  67. %% files/directories created by port_pre_script.
  68. %%
  69. compile(Config, AppFile) ->
  70. %% Compose list of sources from config file -- defaults to c_src/*.c
  71. Sources = expand_sources(rebar_config:get_list(Config, port_sources, ["c_src/*.c"]), []),
  72. case Sources of
  73. [] ->
  74. ok;
  75. _ ->
  76. %% Extract environment values from the config (if specified) and merge with the
  77. %% default for this operating system. This enables max flexibility for users.
  78. DefaultEnvs = filter_envs(default_env(), []),
  79. OverrideEnvs = filter_envs(rebar_config:get_list(Config, port_envs, []), []),
  80. Env = expand_vars_loop(merge_each_var(os_env() ++ DefaultEnvs ++ OverrideEnvs, [])),
  81. %% One or more files are available for building. Run the pre-compile hook, if
  82. %% necessary.
  83. run_precompile_hook(Config, Env),
  84. %% Compile each of the sources
  85. {NewBins, ExistingBins} = compile_each(Sources, Config, Env, [], []),
  86. %% Construct the driver name and make sure priv/ exists
  87. SoName = so_name(Config, AppFile),
  88. ok = filelib:ensure_dir(SoName),
  89. %% Only relink if necessary, given the SoName and list of new binaries
  90. case needs_link(SoName, NewBins) of
  91. true ->
  92. AllBins = string:join(NewBins ++ ExistingBins, " "),
  93. rebar_utils:sh_failfast(?FMT("$CC ~s $LDFLAGS $DRV_LDFLAGS -o ~s",
  94. [AllBins, SoName]), Env);
  95. false ->
  96. ?INFO("Skipping relink of ~s\n", [SoName]),
  97. ok
  98. end
  99. end.
  100. clean(Config, AppFile) ->
  101. %% Build a list of sources so as to derive all the bins we generated
  102. Sources = expand_sources(rebar_config:get_list(Config, port_sources, ["c_src/*.c"]), []),
  103. rebar_file_utils:delete_each([source_to_bin(S) || S <- Sources]),
  104. %% Delete the .so file
  105. rebar_file_utils:delete_each([so_name(Config, AppFile)]),
  106. %% Run the cleanup script, if it exists
  107. run_cleanup_hook(Config).
  108. %% ===================================================================
  109. %% Internal functions
  110. %% ===================================================================
  111. expand_sources([], Acc) ->
  112. Acc;
  113. expand_sources([Spec | Rest], Acc) ->
  114. Acc2 = filelib:wildcard(Spec) ++ Acc,
  115. expand_sources(Rest, Acc2).
  116. run_precompile_hook(Config, Env) ->
  117. case rebar_config:get(Config, port_pre_script, undefined) of
  118. undefined ->
  119. ok;
  120. {Script, BypassFileName} ->
  121. case filelib:is_regular(BypassFileName) of
  122. false ->
  123. ?CONSOLE("Running ~s\n", [Script]),
  124. rebar_utils:sh_failfast(Script, Env);
  125. true ->
  126. ?INFO("~s exists; not running ~s\n", [BypassFileName, Script])
  127. end
  128. end.
  129. run_cleanup_hook(Config) ->
  130. case rebar_config:get(Config, port_cleanup_script, undefined) of
  131. undefined ->
  132. ok;
  133. Script ->
  134. ?CONSOLE("Running ~s\n", [Script]),
  135. rebar_utils:sh_failfast(Script, [])
  136. end.
  137. compile_each([], _Config, _Env, NewBins, ExistingBins) ->
  138. {lists:reverse(NewBins), lists:reverse(ExistingBins)};
  139. compile_each([Source | Rest], Config, Env, NewBins, ExistingBins) ->
  140. Ext = filename:extension(Source),
  141. Bin = filename:rootname(Source, Ext) ++ ".o",
  142. case needs_compile(Source, Bin) of
  143. true ->
  144. ?CONSOLE("Compiling ~s\n", [Source]),
  145. case compiler(Ext) of
  146. "$CC" ->
  147. rebar_utils:sh_failfast(?FMT("$CC -c $CFLAGS $DRV_CFLAGS ~s -o ~s",
  148. [Source, Bin]), Env);
  149. "$CXX" ->
  150. rebar_utils:sh_failfast(?FMT("$CXX -c $CXXFLAGS $DRV_CFLAGS ~s -o ~s",
  151. [Source, Bin]), Env)
  152. end,
  153. compile_each(Rest, Config, Env, [Bin | NewBins], ExistingBins);
  154. false ->
  155. ?INFO("Skipping ~s\n", [Source]),
  156. compile_each(Rest, Config, Env, NewBins, [Bin | ExistingBins])
  157. end.
  158. needs_compile(Source, Bin) ->
  159. %% TODO: Generate depends using gcc -MM so we can also check for include changes
  160. filelib:last_modified(Bin) < filelib:last_modified(Source).
  161. needs_link(SoName, []) ->
  162. filelib:last_modified(SoName) == 0;
  163. needs_link(SoName, NewBins) ->
  164. MaxLastMod = lists:max([filelib:last_modified(B) || B <- NewBins]),
  165. case filelib:last_modified(SoName) of
  166. 0 ->
  167. ?DEBUG("Last mod is 0 on ~s\n", [SoName]),
  168. true;
  169. Other ->
  170. ?DEBUG("Checking ~p >= ~p", [MaxLastMod, Other]),
  171. MaxLastMod >= Other
  172. end.
  173. %%
  174. %% Choose a compiler variable, based on a provided extension
  175. %%
  176. compiler(".cc") -> "$CXX";
  177. compiler(".cp") -> "$CXX";
  178. compiler(".cxx") -> "$CXX";
  179. compiler(".cpp") -> "$CXX";
  180. compiler(".CPP") -> "$CXX";
  181. compiler(".c++") -> "$CXX";
  182. compiler(".C") -> "$CXX";
  183. compiler(_) -> "$CC".
  184. %%
  185. %% Given a list of {Key, Value} environment variables, where Key may be defined
  186. %% multiple times, walk the list and expand each self-reference so that we
  187. %% end with a list of each variable singly-defined.
  188. %%
  189. merge_each_var([], Vars) ->
  190. Vars;
  191. merge_each_var([{Key, Value} | Rest], Vars) ->
  192. case orddict:find(Key, Vars) of
  193. error ->
  194. %% Nothing yet defined for this key/value. Expand any self-references
  195. %% as blank.
  196. Evalue = expand_env_variable(Value, Key, "");
  197. {ok, Value0} ->
  198. %% Use previous definition in expansion
  199. Evalue = expand_env_variable(Value, Key, Value0)
  200. end,
  201. merge_each_var(Rest, orddict:store(Key, Evalue, Vars)).
  202. %%
  203. %% Give a unique list of {Key, Value} environment variables, expand each one
  204. %% for every other key until no further expansions are possible.
  205. %%
  206. expand_vars_loop(Vars) ->
  207. expand_vars_loop(Vars, 10).
  208. expand_vars_loop(_, 0) ->
  209. ?ABORT("Max. expansion reached for ENV vars!\n", []);
  210. expand_vars_loop(Vars0, Count) ->
  211. Vars = lists:foldl(fun({Key, Value}, Acc) ->
  212. expand_vars(Key, Value, Acc)
  213. end,
  214. Vars0, Vars0),
  215. case orddict:from_list(Vars) of
  216. Vars0 ->
  217. Vars0;
  218. _ ->
  219. expand_vars_loop(Vars, Count-1)
  220. end.
  221. %%
  222. %% Expand all OTHER references to a given K/V pair
  223. %%
  224. expand_vars(Key, Value, Vars) ->
  225. lists:foldl(fun({AKey, AValue}, Acc) ->
  226. case AKey of
  227. Key ->
  228. NewValue = AValue;
  229. _ ->
  230. NewValue = expand_env_variable(AValue, Key, Value)
  231. end,
  232. [{AKey, NewValue} | Acc]
  233. end,
  234. [], Vars).
  235. %%
  236. %% Given env. variable FOO we want to expand all references to
  237. %% it in InStr. References can have two forms: $FOO and ${FOO}
  238. %%
  239. expand_env_variable(InStr, VarName, VarValue) ->
  240. R1 = re:replace(InStr, "\\\$" ++ VarName, VarValue),
  241. re:replace(R1, "\\\${" ++ VarName ++ "}", VarValue, [{return, list}]).
  242. %%
  243. %% Filter a list of env vars such that only those which match the provided
  244. %% architecture regex (or do not have a regex) are returned.
  245. %%
  246. filter_envs([], Acc) ->
  247. lists:reverse(Acc);
  248. filter_envs([{ArchRegex, Key, Value} | Rest], Acc) ->
  249. case rebar_utils:is_arch(ArchRegex) of
  250. true ->
  251. filter_envs(Rest, [{Key, Value} | Acc]);
  252. false ->
  253. filter_envs(Rest, Acc)
  254. end;
  255. filter_envs([{Key, Value} | Rest], Acc) ->
  256. filter_envs(Rest, [{Key, Value} | Acc]).
  257. erts_dir() ->
  258. lists:concat([code:root_dir(), "/erts-", erlang:system_info(version)]).
  259. os_env() ->
  260. [list_to_tuple(re:split(S, "=", [{return, list}, {parts, 2}])) || S <- os:getenv()].
  261. default_env() ->
  262. [
  263. {"CC", "gcc"},
  264. {"CXX", "g++"},
  265. {"ERL_CFLAGS", lists:concat([" -I", code:lib_dir(erl_interface, include),
  266. " -I", filename:join(erts_dir(), include),
  267. " "])},
  268. {"ERL_LDFLAGS", lists:concat([" -L", code:lib_dir(erl_interface, lib),
  269. " -lerl_interface -lei"])},
  270. {"DRV_CFLAGS", "-g -Wall -fPIC $ERL_CFLAGS"},
  271. {"DRV_LDFLAGS", "-shared $ERL_LDFLAGS"},
  272. {"darwin", "DRV_LDFLAGS", "-bundle -flat_namespace -undefined suppress $ERL_LDFLAGS"},
  273. {"ERLANG_ARCH", integer_to_list(8 * erlang:system_info(wordsize))},
  274. {"ERLANG_TARGET", rebar_utils:get_arch()},
  275. {"solaris.*-64$", "CFLAGS", "-D_REENTRANT -m64"}, % Solaris specific flags
  276. {"solaris.*-64$", "LDFLAGS", "-m64"},
  277. {"darwin9.*-64$", "CFLAGS", "-m64"}, % OS X Leopard flags for 64-bit
  278. {"darwin9.*-64$", "LDFLAGS", "-arch x86_64"},
  279. {"darwin10.*-32", "CFLAGS", "-m32"}, % OS X Snow Leopard flags for 32-bit
  280. {"darwin10.*-32", "LDFLAGS", "-arch i386"}
  281. ].
  282. source_to_bin(Source) ->
  283. Ext = filename:extension(Source),
  284. filename:rootname(Source, Ext) ++ ".o".
  285. so_name(Config, AppFile) ->
  286. %% Check config to see if a custom so_name has been specified
  287. PortName = case rebar_config:get(Config, so_name, undefined) of
  288. undefined ->
  289. %% Get the app name, which we'll use to
  290. %% generate the linked port driver name
  291. AppName = rebar_app_utils:app_name(AppFile),
  292. lists:concat([AppName, "_drv.so"]);
  293. Soname ->
  294. Soname
  295. end,
  296. %% Construct the driver name
  297. ?FMT("priv/~s", [PortName]).