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.

446 rader
17 KiB

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