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.

446 lines
17 KiB

14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
  1. %% -*- 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
  37. %% compiled. Platform specific sources can be specified
  38. %% by enclosing a string in a tuple of the form
  39. %% {Regex, String} wherein Regex is a regular expression
  40. %% that is checked against the system architecture.
  41. %%
  42. %% * so_specs - Erlang list of tuples of the form
  43. %% {"priv/so_name.so", ["c_src/object_file_name.o"]}
  44. %% useful for building multiple *.so files.
  45. %%
  46. %% * port_envs - Erlang list of key/value pairs which will control
  47. %% the environment when running the compiler and linker.
  48. %%
  49. %% By default, the following variables
  50. %% are defined:
  51. %% CC - C compiler
  52. %% CXX - C++ compiler
  53. %% CFLAGS - C compiler
  54. %% CXXFLAGS - C++ compiler
  55. %% LDFLAGS - Link flags
  56. %% ERL_CFLAGS - default -I paths for erts and ei
  57. %% ERL_LDFLAGS - default -L and -lerl_interface -lei
  58. %% DRV_CFLAGS - flags that will be used for compiling the driver
  59. %% DRV_LDFLAGS - flags that will be used for linking the driver
  60. %%
  61. %% Note that if you wish to extend (vs. replace) these variables,
  62. %% you MUST include a shell-style reference in your definition.
  63. %% e.g. to extend CFLAGS, do something like:
  64. %%
  65. %% {port_envs, [{"CFLAGS", "$CFLAGS -MyOtherOptions"}]}
  66. %%
  67. %% It is also possible to specify platform specific options
  68. %% by specifying a tripletwhere the first string is a regex
  69. %% that is checked against erlang's system architecture string.
  70. %% e.g. to specify a CFLAG that only applies to x86_64 on linux
  71. %% do:
  72. %%
  73. %% {port_envs, [{"x86_64.*-linux", "CFLAGS",
  74. %% "$CFLAGS -X86Options"}]}
  75. %%
  76. %% * port_pre_script - Tuple which specifies a pre-compilation script to run,
  77. %% and a filename that exists as a result of the script
  78. %% running.
  79. %%
  80. %% * port_cleanup_script - String that specifies a script to run during cleanup.
  81. %% Use this to remove files/directories created by
  82. %% port_pre_script.
  83. %%
  84. compile(Config, AppFile) ->
  85. %% Compose list of sources from config file -- defaults to c_src/*.c
  86. Sources = expand_sources(rebar_config:get_list(Config, port_sources,
  87. ["c_src/*.c"]), []),
  88. case Sources of
  89. [] ->
  90. ok;
  91. _ ->
  92. %% Extract environment values from the config (if specified) and
  93. %% merge with the default for this operating system. This enables
  94. %% max flexibility for users.
  95. DefaultEnvs = filter_envs(default_env(), []),
  96. PortEnvs = rebar_config:get_list(Config, port_envs, []),
  97. OverrideEnvs = filter_envs(PortEnvs, []),
  98. RawEnv = DefaultEnvs ++ OverrideEnvs ++ os_env(),
  99. Env = expand_vars_loop(merge_each_var(RawEnv, [])),
  100. %% One or more files are available for building.
  101. %% Run the pre-compile hook, if necessary.
  102. ok = run_precompile_hook(Config, Env),
  103. %% Compile each of the sources
  104. {NewBins, ExistingBins} = compile_each(Sources, Config, Env,
  105. [], []),
  106. %% Construct the driver name and make sure priv/ exists
  107. SoSpecs = so_specs(Config, AppFile, NewBins ++ ExistingBins),
  108. ?INFO("Using specs ~p\n", [SoSpecs]),
  109. lists:foreach(fun({SoName,_}) ->
  110. ok = filelib:ensure_dir(SoName)
  111. end, SoSpecs),
  112. %% Only relink if necessary, given the SoName
  113. %% and list of new binaries
  114. lists:foreach(
  115. fun({SoName,Bins}) ->
  116. AllBins = [sets:from_list(Bins), sets:from_list(NewBins)],
  117. Intersection = sets:intersection(AllBins),
  118. case needs_link(SoName, sets:to_list(Intersection)) of
  119. true ->
  120. rebar_utils:sh(
  121. ?FMT("$CC ~s $LDFLAGS $DRV_LDFLAGS -o ~s",
  122. [string:join(Bins, " "), SoName]),
  123. [{env, Env}]);
  124. false ->
  125. ?INFO("Skipping relink of ~s\n", [SoName]),
  126. ok
  127. end
  128. end, SoSpecs)
  129. end.
  130. clean(Config, AppFile) ->
  131. %% Build a list of sources so as to derive all the bins we generated
  132. Sources = expand_sources(rebar_config:get_list(Config, port_sources,
  133. ["c_src/*.c"]), []),
  134. rebar_file_utils:delete_each([source_to_bin(S) || S <- Sources]),
  135. %% Delete the .so file
  136. ExtractSoName = fun({SoName, _}) -> SoName end,
  137. rebar_file_utils:delete_each([ExtractSoName(S)
  138. || S <- so_specs(Config, AppFile,
  139. expand_objects(Sources))]),
  140. %% Run the cleanup script, if it exists
  141. run_cleanup_hook(Config).
  142. %% ===================================================================
  143. %% Internal functions
  144. %% ===================================================================
  145. expand_sources([], Acc) ->
  146. Acc;
  147. expand_sources([{ArchRegex, Spec} | Rest], Acc) ->
  148. case rebar_utils:is_arch(ArchRegex) of
  149. true ->
  150. Acc2 = filelib:wildcard(Spec) ++ Acc,
  151. expand_sources(Rest, Acc2);
  152. false ->
  153. expand_sources(Rest, Acc)
  154. end;
  155. expand_sources([Spec | Rest], Acc) ->
  156. Acc2 = filelib:wildcard(Spec) ++ Acc,
  157. expand_sources(Rest, Acc2).
  158. expand_objects(Sources) ->
  159. [filename:join([filename:dirname(F), filename:basename(F) ++ ".o"])
  160. || F <- Sources].
  161. run_precompile_hook(Config, Env) ->
  162. case rebar_config:get(Config, port_pre_script, undefined) of
  163. undefined ->
  164. ok;
  165. {Script, BypassFileName} ->
  166. ?CONSOLE(
  167. <<
  168. "WARNING: option deprecated~n"
  169. "Config option 'port_pre_script' has been deprecated "
  170. "in favor of ~n{pre_hooks, [{compile, \"script\"}]}."
  171. "~nskipfile support has also been removed. Add skipfile"
  172. " logic to the~nscript instead.~nFuture builds of rebar"
  173. " will remove the option 'port_pre_script'.~n~n"
  174. >>, []),
  175. case filelib:is_regular(BypassFileName) of
  176. false ->
  177. ?CONSOLE("Running ~s\n", [Script]),
  178. {ok, _} = rebar_utils:sh(Script, [{env, Env}]),
  179. ok;
  180. true ->
  181. ?INFO("~s exists; not running ~s\n",
  182. [BypassFileName, Script])
  183. end
  184. end.
  185. run_cleanup_hook(Config) ->
  186. case rebar_config:get(Config, port_cleanup_script, undefined) of
  187. undefined ->
  188. ok;
  189. Script ->
  190. ?CONSOLE(
  191. <<
  192. "WARNING: option deprecated~n"
  193. "Config option 'port_pre_script' has been deprecated "
  194. "in favor of ~n{post_hooks, [{clean, \"script\"}]}."
  195. "~nFuture builds of rebar will remove the option "
  196. "'port_pre_script'.~n~n"
  197. >>, []),
  198. ?CONSOLE("Running ~s\n", [Script]),
  199. {ok, _} = rebar_utils:sh(Script, []),
  200. ok
  201. end.
  202. compile_each([], _Config, _Env, NewBins, ExistingBins) ->
  203. {lists:reverse(NewBins), lists:reverse(ExistingBins)};
  204. compile_each([Source | Rest], Config, Env, NewBins, ExistingBins) ->
  205. Ext = filename:extension(Source),
  206. Bin = filename:rootname(Source, Ext) ++ ".o",
  207. case needs_compile(Source, Bin) of
  208. true ->
  209. ?CONSOLE("Compiling ~s\n", [Source]),
  210. case compiler(Ext) of
  211. "$CC" ->
  212. rebar_utils:sh(?FMT("$CC -c $CFLAGS $DRV_CFLAGS ~s -o ~s",
  213. [Source, Bin]), [{env, Env}]);
  214. "$CXX" ->
  215. rebar_utils:sh(
  216. ?FMT("$CXX -c $CXXFLAGS $DRV_CFLAGS ~s -o ~s",
  217. [Source, Bin]), [{env, Env}])
  218. end,
  219. compile_each(Rest, Config, Env, [Bin | NewBins], ExistingBins);
  220. false ->
  221. ?INFO("Skipping ~s\n", [Source]),
  222. compile_each(Rest, Config, Env, NewBins, [Bin | ExistingBins])
  223. end.
  224. needs_compile(Source, Bin) ->
  225. %% TODO: Generate depends using gcc -MM so we can also
  226. %% check for include changes
  227. filelib:last_modified(Bin) < filelib:last_modified(Source).
  228. needs_link(SoName, []) ->
  229. filelib:last_modified(SoName) == 0;
  230. needs_link(SoName, NewBins) ->
  231. MaxLastMod = lists:max([filelib:last_modified(B) || B <- NewBins]),
  232. case filelib:last_modified(SoName) of
  233. 0 ->
  234. ?DEBUG("Last mod is 0 on ~s\n", [SoName]),
  235. true;
  236. Other ->
  237. ?DEBUG("Checking ~p >= ~p", [MaxLastMod, Other]),
  238. MaxLastMod >= Other
  239. end.
  240. %%
  241. %% Choose a compiler variable, based on a provided extension
  242. %%
  243. compiler(".cc") -> "$CXX";
  244. compiler(".cp") -> "$CXX";
  245. compiler(".cxx") -> "$CXX";
  246. compiler(".cpp") -> "$CXX";
  247. compiler(".CPP") -> "$CXX";
  248. compiler(".c++") -> "$CXX";
  249. compiler(".C") -> "$CXX";
  250. compiler(_) -> "$CC".
  251. %%
  252. %% Given a list of {Key, Value} environment variables, where Key may be defined
  253. %% multiple times, walk the list and expand each self-reference so that we
  254. %% end with a list of each variable singly-defined.
  255. %%
  256. merge_each_var([], Vars) ->
  257. Vars;
  258. merge_each_var([{Key, Value} | Rest], Vars) ->
  259. case orddict:find(Key, Vars) of
  260. error ->
  261. %% Nothing yet defined for this key/value.
  262. %% Expand any self-references as blank.
  263. Evalue = expand_env_variable(Value, Key, "");
  264. {ok, Value0} ->
  265. %% Use previous definition in expansion
  266. Evalue = expand_env_variable(Value, Key, Value0)
  267. end,
  268. merge_each_var(Rest, orddict:store(Key, Evalue, Vars)).
  269. %%
  270. %% Give a unique list of {Key, Value} environment variables, expand each one
  271. %% for every other key until no further expansions are possible.
  272. %%
  273. expand_vars_loop(Vars) ->
  274. expand_vars_loop(Vars, 10).
  275. expand_vars_loop(_, 0) ->
  276. ?ABORT("Max. expansion reached for ENV vars!\n", []);
  277. expand_vars_loop(Vars0, Count) ->
  278. Vars = lists:foldl(fun({Key, Value}, Acc) ->
  279. expand_vars(Key, Value, Acc)
  280. end,
  281. Vars0, Vars0),
  282. case orddict:from_list(Vars) of
  283. Vars0 ->
  284. Vars0;
  285. _ ->
  286. expand_vars_loop(Vars, Count-1)
  287. end.
  288. %%
  289. %% Expand all OTHER references to a given K/V pair
  290. %%
  291. expand_vars(Key, Value, Vars) ->
  292. lists:foldl(
  293. fun({AKey, AValue}, Acc) ->
  294. case AKey of
  295. Key ->
  296. NewValue = AValue;
  297. _ ->
  298. NewValue = expand_env_variable(AValue, Key, Value)
  299. end,
  300. [{AKey, NewValue} | Acc]
  301. end,
  302. [], Vars).
  303. %%
  304. %% Given env. variable FOO we want to expand all references to
  305. %% it in InStr. References can have two forms: $FOO and ${FOO}
  306. %%
  307. expand_env_variable(InStr, VarName, VarValue) ->
  308. R1 = re:replace(InStr, "\\\$" ++ VarName, VarValue),
  309. re:replace(R1, "\\\${" ++ VarName ++ "}", VarValue, [{return, list}]).
  310. %%
  311. %% Filter a list of env vars such that only those which match the provided
  312. %% architecture regex (or do not have a regex) are returned.
  313. %%
  314. filter_envs([], Acc) ->
  315. lists:reverse(Acc);
  316. filter_envs([{ArchRegex, Key, Value} | Rest], Acc) ->
  317. case rebar_utils:is_arch(ArchRegex) of
  318. true ->
  319. filter_envs(Rest, [{Key, Value} | Acc]);
  320. false ->
  321. filter_envs(Rest, Acc)
  322. end;
  323. filter_envs([{Key, Value} | Rest], Acc) ->
  324. filter_envs(Rest, [{Key, Value} | Acc]).
  325. erts_dir() ->
  326. lists:concat([code:root_dir(), "/erts-", erlang:system_info(version)]).
  327. os_env() ->
  328. Os = [list_to_tuple(re:split(S, "=", [{return, list}, {parts, 2}])) ||
  329. S <- os:getenv()],
  330. lists:keydelete([],1,Os). %% Remove Windows current disk and path
  331. default_env() ->
  332. [
  333. {"CC", "cc"},
  334. {"CXX", "c++"},
  335. {"ERL_CFLAGS", lists:concat([" -I", code:lib_dir(erl_interface, include),
  336. " -I", filename:join(erts_dir(), "include"),
  337. " "])},
  338. {"ERL_LDFLAGS", lists:concat([" -L", code:lib_dir(erl_interface, lib),
  339. " -lerl_interface -lei"])},
  340. {"DRV_CFLAGS", "-g -Wall -fPIC $ERL_CFLAGS"},
  341. {"DRV_LDFLAGS", "-shared $ERL_LDFLAGS"},
  342. {"darwin", "DRV_LDFLAGS",
  343. "-bundle -flat_namespace -undefined suppress $ERL_LDFLAGS"},
  344. {"ERLANG_ARCH", integer_to_list(8 * erlang:system_info(wordsize))},
  345. {"ERLANG_TARGET", rebar_utils:get_arch()},
  346. {"solaris.*-64$", "CFLAGS", "-D_REENTRANT -m64"}, % Solaris specific flags
  347. {"solaris.*-64$", "CXXFLAGS", "-D_REENTRANT -m64"},
  348. {"solaris.*-64$", "LDFLAGS", "-m64"},
  349. {"darwin9.*-64$", "CFLAGS", "-m64"}, % OS X Leopard flags for 64-bit
  350. {"darwin9.*-64$", "CXXFLAGS", "-m64"},
  351. {"darwin9.*-64$", "LDFLAGS", "-arch x86_64"},
  352. {"darwin10.*-32", "CFLAGS", "-m32"}, % OS X Snow Leopard flags for 32-bit
  353. {"darwin10.*-32", "CXXFLAGS", "-m32"},
  354. {"darwin10.*-32", "LDFLAGS", "-arch i386"}
  355. ].
  356. source_to_bin(Source) ->
  357. Ext = filename:extension(Source),
  358. filename:rootname(Source, Ext) ++ ".o".
  359. so_specs(Config, AppFile, Bins) ->
  360. Specs = make_so_specs(Config, AppFile, Bins),
  361. case os:type() of
  362. {win32, nt} ->
  363. [switch_so_to_dll(SoSpec) || SoSpec <- Specs];
  364. _ ->
  365. Specs
  366. end.
  367. switch_so_to_dll(Orig = {Name, Spec}) ->
  368. case filename:extension(Name) of
  369. ".so" ->
  370. {filename:rootname(Name, ".so") ++ ".dll", Spec};
  371. _ ->
  372. %% Not a .so; leave it
  373. Orig
  374. end.
  375. make_so_specs(Config, AppFile, Bins) ->
  376. case rebar_config:get(Config, so_specs, undefined) of
  377. undefined ->
  378. %% New form of so_specs is not provided. See if the old form
  379. %% of {so_name} is available instead
  380. Dir = "priv",
  381. SoName = case rebar_config:get(Config, so_name, undefined) of
  382. undefined ->
  383. %% Ok, neither old nor new form is available. Use
  384. %% the app name and generate a sensible default.
  385. AppName = rebar_app_utils:app_name(AppFile),
  386. filename:join(Dir,
  387. lists:concat([AppName, "_drv.so"]));
  388. AName ->
  389. %% Old form is available -- use it
  390. filename:join(Dir, AName)
  391. end,
  392. [{SoName, Bins}];
  393. SoSpecs ->
  394. SoSpecs
  395. end.