diff options
author | Sverker Eriksson <[email protected]> | 2017-08-30 20:55:08 +0200 |
---|---|---|
committer | Sverker Eriksson <[email protected]> | 2017-08-30 20:55:08 +0200 |
commit | 7c67bbddb53c364086f66260701bc54a61c9659c (patch) | |
tree | 92ab0d4b91d5e2f6e7a3f9d61ea25089e8a71fe0 /lib/runtime_tools/src/system_information.erl | |
parent | 97dc5e7f396129222419811c173edc7fa767b0f8 (diff) | |
parent | 3b7a6ffddc819bf305353a593904cea9e932e7dc (diff) | |
download | otp-7c67bbddb53c364086f66260701bc54a61c9659c.tar.gz otp-7c67bbddb53c364086f66260701bc54a61c9659c.tar.bz2 otp-7c67bbddb53c364086f66260701bc54a61c9659c.zip |
Merge tag 'OTP-19.0' into sverker/19/binary_to_atom-utf8-crash/ERL-474/OTP-14590
Diffstat (limited to 'lib/runtime_tools/src/system_information.erl')
-rw-r--r-- | lib/runtime_tools/src/system_information.erl | 834 |
1 files changed, 834 insertions, 0 deletions
diff --git a/lib/runtime_tools/src/system_information.erl b/lib/runtime_tools/src/system_information.erl new file mode 100644 index 0000000000..df25297eb9 --- /dev/null +++ b/lib/runtime_tools/src/system_information.erl @@ -0,0 +1,834 @@ +%% %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() -> [ + {init_arguments, init:get_arguments()}, + {code_paths, code:get_path()}, + {code, code()}, + {system_info, erlang_system_info()}, + {erts_compile_info, erlang:system_info(compile_info)}, + {beam_dynamic_libraries, get_dynamic_libraries()}, + {environment_erts, os_getenv_erts_specific()}, + {environment, [split_env(Env) || Env <- os:getenv()]}, + {sanity_check, sanity_check()} + ]. + +-spec to_file(FileName) -> ok | {error, Reason} when + FileName :: file:name_all(), + Reason :: file:posix() | badarg | terminated | system_limit. + +to_file(File) -> + file:write_file(File, iolist_to_binary([ + io_lib:format("{system_information_version, ~p}.~n", [ + ?REPORT_FILE_VSN + ]), + io_lib:format("{system_information, ~p}.~n", [ + report() + ]) + ])). + +from_file(File) -> + case file:consult(File) of + {ok, Data} -> + case get_value([system_information_version], Data) of + ?REPORT_FILE_VSN -> + get_value([system_information], Data); + Vsn -> + erlang:error({unknown_version, Vsn}) + end; + _ -> + erlang:error(bad_report_file) + 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. + +%% get applications + +code() -> + % order is important + get_code_from_paths(code:get_path()). + +get_code_from_paths([]) -> []; +get_code_from_paths([Path|Paths]) -> + case is_application_path(Path) of + true -> + [{application, get_application_from_path(Path)}|get_code_from_paths(Paths)]; + false -> + [{code, [ + {path, Path}, + {modules, get_modules_from_path(Path)} + ]}|get_code_from_paths(Paths)] + end. + +is_application_path(Path) -> + case filelib:wildcard(filename:join(Path, "*.app")) of + [] -> false; + _ -> true + end. + +get_application_from_path(Path) -> + [Appfile|_] = filelib:wildcard(filename:join(Path, "*.app")), + case file:consult(Appfile) of + {ok, [{application, App, Info}]} -> + {App, [ + {description, proplists:get_value(description, Info, [])}, + {vsn, proplists:get_value(vsn, Info, [])}, + {path, Path}, + {runtime_dependencies, + proplists:get_value(runtime_dependencies, Info, [])}, + {modules, get_modules_from_path(Path)} + ]} + end. + +get_modules_from_path(Path) -> + [ + begin + {ok,{Mod, Md5}} = beam_lib:md5(Beam), + Loaded = case code:is_loaded(Mod) of + false -> false; + _ -> true + end, + {Mod, [ + {loaded, Loaded}, + {native, beam_is_native_compiled(Beam)}, + {compiler, get_compiler_version(Beam)}, + {md5, hexstring(Md5)} + ]} + end || Beam <- filelib:wildcard(filename:join(Path, "*.beam")) + ]. + +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:tokens(VsnStr, "."))). + +rtdepstrs2rtdeps([]) -> + []; +rtdepstrs2rtdeps([RTDep | RTDeps]) -> + [AppStr, VsnStr] = string:tokens(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 |