diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/rebar.erl | 5 | ||||
-rw-r--r-- | src/rebar_app_discover.erl | 12 | ||||
-rw-r--r-- | src/rebar_base_compiler.erl | 16 | ||||
-rw-r--r-- | src/rebar_core.erl | 3 | ||||
-rw-r--r-- | src/rebar_ct.erl | 141 | ||||
-rw-r--r-- | src/rebar_deps.erl | 218 | ||||
-rw-r--r-- | src/rebar_fetch.erl | 9 | ||||
-rw-r--r-- | src/rebar_log.erl | 4 | ||||
-rw-r--r-- | src/rebar_prv_app_builder.erl | 12 | ||||
-rw-r--r-- | src/rebar_prv_update.erl | 48 | ||||
-rw-r--r-- | src/rebar_qc.erl | 216 | ||||
-rw-r--r-- | src/rebar_state.erl | 34 | ||||
-rw-r--r-- | src/rebar_subdirs.erl | 84 | ||||
-rw-r--r-- | src/rebar_utils.erl | 10 | ||||
-rw-r--r-- | src/rebar_xref.erl | 290 |
15 files changed, 299 insertions, 803 deletions
diff --git a/src/rebar.erl b/src/rebar.erl index a7bbe1a..338e97b 100644 --- a/src/rebar.erl +++ b/src/rebar.erl @@ -162,7 +162,10 @@ run_aux(BaseConfig, Commands) -> ok -> ok; {error,{already_started,crypto}} -> ok end, - + application:start(asn1), + application:start(public_key), + application:start(ssl), + inets:start(), [Command | Args] = Commands, CommandAtom = list_to_atom(Command), diff --git a/src/rebar_app_discover.erl b/src/rebar_app_discover.erl index d4c904d..aaaeb03 100644 --- a/src/rebar_app_discover.erl +++ b/src/rebar_app_discover.erl @@ -8,7 +8,7 @@ do(State, LibDirs) -> Apps = find_apps(LibDirs, all), lists:foldl(fun(AppInfo, StateAcc) -> - rebar_state:add_app(StateAcc, AppInfo) + rebar_state:apps_to_build(StateAcc, AppInfo) end, State, Apps). -spec all_app_dirs(list(file:name())) -> list(file:name()). @@ -35,10 +35,15 @@ app_dirs(LibDir) -> "*.app"]), lists:usort(lists:foldl(fun(Path, Acc) -> - Files = filelib:wildcard(Path), + Files = filelib:wildcard(to_list(Path)), [app_dir(File) || File <- Files] ++ Acc end, [], [Path1, Path2, Path3, Path4])). +to_list(S) when is_list(S) -> + S; +to_list(S) when is_binary(S) -> + binary_to_list(S). + find_unbuilt_apps(LibDirs) -> find_apps(LibDirs, invalid). @@ -115,13 +120,12 @@ create_app_info(AppDir, AppFile) -> rebar_app_info:app_details(AppInfo, AppDetails), AppState1), rebar_app_info:dir(AppInfo1, AppDir) end. + -spec validate_application_info(rebar_app_info:t()) -> boolean(). validate_application_info(AppInfo) -> EbinDir = rebar_app_info:ebin_dir(AppInfo), AppFile = rebar_app_info:app_file(AppInfo), - AppName = rebar_app_info:app_details(AppInfo), AppDetail = rebar_app_info:app_details(AppInfo), - AppDir = filename:dirname(EbinDir), case get_modules_list(AppFile, AppDetail) of {ok, List} -> has_all_beams(EbinDir, List); diff --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl index e4e4b79..0536c06 100644 --- a/src/rebar_base_compiler.erl +++ b/src/rebar_base_compiler.erl @@ -135,12 +135,12 @@ compile_each([], _Config, _CompileFn) -> compile_each([Source | Rest], Config, CompileFn) -> case compile(Source, Config, CompileFn) of ok -> - ?INFO("Compiled ~s\n", [Source]); + ?INFO("~sCompiled ~s\n", [rebar_utils:indent(1), Source]); {ok, Warnings} -> report(Warnings), - ?INFO("Compiled ~s\n", [Source]); + ?INFO("~sCompiled ~s\n", [rebar_utils:indent(1), Source]); skipped -> - ?DEBUG("Skipped ~s\n", [Source]); + ?DEBUG("~sSkipped ~s\n", [rebar_utils:indent(1), Source]); Error -> ?INFO("Compiling ~s failed:\n", [maybe_absname(Config, Source)]), @@ -165,7 +165,7 @@ compile_queue(Config, Pids, Targets) -> end; {fail, {_, {source, Source}}=Error} -> - ?CONSOLE("Compiling ~s failed:\n", + ?ERROR("Compiling ~s failed:\n", [maybe_absname(Config, Source)]), maybe_report(Error), ?DEBUG("Worker compilation failed: ~p\n", [Error]), @@ -173,17 +173,15 @@ compile_queue(Config, Pids, Targets) -> {compiled, Source, Warnings} -> report(Warnings), - ?INFO("Compiled ~s\n", [Source]), + ?INFO("~sCompiled ~s\n", [rebar_utils:indent(1), Source]), compile_queue(Config, Pids, Targets); {compiled, Source} -> - ?INFO("Compiled ~s\n", [Source]), + ?INFO("~sCompiled ~s\n", [rebar_utils:indent(1), Source]), compile_queue(Config, Pids, Targets); - {skipped, Source} -> - ?DEBUG("Skipped ~s\n", [Source]), + ?DEBUG("~sSkipped ~s~n", [rebar_utils:indent(1), Source]), compile_queue(Config, Pids, Targets); - {'DOWN', Mref, _, Pid, normal} -> ?DEBUG("Worker exited cleanly\n", []), Pids2 = lists:delete({Pid, Mref}, Pids), diff --git a/src/rebar_core.erl b/src/rebar_core.erl index a146cce..d4daff7 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -44,7 +44,8 @@ process_command(State, Command) -> TargetProviders = rebar_provider:get_target_providers(Command, State), lists:foldl(fun(TargetProvider, Conf) -> - Provider = rebar_provider:get_provider(TargetProvider, rebar_state:providers(Conf)), + Provider = rebar_provider:get_provider(TargetProvider + ,rebar_state:providers(Conf)), {ok, Conf1} = rebar_provider:do(Provider, Conf), Conf1 end, State, TargetProviders). diff --git a/src/rebar_ct.erl b/src/rebar_ct.erl index 6bccb82..781f7da 100644 --- a/src/rebar_ct.erl +++ b/src/rebar_ct.erl @@ -37,21 +37,41 @@ %% ------------------------------------------------------------------- -module(rebar_ct). --export([ct/2]). +-behaviour(rebar_provider). + +-export([init/1, + do/1]). %% for internal use only -export([info/2]). -include("rebar.hrl"). +-define(PROVIDER, ct). +-define(DEPS, [compile]). + %% =================================================================== %% Public API %% =================================================================== -ct(Config, File) -> - TestDir = rebar_config:get_local(Config, ct_dir, "test"), - LogDir = rebar_config:get_local(Config, ct_log_dir, "logs"), - run_test_if_present(TestDir, LogDir, Config, File). +-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. +init(State) -> + State1 = rebar_state:add_provider(State, #provider{name = ?PROVIDER, + provider_impl = ?MODULE, + bare = false, + deps = ?DEPS, + example = "rebar ct", + short_desc = "", + desc = "", + opts = []}), + {ok, State1}. + +-spec do(rebar_state:t()) -> {ok, rebar_state:t()}. +do(State) -> + TestDir = rebar_state:get(State, ct_dir, "test"), + LogDir = rebar_state:get(State, ct_log_dir, "logs"), + run_test_if_present(TestDir, LogDir, State), + {ok, State}. %% =================================================================== %% Internal functions @@ -76,7 +96,7 @@ info(help, ct) -> {ct_use_short_names, true} ]). -run_test_if_present(TestDir, LogDir, Config, File) -> +run_test_if_present(TestDir, LogDir, State) -> case filelib:is_dir(TestDir) of false -> ?WARN("~s directory not present - skipping\n", [TestDir]), @@ -89,7 +109,7 @@ run_test_if_present(TestDir, LogDir, Config, File) -> ok; _ -> try - run_test(TestDir, LogDir, Config, File) + run_test(TestDir, LogDir, State) catch throw:skip -> ok @@ -97,11 +117,11 @@ run_test_if_present(TestDir, LogDir, Config, File) -> end end. -run_test(TestDir, LogDir, Config, _File) -> - {Cmd, RawLog} = make_cmd(TestDir, LogDir, Config), +run_test(TestDir, LogDir, State) -> + {Cmd, RawLog} = make_cmd(TestDir, LogDir, State), ?DEBUG("ct_run cmd:~n~p~n", [Cmd]), clear_log(LogDir, RawLog), - Output = case rebar_log:is_verbose(Config) of + Output = case rebar_log:is_verbose(State) of false -> " >> " ++ RawLog ++ " 2>&1"; true -> @@ -113,11 +133,11 @@ run_test(TestDir, LogDir, Config, _File) -> {ok,_} -> %% in older versions of ct_run, this could have been a failure %% that returned a non-0 code. Check for that! - check_success_log(Config, RawLog); + check_success_log(State, RawLog); {error,Res} -> %% In newer ct_run versions, this may be a sign of a good compile %% that failed cases. In older version, it's a worse error. - check_fail_log(Config, RawLog, Cmd ++ Output, Res) + check_fail_log(State, RawLog, Cmd ++ Output, Res) end. clear_log(LogDir, RawLog) -> @@ -133,8 +153,8 @@ clear_log(LogDir, RawLog) -> %% calling ct with erl does not return non-zero on failure - have to check %% log results -check_success_log(Config, RawLog) -> - check_log(Config, RawLog, fun(Msg) -> ?CONSOLE("DONE.\n~s\n", [Msg]) end). +check_success_log(State, RawLog) -> + check_log(State, RawLog, fun(Msg) -> ?CONSOLE("DONE.\n~s\n", [Msg]) end). -type err_handler() :: fun((string()) -> no_return()). -spec failure_logger(string(), {integer(), string()}) -> err_handler(). @@ -144,10 +164,10 @@ failure_logger(Command, {Rc, Output}) -> [Command, Rc, Output]) end. -check_fail_log(Config, RawLog, Command, Result) -> - check_log(Config, RawLog, failure_logger(Command, Result)). +check_fail_log(State, RawLog, Command, Result) -> + check_log(State, RawLog, failure_logger(Command, Result)). -check_log(Config,RawLog,Fun) -> +check_log(State,RawLog,Fun) -> {ok, Msg} = rebar_utils:sh("grep -e \"TEST COMPLETE\" -e \"{error,make_failed}\" " ++ RawLog, [{use_stdout, false}]), @@ -155,12 +175,12 @@ check_log(Config,RawLog,Fun) -> RunFailed = string:str(Msg, ", 0 failed") =:= 0, if MakeFailed -> - show_log(Config, RawLog), + show_log(State, RawLog), ?ERROR("Building tests failed\n",[]), ?FAIL; RunFailed -> - show_log(Config, RawLog), + show_log(State, RawLog), ?ERROR("One or more tests failed\n",[]), ?FAIL; @@ -170,9 +190,9 @@ check_log(Config,RawLog,Fun) -> %% Show the log if it hasn't already been shown because verbose was on -show_log(Config, RawLog) -> +show_log(State, RawLog) -> ?CONSOLE("Showing log\n", []), - case rebar_log:is_verbose(Config) of + case rebar_log:is_verbose(State) of false -> {ok, Contents} = file:read_file(RawLog), ?CONSOLE("~s", [Contents]); @@ -180,10 +200,9 @@ show_log(Config, RawLog) -> ok end. -make_cmd(TestDir, RawLogDir, Config) -> +make_cmd(TestDir, RawLogDir, State) -> Cwd = rebar_utils:get_cwd(), LogDir = filename:join(Cwd, RawLogDir), - EbinDir = filename:absname(filename:join(Cwd, "ebin")), IncludeDir = filename:join(Cwd, "include"), Include = case filelib:is_dir(IncludeDir) of true -> @@ -205,12 +224,13 @@ make_cmd(TestDir, RawLogDir, Config) -> %% includes the dependencies in the code path. The directories %% that are part of the root Erlang install are filtered out to %% avoid duplication - R = code:root_dir(), - NonLibCodeDirs = [P || P <- code:get_path(), not lists:prefix(R, P)], - CodeDirs = [io_lib:format("\"~s\"", [Dir]) || - Dir <- [EbinDir|NonLibCodeDirs]], + Apps = rebar_state:apps_to_build(State), + DepsDir = rebar_deps:get_deps_dir(State), + DepsDirEbin = filename:join([DepsDir, "*", "ebin"]), + AppDirs = [filename:join(rebar_app_info:dir(A), "ebin") || A <- Apps], + CodeDirs = [io_lib:format("\"~s\"", [Dir]) || Dir <- [DepsDirEbin | AppDirs]], CodePathString = string:join(CodeDirs, " "), - Cmd = case get_ct_specs(Config, Cwd) of + Cmd = case get_ct_specs(State, Cwd) of undefined -> ?FMT("~s" " -pa ~s" @@ -221,14 +241,14 @@ make_cmd(TestDir, RawLogDir, Config) -> [BaseCmd, CodePathString, Include, - build_name(Config), + build_name(State), LogDir, filename:join(Cwd, TestDir)]) ++ - get_cover_config(Config, Cwd) ++ + get_cover_config(State, Cwd) ++ get_ct_config_file(TestDir) ++ get_config_file(TestDir) ++ - get_suites(Config, TestDir) ++ - get_case(Config); + get_suites(State, TestDir) ++ + get_case(State); SpecFlags -> ?FMT("~s" " -pa ~s" @@ -239,31 +259,32 @@ make_cmd(TestDir, RawLogDir, Config) -> [BaseCmd, CodePathString, Include, - build_name(Config), + build_name(State), LogDir, filename:join(Cwd, TestDir)]) ++ - SpecFlags ++ get_cover_config(Config, Cwd) + SpecFlags ++ get_cover_config(State, Cwd) end, - Cmd1 = Cmd ++ get_extra_params(Config), + io:format("Cmd ~s~n", [Cmd]), + Cmd1 = Cmd ++ get_extra_params(State), RawLog = filename:join(LogDir, "raw.log"), {Cmd1, RawLog}. -build_name(Config) -> - case rebar_config:get_local(Config, ct_use_short_names, false) of +build_name(State) -> + case rebar_state:get(State, ct_use_short_names, false) of true -> "-sname test"; false -> " -name test@" ++ net_adm:localhost() end. -get_extra_params(Config) -> - case rebar_config:get_local(Config, ct_extra_params, undefined) of +get_extra_params(State) -> + case rebar_state:get(State, ct_extra_params, undefined) of undefined -> ""; Defined -> " " ++ Defined end. -get_ct_specs(Config, Cwd) -> - case collect_glob(Config, Cwd, ".*\.test\.spec\$") of +get_ct_specs(State, Cwd) -> + case collect_glob(State, Cwd, ".*\.test\.spec\$") of [] -> undefined; [Spec] -> " -spec " ++ Spec; @@ -272,12 +293,12 @@ get_ct_specs(Config, Cwd) -> lists:flatten([io_lib:format("~s ", [Spec]) || Spec <- Specs]) end. -get_cover_config(Config, Cwd) -> - case rebar_config:get_local(Config, cover_enabled, false) of +get_cover_config(State, Cwd) -> + case rebar_state:get(State, cover_enabled, false) of false -> ""; true -> - case collect_glob(Config, Cwd, ".*cover\.spec\$") of + case collect_glob(State, Cwd, ".*cover\.spec\$") of [] -> ?DEBUG("No cover spec found: ~s~n", [Cwd]), ""; @@ -289,15 +310,15 @@ get_cover_config(Config, Cwd) -> end end. -collect_glob(Config, Cwd, Glob) -> - {true, Deps} = rebar_deps:get_deps_dir(Config), +collect_glob(State, Cwd, Glob) -> + DepsDir = rebar_deps:get_deps_dir(State), CwdParts = filename:split(Cwd), filelib:fold_files(Cwd, Glob, true, fun(F, Acc) -> %% Ignore any specs under the deps/ directory. Do this pulling %% the dirname off the F and then splitting it into a list. Parts = filename:split(filename:dirname(F)), Parts2 = remove_common_prefix(Parts, CwdParts), - case lists:member(Deps, Parts2) of + case lists:member(DepsDir, Parts2) of true -> Acc; % There is a directory named "deps" in path false -> @@ -311,25 +332,25 @@ remove_common_prefix(L1, _) -> L1. get_ct_config_file(TestDir) -> - Config = filename:join(TestDir, "test.config"), - case filelib:is_regular(Config) of + State = filename:join(TestDir, "test.config"), + case filelib:is_regular(State) of false -> " "; true -> - " -ct_config " ++ Config + " -ct_config " ++ State end. get_config_file(TestDir) -> - Config = filename:join(TestDir, "app.config"), - case filelib:is_regular(Config) of + State = filename:join(TestDir, "app.config"), + case filelib:is_regular(State) of false -> " "; true -> - " -config " ++ Config + " -config " ++ State end. -get_suites(Config, TestDir) -> - case get_suites(Config) of +get_suites(State, TestDir) -> + case get_suites(State) of undefined -> " -dir " ++ TestDir; Suites -> @@ -338,10 +359,10 @@ get_suites(Config, TestDir) -> string:join([" -suite"] ++ Suites2, " ") end. -get_suites(Config) -> - case rebar_config:get_global(Config, suites, undefined) of +get_suites(State) -> + case rebar_state:get(State, suites, undefined) of undefined -> - rebar_config:get_global(Config, suite, undefined); + rebar_state:get(State, suite, undefined); Suites -> Suites end. @@ -358,8 +379,8 @@ find_suite_path(Suite, TestDir) -> Path end. -get_case(Config) -> - case rebar_config:get_global(Config, 'case', undefined) of +get_case(State) -> + case rebar_state:get(State, 'case', undefined) of undefined -> ""; Case -> diff --git a/src/rebar_deps.erl b/src/rebar_deps.erl index bb7f33f..e050952 100644 --- a/src/rebar_deps.erl +++ b/src/rebar_deps.erl @@ -36,7 +36,6 @@ -export([setup_env/1]). %% for internal use only --export([info/2]). -export([get_deps_dir/1]). -export([get_deps_dir/2]). @@ -54,42 +53,62 @@ init(State) -> bare = false, deps = ?DEPS, example = "rebar deps", - short_desc = "", - desc = "", + short_desc = "Install dependencies", + desc = info_help("Install dependencies"), opts = []}), {ok, State1}. -spec do(rebar_state:t()) -> {ok, rebar_state:t()}. -do(Config) -> - Deps = rebar_state:get(Config, deps, []), - Goals = lists:map(fun({Name, "", _}) -> - Name; - ({Name, ".*", _}) -> - Name; - ({Name, Vsn, _}) -> - {Name, Vsn} - end, Deps), - - {Config1, Deps1} = update_deps(Config, Deps), - Config2 = rebar_state:set(Config1, deps, Deps1), - - {ok, rebar_state:set(Config2, goals, Goals)}. +do(State) -> + %% Read in package index and dep graph + {Packages, Graph} = get_packages(State), + SrcDeps = rebar_state:get(State, src_deps, []), + {State1, SrcDeps1} = update_deps(State, SrcDeps), + + case rebar_state:get(State1, deps, []) of + [] -> + {ok, rebar_state:set(State1, deps, SrcDeps1)}; + Deps -> + Goals = lists:map(fun({Name, Vsn}) -> + {atom_to_binary(Name, utf8), Vsn}; + (Name) -> + atom_to_binary(Name, utf8) + end, Deps), + {ok, Solved} = rlx_depsolver:solve(Graph, Goals), + + M = lists:map(fun({Name, Vsn}) -> + {ok, P} = ec_lists:find(fun(App) -> + Name =:= proplists:get_value(<<"name">>, App) + andalso to_binary(rlx_depsolver:format_version(Vsn)) =:= proplists:get_value(<<"vsn">>, App) + end, Packages), + Link = proplists:get_value(<<"link">>, P), + {Name, Vsn, {Name + ,to_binary(rlx_depsolver:format_version(Vsn)) + ,Link}} + end, Solved), + + {State2, Deps1} = update_deps(State1, M), + State3 = rebar_state:set(State2, deps, Deps1), + + {ok, rebar_state:set(State3, goals, Goals)} + end. -update_deps(Config, Deps) -> - DepsDir = get_deps_dir(Config), +update_deps(State, Deps) -> + DepsDir = get_deps_dir(State), %% Find available apps to fulfill dependencies + %% Should only have to do this once, not every iteration UnbuiltApps = rebar_app_discover:find_unbuilt_apps([DepsDir]), FoundApps = rebar_app_discover:find_apps([DepsDir]), %% Resolve deps and their dependencies Deps1 = handle_deps(Deps, UnbuiltApps++FoundApps), - case download_missing_deps(Config, DepsDir, FoundApps, UnbuiltApps, Deps1) of - {Config1, []} -> - {Config1, Deps1}; - {Config1, _} -> - update_deps(Config1, Deps1) + case download_missing_deps(State, DepsDir, FoundApps, UnbuiltApps, Deps1) of + {State1, []} -> + {State1, Deps1}; + {State1, _} -> + update_deps(State1, Deps1) end. handle_deps(Deps, Found) -> @@ -104,36 +123,58 @@ handle_deps(Deps, Found) -> %% Weed out duplicates lists:umerge(fun(A, B) -> - element(1, A) =:= element(1, B) + dep_name(A) =:= dep_name(B) end, lists:usort(Deps), lists:usort(NewDeps1)). -download_missing_deps(Config, DepsDir, Found, Unbuilt, Deps) -> +dep_name({Name, _, _}) -> + Name; +dep_name({Name, _}) -> + Name; +dep_name(Name) -> + Name. + + +to_binary(X) when is_binary(X) -> + X; +to_binary(X) when is_atom(X) -> + atom_to_binary(X, utf8); +to_binary(X) when is_list(X) -> + iolist_to_binary(X). + +download_missing_deps(State, DepsDir, Found, Unbuilt, Deps) -> Missing = lists:filter(fun(X) -> not lists:any(fun(F) -> - element(1, X) =:= element(2, F) + to_binary(dep_name(X)) =:= to_binary(rebar_app_info:name(F)) end, Found++Unbuilt) end, Deps), - ec_plists:foreach(fun(X) -> - TargetDir = get_deps_dir(DepsDir, element(1, X)), - case filelib:is_dir(TargetDir) of - true -> - ok; - false -> - rebar_fetch:download_source(TargetDir, element(3, X)) - end - end, Missing), - - Config1 = lists:foldl(fun(X, ConfigAcc) -> - TargetDir = get_deps_dir(DepsDir, element(1, X)), - [AppSrc] = rebar_app_discover:find_unbuilt_apps([TargetDir]), - rebar_state:add_app(ConfigAcc, AppSrc) - end, Config, Missing), - - {Config1, Missing}. + lists:foreach(fun({DepName, _DepVsn, DepSource}) -> + TargetDir = get_deps_dir(DepsDir, DepName), + case filelib:is_dir(TargetDir) of + true -> + ok; + false -> + ?INFO("Fetching ~s ~s~n", [element(1, DepSource) + ,element(2, DepSource)]), + rebar_fetch:download_source(TargetDir, DepSource) + end + end, Missing), + + State1 = lists:foldl(fun(X, StateAcc) -> + TargetDir = get_deps_dir(DepsDir, dep_name(X)), + case rebar_app_discover:find_unbuilt_apps([TargetDir]) of + [AppSrc] -> + {_AppInfo1, StateAcc1} = rebar_prv_app_builder:build(StateAcc, AppSrc), + StateAcc1; + [] -> + StateAcc + end + end, State, Missing), + + {State1, []}. %% set REBAR_DEPS_DIR and ERL_LIBS environment variables -setup_env(Config) -> - DepsDir = get_deps_dir(Config), +setup_env(State) -> + DepsDir = get_deps_dir(State), %% include rebar's DepsDir in ERL_LIBS Separator = case os:type() of {win32, nt} -> @@ -161,41 +202,54 @@ get_deps_dir(DepsDir, App) -> %% Internal functions %% =================================================================== -info(help, 'deps') -> - info_help("Display dependencies"). +get_packages(State) -> + RebarDir = rebar_state:get(State, global_rebar_dir, filename:join(os:getenv("HOME"), ".rebar")), + PackagesFile = filename:join(RebarDir, "packages"), + case ec_file:exists(PackagesFile) of + true -> + try + {ok, Binary} = file:read_file(PackagesFile), + binary_to_term(Binary) + catch + _:_ -> + ?ERROR("Bad packages index, try to fix with `rebar update`~n", []), + {[], rlx_depsolver:new()} + end; + false -> + {[], rlx_depsolver:new()} + end. info_help(Description) -> - ?CONSOLE( - "~s.~n" - "~n" - "Valid rebar.config options:~n" - " ~p~n" - " ~p~n" - "Valid command line options:~n" - " deps_dir=\"deps\" (override default or rebar.config deps_dir)~n", - [ - Description, - {deps_dir, "deps"}, - {deps, - [app_name, - {rebar, "1.0.*"}, - {rebar, ".*", - {git, "git://github.com/rebar/rebar.git"}}, - {rebar, ".*", - {git, "git://github.com/rebar/rebar.git", "Rev"}}, - {rebar, "1.0.*", - {git, "git://github.com/rebar/rebar.git", {branch, "master"}}}, - {rebar, "1.0.0", - {git, "git://github.com/rebar/rebar.git", {tag, "1.0.0"}}}, - {rebar, "", - {git, "git://github.com/rebar/rebar.git", {branch, "master"}}, - [raw]}, - {app_name, ".*", {hg, "https://www.example.org/url"}}, - {app_name, ".*", {rsync, "Url"}}, - {app_name, ".*", {svn, "https://www.example.org/url"}}, - {app_name, ".*", {svn, "svn://svn.example.org/url"}}, - {app_name, ".*", {bzr, "https://www.example.org/url", "Rev"}}, - {app_name, ".*", {fossil, "https://www.example.org/url"}}, - {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}, - {app_name, ".*", {p4, "//depot/subdir/app_dir"}}]} - ]). + io_lib:format("~s.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n" + " ~p~n" + "Valid command line options:~n" + " deps_dir=\"deps\" (override default or rebar.config deps_dir)~n", + [ + Description, + {deps_dir, "deps"}, + {deps, + [app_name, + {rebar, "1.0.*"}, + {rebar, ".*", + {git, "git://github.com/rebar/rebar.git"}}, + {rebar, ".*", + {git, "git://github.com/rebar/rebar.git", "Rev"}}, + {rebar, "1.0.*", + {git, "git://github.com/rebar/rebar.git", {branch, "master"}}}, + {rebar, "1.0.0", + {git, "git://github.com/rebar/rebar.git", {tag, "1.0.0"}}}, + {rebar, "", + {git, "git://github.com/rebar/rebar.git", {branch, "master"}}, + [raw]}, + {app_name, ".*", {hg, "https://www.example.org/url"}}, + {app_name, ".*", {rsync, "Url"}}, + {app_name, ".*", {svn, "https://www.example.org/url"}}, + {app_name, ".*", {svn, "svn://svn.example.org/url"}}, + {app_name, ".*", {bzr, "https://www.example.org/url", "Rev"}}, + {app_name, ".*", {fossil, "https://www.example.org/url"}}, + {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}, + {app_name, ".*", {p4, "//depot/subdir/app_dir"}}]} + ]). diff --git a/src/rebar_fetch.erl b/src/rebar_fetch.erl index 6ed0f3f..024e032 100644 --- a/src/rebar_fetch.erl +++ b/src/rebar_fetch.erl @@ -114,7 +114,14 @@ download_source(AppDir, {fossil, Url, Version}) -> rebar_utils:sh(?FMT("fossil clone ~s ~s", [Url, Repository]), [{cd, AppDir}]), rebar_utils:sh(?FMT("fossil open ~s ~s --nested", [Repository, Version]), - []). + []); +download_source(AppDir, {AppName, AppVersion, Url}) when is_binary(AppName) + , is_binary(AppVersion) -> + TmpDir = ec_file:insecure_mkdtemp(), + TmpFile = binary_to_list(filename:join(TmpDir, <<AppName/binary, "-", AppVersion/binary>>)), + {ok, saved_to_file} = httpc:request(get, {binary_to_list(Url), []}, [], [{stream, TmpFile}]), + ok = erl_tar:extract(TmpFile, [{cwd, filename:dirname(AppDir)}, compressed]), + ok. update_source1(AppDir, Args) when element(1, Args) =:= p4 -> download_source(AppDir, Args); diff --git a/src/rebar_log.erl b/src/rebar_log.erl index 81a2d6c..787e5bc 100644 --- a/src/rebar_log.erl +++ b/src/rebar_log.erl @@ -62,8 +62,8 @@ log(Level, Str, Args) -> error_level() -> ?ERROR_LEVEL. default_level() -> ?INFO_LEVEL. -is_verbose(Config) -> - rebar_config:get_xconf(Config, is_verbose, false). +is_verbose(State) -> + rebar_state:get(State, is_verbose, false). %% =================================================================== %% Internal functions diff --git a/src/rebar_prv_app_builder.erl b/src/rebar_prv_app_builder.erl index e608eb1..90de4fb 100644 --- a/src/rebar_prv_app_builder.erl +++ b/src/rebar_prv_app_builder.erl @@ -3,7 +3,8 @@ -behaviour(rebar_provider). -export([init/1, - do/1]). + do/1, + build/2]). -include("rebar.hrl"). @@ -47,17 +48,10 @@ do(Config) -> build(Config, AppInfo) -> {ok, AppInfo1} = rebar_otp_app:compile(Config, AppInfo), - Config1 = rebar_state:add_app(Config, AppInfo1), + Config1 = rebar_state:apps_to_build(Config, AppInfo1), rebar_erlc_compiler:compile(Config, rebar_app_info:dir(AppInfo1)), {AppInfo1, Config1}. %% =================================================================== %% Internal functions %% =================================================================== - -get_deps_dir(Config) -> - BaseDir = rebar_utils:base_dir(Config), - get_deps_dir(BaseDir, deps). - -get_deps_dir(DepsDir, App) -> - filename:join(DepsDir, atom_to_list(App)). diff --git a/src/rebar_prv_update.erl b/src/rebar_prv_update.erl index f48b507..78c7c34 100644 --- a/src/rebar_prv_update.erl +++ b/src/rebar_prv_update.erl @@ -30,22 +30,42 @@ init(State) -> {ok, State1}. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | relx:error(). -do(Config) -> - [Name] = rebar_state:command_args(Config), - ?INFO("Updating ~s~n", [Name]), +do(State) -> + case rebar_state:command_args(State) of + [Name] -> + ?INFO("Updating ~s~n", [Name]), - DepsDir = rebar_deps:get_deps_dir(Config), - Deps = rebar_state:get_local(Config, deps, []), - {_, _, Source} = lists:keyfind(list_to_atom(Name), 1, Deps), - TargetDir = rebar_deps:get_deps_dir(DepsDir, Name), - rebar_fetch:update_source1(TargetDir, Source), + DepsDir = rebar_deps:get_deps_dir(State), + Deps = rebar_state:get_local(State, deps, []), + {_, _, Source} = lists:keyfind(list_to_atom(Name), 1, Deps), + TargetDir = rebar_deps:get_deps_dir(DepsDir, Name), + rebar_fetch:update_source1(TargetDir, Source), - [App] = rebar_app_discover:find_apps([TargetDir]), + [App] = rebar_app_discover:find_apps([TargetDir]), - {ok, AppInfo1} = rebar_otp_app:compile(Config, App), - Config1 = rebar_state:replace_app(Config, rebar_app_info:name(AppInfo1), AppInfo1), - rebar_erlc_compiler:compile(Config, rebar_app_info:dir(AppInfo1)), + {ok, AppInfo1} = rebar_otp_app:compile(State, App), + State1 = rebar_state:replace_app(State, rebar_app_info:name(AppInfo1), AppInfo1), + rebar_erlc_compiler:compile(State, rebar_app_info:dir(AppInfo1)), - %update_lock_file(Config, AppInfo1, Source), + %update_lock_file(State, AppInfo1, Source), - {ok, Config}. + {ok, State}; + [] -> + ?INFO("Updating package index...", []), + Url = rebar_state:get(State, rebar_packages_url, ""), + ec_file:mkdir_p(filename:join([os:getenv("HOME"), ".rebar"])), + PackagesFile = filename:join([os:getenv("HOME"), ".rebar", "packages"]), + {ok, RequestId} = httpc:request(get, {Url, []}, [], [{stream, PackagesFile}, {sync, false}]), + wait(RequestId, State) + end. + +wait(RequestId, State) -> + receive + {http, {RequestId, saved_to_file}} -> + io:format("~n"), + {ok, State} + after + 500 -> + io:format("."), + wait(RequestId, State) + end. diff --git a/src/rebar_qc.erl b/src/rebar_qc.erl deleted file mode 100644 index 2c565f1..0000000 --- a/src/rebar_qc.erl +++ /dev/null @@ -1,216 +0,0 @@ -%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 et -%% ------------------------------------------------------------------- -%% -%% rebar: Erlang Build Tools -%% -%% Copyright (c) 2011-2014 Tuncer Ayaz -%% -%% Permission is hereby granted, free of charge, to any person obtaining a copy -%% of this software and associated documentation files (the "Software"), to deal -%% in the Software without restriction, including without limitation the rights -%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%% copies of the Software, and to permit persons to whom the Software is -%% furnished to do so, subject to the following conditions: -%% -%% The above copyright notice and this permission notice shall be included in -%% all copies or substantial portions of the Software. -%% -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -%% THE SOFTWARE. -%% ------------------------------------------------------------------- --module(rebar_qc). - --export([qc/2, triq/2, eqc/2, clean/2]). - -%% for internal use only --export([info/2]). - --include("rebar.hrl"). - --define(QC_DIR, ".qc"). - -%% =================================================================== -%% Public API -%% =================================================================== - -qc(Config, _AppFile) -> - ?CONSOLE("NOTICE: Using experimental 'qc' command~n", []), - run_qc(Config, qc_opts(Config)). - -triq(Config, _AppFile) -> - ?CONSOLE("NOTICE: Using experimental 'triq' command~n", []), - ok = load_qc_mod(triq), - run_qc(Config, qc_opts(Config), triq). - -eqc(Config, _AppFile) -> - ?CONSOLE("NOTICE: Using experimental 'eqc' command~n", []), - ok = load_qc_mod(eqc), - run_qc(Config, qc_opts(Config), eqc). - -clean(_Config, _File) -> - rebar_file_utils:rm_rf(?QC_DIR). - -%% =================================================================== -%% Internal functions -%% =================================================================== - -info(help, qc) -> - ?CONSOLE( - "Test QuickCheck properties.~n" - "~n" - "Valid rebar.config options:~n" - " {qc_opts, [{qc_mod, module()}, Options]}~n" - " ~p~n" - " ~p~n" - " ~p~n" - " ~p~n" - " ~p~n" - "Valid command line options:~n" - " compile_only=true (Compile but do not test properties)", - [ - {qc_compile_opts, []}, - {qc_first_files, []}, - {cover_enabled, false}, - {cover_print_enabled, false}, - {cover_export_enabled, false} - ]); -info(help, clean) -> - Description = ?FMT("Delete QuickCheck test dir (~s)", [?QC_DIR]), - ?CONSOLE("~s.~n", [Description]). - --define(TRIQ_MOD, triq). --define(EQC_MOD, eqc). - -qc_opts(Config) -> - rebar_config:get(Config, qc_opts, []). - -run_qc(Config, QCOpts) -> - run_qc(Config, QCOpts, select_qc_mod(QCOpts)). - -run_qc(Config, RawQCOpts, QC) -> - ?DEBUG("Selected QC module: ~p~n", [QC]), - QCOpts = lists:filter(fun({qc_mod, _}) -> false; - (_) -> true - end, RawQCOpts), - run(Config, QC, QCOpts). - -select_qc_mod(QCOpts) -> - case proplists:get_value(qc_mod, QCOpts) of - undefined -> - detect_qc_mod(); - QC -> - case code:ensure_loaded(QC) of - {module, QC} -> - QC; - {error, nofile} -> - ?ABORT("Configured QC library '~p' not available~n", [QC]) - end - end. - -detect_qc_mod() -> - case code:ensure_loaded(?TRIQ_MOD) of - {module, ?TRIQ_MOD} -> - ?TRIQ_MOD; - {error, nofile} -> - case code:ensure_loaded(?EQC_MOD) of - {module, ?EQC_MOD} -> - ?EQC_MOD; - {error, nofile} -> - ?ABORT("No QC library available~n", []) - end - end. - -load_qc_mod(Mod) -> - case code:ensure_loaded(Mod) of - {module, Mod} -> - ok; - {error, nofile} -> - ?ABORT("Failed to load QC lib '~p'~n", [Mod]) - end. - -ensure_dirs() -> - ok = filelib:ensure_dir(filename:join(qc_dir(), "dummy")), - ok = filelib:ensure_dir(filename:join(rebar_utils:ebin_dir(), "dummy")). - -setup_codepath() -> - CodePath = code:get_path(), - true = code:add_patha(qc_dir()), - true = code:add_pathz(rebar_utils:ebin_dir()), - CodePath. - -qc_dir() -> - filename:join(rebar_utils:get_cwd(), ?QC_DIR). - -run(Config, QC, QCOpts) -> - ?DEBUG("qc_opts: ~p~n", [QCOpts]), - - ok = ensure_dirs(), - CodePath = setup_codepath(), - - CompileOnly = rebar_config:get_global(Config, compile_only, false), - %% Compile erlang code to ?QC_DIR, using a tweaked config - %% with appropriate defines, and include all the test modules - %% as well. - {ok, SrcErls} = rebar_erlc_compiler:test_compile(Config, "qc", ?QC_DIR), - - case CompileOnly of - "true" -> - true = code:set_path(CodePath), - ?CONSOLE("Compiled modules for qc~n", []); - false -> - run1(QC, QCOpts, Config, CodePath, SrcErls) - end. - -run1(QC, QCOpts, Config, CodePath, SrcErls) -> - - AllBeamFiles = rebar_utils:beams(?QC_DIR), - AllModules = [rebar_utils:beam_to_mod(?QC_DIR, N) - || N <- AllBeamFiles], - PropMods = find_prop_mods(), - FilteredModules = AllModules -- PropMods, - - SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls], - - {ok, CoverLog} = rebar_cover_utils:init(Config, AllBeamFiles, qc_dir()), - - TestModule = fun(M) -> qc_module(QC, QCOpts, M) end, - QCResult = lists:flatmap(TestModule, PropMods), - - rebar_cover_utils:perform_cover(Config, FilteredModules, SrcModules, - qc_dir()), - rebar_cover_utils:close(CoverLog), - ok = rebar_cover_utils:exit(), - - true = code:set_path(CodePath), - - case QCResult of - [] -> - ok; - Errors -> - ?ABORT("One or more QC properties didn't hold true:~n~p~n", - [Errors]) - end. - -qc_module(QC=triq, _QCOpts, M) -> - case QC:module(M) of - true -> - []; - Failed -> - [Failed] - end; -qc_module(QC=eqc, [], M) -> QC:module(M); -qc_module(QC=eqc, QCOpts, M) -> QC:module(QCOpts, M). - -find_prop_mods() -> - Beams = rebar_utils:find_files(?QC_DIR, "^[^._].*\\.beam\$"), - [M || M <- [rebar_utils:erl_to_mod(Beam) || Beam <- Beams], has_prop(M)]. - -has_prop(Mod) -> - lists:any(fun({F,_A}) -> lists:prefix("prop_", atom_to_list(F)) end, - Mod:module_info(exports)). diff --git a/src/rebar_state.erl b/src/rebar_state.erl index 62448ed..1e06ac6 100644 --- a/src/rebar_state.erl +++ b/src/rebar_state.erl @@ -7,8 +7,7 @@ set_skip_dir/2, is_skip_dir/2, reset_skip_dirs/1, create_logic_providers/2, - add_app/2, apps_to_build/1, apps_to_build/2, - deps_to_build/1, deps_to_build/2, + apps_to_build/1, apps_to_build/2, providers/1, providers/2, add_provider/2]). @@ -30,10 +29,10 @@ envs = new_env() :: rebar_dict(), command_args = [] :: list(), + apps = dict:new() :: rebar_dict(), goals = [], providers = [], apps_to_build = [], - deps_to_build = [], skip_dirs = new_skip_dirs() :: rebar_dict() }). -export_type([t/0]). @@ -75,9 +74,6 @@ get(State, Key) -> get(State, Key, Default) -> proplists:get_value(Key, State#state_t.opts, Default). -get_local(State, Key, Default) -> - proplists:get_value(Key, State#state_t.local_opts, Default). - set(State, Key, Value) -> Opts = proplists:delete(Key, State#state_t.opts), State#state_t { opts = [{Key, Value} | Opts] }. @@ -105,40 +101,20 @@ command_args(#state_t{command_args=CmdArgs}) -> command_args(State, CmdArgs) -> State#state_t{command_args=CmdArgs}. -get_app(#state_t{apps_to_build=Apps}, Name) -> - lists:keyfind(Name, 2, Apps). - apps_to_build(#state_t{apps_to_build=Apps}) -> Apps. -apps_to_build(State, Apps) -> - State#state_t{apps_to_build=Apps}. - -deps_to_build(#state_t{deps_to_build=Deps}) -> - Deps. - -deps_to_build(State, Deps) -> - State#state_t{deps_to_build=Deps}. - -add_app(State=#state_t{apps_to_build=Apps}, App) -> +apps_to_build(State=#state_t{apps_to_build=Apps}, Apps) when is_list(Apps) -> + State#state_t{apps_to_build=Apps}; +apps_to_build(State=#state_t{apps_to_build=Apps}, App) -> State#state_t{apps_to_build=[App | Apps]}. -replace_app(State=#state_t{apps_to_build=Apps}, Name, App) -> - Apps1 = lists:keydelete(Name, 2, Apps), - State#state_t{apps_to_build=[App | Apps1]}. - providers(#state_t{providers=Providers}) -> Providers. providers(State, NewProviders) -> State#state_t{providers=NewProviders}. -goals(#state_t{goals=Goals}) -> - Goals. - -goals(State, Goals) -> - State#state_t{goals=Goals}. - add_provider(State=#state_t{providers=Providers}, Provider) -> State#state_t{providers=[Provider | Providers]}. diff --git a/src/rebar_subdirs.erl b/src/rebar_subdirs.erl deleted file mode 100644 index f444a59..0000000 --- a/src/rebar_subdirs.erl +++ /dev/null @@ -1,84 +0,0 @@ -%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 et -%% ------------------------------------------------------------------- -%% -%% rebar: Erlang Build Tools -%% -%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com) -%% -%% Permission is hereby granted, free of charge, to any person obtaining a copy -%% of this software and associated documentation files (the "Software"), to deal -%% in the Software without restriction, including without limitation the rights -%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%% copies of the Software, and to permit persons to whom the Software is -%% furnished to do so, subject to the following conditions: -%% -%% The above copyright notice and this permission notice shall be included in -%% all copies or substantial portions of the Software. -%% -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -%% THE SOFTWARE. -%% ------------------------------------------------------------------- --module(rebar_subdirs). - --include("rebar.hrl"). --include_lib("kernel/include/file.hrl"). - --export([preprocess/2]). - -%% =================================================================== -%% Public API -%% =================================================================== - -preprocess(Config, _) -> - %% Get the list of subdirs specified in the config (if any). - Cwd = rebar_utils:get_cwd(), - ListSubdirs = rebar_config:get_local(Config, sub_dirs, []), - Subdirs0 = lists:flatmap(fun filelib:wildcard/1, ListSubdirs), - case {rebar_config:is_skip_dir(Config, Cwd), Subdirs0} of - {true, []} -> - {ok, []}; - {true, _} -> - ?WARN("Ignoring sub_dirs for ~s~n", [Cwd]), - {ok, []}; - {false, _} -> - Check = check_loop(Cwd), - ok = lists:foreach(Check, Subdirs0), - Subdirs = [filename:join(Cwd, Dir) || Dir <- Subdirs0], - {ok, Subdirs} - end. - -%% =================================================================== -%% Internal functions -%% =================================================================== - -check_loop(Cwd) -> - RebarConfig = filename:join(Cwd, "rebar.config"), - fun(Dir0) -> - IsSymlink = case file:read_link_info(Dir0) of - {ok, #file_info{type=symlink}} -> - {true, resolve_symlink(Dir0)}; - _ -> - {false, Dir0} - end, - case IsSymlink of - {false, Dir="."} -> - ?ERROR("infinite loop detected:~nsub_dirs" - " entry ~p in ~s~n", [Dir, RebarConfig]); - {true, Cwd} -> - ?ERROR("infinite loop detected:~nsub_dirs" - " entry ~p in ~s is a symlink to \".\"~n", - [Dir0, RebarConfig]); - _ -> - ok - end - end. - -resolve_symlink(Dir0) -> - {ok, Dir} = file:read_link(Dir0), - Dir. diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index 6b12e99..18e76fc 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -63,13 +63,16 @@ ebin_dir/0, processing_base_dir/1, processing_base_dir/2, - patch_env/2]). + patch_env/2, + indent/1]). %% for internal use only -export([otp_release/0]). -include("rebar.hrl"). +-define(ONE_LEVEL_INDENT, " "). + %% ==================================================================== %% Public API %% ==================================================================== @@ -598,3 +601,8 @@ filter_defines([{platform_define, ArchRegex, Key, Value} | Rest], Acc) -> end; filter_defines([Opt | Rest], Acc) -> filter_defines(Rest, [Opt | Acc]). + +%% @doc ident to the level specified +-spec indent(non_neg_integer()) -> iolist(). +indent(Amount) when erlang:is_integer(Amount) -> + [?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)]. diff --git a/src/rebar_xref.erl b/src/rebar_xref.erl deleted file mode 100644 index 16e8cc4..0000000 --- a/src/rebar_xref.erl +++ /dev/null @@ -1,290 +0,0 @@ -%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 et -%% ------------------------------------------------------------------- -%% -%% rebar: Erlang Build Tools -%% -%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com) -%% -%% Permission is hereby granted, free of charge, to any person obtaining a copy -%% of this software and associated documentation files (the "Software"), to deal -%% in the Software without restriction, including without limitation the rights -%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%% copies of the Software, and to permit persons to whom the Software is -%% furnished to do so, subject to the following conditions: -%% -%% The above copyright notice and this permission notice shall be included in -%% all copies or substantial portions of the Software. -%% -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -%% THE SOFTWARE. -%% -%% ------------------------------------------------------------------- - -%% ------------------------------------------------------------------- -%% This module borrows heavily from http://github.com/etnt/exrefcheck project as -%% written by Torbjorn Tornkvist <tobbe@kreditor.se>, Daniel Luna -%% <daniel@lunas.se> and others. -%% ------------------------------------------------------------------- --module(rebar_xref). - --include("rebar.hrl"). - --export([xref/2]). - -%% for internal use only --export([info/2]). - -%% =================================================================== -%% Public API -%% =================================================================== - -xref(Config, _) -> - %% Spin up xref - {ok, _} = xref:start(xref), - ok = xref:set_library_path(xref, code_path(Config)), - - xref:set_default(xref, [{warnings, - rebar_config:get(Config, xref_warnings, false)}, - {verbose, rebar_log:is_verbose(Config)}]), - - {ok, _} = xref:add_directory(xref, "ebin"), - - %% Save the code path prior to doing anything - OrigPath = code:get_path(), - true = code:add_path(rebar_utils:ebin_dir()), - - %% Get list of xref checks we want to run - ConfXrefChecks = rebar_config:get(Config, xref_checks, - [exports_not_used, - undefined_function_calls]), - - SupportedXrefs = [undefined_function_calls, undefined_functions, - locals_not_used, exports_not_used, - deprecated_function_calls, deprecated_functions], - - XrefChecks = sets:to_list(sets:intersection( - sets:from_list(SupportedXrefs), - sets:from_list(ConfXrefChecks))), - - %% Run xref checks - XrefNoWarn = xref_checks(XrefChecks), - - %% Run custom queries - QueryChecks = rebar_config:get(Config, xref_queries, []), - QueryNoWarn = lists:all(fun check_query/1, QueryChecks), - - %% Restore the original code path - true = code:set_path(OrigPath), - - %% Stop xref - stopped = xref:stop(xref), - - case lists:member(false, [XrefNoWarn, QueryNoWarn]) of - true -> - ?FAIL; - false -> - ok - end. - -%% =================================================================== -%% Internal functions -%% =================================================================== - -info(help, xref) -> - ?CONSOLE( - "Run cross reference analysis.~n" - "~n" - "Valid rebar.config options:~n" - " ~p~n" - " ~p~n" - " ~p~n" - " ~p~n", - [ - {xref_warnings, false}, - {xref_extra_paths,[]}, - {xref_checks, [undefined_function_calls, undefined_functions, - locals_not_used, exports_not_used, - deprecated_function_calls, deprecated_functions]}, - {xref_queries, - [{"(xc - uc) || (xu - x - b" - " - (\"mod\":\".*foo\"/\"4\"))",[]}]} - ]). - -xref_checks(XrefChecks) -> - XrefWarnCount = lists:foldl(fun run_xref_check/2, 0, XrefChecks), - XrefWarnCount =:= 0. - -run_xref_check(XrefCheck, Acc) -> - {ok, Results} = xref:analyze(xref, XrefCheck), - FilteredResults =filter_xref_results(XrefCheck, Results), - lists:foreach(fun(Res) -> - display_xref_result(XrefCheck, Res) - end, - FilteredResults), - Acc + length(FilteredResults). - -check_query({Query, Value}) -> - {ok, Answer} = xref:q(xref, Query), - case Answer =:= Value of - false -> - ?CONSOLE("Query ~s~n answer ~p~n did not match ~p~n", - [Query, Answer, Value]), - false; - _ -> - true - end. - -code_path(Config) -> - %% Slight hack to ensure that sub_dirs get properly included - %% in code path for xref -- otherwise one gets a lot of undefined - %% functions, even though those functions are present as part - %% of compilation. H/t to @dluna. Long term we should tie more - %% properly into the overall compile code path if possible. - BaseDir = rebar_utils:base_dir(Config), - [P || P <- code:get_path() ++ - rebar_config:get(Config, xref_extra_paths, []) ++ - [filename:join(BaseDir, filename:join(SubDir, "ebin")) - || SubDir <- rebar_config:get(Config, sub_dirs, [])], - filelib:is_dir(P)]. - -%% -%% Ignore behaviour functions, and explicitly marked functions -%% -%% Functions can be ignored by using -%% -ignore_xref([{F, A}, {M, F, A}...]). - -get_xref_ignorelist(Mod, XrefCheck) -> - %% Get ignore_xref attribute and combine them in one list - Attributes = - try - Mod:module_info(attributes) - catch - _Class:_Error -> [] - end, - - IgnoreXref = keyall(ignore_xref, Attributes), - - BehaviourCallbacks = get_behaviour_callbacks(XrefCheck, Attributes), - - %% And create a flat {M,F,A} list - lists:foldl( - fun({F, A}, Acc) -> [{Mod,F,A} | Acc]; - ({M, F, A}, Acc) -> [{M,F,A} | Acc] - end, [], lists:flatten([IgnoreXref, BehaviourCallbacks])). - -keyall(Key, List) -> - lists:flatmap(fun({K, L}) when Key =:= K -> L; (_) -> [] end, List). - -get_behaviour_callbacks(exports_not_used, Attributes) -> - [B:behaviour_info(callbacks) || B <- keyall(behaviour, Attributes)]; -get_behaviour_callbacks(_XrefCheck, _Attributes) -> - []. - -parse_xref_result({_, MFAt}) -> MFAt; -parse_xref_result(MFAt) -> MFAt. - -filter_xref_results(XrefCheck, XrefResults) -> - SearchModules = lists:usort( - lists:map( - fun({Mt,_Ft,_At}) -> Mt; - ({{Ms,_Fs,_As},{_Mt,_Ft,_At}}) -> Ms; - (_) -> undefined - end, XrefResults)), - - Ignores = lists:flatmap(fun(Module) -> - get_xref_ignorelist(Module, XrefCheck) - end, SearchModules), - - [Result || Result <- XrefResults, - not lists:member(parse_xref_result(Result), Ignores)]. - -display_xref_result(Type, XrefResult) -> - { Source, SMFA, TMFA } = case XrefResult of - {MFASource, MFATarget} -> - {format_mfa_source(MFASource), - format_mfa(MFASource), - format_mfa(MFATarget)}; - MFATarget -> - {format_mfa_source(MFATarget), - format_mfa(MFATarget), - undefined} - end, - case Type of - undefined_function_calls -> - ?CONSOLE("~sWarning: ~s calls undefined function ~s (Xref)\n", - [Source, SMFA, TMFA]); - undefined_functions -> - ?CONSOLE("~sWarning: ~s is undefined function (Xref)\n", - [Source, SMFA]); - locals_not_used -> - ?CONSOLE("~sWarning: ~s is unused local function (Xref)\n", - [Source, SMFA]); - exports_not_used -> - ?CONSOLE("~sWarning: ~s is unused export (Xref)\n", - [Source, SMFA]); - deprecated_function_calls -> - ?CONSOLE("~sWarning: ~s calls deprecated function ~s (Xref)\n", - [Source, SMFA, TMFA]); - deprecated_functions -> - ?CONSOLE("~sWarning: ~s is deprecated function (Xref)\n", - [Source, SMFA]); - Other -> - ?CONSOLE("~sWarning: ~s - ~s xref check: ~s (Xref)\n", - [Source, SMFA, TMFA, Other]) - end. - -format_mfa({M, F, A}) -> - ?FMT("~s:~s/~w", [M, F, A]). - -format_mfa_source(MFA) -> - case find_mfa_source(MFA) of - {module_not_found, function_not_found} -> ""; - {Source, function_not_found} -> ?FMT("~s: ", [Source]); - {Source, Line} -> ?FMT("~s:~w: ", [Source, Line]) - end. - -%% -%% Extract an element from a tuple, or undefined if N > tuple size -%% -safe_element(N, Tuple) -> - case catch(element(N, Tuple)) of - {'EXIT', {badarg, _}} -> - undefined; - Value -> - Value - end. - -%% -%% Given a MFA, find the file and LOC where it's defined. Note that -%% xref doesn't work if there is no abstract_code, so we can avoid -%% being too paranoid here. -%% -find_mfa_source({M, F, A}) -> - case code:get_object_code(M) of - error -> {module_not_found, function_not_found}; - {M, Bin, _} -> find_function_source(M,F,A,Bin) - end. - -find_function_source(M, F, A, Bin) -> - AbstractCode = beam_lib:chunks(Bin, [abstract_code]), - {ok, {M, [{abstract_code, {raw_abstract_v1, Code}}]}} = AbstractCode, - %% Extract the original source filename from the abstract code - [{attribute, 1, file, {Source, _}} | _] = Code, - %% Extract the line number for a given function def - Fn = [E || E <- Code, - safe_element(1, E) == function, - safe_element(3, E) == F, - safe_element(4, E) == A], - case Fn of - [{function, Line, F, _, _}] -> {Source, Line}; - %% do not crash if functions are exported, even though they - %% are not in the source. - %% parameterized modules add new/1 and instance/1 for example. - [] -> {Source, function_not_found} - end. |