| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  | #!/usr/bin/env escript | 
					
						
							|  |  |  | %% -*- erlang -*- | 
					
						
							|  |  |  | -mode(compile). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | %% The contents of this file are subject to the Mozilla Public License | 
					
						
							|  |  |  | %% Version 1.1 (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.mozilla.org/MPL/ | 
					
						
							|  |  |  | %% | 
					
						
							|  |  |  | %% Software distributed under the License is distributed on an "AS IS" | 
					
						
							|  |  |  | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See | 
					
						
							|  |  |  | %% the License for the specific language governing rights and | 
					
						
							|  |  |  | %% limitations under the License. | 
					
						
							|  |  |  | %% | 
					
						
							|  |  |  | %% The Original Code is RabbitMQ. | 
					
						
							|  |  |  | %% | 
					
						
							| 
									
										
										
										
											2015-08-31 19:44:45 +08:00
										 |  |  | %% The Initial Developer of the Original Code is Pivotal Software, Inc. | 
					
						
							| 
									
										
										
										
											2015-05-24 09:24:11 +08:00
										 |  |  | %% Copyright (c) 2010-2015 Pivotal Software, Inc.  All rights reserved. | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  | %% | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | main(["-h"]) -> | 
					
						
							|  |  |  |     io:format("usage: check_xref PluginDirectory (options)~n" | 
					
						
							|  |  |  |               "options:~n" | 
					
						
							|  |  |  |               "      -q - quiet mode (only prints errors)~n" | 
					
						
							|  |  |  |               "      -X - disables all filters~n"); | 
					
						
							|  |  |  | main([PluginsDir|Argv]) -> | 
					
						
							| 
									
										
										
										
											2012-07-05 00:35:17 +08:00
										 |  |  |     put({?MODULE, quiet}, lists:member("-q", Argv)), | 
					
						
							|  |  |  |     put({?MODULE, no_filters}, lists:member("-X", Argv)), | 
					
						
							| 
									
										
										
										
											2012-07-04 18:52:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-07-05 00:38:13 +08:00
										 |  |  |     {ok, Cwd} = file:get_cwd(), | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |     code:add_pathz(filename:join(Cwd, "ebin")), | 
					
						
							|  |  |  |     LibDir = filename:join(Cwd, "lib"), | 
					
						
							| 
									
										
										
										
											2012-07-04 18:52:14 +08:00
										 |  |  |     case filelib:is_dir(LibDir) of | 
					
						
							|  |  |  |         false -> ok; | 
					
						
							| 
									
										
										
										
											2012-07-05 00:41:16 +08:00
										 |  |  |         true  -> os:cmd("rm -rf " ++ LibDir) | 
					
						
							| 
									
										
										
										
											2012-07-04 18:52:14 +08:00
										 |  |  |     end, | 
					
						
							| 
									
										
										
										
											2012-07-16 20:11:18 +08:00
										 |  |  |     Rc = try | 
					
						
							|  |  |  |              check(Cwd, PluginsDir, LibDir, checks()) | 
					
						
							|  |  |  |          catch | 
					
						
							|  |  |  |              _:Err -> | 
					
						
							|  |  |  |                  io:format(user, "failed: ~p~n", [Err]), | 
					
						
							|  |  |  |                  1 | 
					
						
							|  |  |  |          end, | 
					
						
							|  |  |  |     shutdown(Rc, LibDir). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | shutdown(Rc, LibDir) -> | 
					
						
							|  |  |  |     os:cmd("rm -rf " ++ LibDir), | 
					
						
							|  |  |  |     erlang:halt(Rc). | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | check(Cwd, PluginsDir, LibDir, Checks) -> | 
					
						
							|  |  |  |     {ok, Plugins} = file:list_dir(PluginsDir), | 
					
						
							|  |  |  |     ok = file:make_dir(LibDir), | 
					
						
							| 
									
										
										
										
											2013-01-16 02:39:00 +08:00
										 |  |  |     put({?MODULE, third_party}, []), | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |     [begin | 
					
						
							|  |  |  |         Source = filename:join(PluginsDir, Plugin), | 
					
						
							|  |  |  |         Target = filename:join(LibDir, Plugin), | 
					
						
							|  |  |  |         IsExternal = external_dependency(Plugin), | 
					
						
							| 
									
										
										
										
											2012-07-05 00:38:13 +08:00
										 |  |  |         AppN = case IsExternal of | 
					
						
							|  |  |  |                    true  -> filename:join(LibDir, unmangle_name(Plugin)); | 
					
						
							|  |  |  |                    false -> filename:join( | 
					
						
							|  |  |  |                               LibDir, filename:basename(Plugin, ".ez")) | 
					
						
							|  |  |  |                end, | 
					
						
							| 
									
										
										
										
											2012-07-04 18:52:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |         report(info, "mkdir -p ~s~n", [Target]), | 
					
						
							|  |  |  |         filelib:ensure_dir(Target), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         report(info, "cp ~s ~s~n", [Source, Target]), | 
					
						
							|  |  |  |         {ok, _} = file:copy(Source, Target), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         report(info, "unzip -d ~s ~s~n", [LibDir, Target]), | 
					
						
							|  |  |  |         {ok, _} = zip:unzip(Target, [{cwd, LibDir}]), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         UnpackDir = filename:join(LibDir, filename:basename(Target, ".ez")), | 
					
						
							|  |  |  |         report(info, "mv ~s ~s~n", [UnpackDir, AppN]), | 
					
						
							|  |  |  |         ok = file:rename(UnpackDir, AppN), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         code:add_patha(filename:join(AppN, "ebin")), | 
					
						
							| 
									
										
										
										
											2012-07-05 00:39:57 +08:00
										 |  |  |         case IsExternal of | 
					
						
							|  |  |  |             true -> App = list_to_atom(hd(string:tokens(filename:basename(AppN), | 
					
						
							|  |  |  |                                                         "-"))), | 
					
						
							|  |  |  |                     report(info, "loading ~p~n", [App]), | 
					
						
							|  |  |  |                     application:load(App), | 
					
						
							|  |  |  |                     store_third_party(App); | 
					
						
							|  |  |  |             _    -> ok | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |         end | 
					
						
							|  |  |  |      end || Plugin <- Plugins, | 
					
						
							|  |  |  |             lists:suffix(".ez", Plugin)], | 
					
						
							| 
									
										
										
										
											2012-07-04 18:52:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-07-04 19:12:25 +08:00
										 |  |  |     RabbitAppEbin = filename:join([LibDir, "rabbit", "ebin"]), | 
					
						
							|  |  |  |     filelib:ensure_dir(filename:join(RabbitAppEbin, "foo")), | 
					
						
							|  |  |  |     {ok, Beams} = file:list_dir("ebin"), | 
					
						
							|  |  |  |     [{ok, _} = file:copy(filename:join("ebin", Beam), | 
					
						
							| 
									
										
										
										
											2012-07-05 00:38:13 +08:00
										 |  |  |                          filename:join(RabbitAppEbin, Beam)) || Beam <- Beams], | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |     xref:start(?MODULE), | 
					
						
							|  |  |  |     xref:set_default(?MODULE, [{verbose, false}, {warnings, false}]), | 
					
						
							|  |  |  |     xref:set_library_path(?MODULE, code:get_path()), | 
					
						
							|  |  |  |     xref:add_release(?MODULE, Cwd, {name, rabbit}), | 
					
						
							|  |  |  |     store_unresolved_calls(), | 
					
						
							| 
									
										
										
										
											2012-07-05 00:38:13 +08:00
										 |  |  |     Results = lists:flatten([perform_analysis(Q) || Q <- Checks]), | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |     report(Results). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | %% | 
					
						
							|  |  |  | %% Analysis | 
					
						
							|  |  |  | %% | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | perform_analysis({Query, Description, Severity}) -> | 
					
						
							| 
									
										
										
										
											2012-06-21 18:05:19 +08:00
										 |  |  |     perform_analysis({Query, Description, Severity, fun(_) -> false end}); | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  | perform_analysis({Query, Description, Severity, Filter}) -> | 
					
						
							|  |  |  |     report_progress("Checking whether any code ~s " | 
					
						
							|  |  |  |                     "(~s)~n", [Description, Query]), | 
					
						
							|  |  |  |     case analyse(Query) of | 
					
						
							|  |  |  |         {ok, Analysis} -> | 
					
						
							|  |  |  |             [filter(Result, Filter) || | 
					
						
							|  |  |  |                 Result <- process_analysis(Query, Description, | 
					
						
							|  |  |  |                                            Severity, Analysis)]; | 
					
						
							|  |  |  |         {error, Module, Reason} -> | 
					
						
							|  |  |  |             {analysis_error, {Module, Reason}} | 
					
						
							|  |  |  |     end. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | partition(Results) -> | 
					
						
							|  |  |  |     lists:partition(fun({{_, L}, _}) -> L =:= error end, Results). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | analyse(Query) when is_atom(Query) -> | 
					
						
							|  |  |  |     xref:analyse(?MODULE, Query, [{verbose, false}]); | 
					
						
							|  |  |  | analyse(Query) when is_list(Query) -> | 
					
						
							|  |  |  |     xref:q(?MODULE, Query). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | process_analysis(Query, Tag, Severity, Analysis) when is_atom(Query) -> | 
					
						
							|  |  |  |     [{{Tag, Severity}, MFA} || MFA <- Analysis]; | 
					
						
							|  |  |  | process_analysis(Query, Tag, Severity, Analysis) when is_list(Query) -> | 
					
						
							|  |  |  |     [{{Tag, Severity}, Result} || Result <- Analysis]. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | checks() -> | 
					
						
							|  |  |  |    [{"(XXL)(Lin) ((XC - UC) || (XU - X - B))", | 
					
						
							|  |  |  |      "has call to undefined function(s)", | 
					
						
							| 
									
										
										
										
											2013-10-29 01:21:02 +08:00
										 |  |  |      error, filters()}, | 
					
						
							|  |  |  |     {"(Lin) (L - LU)", | 
					
						
							|  |  |  |      "has unused local function(s)", | 
					
						
							|  |  |  |      error, filters()}, | 
					
						
							|  |  |  |     {"(E | \"(rabbit|amqp).*\":_/_ || \"gen_server2?\":call/2)", | 
					
						
							|  |  |  |      "has 5 sec timeout in", | 
					
						
							| 
									
										
										
										
											2013-10-24 19:12:00 +08:00
										 |  |  |      error, filters()}, | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |     {"(Lin) (LU * (X - XU))", | 
					
						
							| 
									
										
										
										
											2013-10-29 01:21:02 +08:00
										 |  |  |      "has exported function(s) only used locally", | 
					
						
							|  |  |  |      warning, filters()}, | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |     {"(Lin) (DF * (XU + LU))", "used deprecated function(s)", | 
					
						
							| 
									
										
										
										
											2013-10-29 01:21:02 +08:00
										 |  |  |      warning, filters()}]. | 
					
						
							|  |  |  | %%    {"(Lin) (X - XU)", "possibly unused export", | 
					
						
							|  |  |  | %%     warning, fun filter_unused/1}]. | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | %% | 
					
						
							|  |  |  | %% noise filters (can be disabled with -X) - strip uninteresting analyses | 
					
						
							|  |  |  | %% | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | filter(Result, Filter) -> | 
					
						
							|  |  |  |     case Filter(Result) of | 
					
						
							|  |  |  |         false -> Result; | 
					
						
							|  |  |  |         true  -> []  %% NB: this gets flattened out later on.... | 
					
						
							|  |  |  |     end. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | filters() -> | 
					
						
							|  |  |  |     case get({?MODULE, no_filters}) of | 
					
						
							|  |  |  |         true  -> fun(_) -> false end; | 
					
						
							|  |  |  |         _     -> filter_chain([fun is_unresolved_call/1, fun is_callback/1, | 
					
						
							|  |  |  |                                fun is_unused/1, fun is_irrelevant/1]) | 
					
						
							|  |  |  |     end. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | filter_chain(FnChain) -> | 
					
						
							|  |  |  |     fun(AnalysisResult) -> | 
					
						
							| 
									
										
										
										
											2013-01-16 02:41:10 +08:00
										 |  |  |         Result = cleanup(AnalysisResult), | 
					
						
							|  |  |  |         lists:foldl(fun(F, false) -> F(Result); | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |                        (_F, true) -> true | 
					
						
							|  |  |  |                     end, false, FnChain) | 
					
						
							|  |  |  |     end. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | cleanup({{_, _},{{{{_,_,_}=MFA1,_},{{_,_,_}=MFA2,_}},_}}) -> {MFA1, MFA2}; | 
					
						
							|  |  |  | cleanup({{_, _},{{{_,_,_}=MFA1,_},{{_,_,_}=MFA2,_}}})     -> {MFA1, MFA2}; | 
					
						
							|  |  |  | cleanup({{_, _},{{_,_,_}=MFA1,{_,_,_}=MFA2},_})           -> {MFA1, MFA2}; | 
					
						
							|  |  |  | cleanup({{_, _},{{_,_,_}=MFA1,{_,_,_}=MFA2}})             -> {MFA1, MFA2}; | 
					
						
							|  |  |  | cleanup({{_, _}, {_,_,_}=MFA})                            -> MFA; | 
					
						
							|  |  |  | cleanup({{_, _}, {{_,_,_}=MFA,_}})                        -> MFA; | 
					
						
							|  |  |  | cleanup({{_,_,_}=MFA, {_,_,_}})                           -> MFA; | 
					
						
							|  |  |  | cleanup({{_,_,_}=MFA, {_,_,_},_})                         -> MFA; | 
					
						
							|  |  |  | cleanup(Other)                                            -> Other. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | is_irrelevant({{M,_,_}, {_,_,_}}) -> | 
					
						
							|  |  |  |     is_irrelevant(M); | 
					
						
							|  |  |  | is_irrelevant({M,_,_}) -> | 
					
						
							|  |  |  |     is_irrelevant(M); | 
					
						
							|  |  |  | is_irrelevant(Mod) when is_atom(Mod) -> | 
					
						
							|  |  |  |     lists:member(Mod, get({?MODULE, third_party})). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | is_unused({{_,_,_}=MFA, {_,_,_}}) -> | 
					
						
							|  |  |  |     is_unused(MFA); | 
					
						
							|  |  |  | is_unused({M,_F,_A}) -> | 
					
						
							|  |  |  |     lists:suffix("_tests", atom_to_list(M)); | 
					
						
							|  |  |  | is_unused(_) -> | 
					
						
							|  |  |  |     false. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | is_unresolved_call({_, F, A}) -> | 
					
						
							|  |  |  |     UC = get({?MODULE, unresolved_calls}), | 
					
						
							|  |  |  |     sets:is_element({'$M_EXPR', F, A}, UC); | 
					
						
							|  |  |  | is_unresolved_call(_) -> | 
					
						
							|  |  |  |     false. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | %% TODO: cache this.... | 
					
						
							|  |  |  | is_callback({M,_,_}=MFA) -> | 
					
						
							|  |  |  |     Attributes = M:module_info(attributes), | 
					
						
							|  |  |  |     Behaviours = proplists:append_values(behaviour, Attributes), | 
					
						
							|  |  |  |     {_, Callbacks} = lists:foldl(fun acc_behaviours/2, {M, []}, Behaviours), | 
					
						
							|  |  |  |     lists:member(MFA, Callbacks); | 
					
						
							|  |  |  | is_callback(_) -> | 
					
						
							|  |  |  |     false. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | acc_behaviours(B, {M, CB}=Acc) -> | 
					
						
							|  |  |  |     case catch(B:behaviour_info(callbacks)) of | 
					
						
							| 
									
										
										
										
											2012-07-05 00:38:13 +08:00
										 |  |  |         [{_,_} | _] = Callbacks -> | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |             {M, CB ++ [{M, F, A} || {F,A} <- Callbacks]}; | 
					
						
							|  |  |  |         _ -> | 
					
						
							|  |  |  |             Acc | 
					
						
							|  |  |  |     end. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | %% | 
					
						
							|  |  |  | %% reporting/output | 
					
						
							|  |  |  | %% | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | report(Results) -> | 
					
						
							|  |  |  |     [report_failures(F) || F <- Results], | 
					
						
							|  |  |  |     {Errors, Warnings} = partition(Results), | 
					
						
							|  |  |  |     report(info, "Completed: ~p errors, ~p warnings~n", | 
					
						
							|  |  |  |                  [length(Errors), length(Warnings)]), | 
					
						
							| 
									
										
										
										
											2012-07-05 00:39:57 +08:00
										 |  |  |     case length(Errors) > 0 of | 
					
						
							| 
									
										
										
										
											2012-07-16 20:11:18 +08:00
										 |  |  |         true  -> 1; | 
					
						
							|  |  |  |         false -> 0 | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |     end. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | report_failures({analysis_error, {Mod, Reason}}) -> | 
					
						
							|  |  |  |     report(error, "~s:0 Analysis Error: ~p~n", [source_file(Mod), Reason]); | 
					
						
							|  |  |  | report_failures({{Tag, Level}, {{{{M,_,_},L},{{M2,F2,A2},_}},_}}) -> | 
					
						
							|  |  |  |     report(Level, "~s:~w ~s ~p:~p/~p~n", | 
					
						
							|  |  |  |            [source_file(M), L, Tag, M2, F2, A2]); | 
					
						
							|  |  |  | report_failures({{Tag, Level}, {{M,F,A},L}}) -> | 
					
						
							|  |  |  |     report(Level, "~s:~w ~s ~p:~p/~p~n", [source_file(M), L, Tag, M, F, A]); | 
					
						
							|  |  |  | report_failures({{Tag, Level}, {M,F,A}}) -> | 
					
						
							|  |  |  |     report(Level, "~s:unknown ~s ~p:~p/~p~n", [source_file(M), Tag, M, F, A]); | 
					
						
							|  |  |  | report_failures(Term) -> | 
					
						
							|  |  |  |     report(error, "Ignoring ~p~n", [Term]), | 
					
						
							|  |  |  |     ok. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | report_progress(Fmt, Args) -> | 
					
						
							|  |  |  |     report(info, Fmt, Args). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | report(Level, Fmt, Args) -> | 
					
						
							| 
									
										
										
										
											2012-07-05 00:35:17 +08:00
										 |  |  |     case {get({?MODULE, quiet}), Level} of | 
					
						
							|  |  |  |         {true,  error} -> do_report(lookup_prefix(Level), Fmt, Args); | 
					
						
							|  |  |  |         {false, _}     -> do_report(lookup_prefix(Level), Fmt, Args); | 
					
						
							|  |  |  |         _              -> ok | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |     end. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | do_report(Prefix, Fmt, Args) -> | 
					
						
							|  |  |  |     io:format(Prefix ++ Fmt, Args). | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-07-05 00:38:13 +08:00
										 |  |  | lookup_prefix(error)   -> "ERROR: "; | 
					
						
							|  |  |  | lookup_prefix(warning) -> "WARNING: "; | 
					
						
							|  |  |  | lookup_prefix(info)    -> "INFO: ". | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | source_file(M) -> | 
					
						
							|  |  |  |     proplists:get_value(source, M:module_info(compile)). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | %% | 
					
						
							|  |  |  | %% setup/code-path/file-system ops | 
					
						
							|  |  |  | %% | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | store_third_party(App) -> | 
					
						
							|  |  |  |     {ok, AppConfig} = application:get_all_key(App), | 
					
						
							| 
									
										
										
										
											2013-01-16 02:39:00 +08:00
										 |  |  |     AppModules = proplists:get_value(modules, AppConfig), | 
					
						
							|  |  |  |     put({?MODULE, third_party}, AppModules ++ get({?MODULE, third_party})). | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | %% TODO: this ought not to be maintained in such a fashion | 
					
						
							|  |  |  | external_dependency(Path) -> | 
					
						
							|  |  |  |     lists:any(fun(P) -> lists:prefix(P, Path) end, | 
					
						
							|  |  |  |               ["mochiweb", "webmachine", "rfc4627", "eldap"]). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | unmangle_name(Path) -> | 
					
						
							| 
									
										
										
										
											2012-07-05 00:38:13 +08:00
										 |  |  |     [Name, Vsn | _] = re:split(Path, "-", [{return, list}]), | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |     string:join([Name, Vsn], "-"). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | store_unresolved_calls() -> | 
					
						
							|  |  |  |     {ok, UCFull} = analyse("UC"), | 
					
						
							| 
									
										
										
										
											2012-07-05 00:38:13 +08:00
										 |  |  |     UC = [MFA || {_, {_,_,_} = MFA} <- UCFull], | 
					
						
							| 
									
										
										
										
											2012-06-21 17:45:34 +08:00
										 |  |  |     put({?MODULE, unresolved_calls}, sets:from_list(UC)). |