Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

466 řádky
17 KiB

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