%% %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_VFORK", "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]) || <> <= 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