@ -0,0 +1,11 @@ | |||
all: | |||
./bootstrap.erl | |||
debug: | |||
./bootstrap debug | |||
xref: debug | |||
./xref | |||
clean: | |||
@rm -rf rebar .rebar ebin/*.beam |
@ -0,0 +1,18 @@ | |||
# Erlang nif port compiler | |||
windows linux mac下erlang nif或者port_driver通用编译脚本 | |||
改造自 erlang-native-compiler | |||
## Usage | |||
default_env | |||
1. Clone this repository | |||
1. Run `make` in this directory | |||
1. Copy `erlNpc` to your project "c_src" dir and commit it | |||
1. Add these (or similar) hooks to your rebar.config: | |||
```erlang | |||
{pre_hooks, [{"", compile, "escript c_src/erlNpc compile"}]}. | |||
{post_hooks, [{"", clean, "escript c_src/erlNpc clean"}]}. | |||
``` | |||
After that enc should read your old rebar.config `port\_specs` and `port\_env` settings as expected (it is rebar2's port compiler after all...). |
@ -0,0 +1,212 @@ | |||
#!/usr/bin/env escript | |||
%% -*- erlang -* | |||
%%! | |||
-include_lib("kernel/include/file.hrl"). | |||
main(Args) -> | |||
case lists:member("--help", Args) of | |||
true -> | |||
usage(), | |||
halt(0); | |||
false -> | |||
ok | |||
end, | |||
%% Get a string repr of build time | |||
BuiltTime = getTimeStr(), | |||
%% Get a string repr of first matching VCS changeset | |||
VcsInfo = vcsInfo([{git, ".git", "git describe --always --tags", "git status -s"}]), | |||
%% Check for force=1 flag to force a rebuild | |||
case lists:member("force=1", Args) of | |||
true -> | |||
rm("ebin/*.beam"); | |||
false -> | |||
case filelib:is_file("ebin/rebar.beam") of | |||
true -> rm("ebin/rebar.beam"); | |||
false -> io:fwrite("No beam files found.~n") | |||
end | |||
end, | |||
%% Add check for debug flag | |||
DebugFlag = | |||
case lists:member("debug", Args) of | |||
true -> debug_info; | |||
false -> undefined | |||
end, | |||
OtpInfo = string:strip(erlang:system_info(otp_release), both, $\n), | |||
%% Types dict:dict() and digraph:digraph() have been introduced in | |||
%% Erlang 17. | |||
%% At the same time, their counterparts dict() and digraph() are to be | |||
%% deprecated in Erlang 18. namespaced_types option is used to select | |||
%% proper type name depending on the OTP version used. | |||
%% Extract the system info of the version of OTP we use to compile rebar | |||
%% NamespacedTypes = | |||
%% case is_otp(OtpInfo, "^[0-9]+") of | |||
%% true -> {d, namespaced_types}; | |||
%% false -> undefined | |||
%% end, | |||
%% Compile all src/*.erl to ebin | |||
%% To not accidentally try to compile files like Mac OS X resource forks, | |||
%% we only look for rebar source files that start with a letter. | |||
Opts = [ | |||
DebugFlag, | |||
{outdir, "ebin"}, | |||
{i, "include"}, | |||
{d, 'BUILD_TIME', BuiltTime}, | |||
{d, 'VCS_INFO', VcsInfo}, | |||
{d, 'OTP_INFO', OtpInfo} | |||
], | |||
case make:files(filelib:wildcard("src/*.erl"), Opts) of | |||
up_to_date -> | |||
ok; | |||
error -> | |||
io:format("Failed to compile erlNpc files!\n"), | |||
halt(1) | |||
end, | |||
%% Make sure file:consult can parse the .app file | |||
case file:consult("ebin/erlNpc.app") of | |||
{ok, _} -> | |||
ok; | |||
{error, Reason} -> | |||
io:format("Invalid syntax in ebin/erlNpc.app: ~p\n", [Reason]), | |||
halt(1) | |||
end, | |||
%% Add ebin/ to our path | |||
true = code:add_path("ebin"), | |||
%% Run rebar compile to do proper .app validation etc. | |||
%% and rebar escriptize to create the rebar script | |||
%% RebarArgs = Args -- ["debug"], %% Avoid trying to run 'debug' command | |||
% rebar:main(["compile"] ++ RebarArgs), | |||
escriptize(), | |||
%% Finally, update executable perms for our script on *nix, | |||
%% or write out script files on win32. | |||
case os:type() of | |||
{unix, _} -> | |||
[] = os:cmd("chmod u+x erlNpc"), | |||
ok; | |||
{win32, _} -> | |||
writeWindowsScripts(), | |||
ok; | |||
_ -> | |||
ok | |||
end, | |||
%% Add a helpful message | |||
io:format(<<"Congratulations! You now have a self-contained script called" | |||
" \"erlNpc\" in\n" | |||
"your current working directory. " | |||
"Place this script anywhere in your path\n" | |||
"and you can use erlNpc to build native code for Erlang\n">>). | |||
usage() -> | |||
io:format(<<"Usage: bootstrap [OPTION]...~n">>), | |||
io:format(<<" force=1 unconditional build~n">>), | |||
io:format(<<" debug add debug information~n">>). | |||
%% is_otp(OtpInfo, Regex) -> | |||
%% case re:run(OtpInfo, Regex, [{capture, none}]) of | |||
%% match -> true; | |||
%% nomatch -> false | |||
%% end. | |||
rm(Path) -> | |||
NativePath = filename:nativename(Path), | |||
Cmd = case os:type() of | |||
{unix, _} -> "rm -f "; | |||
{win32, _} -> "del /q " | |||
end, | |||
[] = os:cmd(Cmd ++ NativePath), | |||
ok. | |||
getTimeStr() -> | |||
{{Y, M, D}, {H, Min, S}} = erlang:localtime(), | |||
lists:flatten(io_lib:format("~B_~2.10.0B_~2.10.0B ~B:~2.10.0B:~2.10.0B", [Y, M, D, H, Min, S])). | |||
vcsInfo([]) -> | |||
"No VCS info available."; | |||
vcsInfo([{Id, Dir, VsnCmd, StatusCmd} | Rest]) -> | |||
case filelib:is_dir(Dir) of | |||
true -> | |||
Vsn = string:strip(os:cmd(VsnCmd), both, $\n), | |||
Status = | |||
case string:strip(os:cmd(StatusCmd), both, $\n) of | |||
[] -> | |||
""; | |||
_ -> | |||
"-dirty" | |||
end, | |||
lists:concat([Id, " ", Vsn, Status]); | |||
false -> | |||
vcsInfo(Rest) | |||
end. | |||
writeWindowsScripts() -> | |||
CmdScript = | |||
"@echo off\r\n" | |||
"setlocal\r\n" | |||
"set rebarscript=%~f0\r\n" | |||
"escript.exe \"%rebarscript:.cmd=%\" %*\r\n", | |||
ok = file:write_file("erlNpc.cmd", CmdScript). | |||
escriptize() -> | |||
AppName = "erlNpc", | |||
ScriptName = "erlNpc", | |||
Files = loadEScriptFiles(AppName, "ebin", "*"), | |||
{ok, {"mem", ZipBin}} = zip:create("mem", Files, [memory]), | |||
Shebang = "#!/usr/bin/env escript\n", | |||
Comment = "%%\n", | |||
EmuArgs = io_lib:format("%%! -pa ~s/~s/ebin\n", [AppName, AppName]), | |||
Script = iolist_to_binary([Shebang, Comment, EmuArgs, ZipBin]), | |||
ok = file:write_file(ScriptName, Script), | |||
%% Finally, update executable perms for our script | |||
{ok, #file_info{mode = Mode}} = file:read_file_info(ScriptName), | |||
ok = file:change_mode(ScriptName, Mode bor 8#00111). | |||
loadEScriptFiles(AppName, Path, WildCard) -> | |||
FileNames = filelib:wildcard(WildCard, Path), | |||
Entries = | |||
lists:flatmap( | |||
fun(FN) -> | |||
FPath = filename:join(Path, FN), | |||
{ok, Contents} = file:read_file(FPath), | |||
ZipPath = filename:join(AppName, FPath), | |||
[{ZipPath, Contents} | dirEntries(ZipPath)] | |||
end, | |||
FileNames), | |||
usort(Entries). | |||
%% Given a filename, return zip archive dir entries for each sub-dir. | |||
%% Required to work around issues fixed in OTP-10071. | |||
dirEntries(File) -> | |||
Dirs = dirs(File), | |||
[{Dir ++ "/", <<>>} || Dir <- Dirs]. | |||
%% Given "foo/bar/baz", return ["foo", "foo/bar", "foo/bar/baz"]. | |||
dirs(Dir) -> | |||
dirs1(filename:split(Dir), "", []). | |||
dirs1([], _, Acc) -> | |||
lists:reverse(Acc); | |||
dirs1([H | T], "", []) -> | |||
dirs1(T, H, [H]); | |||
dirs1([H | T], Last, Acc) -> | |||
Dir = filename:join(Last, H), | |||
dirs1(T, Dir, [Dir | Acc]). | |||
usort(List) -> | |||
lists:ukeysort(1, lists:flatten(List)). |
@ -0,0 +1,2 @@ | |||
@echo off | |||
escript.exe bootstrap %* |
@ -0,0 +1,9 @@ | |||
{application,erlNpc, | |||
[{description,"erlNpc: Erlang Native Compiler"}, | |||
{vsn,"0.1.0"}, | |||
{registered,[]}, | |||
{applications,[kernel,stdlib]}, | |||
{modules,[erlNpc,rebar,rebarConfig,rebarNpCompiler,rebarUtils]}, | |||
{licenses,["Apache 2.0"]}, | |||
{links,[]}, | |||
{env,[{log_level,warn}]}]}. |
@ -0,0 +1,9 @@ | |||
-define(FAIL, rebarUtils:abort()). | |||
-define(ABORT(Str, Args), rebarUtils:abort(Str, Args)). | |||
-define(INFO(Str, Args), rebar:log(info, Str, Args)). | |||
-define(WARN(Str, Args), rebar:log(warn, Str, Args)). | |||
-define(ERROR(Str, Args), rebar:log(error, Str, Args)). | |||
-define(CONSOLE(Str, Args), io:format(Str, Args)). | |||
-define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))). |
@ -0,0 +1,2 @@ | |||
{erl_opts, [debug_info]}. | |||
{deps, []}. |
@ -0,0 +1,31 @@ | |||
-module(erlNpc). | |||
-export([ | |||
main/1 | |||
]). | |||
main(Args) -> | |||
file:set_cwd("c_src"), | |||
{ok, Dir} = file:get_cwd(), | |||
io:format("erlNpc begin compile pwd:~p ~n", [Dir]), | |||
FunCom = | |||
fun(File) -> | |||
case filelib:is_dir(File) == true andalso lists:nth(1, File) =/= 46 andalso filename:basename(File) =/= "include" of | |||
true -> | |||
{ok, CurDir} = file:get_cwd(), | |||
io:format("erlNpc cur compile/clean: ~s, cur pwd:~p ~n", [File, CurDir]), | |||
file:set_cwd(File), | |||
rebar:main(Args), | |||
file:set_cwd(".."); | |||
_ -> | |||
ignore | |||
end | |||
end, | |||
case file:list_dir(".") of | |||
{ok, Files} -> | |||
lists:foreach(FunCom, Files); | |||
_Err -> | |||
rebar:log(error, "erlNpc start compile error ~p ~n", [_Err]) | |||
end. | |||
@ -0,0 +1,230 @@ | |||
-module(rebar). | |||
-export([ | |||
main/1, | |||
log/3 | |||
]). | |||
-include("rebar.hrl"). | |||
-ifndef(BUILD_TIME). | |||
-define(BUILD_TIME, "undefined"). | |||
-endif. | |||
-ifndef(VCS_INFO). | |||
-define(VCS_INFO, "undefined"). | |||
-endif. | |||
-ifndef(OTP_INFO). | |||
-define(OTP_INFO, "undefined"). | |||
-endif. | |||
-define(DEFAULT_JOBS, 3). | |||
main(Args) -> | |||
case catch (run(Args)) of | |||
ok -> | |||
ok; | |||
rebar_abort -> | |||
rebarUtils:delayedHalt(1); | |||
Error -> | |||
%% Nothing should percolate up from rebar_core; | |||
%% Dump this error to console | |||
io:format("Uncaught error in rebar_core: ~p\n", [Error]), | |||
rebarUtils:delayedHalt(1) | |||
end. | |||
log(Level, Format, Args) -> | |||
{ok, LimitLevel} = application:get_env(erlNpc, log_level), | |||
case levelInt(LimitLevel) >= levelInt(Level) of | |||
true -> | |||
io:format(destination(Level), Format, Args); | |||
false -> | |||
ok | |||
end. | |||
levelInt(info) -> 2; | |||
levelInt(warn) -> 1; | |||
levelInt(error) -> 0. | |||
destination(error) -> standard_error; | |||
destination(_) -> group_leader(). | |||
run(["help"]) -> | |||
usage(), | |||
help(compile); | |||
run(["help" | RawCommands]) -> | |||
lists:foreach(fun help/1, [list_to_atom(C) || C <- RawCommands]); | |||
run(["version"]) -> | |||
ok = loadRebarApp(), | |||
%% Display vsn and build time info | |||
version(); | |||
run(RawArgs) -> | |||
ok = loadRebarApp(), | |||
Args = parseArgs(RawArgs), | |||
BaseConfig = initConfig(Args), | |||
{BaseConfig1, Cmds} = saveOptions(BaseConfig, Args), | |||
runAux(BaseConfig1, Cmds). | |||
loadRebarApp() -> | |||
%% Pre-load the rebar app so that we get default configuration | |||
case application:load(erlNpc) of | |||
ok -> | |||
ok; | |||
{error, {already_loaded,erlNpc}} -> | |||
ok; | |||
_ -> | |||
rebarUtils:delayedHalt(1) | |||
end. | |||
help(compile) -> | |||
rebarNpCompiler:info(help, compile); | |||
help(clean) -> | |||
rebarNpCompiler:info(help, clean); | |||
help(Command) -> | |||
?CONSOLE("erlNpc no help available for \"~p\"~n", [Command]). | |||
parseArgs([]) -> | |||
{[], []}; | |||
parseArgs(["-h" | _]) -> | |||
usage(), | |||
help(compile), | |||
rebarUtils:delayedHalt(0); | |||
parseArgs(["--help" | _]) -> | |||
usage(), | |||
help(compile), | |||
rebarUtils:delayedHalt(0); | |||
parseArgs(["-v" | _]) -> | |||
version(), | |||
rebarUtils:delayedHalt(0); | |||
parseArgs(["--version" | _]) -> | |||
version(), | |||
rebarUtils:delayedHalt(0); | |||
parseArgs(["-c", FileName | Rest]) -> | |||
{Opts, NonOpts} = parseArgs(Rest), | |||
{[{config, FileName} | Opts], NonOpts}; | |||
parseArgs(["--config", FileName | Rest]) -> | |||
parseArgs(["-c", FileName | Rest]); | |||
parseArgs([NonOpt | Rest]) -> | |||
{Opts, NonOpts} = parseArgs(Rest), | |||
{Opts, [NonOpt | NonOpts]}. | |||
usage() -> | |||
?CONSOLE("erlNpc [-hv] [-c CONFIG_FILE] COMMAND [COMMAND ...]~n~n", []). | |||
initConfig({Options, _NonOptArgs}) -> | |||
%% If $HOME/.rebar/config exists load and use as global config | |||
GlobalConfigFile = filename:join([os:getenv("HOME"), ".rebar", "config"]), | |||
GlobalConfig = | |||
case filelib:is_regular(GlobalConfigFile) of | |||
true -> | |||
rebarConfig:new(GlobalConfigFile); | |||
false -> | |||
rebarConfig:new() | |||
end, | |||
%% Set the rebar config to use | |||
GlobalConfig1 = | |||
case proplists:get_value(config, Options) of | |||
undefined -> | |||
GlobalConfig; | |||
Conf -> | |||
rebarConfig:setGlobal(GlobalConfig, config, Conf) | |||
end, | |||
BaseConfig = rebarConfig:baseConfig(GlobalConfig1), | |||
%% Keep track of how many operations we do, so we can detect bad commands | |||
BaseConfig1 = rebarConfig:setXconf(BaseConfig, operations, 0), | |||
%% Initialize vsn cache | |||
rebarUtils:initVsnCache(BaseConfig1). | |||
initConfig_1(BaseConfig) -> | |||
%% Determine the location of the rebar executable; important for pulling | |||
%% resources out of the escript | |||
ScriptName = filename:absname(escript:script_name()), | |||
BaseConfig1 = rebarConfig:setXconf(BaseConfig, escript, ScriptName), | |||
%% Note the top-level directory for reference | |||
AbsCwd = filename:absname(rebarUtils:getCwd()), | |||
rebarConfig:setXconf(BaseConfig1, base_dir, AbsCwd). | |||
runAux(BaseConfig, Commands) -> | |||
%% Make sure crypto is running | |||
case crypto:start() of | |||
ok -> ok; | |||
{error, {already_started, crypto}} -> ok | |||
end, | |||
%% Convert command strings to atoms | |||
CommandAtoms = [list_to_atom(C) || C <- Commands], | |||
BaseConfig1 = initConfig_1(BaseConfig), | |||
%% Make sure we're an app directory | |||
AppFile = "", | |||
%% case rebarUtils:isAppDir() of | |||
%% {true, AppFile0} -> | |||
%% AppFile0; | |||
%% false -> | |||
%% rebarUtils:delayedHalt(1) | |||
%% end, | |||
% Setup our environment | |||
BaseConfig2 = setupEnvs(BaseConfig1, [rebarNpCompiler]), | |||
%% Process each command, resetting any state between each one | |||
lists:foreach( | |||
fun(Command) -> | |||
%processCommand(Command, BaseConfig2, AppFile)"" | |||
processCommand(Command, BaseConfig2, AppFile) | |||
end, CommandAtoms). | |||
setupEnvs(Config, Modules) -> | |||
lists:foldl( | |||
fun(Module, CfgAcc) -> | |||
Env = Module:setupEnv(CfgAcc), | |||
rebarConfig:saveEnv(CfgAcc, Module, Env) | |||
end, Config, Modules). | |||
processCommand(compile, Config, AppFile) -> | |||
rebarNpCompiler:compile(Config, AppFile); | |||
processCommand(clean, Config, AppFile) -> | |||
rebarNpCompiler:clean(Config, AppFile); | |||
processCommand(Other, _, _) -> | |||
?CONSOLE("Unknown command: ~s~n", [Other]), | |||
rebarUtils:delayedHalt(1). | |||
saveOptions(Config, {Options, NonOptArgs}) -> | |||
GlobalDefines = proplists:get_all_values(defines, Options), | |||
Config1 = rebarConfig:setXconf(Config, defines, GlobalDefines), | |||
filterFlags(Config1, NonOptArgs, []). | |||
%% show version information and halt | |||
version() -> | |||
{ok, Vsn} = application:get_key(erlNpc, vsn), | |||
?CONSOLE("erlNpc ~s ~s ~s ~s\n", [Vsn, ?OTP_INFO, ?BUILD_TIME, ?VCS_INFO]). | |||
%% Seperate all commands (single-words) from flags (key=value) and store | |||
%% values into the rebar_config global storage. | |||
filterFlags(Config, [], Commands) -> | |||
{Config, lists:reverse(Commands)}; | |||
filterFlags(Config, [Item | Rest], Commands) -> | |||
case string:tokens(Item, "=") of | |||
[Command] -> | |||
filterFlags(Config, Rest, [Command | Commands]); | |||
[KeyStr, RawValue] -> | |||
Key = list_to_atom(KeyStr), | |||
Value = | |||
case Key of | |||
verbose -> | |||
list_to_integer(RawValue); | |||
_ -> | |||
RawValue | |||
end, | |||
Config1 = rebarConfig:setGlobal(Config, Key, Value), | |||
filterFlags(Config1, Rest, Commands); | |||
Other -> | |||
?CONSOLE("Ignoring command line argument: ~p\n", [Other]), | |||
filterFlags(Config, Rest, Commands) | |||
end. |
@ -0,0 +1,211 @@ | |||
-module(rebarConfig). | |||
-include("rebar.hrl"). | |||
-export([ | |||
new/0, | |||
new/1, | |||
baseConfig/1, | |||
consultFile/1, | |||
get/3, | |||
getLocal/3, | |||
getList/3, | |||
setGlobal/3, | |||
getGlobal/3, | |||
saveEnv/3, | |||
getEnv/2, | |||
setXconf/3, | |||
getXconf/2, | |||
getXconf/3 | |||
]). | |||
-type key() :: atom(). | |||
-type rebarDict() :: dict:dict(term(), term()). | |||
-record(config, { | |||
dir :: file:filename(), | |||
opts = [] :: list(), | |||
globals = newGlobals() :: rebarDict(), | |||
envs = newEnv() :: rebarDict(), | |||
%% cross-directory/-command config | |||
skipDirs = newSkipDirs() :: rebarDict(), | |||
xconf = newXconf() :: rebarDict() | |||
}). | |||
-opaque config() :: #config{}. | |||
-export_type([config/0]). | |||
-define(DEFAULT_NAME, "rebar.config"). | |||
-spec baseConfig(config()) -> config(). | |||
baseConfig(GlobalConfig) -> | |||
ConfName = rebarConfig:getGlobal(GlobalConfig, config, ?DEFAULT_NAME), | |||
new(GlobalConfig, ConfName). | |||
-spec new() -> config(). | |||
new() -> | |||
#config{dir = rebarUtils:getCwd()}. | |||
-spec new(file:filename() | config()) -> config(). | |||
new(ConfigFile) when is_list(ConfigFile) -> | |||
case consultFile(ConfigFile) of | |||
{ok, Opts} -> | |||
#config{dir = rebarUtils:getCwd(), | |||
opts = Opts}; | |||
Other -> | |||
?ABORT("Failed to load ~s: ~p~n", [ConfigFile, Other]) | |||
end; | |||
new(#config{opts = Opts0, globals = Globals, skipDirs = SkipDirs, xconf = Xconf}) -> | |||
new(#config{opts = Opts0, globals = Globals, skipDirs = SkipDirs, xconf = Xconf}, | |||
?DEFAULT_NAME). | |||
-spec get(config(), key(), term()) -> term(). | |||
get(Config, Key, Default) -> | |||
proplists:get_value(Key, Config#config.opts, Default). | |||
-spec getList(config(), key(), term()) -> term(). | |||
getList(Config, Key, Default) -> | |||
get(Config, Key, Default). | |||
-spec getLocal(config(), key(), term()) -> term(). | |||
getLocal(Config, Key, Default) -> | |||
proplists:get_value(Key, localOpts(Config#config.opts, []), Default). | |||
-spec setGlobal(config(), key(), term()) -> config(). | |||
setGlobal(Config, jobs = Key, Value) when is_list(Value) -> | |||
setGlobal(Config, Key, list_to_integer(Value)); | |||
setGlobal(Config, jobs = Key, Value) when is_integer(Value) -> | |||
NewGlobals = dict:store(Key, erlang:max(1, Value), Config#config.globals), | |||
Config#config{globals = NewGlobals}; | |||
setGlobal(Config, Key, Value) -> | |||
NewGlobals = dict:store(Key, Value, Config#config.globals), | |||
Config#config{globals = NewGlobals}. | |||
-spec getGlobal(config(), key(), term()) -> term(). | |||
getGlobal(Config, Key, Default) -> | |||
case dict:find(Key, Config#config.globals) of | |||
error -> | |||
Default; | |||
{ok, Value} -> | |||
Value | |||
end. | |||
-spec consultFile(file:filename()) -> term(). | |||
consultFile(File) -> | |||
case filename:extension(File) of | |||
".script" -> | |||
consultAndEval(removeScriptExt(File), File); | |||
_ -> | |||
Script = File ++ ".script", | |||
case filelib:is_regular(Script) of | |||
true -> | |||
consultAndEval(File, Script); | |||
false -> | |||
file:consult(File) | |||
end | |||
end. | |||
-spec saveEnv(config(), module(), nonempty_list()) -> config(). | |||
saveEnv(Config, Mod, Env) -> | |||
NewEnvs = dict:store(Mod, Env, Config#config.envs), | |||
Config#config{envs = NewEnvs}. | |||
-spec getEnv(config(), module()) -> term(). | |||
getEnv(Config, Mod) -> | |||
dict:fetch(Mod, Config#config.envs). | |||
-spec setXconf(config(), term(), term()) -> config(). | |||
setXconf(Config, Key, Value) -> | |||
NewXconf = dict:store(Key, Value, Config#config.xconf), | |||
Config#config{xconf = NewXconf}. | |||
-spec getXconf(config(), term()) -> term(). | |||
getXconf(Config, Key) -> | |||
{ok, Value} = dict:find(Key, Config#config.xconf), | |||
Value. | |||
-spec getXconf(config(), term(), term()) -> term(). | |||
getXconf(Config, Key, Default) -> | |||
case dict:find(Key, Config#config.xconf) of | |||
error -> | |||
Default; | |||
{ok, Value} -> | |||
Value | |||
end. | |||
%% =================================================================== | |||
%% Internal functions | |||
%% =================================================================== | |||
-spec new(config(), file:filename()) -> config(). | |||
new(ParentConfig, ConfName) -> | |||
%% Load terms from rebar.config, if it exists | |||
Dir = rebarUtils:getCwd(), | |||
ConfigFile = filename:join([Dir, ConfName]), | |||
Opts0 = ParentConfig#config.opts, | |||
Opts = case consultFile(ConfigFile) of | |||
{ok, Terms} -> | |||
%% Found a config file with some terms. We need to | |||
%% be able to distinguish between local definitions | |||
%% (i.e. from the file in the cwd) and inherited | |||
%% definitions. To accomplish this, we use a marker | |||
%% in the proplist (since order matters) between | |||
%% the new and old defs. | |||
Terms ++ [local] ++ | |||
[Opt || Opt <- Opts0, Opt /= local]; | |||
{error, enoent} -> | |||
[local] ++ | |||
[Opt || Opt <- Opts0, Opt /= local]; | |||
Other -> | |||
?ABORT("Failed to load ~s: ~p\n", [ConfigFile, Other]) | |||
end, | |||
ParentConfig#config{dir = Dir, opts = Opts}. | |||
-spec consultAndEval(file:filename(), file:filename()) -> {ok, term()}. | |||
consultAndEval(File, Script) -> | |||
ConfigData = tryConsult(File), | |||
file:script(Script, bs([{'CONFIG', ConfigData}, {'SCRIPT', Script}])). | |||
-spec removeScriptExt(file:filename()) -> file:filename(). | |||
removeScriptExt(F) -> | |||
"tpircs." ++ Rev = lists:reverse(F), | |||
lists:reverse(Rev). | |||
-spec tryConsult(file:filename()) -> term(). | |||
tryConsult(File) -> | |||
case file:consult(File) of | |||
{ok, Terms} -> | |||
Terms; | |||
{error, enoent} -> | |||
[]; | |||
{error, Reason} -> | |||
?ABORT("Failed to read config file ~s: ~p~n", [File, Reason]) | |||
end. | |||
-type bs_vars() :: [{term(), term()}]. | |||
-spec bs(bs_vars()) -> bs_vars(). | |||
bs(Vars) -> | |||
lists:foldl(fun({K, V}, Bs) -> | |||
erl_eval:add_binding(K, V, Bs) | |||
end, erl_eval:new_bindings(), Vars). | |||
-spec localOpts(list(), list()) -> list(). | |||
localOpts([], Acc) -> | |||
lists:reverse(Acc); | |||
localOpts([local | _Rest], Acc) -> | |||
lists:reverse(Acc); | |||
localOpts([Item | Rest], Acc) -> | |||
localOpts(Rest, [Item | Acc]). | |||
-spec newGlobals() -> rebarDict(). | |||
newGlobals() -> dict:new(). | |||
-spec newEnv() -> rebarDict(). | |||
newEnv() -> dict:new(). | |||
-spec newSkipDirs() -> rebarDict(). | |||
newSkipDirs() -> dict:new(). | |||
-spec newXconf() -> rebarDict(). | |||
newXconf() -> dict:new(). |
@ -0,0 +1,740 @@ | |||
-module(rebarNpCompiler). | |||
-include("rebar.hrl"). | |||
-export([ | |||
compile/2, | |||
clean/2 | |||
]). | |||
%% for internal use only | |||
-export([ | |||
setupEnv/1, | |||
info/2 | |||
]). | |||
-record(spec, { | |||
type :: 'drv' | 'exe', | |||
link_lang :: 'cc' | 'cxx', | |||
target :: file:filename(), | |||
sources = [] :: [file:filename(), ...], | |||
objects = [] :: [file:filename(), ...], | |||
opts = [] :: list() | [] | |||
}). | |||
compile(Config, AppFile) -> | |||
case getSpecs(Config, AppFile) of | |||
[] -> | |||
ok; | |||
Specs -> | |||
SharedEnv = rebarConfig:getEnv(Config, ?MODULE), | |||
%% Compile each of the sources | |||
NewBins = compileSources(Config, Specs, SharedEnv), | |||
%% Make sure that the target directories exist | |||
?INFO("Using specs ~p\n", [Specs]), | |||
lists:foreach( | |||
fun(#spec{target = Target}) -> | |||
ok = filelib:ensure_dir(Target) | |||
end, Specs), | |||
%% Only relink if necessary, given the Target | |||
%% and list of new binaries | |||
lists:foreach( | |||
fun(#spec{target = Target, objects = Bins, opts = Opts, link_lang = LinkLang}) -> | |||
AllBins = [sets:from_list(Bins), sets:from_list(NewBins)], | |||
Intersection = sets:intersection(AllBins), | |||
case needsLink(Target, sets:to_list(Intersection)) of | |||
true -> | |||
LinkTemplate = selectLinkTemplate(LinkLang, Target), | |||
Env = proplists:get_value(env, Opts, SharedEnv), | |||
Cmd = expandCommand(LinkTemplate, Env, string:join(Bins, " "), Target), | |||
rebarUtils:sh(Cmd, [{env, Env}]); | |||
false -> | |||
?INFO("Skipping relink of ~s\n", [Target]), | |||
ok | |||
end | |||
end, Specs) | |||
end. | |||
clean(Config, AppFile) -> | |||
case getSpecs(Config, AppFile) of | |||
[] -> | |||
ok; | |||
Specs -> | |||
lists:foreach( | |||
fun(#spec{target = Target, objects = Objects}) -> | |||
rebarUtils:deleteEach([Target]), | |||
rebarUtils:deleteEach(Objects), | |||
rebarUtils:deleteEach(portDeps(Objects)) | |||
end, Specs) | |||
end, | |||
ok. | |||
setupEnv(Config) -> | |||
setupEnv(Config, defaultEnv(Config)). | |||
%% =================================================================== | |||
%% Internal functions | |||
%% =================================================================== | |||
info(help, compile) -> | |||
infoHelp("Build port sources"); | |||
info(help, clean) -> | |||
infoHelp("Delete port build results"). | |||
infoHelp(Description) -> | |||
?CONSOLE( | |||
"~s.~n" | |||
"~n" | |||
"Valid rebar.config options:~n" | |||
"port_specs - Erlang list of tuples of the forms~n" | |||
" {ArchRegex, TargetFile, Sources, Options}~n" | |||
" {ArchRegex, TargetFile, Sources}~n" | |||
" {TargetFile, Sources}~n" | |||
"~n" | |||
" Examples:~n" | |||
" ~p~n" | |||
"~n" | |||
"port_env - Erlang list of key/value pairs which will control~n" | |||
" the environment when running the compiler and linker.~n" | |||
" Variables set in the surrounding system shell are taken~n" | |||
" into consideration when expanding port_env.~n" | |||
"~n" | |||
" By default, the following variables are defined:~n" | |||
" CC - C compiler~n" | |||
" CXX - C++ compiler~n" | |||
" CFLAGS - C compiler~n" | |||
" CXXFLAGS - C++ compiler~n" | |||
" LDFLAGS - Link flags~n" | |||
" ERL_CFLAGS - default -I paths for erts and ei~n" | |||
" ERL_LDFLAGS - default -L and -lerl_interface -lei~n" | |||
" DRV_CFLAGS - flags that will be used for compiling~n" | |||
" DRV_LDFLAGS - flags that will be used for linking~n" | |||
" EXE_CFLAGS - flags that will be used for compiling~n" | |||
" EXE_LDFLAGS - flags that will be used for linking~n" | |||
" ERL_EI_LIBDIR - ei library directory~n" | |||
" DRV_CXX_TEMPLATE - C++ command template~n" | |||
" DRV_CC_TEMPLATE - C command template~n" | |||
" DRV_LINK_TEMPLATE - C Linker command template~n" | |||
" DRV_LINK_CXX_TEMPLATE - C++ Linker command template~n" | |||
" EXE_CXX_TEMPLATE - C++ command template~n" | |||
" EXE_CC_TEMPLATE - C command template~n" | |||
" EXE_LINK_TEMPLATE - C Linker command template~n" | |||
" EXE_LINK_CXX_TEMPLATE - C++ Linker command template~n" | |||
"~n" | |||
" Note that if you wish to extend (vs. replace) these variables,~n" | |||
" you MUST include a shell-style reference in your definition.~n" | |||
" e.g. to extend CFLAGS, do something like:~n" | |||
"~n" | |||
" {port_env, [{\"CFLAGS\", \"$CFLAGS -MyOtherOptions\"}]}~n" | |||
"~n" | |||
" It is also possible to specify platform specific options~n" | |||
" by specifying a triplet where the first string is a regex~n" | |||
" that is checked against Erlang's system architecture string.~n" | |||
" e.g. to specify a CFLAG that only applies to x86_64 on linux~n" | |||
" do:~n" | |||
" {port_env, [{\"x86_64.*-linux\", \"CFLAGS\",~n" | |||
" \"$CFLAGS -X86Options\"}]}~n" | |||
"~n" | |||
"Cross-arch environment variables to configure toolchain:~n" | |||
" REBAR_TARGET_ARCH to set the tool chain name to use~n" | |||
" REBAR_TARGET_ARCH_WORDSIZE (optional - " | |||
"if CC fails to determine word size)~n" | |||
" fallback word size is 32~n" | |||
" REBAR_TARGET_ARCH_VSN (optional - " | |||
"if a special version of CC/CXX is requested)~n", | |||
[ | |||
Description, | |||
{port_specs, [{"priv/so_name.so", ["c_src/*.c"]}, | |||
{"linux", "priv/hello_linux", ["c_src/hello_linux.c"]}, | |||
{"linux", "priv/hello_linux", ["c_src/*.c"], [{env, []}]}]} | |||
]). | |||
%% set REBAR_DEPS_DIR and ERL_LIBS environment variables | |||
defaultEnv(Config) -> | |||
BaseDir = rebarUtils:baseDir(Config), | |||
DepsDir0 = rebarConfig:getXconf(Config, deps_dir, "deps"), | |||
DepsDir = filename:dirname(filename:join([BaseDir, DepsDir0, "dummy"])), | |||
%% include rebar's DepsDir in ERL_LIBS | |||
Separator = case os:type() of | |||
{win32, nt} -> ";"; | |||
_ -> ":" | |||
end, | |||
ERL_LIBS = case os:getenv("ERL_LIBS") of | |||
false -> | |||
{"ERL_LIBS", DepsDir}; | |||
PrevValue -> | |||
{"ERL_LIBS", DepsDir ++ Separator ++ PrevValue} | |||
end, | |||
[ | |||
{"REBAR_DEPS_DIR", DepsDir}, | |||
ERL_LIBS | |||
]. | |||
setupEnv(Config, ExtraEnv) -> | |||
%% Extract environment values from the config (if specified) and | |||
%% merge with the default for this operating system. This enables | |||
%% max flexibility for users. | |||
DefaultEnv = filterEnv(defaultEnv(), []), | |||
%% Get any port-specific envs; use port_env first and then fallback | |||
%% to port_envs for compatibility | |||
RawPortEnv = rebarConfig:getList( | |||
Config, | |||
port_env, | |||
rebarConfig:getList(Config, port_envs, [])), | |||
PortEnv = filterEnv(RawPortEnv, []), | |||
Defines = getDefines(Config), | |||
OverrideEnv = Defines ++ PortEnv ++ filterEnv(ExtraEnv, []), | |||
RawEnv = applyDefaults(osEnv(), DefaultEnv) ++ OverrideEnv, | |||
expandVarsLoop(mergeEachVar(RawEnv, [])). | |||
getDefines(Config) -> | |||
RawDefines = rebarConfig:getXconf(Config, defines, []), | |||
Defines = string:join(["-D" ++ D || D <- RawDefines], " "), | |||
[{"ERL_CFLAGS", "$ERL_CFLAGS " ++ Defines}]. | |||
replaceExtension(File, NewExt) -> | |||
OldExt = filename:extension(File), | |||
replaceExtension(File, OldExt, NewExt). | |||
replaceExtension(File, OldExt, NewExt) -> | |||
filename:rootname(File, OldExt) ++ NewExt. | |||
%% | |||
%% == compile and link == | |||
%% | |||
compileSources(Config, Specs, SharedEnv) -> | |||
{NewBins, Db} = | |||
lists:foldl( | |||
fun(#spec{sources = Sources, type = Type, opts = Opts}, Acc) -> | |||
Env = proplists:get_value(env, Opts, SharedEnv), | |||
compileEach(Config, Sources, Type, Env, Acc) | |||
end, {[], []}, Specs), | |||
%% Rewrite clang compile commands database file only if something | |||
%% was compiled. | |||
case NewBins of | |||
[] -> | |||
ok; | |||
_ -> | |||
{ok, ClangDbFile} = file:open("compile_commands.json", [write]), | |||
ok = io:fwrite(ClangDbFile, "[~n", []), | |||
lists:foreach(fun(E) -> ok = io:fwrite(ClangDbFile, E, []) end, Db), | |||
ok = io:fwrite(ClangDbFile, "]~n", []), | |||
ok = file:close(ClangDbFile) | |||
end, | |||
NewBins. | |||
compileEach(_Config, [], _Type, _Env, {NewBins, CDB}) -> | |||
{lists:reverse(NewBins), lists:reverse(CDB)}; | |||
compileEach(Config, [Source | Rest], Type, Env, {NewBins, CDB}) -> | |||
Ext = filename:extension(Source), | |||
Bin = replaceExtension(Source, Ext, ".o"), | |||
Template = selectCompileTemplate(Type, compiler(Ext)), | |||
Cmd = expandCommand(Template, Env, Source, Bin), | |||
CDBEnt = cdbEntry(Source, Cmd, Rest), | |||
NewCDB = [CDBEnt | CDB], | |||
case needsCompile(Source, Bin) of | |||
true -> | |||
ShOpts = [{env, Env}, return_on_error, {use_stdout, false}], | |||
execCompiler(Config, Source, Cmd, ShOpts), | |||
compileEach(Config, Rest, Type, Env, {[Bin | NewBins], NewCDB}); | |||
false -> | |||
?INFO("Skipping ~s\n", [Source]), | |||
compileEach(Config, Rest, Type, Env, {NewBins, NewCDB}) | |||
end. | |||
%% Generate a clang compilation db entry for Src and Cmd | |||
cdbEntry(Src, Cmd, SrcRest) -> | |||
%% Omit all variables from cmd, and use that as cmd in | |||
%% CDB, because otherwise clang-* will complain about it. | |||
CDBCmd = string:join( | |||
lists:filter( | |||
fun("$" ++ _) -> false; | |||
(_) -> true | |||
end, | |||
string:tokens(Cmd, " ")), | |||
" "), | |||
Cwd = rebarUtils:getCwd(), | |||
%% If there are more source files, make sure we end the CDB entry | |||
%% with a comma. | |||
Sep = case SrcRest of | |||
[] -> "~n"; | |||
_ -> ",~n" | |||
end, | |||
%% CDB entry | |||
?FMT("{ \"file\" : ~p~n" | |||
", \"directory\" : ~p~n" | |||
", \"command\" : ~p~n" | |||
"}~s", | |||
[Src, Cwd, CDBCmd, Sep]). | |||
execCompiler(Config, Source, Cmd, ShOpts) -> | |||
case rebarUtils:sh(Cmd, ShOpts) of | |||
{error, {_RC, RawError}} -> | |||
AbsSource = | |||
case rebarUtils:processingBaseDir(Config) of | |||
true -> | |||
Source; | |||
false -> | |||
filename:absname(Source) | |||
end, | |||
?CONSOLE("Compiling ~s\n", [AbsSource]), | |||
Error = re:replace(RawError, Source, AbsSource, | |||
[{return, list}, global]), | |||
?CONSOLE("~s", [Error]), | |||
?FAIL; | |||
{ok, Output} -> | |||
?CONSOLE("Compiling ~s\n", [Source]), | |||
?CONSOLE("~s", [Output]) | |||
end. | |||
needsCompile(Source, Bin) -> | |||
needsLink(Bin, [Source | binDeps(Bin)]). | |||
%% NOTE: This relies on -MMD being passed to the compiler and returns an | |||
%% empty list if the .d file is not available. This means header deps are | |||
%% ignored on win32. | |||
binDeps(Bin) -> | |||
[DepFile] = portDeps([Bin]), | |||
case file:read_file(DepFile) of | |||
{ok, Deps} -> | |||
Ds = parseBinDeps(list_to_binary(Bin), Deps), | |||
Ds; | |||
{error, _Err} -> | |||
[] | |||
end. | |||
parseBinDeps(Bin, Deps) -> | |||
Sz = size(Bin), | |||
<<Bin:Sz/binary, ": ", X/binary>> = Deps, | |||
Ds = re:split(X, "\\s*\\\\\\R\\s*|\\s+", [{return, binary}]), | |||
[D || D <- Ds, D =/= <<>>]. | |||
needsLink(SoName, []) -> | |||
filelib:last_modified(SoName) == 0; | |||
needsLink(SoName, NewBins) -> | |||
MaxLastMod = lists:max([filelib:last_modified(B) || B <- NewBins]), | |||
case filelib:last_modified(SoName) of | |||
0 -> | |||
true; | |||
Other -> | |||
MaxLastMod >= Other | |||
end. | |||
%% | |||
%% == port_specs == | |||
%% | |||
getSpecs(Config, AppFile) -> | |||
Specs = | |||
case rebarConfig:getLocal(Config, port_specs, []) of | |||
[] -> | |||
%% No spec provided. Construct a spec | |||
%% from old-school so_name and sources | |||
[portSpecFromLegacy(Config, AppFile)]; | |||
PortSpecs -> | |||
Filtered = filterPortSpecs(PortSpecs), | |||
OsType = os:type(), | |||
[getPortSpec(Config, OsType, Spec) || Spec <- Filtered] | |||
end, | |||
[S || S <- Specs, S#spec.sources /= []]. | |||
portSpecFromLegacy(Config, AppFile) -> | |||
%% Get the target from the so_name variable | |||
Target = | |||
case rebarConfig:get(Config, so_name, undefined) of | |||
undefined -> | |||
%% Generate a sensible default from app file | |||
{_, AppName} = rebarUtils:appName(Config, AppFile), | |||
filename:join("priv", lists:concat([AppName, "_drv.so"])); | |||
AName -> | |||
%% Old form is available -- use it | |||
filename:join("priv", AName) | |||
end, | |||
%% Get the list of source files from port_sources | |||
Sources = portSources(rebarConfig:getList(Config, port_sources, ["c_src/*.c"])), | |||
#spec{ | |||
type = targetType(Target), | |||
link_lang = cc, | |||
target = maybeSwitchExtension(os:type(), Target), | |||
sources = Sources, | |||
objects = portObjects(Sources) | |||
}. | |||
filterPortSpecs(Specs) -> | |||
[S || S <- Specs, filterPortSpec(S)]. | |||
filterPortSpec({ArchRegex, _, _, _}) -> | |||
rebarUtils:isArch(ArchRegex); | |||
filterPortSpec({ArchRegex, _, _}) -> | |||
rebarUtils:isArch(ArchRegex); | |||
filterPortSpec({_, _}) -> | |||
true. | |||
getPortSpec(Config, OsType, {Target, Sources}) -> | |||
getPortSpec(Config, OsType, {undefined, Target, Sources, []}); | |||
getPortSpec(Config, OsType, {Arch, Target, Sources}) -> | |||
getPortSpec(Config, OsType, {Arch, Target, Sources, []}); | |||
getPortSpec(Config, OsType, {_Arch, Target, Sources, Opts}) -> | |||
SourceFiles = portSources(Sources), | |||
LinkLang = | |||
case lists:any( | |||
fun(Src) -> compiler(filename:extension(Src)) == "$CXX" end, | |||
SourceFiles) | |||
of | |||
true -> cxx; | |||
false -> cc | |||
end, | |||
ObjectFiles = portObjects(SourceFiles), | |||
#spec{ | |||
type = targetType(Target), | |||
target = maybeSwitchExtension(OsType, Target), | |||
link_lang = LinkLang, | |||
sources = SourceFiles, | |||
objects = ObjectFiles, | |||
opts = portOpts(Config, Opts) | |||
}. | |||
portSources(Sources) -> | |||
lists:flatmap(fun filelib:wildcard/1, Sources). | |||
portObjects(SourceFiles) -> | |||
[replaceExtension(O, ".o") || O <- SourceFiles]. | |||
portDeps(SourceFiles) -> | |||
[replaceExtension(O, ".d") || O <- SourceFiles]. | |||
portOpts(Config, Opts) -> | |||
[portOpt(Config, O) || O <- Opts]. | |||
portOpt(Config, {env, Env}) -> | |||
{env, setupEnv(Config, Env)}; | |||
portOpt(_Config, Opt) -> | |||
Opt. | |||
maybeSwitchExtension({win32, nt}, Target) -> | |||
switchToDllOrExe(Target); | |||
maybeSwitchExtension(_OsType, Target) -> | |||
Target. | |||
switchToDllOrExe(Target) -> | |||
case filename:extension(Target) of | |||
".so" -> filename:rootname(Target, ".so") ++ ".dll"; | |||
[] -> Target ++ ".exe"; | |||
_Other -> Target | |||
end. | |||
%% == port_env == | |||
%% Choose a compiler variable, based on a provided extension | |||
compiler(".cc") -> "$CXX"; | |||
compiler(".cp") -> "$CXX"; | |||
compiler(".cxx") -> "$CXX"; | |||
compiler(".cpp") -> "$CXX"; | |||
compiler(".CPP") -> "$CXX"; | |||
compiler(".c++") -> "$CXX"; | |||
compiler(".C") -> "$CXX"; | |||
compiler(_) -> "$CC". | |||
%% Given a list of {Key, Value} variables, and another list of default | |||
%% {Key, Value} variables, return a merged list where the rule is if the | |||
%% default is expandable expand it with the value of the variable list, | |||
%% otherwise just return the value of the variable. | |||
applyDefaults(Vars, Defaults) -> | |||
dict:to_list( | |||
dict:merge( | |||
fun(Key, VarValue, DefaultValue) -> | |||
case isExpandable(DefaultValue) of | |||
true -> | |||
rebarUtils:expandEnvVariable(DefaultValue, | |||
Key, | |||
VarValue); | |||
false -> VarValue | |||
end | |||
end, | |||
dict:from_list(Vars), | |||
dict:from_list(Defaults))). | |||
%% | |||
%% Given a list of {Key, Value} environment variables, where Key may be defined | |||
%% multiple times, walk the list and expand each self-reference so that we | |||
%% end with a list of each variable singly-defined. | |||
%% | |||
mergeEachVar([], Vars) -> | |||
Vars; | |||
mergeEachVar([{Key, Value} | Rest], Vars) -> | |||
Evalue = | |||
case orddict:find(Key, Vars) of | |||
error -> | |||
%% Nothing yet defined for this key/value. | |||
%% Expand any self-references as blank. | |||
rebarUtils:expandEnvVariable(Value, Key, ""); | |||
{ok, Value0} -> | |||
%% Use previous definition in expansion | |||
rebarUtils:expandEnvVariable(Value, Key, Value0) | |||
end, | |||
mergeEachVar(Rest, orddict:store(Key, Evalue, Vars)). | |||
%% | |||
%% Give a unique list of {Key, Value} environment variables, expand each one | |||
%% for every other key until no further expansions are possible. | |||
%% | |||
expandVarsLoop(Vars) -> | |||
expandVarsLoop(Vars, [], dict:from_list(Vars), 10). | |||
expandVarsLoop(_Pending, _Recurse, _Vars, 0) -> | |||
?ABORT("Max. expansion reached for ENV vars!\n", []); | |||
expandVarsLoop([], [], Vars, _Count) -> | |||
lists:keysort(1, dict:to_list(Vars)); | |||
expandVarsLoop([], Recurse, Vars, Count) -> | |||
expandVarsLoop(Recurse, [], Vars, Count - 1); | |||
expandVarsLoop([{K, V} | Rest], Recurse, Vars, Count) -> | |||
%% Identify the variables that need expansion in this value | |||
ReOpts = [global, {capture, all_but_first, list}, unicode], | |||
case re:run(V, "\\\${?(\\w+)}?", ReOpts) of | |||
{match, Matches} -> | |||
%% Identify the unique variables that need to be expanded | |||
UniqueMatches = lists:usort([M || [M] <- Matches]), | |||
%% For each variable, expand it and return the final | |||
%% value. Note that if we have a bunch of unresolvable | |||
%% variables, nothing happens and we don't bother | |||
%% attempting further expansion | |||
case expandKeysInValue(UniqueMatches, V, Vars) of | |||
V -> | |||
%% No change after expansion; move along | |||
expandVarsLoop(Rest, Recurse, Vars, Count); | |||
Expanded -> | |||
%% Some expansion occurred; move to next k/v but | |||
%% revisit this value in the next loop to check | |||
%% for further expansion | |||
NewVars = dict:store(K, Expanded, Vars), | |||
expandVarsLoop(Rest, [{K, Expanded} | Recurse], | |||
NewVars, Count) | |||
end; | |||
nomatch -> | |||
%% No values in this variable need expansion; move along | |||
expandVarsLoop(Rest, Recurse, Vars, Count) | |||
end. | |||
expandKeysInValue([], Value, _Vars) -> | |||
Value; | |||
expandKeysInValue([Key | Rest], Value, Vars) -> | |||
NewValue = | |||
case dict:find(Key, Vars) of | |||
{ok, KValue} -> | |||
rebarUtils:expandEnvVariable(Value, Key, KValue); | |||
error -> | |||
Value | |||
end, | |||
expandKeysInValue(Rest, NewValue, Vars). | |||
expandCommand(TmplName, Env, InFiles, OutFile) -> | |||
Cmd0 = proplists:get_value(TmplName, Env), | |||
Cmd1 = rebarUtils:expandEnvVariable(Cmd0, "PORT_IN_FILES", InFiles), | |||
rebarUtils:expandEnvVariable(Cmd1, "PORT_OUT_FILE", OutFile). | |||
%% | |||
%% Given a string, determine if it is expandable | |||
%% | |||
isExpandable(InStr) -> | |||
case re:run(InStr, "\\\$", [{capture, none}]) of | |||
match -> true; | |||
nomatch -> false | |||
end. | |||
%% | |||
%% Filter a list of env vars such that only those which match the provided | |||
%% architecture regex (or do not have a regex) are returned. | |||
%% | |||
filterEnv([], Acc) -> | |||
lists:reverse(Acc); | |||
filterEnv([{ArchRegex, Key, Value} | Rest], Acc) -> | |||
case rebarUtils:isArch(ArchRegex) of | |||
true -> | |||
filterEnv(Rest, [{Key, Value} | Acc]); | |||
false -> | |||
filterEnv(Rest, Acc) | |||
end; | |||
filterEnv([{Key, Value} | Rest], Acc) -> | |||
filterEnv(Rest, [{Key, Value} | Acc]). | |||
ertsDir() -> | |||
lists:concat([code:root_dir(), "/erts-", erlang:system_info(version)]). | |||
osEnv() -> | |||
ReOpts = [{return, list}, {parts, 2}, unicode], | |||
Os = [list_to_tuple(re:split(S, "=", ReOpts)) || | |||
S <- lists:filter(fun discardDepsVars/1, os:getenv())], | |||
%% Drop variables without a name (win32) | |||
[T1 || {K, _V} = T1 <- Os, K =/= []]. | |||
%% | |||
%% To avoid having multiple repetitions of the same environment variables | |||
%% (ERL_LIBS), avoid exporting any variables that may cause conflict with | |||
%% those exported by the rebar_deps module (ERL_LIBS, REBAR_DEPS_DIR) | |||
%% | |||
discardDepsVars("ERL_LIBS=" ++ _Value) -> false; | |||
discardDepsVars("REBAR_DEPS_DIR=" ++ _Value) -> false; | |||
discardDepsVars(_Var) -> true. | |||
selectCompileTemplate(drv, Compiler) -> | |||
selectCompileDrvTemplate(Compiler); | |||
selectCompileTemplate(exe, Compiler) -> | |||
selectCompileExeTemplate(Compiler). | |||
selectCompileDrvTemplate("$CC") -> "DRV_CC_TEMPLATE"; | |||
selectCompileDrvTemplate("$CXX") -> "DRV_CXX_TEMPLATE". | |||
selectCompileExeTemplate("$CC") -> "EXE_CC_TEMPLATE"; | |||
selectCompileExeTemplate("$CXX") -> "EXE_CXX_TEMPLATE". | |||
selectLinkTemplate(LinkLang, Target) -> | |||
case {LinkLang, targetType(Target)} of | |||
{cc, drv} -> "DRV_LINK_TEMPLATE"; | |||
{cxx, drv} -> "DRV_LINK_CXX_TEMPLATE"; | |||
{cc, exe} -> "EXE_LINK_TEMPLATE"; | |||
{cxx, exe} -> "EXE_LINK_CXX_TEMPLATE" | |||
end. | |||
targetType(Target) -> targetType_1(filename:extension(Target)). | |||
targetType_1(".so") -> drv; | |||
targetType_1(".dll") -> drv; | |||
targetType_1("") -> exe; | |||
targetType_1(".exe") -> exe. | |||
erlInterfaceDir(Subdir) -> | |||
case code:lib_dir(erl_interface, Subdir) of | |||
{error, bad_name} -> | |||
throw({error, {erl_interface, Subdir, "code:lib_dir(erl_interface)" | |||
"is unable to find the erl_interface library."}}); | |||
Dir -> Dir | |||
end. | |||
defaultEnv() -> | |||
Arch = os:getenv("REBAR_TARGET_ARCH"), | |||
Vsn = os:getenv("REBAR_TARGET_ARCH_VSN"), | |||
[ | |||
{"CC", getTool(Arch, Vsn, "gcc", "cc")}, | |||
{"CXX", getTool(Arch, Vsn, "g++", "c++")}, | |||
{"AR", getTool(Arch, "ar", "ar")}, | |||
{"AS", getTool(Arch, "as", "as")}, | |||
{"CPP", getTool(Arch, Vsn, "cpp", "cpp")}, | |||
{"LD", getTool(Arch, "ld", "ld")}, | |||
{"RANLIB", getTool(Arch, Vsn, "ranlib", "ranlib")}, | |||
{"STRIP", getTool(Arch, "strip", "strip")}, | |||
{"NM", getTool(Arch, "nm", "nm")}, | |||
{"OBJCOPY", getTool(Arch, "objcopy", "objcopy")}, | |||
{"OBJDUMP", getTool(Arch, "objdump", "objdump")}, | |||
{"DRV_CXX_TEMPLATE", | |||
"$CXX -c $CXXFLAGS $DRV_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE"}, | |||
{"DRV_CC_TEMPLATE", | |||
"$CC -c $CFLAGS $DRV_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE"}, | |||
{"DRV_LINK_TEMPLATE", | |||
"$CC $PORT_IN_FILES $LDFLAGS $DRV_LDFLAGS -o $PORT_OUT_FILE"}, | |||
{"DRV_LINK_CXX_TEMPLATE", | |||
"$CXX $PORT_IN_FILES $LDFLAGS $DRV_LDFLAGS -o $PORT_OUT_FILE"}, | |||
{"EXE_CXX_TEMPLATE", | |||
"$CXX -c $CXXFLAGS $EXE_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE"}, | |||
{"EXE_CC_TEMPLATE", | |||
"$CC -c $CFLAGS $EXE_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE"}, | |||
{"EXE_LINK_TEMPLATE", | |||
"$CC $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS -o $PORT_OUT_FILE"}, | |||
{"EXE_LINK_CXX_TEMPLATE", | |||
"$CXX $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS -o $PORT_OUT_FILE"}, | |||
{"DRV_CFLAGS", "-g -Wall -fPIC -MMD $ERL_CFLAGS"}, | |||
{"DRV_LDFLAGS", "-shared $ERL_LDFLAGS"}, | |||
{"EXE_CFLAGS", "-g -Wall -fPIC -MMD $ERL_CFLAGS"}, | |||
{"EXE_LDFLAGS", "$ERL_LDFLAGS"}, | |||
{"ERL_CFLAGS", lists:concat( | |||
[ | |||
" -I\"", erlInterfaceDir(include), | |||
"\" -I\"", filename:join(ertsDir(), "include"), | |||
"\" " | |||
])}, | |||
{"ERL_EI_LIBDIR", lists:concat(["\"", erlInterfaceDir(lib), "\""])}, | |||
{"ERL_LDFLAGS", " -L$ERL_EI_LIBDIR -lerl_interface -lei"}, | |||
{"ERLANG_ARCH", rebarUtils:wordsize()}, | |||
{"ERLANG_TARGET", rebarUtils:getArch()}, | |||
{"darwin", "DRV_LDFLAGS", | |||
"-bundle -flat_namespace -undefined suppress $ERL_LDFLAGS"}, | |||
%% Solaris specific flags | |||
{"solaris.*-64$", "CFLAGS", "-D_REENTRANT -m64 $CFLAGS"}, | |||
{"solaris.*-64$", "CXXFLAGS", "-D_REENTRANT -m64 $CXXFLAGS"}, | |||
{"solaris.*-64$", "LDFLAGS", "-m64 $LDFLAGS"}, | |||
%% OS X Leopard flags for 64-bit | |||
{"darwin9.*-64$", "CFLAGS", "-m64 $CFLAGS"}, | |||
{"darwin9.*-64$", "CXXFLAGS", "-m64 $CXXFLAGS"}, | |||
{"darwin9.*-64$", "LDFLAGS", "-arch x86_64 -flat_namespace -undefined suppress $LDFLAGS"}, | |||
%% OS X Lion onwards flags for 64-bit | |||
{"darwin1[0-4].*-64$", "CFLAGS", "-m64 $CFLAGS"}, | |||
{"darwin1[0-4].*-64$", "CXXFLAGS", "-m64 $CXXFLAGS"}, | |||
{"darwin1[0-4].*-64$", "LDFLAGS", "-arch x86_64 -flat_namespace -undefined suppress $LDFLAGS"}, | |||
%% OS X Snow Leopard, Lion, and Mountain Lion flags for 32-bit | |||
{"darwin1[0-2].*-32", "CFLAGS", "-m32 $CFLAGS"}, | |||
{"darwin1[0-2].*-32", "CXXFLAGS", "-m32 $CXXFLAGS"}, | |||
{"darwin1[0-2].*-32", "LDFLAGS", "-arch i386 -flat_namespace -undefined suppress $LDFLAGS"}, | |||
%% Windows specific flags | |||
%% add MS Visual C++ support to rebar on Windows | |||
{"win32", "CC", "cl.exe"}, | |||
{"win32", "CXX", "cl.exe"}, | |||
{"win32", "LINKER", "link.exe"}, | |||
{"win32", "DRV_CXX_TEMPLATE", | |||
%% DRV_* and EXE_* Templates are identical | |||
"$CXX /c $CXXFLAGS $DRV_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"}, | |||
{"win32", "DRV_CC_TEMPLATE", | |||
"$CC /c $CFLAGS $DRV_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"}, | |||
{"win32", "DRV_LINK_TEMPLATE", | |||
"$LINKER $PORT_IN_FILES $LDFLAGS $DRV_LDFLAGS /OUT:$PORT_OUT_FILE"}, | |||
{"win32", "DRV_LINK_CXX_TEMPLATE", | |||
"$LINKER $PORT_IN_FILES $LDFLAGS $DRV_LDFLAGS /OUT:$PORT_OUT_FILE"}, | |||
%% DRV_* and EXE_* Templates are identical | |||
{"win32", "EXE_CXX_TEMPLATE", | |||
"$CXX /c $CXXFLAGS $EXE_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"}, | |||
{"win32", "EXE_CC_TEMPLATE", | |||
"$CC /c $CFLAGS $EXE_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"}, | |||
{"win32", "EXE_LINK_TEMPLATE", | |||
"$LINKER $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS /OUT:$PORT_OUT_FILE"}, | |||
{"win32", "EXE_LINK_CXX_TEMPLATE", | |||
"$LINKER $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS /OUT:$PORT_OUT_FILE"}, | |||
%% ERL_CFLAGS are ok as -I even though strictly it should be /I | |||
{"win32", "ERL_LDFLAGS", | |||
" /LIBPATH:$ERL_EI_LIBDIR erl_interface.lib ei.lib"}, | |||
{"win32", "DRV_CFLAGS", "/Zi /Wall $ERL_CFLAGS"}, | |||
{"win32", "DRV_LDFLAGS", "/DLL $ERL_LDFLAGS"}, | |||
%% Provide some default Windows defines for convenience | |||
{"win32", "CFLAGS", "/Wall /DWIN32 /D_WINDOWS /D_WIN32 /DWINDOWS /Ic_src $CFLAGS"}, | |||
{"win32", "CXXFLAGS", "/Wall /DWIN32 /D_WINDOWS /D_WIN32 /DWINDOWS /Ic_src $CXXFLAGS"} | |||
]. | |||
getTool(Arch, Tool, Default) -> | |||
getTool(Arch, false, Tool, Default). | |||
getTool(false, _, _, Default) -> Default; | |||
getTool("", _, _, Default) -> Default; | |||
getTool(Arch, false, Tool, _Default) -> Arch ++ "-" ++ Tool; | |||
getTool(Arch, "", Tool, _Default) -> Arch ++ "-" ++ Tool; | |||
getTool(Arch, Vsn, Tool, _Default) -> Arch ++ "-" ++ Tool ++ "-" ++ Vsn. |
@ -0,0 +1,520 @@ | |||
-module(rebarUtils). | |||
-include("rebar.hrl"). | |||
-export([ | |||
getCwd/0, | |||
isArch/1, | |||
getArch/0, | |||
isAppDir/0, | |||
appName/2, | |||
wordsize/0, | |||
sh/2, | |||
abort/0, | |||
abort/2, | |||
expandEnvVariable/3, | |||
delayedHalt/1, | |||
baseDir/1, | |||
processingBaseDir/1, | |||
initVsnCache/1, | |||
deleteEach/1 | |||
]). | |||
getCwd() -> | |||
{ok, Dir} = file:get_cwd(), | |||
filename:join([Dir]). | |||
isArch(ArchRegex) -> | |||
case re:run(getArch(), ArchRegex, [{capture, none}]) of | |||
match -> | |||
true; | |||
nomatch -> | |||
false | |||
end. | |||
%% REBAR_TARGET_ARCH, if used, should be set to the "standard" | |||
%% target string. That is a prefix for binutils tools. | |||
%% "x86_64-linux-gnu" or "arm-linux-gnueabi" are good candidates | |||
%% ${REBAR_TARGET_ARCH}-gcc, ${REBAR_TARGET_ARCH}-ld ... | |||
getArch() -> | |||
Arch = os:getenv("REBAR_TARGET_ARCH"), | |||
Words = wordSize(Arch), | |||
otpRelease() ++ "-" ++ get_system_arch(Arch) ++ "-" ++ Words. | |||
get_system_arch(Arch) when Arch =:= false; Arch =:= "" -> | |||
erlang:system_info(system_architecture); | |||
get_system_arch(Arch) -> | |||
Arch. | |||
isAppDir() -> | |||
isAppDir(rebarUtils:getCwd()). | |||
isAppDir(Dir) -> | |||
SrcDir = filename:join([Dir, "src"]), | |||
AppSrcScript = filename:join([SrcDir, "*.app.src.script"]), | |||
AppSrc = filename:join([SrcDir, "*.app.src"]), | |||
case {filelib:wildcard(AppSrcScript), filelib:wildcard(AppSrc)} of | |||
{[AppSrcScriptFile], _} -> | |||
{true, AppSrcScriptFile}; | |||
{[], [AppSrcFile]} -> | |||
{true, AppSrcFile}; | |||
{[], []} -> | |||
EbinDir = filename:join([Dir, "ebin"]), | |||
App = filename:join([EbinDir, "*.app"]), | |||
case filelib:wildcard(App) of | |||
[AppFile] -> | |||
{true, AppFile}; | |||
[] -> | |||
false; | |||
_ -> | |||
?ERROR("More than one .app file in ~s~n", [EbinDir]), | |||
false | |||
end; | |||
{_, _} -> | |||
?ERROR("More than one .app.src file in ~s~n", [SrcDir]), | |||
false | |||
end. | |||
appName(Config, AppFile) -> | |||
case loadAppFile(Config, AppFile) of | |||
{ok, NewConfig, AppName, _} -> | |||
{NewConfig, AppName}; | |||
{error, Reason} -> | |||
?ABORT("Failed to extract name from ~s: ~p\n", | |||
[AppFile, Reason]) | |||
end. | |||
loadAppFile(Config, Filename) -> | |||
AppFile = {app_file, Filename}, | |||
case rebarConfig:getXconf(Config, {appfile, AppFile}, undefined) of | |||
undefined -> | |||
case consultAppFile(Filename) of | |||
{ok, {application, AppName, AppData}} -> | |||
Config1 = rebarConfig:setXconf(Config, | |||
{appfile, AppFile}, | |||
{AppName, AppData}), | |||
{ok, Config1, AppName, AppData}; | |||
{error, _} = Error -> | |||
{error, {error, Error}}; | |||
Other -> | |||
{error, {unexpected_terms, Other}} | |||
end; | |||
{AppName, AppData} -> | |||
{ok, Config, AppName, AppData} | |||
end. | |||
consultAppFile(Filename) -> | |||
Result = case lists:suffix(".app", Filename) of | |||
true -> | |||
file:consult(Filename); | |||
false -> | |||
rebarConfig:consultFile(Filename) | |||
end, | |||
case Result of | |||
{ok, [Term]} -> | |||
{ok, Term}; | |||
_ -> | |||
Result | |||
end. | |||
wordsize() -> | |||
wordSize(os:getenv("REBAR_TARGET_ARCH")). | |||
%% | |||
%% Options = [Option] -- defaults to [use_stdout, abort_on_error] | |||
%% Option = ErrorOption | OutputOption | {cd, string()} | {env, Env} | |||
%% ErrorOption = return_on_error | abort_on_error | {abort_on_error, string()} | |||
%% OutputOption = use_stdout | {use_stdout, bool()} | |||
%% Env = [{string(), Val}] | |||
%% Val = string() | false | |||
%% | |||
sh(Command0, Options0) -> | |||
?INFO("sh info:\n\tcwd: ~p\n\tcmd: ~s\n", [getCwd(), Command0]), | |||
DefaultOptions = [use_stdout, abort_on_error], | |||
Options = [expandShFlag(V) | |||
|| V <- proplists:compact(Options0 ++ DefaultOptions)], | |||
ErrorHandler = proplists:get_value(error_handler, Options), | |||
OutputHandler = proplists:get_value(output_handler, Options), | |||
Command = patchOnWindows(Command0, proplists:get_value(env, Options, [])), | |||
PortSettings = proplists:get_all_values(port_settings, Options) ++ | |||
[exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide], | |||
Port = open_port({spawn, Command}, PortSettings), | |||
case sh_loop(Port, OutputHandler, []) of | |||
{ok, _Output} = Ok -> | |||
Ok; | |||
{error, {_Rc, _Output} = Err} -> | |||
ErrorHandler(Command, Err) | |||
end. | |||
-spec abort() -> no_return(). | |||
abort() -> | |||
throw(rebar_abort). | |||
-spec abort(string(), [term()]) -> no_return(). | |||
abort(String, Args) -> | |||
?ERROR(String, Args), | |||
abort(). | |||
%% | |||
%% Given env. variable FOO we want to expand all references to | |||
%% it in InStr. References can have two forms: $FOO and ${FOO} | |||
%% The end of form $FOO is delimited with whitespace or eol | |||
%% | |||
expandEnvVariable(InStr, VarName, RawVarValue) -> | |||
case string:chr(InStr, $$) of | |||
0 -> | |||
%% No variables to expand | |||
InStr; | |||
_ -> | |||
ReOpts = [global, unicode, {return, list}], | |||
VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", ReOpts), | |||
%% Use a regex to match/replace: | |||
%% Given variable "FOO", match $FOO\W | $FOOeol | ${FOO}. | |||
RegEx = io_lib:format("\\\$(~s(\\W|$)|{~s})", [VarName, VarName]), | |||
re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts) | |||
end. | |||
initVsnCache(Config) -> | |||
initVsnCache(Config, os:getenv("REBAR_VSN_CACHE_FILE")). | |||
initVsnCache(Config, false) -> | |||
rebarConfig:setXconf(Config, vsn_cache, dict:new()); | |||
initVsnCache(Config, CacheFile) -> | |||
{ok, CacheList} = file:consult(CacheFile), | |||
CacheDict = dict:from_list(CacheList), | |||
rebarConfig:setXconf(Config, vsn_cache, CacheDict). | |||
-spec delayedHalt(integer()) -> no_return(). | |||
delayedHalt(Code) -> | |||
%% Work around buffer flushing issue in erlang:halt if OTP older | |||
%% than R15B01. | |||
%% TODO: remove workaround once we require R15B01 or newer | |||
%% R15B01 introduced erlang:halt/2 | |||
case erlang:is_builtin(erlang, halt, 2) of | |||
true -> | |||
halt(Code); | |||
false -> | |||
case os:type() of | |||
{win32, nt} -> | |||
timer:sleep(100), | |||
halt(Code); | |||
_ -> | |||
halt(Code), | |||
%% workaround to delay exit until all output is written | |||
receive after infinity -> ok end | |||
end | |||
end. | |||
deleteEach([]) -> | |||
ok; | |||
deleteEach([File | Rest]) -> | |||
case file:delete(File) of | |||
ok -> | |||
deleteEach(Rest); | |||
{error, enoent} -> | |||
deleteEach(Rest); | |||
{error, Reason} -> | |||
?ERROR("Failed to delete file ~s: ~p\n", [File, Reason]), | |||
?FAIL | |||
end. | |||
baseDir(Config) -> | |||
rebarConfig:getXconf(Config, base_dir). | |||
processingBaseDir(Config) -> | |||
Cwd = rebarUtils:getCwd(), | |||
processingBaseDir(Config, Cwd). | |||
processingBaseDir(Config, Dir) -> | |||
AbsDir = filename:absname(Dir), | |||
AbsDir =:= baseDir(Config). | |||
otpRelease() -> | |||
case application:get_env(erlNpc, memoized_otp_release) of | |||
{ok, Return} -> | |||
Return; | |||
undefined -> | |||
Return = otpRelease_1(erlang:system_info(otp_release)), | |||
application:set_env(erlNpc, memoized_otp_release, Return), | |||
Return | |||
end. | |||
%% If OTP <= R16, otp_release is already what we want. | |||
otpRelease_1([$R, N | _] = Rel) when is_integer(N) -> | |||
Rel; | |||
%% If OTP >= 17.x, erlang:system_info(otp_release) returns just the | |||
%% major version number, we have to read the full version from | |||
%% a file. See http://www.erlang.org/doc/system_principles/versions.html | |||
otpRelease_1(Rel) -> | |||
Files = [ | |||
filename:join([code:root_dir(), "releases", Rel, "OTP_VERSION"]), | |||
filename:join([code:root_dir(), "OTP_VERSION"]) | |||
], | |||
%% It's possible that none of the above files exist on the filesystem, in | |||
%% which case, we're just going to rely on the provided "Rel" (which should | |||
%% just be the value of `erlang:system_info(otp_release)`). | |||
case readOtpVersionFiles(Files) of | |||
undefined -> | |||
warnMissingOtpVersionFile(Rel), | |||
Rel; | |||
Vsn -> | |||
Vsn | |||
end. | |||
warnMissingOtpVersionFile(Rel) -> | |||
?WARN("No OTP_VERSION file found. Using version string ~p.~n", [Rel]). | |||
%% Try to open each file path provided, and if any of them exist on the | |||
%% filesystem, read their contents and return the value of the first one found. | |||
readOtpVersionFiles([]) -> | |||
undefined; | |||
readOtpVersionFiles([File | Rest]) -> | |||
case file:read_file(File) of | |||
{ok, Vsn} -> normalizeOtpVersion(Vsn); | |||
{error, enoent} -> readOtpVersionFiles(Rest) | |||
end. | |||
%% Takes the Version binary as read from the OTP_VERSION file and strips any | |||
%% trailing "**" and trailing "\n", returning the string as a list. | |||
normalizeOtpVersion(Vsn) -> | |||
%% It's fine to rely on the binary module here because we can | |||
%% be sure that it's available when the otp_release string does | |||
%% not begin with $R. | |||
Size = byte_size(Vsn), | |||
%% The shortest vsn string consists of at least two digits | |||
%% followed by "\n". Therefore, it's safe to assume Size >= 3. | |||
case binary:part(Vsn, {Size, -3}) of | |||
<<"**\n">> -> | |||
%% The OTP documentation mentions that a system patched | |||
%% using the otp_patch_apply tool available to licensed | |||
%% customers will leave a '**' suffix in the version as a | |||
%% flag saying the system consists of application versions | |||
%% from multiple OTP versions. We ignore this flag and | |||
%% drop the suffix, given for all intents and purposes, we | |||
%% cannot obtain relevant information from it as far as | |||
%% tooling is concerned. | |||
binary:bin_to_list(Vsn, {0, Size - 3}); | |||
_ -> | |||
binary:bin_to_list(Vsn, {0, Size - 1}) | |||
end. | |||
%% We do the shell variable substitution ourselves on Windows and hope that the | |||
%% command doesn't use any other shell magic. | |||
patchOnWindows(Cmd, Env) -> | |||
case os:type() of | |||
{win32, nt} -> | |||
Cmd1 = "cmd /q /c " | |||
++ lists:foldl(fun({Key, Value}, Acc) -> | |||
expandEnvVariable(Acc, Key, Value) | |||
end, Cmd, Env), | |||
%% Remove left-over vars | |||
re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "", | |||
[global, {return, list}]); | |||
_ -> | |||
Cmd | |||
end. | |||
expandShFlag(return_on_error) -> | |||
{error_handler, | |||
fun(_Command, Err) -> | |||
{error, Err} | |||
end}; | |||
expandShFlag({abort_on_error, Message}) -> | |||
{error_handler, | |||
logMsgAndAbort(Message)}; | |||
expandShFlag(abort_on_error) -> | |||
{error_handler, | |||
fun logAndAbort/2}; | |||
expandShFlag(use_stdout) -> | |||
{output_handler, | |||
fun(Line, Acc) -> | |||
?CONSOLE("~s", [Line]), | |||
[Line | Acc] | |||
end}; | |||
expandShFlag({use_stdout, false}) -> | |||
{output_handler, | |||
fun(Line, Acc) -> | |||
[Line | Acc] | |||
end}; | |||
expandShFlag({cd, _CdArg} = Cd) -> | |||
{port_settings, Cd}; | |||
expandShFlag({env, _EnvArg} = Env) -> | |||
{port_settings, Env}. | |||
-type err_handler() :: fun((string(), {integer(), string()}) -> no_return()). | |||
-spec logMsgAndAbort(string()) -> err_handler(). | |||
logMsgAndAbort(Message) -> | |||
fun(_Command, {_Rc, _Output}) -> | |||
?ABORT(Message, []) | |||
end. | |||
-spec logAndAbort(string(), {integer(), string()}) -> no_return(). | |||
logAndAbort(Command, {Rc, Output}) -> | |||
?ABORT("sh(~s)~n" | |||
"failed with return code ~w and the following output:~n" | |||
"~s~n", [Command, Rc, Output]). | |||
sh_loop(Port, Fun, Acc) -> | |||
receive | |||
{Port, {data, {eol, Line}}} -> | |||
sh_loop(Port, Fun, Fun(Line ++ "\n", Acc)); | |||
{Port, {data, {noeol, Line}}} -> | |||
sh_loop(Port, Fun, Fun(Line, Acc)); | |||
{Port, {exit_status, 0}} -> | |||
{ok, lists:flatten(lists:reverse(Acc))}; | |||
{Port, {exit_status, Rc}} -> | |||
{error, {Rc, lists:flatten(lists:reverse(Acc))}} | |||
end. | |||
wordSize(Arch) when Arch =:= false; Arch =:= "" -> | |||
nativeWordsize(); | |||
wordSize(Arch) -> | |||
AllArchs = [ | |||
{"i686", "32"}, | |||
{"i386", "32"}, | |||
{"arm", "32"}, | |||
{"aarch64", "64"}, | |||
{"x86_64", "64"} | |||
], | |||
case matchWordSize(Arch, AllArchs) of | |||
false -> | |||
case crossWordSize(Arch) of | |||
"" -> | |||
envWordSize(os:getenv("REBAR_TARGET_ARCH_WORDSIZE")); | |||
WordSize -> | |||
WordSize | |||
end; | |||
{_, Wordsize} -> | |||
Wordsize | |||
end. | |||
matchWordSize(Arch, [V = {Match, _Bits} | Vs]) -> | |||
case re:run(Arch, Match, [{capture, none}]) of | |||
match -> | |||
V; | |||
nomatch -> | |||
matchWordSize(Arch, Vs) | |||
end; | |||
matchWordSize(_Arch, []) -> | |||
false. | |||
envWordSize(Wordsize) when Wordsize =:= false; | |||
Wordsize =:= "" -> | |||
?WARN("REBAR_TARGET_ARCH_WORDSIZE not set, assuming 32\n", []), | |||
"32"; | |||
envWordSize(Wordsize) -> | |||
case Wordsize of | |||
"16" -> Wordsize; | |||
"32" -> Wordsize; | |||
"64" -> Wordsize; | |||
_ -> | |||
?WARN("REBAR_TARGET_ARCH_WORDSIZE bad value: ~p\n", [Wordsize]), | |||
"32" | |||
end. | |||
%% | |||
%% Find out the word size of the target by using Arch-gcc | |||
%% | |||
crossWordSize(Arch) -> | |||
crossSizeof(Arch, "void*"). | |||
%% | |||
%% Find the size of target Type using a specially crafted C file | |||
%% that will report an error on the line of the byte size of the type. | |||
%% | |||
crossSizeof(Arch, Type) -> | |||
Compiler = if Arch =:= "" -> "cc"; | |||
true -> Arch ++ "-gcc" | |||
end, | |||
TempFile = mkTempFile(".c"), | |||
ok = file:write_file(TempFile, | |||
<<"int t01 [1 - 2*(((long) (sizeof (TYPE))) == 1)];\n" | |||
"int t02 [1 - 2*(((long) (sizeof (TYPE))) == 2)];\n" | |||
"int t03 [1 - 2*(((long) (sizeof (TYPE))) == 3)];\n" | |||
"int t04 [1 - 2*(((long) (sizeof (TYPE))) == 4)];\n" | |||
"int t05 [1 - 2*(((long) (sizeof (TYPE))) == 5)];\n" | |||
"int t06 [1 - 2*(((long) (sizeof (TYPE))) == 6)];\n" | |||
"int t07 [1 - 2*(((long) (sizeof (TYPE))) == 7)];\n" | |||
"int t08 [1 - 2*(((long) (sizeof (TYPE))) == 8)];\n" | |||
"int t09 [1 - 2*(((long) (sizeof (TYPE))) == 9)];\n" | |||
"int t10 [1 - 2*(((long) (sizeof (TYPE))) == 10)];\n" | |||
"int t11 [1 - 2*(((long) (sizeof (TYPE))) == 11)];\n" | |||
"int t12 [1 - 2*(((long) (sizeof (TYPE))) == 12)];\n" | |||
"int t13 [1 - 2*(((long) (sizeof (TYPE))) == 13)];\n" | |||
"int t14 [1 - 2*(((long) (sizeof (TYPE))) == 14)];\n" | |||
"int t15 [1 - 2*(((long) (sizeof (TYPE))) == 15)];\n" | |||
"int t16 [1 - 2*(((long) (sizeof (TYPE))) == 16)];\n" | |||
>>), | |||
Cmd = Compiler ++ " -DTYPE=\"" ++ Type ++ "\" " ++ TempFile, | |||
ShOpts = [{use_stdout, false}, return_on_error], | |||
{error, {_, Res}} = sh(Cmd, ShOpts), | |||
ok = file:delete(TempFile), | |||
case string:tokens(Res, ":") of | |||
[_, Ln | _] -> | |||
try list_to_integer(Ln) of | |||
NumBytes -> integer_to_list(NumBytes * 8) | |||
catch | |||
error:_ -> | |||
"" | |||
end; | |||
_ -> | |||
"" | |||
end. | |||
mkTempFile(Suffix) -> | |||
{A, B, C} = rebarNow(), | |||
Dir = tempDir(), | |||
File = "rebar_" ++ os:getpid() ++ | |||
integer_to_list(A) ++ "_" ++ | |||
integer_to_list(B) ++ "_" ++ | |||
integer_to_list(C) ++ Suffix, | |||
filename:join(Dir, File). | |||
tempDir() -> | |||
case os:type() of | |||
{win32, _} -> windowsTempDir(); | |||
_ -> "/tmp" | |||
end. | |||
windowsTempDir() -> | |||
case os:getenv("TEMP") of | |||
false -> | |||
case os:getenv("TMP") of | |||
false -> "C:/WINDOWS/TEMP"; | |||
TMP -> TMP | |||
end; | |||
TEMP -> TEMP | |||
end. | |||
rebarNow() -> | |||
case erlang:function_exported(erlang, timestamp, 0) of | |||
true -> | |||
apply(erlang, timestamp, []); | |||
false -> | |||
%% erlang:now/0 was deprecated in 18.0. One solution to avoid the | |||
%% deprecation warning is to use | |||
%% -compile({nowarn_deprecated_function, [{erlang, now, 0}]}), but | |||
%% that would raise a warning in versions older than 18.0. Calling | |||
%% erlang:now/0 via apply/3 avoids that. | |||
apply(erlang, now, []) | |||
end. | |||
nativeWordsize() -> | |||
try erlang:system_info({wordsize, external}) of | |||
Val -> | |||
integer_to_list(8 * Val) | |||
catch | |||
error:badarg -> | |||
integer_to_list(8 * erlang:system_info(wordsize)) | |||
end. |