From: Torbjorn T. <et...@us...> - 2005-09-08 10:53:22
|
Update of /cvsroot/jungerl/jungerl/lib/erlmerge/src In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv17396/src Modified Files: erlmerge.erl Log Message: Index: erlmerge.erl =================================================================== RCS file: /cvsroot/jungerl/jungerl/lib/erlmerge/src/erlmerge.erl,v retrieving revision 1.1 retrieving revision 1.2 diff -u -d -r1.1 -r1.2 --- erlmerge.erl 1 Sep 2005 08:52:29 -0000 1.1 +++ erlmerge.erl 8 Sep 2005 10:53:13 -0000 1.2 @@ -4,16 +4,13 @@ %%% Purpose : Installation tool for Erlang applications. %%%---------------------------------------------------------------------- -module(erlmerge). --behaviour(gen_server). %% External exports --export([start/0, start_link/0, run/0]). +-export([run/0]). -export([url/1]). -export([get_all_app_files/0, parse_app_info/2]). -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). -define(elog(F,A), error_logger:info_msg("~p(~w): "++F, [?MODULE, ?LINE | A])). @@ -22,6 +19,7 @@ -define(DB_NAME, erlmerge). + -record(app, { name, % name of application: atom() desc = "", % description: string() @@ -45,7 +43,9 @@ elib_dir, dryrun = false, update = false, - url = "http://www.trapexit.org/trapexit.erlmerge" + url = "http://www.trapexit.org/trapexit.erlmerge", + proxy = [], + make_used = [] }). -record(s, {}). @@ -54,63 +54,61 @@ %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- -start() -> - gen_server:start_link({local, erlmerge}, erlmerge, [], []). - -start_link() -> - gen_server:start_link({local, erlmerge}, erlmerge, [], []). - - run() -> application:start(inets), Opts = get_opts(), - analyse_switches(), - exec(Opts#options.cmd, Opts). + #options{elib_dir=Lib_dir, cmd=Cmd} = Opts, + add_libs_to_path( Lib_dir ), +% analyse_switches(), + exec( Cmd, Opts ), + init:stop(). get_opts() -> #options{cmd = l2a(os:getenv("EM_CMD")), - args = rm_space2(os:getenv("EM_ARGS")), + %% Remove leading and trailing space + args = string:strip(os:getenv("EM_ARGS")), elib_dir = os:getenv("ERL_LIB_DIR"), dryrun = list2bool(os:getenv("EM_DRYRUN"), false), update = list2bool(os:getenv("EM_UPDATE"), false), - url = os:getenv("EM_URL")}. - -analyse_switches() -> - case init:get_argument(erlmerge) of - {ok, Switches} -> put_switches(Switches); - _ -> ok - end. - -put_switches([[Key, Value] | T]) -> - put(l2a(Key), l2a(Value)), - put_switches(T); -put_switches([]) -> - ok. + url = os:getenv("EM_URL"), + proxy = os:getenv("EM_PROXY"), + make_used = os:getenv("MAKE_USED_FOR_ERLMERGE")}. +%analyse_switches() -> +% case init:get_argument(erlmerge) of +% {ok, Switches} -> put_switches(Switches); +% _ -> ok +% end. +% +%put_switches([[Key, Value] | T]) -> +% put(l2a(Key), l2a(Value)), +% put_switches(T); +%put_switches([]) -> +% ok. -rm_space([$\s|T]) -> rm_space(T); -rm_space([]) -> []; -rm_space(L) -> L. -%%% Remove leading and trailing space -rm_space2(L) -> lists:reverse(rm_space(lists:reverse(rm_space(L)))). +%rm_space([$\s|T]) -> rm_space(T); +%rm_space([]) -> []; +%rm_space(L) -> L. +% +%%%% Remove leading and trailing space +%rm_space2(L) -> lists:reverse(rm_space(lists:reverse(rm_space(L)))). exec(sync, P) -> - Url = P#options.url, - case url(Url) of + #options{url=Url, proxy=Proxy} = P, + case url(Url, Proxy) of {ok, File} -> ElibDir = P#options.elib_dir, - SyncFname = ElibDir ++ "/../erlmerge_DB/sync.erlmerge", + SyncFname = sync_fname( ElibDir ), file:write_file(SyncFname, l2b(File)), DbFname = db_fname(ElibDir), sync_db(SyncFname, DbFname); {error, econnrefused} -> - io:format("Unable to connect with: ~p~n", [P#options.url]); + io:format("Unable to connect with: ~p~n", [Url]); Else -> - ?elog("failed to retrieve URL=~p, got: ~p~n", [P#options.url, Else]), + ?elog("failed to retrieve URL=~p, got: ~p~n", [Url, Else]), {error, "failed to retrieve URL!"} - end, - init:stop(); + end; %%% exec(search, P) -> Args = P#options.args, @@ -126,8 +124,7 @@ end, L = dets:foldl(F, [], ?DB_NAME), db_close(), - print(lists:keysort(#app.name, L)), - init:stop(); + print(lists:keysort(#app.name, L)); %%% exec(delete, P) -> Args = l2a(P#options.args), @@ -135,8 +132,7 @@ Opts = db_wopts(), db_open(db_fname(ElibDir), Opts), delete(Args), - db_close(), - init:stop(); + db_close(); %%% exec(install, P0) -> P = P0#options{args = string:tokens(P0#options.args, " ")}, @@ -144,8 +140,7 @@ Opts = db_wopts(), db_open(db_fname(ElibDir), Opts), install(P), - db_close(), - init:stop(); + db_close(); %%% exec(dump, P) -> io:format("Opts = ~p~n", [P]), @@ -154,60 +149,53 @@ Opts = db_ropts(), db_open(db_fname(ElibDir), Opts), dump(Args), - db_close(), - init:stop(); + db_close(); %%% exec(setup, P) -> - ElibDir = P#options.elib_dir, Opts = db_wopts(), + ElibDir = P#options.elib_dir, db_open(db_fname(ElibDir), Opts), As = get_all_app_files(), Ps = parse_app_info(As, true), store_app_info(Ps, true), - db_close(), - init:stop(); + db_close(); %%% exec(suicide, P) -> ElibDir = P#options.elib_dir, Opts = db_ropts(), db_open(db_fname(ElibDir), Opts), - rm_non_orig_apps(ElibDir), + rm_non_original_applications(ElibDir), db_close(), - rm_erlmerge(ElibDir), - init:stop(); + rm_erlmerge(ElibDir); %%% exec(Cmd, P) -> - io:format("<ERROR> Unknown command: ~p , Opts: ~p~n", [Cmd, P]), - init:stop(). + io:format("<ERROR> Unknown command: ~p , Opts: ~p~n", [Cmd, P]). rm_erlmerge(ElibDir) -> %% Remove packet database - Dir = filename:join([ElibDir, "..", "erlmerge_DB"]), - os:cmd("(rm -rf " ++ Dir ++ ")"), + Dir = erlmerge_db_directory(ElibDir), + rm_all( Dir ), io:format("Removed: ~s~n", [Dir]), %% Remove the application(s) , all versions - os:cmd("(rm -rf " ++ ElibDir ++ "/erlmerge-*)"), + Wildcard = filename:join( [ElibDir, "erlmerge-*"] ), + lists:foreach( fun rm_all/1, filelib:wildcard(Wildcard) ), io:format("Removed: erlmerge application~n", []), %% Remove the erlmerge script (the link is removed from the script itself) Script = filename:join([ElibDir, "..", "bin", "erlmerge"]), - os:cmd("(rm -rf " ++ Script ++ ")"), + ok = file:delete( Script ), io:format("Removed: ~s~n", [Script]). -rm_non_orig_apps(ElibDir) -> - As = get_non_orig_apps(), - rm_apps(As, ElibDir). - -rm_apps([A|T], ElibDir) when A#app.installed == true -> - Dir = lists:concat([A#app.name, "-", A#app.vsn]), - os:cmd("(cd " ++ ElibDir ++ "; rm -rf " ++ Dir ++ ")"), - io:format("Removed: ~s~n", [filename:join([ElibDir, Dir])]), - rm_apps(T, ElibDir); -rm_apps([A|T], ElibDir) when A#app.installed == false -> - rm_apps(T, ElibDir); -rm_apps([], _) -> - ok. +%rm_apps([A|T], ElibDir) when A#app.installed == true -> +% Dir = lists:concat([A#app.name, "-", A#app.vsn]), +% os:cmd("(cd " ++ ElibDir ++ "; rm -rf " ++ Dir ++ ")"), +% io:format("Removed: ~s~n", [filename:join([ElibDir, Dir])]), +% rm_apps(T, ElibDir); +%rm_apps([A|T], ElibDir) when A#app.installed == false -> +% rm_apps(T, ElibDir); +%rm_apps([], _) -> +% ok. get_non_orig_apps() -> F = fun(A,Acc) when A#app.original == false -> @@ -221,7 +209,8 @@ install(P) -> case analyse_deps(P#options.args) of {ok, Apps} -> % list of {App,Vsn} - analyse_versions(P, Apps); + Packages_to_install = analyse_versions(P, Apps), + fetch_tar_balls_and_install(P, Packages_to_install); {error, Emsg} -> io:format("<ERROR>: ~s~n", [Emsg]); {cycle, Cycle} -> @@ -236,17 +225,19 @@ case catch not_installed(Uapps) of {not_found, {App, Vsn}} -> io:format(green("~nDependency check failed~n~n"), []), - io:format(green("Missing Application: ~p-~s~n"), [App, Vsn]); - + io:format(green("Missing Application: ~p-~s~n"), [App, Vsn]), + []; [] -> - io:format(green("~nNothing to merge~n~n"), []); + io:format(green("~nNothing to merge~n~n"), []), + []; L when P#options.dryrun == false -> - fetch_tar_balls(P, L); + L; L when P#options.dryrun == true -> io:format(green("~nThese are the packages that I would merge" ", in order:~n~n"),[]), F = fun(A) -> install_reason(A) end, - lists:foreach(F, L) + lists:foreach(F, L), + [] end. %%% If the Update switch is on, then find the latest @@ -324,70 +315,18 @@ -fetch_tar_balls(P, L) -> - case fetch_tar_balls(P, L, [], []) of - {Fetched, []} -> unpack_and_make(P, Fetched); - {Fetched, NotFetched} -> rm_fetched(P, Fetched, NotFetched) - end. - -fetch_tar_balls(P, [H|T], Fetched, NotFetched) -> - Location = H#app.loc, - Fname = fname(H#app.loc), - case is_already_fetched(P, Fname) of - true -> - io:format(green("already retrieved:")++" ~s~n", [Fname]), - fetch_tar_balls(P, T, [H|Fetched], NotFetched); - false -> - case url(Location) of - {ok, File} -> - io:format(green("retrieved:")++" ~s~n", [Location]), - ElibDir = P#options.elib_dir, - PathName = distfiles(ElibDir) ++ Fname, - file:write_file(PathName, l2b(File)), - fetch_tar_balls(P, T, [H|Fetched], NotFetched); - {error, econnrefused} -> - io:format(green("Unable to connect to:")++" ~p~n", [Location]), - fetch_tar_balls(P, T, Fetched, [H|NotFetched]); - Else -> - io:format(green("failed to retrieve:")++" ~p, got: ~p~n", [Location, Else]), - fetch_tar_balls(P, T, Fetched, [H|NotFetched]) - end - end; -fetch_tar_balls(_P, [], Fetched, NotFetched) -> - {lists:reverse(Fetched), % keep the order - NotFetched}. - -unpack_and_make(P, [H|T]) -> - ElibDir = P#options.elib_dir, - Fname = fname(H#app.loc), - PathName = distfiles(ElibDir) ++ Fname, - %% Unpack the tar-ball - io:format(green("unpacking:")++" ~s.....", [Fname]), - Res = os:cmd("tar -xz -f " ++ PathName ++ " -C " ++ ElibDir), - io:format("~s~n", [Res]), - %% Run make - Dir = lists:concat([H#app.name,"-",H#app.vsn]), - io:format(green("compiling:")++" ~s.....", [Dir]), - %%?elog("~s~n", ["(cd " ++ ElibDir ++ "/" ++ Dir ++ "; make)"]), - Res2 = os:cmd("(cd " ++ ElibDir ++ "/" ++ Dir ++ "; make)"), - io:format("~s~n", [Res2]), - %% Mark the application as: installed - db_insert(H#app{installed = true}), - unpack_and_make(P, T); -unpack_and_make(_P, []) -> - io:format(green("finished!")++"~n", []). - is_already_fetched(P, Fname) -> ElibDir = P#options.elib_dir, - PathName = distfiles(ElibDir) ++ Fname, + PathName = filename:join( [distfiles(ElibDir), Fname] ), case file:read_file_info(PathName) of {ok, _} -> true; _ -> false end. -distfiles(Dir) -> - Dir ++ "/../erlmerge_DB/distfiles/". +distfiles(ElibDir) -> + filename:join( [erlmerge_db_directory(ElibDir), "distfiles"] ). +%%% the name suggest something more than just a printout... rm_fetched(_P, _Fetched, NotFetched) -> F = fun(A) -> io:format(green("Failed to retrieve:")++" ~s.....", [A#app.loc]) @@ -396,7 +335,7 @@ fname(Path) -> - hd(lists:reverse(string:tokens(Path, "/"))). + filename:basename( Path ). %%% Check which of the needed applications that @@ -477,7 +416,7 @@ case db_lookup(l2a(H)) of false -> io:format("Application: ~p not found~n", [H]), - throw({error, "application not found: " ++ H}); + throw({error, "application not found: " ++ a2l( H )}); {ok, [A]} -> digraph:add_vertex(G, A#app.name, A#app.vsn), F = fun({Da,Dv}) -> @@ -537,68 +476,6 @@ end_colour() -> [27,91,51,57,59,52,57,59,48,48,109]. -db_fname(ElibDir) -> - ElibDir++"/../erlmerge_DB/erlmerge.dets". - -db_wopts() -> - [{type, set}, {keypos, #app.name}]. - -db_ropts() -> - [{access,read}, {type, set}, {keypos, #app.name}]. - -%%%---------------------------------------------------------------------- -%%% Callback functions from gen_server -%%%---------------------------------------------------------------------- - -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%%---------------------------------------------------------------------- -init([]) -> - {ok, #s{}}. - -%%---------------------------------------------------------------------- -%% Func: handle_call/3 -%% Returns: {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | (terminate/2 is called) -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- -handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. - -%%---------------------------------------------------------------------- -%% Func: handle_cast/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- -handle_cast(_Msg, State) -> - {noreply, State}. - -%%---------------------------------------------------------------------- -%% Func: handle_info/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- -handle_info(_Info, State) -> - {noreply, State}. - -%%---------------------------------------------------------------------- -%% Func: terminate/2 -%% Purpose: Shutdown the server -%% Returns: any (ignored by gen_server) -%%---------------------------------------------------------------------- -terminate(_Reason, _State) -> - ok. - %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- @@ -617,8 +494,10 @@ case db_lookup(App) of false -> io:format("Application: ~p not found~n", [App]); + {ok, [#app{installed = false}]} -> + io:format("Application: ~p not installed~n", [App]); {ok, [Val]} -> - os:cmd("rm -rf " ++ code:lib_dir(App)), + rm_all( code:lib_dir(App) ), db_insert(Val#app{installed = false}), io:format(green("Deleted application: ")++"~p~n", [App]) end. @@ -653,6 +532,27 @@ {error, Else} end. +url( Url, [] ) -> + url( Url ); +url( Url, Proxy ) -> + Options = [{proxy, {url_proxy(Proxy), ["localhost"]}}], + ok = http:set_options( Options ), + url( Url ). + +url_proxy( Proxy ) -> + case regexp:split( Proxy, ":" ) of + {ok, [Hostname]} -> + {Hostname, 80}; % default port + {ok, [Hostname, Port]} -> + {Hostname, erlang:list_to_integer( Port )} + end. + +erlmerge_db_directory(ElibDir) -> + filename:join( [ElibDir, "..", "erlmerge_DB"] ). + +sync_fname(ElibDir) -> + filename:join( [erlmerge_db_directory(ElibDir), "sync.erlmerge"] ). + sync_db(SyncFname, DbFname) -> case file:consult(SyncFname) of {ok, L} -> @@ -667,8 +567,17 @@ end. -db_open(Fname) -> - db_open(Fname, [{type, bag}]). +db_fname(ElibDir) -> + filename:join( [erlmerge_db_directory(ElibDir), "erlmerge.dets"] ). + +db_wopts() -> + [{type, set}, {keypos, #app.name}]. + +db_ropts() -> + [{access,read}, {type, set}, {keypos, #app.name}]. + +%db_open(Fname) -> +% db_open(Fname, [{type, bag}]). db_open(Fname, Opts) -> {ok, _} = dets:open_file(?DB_NAME, [{file, Fname}|Opts]). @@ -676,8 +585,8 @@ db_close() -> dets:close(?DB_NAME). -db_insert(Key, Value) -> - dets:insert(?DB_NAME, {Key, Value}). +%db_insert(Key, Value) -> +% dets:insert(?DB_NAME, {Key, Value}). db_insert(A) when record(A, app) -> dets:insert(?DB_NAME, A). @@ -701,7 +610,8 @@ {ok, Apps} = file:list_dir(LibDir), F = fun(A, Acc) -> [A1|_] = string:tokens(A, "-"), - App = LibDir ++ "/" ++ A ++ "/ebin/" ++ A1 ++ ".app", +% App = LibDir ++ "/" ++ A ++ "/ebin/" ++ A1 ++ ".app", + App = filename:join( [LibDir, A, "ebin", lists:append( A1, ".app")] ), case file:consult(App) of {ok, Res} -> Res ++ Acc; _ -> Acc @@ -770,4 +680,147 @@ list2bool("false", _) -> false; list2bool(_, Default) -> Default. -sleep(T) -> receive after T -> true end. +%sleep(T) -> receive after T -> true end. + + +fetch_tar_balls_and_install(_P, []) -> ok; +fetch_tar_balls_and_install(P, L) -> + case fetch_tar_balls(P, L, [], []) of + {Fetched, []} -> unpack_and_make(P, Fetched); + {Fetched, NotFetched} -> rm_fetched(P, Fetched, NotFetched) + end. + +fetch_tar_balls(P, [H|T], Fetched, NotFetched) -> + Location = H#app.loc, + Fname = fname(H#app.loc), + case is_already_fetched(P, Fname) of + true -> + io:format(green("already retrieved:")++" ~s~n", [Fname]), + fetch_tar_balls(P, T, [H|Fetched], NotFetched); + false -> + #options{proxy=Proxy} = P, + case url(Location, Proxy) of + {ok, File} -> + io:format(green("retrieved:")++" ~s~n", [Location]), + ElibDir = P#options.elib_dir, + PathName = filename:join( [distfiles(ElibDir), Fname] ), + file:write_file(PathName, l2b(File)), + fetch_tar_balls(P, T, [H|Fetched], NotFetched); + {error, econnrefused} -> + io:format(green("Unable to connect to:")++" ~p~n", [Location]), + fetch_tar_balls(P, T, Fetched, [H|NotFetched]); + Else -> + io:format(green("failed to retrieve:")++" ~p, got: ~p~n", [Location, Else]), + fetch_tar_balls(P, T, Fetched, [H|NotFetched]) + end + end; +fetch_tar_balls(_P, [], Fetched, NotFetched) -> + {lists:reverse(Fetched), % keep the order + NotFetched}. + +unpack_and_make(P, Fetched) -> + #options{elib_dir=ElibDir, make_used=Make} = P, + {ok, Current_directory} = file:get_cwd(), + %% work in the lib directory + ok = file:set_cwd( ElibDir ), + Fun = fun (#app{loc=Loc, name=Name, vsn=Vsn} = App) -> + Fname = fname( Loc ), + PathName = filename:join( [distfiles( ElibDir ), Fname] ), + %% Unpack the tar-ball + io:format(green("unpacking:")++" ~s.....", [Fname]), + Application = lists:concat([Name,"-",Vsn]), +% Dir = filename:join( [ElibDir, Application] ), + case is_untar_ok( PathName, Application, Make ) of + true -> + %% Run make + Installed = is_make_ok( Application, Make ), + db_insert(App#app{installed = Installed}); + false -> + io:format("failed unpacking: ~s.....", [Fname]) + end + end, + lists:foreach(Fun, Fetched), + %% back to original directory + ok = file:set_cwd(Current_directory), + io:format(green("finished!")++"~n", []). + +is_untar_ok( Tarfile, Target, Make ) -> + Tar = lists:append( ["tar -xzf ", Tarfile] ), + %% make used for erlmerge was gnumake. + %% wild assumption: gnutar is in the same directory as gnumake. + Gtar = filename:join( [filename:dirname( Make ), "gtar"] ), + Gnu_tar = lists:append( [Gtar, " -xzf ", Tarfile] ), + Gtar2 = filename:join( [filename:dirname( Make ), "tar"] ), + Gnu_tar2 = lists:append( [Gtar2, " -xzf ", Tarfile] ), + More_tar = lists:append( ["gunzip -c ", Tarfile, " | tar -xf -"] ), + is_untar_ok_alternatives( Target, [Tar, Gnu_tar, Gnu_tar2, More_tar] ). + +is_untar_ok_alternatives( _Target, [] ) -> false; +is_untar_ok_alternatives( Target, [Tar|T] ) -> + Res = os:cmd(Tar), + io:format("~s~n", [Res]), + case filelib:is_dir( Target ) of + true -> + true; + false -> + is_untar_ok_alternatives( Target, T ) + end. + +is_make_ok( Application, Make ) -> + {ok, Current_directory} = file:get_cwd(), + %% work in the application directory + ok = file:set_cwd(Application), + %% make used for erlmerge was gnumake. + %% wild assumption: other gnu programs are in the same directory as gnumake. + %% if original make fails, add this directory to the path + New_path = lists:append( ["PATH=", filename:dirname( Make ), ":$PATH"] ), + Make2 = lists:append( [New_path, " ", filename:basename( Make )] ), + Result = is_make_ok_alternatives(Application, ["make", Make, Make2]), + ok = file:set_cwd(Current_directory), + Result. + +is_make_ok_alternatives( _Application, [] ) -> false; +is_make_ok_alternatives( Application, [Make|T] ) -> + io:format(green("compiling:")++" ~s.....", [Application]), + %%?elog("~s~n", ["(cd " ++ ElibDir ++ "/" ++ Dir ++ "; make)"]), + Res = os:cmd( lists:append([Make, " ; echo $?"]) ), + io:format("~s~n", [Res]), + case erlang:list_to_integer( erlang:hd(lists:reverse( string:tokens(Res, "\n") )) ) of + 0 -> + true; + _Else -> + is_make_ok_alternatives( Application, T ) + end. + + +%%% is this applications or libraries? +rm_non_original_applications( ElibDir ) -> + {ok, Current_directory} = file:get_cwd(), + ok = file:set_cwd( ElibDir ), + Fun = fun (#app{installed=true, name=Name, vsn=Vsn}) -> + Application = lists:concat( [Name, "-", Vsn] ), + rm_all( Application ), + io:format("Removed: ~s~n", [a2l( Name )]); + (#app{installed=false}) -> ok + end, + lists:foreach( Fun, get_non_orig_apps() ), + ok = file:set_cwd(Current_directory). + +rm_all( Directory ) -> + case filelib:is_dir( Directory ) of + true -> + {ok, Contents} = file:list_dir( Directory ), + % create absolut path + Fun = fun ( Name ) -> + filename:join( [Directory, Name] ) + end, + lists:foreach( fun rm_all/1, lists:map(Fun, Contents) ), + ok = file:del_dir( Directory ); + false -> % file + ok = file:delete( Directory ) + end. + + +add_libs_to_path( Lib_dir ) -> + Ebin_directories = filelib:wildcard( filename:join([Lib_dir, "*", ebin]) ), + ok = code:add_pathsz( Ebin_directories ). |