diff options
-rw-r--r-- | priv/templates/otp_app.app.src.dtl | 1 | ||||
-rw-r--r-- | priv/templates/otp_lib.app.src.dtl | 1 | ||||
-rw-r--r-- | rebar.config | 18 | ||||
-rw-r--r-- | src/rebar3.erl | 4 | ||||
-rw-r--r-- | src/rebar_app_info.erl | 2 | ||||
-rw-r--r-- | src/rebar_fetch.erl | 304 | ||||
-rw-r--r-- | src/rebar_git_resource.erl | 146 | ||||
-rw-r--r-- | src/rebar_hooks.erl | 24 | ||||
-rw-r--r-- | src/rebar_pkg_resource.erl | 32 | ||||
-rw-r--r-- | src/rebar_prv_clean.erl | 48 | ||||
-rw-r--r-- | src/rebar_prv_compile.erl | 34 | ||||
-rw-r--r-- | src/rebar_prv_install_deps.erl | 137 | ||||
-rw-r--r-- | src/rebar_prv_tar.erl | 4 | ||||
-rw-r--r-- | src/rebar_prv_upgrade.erl | 11 | ||||
-rw-r--r-- | src/rebar_resource.erl | 43 | ||||
-rw-r--r-- | src/rebar_topo.erl | 2 | ||||
-rw-r--r-- | src/rebar_utils.erl | 28 |
17 files changed, 506 insertions, 333 deletions
diff --git a/priv/templates/otp_app.app.src.dtl b/priv/templates/otp_app.app.src.dtl index 1a4e4e8..0af909f 100644 --- a/priv/templates/otp_app.app.src.dtl +++ b/priv/templates/otp_app.app.src.dtl @@ -9,4 +9,5 @@ ,stdlib ]} ,{env,[]} + ,{modules, []} ]}. diff --git a/priv/templates/otp_lib.app.src.dtl b/priv/templates/otp_lib.app.src.dtl index 5009c3d..5192af7 100644 --- a/priv/templates/otp_lib.app.src.dtl +++ b/priv/templates/otp_lib.app.src.dtl @@ -8,4 +8,5 @@ ,stdlib ]} ,{env,[]} + ,{modules, []} ]}. diff --git a/rebar.config b/rebar.config index 0014c3a..4cc0168 100644 --- a/rebar.config +++ b/rebar.config @@ -27,13 +27,17 @@ {platform_define, "^[0-9]+", namespaced_types} ]}. -{deps, [{providers, "", - {git, "https://github.com/tsloughter/providers.git", - {branch, "format_error"}}}, - {relx, "", - {git, "https://github.com/tsloughter/relx.git", - {branch, "format_error2"}}}, - {getopt, "", {git, "https://github.com/jcomellas/getopt.git", {branch, "master"}}}]}. +{deps, [ + {erlware_commons, ".*", + {git, "https://github.com/erlware/erlware_commons.git", + {branch, "master"}}}, + {providers, "", + {git, "https://github.com/tsloughter/providers.git", + {branch, "master"}}}, + {relx, "", + {git, "https://github.com/tsloughter/relx.git", + {branch, "master"}}}, + {getopt, "", {git, "https://github.com/jcomellas/getopt.git", {branch, "master"}}}]}. {erlydtl_opts, [{doc_root, "priv/templates"}, {compiler_options, [report, return, debug_info]}]}. diff --git a/src/rebar3.erl b/src/rebar3.erl index 22c08ca..e5d82f9 100644 --- a/src/rebar3.erl +++ b/src/rebar3.erl @@ -49,10 +49,12 @@ main(Args) -> ok; rebar_abort -> rebar_utils:delayed_halt(1); + {error, rebar_abort} -> + rebar_utils:delayed_halt(1); {error, {Module, Reason}} -> ?ERROR(Module:format_error(Reason, []), []), rebar_utils:delayed_halt(1); - {error, Error} -> + {error, Error} when is_list(Error) -> ?ERROR(Error++"~n", []), rebar_utils:delayed_halt(1); Error -> diff --git a/src/rebar_app_info.erl b/src/rebar_app_info.erl index 7a33811..47dfcad 100644 --- a/src/rebar_app_info.erl +++ b/src/rebar_app_info.erl @@ -38,7 +38,7 @@ original_vsn :: binary() | string() | undefined, app_details=[] :: list(), deps=[] :: list(), - dep_level :: integer(), + dep_level=0 :: integer(), dir :: file:name(), source :: string() | tuple() | undefined, valid :: boolean()}). diff --git a/src/rebar_fetch.erl b/src/rebar_fetch.erl index 0a90848..a7ae446 100644 --- a/src/rebar_fetch.erl +++ b/src/rebar_fetch.erl @@ -7,256 +7,78 @@ %% ------------------------------------------------------------------- -module(rebar_fetch). --export([new/4, - lock_source/2, +-export([lock_source/2, download_source/2, - update_source1/2, - source_engine_avail/1, - source_engine_avail/2, - has_vcs_dir/2, - print_source/1, - format_source/2]). + needs_update/2]). -include("rebar.hrl"). --record(p4_settings, { - client=undefined, - transport="tcp4:perforce:1666", - username, - password - }). - -new(Dir, App, Vsn, Source) -> - NewSource = lock_source(Dir, Source), - {App, Vsn, NewSource}. - -init_p4_settings(Basename) -> - #p4_settings{client = - case inet:gethostname() of - {ok,HostName} -> - HostName ++ "-" - ++ os:getenv("USER") ++ "-" - ++ Basename - ++ "-Rebar-automated-download" - end}. - -lock_source(AppDir, {git, Url, _}) -> - Ref = string:strip(os:cmd("git --git-dir='" ++ AppDir ++ "/.git' rev-parse --verify HEAD"), both, $\n), - {git, Url, Ref}; -lock_source(_AppDir, Source) -> - Source. +%% map short versions of resources to module names +-define(RESOURCES, [{git, rebar_git_resource}, {pkg, rebar_pkg_resource}]). + +-spec lock_source(file:filename_all(), rebar_resource:resource()) -> + rebar_resource:resource() | {error, string()}. +lock_source(AppDir, Source) -> + case get_resource_type(Source) of + {error, _}=Error -> + Error; + Module -> + Module:lock(AppDir, Source) + end. +-spec download_source(file:filename_all(), rebar_resource:resource()) -> true | {error, any()}. download_source(AppDir, Source) -> - TmpDir = ec_file:insecure_mkdtemp(), - AppDir1 = ec_cnv:to_list(AppDir), - ec_file:mkdir_p(AppDir1), - case download_source_tmp(TmpDir, Source) of - {ok, _} -> - ok = ec_file:copy(TmpDir, filename:absname(AppDir1), [recursive]); - {tarball, File} -> - ok = erl_tar:extract(File, [{cwd, TmpDir} - ,compressed]), - BaseName = filename:basename(AppDir1), - [FromDir] = filelib:wildcard(filename:join(TmpDir, BaseName++"-*")), - ec_file:copy(FromDir, AppDir1, [recursive]) + case get_resource_type(Source) of + {error, _}=Error -> + Error; + Module -> + TmpDir = ec_file:insecure_mkdtemp(), + AppDir1 = ec_cnv:to_list(AppDir), + ec_file:mkdir_p(AppDir1), + case Module:download(TmpDir, Source) of + {ok, _} -> + code:del_path(filename:absname(filename:join(AppDir1, "ebin"))), + ec_file:remove(filename:absname(AppDir1), [recursive]), + ok = ec_file:copy(TmpDir, filename:absname(AppDir1), [recursive]), + true; + {tarball, File} -> + ok = erl_tar:extract(File, [{cwd, TmpDir} + ,compressed]), + BaseName = filename:basename(AppDir1), + [FromDir] = filelib:wildcard(filename:join(TmpDir, BaseName++"-*")), + code:del_path(filename:absname(filename:join(AppDir1, "ebin"))), + ec_file:remove(filename:absname(AppDir1), [recursive]), + ok = ec_file:copy(FromDir, filename:absname(AppDir1), [recursive]), + true + end end. -download_source_tmp(TmpDir, {p4, Url}) -> - download_source_tmp(TmpDir, {p4, Url, "#head"}); -download_source_tmp(TmpDir, {p4, Url, Rev}) -> - download_source_tmp(TmpDir, {p4, Url, Rev, init_p4_settings(filename:basename(TmpDir))}); -download_source_tmp(TmpDir, {p4, Url, _Rev, Settings}) -> - ok = filelib:ensure_dir(TmpDir), - rebar_utils:sh_send("p4 client -i", - ?FMT("Client: ~s~n" - ++"Description: generated by Rebar~n" - ++"Root: ~s~n" - ++"View:~n" - ++" ~s/... //~s/...~n", - [Settings#p4_settings.client, - TmpDir, - Url, - Settings#p4_settings.client]), - []), - rebar_utils:sh(?FMT("p4 -c ~s sync -f", [Settings#p4_settings.client]), []); -download_source_tmp(TmpDir, {hg, Url, Rev}) -> - ok = filelib:ensure_dir(TmpDir), - rebar_utils:sh(?FMT("hg clone -U ~s ~s", [Url, filename:basename(TmpDir)]), - [{cd, filename:dirname(TmpDir)}]), - rebar_utils:sh(?FMT("hg update ~s", [Rev]), [{cd, TmpDir}]); -download_source_tmp(TmpDir, {git, Url}) -> - download_source_tmp(TmpDir, {git, Url, {branch, "HEAD"}}); -download_source_tmp(TmpDir, {git, Url, ""}) -> - download_source_tmp(TmpDir, {git, Url, {branch, "HEAD"}}); -download_source_tmp(TmpDir, {git, Url, {branch, Branch}}) -> - ok = filelib:ensure_dir(TmpDir), - rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(TmpDir)]), - [{cd, filename:dirname(TmpDir)}]), - rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), [{cd, TmpDir}]); -download_source_tmp(TmpDir, {git, Url, {tag, Tag}}) -> - ok = filelib:ensure_dir(TmpDir), - rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(TmpDir)]), - [{cd, filename:dirname(TmpDir)}]), - rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), [{cd, TmpDir}]); -download_source_tmp(TmpDir, {git, Url, Rev}) -> - ok = filelib:ensure_dir(TmpDir), - rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(TmpDir)]), - [{cd, filename:dirname(TmpDir)}]), - rebar_utils:sh(?FMT("git checkout -q ~s", [Rev]), [{cd, TmpDir}]); -download_source_tmp(TmpDir, {bzr, Url, Rev}) -> - ok = filelib:ensure_dir(TmpDir), - rebar_utils:sh(?FMT("bzr branch -r ~s ~s ~s", - [Rev, Url, filename:basename(TmpDir)]), - [{cd, filename:dirname(TmpDir)}]); -download_source_tmp(TmpDir, {svn, Url, Rev}) -> - ok = filelib:ensure_dir(TmpDir), - rebar_utils:sh(?FMT("svn checkout -r ~s ~s ~s", - [Rev, Url, filename:basename(TmpDir)]), - [{cd, filename:dirname(TmpDir)}]); -download_source_tmp(TmpDir, {rsync, Url}) -> - ok = filelib:ensure_dir(TmpDir), - rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, TmpDir]), []); -download_source_tmp(TmpDir, {fossil, Url}) -> - download_source_tmp(TmpDir, {fossil, Url, ""}); -download_source_tmp(TmpDir, {fossil, Url, Version}) -> - Repository = filename:join(TmpDir, filename:basename(TmpDir) ++ ".fossil"), - ok = filelib:ensure_dir(Repository), - ok = file:set_cwd(TmpDir), - rebar_utils:sh(?FMT("fossil clone ~s ~s", [Url, Repository]), - [{cd, TmpDir}]), - rebar_utils:sh(?FMT("fossil open ~s ~s --nested", [Repository, Version]), - []); -download_source_tmp(TmpDir, Url) -> - TmpFile = filename:join(TmpDir, "package.tar.gz"), - {ok, saved_to_file} = httpc:request(get, {binary_to_list(Url), []}, [], [{stream, TmpFile}]), - {tarball, TmpFile}. - -update_source1(AppDir, Args) when element(1, Args) =:= p4 -> - download_source_tmp(AppDir, Args); -update_source1(AppDir, {git, Url}) -> - update_source1(AppDir, {git, Url, {branch, "HEAD"}}); -update_source1(AppDir, {git, Url, ""}) -> - update_source1(AppDir, {git, Url, {branch, "HEAD"}}); -update_source1(AppDir, {git, _Url, {branch, Branch}}) -> - ShOpts = [{cd, AppDir}], - rebar_utils:sh("git fetch origin", ShOpts), - rebar_utils:sh(?FMT("git checkout -q ~s", [Branch]), ShOpts), - rebar_utils:sh( - ?FMT("git pull --ff-only --no-rebase -q origin ~s", [Branch]),ShOpts); -update_source1(AppDir, {git, _Url, {tag, Tag}}) -> - ShOpts = [{cd, AppDir}], - rebar_utils:sh("git fetch origin", ShOpts), - rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), ShOpts); -update_source1(AppDir, {git, _Url, Refspec}) -> - ShOpts = [{cd, AppDir}], - rebar_utils:sh("git fetch origin", ShOpts), - rebar_utils:sh(?FMT("git checkout -q ~s", [Refspec]), ShOpts); -update_source1(AppDir, {svn, _Url, Rev}) -> - rebar_utils:sh(?FMT("svn up -r ~s", [Rev]), [{cd, AppDir}]); -update_source1(AppDir, {hg, _Url, Rev}) -> - rebar_utils:sh(?FMT("hg pull -u -r ~s", [Rev]), [{cd, AppDir}]); -update_source1(AppDir, {bzr, _Url, Rev}) -> - rebar_utils:sh(?FMT("bzr update -r ~s", [Rev]), [{cd, AppDir}]); -update_source1(AppDir, {rsync, Url}) -> - rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]); -update_source1(AppDir, {fossil, Url}) -> - update_source1(AppDir, {fossil, Url, ""}); -update_source1(AppDir, {fossil, _Url, Version}) -> - ok = file:set_cwd(AppDir), - rebar_utils:sh("fossil pull", [{cd, AppDir}]), - rebar_utils:sh(?FMT("fossil update ~s", [Version]), []). - -%% =================================================================== -%% Source helper functions -%% =================================================================== +-spec needs_update(file:filename_all(), rebar_resource:resource()) -> boolean() | {error, string()}. +needs_update(AppDir, Source) -> + case get_resource_type(Source) of + {error, _}=Error -> + Error; + Module -> + Module:needs_update(AppDir, Source) + end. -source_engine_avail(Source) -> - Name = element(1, Source), - source_engine_avail(Name, Source). +get_resource_type({Type, Location, _}) -> + find_resource_module(Type, Location); +get_resource_type({Type, _, _, Location}) -> + find_resource_module(Type, Location); +get_resource_type(_) -> + rebar_pkg_resource. -source_engine_avail(Name, Source) - when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync; - Name == fossil; Name == p4 -> - case vcs_client_vsn(Name) >= required_vcs_client_vsn(Name) of - true -> - true; +find_resource_module(Type, Location) -> + case lists:keyfind(Type, 1, ?RESOURCES) of false -> - ?ABORT("Rebar requires version ~p or higher of ~s to process ~p\n", - [required_vcs_client_vsn(Name), Name, Source]) + case code:which(Type) of + non_existing -> + {error, io_lib:format("Cannot handle dependency ~s.~n" + " No module for resource type ~p", [Location, Type])}; + _ -> + Type + end; + {Type, Module} -> + Module end. - -vcs_client_vsn(false, _VsnArg, _VsnRegex) -> - false; -vcs_client_vsn(Path, VsnArg, VsnRegex) -> - {ok, Info} = rebar_utils:sh(Path ++ VsnArg, [{env, [{"LANG", "C"}]}, - {use_stdout, false}]), - case re:run(Info, VsnRegex, [{capture, all_but_first, list}]) of - {match, Match} -> - list_to_tuple([list_to_integer(S) || S <- Match]); - _ -> - false - end. - -required_vcs_client_vsn(p4) -> {2013, 1}; -required_vcs_client_vsn(hg) -> {1, 1}; -required_vcs_client_vsn(git) -> {1, 5}; -required_vcs_client_vsn(bzr) -> {2, 0}; -required_vcs_client_vsn(svn) -> {1, 6}; -required_vcs_client_vsn(rsync) -> {2, 0}; -required_vcs_client_vsn(fossil) -> {1, 0}. - -vcs_client_vsn(p4) -> - vcs_client_vsn(rebar_utils:find_executable("p4"), " -V", - "Rev\\. .*/(\\d+)\\.(\\d)/"); -vcs_client_vsn(hg) -> - vcs_client_vsn(rebar_utils:find_executable("hg"), " --version", - "version (\\d+).(\\d+)"); -vcs_client_vsn(git) -> - vcs_client_vsn(rebar_utils:find_executable("git"), " --version", - "git version (\\d+).(\\d+)"); -vcs_client_vsn(bzr) -> - vcs_client_vsn(rebar_utils:find_executable("bzr"), " --version", - "Bazaar \\(bzr\\) (\\d+).(\\d+)"); -vcs_client_vsn(svn) -> - vcs_client_vsn(rebar_utils:find_executable("svn"), " --version", - "svn, version (\\d+).(\\d+)"); -vcs_client_vsn(rsync) -> - vcs_client_vsn(rebar_utils:find_executable("rsync"), " --version", - "rsync version (\\d+).(\\d+)"); -vcs_client_vsn(fossil) -> - vcs_client_vsn(rebar_utils:find_executable("fossil"), " version", - "version (\\d+).(\\d+)"). - -has_vcs_dir(p4, _) -> - true; -has_vcs_dir(git, Dir) -> - filelib:is_dir(filename:join(Dir, ".git")); -has_vcs_dir(hg, Dir) -> - filelib:is_dir(filename:join(Dir, ".hg")); -has_vcs_dir(bzr, Dir) -> - filelib:is_dir(filename:join(Dir, ".bzr")); -has_vcs_dir(svn, Dir) -> - filelib:is_dir(filename:join(Dir, ".svn")) - orelse filelib:is_dir(filename:join(Dir, "_svn")); -has_vcs_dir(rsync, _) -> - true; -has_vcs_dir(_, _) -> - true. - -print_source({App, _, Source}) -> - ?CONSOLE("~s~n", [format_source(App, Source)]). - -format_source(App, {p4, Url}) -> - format_source(App, {p4, Url, "#head"}); -format_source(App, {git, Url}) -> - ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]); -format_source(App, {git, Url, ""}) -> - ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]); -format_source(App, {git, Url, {branch, Branch}}) -> - ?FMT("~p BRANCH ~s ~s", [App, Branch, Url]); -format_source(App, {git, Url, {tag, Tag}}) -> - ?FMT("~p TAG ~s ~s", [App, Tag, Url]); -format_source(App, {_, Url, Rev}) -> - ?FMT("~p REV ~s ~s", [App, Rev, Url]); -format_source(App, undefined) -> - ?FMT("~p", [App]). diff --git a/src/rebar_git_resource.erl b/src/rebar_git_resource.erl new file mode 100644 index 0000000..a6b60d5 --- /dev/null +++ b/src/rebar_git_resource.erl @@ -0,0 +1,146 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(rebar_git_resource). + +-behaviour(rebar_resource). + +-export([lock/2 + ,download/2 + ,needs_update/2 + ,make_vsn/1]). + +-include("rebar.hrl"). + +lock(AppDir, {git, Url, _}) -> + Ref = string:strip( + os:cmd("git --git-dir='" ++ AppDir ++ "/.git' rev-parse --verify HEAD") + ,both, $\n), + {git, Url, {ref, Ref}}. + +%% Return true if either the git url or tag/branch/ref is not the same as the currently +%% checked out git repo for the dep +needs_update(Dir, {git, Url, {tag, Tag}}) -> + {ok, CurrentUrl} = rebar_utils:sh(?FMT("git config --get remote.origin.url", []), + [{cd, Dir}]), + {ok, Current} = rebar_utils:sh(?FMT("git describe --tags --exact-match", []), + [{cd, Dir}]), + Current1 = string:strip(string:strip(Current, both, $\n), both, $\r), + CurrentUrl1 = string:strip(string:strip(CurrentUrl, both, $\n), both, $\r), + ?DEBUG("Comparing git tag ~s with ~s and url ~s with ~s~n", [Tag, Current1, Url, CurrentUrl1]), + not ((Current1 =:= Tag) andalso (CurrentUrl1 =:= Url)); +needs_update(Dir, {git, Url, {branch, Branch}}) -> + {ok, CurrentUrl} = rebar_utils:sh(?FMT("git config --get remote.origin.url", []), + [{cd, Dir}]), + {ok, Current} = rebar_utils:sh(?FMT("git symbolic-ref -q --short HEAD", []), + [{cd, Dir}]), + Current1 = string:strip(string:strip(Current, both, $\n), both, $\r), + CurrentUrl1 = string:strip(string:strip(CurrentUrl, both, $\n), both, $\r), + ?DEBUG("Comparing git branch ~s with ~s and url ~s with ~s~n", [Branch, Current1, Url, CurrentUrl1]), + not ((Current1 =:= Branch) andalso (CurrentUrl1 =:= Url)); +needs_update(Dir, {git, Url, Ref}) -> + case Ref of + {ref, Ref1} -> + Ref1; + Ref1 -> + Ref1 + end, + {ok, CurrentUrl} = rebar_utils:sh(?FMT("git config --get remote.origin.url", []), + [{cd, Dir}]), + {ok, Current} = rebar_utils:sh(?FMT("git rev-parse -q HEAD", []), + [{cd, Dir}]), + Current1 = string:strip(string:strip(Current, both, $\n), both, $\r), + CurrentUrl1 = string:strip(string:strip(CurrentUrl, both, $\n), both, $\r), + ?DEBUG("Comparing git ref ~s with ~s and url ~s with ~s~n", [Ref1, Current1, Url, CurrentUrl1]), + not ((Current1 =:= Ref1) andalso (CurrentUrl1 =:= Url)). + +download(Dir, {git, Url}) -> + ?WARN("WARNING: It is recommended to use {branch, Name}, {tag, Tag} or {ref, Ref}, otherwise updating the dep may not work as expected.~n", []), + download(Dir, {git, Url, {branch, "HEAD"}}); +download(Dir, {git, Url, ""}) -> + ?WARN("WARNING: It is recommended to use {branch, Name}, {tag, Tag} or {ref, Ref}, otherwise updating the dep may not work as expected.~n", []), + download(Dir, {git, Url, {branch, "HEAD"}}); +download(Dir, {git, Url, {branch, Branch}}) -> + ok = filelib:ensure_dir(Dir), + rebar_utils:sh(?FMT("git clone ~s ~s -b ~s --single-branch", + [Url, filename:basename(Dir), Branch]), + [{cd, filename:dirname(Dir)}]); +download(Dir, {git, Url, {tag, Tag}}) -> + ok = filelib:ensure_dir(Dir), + rebar_utils:sh(?FMT("git clone ~s ~s -b ~s --single-branch", + [Url, filename:basename(Dir), Tag]), + [{cd, filename:dirname(Dir)}]); +download(Dir, {git, Url, {ref, Ref}}) -> + ok = filelib:ensure_dir(Dir), + rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(Dir)]), + [{cd, filename:dirname(Dir)}]), + rebar_utils:sh(?FMT("git checkout -q ~s", [Ref]), [{cd, Dir}]); +download(Dir, {git, Url, Rev}) -> + ?WARN("WARNING: It is recommended to use {branch, Name}, {tag, Tag} or {ref, Ref}, otherwise updating the dep may not work as expected.~n", []), + ok = filelib:ensure_dir(Dir), + rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(Dir)]), + [{cd, filename:dirname(Dir)}]), + rebar_utils:sh(?FMT("git checkout -q ~s", [Rev]), [{cd, Dir}]). + +make_vsn(Dir) -> + Cwd = file:get_cwd(), + try + ok = file:set_cwd(Dir), + {Vsn, RawRef, RawCount} = collect_default_refcount(), + {plain, build_vsn_string(Vsn, RawRef, RawCount)} + after + file:set_cwd(Cwd) + end. + +%% Internal functions + +collect_default_refcount() -> + %% Get the tag timestamp and minimal ref from the system. The + %% timestamp is really important from an ordering perspective. + RawRef = os:cmd("git log -n 1 --pretty=format:'%h\n' "), + + {Tag, TagVsn} = parse_tags(), + RawCount = + case Tag of + undefined -> + os:cmd("git rev-list HEAD | wc -l"); + _ -> + get_patch_count(Tag) + end, + {TagVsn, RawRef, RawCount}. + +build_vsn_string(Vsn, RawRef, RawCount) -> + %% Cleanup the tag and the Ref information. Basically leading 'v's and + %% whitespace needs to go away. + RefTag = case RawRef of + undefined -> + ""; + RawRef -> + [".ref", re:replace(RawRef, "\\s", "", [global])] + end, + 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">> -> + erlang:binary_to_list(erlang:iolist_to_binary(Vsn)); + _ -> + erlang:binary_to_list(erlang:iolist_to_binary([Vsn, "+build.", + Count, RefTag])) + end. + +get_patch_count(RawRef) -> + Ref = re:replace(RawRef, "\\s", "", [global]), + Cmd = io_lib:format("git rev-list ~s..HEAD | wc -l", + [Ref]), + os:cmd(Cmd). + +parse_tags() -> + first_valid_tag(os:cmd("git log --oneline --decorate | fgrep \"tag: \" -1000")). + +first_valid_tag(Line) -> + case re:run(Line, "(\\(|\\s)tag:\\s(v([^,\\)]+))", [{capture, [2, 3], list}]) of + {match,[Tag, Vsn]} -> + {Tag, Vsn}; + nomatch -> + {undefined, "0.0.0"} + end. diff --git a/src/rebar_hooks.erl b/src/rebar_hooks.erl new file mode 100644 index 0000000..2599d8e --- /dev/null +++ b/src/rebar_hooks.erl @@ -0,0 +1,24 @@ +-module(rebar_hooks). + +-export([run_compile_hooks/4]). + +run_compile_hooks(Dir, Type, Command, State) -> + Hooks = rebar_state:get(State, Type, []), + lists:foreach(fun({_, C, _}=Hook) when C =:= Command -> + apply_hook(Dir, [], Hook); + ({C, _}=Hook) when C =:= Command -> + apply_hook(Dir, [], Hook); + (_) -> + continue + end, Hooks). + +apply_hook(Dir, Env, {Arch, Command, Hook}) -> + case rebar_utils:is_arch(Arch) of + true -> + apply_hook(Dir, Env, {Command, Hook}); + false -> + ok + end; +apply_hook(Dir, Env, {Command, Hook}) -> + Msg = lists:flatten(io_lib:format("Hook for ~p failed!~n", [Command])), + rebar_utils:sh(Hook, [{cd, Dir}, {env, Env}, {abort_on_error, Msg}]). diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl new file mode 100644 index 0000000..5cd6fc8 --- /dev/null +++ b/src/rebar_pkg_resource.erl @@ -0,0 +1,32 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(rebar_pkg_resource). + +-behaviour(rebar_resource). + +-export([lock/2 + ,download/2 + ,needs_update/2 + ,make_vsn/1]). + +-include("rebar.hrl"). + +lock(_AppDir, Source) -> + Source. + +needs_update(Dir, {pkg, _Name, Vsn, _Url}) -> + [AppInfo] = rebar_app_discover:find_apps([Dir], all), + case rebar_app_info:original_vsn(AppInfo) =:= ec_cnv:to_list(Vsn) of + true -> + false; + false -> + true + end. + +download(Dir, {pkg, _Name, _Vsn, Url}) -> + TmpFile = filename:join(Dir, "package.tar.gz"), + {ok, saved_to_file} = httpc:request(get, {binary_to_list(Url), []}, [], [{stream, TmpFile}]), + {tarball, TmpFile}. + +make_vsn(_) -> + {error, "Replacing version of type pkg not supported."}. diff --git a/src/rebar_prv_clean.erl b/src/rebar_prv_clean.erl index 72b85dc..a64e63a 100644 --- a/src/rebar_prv_clean.erl +++ b/src/rebar_prv_clean.erl @@ -27,18 +27,56 @@ init(State) -> {example, "rebar clean"}, {short_desc, "Remove compiled beam files from apps."}, {desc, ""}, - {opts, []}])), + {opts, [{all, $a, "all", undefined, "Clean all apps include deps"}]}])), {ok, State1}. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ProjectApps = rebar_state:project_apps(State), - lists:foreach(fun(AppInfo) -> - ?INFO("Cleaning out ~s...~n", [rebar_app_info:name(AppInfo)]), - rebar_erlc_compiler:clean(State, ec_cnv:to_list(rebar_app_info:dir(AppInfo))) - end, ProjectApps), + {all, All} = handle_args(State), + + case All of + true -> + DepsDir = rebar_state:get(State, deps_dir, ?DEFAULT_DEPS_DIR), + DepApps = rebar_app_discover:find_apps([DepsDir], all); + false -> + DepApps = [] + end, + + %% 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(), + clean_apps(EmptyState, DepApps), + + Cwd = rebar_utils:get_cwd(), + rebar_hooks:run_compile_hooks(Cwd, pre_hooks, clean, State), + clean_apps(State, ProjectApps), + rebar_hooks:run_compile_hooks(Cwd, post_hooks, clean, State), + {ok, State}. -spec format_error(any(), rebar_state:t()) -> {iolist(), rebar_state:t()}. format_error(Reason, State) -> {io_lib:format("~p", [Reason]), State}. + +%% =================================================================== +%% Internal functions +%% =================================================================== + +clean_apps(State, Apps) -> + lists:foreach(fun(AppInfo) -> + AppDir = rebar_app_info:dir(AppInfo), + C = rebar_config:consult(AppDir), + S = rebar_state:new(State, C, AppDir), + + ?INFO("Cleaning out ~s...~n", [rebar_app_info:name(AppInfo)]), + %% Legacy hook support + rebar_hooks:run_compile_hooks(AppDir, pre_hooks, clean, S), + rebar_erlc_compiler:clean(State, ec_cnv:to_list(rebar_app_info:dir(AppInfo))), + rebar_hooks:run_compile_hooks(AppDir, post_hooks, clean, S) + end, Apps). + +handle_args(State) -> + {Args, _} = rebar_state:command_parsed_args(State), + All = proplists:get_value(all, Args, false), + {all, All}. diff --git a/src/rebar_prv_compile.erl b/src/rebar_prv_compile.erl index 30611cd..d80586d 100644 --- a/src/rebar_prv_compile.erl +++ b/src/rebar_prv_compile.erl @@ -31,23 +31,31 @@ init(State) -> {short_desc, "Compile apps .app.src and .erl files."}, {desc, ""}, {opts, [ - {jobs, $j, "jobs", integer, JobsHelp} + {jobs, $j, "jobs", integer, JobsHelp} ]}])), {ok, State1}. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> {ok, State1} = handle_args(State), + Jobs = rebar_state:get(State1, jobs), ProjectApps = rebar_state:project_apps(State1), Deps = rebar_state:get(State1, deps_to_build, []), - lists:foreach(fun(AppInfo) -> - AppDir = rebar_app_info:dir(AppInfo), - C = rebar_config:consult(AppDir), - S = rebar_state:new(State1, C, AppDir), - build(S, AppInfo) - end, Deps++ProjectApps), + %% 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(), + EmptyState1 = rebar_state:set(EmptyState, jobs, Jobs), + build_apps(EmptyState1, Deps), + + %% Use the project State for building project apps + Cwd = rebar_utils:get_cwd(), + rebar_hooks:run_compile_hooks(Cwd, pre_hooks, compile, State1), + %% Set hooks to empty so top-level hooks aren't run for each project app + State2 = rebar_state:set(rebar_state:set(State1, post_hooks, []), pre_hooks, []), + build_apps(State2, ProjectApps), + rebar_hooks:run_compile_hooks(Cwd, post_hooks, compile, State1), {ok, State1}. @@ -55,6 +63,18 @@ do(State) -> format_error(Reason, State) -> {io_lib:format("~p", [Reason]), State}. +build_apps(State, Apps) -> + lists:foreach(fun(AppInfo) -> + AppDir = rebar_app_info:dir(AppInfo), + C = rebar_config:consult(AppDir), + S = rebar_state:new(State, C, AppDir), + + %% Legacy hook support + rebar_hooks:run_compile_hooks(AppDir, pre_hooks, compile, S), + build(S, AppInfo), + rebar_hooks:run_compile_hooks(AppDir, post_hooks, compile, S) + end, Apps). + build(State, AppInfo) -> ?INFO("Compiling ~s~n", [rebar_app_info:name(AppInfo)]), rebar_erlc_compiler:compile(State, ec_cnv:to_list(rebar_app_info:dir(AppInfo))), diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl index 522420d..9fb1630 100644 --- a/src/rebar_prv_install_deps.erl +++ b/src/rebar_prv_install_deps.erl @@ -68,19 +68,26 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ProjectApps = rebar_state:project_apps(State), - {ok, State1} = case rebar_state:get(State, locks, []) of - [] -> - handle_deps(State, rebar_state:get(State, deps, [])); - Locks -> - handle_deps(State, Locks) - end, - - Source = ProjectApps ++ rebar_state:src_apps(State1), - case rebar_topo:sort_apps(Source) of - {ok, Sort} -> - {ok, rebar_state:set(State1, deps_to_build, lists:dropwhile(fun rebar_app_info:valid/1, Sort -- ProjectApps))}; - {error, Error} -> - {error, Error} + try + {ok, State1} = case rebar_state:get(State, locks, []) of + [] -> + handle_deps(State, rebar_state:get(State, deps, [])); + Locks -> + handle_deps(State, Locks) + end, + + Source = ProjectApps ++ rebar_state:src_apps(State1), + case rebar_topo:sort_apps(Source) of + {ok, Sort} -> + {ok, rebar_state:set(State1, deps_to_build, + lists:dropwhile(fun rebar_app_info:valid/1, Sort -- ProjectApps))}; + {error, Error} -> + {error, Error} + end + catch + %% maybe_fetch will maybe_throw an exception to break out of some loops + _:Reason -> + {error, Reason} end. -spec format_error(any(), rebar_state:t()) -> {iolist(), rebar_state:t()}. @@ -102,7 +109,7 @@ handle_deps(State, Deps) -> handle_deps(State, Deps, false). -spec handle_deps(rebar_state:t(), [dep()], boolean() | {true, binary(), integer()}) - -> {ok, rebar_state:t()}. + -> {ok, rebar_state:t()} | {error, string()}. handle_deps(State, [], _) -> {ok, State}; handle_deps(State, Deps, Update) -> @@ -116,10 +123,11 @@ handle_deps(State, Deps, Update) -> SrcDeps), %% Fetch transitive src deps - State2 = update_src_deps(0, State1, Update), + {State2, _Seen} = update_src_deps(0, State1, Update, sets:new()), + Solved = case rebar_state:pkg_deps(State2) of [] -> %% No pkg deps - []; + []; PkgDeps1 -> %% Find pkg deps needed {ok, S} = rlx_depsolver:solve(Graph, PkgDeps1), @@ -128,12 +136,12 @@ handle_deps(State, Deps, Update) -> AppInfo <- package_to_app(DepsDir ,Packages ,Pkg), - maybe_fetch(AppInfo, Update)] + maybe_fetch(AppInfo, Update, sets:new())] end, AllDeps = lists:ukeymerge(2 - ,lists:ukeysort(2, rebar_state:src_apps(State2)) - ,lists:ukeysort(2, Solved)), + ,lists:ukeysort(2, rebar_state:src_apps(State2)) + ,lists:ukeysort(2, Solved)), %% Sort all apps to build order State3 = rebar_state:set(State2, all_deps, AllDeps), {ok, State3}. @@ -154,37 +162,42 @@ package_to_app(DepsDir, Packages, Pkg={_, Vsn}) -> {ok, AppInfo} = rebar_app_info:new(Name, FmtVsn), AppInfo1 = rebar_app_info:deps(AppInfo, PkgDeps), AppInfo2 = rebar_app_info:dir(AppInfo1, get_deps_dir(DepsDir, Name)), - [rebar_app_info:source(AppInfo2, Link)] + [rebar_app_info:source(AppInfo2, {pkg, Name, FmtVsn, Link})] end. --spec update_src_deps(integer(), rebar_state:t(), boolean()) -> rebar_state:t(). -update_src_deps(Level, State, Update) -> +-spec update_src_deps(integer(), rebar_state:t(), boolean(), sets:set(binary())) -> + {rebar_state:t(), [binary()]}. +update_src_deps(Level, State, Update, Seen) -> SrcDeps = rebar_state:src_deps(State), - case lists:foldl(fun(AppInfo, {SrcDepsAcc, PkgDepsAcc, StateAcc}) -> - case Update of - {true, UpdateName, UpdateLevel} -> - handle_update(AppInfo - ,UpdateName - ,UpdateLevel - ,SrcDepsAcc - ,PkgDepsAcc - ,Level - ,StateAcc); - _ -> - maybe_fetch(AppInfo, false), - handle_dep(AppInfo - ,SrcDepsAcc - ,PkgDepsAcc - ,Level - ,StateAcc) - end - end, {[], rebar_state:pkg_deps(State), State}, SrcDeps) of - {[], NewPkgDeps, State1} -> - rebar_state:pkg_deps(State1, NewPkgDeps); - {NewSrcDeps, NewPkgDeps, State1} -> + case lists:foldl(fun(AppInfo, {SrcDepsAcc, PkgDepsAcc, StateAcc, SeenAcc}) -> + SeenAcc1 = sets:add_element(rebar_app_info:name(AppInfo), SeenAcc), + {SrcDepsAcc1, PkgDepsAcc1, StateAcc1} = + case Update of + {true, UpdateName, UpdateLevel} -> + handle_update(AppInfo + ,UpdateName + ,UpdateLevel + ,SrcDepsAcc + ,PkgDepsAcc + ,Level + ,StateAcc); + _ -> + maybe_fetch(AppInfo, false, SeenAcc), + handle_dep(AppInfo + ,SrcDepsAcc + ,PkgDepsAcc + ,Level + ,StateAcc) + + end, + {SrcDepsAcc1, PkgDepsAcc1, StateAcc1, SeenAcc1} + end, {[], rebar_state:pkg_deps(State), State, Seen}, SrcDeps) of + {[], NewPkgDeps, State1, Seen1} -> + {rebar_state:pkg_deps(State1, NewPkgDeps), Seen1}; + {NewSrcDeps, NewPkgDeps, State1, Seen1} -> State2 = rebar_state:pkg_deps(State1, NewPkgDeps), State3 = rebar_state:src_deps(State2, NewSrcDeps), - update_src_deps(Level+1, State3, Update) + update_src_deps(Level+1, State3, Update, Seen1) end. handle_update(AppInfo, UpdateName, UpdateLevel, SrcDeps, PkgDeps, Level, State) -> @@ -194,7 +207,7 @@ handle_update(AppInfo, UpdateName, UpdateLevel, SrcDeps, PkgDeps, Level, State) case UpdateLevel < DepLevel orelse Name =:= UpdateName of true -> - case maybe_fetch(AppInfo, true) of + case maybe_fetch(AppInfo, true, []) of true -> handle_dep(AppInfo ,SrcDeps @@ -228,8 +241,9 @@ handle_dep(DepsDir, AppInfo) -> {SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps), {AppInfo1, SrcDeps, PkgDeps}. --spec maybe_fetch(rebar_app_info:t(), boolean() | {true, binary(), integer()}) -> boolean(). -maybe_fetch(AppInfo, Update) -> +-spec maybe_fetch(rebar_app_info:t(), boolean() | {true, binary(), integer()}, + sets:set(binary())) -> boolean(). +maybe_fetch(AppInfo, Update, Seen) -> AppDir = ec_cnv:to_list(rebar_app_info:dir(AppInfo)), Apps = rebar_app_discover:find_apps(["_checkouts"], all), case rebar_app_utils:find(rebar_app_info:name(AppInfo), Apps) of @@ -253,10 +267,31 @@ maybe_fetch(AppInfo, Update) -> true -> ?INFO("Fetching ~s~n", [rebar_app_info:name(AppInfo)]), Source = rebar_app_info:source(AppInfo), - rebar_fetch:download_source(AppDir, Source), - true; + case rebar_fetch:download_source(AppDir, Source) of + {error, Reason} -> + throw(Reason); + Result -> + Result + end; _ -> - false + case sets:is_element(rebar_app_info:name(AppInfo), Seen) of + true -> + false; + false -> + Source = rebar_app_info:source(AppInfo), + case rebar_fetch:needs_update(AppDir, Source) of + true -> + ?INFO("Updating ~s~n", [rebar_app_info:name(AppInfo)]), + case rebar_fetch:download_source(AppDir, Source) of + {error, Reason} -> + throw(Reason); + Result -> + Result + end; + false -> + false + end + end end end. diff --git a/src/rebar_prv_tar.erl b/src/rebar_prv_tar.erl index 04a1b33..03117ff 100644 --- a/src/rebar_prv_tar.erl +++ b/src/rebar_prv_tar.erl @@ -34,9 +34,9 @@ init(State) -> do(State) -> case rebar_state:get(State, relx, []) of [] -> - relx:main(["release tar"]); + relx:main(["release", "tar"]); Config -> - relx:main([{config, Config}], ["release tar"]) + relx:main([{config, Config}], ["release", "tar"]) end, {ok, State}. diff --git a/src/rebar_prv_upgrade.erl b/src/rebar_prv_upgrade.erl index 1668e1c..8c1c84e 100644 --- a/src/rebar_prv_upgrade.erl +++ b/src/rebar_prv_upgrade.erl @@ -12,7 +12,7 @@ -include("rebar.hrl"). -define(PROVIDER, upgrade). --define(DEPS, [lock]). +-define(DEPS, []). %% =================================================================== %% Public API @@ -37,14 +37,13 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> {Args, _} = rebar_state:command_parsed_args(State), - Name = proplists:get_value(package, Args), - ?INFO("Updating ~s~n", [Name]), + Name = ec_cnv:to_binary(proplists:get_value(package, Args)), Locks = rebar_state:get(State, locks, []), - case lists:keyfind(ec_cnv:to_binary(Name), 1, Locks) of + case lists:keyfind(Name, 1, Locks) of {_, _, _, Level} -> Deps = rebar_state:get(State, deps), - Dep = lists:keyfind(list_to_atom(Name), 1, Deps), - rebar_prv_install_deps:handle_deps(State, [Dep], {true, ec_cnv:to_binary(Name), Level}), + Dep = lists:keyfind(binary_to_atom(Name, utf8), 1, Deps), + rebar_prv_install_deps:handle_deps(State, [Dep], {true, Name, Level}), {ok, State}; _ -> {error, io_lib:format("No such dependency ~s~n", [Name])} diff --git a/src/rebar_resource.erl b/src/rebar_resource.erl new file mode 100644 index 0000000..04b8d73 --- /dev/null +++ b/src/rebar_resource.erl @@ -0,0 +1,43 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(rebar_resource). + +-export([]). + +-export_types([resource/0 + ,type/0 + ,location/0 + ,ref/0]). + +-type resource() :: {type(), location(), ref()}. +-type type() :: atom(). +-type location() :: string(). +-type ref() :: any(). + +-ifdef(have_callback_support). + +%% In the case where R14 or lower is being used to compile the system +%% we need to export a behaviour info +-export([behaviour_info/1]). + +-spec behaviour_info(atom()) -> [{atom(), arity()}] | undefined. +behaviour_info(callbacks) -> + [{lock, 2}, + {download, 2}, + {needs_update,2}, + {make_vsn, 1}]; +behaviour_info(_) -> + undefined. + +-else. + +-callback lock(file:filename_all(), tuple()) -> + rebar_resource:resource(). +-callback download(file:filename_all(), tuple()) -> + {tarball, file:filename_all()} | {ok, any()} | {error, any()}. +-callback needs_update(file:filename_all(), tuple()) -> + {tarball, file:filename_all()} | {ok, any()} | {error, any()}. +-callback make_vsn(file:filename_all()) -> + {plain, string()} | {error, string()}. + +-endif. diff --git a/src/rebar_topo.erl b/src/rebar_topo.erl index de3351a..d9f426d 100644 --- a/src/rebar_topo.erl +++ b/src/rebar_topo.erl @@ -61,7 +61,7 @@ sort_apps(Apps) -> {ok, Names} -> {ok, names_to_apps(Names, Apps)}; E -> - {error, E} + E end. %% @doc Do a topological sort on the list of pairs. diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index 87387be..ae1d126 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -135,7 +135,7 @@ sh(Command0, Options0) -> ?DEBUG("sh info:\n\tcwd: ~p\n\tcmd: ~s\n", [get_cwd(), Command0]), ?DEBUG("\topts: ~p\n", [Options0]), - DefaultOptions = [use_stdout, abort_on_error], + DefaultOptions = [{use_stdout, false}, abort_on_error], Options = [expand_sh_flag(V) || V <- proplists:compact(Options0 ++ DefaultOptions)], @@ -438,13 +438,15 @@ escript_foldl(Fun, Acc, File) -> end. vcs_vsn_1(Vcs, Dir) -> - case vcs_vsn_cmd(Vcs) of + case vcs_vsn_cmd(Vcs, Dir) of {plain, VsnString} -> VsnString; {cmd, CmdString} -> vcs_vsn_invoke(CmdString, Dir); unknown -> ?ABORT("vcs_vsn: Unknown vsn format: ~p\n", [Vcs]); + {error, Reason} -> + ?ABORT("vcs_vsn: ~s\n", [Reason]); Cmd -> %% If there is a valid VCS directory in the application directory, %% use that version info @@ -471,15 +473,19 @@ vcs_vsn_1(Vcs, Dir) -> end end. -vcs_vsn_cmd(git) -> "git describe --always --tags"; -vcs_vsn_cmd(p4) -> "echo #head"; -vcs_vsn_cmd(hg) -> "hg identify -i"; -vcs_vsn_cmd(bzr) -> "bzr revno"; -vcs_vsn_cmd(svn) -> "svnversion"; -vcs_vsn_cmd(fossil) -> "fossil info"; -vcs_vsn_cmd({cmd, _Cmd}=Custom) -> Custom; -vcs_vsn_cmd(Version) when is_list(Version) -> {plain, Version}; -vcs_vsn_cmd(_) -> unknown. +%% 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, _) -> + Custom; +vcs_vsn_cmd(Version, _) when is_list(Version) -> + {plain, Version}; +vcs_vsn_cmd(_, _) -> + unknown. vcs_vsn_invoke(Cmd, Dir) -> {ok, VsnString} = rebar_utils:sh(Cmd, [{cd, Dir}, {use_stdout, false}]), |