Current section
Files
Jump to
Current section
Files
src/grisp_tools_util.erl
-module(grisp_tools_util).
% API
-export([env/1]).
-export([cdn_path/2]).
-export([otp_path/2]).
-export([package_name/1]).
-export([package_cache_temp/1]).
-export([package_cache_file/1]).
-export([package_cache_etag/1]).
-export([ensure_dir/1]).
-export([mv/2]).
-export([rm/1]).
-export([source_files/2]).
-export([source_hash/2]).
-export([with_file/3]).
%--- Macros --------------------------------------------------------------------
-define(HASH_BLOCK_SIZE, 4194304). % 4MB
%--- API -----------------------------------------------------------------------
% FIXME: Change to grisp_tools when migrated
env(Key) ->
{ok, Value} = application:get_env(rebar3_grisp, Key),
Value.
cdn_path(otp, #{board := Board} = State) ->
#{path := Path} = URI = uri_string:parse(env(cdn)),
File = grisp_tools_util:package_name(State) ++ ".tar.gz",
uri_string:recompose(URI#{
path => Path ++ string:join(["/platforms", Board, "otp", File], "/")
}).
otp_path(#{custom_build := true, project_root := Root, otp_version := OTPVersion}, build_root) ->
filename:join([Root, "otp", OTPVersion, "build"]);
otp_path(#{custom_build := true, project_root := Root, otp_version := OTPVersion}, install_root) ->
filename:join([Root, "otp", OTPVersion, "install"]);
otp_path(State, install_root) ->
filename:join([package_dir(), package_name(State)]).
package_name(#{otp_version := OTPVersion, hash := Hash}) ->
"grisp_otp_build_" ++ OTPVersion ++ "_" ++ Hash.
package_cache_temp(State) ->
package_cache_file(State) ++ ".temp".
package_cache_file(State) ->
filename:join(otp_path(State, install_root), package_name(State)).
package_cache_etag(State) ->
InstallRoot = otp_path(State, install_root),
Tarball = grisp_tools_util:package_name(State),
PackageFile = filename:join(InstallRoot, Tarball),
ETagFile = filename:join(InstallRoot, "ETag"),
ETag = case {filelib:is_regular(PackageFile), filelib:is_regular(ETagFile)} of
{true, true} ->
{ok, Properties} = file:consult(ETagFile),
proplists:get_value(etag, Properties);
_Else ->
undefined
end,
{ETagFile, ETag}.
ensure_dir(File) ->
case filelib:ensure_dir(File) of
ok -> ok;
Error -> error({create_dir_failed, filename:dirname(File), Error})
end.
mv(From, To) ->
rm(To),
case file:rename(From, To) of
ok -> ok;
Error -> error({rename_file_failed, From, To, Error})
end.
rm(File) ->
case file:delete(File) of
ok -> ok;
{error, enoent} -> ok;
Error -> error({delete_file_failed, File, Error})
end.
source_files(Apps, Board) ->
lists:foldl(fun({_App, Dir}, {Sys, Drivers}) ->
{AppSys, AppDrivers} = collect_c_sources(Dir, Board),
{maps:merge(Sys, AppSys), maps:merge(Drivers, AppDrivers)}
end, {#{}, #{}}, Apps).
source_hash(Apps, Board) ->
{DriverFiles, SystemFiles} = source_files(Apps, Board),
Targets = maps:merge(DriverFiles, SystemFiles),
hash_files(Targets).
with_file(File, Opts, Fun) ->
Handle = case file:open(File, Opts) of
{ok, H} -> H;
Error -> error({open_file_failed, File, Error})
end,
try
Fun(Handle)
after
file:close(Handle)
end.
%--- Internal ------------------------------------------------------------------
cache_dir() -> filename:basedir(user_cache, "grisp").
package_dir() -> filename:join([cache_dir(), "packages", "otp"]).
collect_c_sources(Dir, Board) ->
Source = filename:join([Dir, "grisp", Board]),
case filelib:is_dir(Source) of
true -> {collect_sys(Source), collect_drivers(Source)};
false -> {#{}, #{}}
end.
collect_sys(Source) ->
maps:merge(
collect_files({Source, "sys/*.h"}, "erts/emulator/sys/unix"),
collect_files({Source, "sys/*.c"}, "erts/emulator/sys/unix")
).
collect_drivers(Source) ->
maps:merge(
collect_files(
{Source, "drivers/*.h"},
"erts/emulator/drivers/unix"
),
collect_files(
{Source, "drivers/*.c"},
"erts/emulator/drivers/unix"
)
).
collect_files({SourceRoot, Pattern}, Target) ->
Files = filelib:wildcard(filename:join(SourceRoot, Pattern)),
lists:foldl(fun(File, Collected) ->
TargetFile = filename:join([Target, filename:basename(File)]),
Collected#{TargetFile => File}
end, #{}, Files).
hash_files(Targets) ->
Sorted = lists:keysort(1, maps:to_list(Targets)),
FileHashes = lists:map(fun({Target, Source}) ->
{ok, Hash} = hash_file(Source, sha256),
{Target, Hash}
end, Sorted),
HashIndex = lists:map(fun({Target, Hash}) ->
io_lib:format("~s ~s~n", [Target, format_hash(sha256, Hash)]) end,
FileHashes),
TopHash = format_hash(sha256, crypto:hash(sha256, HashIndex)),
{TopHash, HashIndex}.
hash_file(File, Algorithm) ->
Context = crypto:hash_init(Algorithm),
with_file(File, [binary, raw, read], fun(Handle) ->
hash_file_read(Handle, Context)
end).
hash_file_read(Handle, Context) ->
case file:read(Handle, ?HASH_BLOCK_SIZE) of
{ok, Bin} -> hash_file_read(Handle, crypto:hash_update(Context, Bin));
eof -> {ok, crypto:hash_final(Context)}
end.
format_hash(sha256, <<Int:256/big-unsigned-integer>>) -> format_hash(Int);
format_hash(md5, <<Int:128/big-unsigned-integer>>) -> format_hash(Int).
format_hash(Int) when is_integer(Int) -> io_lib:format("~.16b", [Int]).