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.

283 lines
11 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. %% DRIVER_CFLAGS - default -I paths for erts and ei
  47. %% DRIVER_LDFLAGS - default -L and -lerl_interface -lei
  48. %%
  49. %% Note that if you wish to extend (vs. replace) these variables, you MUST
  50. %% include a shell-style reference in your definition. E.g. to extend CFLAGS,
  51. %% do something like:
  52. %%
  53. %% {port_envs, [{"CFLAGS", "$CFLAGS -MyOtherOptions"}]}
  54. %%
  55. %% It is also possible to specify platform specific options by specifying a triplet
  56. %% where the first string is a regex that is checked against erlang's system architecture
  57. %% string. E.g. to specify a CFLAG that only applies to x86_64 on linux do:
  58. %%
  59. %% {port_envs, [{"x86_64.*-linux", "CFLAGS", "$CFLAGS -X86Options"}]}
  60. %%
  61. %% * port_pre_script - Tuple which specifies a pre-compilation script to run, and a filename that
  62. %% exists as a result of the script running.
  63. %%
  64. %% * port_cleanup_script - String that specifies a script to run during cleanup. Use this to remove
  65. %% files/directories created by port_pre_script.
  66. %%
  67. compile(Config, AppFile) ->
  68. %% Compose list of sources from config file -- defaults to c_src/*.c
  69. Sources = expand_sources(rebar_config:get_list(Config, port_sources, ["c_src/*.c"]), []),
  70. case Sources of
  71. [] ->
  72. ok;
  73. _ ->
  74. %% Extract environment values from the config (if specified) and merge with the
  75. %% default for this operating system. This enables max flexibility for users.
  76. DefaultEnvs = filter_envs(default_env(), []),
  77. OverrideEnvs = filter_envs(rebar_config:get_list(Config, port_envs, []), []),
  78. Env = merge_envs(OverrideEnvs, DefaultEnvs),
  79. %% One or more files are available for building. Run the pre-compile hook, if
  80. %% necessary.
  81. run_precompile_hook(Config, Env),
  82. %% Compile each of the sources
  83. {NewBins, ExistingBins} = compile_each(Sources, Config, Env, [], []),
  84. %% Construct the driver name and make sure priv/ exists
  85. SoName = so_name(Config, AppFile),
  86. ok = filelib:ensure_dir(SoName),
  87. %% Only relink if necessary, given the SoName and list of new binaries
  88. case needs_link(SoName, NewBins) of
  89. true ->
  90. AllBins = string:join(NewBins ++ ExistingBins, " "),
  91. rebar_utils:sh_failfast(?FMT("$CC ~s $LDFLAGS $DRIVER_LDFLAGS -o ~s", [AllBins, SoName]), Env);
  92. false ->
  93. ?INFO("Skipping relink of ~s\n", [SoName]),
  94. ok
  95. end
  96. end.
  97. clean(Config, AppFile) ->
  98. %% Build a list of sources so as to derive all the bins we generated
  99. Sources = expand_sources(rebar_config:get_list(Config, port_sources, ["c_src/*.c"]), []),
  100. rebar_file_utils:delete_each([source_to_bin(S) || S <- Sources]),
  101. %% Delete the .so file
  102. rebar_file_utils:delete_each([so_name(Config, AppFile)]),
  103. %% Run the cleanup script, if it exists
  104. run_cleanup_hook(Config).
  105. %% ===================================================================
  106. %% Internal functions
  107. %% ===================================================================
  108. expand_sources([], Acc) ->
  109. Acc;
  110. expand_sources([Spec | Rest], Acc) ->
  111. Acc2 = filelib:wildcard(Spec) ++ Acc,
  112. expand_sources(Rest, Acc2).
  113. run_precompile_hook(Config, Env) ->
  114. case rebar_config:get(Config, port_pre_script, undefined) of
  115. undefined ->
  116. ok;
  117. {Script, BypassFileName} ->
  118. case filelib:is_regular(BypassFileName) of
  119. false ->
  120. ?CONSOLE("Running ~s\n", [Script]),
  121. rebar_utils:sh_failfast(Script, Env);
  122. true ->
  123. ?INFO("~s exists; not running ~s\n", [BypassFileName, Script])
  124. end
  125. end.
  126. run_cleanup_hook(Config) ->
  127. case rebar_config:get(Config, port_cleanup_script, undefined) of
  128. undefined ->
  129. ok;
  130. Script ->
  131. ?CONSOLE("Running ~s\n", [Script]),
  132. rebar_utils:sh_failfast(Script, [])
  133. end.
  134. compile_each([], _Config, _Env, NewBins, ExistingBins) ->
  135. {lists:reverse(NewBins), lists:reverse(ExistingBins)};
  136. compile_each([Source | Rest], Config, Env, NewBins, ExistingBins) ->
  137. Ext = filename:extension(Source),
  138. Bin = filename:rootname(Source, Ext) ++ ".o",
  139. case needs_compile(Source, Bin) of
  140. true ->
  141. ?CONSOLE("Compiling ~s\n", [Source]),
  142. case compiler(Ext) of
  143. "$CC" ->
  144. rebar_utils:sh_failfast(?FMT("$CC -c $CFLAGS $DRIVER_CFLAGS ~s -o ~s", [Source, Bin]), Env);
  145. "$CXX" ->
  146. rebar_utils:sh_failfast(?FMT("$CXX -c $CXXFLAGS $DRIVER_CFLAGS ~s -o ~s", [Source, Bin]), Env)
  147. end,
  148. compile_each(Rest, Config, Env, [Bin | NewBins], ExistingBins);
  149. false ->
  150. ?INFO("Skipping ~s\n", [Source]),
  151. compile_each(Rest, Config, Env, NewBins, [Bin | ExistingBins])
  152. end.
  153. needs_compile(Source, Bin) ->
  154. %% TODO: Generate depends using gcc -MM so we can also check for include changes
  155. filelib:last_modified(Bin) < filelib:last_modified(Source).
  156. needs_link(SoName, []) ->
  157. filelib:last_modified(SoName) == 0;
  158. needs_link(SoName, NewBins) ->
  159. MaxLastMod = lists:max([filelib:last_modified(B) || B <- NewBins]),
  160. case filelib:last_modified(SoName) of
  161. 0 ->
  162. ?DEBUG("Last mod is 0 on ~s\n", [SoName]),
  163. true;
  164. Other ->
  165. ?DEBUG("Checking ~p >= ~p", [MaxLastMod, Other]),
  166. MaxLastMod >= Other
  167. end.
  168. merge_envs(OverrideEnvs, DefaultEnvs) ->
  169. orddict:merge(fun(Key, Override, Default) ->
  170. expand_env_variable(Override, Key, Default)
  171. end,
  172. orddict:from_list(OverrideEnvs),
  173. orddict:from_list(DefaultEnvs)).
  174. %%
  175. %% Choose a compiler variable, based on a provided extension
  176. %%
  177. compiler(".cc") -> "$CXX";
  178. compiler(".cp") -> "$CXX";
  179. compiler(".cxx") -> "$CXX";
  180. compiler(".cpp") -> "$CXX";
  181. compiler(".CPP") -> "$CXX";
  182. compiler(".c++") -> "$CXX";
  183. compiler(".C") -> "$CXX";
  184. compiler(_) -> "$CC".
  185. %%
  186. %% Given env. variable FOO we want to expand all references to
  187. %% it in InStr. References can have two forms: $FOO and ${FOO}
  188. %%
  189. expand_env_variable(InStr, VarName, VarValue) ->
  190. R1 = re:replace(InStr, "\\\$" ++ VarName, VarValue),
  191. re:replace(R1, "\\\${" ++ VarName ++ "}", VarValue, [{return, list}]).
  192. %%
  193. %% Filter a list of env vars such that only those which match the provided
  194. %% architecture regex (or do not have a regex) are returned.
  195. %%
  196. filter_envs([], Acc) ->
  197. lists:reverse(Acc);
  198. filter_envs([{ArchRegex, Key, Value} | Rest], Acc) ->
  199. case rebar_utils:is_arch(ArchRegex) of
  200. true ->
  201. filter_envs(Rest, [{Key, Value} | Acc]);
  202. false ->
  203. filter_envs(Rest, Acc)
  204. end;
  205. filter_envs([{Key, Value} | Rest], Acc) ->
  206. filter_envs(Rest, [{Key, Value} | Acc]).
  207. erts_dir() ->
  208. lists:concat([code:root_dir(), "/erts-", erlang:system_info(version)]).
  209. default_env() ->
  210. [{"CC", "gcc"},
  211. {"CXX", "g++"},
  212. {"CFLAGS", "-g -Wall -fPIC"},
  213. {"CXXFLAGS", "-g -Wall -fPIC"},
  214. {"LDFLAGS", "-shared"},
  215. {"darwin", "LDFLAGS", "-bundle -flat_namespace -undefined suppress"},
  216. {"DRIVER_CFLAGS", lists:concat([" -I", code:lib_dir(erl_interface, include),
  217. " -I", filename:join(erts_dir(), include),
  218. " "])},
  219. {"DRIVER_LDFLAGS", lists:concat([" -L", code:lib_dir(erl_interface, lib),
  220. " -lerl_interface -lei"])},
  221. {"ERLANG_ARCH", integer_to_list(8 * erlang:system_info(wordsize))},
  222. {"ERLANG_TARGET", rebar_utils:get_arch()}].
  223. source_to_bin(Source) ->
  224. Ext = filename:extension(Source),
  225. filename:rootname(Source, Ext) ++ ".o".
  226. so_name(Config, AppFile) ->
  227. %% Check config to see if a custom so_name has been specified
  228. PortName = case rebar_config:get(Config, so_name, undefined) of
  229. undefined ->
  230. %% Get the app name, which we'll use to
  231. %% generate the linked port driver name
  232. case rebar_app_utils:load_app_file(AppFile) of
  233. {ok, AppName, _} ->
  234. lists:concat([AppName, "_drv.so"]);
  235. error ->
  236. ?FAIL
  237. end;
  238. Soname ->
  239. Soname
  240. end,
  241. %% Construct the driver name
  242. ?FMT("priv/~s", [PortName]).