You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

347 lines
13 KiB

преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
  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]).