%%%------------------------------------------------------------------- %%% File: geas_doc.erl %%% @author Eric Pailleau %%% @copyright 2015 crownedgrouse.com %%% @doc %%% Guess Erlang Applicaion Scattering %%% Database generation module %%% @end %%% %%% Permission to use, copy, modify, and/or distribute this software %%% for any purpose with or without fee is hereby granted, provided %%% that the above copyright notice and this permission notice appear %%% in all copies. %%% %%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL %%% WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED %%% WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE %%% AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR %%% CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM %%% LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, %%% NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN %%% CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %%% %%% Created : 2015-11-05 %%%------------------------------------------------------------------- -module(geas_db). -author("Eric Pailleau "). -export([generate/2, get_rel_list/0]). % List of known releases -define(REL_LIST, ["R15", "R15B", "R15B01", "R15B02", "R15B03", "R15B03-1", "R16B", "R16B01", "R16B02", "R16B03", "R16B03-1", "17.0", "17.1", "17.3", "17.4", "17.5", "18.0", "18.1", "18.2", "18.3", "19.0", "19.1", "19.2", "19.3", "20.0", "20.1", "20.2", "20.3", "21.0", "21.1", "21.2", "21.3", "22.0"]). %% This module generate the geas_db.hrl %% providing the min and max release of any Erlang/OTP function %%------------------------------------------------------------------------- %% @doc Generate target geas database from inventory root path %% @end %%------------------------------------------------------------------------- -spec generate(list(), list()) -> ok. generate(Dir, Target) -> generate(filename:join([Dir, "relinfos", "term"]), filename:join([Dir, "reldiffs", "term"]), Target). %%------------------------------------------------------------------------- %% @doc Generate target geas database from info and diff directories %% @end %%------------------------------------------------------------------------- -spec generate(list(), list(), list()) -> ok | {'error',atom()}. generate(IDir, DDir, Target) -> {ok, TargetIo} = file:open(Target, [write]), do_header(TargetIo), do_defines(TargetIo), % List all functions removed in what release [{module, release}, ...] Data11 = lists:flatten(get_removed_functions(DDir)), do_max_functions(TargetIo, Data11), % list of modules removed in what release [{module, release}, ...] Data12 = lists:flatten(get_removed_modules(DDir)), do_max_modules(TargetIo, Data12), io:nl(TargetIo), % List all functions added in what release [{module, release}, ...] Data21 = lists:flatten(get_added_functions(DDir)), do_min_functions(TargetIo, Data21), % List the whole modules of oldest known release {ok, [R1]} = file:consult(filename:join(IDir, hd(?REL_LIST))), {mfa, Data22} = lists:keyfind(mfa, 1, R1), do_min_modules(TargetIo, Data22), % Close file file:close(TargetIo). %%------------------------------------------------------------------------- %% @doc Get all removed functions from diff directory %% @end %%------------------------------------------------------------------------- -spec get_removed_functions(list()) -> list(). get_removed_functions(DDir) -> % List all reldiffs files {ok, Reldiffs} = file:list_dir(DDir), % Loop over list and pick up all removed functions, linked to 'From' _RMF = lists:map(fun(F) -> % Load file {ok, [R]} = file:consult(filename:join(DDir, F)), % Pick 'From' where last presence of function is {from, From} = lists:keyfind(from, 1, R), From_s = atom_to_list(From), % Pick 'functions' entry {functions, Fs} = lists:keyfind(functions, 1, R), % Pick 'removed' entry {removed, RF} = lists:keyfind(removed, 1, Fs), % Compose [{module, function, arity, release}, ...] lists:map(fun({Mm, FL}) -> lists:map(fun(Fa) -> Fa_s = atom_to_list(Fa), % Extract function and arity [Ff, Aa] = string:tokens(Fa_s, "/"), {Int, _} = string:to_integer(Aa), [{Mm, list_to_atom(Ff), Int, From_s}] end, FL) end, RF) end, Reldiffs). %%------------------------------------------------------------------------- %% @doc Get all added functions from diff directory %% @end %%------------------------------------------------------------------------- -spec get_added_functions(list()) -> list(). get_added_functions(DDir) -> % List all reldiffs files {ok, Reldiffs} = file:list_dir(DDir), % Loop over list and pick up all removed functions, linked to 'From' _AMF = lists:map(fun(F) -> % Load file {ok, [R]} = file:consult(filename:join(DDir, F)), % Pick 'To' where new presence of function is {to, To} = lists:keyfind(to, 1, R), To_s = atom_to_list(To), % Pick 'functions' entry {functions, Fs} = lists:keyfind(functions, 1, R), % Pick 'added' entry {new, NF} = lists:keyfind(new, 1, Fs), % Compose [{module, function, arity, release}, ...] lists:map(fun({Mm, FL}) -> lists:map(fun(Fa) -> Fa_s = atom_to_list(Fa), % Extract function and arity [Ff, Aa] = string:tokens(Fa_s, "/"), {Int, _} = string:to_integer(Aa), [{Mm, list_to_atom(Ff), Int, To_s}] end, FL) end, NF) end, Reldiffs). %%------------------------------------------------------------------------- %% @doc Get all removed modules from diff directory %% @end %%------------------------------------------------------------------------- -spec get_removed_modules(list()) -> list(). get_removed_modules(DDir) -> % List all reldiffs files {ok, Reldiffs} = file:list_dir(DDir), % Loop over list and pick up all removed modules, linked to 'To' _RMS = lists:map(fun(F) -> % Load file {ok, [R]} = file:consult(filename:join(DDir, F)), % Pick 'From' {from, From} = lists:keyfind(from, 1, R), % Pick 'modules' entry {modules, M} = lists:keyfind(modules, 1, R), % Pick 'removed' entry {removed, RM} = lists:keyfind(removed, 1, M), % Compose [{module, release}, ...] lists:map(fun(X) -> [{X, atom_to_list(From)}] end, RM) end, Reldiffs) . %%------------------------------------------------------------------------- %% @doc Geas database header %% @end %%------------------------------------------------------------------------- do_header(Io) -> Header = ["%% File: geas_db.hrl" ,"%% @author Generated by geas_db module " ,"%% @warning DO NOT EDIT BY HAND OR YOUR CHANGE WILL BE LOST" ,"%% @copyright Eric Pailleau " ,"%% @licence https://github.com/crownedgrouse/geas/blob/master/LICENCE" ,"%% @doc " ,"%% Geas database " ,"%% @end " ], io:put_chars(Io, erl_prettypr:format(erl_syntax:comment(Header))). %%------------------------------------------------------------------------- %% @doc defines in top of Geas database %% @end %%------------------------------------------------------------------------- do_defines(Io) -> DefMin = erl_syntax:attribute( erl_syntax:atom('define'), [erl_syntax:atom('GEAS_MIN_REL'), erl_syntax:string(hd(?REL_LIST))]), DefMax = erl_syntax:attribute( erl_syntax:atom('define'), [erl_syntax:atom('GEAS_MAX_REL'), erl_syntax:string(hd(lists:reverse(?REL_LIST)))]), io:put_chars(Io, erl_prettypr:format(DefMin)), io:nl(Io), io:put_chars(Io, erl_prettypr:format(DefMax)), io:nl(Io), io:nl(Io). %%------------------------------------------------------------------------- %% @doc Oldest modules in min release %% @end %%------------------------------------------------------------------------- do_min_modules(Io, Data) -> lists:foreach(fun({M, _}) -> io:format(Io,"rel_min({~p, _, _}) -> ?GEAS_MIN_REL ;~n", [M]) end, Data), % If no match, MFA is probably from a non core Erlang (i.e. private) module io:format(Io,"rel_min({_, _, _}) -> ~p .~n", [undefined]) . %%------------------------------------------------------------------------- %% @doc Add all removed modules in reldiffs %% @end %%------------------------------------------------------------------------- do_max_modules(Io, Data) -> lists:foreach(fun({M, R}) -> io:format(Io,"rel_max({~p, _, _}) -> ~p ;~n", [M, R]) end, Data), % If no match, MFA is still available in max release io:format(Io,"rel_max({_, _, _}) -> ?GEAS_MAX_REL.~n", []) . %%------------------------------------------------------------------------- %% @doc Oldest modules in min release %% @end %%------------------------------------------------------------------------- do_min_functions(Io, Data) -> lists:foreach(fun({M, F, A, R}) -> case dooblon(min, {M, F, A}) of true -> exception(min, {M, F, A, R}), io:format(Io,"%rel_min({~p, ~p, ~p}) -> ~p ;~n", [M, F, A, R]); false -> minlist({M, F, A}), io:format(Io,"rel_min({~p, ~p, ~p}) -> ~p ;~n", [M, F, A, R]) end end, Data). %%------------------------------------------------------------------------- %% @doc Add all removed modules in reldiffs %% @end %%------------------------------------------------------------------------- do_max_functions(Io, Data) -> lists:foreach(fun({M, F, A, R}) -> case dooblon(max, {M, F, A}) of true -> exception(max, {M, F, A, R}), io:format(Io,"%rel_max({~p, ~p, ~p}) -> ~p ;~n", [M, F, A, R]); false -> maxlist({M, F, A}), io:format(Io,"rel_max({~p, ~p, ~p}) -> ~p ;~n", [M, F, A, R]) end end, Data). %%------------------------------------------------------------------------- %% @doc Give release list %% @end %%------------------------------------------------------------------------- get_rel_list() -> ?REL_LIST. %%------------------------------------------------------------------------- %% @doc Give release list %% @end %%------------------------------------------------------------------------- exception(min, X) -> case get(exception_min) of undefined -> put(exception_min, [X]); L when is_list(L) -> put(exception_min, L ++ [X] ) ; _ -> throw(exception_min_error) end; exception(max, X) -> case get(exception_max) of undefined -> put(exception_max, [X]); L when is_list(L) -> put(exception_max, L ++ [X] ) ; _ -> throw(exception_max_error) end. dooblon(min, X) -> case get(minlist) of undefined -> put(minlist, [X]), false; L when is_list(L) -> lists:member(X, L) end; dooblon(max, X) -> case get(maxlist) of undefined -> put(maxlist, [X]), false; L when is_list(L) -> lists:member(X, L) end. minlist(X) -> case get(minlist) of undefined -> put(minlist, [X]); L when is_list(L) -> put(minlist, L ++ [X] ) ; _ -> throw(exception_minlist_error) end. maxlist(X) -> case get(maxlist) of undefined -> put(maxlist, [X]); L when is_list(L) -> put(maxlist, L ++ [X] ) ; _ -> throw(exception_maxlist_error) end.