%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2013. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%% The main purpose of system_information is to aggregate all information
%% deemed useful for investigation, i.e. system_information:report/0.
%% The server and all other utilities surrounding this is for inspecting
%% reported values. Functions will be added to this as time goes by.
-module(system_information).
-behaviour(gen_server).
%% API
-export([report/0,
from_file/1,
to_file/1]).
-export([start/0, stop/0,
load_report/0, load_report/2,
applications/0, applications/1,
application/1, application/2,
environment/0, environment/1,
module/1, module/2,
modules/1,
sanity_check/0]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-define(SERVER, ?MODULE).
%% change version if parsing of file changes
-define(REPORT_FILE_VSN, "1.0").
-record(state, {
report
}).
%%===================================================================
%% API
%%===================================================================
start() ->
gen_server:start({local, ?SERVER}, ?MODULE, [], []).
stop() ->
gen_server:call(?SERVER, stop, infinity).
load_report() -> load_report(data, report()).
load_report(file, File) -> load_report(data, from_file(File));
load_report(data, Report) ->
ok = start_internal(), gen_server:call(?SERVER, {load_report, Report}, infinity).
report() ->
%% This is ugly but beats having to maintain two distinct implementations,
%% and we don't really care about memory use since it's internal and
%% undocumented.
{ok, Fd} = file:open([], [ram, read, write]),
to_fd(Fd),
{ok, _} = file:position(Fd, bof),
from_fd(Fd).
-spec to_file(FileName) -> ok | {error, Reason} when
FileName :: file:name_all(),
Reason :: file:posix() | badarg | terminated | system_limit.
to_file(File) ->
case file:open(File, [raw, write, binary, delayed_write]) of
{ok, Fd} ->
try
to_fd(Fd)
after
file:close(Fd)
end;
{error, Reason} ->
{error, Reason}
end.
from_file(File) ->
{ok, Fd} = file:open(File, [raw, read]),
try
from_fd(Fd)
after
file:close(Fd)
end.
applications() -> applications([]).
applications(Opts) when is_list(Opts) ->
gen_server:call(?SERVER, {applications, Opts}, infinity).
application(App) when is_atom(App) -> application(App, []).
application(App, Opts) when is_atom(App), is_list(Opts) ->
gen_server:call(?SERVER, {application, App, Opts}, infinity).
environment() -> environment([]).
environment(Opts) when is_list(Opts) ->
gen_server:call(?SERVER, {environment, Opts}, infinity).
module(M) when is_atom(M) -> module(M, []).
module(M, Opts) when is_atom(M), is_list(Opts) ->
gen_server:call(?SERVER, {module, M, Opts}, infinity).
modules(Opt) when is_atom(Opt) ->
gen_server:call(?SERVER, {modules, Opt}, infinity).
-spec sanity_check() -> ok | {failed, Failures} when
Application :: atom(),
ApplicationVersion :: string(),
MissingRuntimeDependencies :: {missing_runtime_dependencies,
ApplicationVersion,
[ApplicationVersion]},
InvalidApplicationVersion :: {invalid_application_version,
ApplicationVersion},
InvalidAppFile :: {invalid_app_file, Application},
Failure :: MissingRuntimeDependencies
| InvalidApplicationVersion
| InvalidAppFile,
Failures :: [Failure].
sanity_check() ->
case check_runtime_dependencies() of
[] -> ok;
Issues -> {failed, Issues}
end.
%%===================================================================
%% gen_server callbacks
%%===================================================================
init([]) ->
{ok, #state{}}.
handle_call(stop, _From, S) ->
{stop, normal, ok, S};
handle_call({load_report, Report}, _From, S) ->
Version = get_value([system_info, system_version], Report),
io:format("Loaded report from system version: ~s~n", [Version]),
{reply, ok, S#state{ report = Report }};
handle_call(_Req, _From, #state{ report = undefined } = S) ->
{reply, {error, report_not_loaded}, S};
handle_call({applications, Opts}, _From, #state{ report = Report } = S) ->
ok = print_applications(get_value([code], Report), Opts),
{reply, ok, S};
handle_call({application, App, Opts}, _From, #state{ report = Report } = S) ->
Data = get_value([App], [AppInfo||{application, AppInfo}<-get_value([code], Report)]),
ok = print_application({App, Data}, Opts),
{reply, ok, S};
handle_call({environment, Opts}, _From, #state{ report = Report } = S) ->
Choices = case proplists:get_bool(full, Opts) of
true -> [environment];
false -> [environment_erts]
end,
ok = print_environments(get_value(Choices, Report), Opts),
{reply, ok, S};
handle_call({module, M, Opts}, _From, #state{ report = Report } = S) ->
Mods = find_modules_from_code(M, get_value([code], Report)),
print_modules_from_code(M, Mods, Opts),
{reply, ok, S};
handle_call({modules, native}, _From, #state{ report = Report } = S) ->
Codes = get_native_modules_from_code(get_value([code],Report)),
io:format("~p~n", [Codes]),
{reply, ok, S};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%===================================================================
%% Internal functions
%%===================================================================
start_internal() ->
case start() of
{ok,_} -> ok;
{error, {already_started,_}} -> ok;
Error -> Error
end.
%% handle report values
get_value([], Data) -> Data;
get_value([K|Ks], Data) ->
get_value(Ks, proplists:get_value(K, Data, [])).
find_modules_from_code(M, [{code, Info}|Codes]) ->
case find_modules(M, get_value([modules], Info)) of
[] -> find_modules_from_code(M, Codes);
Mods ->
Path = get_value([path], Info),
[{Path, Mods}|find_modules_from_code(M, Codes)]
end;
find_modules_from_code(M, [{application, {App, Info}}|Codes]) ->
case find_modules(M, get_value([modules], Info)) of
[] -> find_modules_from_code(M, Codes);
Mods ->
Path = get_value([path], Info),
Vsn = get_value([vsn], Info),
[{App, Vsn, Path, Mods}|find_modules_from_code(M, Codes)]
end;
find_modules_from_code(_, []) -> [].
find_modules(M, [{M, _}=Info|Ms]) -> [Info|find_modules(M,Ms)];
find_modules(M, [_|Ms]) -> find_modules(M, Ms);
find_modules(_, []) -> [].
get_native_modules_from_code([{application, {App, Info}}|Cs]) ->
case get_native_modules(get_value([modules], Info)) of
[] -> get_native_modules_from_code(Cs);
Mods ->
Path = get_value([path], Info),
Vsn = get_value([vsn], Info),
[{App, Vsn, Path, Mods}|get_native_modules_from_code(Cs)]
end;
get_native_modules_from_code([{code, Info}|Cs]) ->
case get_native_modules(get_value([modules], Info)) of
[] -> get_native_modules_from_code(Cs);
Mods ->
Path = get_value([path], Info),
[{Path, Mods}|get_native_modules_from_code(Cs)]
end;
get_native_modules_from_code([]) -> [].
get_native_modules([]) -> [];
get_native_modules([{Mod, Info}|Ms]) ->
case proplists:get_value(native, Info) of
false -> get_native_modules(Ms);
_ -> [Mod|get_native_modules(Ms)]
end.
%% print information
print_applications([{application, App}|Apps], Opts) ->
print_application(App, Opts),
print_applications(Apps, Opts);
print_applications([{code,_}|Apps], Opts) ->
print_applications(Apps, Opts);
print_applications([], _) ->
ok.
print_application({App, Info}, Opts) ->
Vsn = get_value([vsn], Info),
io:format(" * ~w-~s~n", [App, Vsn]),
case proplists:get_bool(full, Opts) of
true ->
_ = [ begin
print_module(Minfo)
end || Minfo <- get_value([modules], Info) ],
ok;
false ->
ok
end.
print_environments([Env|Envs],Opts) ->
print_environment(Env,Opts),
print_environments(Envs,Opts);
print_environments([],_) ->
ok.
print_environment({_Key, false},_) -> ok;
print_environment({Key, Value},_) ->
io:format(" - ~s = ~ts~n", [Key, Value]).
print_modules_from_code(M, [Info|Ms], Opts) ->
print_module_from_code(M, Info),
case proplists:get_bool(full, Opts) of
true -> print_modules_from_code(M, Ms, Opts);
false -> ok
end;
print_modules_from_code(_, [], _) ->
ok.
print_module_from_code(M, {Path, [{M,ModInfo}]}) ->
io:format(" from path \"~ts\" (no application):~n", [Path]),
io:format(" - compiler: ~s~n", [get_value([compiler], ModInfo)]),
io:format(" - md5: ~s~n", [get_value([md5], ModInfo)]),
io:format(" - native: ~w~n", [get_value([native], ModInfo)]),
io:format(" - loaded: ~w~n", [get_value([loaded], ModInfo)]),
ok;
print_module_from_code(M, {App,Vsn,Path,[{M,ModInfo}]}) ->
io:format(" from path \"~ts\" (~w-~s):~n", [Path,App,Vsn]),
io:format(" - compiler: ~s~n", [get_value([compiler], ModInfo)]),
io:format(" - md5: ~s~n", [get_value([md5], ModInfo)]),
io:format(" - native: ~w~n", [get_value([native], ModInfo)]),
io:format(" - loaded: ~w~n", [get_value([loaded], ModInfo)]),
ok.
print_module({Mod, ModInfo}) ->
io:format(" - ~w:~n", [Mod]),
io:format(" - compiler: ~s~n", [get_value([compiler], ModInfo)]),
io:format(" - md5: ~s~n", [get_value([md5], ModInfo)]),
io:format(" - native: ~w~n", [get_value([native], ModInfo)]),
io:format(" - loaded: ~w~n", [get_value([loaded], ModInfo)]),
ok.
%% get useful information from erlang:system_info/1
erlang_system_info() ->
erlang_system_info([
allocator,
check_io,
otp_release,
port_limit,
process_limit,
% procs, % not needed
smp_support,
system_version,
system_architecture,
threads,
thread_pool_size,
{wordsize,internal},
{wordsize,external},
{cpu_topology, defined},
{cpu_topology, detected},
scheduler_bind_type,
scheduler_bindings,
compat_rel,
schedulers_state,
build_type,
logical_processors,
logical_processors_online,
logical_processors_available,
driver_version,
nif_version,
emu_args,
ethread_info,
beam_jump_table,
taints
]).
erlang_system_info([]) -> [];
erlang_system_info([Type|Types]) ->
[{Type, erlang:system_info(Type)}|erlang_system_info(Types)].
%% get known useful erts environment
os_getenv_erts_specific() ->
os_getenv_erts_specific([
"BINDIR",
"DIALYZER_EMULATOR",
"CERL_DETACHED_PROG",
"EMU",
"ERL_CONSOLE_MODE",
"ERL_CRASH_DUMP",
"ERL_CRASH_DUMP_NICE",
"ERL_CRASH_DUMP_SECONDS",
"ERL_EPMD_PORT",
"ERL_EMULATOR_DLL",
"ERL_FULLSWEEP_AFTER",
"ERL_LIBS",
"ERL_MALLOC_LIB",
"ERL_MAX_PORTS",
"ERL_MAX_ETS_TABLES",
"ERL_NO_KERNEL_POLL",
"ERL_THREAD_POOL_SIZE",
"ERLC_EMULATOR",
"ESCRIPT_EMULATOR",
"HOME",
"HOMEDRIVE",
"HOMEPATH",
"LANG",
"LC_ALL",
"LC_CTYPE",
"PATH",
"PROGNAME",
"RELDIR",
"ROOTDIR",
"TERM",
%"VALGRIND_LOG_XML",
%% heart
"COMSPEC",
"HEART_COMMAND",
%% run_erl
"RUN_ERL_LOG_ALIVE_MINUTES",
"RUN_ERL_LOG_ACTIVITY_MINUTES",
"RUN_ERL_LOG_ALIVE_FORMAT",
"RUN_ERL_LOG_ALIVE_IN_UTC",
"RUN_ERL_LOG_GENERATIONS",
"RUN_ERL_LOG_MAXSIZE",
"RUN_ERL_DISABLE_FLOWCNTRL",
%% driver getenv
"CALLER_DRV_USE_OUTPUTV",
"ERL_INET_GETHOST_DEBUG",
"ERL_EFILE_THREAD_SHORT_CIRCUIT",
"ERL_WINDOW_TITLE",
"ERL_ABORT_ON_FAILURE",
"TTYSL_DEBUG_LOG"
]).
os_getenv_erts_specific([]) -> [];
os_getenv_erts_specific([Key|Keys]) ->
[{Key, os:getenv(Key)}|os_getenv_erts_specific(Keys)].
split_env(Env) ->
split_env(Env, []).
split_env([$=|Vs], Key) -> {lists:reverse(Key), Vs};
split_env([I|Vs], Key) -> split_env(Vs, [I|Key]);
split_env([], KV) -> lists:reverse(KV). % should not happen.
from_fd(Fd) ->
try
[{system_information_version, "1.0"},
{system_information, Data}] = consult_fd(Fd),
Data
catch
_:_ -> erlang:error(bad_report_file)
end.
consult_fd(Fd) ->
consult_fd_1(Fd, [], {ok, []}).
consult_fd_1(Fd, Cont0, ReadResult) ->
Data =
case ReadResult of
{ok, Characters} -> Characters;
eof -> eof
end,
case erl_scan:tokens(Cont0, Data, 1) of
{done, {ok, Tokens, _}, Next} ->
{ok, Term} = erl_parse:parse_term(Tokens),
[Term | consult_fd_1(Fd, [], {ok, Next})];
{more, Cont} ->
consult_fd_1(Fd, Cont, file:read(Fd, 1 bsl 20));
{done, {eof, _}, eof} -> []
end.
%%
%% Dumps a system_information tuple to the given Fd, writing the term in chunks
%% to avoid eating too much memory on large systems.
%%
to_fd(Fd) ->
EmitChunk =
fun(Format, Args) ->
ok = file:write(Fd, io_lib:format(Format, Args))
end,
EmitChunk("{system_information_version, ~w}.~n"
"{system_information,["
"{init_arguments,~w},"
"{code_paths,~w},",
[?REPORT_FILE_VSN,
init:get_arguments(),
code:get_path()]),
emit_code_info(EmitChunk),
EmitChunk( "," %% Note the leading comma!
"{system_info,~w},"
"{erts_compile_info,~w},"
"{beam_dynamic_libraries,~w},"
"{environment_erts,~w},"
"{environment,~w},"
"{sanity_check,~w}"
"]}.~n",
[erlang_system_info(),
erlang:system_info(compile_info),
get_dynamic_libraries(),
os_getenv_erts_specific(),
[split_env(Env) || Env <- os:getenv()],
sanity_check()]).
%% Emits all modules/applications in the *code path order*
emit_code_info(EmitChunk) ->
EmitChunk("{code, [", []),
comma_separated_foreach(EmitChunk,
fun(Path) ->
case is_application_path(Path) of
true -> emit_application_info(EmitChunk, Path);
false -> emit_code_path_info(EmitChunk, Path)
end
end, code:get_path()),
EmitChunk("]}", []).
emit_application_info(EmitChunk, Path) ->
[Appfile|_] = filelib:wildcard(filename:join(Path, "*.app")),
case file:consult(Appfile) of
{ok, [{application, App, Info}]} ->
RtDeps = proplists:get_value(runtime_dependencies, Info, []),
Description = proplists:get_value(description, Info, []),
Version = proplists:get_value(vsn, Info, []),
EmitChunk("{application, {~w,["
"{description,~w},"
"{vsn,~w},"
"{path,~w},"
"{runtime_dependencies,~w},",
[App, Description, Version, Path, RtDeps]),
emit_module_info_from_path(EmitChunk, Path),
EmitChunk("]}}", [])
end.
emit_code_path_info(EmitChunk, Path) ->
EmitChunk("{code, ["
"{path, ~w},", [Path]),
emit_module_info_from_path(EmitChunk, Path),
EmitChunk("]}", []).
emit_module_info_from_path(EmitChunk, Path) ->
BeamFiles = filelib:wildcard(filename:join(Path, "*.beam")),
EmitChunk("{modules, [", []),
comma_separated_foreach(EmitChunk,
fun(Beam) ->
emit_module_info(EmitChunk, Beam)
end, BeamFiles),
EmitChunk("]}", []).
emit_module_info(EmitChunk, Beam) ->
%% FIXME: The next three calls load *all* significant chunks onto the heap,
%% which may cause us to run out of memory if there's a huge module in the
%% code path.
{ok,{Mod, Md5}} = beam_lib:md5(Beam),
CompilerVersion = get_compiler_version(Beam),
Native = beam_is_native_compiled(Beam),
Loaded = case code:is_loaded(Mod) of
false -> false;
_ -> true
end,
EmitChunk("{~w,["
"{loaded,~w},"
"{native,~w},"
"{compiler,~w},"
"{md5,~w}"
"]}",
[Mod, Loaded, Native, CompilerVersion, hexstring(Md5)]).
comma_separated_foreach(_EmitChunk, _Fun, []) ->
ok;
comma_separated_foreach(_EmitChunk, Fun, [H]) ->
Fun(H);
comma_separated_foreach(EmitChunk, Fun, [H | T]) ->
Fun(H),
EmitChunk(",", []),
comma_separated_foreach(EmitChunk, Fun, T).
is_application_path(Path) ->
case filelib:wildcard(filename:join(Path, "*.app")) of
[] -> false;
_ -> true
end.
hexstring(Bin) when is_binary(Bin) ->
lists:flatten([io_lib:format("~2.16.0b", [V]) || <<V>> <= Bin]).
%% inspect beam files for information
get_compiler_version(Beam) ->
case beam_lib:chunks(Beam, [compile_info]) of
{ok,{_,[{compile_info, Info}]}} ->
proplists:get_value(version, Info);
_ -> undefined
end.
%% we don't know the specific chunk names of native code
%% we don't want to load the code to check it
beam_is_native_compiled(Beam) ->
Chunks = get_value([chunks], beam_lib:info(Beam)),
case check_known_hipe_chunks(Chunks) of
[] -> false;
[Arch] -> {true, Arch};
Archs -> {true, Archs}
end.
check_known_hipe_chunks([{Tag,_,_}|Cs]) ->
case is_chunk_tag_hipe_arch(Tag) of
false -> check_known_hipe_chunks(Cs);
{true, Arch} -> [Arch|check_known_hipe_chunks(Cs)]
end;
check_known_hipe_chunks([]) -> [].
%% these values are taken from hipe_unified_loader
%% perhaps these should be exported in that module?
-define(HS8P_TAG,"HS8P").
-define(HPPC_TAG,"HPPC").
-define(HP64_TAG,"HP64").
-define(HARM_TAG,"HARM").
-define(HX86_TAG,"HX86").
-define(HA64_TAG,"HA64").
is_chunk_tag_hipe_arch(Tag) ->
case Tag of
?HA64_TAG -> {true, amd64}; %% HiPE, x86_64, (implicit: 64-bit, Unix)
?HARM_TAG -> {true, arm}; %% HiPE, arm, v5 (implicit: 32-bit, Linux)
?HPPC_TAG -> {true, powerpc}; %% HiPE, PowerPC (implicit: 32-bit, Linux)
?HP64_TAG -> {true, ppc64}; %% HiPE, ppc64 (implicit: 64-bit, Linux)
?HS8P_TAG -> {true, ultrasparc}; %% HiPE, SPARC, V8+ (implicit: 32-bit)
%% Future: HSV9 %% HiPE, SPARC, V9 (implicit: 64-bit)
%% HW32 %% HiPE, x86, Win32
_ -> false
end.
get_dynamic_libraries() ->
Beam = filename:join([os:getenv("BINDIR"),get_beam_name()]),
case os:type() of
{unix, darwin} -> os:cmd("otool -L " ++ Beam);
_ -> os:cmd("ldd " ++ Beam)
end.
get_beam_name() ->
Type = case erlang:system_info(build_type) of
opt -> "";
TypeName -> "." ++ atom_to_list(TypeName)
end,
Flavor = case erlang:system_info(smp_support) of
false -> "";
true -> ".smp"
end,
Beam = os:getenv("EMU", "beam"),
Beam ++ Type ++ Flavor.
%% Check runtime dependencies...
vsnstr2vsn(VsnStr) ->
list_to_tuple(lists:map(fun (Part) ->
list_to_integer(Part)
end,
string:lexemes(VsnStr, "."))).
rtdepstrs2rtdeps([]) ->
[];
rtdepstrs2rtdeps([RTDep | RTDeps]) ->
[AppStr, VsnStr] = string:lexemes(RTDep, "-"),
[{list_to_atom(AppStr), vsnstr2vsn(VsnStr)} | rtdepstrs2rtdeps(RTDeps)].
build_app_table([], AppTab) ->
AppTab;
build_app_table([App | Apps], AppTab0) ->
AppTab1 = try
%% We may have multiple application versions installed
%% of the same application! It is therefore important
%% to look up the application version that actually will
%% be used via code server.
AppFile = code:where_is_file(atom_to_list(App) ++ ".app"),
{ok, [{application, App, Info}]} = file:consult(AppFile),
VsnStr = proplists:get_value(vsn, Info),
Vsn = vsnstr2vsn(VsnStr),
RTDepStrs = proplists:get_value(runtime_dependencies,
Info, []),
RTDeps = rtdepstrs2rtdeps(RTDepStrs),
gb_trees:insert(App, {Vsn, RTDeps}, AppTab0)
catch
_ : _ ->
AppTab0
end,
build_app_table(Apps, AppTab1).
meets_min_req(Vsn, Vsn) ->
true;
meets_min_req({X}, VsnReq) ->
meets_min_req({X, 0, 0}, VsnReq);
meets_min_req({X, Y}, VsnReq) ->
meets_min_req({X, Y, 0}, VsnReq);
meets_min_req(Vsn, {X}) ->
meets_min_req(Vsn, {X, 0, 0});
meets_min_req(Vsn, {X, Y}) ->
meets_min_req(Vsn, {X, Y, 0});
meets_min_req({X, _Y, _Z}, {XReq, _YReq, _ZReq}) when X > XReq ->
true;
meets_min_req({X, Y, _Z}, {X, YReq, _ZReq}) when Y > YReq ->
true;
meets_min_req({X, Y, Z}, {X, Y, ZReq}) when Z > ZReq ->
true;
meets_min_req({_X, _Y, _Z}, {_XReq, _YReq, _ZReq}) ->
false;
meets_min_req(Vsn, VsnReq) ->
gp_meets_min_req(mk_gp_vsn_list(Vsn), mk_gp_vsn_list(VsnReq)).
gp_meets_min_req([X, Y, Z | _Vs], [X, Y, Z]) ->
true;
gp_meets_min_req([X, Y, Z | _Vs], [XReq, YReq, ZReq]) ->
meets_min_req({X, Y, Z}, {XReq, YReq, ZReq});
gp_meets_min_req([X, Y, Z | Vs], [X, Y, Z | VReqs]) ->
gp_meets_min_req_tail(Vs, VReqs);
gp_meets_min_req(_Vsn, _VReq) ->
%% Versions on different version branches, i.e., the minimum
%% required functionality is not included in Vsn.
false.
gp_meets_min_req_tail([V | Vs], [V | VReqs]) ->
gp_meets_min_req_tail(Vs, VReqs);
gp_meets_min_req_tail([], []) ->
true;
gp_meets_min_req_tail([_V | _Vs], []) ->
true;
gp_meets_min_req_tail([V | _Vs], [VReq]) when V > VReq ->
true;
gp_meets_min_req_tail(_Vs, _VReqs) ->
%% Versions on different version branches, i.e., the minimum
%% required functionality is not included in Vsn.
false.
mk_gp_vsn_list(Vsn) ->
[X, Y, Z | Tail] = tuple_to_list(Vsn),
[X, Y, Z | remove_trailing_zeroes(Tail)].
remove_trailing_zeroes([]) ->
[];
remove_trailing_zeroes([0 | Vs]) ->
case remove_trailing_zeroes(Vs) of
[] -> [];
NewVs -> [0 | NewVs]
end;
remove_trailing_zeroes([V | Vs]) ->
[V | remove_trailing_zeroes(Vs)].
mk_app_vsn_str({App, Vsn}) ->
mk_app_vsn_str(App, Vsn).
mk_app_vsn_str(App, Vsn) ->
VsnList = tuple_to_list(Vsn),
lists:flatten([atom_to_list(App),
$-,
integer_to_list(hd(VsnList)),
lists:map(fun (Part) ->
[$., integer_to_list(Part)]
end, tl(VsnList))]).
otp_17_0_vsns_orddict() ->
[{asn1,{3,0}},
{common_test,{1,8}},
{compiler,{5,0}},
{cosEvent,{2,1,15}},
{cosEventDomain,{1,1,14}},
{cosFileTransfer,{1,1,16}},
{cosNotification,{1,1,21}},
{cosProperty,{1,1,17}},
{cosTime,{1,1,14}},
{cosTransactions,{1,2,14}},
{crypto,{3,3}},
{debugger,{4,0}},
{dialyzer,{2,7}},
{diameter,{1,6}},
{edoc,{0,7,13}},
{eldap,{1,0,3}},
{erl_docgen,{0,3,5}},
{erl_interface,{3,7,16}},
{erts,{6,0}},
{et,{1,5}},
{eunit,{2,2,7}},
{gs,{1,5,16}},
{hipe,{3,10,3}},
{ic,{4,3,5}},
{inets,{5,10}},
{jinterface,{1,5,9}},
{kernel,{3,0}},
{megaco,{3,17,1}},
{mnesia,{4,12}},
{observer,{2,0}},
{odbc,{2,10,20}},
{orber,{3,6,27}},
{os_mon,{2,2,15}},
{ose,{1,0}},
{otp_mibs,{1,0,9}},
{parsetools,{2,0,11}},
{percept,{0,8,9}},
{public_key,{0,22}},
{reltool,{0,6,5}},
{runtime_tools,{1,8,14}},
{sasl,{2,4}},
{snmp,{4,25,1}},
{ssh,{3,0,1}},
{ssl,{5,3,4}},
{stdlib,{2,0}},
{syntax_tools,{1,6,14}},
{test_server,{3,7}},
{tools,{2,6,14}},
{typer,{0,9,6}},
{webtool,{0,8,10}},
{wx,{1,2}},
{xmerl,{1,3,7}}].
otp_17_0_vsns_tab() ->
gb_trees:from_orddict(otp_17_0_vsns_orddict()).
check_runtime_dependency({App, DepVsn}, AppTab) ->
case gb_trees:lookup(App, AppTab) of
none ->
false;
{value, {Vsn, _}} ->
meets_min_req(Vsn, DepVsn)
end.
check_runtime_dependencies(App, AppTab, OtpMinVsnTab) ->
case gb_trees:lookup(App, AppTab) of
none ->
[{invalid_app_file, App}];
{value, {Vsn, RTDeps}} ->
RTD = case lists:foldl(
fun (RTDep, Acc) ->
case check_runtime_dependency(RTDep, AppTab) of
true ->
Acc;
false ->
[mk_app_vsn_str(RTDep) | Acc]
end
end,
[],
RTDeps) of
[] ->
[];
MissingDeps ->
[{missing_runtime_dependencies,
mk_app_vsn_str(App, Vsn),
MissingDeps}]
end,
case gb_trees:lookup(App, OtpMinVsnTab) of
none ->
RTD;
{value, MinVsn} ->
case meets_min_req(Vsn, MinVsn) of
true ->
RTD;
false ->
[{invalid_application_version,
mk_app_vsn_str(App, Vsn)} | RTD]
end
end
end.
app_file_to_app(AF) ->
list_to_atom(filename:basename(AF, ".app")).
get_apps() ->
get_apps(code:get_path(), []).
get_apps([], Apps) ->
lists:usort(Apps);
get_apps([Path|Paths], Apps) ->
case filelib:wildcard(filename:join(Path, "*.app")) of
[] ->
%% Not app or invalid app
get_apps(Paths, Apps);
[AppFile] ->
get_apps(Paths, [app_file_to_app(AppFile) | Apps]);
[_AppFile| _] = AppFiles ->
%% Strange with multple .app files... Lets put them
%% all in the list and see what we get...
lists:map(fun (AF) ->
app_file_to_app(AF)
end, AppFiles) ++ Apps
end.
check_runtime_dependencies() ->
OtpMinVsnTab = otp_17_0_vsns_tab(),
Apps = get_apps(),
AppTab = build_app_table(Apps, gb_trees:empty()),
lists:foldl(fun (App, Acc) ->
case check_runtime_dependencies(App,
AppTab,
OtpMinVsnTab) of
[] -> Acc;
Issues -> Issues ++ Acc
end
end,
[],
Apps).
%% End of runtime dependency checks