Packages
geas
2.7.11
2.11.1
2.11.0
retired
2.10.1
2.10.0
retired
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.14
2.7.13
2.7.12
2.7.11
2.7.10
2.7.9
2.7.8
2.7.7
2.7.6
2.7.5
2.7.4
2.7.3
2.7.2
2.7.1
2.7.0
2.6.2
2.6.1
2.6.0
retired
2.5.1
2.5.0
retired
2.4.6
2.4.5
retired
2.4.4
retired
2.4.3
retired
2.4.2
retired
2.4.1
retired
2.4.0
2.3.0
2.2.0
Guess Erlang Application Scattering
Current section
Files
Jump to
Current section
Files
src/geas_db.erl
%%%-------------------------------------------------------------------
%%% File: geas_doc.erl
%%% @author Eric Pailleau <geas@crownedgrouse.com>
%%% @copyright 2022 crownedgrouse.com
%%% @doc
%%% Guess Erlang Application 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 <geas@crownedgrouse.com>").
-export([generate/2, get_rel_list/0, newgenerate/2]).
% 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", "22.1", "22.2", "22.3",
"23.0", "23.1", "23.2", "23.3",
"24.0", "24.1", "24.2", "24.3",
"25.0", "25.1", "25.2"]).
%% This module generate the geas_db.hrl
%% providing the min and max release of any Erlang/OTP function
newgenerate(DirSource, DirTarget)
->
try
true = filelib:is_dir(DirSource),
true = filelib:is_dir(DirTarget),
Target = filename:join(DirTarget, "geas_db.dets"),
% Create Target as a DETS, remove if any
file:delete(Target),
{ok, geas_db} = dets:open_file(geas_db, [{file, Target},{ram_file, true}]),
% List all files under Dir/doc/relinfos/term
WildcardR = filename:join([DirSource, "doc/relinfos/term/R*"]),
WildcardN = filename:join([DirSource, "doc/relinfos/term/[0-9]*"]),
FilesR = lists:sort(filelib:wildcard(WildcardR)),
FilesN = lists:sort(filelib:wildcard(WildcardN)),
Files = FilesR ++ FilesN,
%io:format("Files: ~p~n", [Files]),
Fun = fun(F) ->
io:format("Treating: ~ts~n", [F]),
% Load file
{ok, [Terms]} = file:consult(F),
%io:format("Terms: ~p~n", [Terms]),
R = erlang:atom_to_list(proplists:get_value(release, Terms, "")),
%_O = erlang:atom_to_list(proplists:get_value(version, Terms, "")),
%_D = erlang:atom_to_list(proplists:get_value(driver_version, Terms, "")),
%_N = erlang:atom_to_list(proplists:get_value(nif_version, Terms, "")),
X = proplists:get_value(mfa, Terms),
Fun2 = fun({M, L}) ->
Fun3 = fun(FA, Acc) ->
FAS = erlang:atom_to_list(FA),
[AS | Rest] = lists:reverse(filename:split(FAS)),
FS = filename:join(lists:reverse(Rest)),
Acc ++ [{M, erlang:list_to_atom(FS), erlang:list_to_integer(AS)}]
end,
Acc = lists:foldl(Fun3, [], L),
%io:format("Treating: ~p~n", [Acc]),
% Insert in Dets
Fun4 = fun(Key) ->
case dets:lookup(geas_db, Key) of
{error, Reason} -> throw({Key, Reason}) ;
[] -> % Insert
ok = dets:insert(geas_db, {Key, [R]}) ;
[{Key, V}] -> %
ok = dets:insert(geas_db, {Key, V ++ [R]})
end
end,
ok = lists:foreach(Fun4, Acc)
end,
ok = lists:foreach(Fun2, X)
end,
ok = lists:foreach(Fun, Files)
catch
%_:Error:Stack -> io:format("Error: ~p~n~p~n", [Error, Stack])
_:Error -> io:format("Error: ~p~n", [Error])
after
ok = dets:sync(geas_db),
io:format("Converting release list to {min, max, semver, cont}~n", []),
% Traverse for Semver and continuous flag
FunS = fun({Key, L}) ->
case is_list(L) of
true ->
%io:format("~p~n", [L]),
Min = geas:lowest_version(L),
Max = geas:highest_version(L),
{ok, SemVer} = geas_semver:l2s(lists:usort(L)),
Cont = case string:find(SemVer, "||") of
nomatch -> true ;
_ -> false
end,
% trace some module here
% {M,_,_} = Key,
% case M =:= pg of
% true ->
% io:format("~p~n", [{Key, {Min, Max, SemVer, Cont}}]);
% false ->
% ok
% end,
V = {Key, {Min, Max, SemVer, Cont}},
%ok = dets:insert(geas_db, {Key, {Min, Max, SemVer, Cont}}),
{continue, V};
false
-> continue
end
end,
Res = lists:sort(dets:traverse(geas_db, FunS)),
ok = dets:close(geas_db),
{C, U} = lists:partition(fun({_, {_,_,_,Y}}) -> Y end, Res) ,
NbO = length(Res),
io:format("Number of MFAs : ~p~n", [NbO]),
% Traverse for functions
NbC = length(C),
NbD = length(U),
io:format("Number of continuous MFAs : ~p~n", [NbC]),
io:format("Number of discontinuous MFAs: ~p~n", [NbD]),
% Writing a Report
Report = filename:join(DirTarget, "geas_db.txt"),
file:delete(Report),
{ok, ReportIo} = file:open(Report, [write]),
lists:foreach(fun({{M,F,A}, {_,_, SemVer, _}}) -> io:format(ReportIo,"~p:~p/~p : ~p~n", [M,F,A,SemVer]) end, U),
% Writing geas_db.hrl
Hrl = filename:join(DirTarget, "geas_db.hrl"),
file:delete(Hrl),
{ok, TargetIo} = file:open(Hrl, [write]),
do_header(TargetIo),
do_defines(TargetIo),
% Add former rel_min / rel_max functions
io:format(TargetIo, "rel_min(X) -> {Min, _, _, _} = rel_info(X), Min.~n", []),
io:format(TargetIo, "rel_max(X) -> {_, Max, _, _} = rel_info(X), Max.~n~n", []),
lists:foreach(fun({K, {Min, Max, SemVer, Cont}}) ->
io:format(TargetIo, "rel_info(~w) -> {~p, ~p, ~p, ~p};~n", [K, Min, Max, SemVer, Cont])
end, Res),
io:format(TargetIo, "rel_info({_, _, _}) -> {?GEAS_MIN_REL, ?GEAS_MAX_REL, \">=\" ++ ?GEAS_MIN_REL ++ \"<=\" ++ ?GEAS_MAX_REL, true}.~n", []),
file:close(TargetIo)
end.
%%-------------------------------------------------------------------------
%% @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)
->
put(trace, pg),
{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 <geas@crownedgrouse.com>"
,"%% @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, _}) ->
case ( get(trace) =:= M ) of
true -> erlang:display({?FUNCTION_NAME, M}) ;
_ -> ok
end,
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}) ->
case ( get(trace) =:= M ) of
true -> erlang:display({?FUNCTION_NAME, M, R}) ;
_ -> ok
end,
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 ( get(trace) =:= M ) of
true -> erlang:display({?FUNCTION_NAME, M, F, A, R}) ;
_ -> ok
end,
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 ( get(trace) =:= M ) of
true -> erlang:display({?FUNCTION_NAME, M, F, A, R}) ;
_ -> ok
end,
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.