diff options
Diffstat (limited to 'src')
35 files changed, 768 insertions, 661 deletions
diff --git a/src/rebar.app.src b/src/rebar.app.src index 63cca9c..f753784 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -3,7 +3,7 @@ {application, rebar, [{description, "Rebar: Erlang Build Tool"}, - {vsn, "3.0.0-alpha-4"}, + {vsn, "3.0.0-alpha-5"}, {modules, []}, {registered, []}, {applications, [kernel, @@ -37,7 +37,6 @@ rebar_prv_dialyzer, rebar_prv_do, rebar_prv_edoc, - rebar_prv_erlydtl_compiler, rebar_prv_escriptize, rebar_prv_eunit, rebar_prv_help, @@ -49,6 +48,7 @@ rebar_prv_report, rebar_prv_shell, rebar_prv_tar, + rebar_prv_unlock, rebar_prv_update, rebar_prv_upgrade, rebar_prv_version, diff --git a/src/rebar3.erl b/src/rebar3.erl index 460bc36..92803c5 100644 --- a/src/rebar3.erl +++ b/src/rebar3.erl @@ -27,6 +27,7 @@ -module(rebar3). -export([main/1, + run/1, run/2, global_option_spec_list/0, init_config/0, @@ -114,6 +115,8 @@ run_aux(State, RawArgs) -> State2 = case os:getenv("REBAR_PROFILE") of false -> State; + "" -> + State; Profile -> rebar_state:apply_profiles(State, [list_to_atom(Profile)]) end, @@ -124,16 +127,16 @@ run_aux(State, RawArgs) -> filename:join(filename:absname(rebar_state:dir(State2)), BaseDir)), {ok, Providers} = application:get_env(rebar, providers), - {ok, Resources} = application:get_env(rebar, resources), - State4 = rebar_state:resources(State3, Resources), - State5 = rebar_plugins:install(State4), + State4 = rebar_plugins:install(State3), %% Providers can modify profiles stored in opts, so set default after initializing providers - State6 = rebar_state:create_logic_providers(Providers, State5), - State7 = rebar_state:default(State6, rebar_state:opts(State6)), + State5 = rebar_state:create_logic_providers(Providers, State4), + State6 = rebar_state:default(State5, rebar_state:opts(State5)), {Task, Args} = parse_args(RawArgs), + State7 = rebar_state:code_paths(State6, default, code:get_path()), + rebar_core:init_command(rebar_state:command_args(State7, Args), Task). init_config() -> diff --git a/src/rebar_api.erl b/src/rebar_api.erl index 3fc7f61..45083df 100644 --- a/src/rebar_api.erl +++ b/src/rebar_api.erl @@ -8,7 +8,10 @@ debug/2, info/2, warn/2, error/2, expand_env_variable/3, get_arch/0, - wordsize/0]). + wordsize/0, + add_deps_to_path/1, + restore_code_path/1, + processing_base_dir/1]). -export_type([rebar_dict/0, rebar_digraph/0]). @@ -48,3 +51,15 @@ get_arch() -> wordsize() -> rebar_utils:wordsize(). + + +%% Add deps to the code path +add_deps_to_path(State) -> + code:add_pathsa(rebar_state:code_paths(State, all_deps)). + +%% Revert to only having the beams necessary for running rebar3 and plugins in the path +restore_code_path(State) -> + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)). + +processing_base_dir(State) -> + rebar_dir:processing_base_dir(State). diff --git a/src/rebar_app_discover.erl b/src/rebar_app_discover.erl index 16dcf24..73401bc 100644 --- a/src/rebar_app_discover.erl +++ b/src/rebar_app_discover.erl @@ -37,12 +37,13 @@ merge_deps(AppInfo, State) -> Default = rebar_state:default(State), CurrentProfiles = rebar_state:current_profiles(State), Name = rebar_app_info:name(AppInfo), - C = rebar_config:consult(rebar_app_info:dir(AppInfo)), + C = project_app_config(AppInfo, State), %% We reset the opts here to default so no profiles are applied multiple times AppState = rebar_state:apply_overrides( rebar_state:apply_profiles( - rebar_state:new(rebar_state:opts(State, Default), C, rebar_app_info:dir(AppInfo)), CurrentProfiles), Name), + rebar_state:new(reset_hooks(rebar_state:opts(State, Default)), C, + rebar_app_info:dir(AppInfo)), CurrentProfiles), Name), AppInfo1 = rebar_app_info:state(AppInfo, AppState), State1 = lists:foldl(fun(Profile, StateAcc) -> @@ -56,6 +57,27 @@ merge_deps(AppInfo, State) -> {AppInfo1, State1}. +project_app_config(AppInfo, State) -> + C = rebar_config:consult(rebar_app_info:dir(AppInfo)), + Dir = rebar_app_info:dir(AppInfo), + maybe_reset_hooks(C, Dir, State). + +%% Here we check if the app is at the root of the project. +%% If it is, then drop the hooks from the config so they aren't run twice +maybe_reset_hooks(C, Dir, State) -> + case filename:dirname(rebar_dir:root_dir(State)) of + Dir -> + C1 = proplists:delete(provider_hooks, C), + proplists:delete(hooks, C1); + _ -> + C + end. + +reset_hooks(State) -> + lists:foldl(fun(Key, StateAcc) -> + rebar_state:set(StateAcc, Key, []) + end, State, [post_hooks, pre_hooks, provider_hooks]). + -spec all_app_dirs(list(file:name())) -> list(file:name()). all_app_dirs(LibDirs) -> lists:flatmap(fun(LibDir) -> @@ -137,7 +159,12 @@ find_app(AppDir, Validate) -> case Validate of V when V =:= invalid ; V =:= all -> AppInfo = create_app_info(AppDir, File), - {true, rebar_app_info:app_file_src(AppInfo, File)}; + case AppInfo of + {error, Reason} -> + throw({error, {invalid_app_file, File, Reason}}); + _ -> + {true, rebar_app_info:app_file_src(AppInfo, File)} + end; valid -> false end; @@ -153,7 +180,7 @@ find_app(AppDir, Validate) -> app_dir(AppFile) -> filename:join(rebar_utils:droplast(filename:split(filename:dirname(AppFile)))). --spec create_app_info(file:name(), file:name()) -> rebar_app_info:t() | error. +-spec create_app_info(file:name(), file:name()) -> rebar_app_info:t() | {error, term()}. create_app_info(AppDir, AppFile) -> case file:consult(AppFile) of {ok, [{application, AppName, AppDetails}]} -> @@ -171,8 +198,8 @@ create_app_info(AppDir, AppFile) -> false end, rebar_app_info:dir(rebar_app_info:valid(AppInfo1, Valid), AppDir); - _ -> - error + {error, Reason} -> + {error, Reason} end. dedup([]) -> []; diff --git a/src/rebar_app_info.erl b/src/rebar_app_info.erl index 9659c0b..91640f2 100644 --- a/src/rebar_app_info.erl +++ b/src/rebar_app_info.erl @@ -61,7 +61,7 @@ %%============================================================================ %% types %%============================================================================ --type t() :: record(app_info_t). +-type t() :: #app_info_t{}. %%============================================================================ %% API @@ -157,6 +157,19 @@ app_file(AppInfo=#app_info_t{}, AppFile) -> AppInfo#app_info_t{app_file=AppFile}. -spec app_details(t()) -> list(). +app_details(AppInfo=#app_info_t{app_details=[]}) -> + AppFile = case app_file(AppInfo) of + undefined -> + app_file_src(AppInfo); + File -> + File + end, + case file:consult(AppFile) of + {ok, [{application, _, AppDetails}]} -> + AppDetails; + _ -> + [] + end; app_details(#app_info_t{app_details=AppDetails}) -> AppDetails. diff --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl index df950d7..0101218 100644 --- a/src/rebar_base_compiler.erl +++ b/src/rebar_base_compiler.erl @@ -114,7 +114,7 @@ compile_each([Source | Rest], Config, CompileFn) -> skipped -> ?DEBUG("~sSkipped ~s", [rebar_utils:indent(1), filename:basename(Source)]); Error -> - ?INFO("Compiling ~s failed:", + ?ERROR("Compiling ~s failed", [maybe_absname(Config, Source)]), maybe_report(Error), ?DEBUG("Compilation failed: ~p", [Error]), diff --git a/src/rebar_config.erl b/src/rebar_config.erl index 76e03ea..c858fef 100644 --- a/src/rebar_config.erl +++ b/src/rebar_config.erl @@ -57,7 +57,6 @@ consult_file(File) -> {ok, Terms} = consult_and_eval(File, Script), Terms; false -> - ?DEBUG("Consult config file ~p", [File]), try_consult(File) end end. @@ -70,7 +69,7 @@ merge_locks(Config, [[]]) -> Config; %% lockfile with entries merge_locks(Config, [Locks]) -> - {deps, ConfigDeps} = lists:keyfind(deps, 1, Config), + ConfigDeps = proplists:get_value(deps, Config, []), %% We want the top level deps only from the lock file. %% This ensures deterministic overrides for configs. %% Then check if any new deps have been added to the config diff --git a/src/rebar_dir.erl b/src/rebar_dir.erl index 7903ed5..a94c72d 100644 --- a/src/rebar_dir.erl +++ b/src/rebar_dir.erl @@ -1,8 +1,10 @@ -module(rebar_dir). -export([base_dir/1, + profile_dir/2, deps_dir/1, deps_dir/2, + root_dir/1, checkouts_dir/1, checkouts_dir/2, plugins_dir/1, @@ -24,16 +26,20 @@ -spec base_dir(rebar_state:t()) -> file:filename_all(). base_dir(State) -> - Profiles = rebar_state:current_profiles(State), + profile_dir(State, rebar_state:current_profiles(State)). + +-spec profile_dir(rebar_state:t(), [atom()]) -> file:filename_all(). +profile_dir(State, Profiles) -> ProfilesStrings = case [ec_cnv:to_list(P) || P <- Profiles] of ["default"] -> ["default"]; %% drop `default` from the profile dir if it's implicit and reverse order %% of profiles to match order passed to `as` - ["default"|Rest] -> lists:reverse(Rest) + ["default"|Rest] -> Rest end, ProfilesDir = string:join(ProfilesStrings, "+"), filename:join(rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR), ProfilesDir). + -spec deps_dir(rebar_state:t()) -> file:filename_all(). deps_dir(State) -> filename:join(base_dir(State), rebar_state:get(State, deps_dir, ?DEFAULT_DEPS_DIR)). @@ -43,7 +49,7 @@ deps_dir(DepsDir, App) -> filename:join(DepsDir, App). root_dir(State) -> - rebar_state:get(State, root_dir, ?DEFAULT_ROOT_DIR). + filename:absname(rebar_state:get(State, root_dir, ?DEFAULT_ROOT_DIR)). -spec checkouts_dir(rebar_state:t()) -> file:filename_all(). checkouts_dir(State) -> diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl index 792d358..8fe03b7 100644 --- a/src/rebar_erlc_compiler.erl +++ b/src/rebar_erlc_compiler.erl @@ -148,8 +148,8 @@ doterl_compile(Config, Dir, OutDir, MoreSources, ErlOpts) -> %% Make sure that ebin/ exists and is on the path ok = filelib:ensure_dir(filename:join(OutDir, "dummy.beam")), - CurrPath = code:get_path(), - true = code:add_path(filename:absname(OutDir)), + true = code:add_patha(filename:absname(OutDir)), + OutDir1 = proplists:get_value(outdir, ErlOpts, OutDir), G = init_erlcinfo(proplists:get_all_values(i, ErlOpts), AllErlFiles, Dir), @@ -161,7 +161,7 @@ doterl_compile(Config, Dir, OutDir, MoreSources, ErlOpts) -> filename:extension(File) =:= ".erl"], NeededErlFiles = needed_files(G, ErlOpts, Dir, OutDir1, AllErlFiles), - ErlFirstFiles = erl_first_files(Config, NeededErlFiles), + {ErlFirstFiles, ErlOptsFirst} = erl_first_files(Config, ErlOpts, Dir, NeededErlFiles), {DepErls, OtherErls} = lists:partition( fun(Source) -> digraph:in_degree(G, Source) > 0 end, [File || File <- NeededErlFiles, not lists:member(File, ErlFirstFiles)]), @@ -171,15 +171,36 @@ doterl_compile(Config, Dir, OutDir, MoreSources, ErlOpts) -> rebar_base_compiler:run( Config, FirstErls, OtherErls, fun(S, C) -> - internal_erl_compile(C, Dir, S, OutDir1, ErlOpts) + ErlOpts1 = case lists:member(S, ErlFirstFiles) of + true -> ErlOptsFirst; + false -> ErlOpts + end, + internal_erl_compile(C, Dir, S, OutDir1, ErlOpts1) end), - true = code:set_path(CurrPath), ok. -erl_first_files(Config, NeededErlFiles) -> +%% Get files which need to be compiled first, i.e. those specified in erl_first_files +%% and parse_transform options. Also produce specific erl_opts for these first +%% files, so that yet to be compiled parse transformations are excluded from it. +erl_first_files(Config, ErlOpts, Dir, NeededErlFiles) -> ErlFirstFilesConf = rebar_state:get(Config, erl_first_files, []), - %% NOTE: order of files in ErlFirstFiles is important! - [File || File <- ErlFirstFilesConf, lists:member(File, NeededErlFiles)]. + NeededSrcDirs = lists:usort(lists:map(fun filename:dirname/1, NeededErlFiles)), + %% NOTE: order of files here is important! + ErlFirstFiles = + [filename:join(Dir, File) || File <- ErlFirstFilesConf, + lists:member(filename:join(Dir, File), NeededErlFiles)], + {ParseTransforms, ParseTransformsErls} = + lists:unzip(lists:flatmap( + fun(PT) -> + PTerls = [filename:join(D, module_to_erl(PT)) || D <- NeededSrcDirs], + [{PT, PTerl} || PTerl <- PTerls, lists:member(PTerl, NeededErlFiles)] + end, proplists:get_all_values(parse_transform, ErlOpts))), + ErlOptsFirst = lists:filter(fun({parse_transform, PT}) -> + not lists:member(PT, ParseTransforms); + (_) -> + true + end, ErlOpts), + {ErlFirstFiles ++ ParseTransformsErls, ErlOptsFirst}. %% Get subset of SourceFiles which need to be recompiled, respecting %% dependencies induced by given graph G. @@ -541,21 +562,17 @@ maybe_expand_include_lib_path(File, Dir) -> %% The use of -include_lib was probably incorrect by the user but lets try to make it work. %% We search in the outdir and outdir/../include to see if the header exists. warn_and_find_path(File, Dir) -> - ?WARN("Bad use of -include_lib(\"~s\")." - " First path component should be the name of an application." - " You probably meant -include(\"~s\").", [File, File]), SrcHeader = filename:join(Dir, File), case filelib:is_regular(SrcHeader) of true -> [SrcHeader]; false -> - IncludeDir = filename:join(filename:join(lists:droplast(filename:split(Dir))), "include"), + IncludeDir = filename:join(filename:join(rebar_utils:droplast(filename:split(Dir))), "include"), IncludeHeader = filename:join(IncludeDir, File), case filelib:is_regular(IncludeHeader) of true -> [filename:join(IncludeDir, File)]; false -> - ?WARN("Could not find header for -include_lib(\"~s\").", [File]), [] end end. diff --git a/src/rebar_git_resource.erl b/src/rebar_git_resource.erl index 3b77da2..2d83579 100644 --- a/src/rebar_git_resource.erl +++ b/src/rebar_git_resource.erl @@ -16,7 +16,7 @@ lock(AppDir, {git, Url, _}) -> lock(AppDir, {git, Url}) -> AbortMsg = io_lib:format("Locking of git dependency failed in ~s", [AppDir]), {ok, VsnString} = - rebar_utils:sh("git --git-dir='" ++ AppDir ++ "/.git' rev-parse --verify HEAD", + rebar_utils:sh("git --git-dir=\"" ++ AppDir ++ "/.git\" rev-parse --verify HEAD", [{use_stdout, false}, {debug_abort_on_error, AbortMsg}]), Ref = string:strip(VsnString, both, $\n), {git, Url, {ref, Ref}}. @@ -31,11 +31,14 @@ needs_update(Dir, {git, Url, {tag, Tag}}) -> ?DEBUG("Comparing git tag ~s with ~s", [Tag, Current1]), not ((Current1 =:= Tag) andalso compare_url(Dir, Url)); needs_update(Dir, {git, Url, {branch, Branch}}) -> - {ok, Current} = rebar_utils:sh(?FMT("git symbolic-ref -q --short HEAD", []), + %% Fetch remote so we can check if the branch has changed + {ok, _} = rebar_utils:sh(?FMT("git fetch origin ~s", [Branch]), + [{cd, Dir}]), + %% Check for new commits to origin/Branch + {ok, Current} = rebar_utils:sh(?FMT("git log HEAD..origin/~s --oneline", [Branch]), [{cd, Dir}]), - Current1 = string:strip(string:strip(Current, both, $\n), both, $\r), - ?DEBUG("Comparing git branch ~s with ~s", [Branch, Current1]), - not ((Current1 =:= Branch) andalso compare_url(Dir, Url)); + ?DEBUG("Checking git branch ~s for updates", [Branch]), + not ((Current =:= []) andalso compare_url(Dir, Url)); needs_update(Dir, {git, Url, "master"}) -> needs_update(Dir, {git, Url, {branch, "master"}}); needs_update(Dir, {git, Url, Ref}) -> @@ -122,7 +125,7 @@ collect_default_refcount() -> %% timestamp is really important from an ordering perspective. AbortMsg1 = "Getting log of git dependency failed in " ++ rebar_dir:get_cwd(), {ok, String} = - rebar_utils:sh("git log -n 1 --pretty=format:'%h\n' ", + rebar_utils:sh("git log -n 1 --pretty=format:\"%h\n\" ", [{use_stdout, false}, {debug_abort_on_error, AbortMsg1}]), RawRef = string:strip(String, both, $\n), @@ -132,41 +135,43 @@ collect_default_refcount() -> case Tag of undefined -> AbortMsg2 = "Getting rev-list of git depedency failed in " ++ rebar_dir:get_cwd(), - rebar_utils:sh("git rev-list HEAD | wc -l", - [{use_stdout, false}, - {debug_abort_on_error, AbortMsg2}]); + {ok, PatchLines} = rebar_utils:sh("git rev-list HEAD", + [{use_stdout, false}, + {debug_abort_on_error, AbortMsg2}]), + rebar_utils:line_count(PatchLines); _ -> get_patch_count(Tag) end, {TagVsn, RawRef, RawCount}. -build_vsn_string(Vsn, RawRef, RawCount) -> +build_vsn_string(Vsn, RawRef, Count) -> %% Cleanup the tag and the Ref information. Basically leading 'v's and %% whitespace needs to go away. RefTag = [".ref", re:replace(RawRef, "\\s", "", [global])], - Count = erlang:iolist_to_binary(re:replace(RawCount, "\\s", "", [global])), %% Create the valid [semver](http://semver.org) version from the tag case Count of - <<"0">> -> + 0 -> erlang:binary_to_list(erlang:iolist_to_binary(Vsn)); _ -> erlang:binary_to_list(erlang:iolist_to_binary([Vsn, "+build.", - Count, RefTag])) + integer_to_list(Count), RefTag])) end. get_patch_count(RawRef) -> AbortMsg = "Getting rev-list of git dep failed in " ++ rebar_dir:get_cwd(), Ref = re:replace(RawRef, "\\s", "", [global]), - Cmd = io_lib:format("git rev-list ~s..HEAD | wc -l", + Cmd = io_lib:format("git rev-list ~s..HEAD", [Ref]), - rebar_utils:sh(Cmd, - [{use_stdout, false}, - {debug_abort_on_error, AbortMsg}]). + {ok, PatchLines} = rebar_utils:sh(Cmd, + [{use_stdout, false}, + {debug_abort_on_error, AbortMsg}]), + rebar_utils:line_count(PatchLines). + parse_tags() -> %% Don't abort on error, we want the bad return to be turned into 0.0.0 - case rebar_utils:sh("git log --oneline --decorate | fgrep \"tag: \" -1000", + case rebar_utils:sh("git log --oneline --no-walk --tags --decorate", [{use_stdout, false}, return_on_error]) of {error, _} -> {undefined, "0.0.0"}; diff --git a/src/rebar_otp_app.erl b/src/rebar_otp_app.erl index a93defc..0bf27c9 100644 --- a/src/rebar_otp_app.erl +++ b/src/rebar_otp_app.erl @@ -104,7 +104,7 @@ preprocess(State, AppInfo, AppSrcFile) -> A1 = apply_app_vars(AppVars, AppData), %% AppSrcFile may contain instructions for generating a vsn number - Vsn = app_vsn(AppSrcFile), + Vsn = app_vsn(AppSrcFile, State), A2 = lists:keystore(vsn, 1, A1, {vsn, Vsn}), %% systools:make_relup/4 fails with {missing_param, registered} @@ -120,10 +120,6 @@ preprocess(State, AppInfo, AppSrcFile) -> AppFile = rebar_app_utils:app_src_to_app(OutDir, AppSrcFile), ok = rebar_file_utils:write_file_if_contents_differ(AppFile, Spec), - %% Make certain that the ebin/ directory is available - %% on the code path - true = code:add_path(filename:absname(filename:dirname(AppFile))), - AppFile; {error, Reason} -> ?PRV_ERROR({file_read, AppSrcFile, Reason}) @@ -132,7 +128,6 @@ preprocess(State, AppInfo, AppSrcFile) -> load_app_vars(State) -> case rebar_state:get(State, app_vars_file, undefined) of undefined -> - ?DEBUG("No app_vars_file defined.", []), []; Filename -> ?INFO("Loading app vars from ~p", [Filename]), @@ -202,11 +197,12 @@ consult_app_file(Filename) -> end end. -app_vsn(AppFile) -> +app_vsn(AppFile, State) -> case consult_app_file(AppFile) of {ok, [{application, _AppName, AppData}]} -> AppDir = filename:dirname(filename:dirname(AppFile)), - rebar_utils:vcs_vsn(get_value(vsn, AppData, AppFile), AppDir); + Resources = rebar_state:resources(State), + rebar_utils:vcs_vsn(get_value(vsn, AppData, AppFile), AppDir, Resources); {error, Reason} -> ?ABORT("Failed to consult app file ~s: ~p\n", [AppFile, Reason]) end. diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl index 8982573..fb2d094 100644 --- a/src/rebar_packages.erl +++ b/src/rebar_packages.erl @@ -73,14 +73,14 @@ find_highest_matching(Dep, Constraint, T) -> [{Dep, [[Vsn]]}] -> case ec_semver:pes(Vsn, Constraint) of true -> - Vsn; + {ok, Vsn}; false -> ?WARN("Only existing version of ~s is ~s which does not match constraint ~~> ~s. " "Using anyway, but it is not guarenteed to work.", [Dep, Vsn, Constraint]), - Vsn + {ok, Vsn} end; [{Dep, [[HeadVsn | VsnTail]]}] -> - lists:foldl(fun(Version, Highest) -> + {ok, lists:foldl(fun(Version, Highest) -> case ec_semver:pes(Version, Constraint) andalso ec_semver:gt(Version, Highest) of true -> @@ -88,5 +88,8 @@ find_highest_matching(Dep, Constraint, T) -> false -> Highest end - end, HeadVsn, VsnTail) + end, HeadVsn, VsnTail)}; + [] -> + ?WARN("Missing registry entry for package ~s", [Dep]), + none end. diff --git a/src/rebar_plugins.erl b/src/rebar_plugins.erl index 02e8f4e..c16223e 100644 --- a/src/rebar_plugins.erl +++ b/src/rebar_plugins.erl @@ -42,6 +42,7 @@ handle_plugin(Plugin, State) -> Apps = rebar_state:all_deps(State2), ToBuild = lists:dropwhile(fun rebar_app_info:valid/1, Apps), [build_plugin(AppInfo) || AppInfo <- ToBuild], + [true = code:add_patha(filename:join(rebar_app_info:dir(AppInfo), "ebin")) || AppInfo <- Apps], plugin_providers(Plugin) catch C:T -> @@ -54,8 +55,7 @@ build_plugin(AppInfo) -> AppDir = rebar_app_info:dir(AppInfo), C = rebar_config:consult(AppDir), S = rebar_state:new(rebar_state:new(), C, AppDir), - rebar_prv_compile:compile(S, AppInfo), - true = code:add_patha(filename:join(AppDir, "ebin")). + rebar_prv_compile:compile(S, AppInfo). plugin_providers({Plugin, _, _}) when is_atom(Plugin) -> validate_plugin(Plugin); diff --git a/src/rebar_prv_app_discovery.erl b/src/rebar_prv_app_discovery.erl index 31c0f59..97862c1 100644 --- a/src/rebar_prv_app_discovery.erl +++ b/src/rebar_prv_app_discovery.erl @@ -45,5 +45,13 @@ do(State) -> -spec format_error(any()) -> iolist(). format_error({multiple_app_files, Files}) -> io_lib:format("Multiple app files found in one app dir: ~s", [string:join(Files, " and ")]); +format_error({invalid_app_file, File, Reason}) -> + case Reason of + {Line, erl_parse, Description} -> + io_lib:format("Invalid app file ~s at line ~b: ~p", + [File, Line, lists:flatten(Description)]); + _ -> + io_lib:format("Invalid app file ~s: ~p", [File, Reason]) + end; format_error(Reason) -> io_lib:format("~p", [Reason]). diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 893f373..d4370de 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -38,6 +38,8 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ?INFO("Running Common Test suites...", []), + code:add_pathsa(rebar_state:code_paths(State, all_deps)), + %% Run ct provider prehooks Providers = rebar_state:providers(State), Cwd = rebar_dir:get_cwd(), @@ -47,11 +49,14 @@ do(State) -> {ok, State1} = Result -> %% Run ct provider posthooks rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State1), + rebar_utils:cleanup_code_path(rebar_state:code_paths(State1, default)), Result; ?PRV_ERROR(_) = Error -> + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), Error catch throw:{error, Reason} -> + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), ?PRV_ERROR(Reason) end. @@ -242,7 +247,7 @@ copy_and_compile_test_suites(State, Opts) -> Dirs = find_suite_dirs(AllSuites), lists:foreach(fun(S) -> NewPath = copy(State, S), - compile_dir(State, NewPath) + compile_dir(State, S, NewPath) end, Dirs), NewSuites = lists:map(fun(S) -> retarget_path(State, S) end, AllSuites), [{suite, NewSuites}|lists:keydelete(suite, 1, Opts)] @@ -254,12 +259,12 @@ copy_and_compile_test_dirs(State, Opts) -> %% dir is a single directory Dir when is_list(Dir), is_integer(hd(Dir)) -> NewPath = copy(State, Dir), - [{dir, compile_dir(State, NewPath)}|lists:keydelete(dir, 1, Opts)]; + [{dir, compile_dir(State, Dir, NewPath)}|lists:keydelete(dir, 1, Opts)]; %% dir is a list of directories Dirs when is_list(Dirs) -> NewDirs = lists:map(fun(Dir) -> NewPath = copy(State, Dir), - compile_dir(State, NewPath) + compile_dir(State, Dir, NewPath) end, Dirs), [{dir, NewDirs}|lists:keydelete(dir, 1, Opts)] end. @@ -296,11 +301,11 @@ copy(State, Dir) -> Target end. -compile_dir(State, Dir) -> +compile_dir(State, Dir, OutDir) -> NewState = replace_src_dirs(State, [Dir]), - ok = rebar_erlc_compiler:compile(NewState, rebar_dir:base_dir(State), Dir), + ok = rebar_erlc_compiler:compile(NewState, rebar_dir:base_dir(State), OutDir), ok = maybe_cover_compile(State, Dir), - Dir. + OutDir. retarget_path(State, Path) -> ProjectApps = rebar_state:project_apps(State), diff --git a/src/rebar_prv_compile.erl b/src/rebar_prv_compile.erl index 11a01ed..1fb29bd 100644 --- a/src/rebar_prv_compile.erl +++ b/src/rebar_prv_compile.erl @@ -31,27 +31,35 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> + DepsPaths = rebar_state:code_paths(State, all_deps), + code:add_pathsa(DepsPaths), + ProjectApps = rebar_state:project_apps(State), Providers = rebar_state:providers(State), Deps = rebar_state:deps_to_build(State), Cwd = rebar_dir:get_cwd(), - rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State), - %% Need to allow global config vars used on deps %% Right now no way to differeniate and just give deps a new state EmptyState = rebar_state:new(), build_apps(EmptyState, Providers, Deps), - %% Use the project State for building project apps - %% Set hooks to empty so top-level hooks aren't run for each project app - State2 = rebar_state:set(rebar_state:set(State, post_hooks, []), pre_hooks, []), {ok, ProjectApps1} = rebar_digraph:compile_order(ProjectApps), - ProjectApps2 = build_apps(State2, Providers, ProjectApps1), - rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State), + %% Run top level hooks *before* project apps compiled but *after* deps are + rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State), + + ProjectApps2 = build_apps(State, Providers, ProjectApps1), + State2 = rebar_state:project_apps(State, ProjectApps2), - {ok, rebar_state:project_apps(State, ProjectApps2)}. + ProjAppsPaths = [filename:join(rebar_app_info:out_dir(X), "ebin") || X <- ProjectApps2], + State3 = rebar_state:code_paths(State2, all_deps, DepsPaths ++ ProjAppsPaths), + + rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State2), + + rebar_utils:cleanup_code_path(rebar_state:code_paths(State3, default)), + + {ok, State3}. -spec format_error(any()) -> iolist(). format_error(Reason) -> @@ -75,12 +83,10 @@ build_app(State, Providers, AppInfo) -> end, %% Legacy hook support - rebar_hooks:run_all_hooks(AppDir, pre, ?PROVIDER, Providers, S), AppInfo1 = compile(S, AppInfo), rebar_hooks:run_all_hooks(AppDir, post, ?PROVIDER, Providers, S), - true = code:add_patha(rebar_app_info:ebin_dir(AppInfo1)), AppInfo1. compile(State, AppInfo) -> diff --git a/src/rebar_prv_cover.erl b/src/rebar_prv_cover.erl index 83fe012..b38c6b0 100644 --- a/src/rebar_prv_cover.erl +++ b/src/rebar_prv_cover.erl @@ -150,7 +150,7 @@ analysis(State, Task) -> {Mod, process(Answer), File} end, Mods), - true = code:set_path(OldPath), + true = rebar_utils:cleanup_code_path(OldPath), Analysis. restore_cover_paths(State) -> diff --git a/src/rebar_prv_deps.erl b/src/rebar_prv_deps.erl index 3627e91..be81c31 100644 --- a/src/rebar_prv_deps.erl +++ b/src/rebar_prv_deps.erl @@ -13,14 +13,18 @@ -spec init(rebar_state:t()) -> {ok, rebar_state:t()}. init(State) -> - State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, - {module, ?MODULE}, - {bare, true}, - {deps, ?DEPS}, - {example, "rebar3 deps"}, - {short_desc, "List dependencies"}, - {desc, info("List dependencies")}, - {opts, []}])), + State1 = rebar_state:add_provider( + State, + providers:create([ + {name, ?PROVIDER}, + {module, ?MODULE}, + {bare, true}, + {deps, ?DEPS}, + {example, "rebar3 deps"}, + {short_desc, "List dependencies"}, + {desc, "List dependencies. Those not matching lock files " + "are followed by an asterisk (*)."}, + {opts, []}])), {ok, State1}. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. @@ -44,35 +48,55 @@ display(State, Profile, Deps) -> display_deps(State, Deps), ?CONSOLE("", []). -merge(Deps, Deps) -> - Deps; merge(Deps, SourceDeps) -> + merge1(dedup([normalize(Dep) || Dep <- Deps]), + [normalize(Dep) || Dep <- SourceDeps]). + +normalize(Name) when is_binary(Name) -> + Name; +normalize(Name) when is_atom(Name) -> + ec_cnv:to_binary(Name); +normalize(Dep) when is_tuple(Dep) -> + Name = element(1, Dep), + setelement(1, Dep, normalize(Name)). + +merge1(Deps, SourceDeps) -> + Names = [name(Dep) || Dep <- Deps], ToAdd = [Dep || Dep <- SourceDeps, - not lists:keymember(ec_cnv:to_binary(element(1,Dep)), 1, Deps)], + not lists:member(name(Dep), Names)], Deps ++ ToAdd. +%% Keep the latter one as locks come after regular deps in the list. +%% This is totally not safe as an assumption, but it's what we got. +%% We do this by comparing the current element and looking if a +%% similar named one happens later. If so, drop the current one. +dedup(Deps) -> dedup(Deps, [name(Dep) || Dep <- Deps]). + +dedup([], []) -> []; +dedup([Dep|Deps], [Name|DepNames]) -> + case lists:member(Name, DepNames) of + true -> dedup(Deps, DepNames); + false -> [Dep | dedup(Deps, DepNames)] + end. + +name(T) when is_tuple(T) -> element(1, T); +name(B) when is_binary(B) -> B. + + display_deps(State, Deps) -> lists:foreach(fun(Dep) -> display_dep(State, Dep) end, Deps). %% packages display_dep(_State, {Name, Vsn}) when is_list(Vsn) -> ?CONSOLE("~s* (package ~s)", [ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)]); -display_dep(_State, Name) when is_atom(Name) -> - ?CONSOLE("~s* (package)", [ec_cnv:to_binary(Name)]); -%% git source -display_dep(_State, {Name, Source}) when is_tuple(Source), element(1, Source) =:= git -> - ?CONSOLE("~s* (git source)", [ec_cnv:to_binary(Name)]); -display_dep(_State, {Name, _Vsn, Source}) when is_tuple(Source), element(1, Source) =:= git -> - ?CONSOLE("~s* (git source)", [ec_cnv:to_binary(Name)]); -display_dep(_State, {Name, _Vsn, Source, _Opts}) when is_tuple(Source), element(1, Source) =:= git -> - ?CONSOLE("~s* (git soutce)", [ec_cnv:to_binary(Name)]); -%% unknown source +display_dep(_State, Name) when is_binary(Name) -> + ?CONSOLE("~s* (package)", [Name]); display_dep(_State, {Name, Source}) when is_tuple(Source) -> - ?CONSOLE("~s* (source ~p)", [ec_cnv:to_binary(Name), Source]); + ?CONSOLE("~s* (~s source)", [ec_cnv:to_binary(Name), type(Source)]); display_dep(_State, {Name, _Vsn, Source}) when is_tuple(Source) -> - ?CONSOLE("~s* (source ~p)", [ec_cnv:to_binary(Name), Source]); + ?CONSOLE("~s* (~s source)", [ec_cnv:to_binary(Name), type(Source)]); display_dep(_State, {Name, _Vsn, Source, _Opts}) when is_tuple(Source) -> - ?CONSOLE("~s* (source ~p)", [ec_cnv:to_binary(Name), Source]); + ?CONSOLE("~s* (~s source)", [ec_cnv:to_binary(Name), type(Source)]); %% Locked display_dep(State, {Name, Source={pkg, _, Vsn}, Level}) when is_integer(Level) -> DepsDir = rebar_dir:deps_dir(State), @@ -82,14 +106,6 @@ display_dep(State, {Name, Source={pkg, _, Vsn}, Level}) when is_integer(Level) - false -> "" end, ?CONSOLE("~s~s (locked package ~s)", [Name, NeedsUpdate, Vsn]); -display_dep(State, {Name, Source, Level}) when is_tuple(Source), is_integer(Level), element(1, Source) =:= git -> - DepsDir = rebar_dir:deps_dir(State), - AppDir = filename:join([DepsDir, ec_cnv:to_binary(Name)]), - NeedsUpdate = case rebar_fetch:needs_update(AppDir, Source, State) of - true -> "*"; - false -> "" - end, - ?CONSOLE("~s~s (locked git source)", [Name, NeedsUpdate]); display_dep(State, {Name, Source, Level}) when is_tuple(Source), is_integer(Level) -> DepsDir = rebar_dir:deps_dir(State), AppDir = filename:join([DepsDir, ec_cnv:to_binary(Name)]), @@ -97,39 +113,6 @@ display_dep(State, {Name, Source, Level}) when is_tuple(Source), is_integer(Leve true -> "*"; false -> "" end, - ?CONSOLE("~s~s (locked ~p)", [Name, NeedsUpdate, Source]). - -info(Description) -> - 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"}}]} - ]). + ?CONSOLE("~s~s (locked ~s source)", [Name, NeedsUpdate, type(Source)]). + +type(Source) when is_tuple(Source) -> element(1, Source). diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index ef6a64c..67cf4b9 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -64,6 +64,7 @@ short_desc() -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ?INFO("Dialyzer starting, this may take a while...", []), + code:add_pathsa(rebar_state:code_paths(State, all_deps)), Plt = get_plt_location(State), Apps = rebar_state:project_apps(State), @@ -74,6 +75,8 @@ do(State) -> ?PRV_ERROR({error_processing_apps, Error}); throw:{dialyzer_warnings, Warnings} -> ?PRV_ERROR({dialyzer_warnings, Warnings}) + after + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)) end. -spec format_error(any()) -> iolist(). diff --git a/src/rebar_prv_erlydtl_compiler.erl b/src/rebar_prv_erlydtl_compiler.erl deleted file mode 100644 index 65f0094..0000000 --- a/src/rebar_prv_erlydtl_compiler.erl +++ /dev/null @@ -1,260 +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), -%% Bryan Fink (bryan@basho.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. -%% ------------------------------------------------------------------- - -%% The rebar_erlydtl_compiler module is a plugin for rebar that compiles -%% ErlyDTL templates. By default, it compiles all templates/*.dtl -%% to ebin/*_dtl.beam. -%% -%% Configuration options should be placed in rebar.config under -%% 'erlydtl_opts'. It can be a list of name-value tuples or a list of -%% lists of name-value tuples if you have multiple template directories -%% that need to have different settings (see example below). -%% -%% Available options include: -%% -%% doc_root: where to find templates to compile -%% "templates" by default -%% -%% out_dir: where to put compiled template beam files -%% "ebin" by default -%% -%% source_ext: the file extension the template sources have -%% ".dtl" by default -%% -%% module_ext: characters to append to the template's module name -%% "_dtl" by default -%% -%% recursive: boolean that determines if doc_root(s) need to be -%% scanned recursively for matching template file names -%% (default: true). -%% For example, if you had: -%% /t_src/ -%% base.html -%% foo.html -%% -%% And you wanted them compiled to: -%% /priv/ -%% base.beam -%% foo.beam -%% -%% You would add to your rebar.config: -%% {erlydtl_opts, [ -%% {doc_root, "t_src"}, -%% {out_dir, "priv"}, -%% {source_ext, ".html"}, -%% {module_ext, ""} -%% ]}. -%% -%% The default settings are the equivalent of: -%% {erlydtl_opts, [ -%% {doc_root, "templates"}, -%% {out_dir, "ebin"}, -%% {source_ext, ".dtl"}, -%% {module_ext, "_dtl"} -%% ]}. -%% -%% The following example will compile the following templates: -%% "src/*.dtl" files into "ebin/*_dtl.beam" and -%% "templates/*.html" into "ebin/*.beam". Note that any tuple option -%% (such as 'out_dir') in the outer list is added to each inner list: -%% {erlydtl_opts, [ -%% {out_dir, "ebin"}, -%% {recursive, false}, -%% [ -%% {doc_root, "src"}, {module_ext, "_dtl"} -%% ], -%% [ -%% {doc_root, "templates"}, {module_ext, ""}, {source_ext, ".html"} -%% ] -%% ]}. --module(rebar_prv_erlydtl_compiler). - --behaviour(provider). - --export([init/1, - do/1, - format_error/1]). - --include("rebar.hrl"). --include_lib("providers/include/providers.hrl"). - --define(PROVIDER, compile). --define(DEPS, [{default, compile}]). - -%% =================================================================== -%% Public API -%% =================================================================== - --spec init(rebar_state:t()) -> {ok, rebar_state:t()}. -init(State) -> - State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, - {module, ?MODULE}, - {namespace, erlydtl}, - {bare, false}, - {deps, ?DEPS}, - {example, "rebar3 erlydtl compile"}, - {short_desc, "Compile erlydtl templates."}, - {desc, "Compile erlydtl templates."}, - {opts, []}])), - {ok, State1}. - -do(State) -> - ?INFO("Running erlydtl...", []), - DtlOpts = proplists:unfold(rebar_state:get(State, erlydtl_opts, [])), - - %% We need a project app to store the results under in _build - %% If there is more than 1 project app, check for an app config - %% if that doesn't exist, error out. - App1 = case rebar_state:project_apps(State) of - [App] -> - App; - Apps -> - case option(app, DtlOpts) of - undefined -> - ?PRV_ERROR(no_main_app); - Name -> - rebar_app_utils:find(Name, Apps) - end - end, - - Dir = rebar_app_info:dir(App1), - OutDir = rebar_app_info:ebin_dir(App1), - rebar_base_compiler:run(State, - [], - filename:join(Dir, option(doc_root, DtlOpts)), - option(source_ext, DtlOpts), - OutDir, - option(module_ext, DtlOpts) ++ ".beam", - fun(S, T, C) -> - compile_dtl(C, S, T, DtlOpts, Dir, OutDir) - end, - [{check_last_mod, false}, - {recursive, option(recursive, DtlOpts)}]), - - {ok, State}. - --spec format_error(any()) -> iolist(). -format_error(no_main_app) -> - "Erlydtl Error: Multiple project apps found and no {app, atom()} option found in erlydtl_opts."; -format_error(Reason) -> - io_lib:format("~p", [Reason]). - -%% =================================================================== -%% Internal functions -%% =================================================================== - -option(Opt, DtlOpts) -> - proplists:get_value(Opt, DtlOpts, default(Opt)). - -default(app) -> undefined; -default(doc_root) -> "priv/templates"; -default(source_ext) -> ".dtl"; -default(module_ext) -> "_dtl"; -default(custom_tags_dir) -> ""; -default(compiler_options) -> [return]; -default(recursive) -> true. - -compile_dtl(State, Source, Target, DtlOpts, Dir, OutDir) -> - case needs_compile(Source, Target, DtlOpts) of - true -> - do_compile(State, Source, Target, DtlOpts, Dir, OutDir); - false -> - skipped - end. - -do_compile(State, Source, Target, DtlOpts, Dir, OutDir) -> - CompilerOptions = option(compiler_options, DtlOpts), - - Sorted = proplists:unfold( - lists:sort( - [{out_dir, OutDir}, - {doc_root, filename:join(Dir, option(doc_root, DtlOpts))}, - {custom_tags_dir, option(custom_tags_dir, DtlOpts)}, - {compiler_options, CompilerOptions}])), - - %% ensure that doc_root and out_dir are defined, - %% using defaults if necessary - Opts = lists:ukeymerge(1, DtlOpts, Sorted), - ?DEBUG("Compiling \"~s\" -> \"~s\" with options:~n ~s", - [Source, Target, io_lib:format("~p", [Opts])]), - case erlydtl:compile_file(ec_cnv:to_list(Source), - list_to_atom(module_name(Target)), - Opts) of - {ok, _Mod} -> - ok; - {ok, _Mod, Ws} -> - rebar_base_compiler:ok_tuple(State, Source, Ws); - error -> - rebar_base_compiler:error_tuple(State, Source, [], [], Opts); - {error, Es, Ws} -> - rebar_base_compiler:error_tuple(State, Source, Es, Ws, Opts) - end. - -module_name(Target) -> - filename:rootname(filename:basename(Target), ".beam"). - -needs_compile(Source, Target, DtlOpts) -> - LM = filelib:last_modified(Target), - LM < filelib:last_modified(Source) orelse - lists:any(fun(D) -> LM < filelib:last_modified(D) end, - referenced_dtls(Source, DtlOpts)). - -referenced_dtls(Source, DtlOpts) -> - DtlOpts1 = lists:keyreplace(doc_root, 1, DtlOpts, - {doc_root, filename:dirname(Source)}), - Set = referenced_dtls1([Source], DtlOpts1, - sets:add_element(Source, sets:new())), - sets:to_list(sets:del_element(Source, Set)). - -referenced_dtls1(Step, DtlOpts, Seen) -> - ExtMatch = re:replace(option(source_ext, DtlOpts), "\.", "\\\\\\\\.", - [{return, list}]), - - ShOpts = [{use_stdout, false}, return_on_error], - AllRefs = - lists:append( - [begin - Cmd = lists:flatten(["grep -o [^\\\"]*\\", - ExtMatch, "[^\\\"]* ", F]), - case rebar_utils:sh(Cmd, ShOpts) of - {ok, Res} -> - string:tokens(Res, "\n"); - {error, _} -> - "" - end - end || F <- Step]), - DocRoot = option(doc_root, DtlOpts), - WithPaths = [ filename:join([DocRoot, F]) || F <- AllRefs ], - ?DEBUG("All deps: ~p\n", [WithPaths]), - Existing = [F || F <- WithPaths, filelib:is_regular(F)], - New = sets:subtract(sets:from_list(Existing), Seen), - case sets:size(New) of - 0 -> Seen; - _ -> referenced_dtls1(sets:to_list(New), DtlOpts, - sets:union(New, Seen)) - end. diff --git a/src/rebar_prv_escriptize.erl b/src/rebar_prv_escriptize.erl index d17699e..03332a0 100644 --- a/src/rebar_prv_escriptize.erl +++ b/src/rebar_prv_escriptize.erl @@ -62,22 +62,22 @@ desc() -> do(State) -> ?INFO("Building escript...", []), - escriptize(State). - -escriptize(State0) -> - App1 = case rebar_state:project_apps(State0) of - [App] -> - App; - Apps -> - case rebar_state:get(State0, escript_main_app, undefined) of - undefined -> - ?PRV_ERROR(no_main_app); - Name -> - rebar_app_utils:find(Name, Apps) - end - end, - - AppName = rebar_app_info:name(App1), + case rebar_state:get(State, escript_main_app, undefined) of + undefined -> + case rebar_state:project_apps(State) of + [App] -> + escriptize(State, App); + _ -> + ?PRV_ERROR(no_main_app) + end; + Name -> + AllApps = rebar_state:all_deps(State)++rebar_state:project_apps(State), + AppInfo = rebar_app_utils:find(Name, AllApps), + escriptize(State, AppInfo) + end. + +escriptize(State0, App) -> + AppName = rebar_app_info:name(App), AppNameStr = ec_cnv:to_list(AppName), %% Get the output filename for the escript -- this may include dirs @@ -90,8 +90,9 @@ escriptize(State0) -> %% in the output file. We then use the .app files for each of these %% to pull in all the .beam files. InclApps = lists:usort(rebar_state:get(State, escript_incl_apps, []) - ++ all_deps(State)), - InclBeams = get_app_beams(InclApps), + ++ all_deps(State)), + AllApps = rebar_state:all_deps(State)++rebar_state:project_apps(State), + InclBeams = get_apps_beams(InclApps, AllApps), %% Look for a list of extra files to include in the output file. %% For internal rebar-private use only. Do not use outside rebar. @@ -140,21 +141,32 @@ format_error(no_main_app) -> %% Internal functions %% =================================================================== -get_app_beams(Apps) -> - get_app_beams(Apps, []). +get_apps_beams(Apps, AllApps) -> + get_apps_beams(Apps, AllApps, []). -get_app_beams([], Acc) -> +get_apps_beams([], _, Acc) -> Acc; -get_app_beams([App | Rest], Acc) -> - case code:lib_dir(App, ebin) of - {error, bad_name} -> - throw(?PRV_ERROR({bad_name, App})); - Path -> - Prefix = filename:join(atom_to_list(App), "ebin"), - Acc2 = load_files(Prefix, "*.beam", Path), - get_app_beams(Rest, Acc2 ++ Acc) +get_apps_beams([App | Rest], AllApps, Acc) -> + case rebar_app_utils:find(ec_cnv:to_binary(App), AllApps) of + {ok, App1} -> + OutDir = filename:absname(rebar_app_info:ebin_dir(App1)), + Beams = get_app_beams(App, OutDir), + get_apps_beams(Rest, AllApps, Beams ++ Acc); + _-> + case code:lib_dir(App, ebin) of + {error, bad_name} -> + throw(?PRV_ERROR({bad_name, App})); + Path -> + Beams = get_app_beams(App, Path), + get_apps_beams(Rest, AllApps, Beams ++ Acc) + end end. +get_app_beams(App, Path) -> + Prefix = filename:join(atom_to_list(App), "ebin"), + load_files(Prefix, "*.beam", Path) ++ + load_files(Prefix, "*.app", Path). + get_extra(State) -> Extra = rebar_state:get(State, escript_incl_extra, []), lists:foldl(fun({Wildcard, Dir}, Files) -> diff --git a/src/rebar_prv_eunit.erl b/src/rebar_prv_eunit.erl index 0ea3ae8..12dd5f9 100644 --- a/src/rebar_prv_eunit.erl +++ b/src/rebar_prv_eunit.erl @@ -37,6 +37,7 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ?INFO("Performing EUnit tests...", []), + code:add_pathsa(rebar_state:code_paths(State, all_deps)), %% Run eunit provider prehooks Providers = rebar_state:providers(State), Cwd = rebar_dir:get_cwd(), @@ -48,11 +49,14 @@ do(State) -> {ok, State1} -> %% Run eunit provider posthooks rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State1), + rebar_utils:cleanup_code_path(rebar_state:code_paths(State1, default)), {ok, State1}; Error -> + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), Error end; Error -> + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), Error end. diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl index c535b4d..5aa34c3 100644 --- a/src/rebar_prv_install_deps.erl +++ b/src/rebar_prv_install_deps.erl @@ -82,13 +82,9 @@ do(State) -> ?PRV_ERROR({cycles, Cycles}); {error, Error} -> {error, Error}; - no_cycle -> - case compile_order(Source, ProjectApps) of - {ok, ToCompile} -> - {ok, rebar_state:deps_to_build(State1, ToCompile)}; - {error, Error} -> - {error, Error} - end + {no_cycle, Sorted} -> + ToCompile = cull_compile(Sorted, ProjectApps), + {ok, rebar_state:deps_to_build(State1, ToCompile)} end catch %% maybe_fetch will maybe_throw an exception to break out of some loops @@ -132,30 +128,39 @@ handle_deps(Profile, State, Deps, Locks) when is_list(Locks) -> handle_deps(_Profile, State, [], _, _) -> {ok, [], State}; handle_deps(Profile, State0, Deps, Upgrade, Locks) -> - %% Read in package index and dep graph - {Packages, Graph} = rebar_state:packages(State0), - Registry = rebar_packages:registry(State0), - State = rebar_state:packages(rebar_state:registry(State0, Registry), {Packages, Graph}), %% Split source deps from pkg deps, needed to keep backwards compatibility - DepsDir = rebar_dir:deps_dir(State), - {SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, State, Locks, 0), + DepsDir = profile_dep_dir(State0, Profile), + + {SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, State0, Locks, 0), %% Fetch transitive src deps - {State1, SrcApps, PkgDeps1, Seen} = - update_src_deps(Profile, 0, SrcDeps, PkgDeps, [], State, Upgrade, sets:new(), Locks), + {State1, SrcApps, PkgDeps1, Seen} = update_src_deps(Profile, 0, SrcDeps, PkgDeps, [] + ,State0, Upgrade, sets:new(), Locks), - {Solved, State2} = - update_pkg_deps(Profile, Packages, PkgDeps1, Graph, Upgrade, Seen, State1), + {Solved, State4} = + case PkgDeps1 of + [] -> + {[], State1}; + _ -> + %% Read in package index and dep graph + {Packages, Graph} = rebar_state:packages(State1), + Registry = rebar_packages:registry(State1), + State2 = rebar_state:packages(rebar_state:registry(State1, Registry) + ,{Packages, Graph}), + + update_pkg_deps(Profile, Packages, PkgDeps1 + ,Graph, Upgrade, Seen, State2) + end, AllDeps = lists:ukeymerge(2 ,lists:ukeysort(2, SrcApps) ,lists:ukeysort(2, Solved)), - %% Sort all apps to build order - State3 = rebar_state:all_deps(State2, AllDeps), + + State5 = rebar_state:update_all_deps(State4, AllDeps), CodePaths = [rebar_app_info:ebin_dir(A) || A <- AllDeps], - ok = code:add_pathsa(CodePaths), + State6 = rebar_state:update_code_paths(State5, all_deps, CodePaths), - {ok, AllDeps, State3}. + {ok, AllDeps, State6}. %% =================================================================== %% Internal functions @@ -171,17 +176,11 @@ find_cycles(Apps) -> case rebar_digraph:compile_order(Apps) of {error, {cycles, Cycles}} -> {cycles, Cycles}; {error, Error} -> {error, Error}; - {ok, _} -> no_cycle + {ok, Sorted} -> {no_cycle, Sorted} end. -compile_order(Source, ProjectApps) -> - case rebar_digraph:compile_order(Source) of - {ok, Sort} -> - %% Valid apps are compiled and good - {ok, lists:dropwhile(fun not_needs_compile/1, Sort -- ProjectApps)}; - {error, Error} -> - {error, Error} - end. +cull_compile(TopSortedDeps, ProjectApps) -> + lists:dropwhile(fun not_needs_compile/1, TopSortedDeps -- ProjectApps). update_pkg_deps(Profile, Packages, PkgDeps, Graph, Upgrade, Seen, State) -> case PkgDeps of @@ -203,7 +202,7 @@ update_pkg_deps(Profile, Packages, PkgDeps, Graph, Upgrade, Seen, State) -> update_pkg_deps(Profile, Pkgs, Packages, Upgrade, Seen, State) -> %% Create app_info record for each pkg dep - DepsDir = rebar_dir:deps_dir(State), + DepsDir = profile_dep_dir(State, Profile), {Solved, _, State1} = lists:foldl(fun(Pkg, {Acc, SeenAcc, StateAcc}) -> handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Acc, SeenAcc, StateAcc) @@ -214,7 +213,7 @@ handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Fetched, Seen, State) - AppInfo = package_to_app(DepsDir, Packages, Pkg), Level = rebar_app_info:dep_level(AppInfo), {NewSeen, NewState} = maybe_lock(Profile, AppInfo, Seen, State, Level), - {_, AppInfo1} = maybe_fetch(AppInfo, Upgrade, Seen, NewState), + {_, AppInfo1} = maybe_fetch(AppInfo, Profile, Upgrade, Seen, NewState), {[AppInfo1 | Fetched], NewSeen, NewState}. maybe_lock(Profile, AppInfo, Seen, State, Level) -> @@ -252,7 +251,7 @@ package_to_app(DepsDir, Packages, {Name, Vsn, Level}) -> || {PkgName,PkgVsn} <- proplists:get_value(<<"deps">>, P, [])], {ok, AppInfo} = rebar_app_info:new(Name, Vsn), AppInfo1 = rebar_app_info:deps(AppInfo, PkgDeps), - AppInfo2 = rebar_app_info:dir(AppInfo1, rebar_dir:deps_dir(DepsDir, Name)), + AppInfo2 = rebar_app_info:dir(AppInfo1, filename:join([DepsDir, Name])), AppInfo3 = rebar_app_info:dep_level(AppInfo2, Level), rebar_app_info:source(AppInfo3, {pkg, Name, Vsn}) end. @@ -278,7 +277,7 @@ update_src_dep(AppInfo, Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrad Name = rebar_app_info:name(AppInfo), case sets:is_element(Name, Seen) of true -> - update_seen_src_dep(AppInfo, Level, + update_seen_src_dep(AppInfo, Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrade, Seen, BaseLocks, Locks); false -> @@ -288,7 +287,13 @@ update_src_dep(AppInfo, Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrad end. -update_seen_src_dep(AppInfo, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrade, Seen, BaseLocks, Locks) -> +profile_dep_dir(State, Profile) -> + case Profile of + default -> filename:join([rebar_dir:profile_dir(State, [default]), rebar_state:get(State, deps_dir, ?DEFAULT_DEPS_DIR)]); + _ -> rebar_dir:deps_dir(State) + end. + +update_seen_src_dep(AppInfo, Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrade, Seen, BaseLocks, Locks) -> Name = rebar_app_info:name(AppInfo), %% If seen from lock file don't print warning about skipping case lists:keymember(Name, 1, BaseLocks) of @@ -303,7 +308,7 @@ update_seen_src_dep(AppInfo, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrade, S {SrcDeps, PkgDeps, SrcApps, State, Seen, Locks}; true -> {NewSrcDeps, NewPkgDeps, NewSrcApps, NewState, NewLocks} - = handle_dep(AppInfo, SrcDeps, PkgDeps, SrcApps, + = handle_dep(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Locks), {NewSrcDeps, NewPkgDeps, NewSrcApps, NewState, Seen, NewLocks} end. @@ -313,22 +318,22 @@ update_unseen_src_dep(AppInfo, Profile, Level, SrcDeps, PkgDeps, SrcApps, State, {NewSrcDeps, NewPkgDeps, NewSrcApps, State2, NewLocks} = case Upgrade of true -> - handle_upgrade(AppInfo, SrcDeps, PkgDeps, SrcApps, + handle_upgrade(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, Level, State1, Locks); _ -> - {_, AppInfo1} = maybe_fetch(AppInfo, false, Seen, State1), - handle_dep(AppInfo1, SrcDeps, PkgDeps, SrcApps, + {_, AppInfo1} = maybe_fetch(AppInfo, Profile, false, Seen, State1), + handle_dep(AppInfo1, Profile, SrcDeps, PkgDeps, SrcApps, Level, State1, Locks) end, {NewSrcDeps, NewPkgDeps, NewSrcApps, State2, NewSeen, NewLocks}. -handle_upgrade(AppInfo, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) -> +handle_upgrade(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) -> Name = rebar_app_info:name(AppInfo), case lists:keyfind(Name, 1, Locks) of false -> - case maybe_fetch(AppInfo, true, sets:new(), State) of + case maybe_fetch(AppInfo, Profile, true, sets:new(), State) of {true, AppInfo1} -> - handle_dep(AppInfo1, SrcDeps, PkgDeps, SrcApps, + handle_dep(AppInfo1, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Locks); {false, AppInfo1} -> @@ -338,8 +343,8 @@ handle_upgrade(AppInfo, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) -> {[AppInfo|SrcDeps], PkgDeps, SrcApps, State, Locks} end. -handle_dep(AppInfo, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) -> - DepsDir = rebar_dir:deps_dir(State), +handle_dep(AppInfo, Profile, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) -> + DepsDir = profile_dep_dir(State, Profile), {AppInfo1, NewSrcDeps, NewPkgDeps, NewLocks, State1} = handle_dep(State, DepsDir, AppInfo, Locks, Level), AppInfo2 = rebar_app_info:dep_level(AppInfo1, Level), @@ -374,9 +379,9 @@ handle_dep(State, DepsDir, AppInfo, Locks, Level) -> {SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, S3, Locks, Level), {AppInfo2, SrcDeps, PkgDeps, Locks++NewLocks, State1}. --spec maybe_fetch(rebar_app_info:t(), boolean() | {true, binary(), integer()}, +-spec maybe_fetch(rebar_app_info:t(), atom(), boolean() | {true, binary(), integer()}, sets:set(binary()), rebar_state:t()) -> {boolean(), rebar_app_info:t()}. -maybe_fetch(AppInfo, Upgrade, Seen, State) -> +maybe_fetch(AppInfo, Profile, Upgrade, Seen, State) -> AppDir = ec_cnv:to_list(rebar_app_info:dir(AppInfo)), %% Don't fetch dep if it exists in the _checkouts dir case rebar_app_info:is_checkout(AppInfo) of @@ -385,22 +390,27 @@ maybe_fetch(AppInfo, Upgrade, Seen, State) -> false -> case rebar_app_discover:find_app(AppDir, all) of false -> - case in_default(AppInfo, State) of + case already_in_default(AppInfo, State) of false -> - {fetch_app(AppInfo, AppDir, State), AppInfo}; + case fetch_app(AppInfo, AppDir, State) of + true -> + maybe_symlink_default(State, Profile, AppDir, AppInfo), + {true, update_app_info(AppInfo)}; + Other -> + {Other, AppInfo} + end; {true, FoundApp} -> %% Preserve the state we created with overrides AppState = rebar_app_info:state(AppInfo), FoundApp1 = rebar_app_info:state(FoundApp, AppState), - ?INFO("Linking ~s to ~s", [rebar_app_info:dir(FoundApp1), AppDir]), - filelib:ensure_dir(AppDir), - rebar_file_utils:symlink_or_copy(rebar_app_info:dir(FoundApp1), AppDir), + symlink_dep(rebar_app_info:dir(FoundApp1), AppDir), {true, FoundApp1} end; {true, AppInfo1} -> %% Preserve the state we created with overrides AppState = rebar_app_info:state(AppInfo), AppInfo2 = rebar_app_info:state(AppInfo1, AppState), + maybe_symlink_default(State, Profile, AppDir, AppInfo2), case sets:is_element(rebar_app_info:name(AppInfo), Seen) of true -> {false, AppInfo2}; @@ -410,11 +420,42 @@ maybe_fetch(AppInfo, Upgrade, Seen, State) -> end end. -in_default(AppInfo, State) -> +already_in_default(AppInfo, State) -> Name = ec_cnv:to_list(rebar_app_info:name(AppInfo)), DefaultAppDir = filename:join([rebar_state:get(State, base_dir), "default", "lib", Name]), rebar_app_discover:find_app(DefaultAppDir, all). +needs_symlinking(State, Profile) -> + case {rebar_state:current_profiles(State), Profile} of + {[default], default} -> + %% file will be in default already -- this is the only run we have + false; + {_, default} -> + %% file fetched to default, needs to be linked to the current + %% run's directory. + true; + _ -> + %% File fetched to the right directory already + false + end. + +maybe_symlink_default(State, Profile, AppDir, AppInfo) -> + case needs_symlinking(State, Profile) of + true -> + SymDir = filename:join([rebar_dir:deps_dir(State), + rebar_app_info:name(AppInfo)]), + symlink_dep(AppDir, SymDir), + true; + false -> + false + end. + + +symlink_dep(From, To) -> + ?INFO("Linking ~s to ~s", [From, To]), + filelib:ensure_dir(To), + rebar_file_utils:symlink_or_copy(From, To). + -spec parse_deps(binary(), list(), rebar_state:t(), list(), integer()) -> {[rebar_app_info:t()], [pkg_dep()]}. parse_deps(DepsDir, Deps, State, Locks, Level) -> lists:foldl(fun(Dep, Acc) -> @@ -521,6 +562,15 @@ fetch_app(AppInfo, AppDir, State) -> Result end. +update_app_info(AppInfo) -> + AppDetails = rebar_app_info:app_details(AppInfo), + Applications = proplists:get_value(applications, AppDetails, []), + IncludedApplications = proplists:get_value(included_applications, AppDetails, []), + AppInfo1 = rebar_app_info:applications( + rebar_app_info:app_details(AppInfo, AppDetails), + IncludedApplications++Applications), + rebar_app_info:valid(AppInfo1, false). + maybe_upgrade(AppInfo, AppDir, false, State) -> Source = rebar_app_info:source(AppInfo), rebar_fetch:needs_update(AppDir, Source, State); @@ -576,7 +626,7 @@ not_needs_compile(App) -> get_package(Dep, State) -> case rebar_state:registry(State) of {ok, T} -> - HighestDepVsn = rebar_packages:find_highest_matching(Dep, "0", T), + {ok, HighestDepVsn} = rebar_packages:find_highest_matching(Dep, "0", T), {Dep, HighestDepVsn}; error -> throw(?PRV_ERROR({load_registry_fail, Dep})) diff --git a/src/rebar_prv_lock.erl b/src/rebar_prv_lock.erl index 5536ec9..6fdd743 100644 --- a/src/rebar_prv_lock.erl +++ b/src/rebar_prv_lock.erl @@ -23,30 +23,31 @@ init(State) -> {deps, ?DEPS}, {example, ""}, {short_desc, "Locks dependencies."}, - {desc, info("Locks dependencies")}, + {desc, "Locks dependencies"}, {opts, []}])), {ok, State1}. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - AllDeps = rebar_state:lock(State), - Locks = [begin - Dir = rebar_app_info:dir(Dep), - Source = rebar_app_info:source(Dep), - - %% If source is tuple it is a source dep - %% e.g. {git, "git://github.com/ninenines/cowboy.git", "master"} - {rebar_app_info:name(Dep) - ,rebar_fetch:lock_source(Dir, Source, State) - ,rebar_app_info:dep_level(Dep)} - end || Dep <- AllDeps, not(rebar_app_info:is_checkout(Dep))], + Locks = build_locks(State), Dir = rebar_state:dir(State), - file:write_file(filename:join(Dir, ?LOCK_FILE), io_lib:format("~p.~n", [Locks])), + file:write_file(filename:join(Dir, ?LOCK_FILE), + io_lib:format("~p.~n", [Locks])), {ok, State}. -spec format_error(any()) -> iolist(). format_error(Reason) -> io_lib:format("~p", [Reason]). -info(_) -> - "". +build_locks(State) -> + AllDeps = rebar_state:lock(State), + [begin + Dir = rebar_app_info:dir(Dep), + Source = rebar_app_info:source(Dep), + + %% If source is tuple it is a source dep + %% e.g. {git, "git://github.com/ninenines/cowboy.git", "master"} + {rebar_app_info:name(Dep) + ,rebar_fetch:lock_source(Dir, Source, State) + ,rebar_app_info:dep_level(Dep)} + end || Dep <- AllDeps, not(rebar_app_info:is_checkout(Dep))]. diff --git a/src/rebar_prv_release.erl b/src/rebar_prv_release.erl index c9a9c0b..595280c 100644 --- a/src/rebar_prv_release.erl +++ b/src/rebar_prv_release.erl @@ -35,8 +35,9 @@ do(State) -> Caller = rebar_state:get(State, caller, api), Options = rebar_state:command_args(State), DepsDir = rebar_dir:deps_dir(State), + ProjectAppDirs = lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS), LibDirs = rebar_utils:filtermap(fun ec_file:exists/1, - [DepsDir | lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS)]), + [?DEFAULT_CHECKOUTS_DIR, DepsDir | ProjectAppDirs]), OutputDir = filename:join(rebar_dir:base_dir(State), ?DEFAULT_RELEASE_DIR), AllOptions = string:join(["release" | Options], " "), try diff --git a/src/rebar_prv_report.erl b/src/rebar_prv_report.erl index 45bc0b0..e052998 100644 --- a/src/rebar_prv_report.erl +++ b/src/rebar_prv_report.erl @@ -43,7 +43,7 @@ do(State) -> %% Show app versions (including rebar3) {ok, Vsn} = application:get_key(rebar, vsn), {ok, Apps} = application:get_key(rebar, applications), - [application:ensure_started(App) || App <- Apps], + [application:load(App) || App <- Apps], Vsns = [io_lib:format("~p: ~s~n", [App, AVsn]) || App <- lists:sort(Apps), {ok, AVsn} <- [application:get_key(App, vsn)]], @@ -101,4 +101,3 @@ time_to_string({{Y,M,D},{H,Min,S}}) -> parse_task(Str) -> hd(re:split(Str, " ")). - diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl index ed75b30..ec2f692 100644 --- a/src/rebar_prv_shell.erl +++ b/src/rebar_prv_shell.erl @@ -52,7 +52,7 @@ init(State) -> {example, "rebar3 shell"}, {short_desc, "Run shell with project apps and deps in path."}, {desc, info()}, - {opts, []}])), + {opts, [{config, undefined, "config", string, "Path to the config file to use. Defaults to the sys_config defined for relx, if present."}]}])), {ok, State1}. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. @@ -92,8 +92,12 @@ shell(State) -> %% times). removes at most the error_logger added by init and the %% error_logger added by the tty handler ok = remove_error_handler(3), - %% add test paths + %% Add deps to path + code:add_pathsa(rebar_state:code_paths(State, all_deps)), + %% add project app test paths ok = add_test_paths(State), + %% try to read in sys.config file + ok = reread_config(State), %% this call never returns (until user quits shell) timer:sleep(infinity). @@ -121,10 +125,67 @@ wait_until_user_started(Timeout) -> add_test_paths(State) -> lists:foreach(fun(App) -> - AppDir = rebar_app_info:out_dir(App), - %% ignore errors resulting from non-existent directories - _ = code:add_path(filename:join([AppDir, "ebin"])), - _ = code:add_path(filename:join([AppDir, "test"])) - end, rebar_state:project_apps(State)), + AppDir = rebar_app_info:out_dir(App), + %% ignore errors resulting from non-existent directories + _ = code:add_path(filename:join([AppDir, "test"])) + end, rebar_state:project_apps(State)), _ = code:add_path(filename:join([rebar_dir:base_dir(State), "test"])), ok. + +reread_config(State) -> + case find_config(State) of + no_config -> + ok; + {ok, ConfigList} -> + lists:foreach(fun ({Application, Items}) -> + lists:foreach(fun ({Key, Val}) -> + application:set_env(Application, Key, Val) + end, + Items) + end, + ConfigList); + {error, Error} -> + ?ABORT("Error while attempting to read configuration file: ~p", [Error]) + end. + +% First try the --config flag, then try the relx sys_config +-spec find_config(rebar_state:t()) -> {ok, [tuple()]}|no_config|{error, tuple()}. +find_config(State) -> + case find_config_option(State) of + no_config -> + find_config_relx(State); + Result -> + Result + end. + +-spec find_config_option(rebar_state:t()) -> {ok, [tuple()]}|no_config|{error, tuple()}. +find_config_option(State) -> + {Opts, _} = rebar_state:command_parsed_args(State), + case proplists:get_value(config, Opts) of + undefined -> + no_config; + Filename -> + consult_config(State, Filename) + end. + +-spec find_config_relx(rebar_state:t()) -> {ok, [tuple()]}|no_config|{error, tuple()}. +find_config_relx(State) -> + case proplists:get_value(sys_config, rebar_state:get(State, relx, [])) of + undefined -> + no_config; + Filename -> + consult_config(State, Filename) + end. + +-spec consult_config(rebar_state:t(), string()) -> {ok, [tuple()]}|{error, tuple()}. +consult_config(State, Filename) -> + Fullpath = filename:join(rebar_dir:root_dir(State), Filename), + ?DEBUG("Loading configuration from ~p", [Fullpath]), + case file:consult(Fullpath) of + {ok, [Config]} -> + {ok, Config}; + {ok, []} -> + {ok, []}; + {error, Error} -> + {error, {Error, Fullpath}} + end. diff --git a/src/rebar_prv_tar.erl b/src/rebar_prv_tar.erl index 019036e..f7557bd 100644 --- a/src/rebar_prv_tar.erl +++ b/src/rebar_prv_tar.erl @@ -35,8 +35,9 @@ do(State) -> Caller = rebar_state:get(State, caller, api), Options = rebar_state:command_args(State), DepsDir = rebar_dir:deps_dir(State), + ProjectAppDirs = lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS), LibDirs = rebar_utils:filtermap(fun ec_file:exists/1, - [DepsDir | lists:delete(".", ?DEFAULT_PROJECT_APP_DIRS)]), + [?DEFAULT_CHECKOUTS_DIR, DepsDir | ProjectAppDirs]), OutputDir = filename:join(rebar_dir:base_dir(State), ?DEFAULT_RELEASE_DIR), AllOptions = string:join(["release", "tar" | Options], " "), case rebar_state:get(State, relx, []) of diff --git a/src/rebar_prv_unlock.erl b/src/rebar_prv_unlock.erl new file mode 100644 index 0000000..dc9fe11 --- /dev/null +++ b/src/rebar_prv_unlock.erl @@ -0,0 +1,84 @@ +-module(rebar_prv_unlock). + +-behaviour(provider). + +-export([init/1, + do/1, + format_error/1]). + +-include("rebar.hrl"). +-include_lib("providers/include/providers.hrl"). + +-define(PROVIDER, unlock). +-define(DEPS, []). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. +init(State) -> + State1 = rebar_state:add_provider( + State, + providers:create([{name, ?PROVIDER}, + {module, ?MODULE}, + {bare, false}, + {deps, ?DEPS}, + {example, ""}, + {short_desc, "Unlock dependencies."}, + {desc, "Unlock project dependencies. Mentioning no application " + "will unlock all dependencies. To unlock specific dependencies, " + "their name can be listed in the command."}, + {opts, [ + {package, undefined, undefined, string, + "List of packages to upgrade. If not specified, all dependencies are upgraded."} + ]} + ]) + ), + {ok, State1}. + +do(State) -> + Dir = rebar_state:dir(State), + LockFile = filename:join(Dir, ?LOCK_FILE), + case file:consult(LockFile) of + {error, enoent} -> + %% Our work is done. + {ok, State}; + {error, Reason} -> + ?PRV_ERROR({file,Reason}); + {ok, [Locks]} -> + case handle_unlocks(State, Locks, LockFile) of + ok -> + {ok, State}; + {error, Reason} -> + ?PRV_ERROR({file,Reason}) + end; + {ok, _Other} -> + ?PRV_ERROR(unknown_lock_format) + end. + +-spec format_error(any()) -> iolist(). +format_error({file, Reason}) -> + io_lib:format("Lock file editing failed for reason ~p", [Reason]); +format_error(unknown_lock_format) -> + "Lock file format unknown"; +format_error(Reason) -> + io_lib:format("~p", [Reason]). + +handle_unlocks(State, Locks, LockFile) -> + {Args, _} = rebar_state:command_parsed_args(State), + Names = parse_names(ec_cnv:to_binary(proplists:get_value(package, Args, <<"">>))), + case [Lock || Lock = {Name, _, _} <- Locks, not lists:member(Name, Names)] of + [] -> + file:delete(LockFile); + _ when Names =:= [] -> % implicitly all locks + file:delete(LockFile); + NewLocks -> + file:write_file(LockFile, io_lib:format("~p.~n", [NewLocks])) + end. + +parse_names(Bin) -> + case lists:usort(re:split(Bin, <<" *, *">>, [trim])) of + [<<"">>] -> []; % nothing submitted + Other -> Other + end. diff --git a/src/rebar_prv_update.erl b/src/rebar_prv_update.erl index 6cdabeb..942b386 100644 --- a/src/rebar_prv_update.erl +++ b/src/rebar_prv_update.erl @@ -52,7 +52,8 @@ do(State) -> write_registry(Dict, Graph, State), ok catch - _E:_C -> + _E:C -> + ?DEBUG("Error creating package index: ~p ~p", [C, erlang:get_stacktrace()]), throw(?PRV_ERROR(package_index_write)) end, @@ -71,6 +72,10 @@ write_registry(Dict, {digraph, Edges, Vertices, Neighbors, _}, State) -> ets:tab2file(Neighbors, filename:join(RegistryDir, "neighbors")), file:write_file(filename:join(RegistryDir, "dict"), term_to_binary(Dict)). +is_supported(<<"make">>) -> true; +is_supported(<<"rebar">>) -> true; +is_supported(_) -> false. + hex_to_graph(Filename) -> {ok, T} = ets:file2tab(Filename), Graph = digraph:new(), @@ -82,23 +87,34 @@ hex_to_graph(Filename) -> ok end, ok, T), - Dict1 = ets:foldl(fun({{Pkg, PkgVsn}, [Deps | _]}, Dict) -> - DepsList = update_graph(Pkg, PkgVsn, Deps, T, Graph), - dict:store({Pkg, PkgVsn}, DepsList, Dict); + Dict1 = ets:foldl(fun({{Pkg, PkgVsn}, [Deps, _, BuildTools | _]}, Dict) when is_list(BuildTools) -> + case lists:any(fun is_supported/1, BuildTools) of + true -> + DepsList = update_graph(Pkg, PkgVsn, Deps, T, Graph), + dict:store({Pkg, PkgVsn}, DepsList, Dict); + false -> + Dict + end; (_, Dict) -> Dict end, dict:new(), T), {Dict1, Graph}. update_graph(Pkg, PkgVsn, Deps, HexRegistry, Graph) -> - lists:foldl(fun([Dep, DepVsn, _, _], DepsListAcc) -> + lists:foldl(fun([Dep, DepVsn, false, _AppName | _], DepsListAcc) -> case DepVsn of <<"~> ", Vsn/binary>> -> - HighestDepVsn = rebar_packages:find_highest_matching(Dep, Vsn, HexRegistry), - digraph:add_edge(Graph, {Pkg, PkgVsn}, {Dep, HighestDepVsn}), - [{Dep, DepVsn} | DepsListAcc]; + case rebar_packages:find_highest_matching(Dep, Vsn, HexRegistry) of + {ok, HighestDepVsn} -> + digraph:add_edge(Graph, {Pkg, PkgVsn}, {Dep, HighestDepVsn}), + [{Dep, DepVsn} | DepsListAcc]; + none -> + DepsListAcc + end; Vsn -> digraph:add_edge(Graph, {Pkg, PkgVsn}, {Dep, Vsn}), [{Dep, Vsn} | DepsListAcc] - end + end; + ([_Dep, _DepVsn, true, _AppName | _], DepsListAcc) -> + DepsListAcc end, [], Deps). diff --git a/src/rebar_prv_upgrade.erl b/src/rebar_prv_upgrade.erl index 37914a7..05845e4 100644 --- a/src/rebar_prv_upgrade.erl +++ b/src/rebar_prv_upgrade.erl @@ -91,29 +91,38 @@ parse_names(Bin, Locks) -> prepare_locks([], _, Locks, Unlocks) -> {Locks, Unlocks}; prepare_locks([Name|Names], Deps, Locks, Unlocks) -> + AtomName = binary_to_atom(Name, utf8), case lists:keyfind(Name, 1, Locks) of {_, _, 0} = Lock -> - AtomName = binary_to_atom(Name, utf8), case lists:keyfind(AtomName, 1, Deps) of false -> ?PRV_ERROR({unknown_dependency, Name}); Dep -> - Source = case Dep of - {_, Src} -> Src; - {_, _, Src} -> Src - end, - {NewLocks, NewUnlocks} = unlock_higher_than(0, Locks -- [Lock]), - prepare_locks(Names, - Deps, - NewLocks, + {Source, NewLocks, NewUnlocks} = prepare_lock(Dep, Lock, Locks), + prepare_locks(Names, Deps, NewLocks, + [{Name, Source, 0} | NewUnlocks ++ Unlocks]) + end; + {_, _, Level} = Lock when Level > 0 -> + case lists:keyfind(AtomName, 1, Deps) of + false -> + ?PRV_ERROR({transitive_dependency, Name}); + Dep -> % Dep has been promoted + {Source, NewLocks, NewUnlocks} = prepare_lock(Dep, Lock, Locks), + prepare_locks(Names, Deps, NewLocks, [{Name, Source, 0} | NewUnlocks ++ Unlocks]) end; - {_, _, Level} when Level > 0 -> - ?PRV_ERROR({transitive_dependency, Name}); false -> ?PRV_ERROR({unknown_dependency, Name}) end. +prepare_lock(Dep, Lock, Locks) -> + Source = Source = case Dep of + {_, Src} -> Src; + {_, _, Src} -> Src + end, + {NewLocks, NewUnlocks} = unlock_higher_than(0, Locks -- [Lock]), + {Source, NewLocks, NewUnlocks}. + top_level_deps(Deps, Locks) -> [Dep || Dep <- Deps, lists:keymember(0, 3, Locks)]. @@ -131,4 +140,3 @@ info_useless(Old, New) -> || Name <- Old, not lists:member(Name, New)], ok. - diff --git a/src/rebar_prv_xref.erl b/src/rebar_prv_xref.erl index 67cd554..1d1afa0 100644 --- a/src/rebar_prv_xref.erl +++ b/src/rebar_prv_xref.erl @@ -36,7 +36,8 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> - {OriginalPath, XrefChecks} = prepare(State), + code:add_pathsa(rebar_state:code_paths(State, all_deps)), + XrefChecks = prepare(State), %% Run xref checks ?INFO("Running cross reference analysis...", []), @@ -45,9 +46,8 @@ do(State) -> %% Run custom queries QueryChecks = rebar_state:get(State, xref_queries, []), QueryResults = lists:foldl(fun check_query/2, [], QueryChecks), - - ok = cleanup(OriginalPath), - + stopped = xref:stop(xref), + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), case XrefResults =:= [] andalso QueryResults =:= [] of true -> {ok, State}; @@ -100,10 +100,6 @@ prepare(State) -> [{ok, _} = xref:add_directory(xref, rebar_app_info:ebin_dir(App)) || App <- rebar_state:project_apps(State)], - %% Save the code path prior to doing any further code path - %% manipulation - OriginalPath = code:get_path(), - %% Get list of xref checks we want to run ConfXrefChecks = rebar_state:get(State, xref_checks, [exports_not_used, @@ -112,15 +108,7 @@ prepare(State) -> XrefChecks = sets:to_list(sets:intersection( sets:from_list(?SUPPORTED_XREFS), sets:from_list(ConfXrefChecks))), - {OriginalPath, XrefChecks}. - -cleanup(Path) -> - %% Restore the code path using the provided path - true = rebar_utils:cleanup_code_path(Path), - - %% Stop xref - stopped = xref:stop(xref), - ok. + XrefChecks. xref_checks(XrefChecks) -> lists:foldl(fun run_xref_check/2, [], XrefChecks). diff --git a/src/rebar_state.erl b/src/rebar_state.erl index f922977..7a6e60d 100644 --- a/src/rebar_state.erl +++ b/src/rebar_state.erl @@ -3,6 +3,8 @@ -export([new/0, new/1, new/2, new/3, get/2, get/3, set/3, + code_paths/2, code_paths/3, update_code_paths/3, + opts/1, opts/2, default/1, default/2, @@ -22,7 +24,7 @@ project_apps/1, project_apps/2, deps_to_build/1, deps_to_build/2, - all_deps/1, all_deps/2, + all_deps/1, all_deps/2, update_all_deps/2, namespace/1, namespace/2, deps_names/1, @@ -40,6 +42,7 @@ -record(state_t, {dir :: file:name(), opts = dict:new() :: rebar_dict(), + code_paths = dict:new() :: rebar_dict(), default = dict:new() :: rebar_dict(), escript_path :: undefined | file:filename_all(), @@ -62,29 +65,32 @@ -export_type([t/0]). --type t() :: record(state_t). +-type t() :: #state_t{}. -spec new() -> t(). new() -> - #state_t{dir = rebar_dir:get_cwd()}. + BaseState = base_state(), + BaseState#state_t{dir = rebar_dir:get_cwd()}. -spec new(list()) -> t(). new(Config) when is_list(Config) -> + BaseState = base_state(), Deps = proplists:get_value(deps, Config, []), Opts = dict:from_list([{{deps, default}, Deps} | Config]), - #state_t { dir = rebar_dir:get_cwd(), - default = Opts, - opts = Opts }. + BaseState#state_t { dir = rebar_dir:get_cwd(), + default = Opts, + opts = Opts }. -spec new(t() | atom(), list()) -> t(). new(Profile, Config) when is_atom(Profile) , is_list(Config) -> + BaseState = base_state(), Deps = proplists:get_value(deps, Config, []), Opts = dict:from_list([{{deps, default}, Deps} | Config]), - #state_t { dir = rebar_dir:get_cwd(), - current_profiles = [Profile], - default = Opts, - opts = Opts }; + BaseState#state_t { dir = rebar_dir:get_cwd(), + current_profiles = [Profile], + default = Opts, + opts = Opts }; new(ParentState=#state_t{}, Config) -> %% Load terms from rebar.config, if it exists Dir = rebar_dir:get_cwd(), @@ -110,6 +116,15 @@ new(ParentState, Config, Dir) -> ,opts=NewOpts ,default=NewOpts}. +base_state() -> + case application:get_env(rebar, resources) of + undefined -> + Resources = []; + {ok, Resources} -> + Resources + end, + #state_t{resources=Resources}. + get(State, Key) -> {ok, Value} = dict:find(Key, State#state_t.opts), Value. @@ -132,6 +147,25 @@ default(#state_t{default=Opts}) -> default(State, Opts) -> State#state_t{default=Opts}. +code_paths(#state_t{code_paths=CodePaths}, Key) -> + case dict:find(Key, CodePaths) of + {ok, CodePath} -> + CodePath; + _ -> + [] + end. + +code_paths(State=#state_t{code_paths=CodePaths}, Key, CodePath) -> + State#state_t{code_paths=dict:store(Key, CodePath, CodePaths)}. + +update_code_paths(State=#state_t{code_paths=CodePaths}, Key, CodePath) -> + case dict:is_key(Key, CodePaths) of + true -> + State#state_t{code_paths=dict:append_list(Key, CodePath, CodePaths)}; + false -> + State#state_t{code_paths=dict:store(Key, CodePath, CodePaths)} + end. + opts(#state_t{opts=Opts}) -> Opts. @@ -207,16 +241,28 @@ apply_profiles(State, Profile) when not is_list(Profile) -> apply_profiles(State, [Profile]); apply_profiles(State, [default]) -> State; -apply_profiles(State=#state_t{opts=Opts, current_profiles=CurrentProfiles}, Profiles) -> +apply_profiles(State=#state_t{default = Defaults, current_profiles=CurrentProfiles}, Profiles) -> + AppliedProfiles = deduplicate(CurrentProfiles ++ Profiles), ConfigProfiles = rebar_state:get(State, profiles, []), - {Profiles1, NewOpts} = - lists:foldl(fun(default, {ProfilesAcc, OptsAcc}) -> - {ProfilesAcc, OptsAcc}; - (Profile, {ProfilesAcc, OptsAcc}) -> + NewOpts = + lists:foldl(fun(default, OptsAcc) -> + OptsAcc; + (Profile, OptsAcc) -> ProfileOpts = dict:from_list(proplists:get_value(Profile, ConfigProfiles, [])), - {[Profile]++ProfilesAcc, merge_opts(Profile, ProfileOpts, OptsAcc)} - end, {[], Opts}, Profiles), - State#state_t{current_profiles=CurrentProfiles++Profiles1, opts=NewOpts}. + merge_opts(Profile, ProfileOpts, OptsAcc) + end, Defaults, AppliedProfiles), + State#state_t{current_profiles = AppliedProfiles, opts=NewOpts}. + +deduplicate(Profiles) -> + do_deduplicate(lists:reverse(Profiles), []). + +do_deduplicate([], Acc) -> + Acc; +do_deduplicate([Head | Rest], Acc) -> + case lists:member(Head, Acc) of + true -> do_deduplicate(Rest, Acc); + false -> do_deduplicate(Rest, [Head | Acc]) + end. merge_opts(Profile, NewOpts, OldOpts) -> Opts = merge_opts(NewOpts, OldOpts), @@ -298,6 +344,9 @@ all_deps(#state_t{all_deps=Apps}) -> all_deps(State=#state_t{}, NewApps) -> State#state_t{all_deps=NewApps}. +update_all_deps(State=#state_t{all_deps=Apps}, NewApps) -> + State#state_t{all_deps=Apps++NewApps}. + namespace(#state_t{namespace=Namespace}) -> Namespace. diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl index 75cbb87..353fa36 100644 --- a/src/rebar_templater.erl +++ b/src/rebar_templater.erl @@ -29,14 +29,10 @@ -export([new/4, list_templates/1]). -%% API for other utilities that need templating functionality --export([resolve_variables/2, - render/2]). -include("rebar.hrl"). -define(TEMPLATE_RE, "^[^._].*\\.template\$"). --define(ERLYDTL_COMPILE_OPTS, [report_warnings, return_errors, {auto_escape, false}, {out_dir, false}]). %% =================================================================== %% Public API @@ -45,7 +41,7 @@ %% Apply a template new(Template, Vars, Force, State) -> {AvailTemplates, Files} = find_templates(State), - ?DEBUG("Looking for ~p~n", [Template]), + ?DEBUG("Looking for ~p", [Template]), case lists:keyfind(Template, 1, AvailTemplates) of false -> {not_found, Template}; TemplateTup -> create(TemplateTup, Files, Vars, Force, State) @@ -57,34 +53,6 @@ list_templates(State) -> [list_template(Files, Template, State) || Template <- AvailTemplates]. %% =================================================================== -%% Rendering API / legacy? -%% =================================================================== - -%% Given a list of key value pairs, for each string value attempt to -%% render it using Dict as the context. Storing the result in Dict as Key. -%% -resolve_variables([], Dict) -> - Dict; -resolve_variables([{Key, Value0} | Rest], Dict) when is_list(Value0) -> - Value = render(Value0, Dict), - resolve_variables(Rest, dict:store(Key, Value, Dict)); -resolve_variables([{Key, {list, Dicts}} | Rest], Dict) when is_list(Dicts) -> - %% just un-tag it so erlydtl can use it - resolve_variables(Rest, dict:store(Key, Dicts, Dict)); -resolve_variables([_Pair | Rest], Dict) -> - resolve_variables(Rest, Dict). - -%% -%% Render a binary to a string, using erlydtl and the specified context -%% -render(Template, Context) when is_atom(Template) -> - Template:render(Context); -render(Template, Context) -> - Module = list_to_atom(Template++"_dtl"), - Module:render(Context). - - -%% =================================================================== %% Internal Functions %% =================================================================== @@ -191,7 +159,7 @@ create({Template, Type, File}, Files, UserVars, Force, State) -> %% Run template instructions one at a time. execute_template([], _, {Template,_,_}, _, _) -> - ?DEBUG("Template ~s applied~n", [Template]), + ?DEBUG("Template ~s applied", [Template]), ok; %% We can't execute the description execute_template([{description, _} | Terms], Files, Template, Vars, Force) -> @@ -201,13 +169,13 @@ execute_template([{variables, _} | Terms], Files, Template, Vars, Force) -> execute_template(Terms, Files, Template, Vars, Force); %% Create a directory execute_template([{dir, Path} | Terms], Files, Template, Vars, Force) -> - ?DEBUG("Creating directory ~p~n", [Path]), + ?DEBUG("Creating directory ~p", [Path]), case ec_file:mkdir_p(expand_path(Path, Vars)) of ok -> ok; {error, Reason} -> ?ABORT("Failed while processing template instruction " - "{dir, ~p}: ~p~n", [Path, Reason]) + "{dir, ~p}: ~p", [Path, Reason]) end, execute_template(Terms, Files, Template, Vars, Force); %% Change permissions on a file @@ -218,29 +186,28 @@ execute_template([{chmod, File, Perm} | Terms], Files, Template, Vars, Force) -> execute_template(Terms, Files, Template, Vars, Force); {error, Reason} -> ?ABORT("Failed while processing template instruction " - "{chmod, ~.8#, ~p}: ~p~n", [Perm, File, Reason]) + "{chmod, ~.8#, ~p}: ~p", [Perm, File, Reason]) end; %% Create a raw untemplated file execute_template([{file, From, To} | Terms], Files, {Template, Type, Cwd}, Vars, Force) -> - ?DEBUG("Creating file ~p~n", [To]), + ?DEBUG("Creating file ~p", [To]), Data = load_file(Files, Type, filename:join(Cwd, From)), Out = expand_path(To,Vars), case write_file(Out, Data, Force) of ok -> ok; - {error, exists} -> ?INFO("File ~p already exists.~n", [Out]) + {error, exists} -> ?INFO("File ~p already exists.", [Out]) end, execute_template(Terms, Files, {Template, Type, Cwd}, Vars, Force); %% Operate on a django template execute_template([{template, From, To} | Terms], Files, {Template, Type, Cwd}, Vars, Force) -> - ?DEBUG("Executing template file ~p~n", [From]), + ?DEBUG("Executing template file ~p", [From]), Out = expand_path(To, Vars), Tpl = load_file(Files, Type, filename:join(Cwd, From)), - TplName = make_template_name("rebar_template", Out), - {ok, Mod} = erlydtl:compile_template(Tpl, TplName, ?ERLYDTL_COMPILE_OPTS), - {ok, Output} = Mod:render(Vars), - case write_file(Out, Output, Force) of - ok -> ok; - {error, exists} -> ?INFO("File ~p already exists~n", [Out]) + case write_file(Out, render(Tpl, Vars), Force) of + ok -> + ok; + {error, exists} -> + ?INFO("File ~p already exists", [Out]) end, execute_template(Terms, Files, {Template, Type, Cwd}, Vars, Force); %% Unknown @@ -266,17 +233,29 @@ replace_var([H|T], Acc, Vars) -> %% Load a list of all the files in the escript and on disk find_templates(State) -> - %% Cache the files since we'll potentially need to walk it several times - %% over the course of a run. - Files = cache_escript_files(State), + DiskTemplates = find_disk_templates(State), + {MainTemplates, Files} = + case rebar_state:escript_path(State) of + undefined -> + {find_priv_templates(State), []}; + _ -> + %% Cache the files since we'll potentially need to walk it several times + %% over the course of a run. + F = cache_escript_files(State), + {find_escript_templates(F), F} + end, + AvailTemplates = find_available_templates(DiskTemplates, + MainTemplates), + ?DEBUG("Available templates: ~p\n", [AvailTemplates]), + {AvailTemplates, Files}. - %% Build a list of available templates +find_available_templates(TemplateList1, TemplateList2) -> AvailTemplates = prioritize_templates( - tag_names(find_disk_templates(State)), - tag_names(find_escript_templates(Files))), + tag_names(TemplateList1), + tag_names(TemplateList2)), ?DEBUG("Available templates: ~p\n", [AvailTemplates]), - {AvailTemplates, Files}. + AvailTemplates. %% Scan the current escript for available files cache_escript_files(State) -> @@ -293,6 +272,12 @@ find_escript_templates(Files) -> || {Name, _Bin} <- Files, re:run(Name, ?TEMPLATE_RE, [{capture, none}]) == match]. +find_priv_templates(State) -> + OtherTemplates = rebar_utils:find_files(code:priv_dir(rebar), ?TEMPLATE_RE), + HomeFiles = rebar_utils:find_files(rebar_dir:template_dir(State), + ?TEMPLATE_RE, true), % recursive + [{file, F} || F <- OtherTemplates ++ HomeFiles]. + %% Fetch template indexes that sit on disk in the user's HOME find_disk_templates(State) -> OtherTemplates = find_other_templates(State), @@ -324,11 +309,11 @@ prioritize_templates([{Name, Type, File} | Rest], Valid) -> prioritize_templates(Rest, [{Name, Type, File} | Valid]); {_, escript, _} -> ?DEBUG("Skipping template ~p, due to presence of a built-in " - "template with the same name~n", [Name]), + "template with the same name", [Name]), prioritize_templates(Rest, Valid); {_, file, _} -> ?DEBUG("Skipping template ~p, due to presence of a custom " - "template at ~s~n", [Name, File]), + "template at ~s", [Name, File]), prioritize_templates(Rest, Valid) end. @@ -391,12 +376,8 @@ write_file(Output, Data, Force) -> {error, exists} end. --spec make_template_name(string(), term()) -> module(). -make_template_name(Base, Value) -> - %% Seed so we get different values each time - random:seed(erlang:now()), - Hash = erlang:phash2(Value), - Ran = random:uniform(10000000), - erlang:list_to_atom(Base ++ "_" ++ - erlang:integer_to_list(Hash) ++ - "_" ++ erlang:integer_to_list(Ran)). +%% +%% Render a binary to a string, using mustache and the specified context +%% +render(Bin, Context) -> + mustache:render(ec_cnv:to_binary(Bin), Context, [{key_type, atom}]). diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index bc2e4ac..4f0bc80 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -42,8 +42,7 @@ erl_to_mod/1, beams/1, find_executable/1, - expand_code_path/0, - vcs_vsn/2, + vcs_vsn/3, deprecated/3, deprecated/4, erl_opts/1, @@ -54,7 +53,8 @@ get_arch/0, wordsize/0, tup_umerge/2, - tup_sort/1]). + tup_sort/1, + line_count/1]). %% for internal use only -export([otp_release/0]). @@ -177,13 +177,6 @@ find_executable(Name) -> "\"" ++ filename:nativename(Path) ++ "\"" end. -%% Convert all the entries in the code path to absolute paths. -expand_code_path() -> - CodePath = lists:foldl(fun(Path, Acc) -> - [filename:absname(Path) | Acc] - end, [], code:get_path()), - code:set_path(lists:reverse(CodePath)). - deprecated(Old, New, Opts, When) when is_list(Opts) -> case lists:member(Old, Opts) of true -> @@ -289,6 +282,11 @@ umerge([], Olds, Merged, CmpMerged, Cmp) when CmpMerged == Cmp -> umerge([], Olds, Merged, _CmpMerged, Cmp) -> lists:reverse(Olds, [Cmp | Merged]). +%% Implements wc -l functionality used to determine patchcount from git output +line_count(PatchLines) -> + Tokenized = string:tokens(PatchLines, "\n"), + {ok, length(Tokenized)}. + %% ==================================================================== %% Internal functions %% ==================================================================== @@ -479,8 +477,8 @@ escript_foldl(Fun, Acc, File) -> Error end. -vcs_vsn(Vcs, Dir) -> - case vcs_vsn_cmd(Vcs, Dir) of +vcs_vsn(Vcs, Dir, Resources) -> + case vcs_vsn_cmd(Vcs, Dir, Resources) of {plain, VsnString} -> VsnString; {cmd, CmdString} -> @@ -492,23 +490,48 @@ vcs_vsn(Vcs, Dir) -> end. %% Temp work around for repos like relx that use "semver" -vcs_vsn_cmd(VCS, Dir) when VCS =:= semver ; VCS =:= "semver" -> - rebar_git_resource:make_vsn(Dir); -vcs_vsn_cmd(VCS, Dir) when VCS =:= git ; VCS =:= "git" -> - rebar_git_resource:make_vsn(Dir); -vcs_vsn_cmd(VCS, Dir) when VCS =:= pkg ; VCS =:= "pkg" -> - rebar_pkg_resource:make_vsn(Dir); -vcs_vsn_cmd({cmd, _Cmd}=Custom, _) -> +vcs_vsn_cmd(VCS, Dir, Resources) when VCS =:= semver ; VCS =:= "semver" -> + vcs_vsn_cmd(git, Dir, Resources); +vcs_vsn_cmd({cmd, _Cmd}=Custom, _, _) -> Custom; -vcs_vsn_cmd(Version, _) when is_list(Version) -> - {plain, Version}; -vcs_vsn_cmd(_, _) -> +vcs_vsn_cmd(VCS, Dir, Resources) when is_atom(VCS) -> + case find_resource_module(VCS, Resources) of + {ok, Module} -> + Module:make_vsn(Dir); + {error, _} -> + unknown + end; +vcs_vsn_cmd(VCS, Dir, Resources) when is_list(VCS) -> + try list_to_existing_atom(VCS) of + AVCS -> + case vcs_vsn_cmd(AVCS, Dir, Resources) of + unknown -> {plain, VCS}; + Other -> Other + end + catch + error:badarg -> + {plain, VCS} + end; +vcs_vsn_cmd(_, _, _) -> unknown. vcs_vsn_invoke(Cmd, Dir) -> {ok, VsnString} = rebar_utils:sh(Cmd, [{cd, Dir}, {use_stdout, false}]), string:strip(VsnString, right, $\n). +find_resource_module(Type, Resources) -> + case lists:keyfind(Type, 1, Resources) of + false -> + case code:which(Type) of + non_existing -> + {error, unknown}; + _ -> + {ok, Type} + end; + {Type, Module} -> + {ok, Module} + end. + %% %% Filter a list of erl_opts platform_define options such that only %% those which match the provided architecture regex are returned. |