#!/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)).