diff options
68 files changed, 1516 insertions, 414 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..6d5a0c2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,31 @@ +### Pre-Check ### + +- If you are filing for a bug, please do a quick search in current issues first +- For bugs, mention if you are willing or interested in helping fix the issue +- For questions or support, it helps to include context around your project or problem +- Think of a descriptive title (more descriptive than 'feature X is broken' unless it is fully broken) + +### Environment ### + +- Add the result of `rebar3 report` to your message: + +``` +$ rebar3 report "my failing command" +... +``` + +- Verify whether the version of rebar3 you're running is the latest release (see https://github.com/erlang/rebar3/releases) +- If possible, include information about your project and its structure. Open source projects or examples are always easier to debug. + If you can provide an example code base to reproduce the issue on, we will generally be able to provide more help, and faster. + +### Current behaviour ### + +Describe the current behaviour. In case of a failure, crash, or exception, please include the result of running the command with debug information: + +``` +DEBUG=1 rebar3 <my failing command> +``` + +### Expected behaviour ### + +Describe what you expected to happen. diff --git a/.travis.yml b/.travis.yml index ab2d28d..dbb4f26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,25 @@ sudo: false language: erlang -install: "true" +install: 'true' otp_release: - - 18.0 - - 17.5 - - R16B03-1 - - R15B03 +- 19.0 +- 18.0 +- 17.5 +- R16B03-1 +- R15B03 before_script: "./bootstrap" script: "./rebar3 ct" branches: only: - - master + - master cache: directories: - - $HOME/.cache/rebar3/hex/default -before_deploy: "rm -rf !(rebar3)" + - "$HOME/.cache/rebar3/hex/default" deploy: + provider: releases + api_key: + secure: MjloYuaQF3cd3Oab57zqwPDLPqt5MDgBIrRLpXOQwNovr2tnkKd4aJK3QJ3pTxvZievjgl+qIYI1IZyjuRV37nkjAfMw14iig959wi0k8XTJoMdylVxE5X7hk4SiWhX/ycnJx3C28PPw1OitGTF76HAJDMgEelNdoNt+hvjvDEo= + file: rebar3 on: + repo: erlang/rebar3 tags: true - condition: $TRAVIS_OTP_RELEASE = R16B03-1 - provider: s3 - access_key_id: AKIAJAPYAQEFYCYSNL7Q - secret_access_key: - secure: "BUv2KQABv0Q4e8DAVNBRTc/lXHWt27yCN46Fdgo1IrcSSIiP+hq2yXzQcXLbPwkEu6pxUZQtL3mvKbt6l7uw3wFrcRfFAi1PGTITAW8MTmxtwcZIBcHSk3XOzDbkK+fYYcaddszmt7hDzzEFPtmYXiNgnaMIVeynhQLgcCcIRRQ=" - bucket: "rebar3" - skip_cleanup: true @@ -53,7 +53,7 @@ best experience you can get. ## Getting Started -A [getting started guide is maintained on the offcial documentation website](http://www.rebar3.org/docs/getting-started), +A [getting started guide is maintained on the official documentation website](http://www.rebar3.org/docs/getting-started), but installing rebar3 can be done by any of the ways described below Nightly compiled version: @@ -24,7 +24,7 @@ main(_) -> bootstrap_rebar3(), %% Build rebar.app from rebar.app.src - {ok, App} = rebar_app_info:new(rebar, "3.1.1", filename:absname("_build/default/lib/rebar/")), + {ok, App} = rebar_app_info:new(rebar, "3.3.0", filename:absname("_build/default/lib/rebar/")), rebar_otp_app:compile(rebar_state:new(), App), %% Because we are compiling files that are loaded already we want to silence @@ -133,7 +133,11 @@ get_rebar_config() -> end. get_http_vars(Scheme) -> - proplists:get_value(Scheme, get_rebar_config(), []). + OS = case os:getenv(atom_to_list(Scheme)) of + Str when is_list(Str) -> Str; + _ -> [] + end, + proplists:get_value(Scheme, get_rebar_config(), OS). set_httpc_options() -> set_httpc_options(https_proxy, get_http_vars(https_proxy)), diff --git a/pr2relnotes.py b/pr2relnotes.py deleted file mode 100755 index 7a23fda..0000000 --- a/pr2relnotes.py +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/env python - -## Install info -## $ virtualenv env -## $ source env/bin/activate -## $ pip install PyGithub -## -## Examples: -## Find the differences from last tag to current -## $ pr2relnotes.py alpha-6 HEAD - -import argparse -import re -import os -import subprocess -from github import Github -from github import GithubException - - -def dprint(*args): - if VERBOSE: - print str(args) - -def get_args(): - """ - Get command line arguments - """ - parser = argparse.ArgumentParser(description="Find the PR's between two versions") - parser.add_argument("old", help = "old version to use") - parser.add_argument("new", help = "new version to use") - parser.add_argument("-v", "--verbose", help="Enable debug output", - default=False, - action="store_true") - parser.add_argument("-f", "--file", - help="Output file to store results (default: tagdiff.md)", - default="tagdiff.md") - return parser.parse_args() - -def search_prs(log): - """ - Search lines of text for PR numbers - """ - # Find all matches using regex iterator, using the PR # as the group match - resultlist = [str(m.group(1)) for m in re.finditer(r"erge pull request #(\d+)", log)] - return sorted(resultlist) - -def get_env(env): - return os.environ[env] - -def get_formatted_issue(repo, issue, title, url): - """ - Single place to adjust formatting output of PR data - """ - # Newline support writelines() call which doesn't add newlines - # on its own - return("* {}/{}: [{}]({})\n".format(repo, issue, title.encode('utf-8'), url)) - -def gh_get_issue_output(org, repo, issuenum): - """ - Look up PR information using the GitHub api - """ - # Attempt to look up the PR, and don't take down the whole - # shebang if a API call fails - # This will fail often on forks who don't have the - # PRs numbers associated with the forked account - # Return empty string on error - try: - repoObj = gh.get_repo(org + "/" + repo) - issue = repoObj.get_issue(int(issuenum)) - title = issue.title - html_url = issue.html_url - except GithubException as e: - print "Github error({0}): {1}".format(e.status, e.data) - return "" - except: - print "Some github error" - return "" - - return(get_formatted_issue(repo, issuenum, title, html_url)) - - -def get_org(repourl): - """ - Simple function to parse the organization out of a GitHub URL - """ - dprint("Current repourl to search: " + repourl) - # GitHub URLs can be: - # http[s]://www.github.com/org/repo - # or git@github.com:/org/repo - pattern = re.compile(r"github.com[/:]+(\w+)/") - m = re.search(pattern, repourl) - # Fail fast if this is wrong so we can add a pattern to the search - if m: - return m.group(1) - else: - raise Exception("Incorrect regex pattern finding repo org") - -def get_name(repourl): - """ - Simple function to parse the repository name out of a GitHub URL - """ - dprint("Current repourl to search: " + repourl) - repo_pattern = re.compile(r"github.com[/:]\w+/(\w+)") - m = re.search(repo_pattern, repourl) - if m: - return m.group(1) - else: - raise Exception("Incorrect rexex pattern finding repo url") - -def get_repo_url_from_remote(): - """ - Function that gets the repository URL from the `git remote` listing - """ - git_remote_bytes = subprocess.check_output(["git", "remote", "-v"]) - # check_output returns the command results in raw byte format - remote_string = git_remote_bytes.decode('utf-8') - - pattern = re.compile(r"github.com[/:]\w+/\w+") - m = re.search(pattern, remote_string) - if m: - return m.group(0) - else: - raise Exception("Incorrect rexex pattern finding repo url") - -def process_log(gitlog, repo_url): - """ - Handles the processing of the gitlog and returns a list - of PRs already formatted for output - """ - pr_list = search_prs(gitlog) - repoorg = get_org(repo_url) - reponame = get_name(repo_url) - pr_buffer = [] - for issue in pr_list: - pr_buffer.append(gh_get_issue_output(repoorg, reponame, issue)) - - return pr_buffer - -def fetch_log(old_ver, new_ver): - """ - Function that processes the git log between the old and new versions - """ - dprint("Current working directory", os.getcwd()) - gitlogbytes = subprocess.check_output(["git", "log", - str(old_ver + ".." + new_ver)]) - return gitlogbytes.decode('utf-8') - - -def compare_versions(repo_url, old_ver, new_ver): - # Formatted list of all PRs for all repos - pr_out = [] - gitlog = fetch_log(old_ver, new_ver) - pr_out.extend(process_log(gitlog, repo_url)) - return pr_out - -def main(): - args = get_args() - - # Setup the GitHub object for later use - global gh - gh = Github(get_env("GHAUTH")) - - if gh == "": - raise Exception("Env var GHAUTH must be set to a valid GitHub API key") - - if args.verbose: - global VERBOSE - VERBOSE=True - - dprint("Inspecting difference in between: ", args.old, " and ", args.new) - - # Find the github URL of the repo we are operating on - repo_url = get_repo_url_from_remote() - - # Compare old and new versions - pr_list = compare_versions(repo_url, args.old, args.new) - - # Writeout PR listing - print "Writing output to file %s" % args.file - with open(args.file, 'w') as output: - output.writelines(pr_list) - - -if __name__ == "__main__": - VERBOSE=False - gh=None - topdir=os.getcwd() - main() diff --git a/pr2relnotes.sh b/pr2relnotes.sh new file mode 100755 index 0000000..28712bb --- /dev/null +++ b/pr2relnotes.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env sh + +if [ -z $1 ] +then + echo "usage: $0 <tag> [pull-request-url]" + exit 0 +fi +export url=${2:-"https://github.com/erlang/rebar3/pull/"} + +git log --merges --pretty=medium $1..HEAD | \ +awk -v url=$url ' + # first line of a merge commit entry + /^commit / {mode="new"} + + # merge commit default message + / +Merge pull request/ { + page_id=substr($4, 2, length($4)-1); + mode="started"; + next; + } + + # line of content including title + mode=="started" && / [^ ]+/ { + print "- [" substr($0, 5) "](" url page_id ")"; mode="done" + }' diff --git a/priv/templates/escript_rebar.config b/priv/templates/escript_rebar.config index 196f835..ef498a8 100644 --- a/priv/templates/escript_rebar.config +++ b/priv/templates/escript_rebar.config @@ -3,7 +3,7 @@ {escript_incl_apps, [{{name}}]}. -{escript_top_level_app, {{name}}}. +{escript_main_app, {{name}}}. {escript_name, {{name}}}. {escript_emu_args, "%%! +sbtu +A0\n"}. diff --git a/priv/templates/gitignore b/priv/templates/gitignore index ced0c5e..121a4de 100644 --- a/priv/templates/gitignore +++ b/priv/templates/gitignore @@ -13,3 +13,4 @@ erl_crash.dump .rebar logs _build +.idea diff --git a/rebar.config b/rebar.config index ea5af27..eabf3ac 100644 --- a/rebar.config +++ b/rebar.config @@ -2,16 +2,20 @@ %% ex: ts=4 sw=4 ft=erlang et {deps, [{erlware_commons, "0.21.0"}, - {ssl_verify_hostname, "1.0.5"}, + {ssl_verify_fun, "1.1.1"}, {certifi, "0.4.0"}, {providers, "1.6.0"}, {getopt, "0.8.2"}, {bbmustache, "1.0.4"}, - {relx, "3.19.0"}, + {relx, "3.21.0"}, {cf, "0.2.1"}, - {cth_readable, "1.2.2"}, + {cth_readable, "1.2.3"}, {eunit_formatters, "0.3.1"}]}. +{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)", + escriptize, + "cp $REBAR_BUILD_DIR/bin/rebar3 ./rebar3 && chmod u+x rebar3"}]}. + {escript_name, rebar3}. {escript_emu_args, "%%! +sbtu +A0\n"}. %% escript_incl_extra is for internal rebar-private use only. @@ -20,6 +24,7 @@ {"rebar/priv/templates/*", "_build/default/lib/"}]}. {erl_opts, [{platform_define, "^[0-9]+", namespaced_types}, + {platform_define, "^(19|2)", rand_only}, no_debug_info, warnings_as_errors]}. @@ -36,7 +41,7 @@ {bootstrap, []}, {dialyze, [{overrides, [{add, erlware_commons, [{erl_opts, [debug_info]}]}, - {add, ssl_verify_hostname, [{erl_opts, [debug_info]}]}, + {add, ssl_verify_fun, [{erl_opts, [debug_info]}]}, {add, certifi, [{erl_opts, [debug_info]}]}, {add, providers, [{erl_opts, [debug_info]}]}, {add, getopt, [{erl_opts, [debug_info]}]}, diff --git a/rebar.config.sample b/rebar.config.sample index cc027f9..be3da71 100644 --- a/rebar.config.sample +++ b/rebar.config.sample @@ -131,7 +131,8 @@ %% Paths to miscellaneous Erlang files to compile for an app %% without including them in its modules list {extra_src_dirs, []}. - +%% Path where custom rebar3 templates could be found +{template_dir, []}. %% == EDoc == @@ -1,10 +1,24 @@ +{"1.1.0", [{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.0.4">>},0}, {<<"certifi">>,{pkg,<<"certifi">>,<<"0.4.0">>},0}, {<<"cf">>,{pkg,<<"cf">>,<<"0.2.1">>},0}, - {<<"cth_readable">>,{pkg,<<"cth_readable">>,<<"1.2.2">>},0}, + {<<"cth_readable">>,{pkg,<<"cth_readable">>,<<"1.2.3">>},0}, {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.21.0">>},0}, {<<"eunit_formatters">>,{pkg,<<"eunit_formatters">>,<<"0.3.1">>},0}, {<<"getopt">>,{pkg,<<"getopt">>,<<"0.8.2">>},0}, {<<"providers">>,{pkg,<<"providers">>,<<"1.6.0">>},0}, - {<<"relx">>,{pkg,<<"relx">>,<<"3.19.0">>},0}, - {<<"ssl_verify_hostname">>,{pkg,<<"ssl_verify_hostname">>,<<"1.0.5">>},0}]. + {<<"relx">>,{pkg,<<"relx">>,<<"3.21.0">>},0}, + {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.1">>},0}]}. +[ +{pkg_hash,[ + {<<"bbmustache">>, <<"7BA94F971C5AFD7B6617918A4BB74705E36CAB36EB84B19B6A1B7EE06427AA38">>}, + {<<"certifi">>, <<"A7966EFB868B179023618D29A407548F70C52466BF1849B9E8EBD0E34B7EA11F">>}, + {<<"cf">>, <<"69D0B1349FD4D7D4DC55B7F407D29D7A840BF9A1EF5AF529F1EBE0CE153FC2AB">>}, + {<<"cth_readable">>, <<"293120673DFF82F0768612C5282E35C40CACC1B6F94FE99077438FD3749D0E27">>}, + {<<"erlware_commons">>, <<"A04433071AD7D112EDEFC75AC77719DD3E6753E697AC09428FC83D7564B80B15">>}, + {<<"eunit_formatters">>, <<"7A6FC351EB5B873E2356B8852EB751E20C13A72FBCA03393CF682B8483509573">>}, + {<<"getopt">>, <<"B17556DB683000BA50370B16C0619DF1337E7AF7ECBF7D64FBF8D1D6BCE3109B">>}, + {<<"providers">>, <<"DB0E2F9043AE60C0155205FCD238D68516331D0E5146155E33D1E79DC452964A">>}, + {<<"relx">>, <<"91E1EA9F09B4EDFDA8461901F4B5C5E0226E43EC161E147EEAB29F7761DF6EB5">>}, + {<<"ssl_verify_fun">>, <<"28A4D65B7F59893BC2C7DE786DEC1E1555BD742D336043FE644AE956C3497FBE">>}]} +]. diff --git a/src/rebar.app.src b/src/rebar.app.src index 5934f38..5b735cf 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -3,7 +3,7 @@ {application, rebar, [{description, "Rebar: Erlang Build Tool"}, - {vsn, "3.1.1"}, + {vsn, "git"}, {modules, []}, {registered, []}, {applications, [kernel, @@ -23,7 +23,7 @@ erlware_commons, providers, bbmustache, - ssl_verify_hostname, + ssl_verify_fun, certifi, cth_readable, relx, diff --git a/src/rebar.hrl b/src/rebar.hrl index 0b7f0b1..f96ed5e 100644 --- a/src/rebar.hrl +++ b/src/rebar.hrl @@ -10,6 +10,7 @@ -define(INFO(Str, Args), rebar_log:log(info, Str, Args)). -define(WARN(Str, Args), rebar_log:log(warn, Str, Args)). -define(ERROR(Str, Args), rebar_log:log(error, Str, Args)). +-define(CRASHDUMP(Str, Args), rebar_log:crashdump(Str, Args)). -define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))). @@ -22,6 +23,7 @@ -define(DEFAULT_TEST_DEPS_DIR, "test/lib"). -define(DEFAULT_RELEASE_DIR, "rel"). -define(DEFAULT_CONFIG_FILE, "rebar.config"). +-define(CONFIG_VERSION, "1.1.0"). -define(DEFAULT_CDN, "https://repo.hex.pm/"). -define(REMOTE_PACKAGE_DIR, "tarballs"). -define(REMOTE_REGISTRY_FILE, "registry.ets.gz"). diff --git a/src/rebar3.erl b/src/rebar3.erl index ff0ab6a..d3ea15f 100644 --- a/src/rebar3.erl +++ b/src/rebar3.erl @@ -221,10 +221,10 @@ set_options(State, {Options, NonOptArgs}) -> %% log_level() -> case os:getenv("QUIET") of - false -> + Q when Q == false; Q == "" -> DefaultLevel = rebar_log:default_level(), case os:getenv("DEBUG") of - false -> + D when D == false; D == "" -> DefaultLevel; _ -> DefaultLevel + 3 @@ -272,20 +272,22 @@ handle_error({error, rebar_abort}) -> handle_error({error, {Module, Reason}}) -> case code:which(Module) of non_existing -> - ?ERROR("Uncaught error in rebar_core. Run with DEBUG=1 to see stacktrace", []), + ?CRASHDUMP("~p: ~p~n~p~n~n", [Module, Reason, erlang:get_stacktrace()]), + ?ERROR("Uncaught error in rebar_core. Run with DEBUG=1 to stacktrace or consult rebar3.crashdump", []), ?DEBUG("Uncaught error: ~p ~p", [Module, Reason]), ?INFO("When submitting a bug report, please include the output of `rebar3 report \"your command\"`", []); _ -> - ?ERROR(Module:format_error(Reason), []) + ?ERROR("~s", [Module:format_error(Reason)]) end, erlang:halt(1); handle_error({error, Error}) when is_list(Error) -> - ?ERROR(Error, []), + ?ERROR("~s", [Error]), erlang:halt(1); handle_error(Error) -> %% Nothing should percolate up from rebar_core; %% Dump this error to console - ?ERROR("Uncaught error in rebar_core. Run with DEBUG=1 to see stacktrace", []), + ?CRASHDUMP("Error: ~p~n~p~n~n", [Error, erlang:get_stacktrace()]), + ?ERROR("Uncaught error in rebar_core. Run with DEBUG=1 to see stacktrace or consult rebar3.crashdump", []), ?DEBUG("Uncaught error: ~p", [Error]), case erlang:get_stacktrace() of [] -> ok; diff --git a/src/rebar_app_utils.erl b/src/rebar_app_utils.erl index d3ef841..50c6314 100644 --- a/src/rebar_app_utils.erl +++ b/src/rebar_app_utils.erl @@ -119,26 +119,33 @@ parse_dep(Dep, Parent, DepsDir, State, Locks, Level) -> parse_dep(Parent, {Name, Vsn, {pkg, PkgName}}, DepsDir, IsLock, State) -> {PkgName1, PkgVsn} = {ec_cnv:to_binary(PkgName), ec_cnv:to_binary(Vsn)}, - dep_to_app(Parent, DepsDir, Name, PkgVsn, {pkg, PkgName1, PkgVsn}, IsLock, State); + dep_to_app(Parent, DepsDir, Name, PkgVsn, {pkg, PkgName1, PkgVsn, undefined}, IsLock, State); parse_dep(Parent, {Name, {pkg, PkgName}}, DepsDir, IsLock, State) -> %% Package dependency with different package name from app name - dep_to_app(Parent, DepsDir, Name, undefined, {pkg, ec_cnv:to_binary(PkgName), undefined}, IsLock, State); + dep_to_app(Parent, DepsDir, Name, undefined, {pkg, ec_cnv:to_binary(PkgName), undefined, undefined}, IsLock, State); parse_dep(Parent, {Name, Vsn}, DepsDir, IsLock, State) when is_list(Vsn); is_binary(Vsn) -> %% Versioned Package dependency {PkgName, PkgVsn} = {ec_cnv:to_binary(Name), ec_cnv:to_binary(Vsn)}, - dep_to_app(Parent, DepsDir, PkgName, PkgVsn, {pkg, PkgName, PkgVsn}, IsLock, State); + dep_to_app(Parent, DepsDir, PkgName, PkgVsn, {pkg, PkgName, PkgVsn, undefined}, IsLock, State); parse_dep(Parent, Name, DepsDir, IsLock, State) when is_atom(Name); is_binary(Name) -> %% Unversioned package dependency - dep_to_app(Parent, DepsDir, ec_cnv:to_binary(Name), undefined, {pkg, ec_cnv:to_binary(Name), undefined}, IsLock, State); + dep_to_app(Parent, DepsDir, ec_cnv:to_binary(Name), undefined, {pkg, ec_cnv:to_binary(Name), undefined, undefined}, IsLock, State); parse_dep(Parent, {Name, Source}, DepsDir, IsLock, State) when is_tuple(Source) -> dep_to_app(Parent, DepsDir, Name, [], Source, IsLock, State); parse_dep(Parent, {Name, _Vsn, Source}, DepsDir, IsLock, State) when is_tuple(Source) -> dep_to_app(Parent, DepsDir, Name, [], Source, IsLock, State); -parse_dep(Parent, {Name, _Vsn, Source, Opts}, DepsDir, IsLock, State) when is_tuple(Source) -> +parse_dep(Parent, {Name, _Vsn, Source, Opts}, DepsDir, IsLock, State) when is_tuple(Source), + is_list(Opts) -> + ?WARN("Dependency option list ~p in ~p is not supported and will be ignored", [Opts, Name]), + dep_to_app(Parent, DepsDir, Name, [], Source, IsLock, State); +parse_dep(Parent, {Name, Source, Opts}, DepsDir, IsLock, State) when is_tuple(Source), + is_list(Opts) -> ?WARN("Dependency option list ~p in ~p is not supported and will be ignored", [Opts, Name]), dep_to_app(Parent, DepsDir, Name, [], Source, IsLock, State); parse_dep(Parent, {Name, {pkg, PkgName, Vsn}, Level}, DepsDir, IsLock, State) when is_integer(Level) -> - dep_to_app(Parent, DepsDir, Name, Vsn, {pkg, PkgName, Vsn}, IsLock, State); + dep_to_app(Parent, DepsDir, Name, Vsn, {pkg, PkgName, Vsn, undefined}, IsLock, State); +parse_dep(Parent, {Name, {pkg, PkgName, Vsn, Hash}, Level}, DepsDir, IsLock, State) when is_integer(Level) -> + dep_to_app(Parent, DepsDir, Name, Vsn, {pkg, PkgName, Vsn, Hash}, IsLock, State); parse_dep(Parent, {Name, Source, Level}, DepsDir, IsLock, State) when is_tuple(Source) , is_integer(Level) -> dep_to_app(Parent, DepsDir, Name, [], Source, IsLock, State); @@ -170,17 +177,24 @@ dep_to_app(Parent, DepsDir, Name, Vsn, Source, IsLock, State) -> AppInfo5 = rebar_app_info:profiles(AppInfo4, [default]), rebar_app_info:is_lock(AppInfo5, IsLock). -update_source(AppInfo, {pkg, PkgName, PkgVsn}, State) -> +update_source(AppInfo, {pkg, PkgName, PkgVsn, Hash}, State) -> {PkgName1, PkgVsn1} = case PkgVsn of undefined -> get_package(PkgName, "0", State); <<"~>", Vsn/binary>> -> - [Vsn1] = binary:split(Vsn, [<<" ">>], [trim_all, global]), + [Vsn1] = [X || X <- binary:split(Vsn, [<<" ">>], [global]), X =/= <<>>], get_package(PkgName, Vsn1, State); _ -> {PkgName, PkgVsn} end, - AppInfo1 = rebar_app_info:source(AppInfo, {pkg, PkgName1, PkgVsn1}), + %% store the expected hash for the dependency + Hash1 = case Hash of + undefined -> % unknown, define the hash since we know the dep + rebar_packages:registry_checksum({pkg, PkgName1, PkgVsn1, Hash}, State); + _ -> % keep as is + Hash + end, + AppInfo1 = rebar_app_info:source(AppInfo, {pkg, PkgName1, PkgVsn1, Hash1}), Deps = rebar_packages:deps(PkgName1 ,PkgVsn1 ,State), diff --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl index 31292af..5d54057 100644 --- a/src/rebar_base_compiler.erl +++ b/src/rebar_base_compiler.erl @@ -32,7 +32,11 @@ run/7, run/8, ok_tuple/2, - error_tuple/4]). + error_tuple/4, + format_error_source/2]). + +-define(DEFAULT_COMPILER_SOURCE_FORMAT, relative). + %% =================================================================== %% Public API @@ -76,6 +80,28 @@ error_tuple(Source, Es, Ws, Opts) -> {error, format_errors(Source, Es), format_warnings(Source, Ws, Opts)}. +format_error_source(Path, Opts) -> + Type = case rebar_opts:get(Opts, compiler_source_format, + ?DEFAULT_COMPILER_SOURCE_FORMAT) of + V when V == absolute; V == relative; V == build -> + V; + Other -> + ?WARN("Invalid argument ~p for compiler_source_format - " + "assuming ~s~n", [Other, ?DEFAULT_COMPILER_SOURCE_FORMAT]), + ?DEFAULT_COMPILER_SOURCE_FORMAT + end, + case Type of + absolute -> resolve_linked_source(Path); + build -> Path; + relative -> + Cwd = rebar_dir:get_cwd(), + rebar_dir:make_relative_path(resolve_linked_source(Path), Cwd) + end. + +resolve_linked_source(Src) -> + {Dir, Base} = rebar_file_utils:split_dirname(Src), + filename:join(rebar_file_utils:resolve_link(Dir), Base). + %% =================================================================== %% Internal functions %% =================================================================== @@ -114,7 +140,8 @@ compile_each([Source | Rest], Config, CompileFn) -> skipped -> ?DEBUG("~sSkipped ~s", [rebar_utils:indent(1), filename:basename(Source)]); Error -> - ?ERROR("Compiling ~s failed", [Source]), + NewSource = format_error_source(Source, Config), + ?ERROR("Compiling ~s failed", [NewSource]), maybe_report(Error), ?DEBUG("Compilation failed: ~p", [Error]), ?FAIL @@ -128,7 +155,13 @@ format_warnings(Source, Warnings) -> format_warnings(Source, Warnings, []). format_warnings(Source, Warnings, Opts) -> - Prefix = case lists:member(warnings_as_errors, Opts) of + %% `Opts' can be passed in both as a list or a dictionary depending + %% on whether the first call to rebar_erlc_compiler was done with + %% the type `rebar_dict()' or `rebar_state:t()'. + LookupFn = if is_list(Opts) -> fun lists:member/2 + ; true -> fun dict:is_key/2 + end, + Prefix = case LookupFn(warnings_as_errors, Opts) of true -> ""; false -> "Warning: " end, @@ -153,12 +186,12 @@ format_errors(_MainSource, Extra, Errors) -> end || {Source, Descs} <- Errors]. -format_error(AbsSource, Extra, {{Line, Column}, Mod, Desc}) -> +format_error(Source, Extra, {{Line, Column}, Mod, Desc}) -> ErrorDesc = Mod:format_error(Desc), - ?FMT("~s:~w:~w: ~s~s~n", [AbsSource, Line, Column, Extra, ErrorDesc]); -format_error(AbsSource, Extra, {Line, Mod, Desc}) -> + ?FMT("~s:~w:~w: ~s~s~n", [Source, Line, Column, Extra, ErrorDesc]); +format_error(Source, Extra, {Line, Mod, Desc}) -> ErrorDesc = Mod:format_error(Desc), - ?FMT("~s:~w: ~s~s~n", [AbsSource, Line, Extra, ErrorDesc]); -format_error(AbsSource, Extra, {Mod, Desc}) -> + ?FMT("~s:~w: ~s~s~n", [Source, Line, Extra, ErrorDesc]); +format_error(Source, Extra, {Mod, Desc}) -> ErrorDesc = Mod:format_error(Desc), - ?FMT("~s: ~s~s~n", [AbsSource, Extra, ErrorDesc]). + ?FMT("~s: ~s~s~n", [Source, Extra, ErrorDesc]). diff --git a/src/rebar_config.erl b/src/rebar_config.erl index 8d7bcf4..b50c030 100644 --- a/src/rebar_config.erl +++ b/src/rebar_config.erl @@ -56,35 +56,105 @@ consult_lock_file(File) -> [] -> []; [Locks] when is_list(Locks) -> % beta lock file - Locks; + read_attrs(beta, Locks, []); [{Vsn, Locks}|Attrs] when is_list(Locks) -> % versioned lock file - %% Make sure the warning above is to be shown whenever a version - %% newer than the current one is being used, as we can't parse - %% all the contents of the lock file properly. + %% Because this is the first version of rebar3 to introduce a lock + %% file, all versionned lock files with a different versions have + %% to be newer. + case Vsn of + ?CONFIG_VERSION -> + ok; + _ -> + %% Make sure the warning below is to be shown whenever a version + %% newer than the current one is being used, as we can't parse + %% all the contents of the lock file properly. + warn_vsn_once() + end, + read_attrs(Vsn, Locks, Attrs) + end. + +warn_vsn_once() -> + Warn = application:get_env(rebar, warn_config_vsn) =/= {ok, false}, + application:set_env(rebar, warn_config_vsn, false), + case Warn of + false -> ok; + true -> ?WARN("Rebar3 detected a lock file from a newer version. " "It will be loaded in compatibility mode, but important " "information may be missing or lost. It is recommended to " - "upgrade Rebar3.", []), - read_attrs(Vsn, Locks, Attrs) + "upgrade Rebar3.", []) end. + write_lock_file(LockFile, Locks) -> - NewLocks = write_attrs(Locks), + {NewLocks, Attrs} = write_attrs(Locks), %% Write locks in the beta format, at least until it's been long %% enough we can start modifying the lock format. - file:write_file(LockFile, io_lib:format("~p.~n", [NewLocks])). + case Attrs of + [] -> % write the old beta copy to avoid changes + file:write_file(LockFile, io_lib:format("~p.~n", [NewLocks])); + _ -> + file:write_file(LockFile, + io_lib:format("{~p,~n~p}.~n[~n~s~n].~n", + [?CONFIG_VERSION, NewLocks, + format_attrs(Attrs)])) + end. + +%% Attributes have a special formatting to ensure there's only one per +%% line in terms of pkg_hash, so we disturb source diffing as little +%% as possible. +format_attrs([]) -> []; +format_attrs([{pkg_hash, Vals}|T]) -> + [io_lib:format("{pkg_hash,[~n",[]), format_hashes(Vals), "]}", + maybe_comma(T) | format_attrs(T)]; +format_attrs([H|T]) -> + [io_lib:format("~p~s", [H, maybe_comma(T)]) | format_attrs(T)]. + +format_hashes([]) -> []; +format_hashes([{Pkg,Hash}|T]) -> + [" {", io_lib:format("~p",[Pkg]), ", ", io_lib:format("~p", [Hash]), "}", + maybe_comma(T) | format_hashes(T)]. + +maybe_comma([]) -> ""; +maybe_comma([_|_]) -> io_lib:format(",~n", []). -read_attrs(_Vsn, Locks, _Attrs) -> +read_attrs(_Vsn, Locks, Attrs) -> %% Beta copy does not know how to expand attributes, but %% is ready to support it. - Locks. + expand_locks(Locks, extract_pkg_hashes(Attrs)). + +extract_pkg_hashes(Attrs) -> + Props = case Attrs of + [First|_] -> First; + [] -> [] + end, + proplists:get_value(pkg_hash, Props, []). + +expand_locks([], _Hashes) -> + []; +expand_locks([{Name, {pkg,PkgName,Vsn}, Lvl} | Locks], Hashes) -> + Hash = proplists:get_value(Name, Hashes), + [{Name, {pkg,PkgName,Vsn,Hash}, Lvl} | expand_locks(Locks, Hashes)]; +expand_locks([Lock|Locks], Hashes) -> + [Lock | expand_locks(Locks, Hashes)]. write_attrs(Locks) -> %% No attribute known that needs to be taken out of the structure, %% just return terms as is. - Locks. - + {NewLocks, Hashes} = split_locks(Locks, [], []), + case Hashes of + [] -> {NewLocks, []}; + _ -> {NewLocks, [{pkg_hash, lists:sort(Hashes)}]} + end. +split_locks([], Locks, Hashes) -> + {lists:reverse(Locks), Hashes}; +split_locks([{Name, {pkg,PkgName,Vsn,undefined}, Lvl} | Locks], LAcc, HAcc) -> + split_locks(Locks, [{Name,{pkg,PkgName,Vsn},Lvl}|LAcc], HAcc); +split_locks([{Name, {pkg,PkgName,Vsn,Hash}, Lvl} | Locks], LAcc, HAcc) -> + split_locks(Locks, [{Name,{pkg,PkgName,Vsn},Lvl}|LAcc], [{Name, Hash}|HAcc]); +split_locks([Lock|Locks], LAcc, HAcc) -> + split_locks(Locks, [Lock|LAcc], HAcc). consult_file(File) -> Terms = consult_file_(File), @@ -180,6 +250,24 @@ check_newly_added({Name, _, Source}, LockedDeps) -> check_newly_added(Dep, LockedDeps) -> check_newly_added_(Dep, LockedDeps). +%% get [raw] deps out of the way +check_newly_added_({Name, Source, Opts}, LockedDeps) when is_tuple(Source), + is_list(Opts) -> + case check_newly_added_(Name, LockedDeps) of + {true, Name1} -> + {true, {Name1, Source}}; + false -> + false + end; +check_newly_added_({Name,_Vsn,Source,Opts}, LockedDeps) when is_tuple(Source), + is_list(Opts) -> + case check_newly_added_(Name, LockedDeps) of + {true, Name1} -> + {true, {Name1, Source}}; + false -> + false + end; +%% and on to regular deps check_newly_added_({Name, Vsn, Source}, LockedDeps) -> case check_newly_added_(Name, LockedDeps) of {true, Name1} -> diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl index 3480cf6..167f2bb 100644 --- a/src/rebar_erlc_compiler.erl +++ b/src/rebar_erlc_compiler.erl @@ -54,7 +54,6 @@ -define(DEFAULT_OUTDIR, "ebin"). -define(RE_PREFIX, "^[^._]"). - %% =================================================================== %% Public API %% =================================================================== @@ -201,7 +200,7 @@ compile_dirs(RebarOpts, BaseDir, SrcDirs, OutDir, Opts) -> ok = filelib:ensure_dir(filename:join(OutDir, "dummy.beam")), true = code:add_patha(filename:absname(OutDir)), - G = init_erlcinfo(proplists:get_all_values(i, ErlOpts), AllErlFiles, BaseDir, OutDir), + G = init_erlcinfo(include_abs_dirs(ErlOpts, BaseDir), AllErlFiles, BaseDir, OutDir), NeededErlFiles = needed_files(G, ErlOpts, BaseDir, OutDir, AllErlFiles), {ErlFirstFiles, ErlOptsFirst} = erl_first_files(RebarOpts, ErlOpts, BaseDir, NeededErlFiles), @@ -308,6 +307,7 @@ needed_files(G, ErlOpts, Dir, OutDir, SourceFiles) -> ,{i, Dir}] ++ ErlOpts, digraph:vertex(G, Source) > {Source, filelib:last_modified(Target)} orelse opts_changed(AllOpts, TargetBase) + orelse erl_compiler_opts_set() end, SourceFiles). maybe_rm_beam_and_edge(G, OutDir, Source) -> @@ -340,6 +340,12 @@ compile_info(Target) -> {error, Reason} end. +erl_compiler_opts_set() -> + case os:getenv("ERL_COMPILER_OPTIONS") of + false -> false; + _ -> true + end. + erlcinfo_file(Dir) -> filename:join(rebar_dir:local_cache_dir(Dir), ?ERLCINFO_FILE). @@ -381,7 +387,7 @@ maybe_rm_beams_and_edges(G, Dir, Files) -> source_and_include_dirs(InclDirs, Erls) -> SourceDirs = lists:map(fun filename:dirname/1, Erls), - lists:usort(["include" | InclDirs ++ SourceDirs]). + lists:usort(InclDirs ++ SourceDirs). update_erlcinfo(G, Dirs, Source) -> case digraph:vertex(G, Source) of @@ -497,10 +503,9 @@ expand_file_names(Files, Dirs) -> end end, Files). - -spec internal_erl_compile(rebar_dict(), file:filename(), file:filename(), file:filename(), list()) -> ok | {ok, any()} | {error, any(), any()}. -internal_erl_compile(_Opts, Dir, Module, OutDir, ErlOpts) -> +internal_erl_compile(Opts, Dir, Module, OutDir, ErlOpts) -> Target = target_base(OutDir, Module) ++ ".beam", ok = filelib:ensure_dir(Target), AllOpts = [{outdir, filename:dirname(Target)}] ++ ErlOpts ++ @@ -509,11 +514,21 @@ internal_erl_compile(_Opts, Dir, Module, OutDir, ErlOpts) -> {ok, _Mod} -> ok; {ok, _Mod, Ws} -> - rebar_base_compiler:ok_tuple(Module, Ws); + FormattedWs = format_error_sources(Ws, Opts), + rebar_base_compiler:ok_tuple(Module, FormattedWs); {error, Es, Ws} -> - rebar_base_compiler:error_tuple(Module, Es, Ws, AllOpts) + error_tuple(Module, Es, Ws, AllOpts, Opts) end. +error_tuple(Module, Es, Ws, AllOpts, Opts) -> + FormattedEs = format_error_sources(Es, Opts), + FormattedWs = format_error_sources(Ws, Opts), + rebar_base_compiler:error_tuple(Module, FormattedEs, FormattedWs, AllOpts). + +format_error_sources(Es, Opts) -> + [{rebar_base_compiler:format_error_source(Src, Opts), Desc} + || {Src, Desc} <- Es]. + target_base(OutDir, Source) -> filename:join(OutDir, filename:basename(Source, ".erl")). @@ -722,6 +737,10 @@ outdir(RebarOpts) -> ErlOpts = rebar_opts:erl_opts(RebarOpts), proplists:get_value(outdir, ErlOpts, ?DEFAULT_OUTDIR). +include_abs_dirs(ErlOpts, BaseDir) -> + InclDirs = ["include"|proplists:get_all_values(i, ErlOpts)], + lists:map(fun(Incl) -> filename:join([BaseDir, Incl]) end, InclDirs). + parse_opts(Opts) -> parse_opts(Opts, #compile_opts{}). parse_opts([], CompileOpts) -> CompileOpts; diff --git a/src/rebar_fetch.erl b/src/rebar_fetch.erl index b80c125..47bfe1d 100644 --- a/src/rebar_fetch.erl +++ b/src/rebar_fetch.erl @@ -67,6 +67,12 @@ needs_update(AppDir, Source, State) -> format_error({bad_download, CachePath}) -> io_lib:format("Download of package does not match md5sum from server: ~s", [CachePath]); +format_error({unexpected_hash, CachePath, Expected, Found}) -> + io_lib:format("The checksum for package at ~s (~s) does not match the " + "checksum previously locked (~s). Either unlock or " + "upgrade the package, or make sure you fetched it from " + "the same index from which it was initially fetched.", + [CachePath, Found, Expected]); format_error({failed_extract, CachePath}) -> io_lib:format("Failed to extract package: ~s", [CachePath]); format_error({bad_etag, Source}) -> diff --git a/src/rebar_file_utils.erl b/src/rebar_file_utils.erl index 0f84520..104c047 100644 --- a/src/rebar_file_utils.erl +++ b/src/rebar_file_utils.erl @@ -27,6 +27,7 @@ -module(rebar_file_utils). -export([try_consult/1, + consult_config/2, format_error/1, symlink_or_copy/2, rm_rf/1, @@ -39,7 +40,9 @@ reset_dir/1, touch/1, path_from_ancestor/2, - canonical_path/1]). + canonical_path/1, + resolve_link/1, + split_dirname/1]). -include("rebar.hrl"). @@ -61,6 +64,20 @@ try_consult(File) -> throw(?PRV_ERROR({bad_term_file, File, Reason})) end. +-spec consult_config(rebar_state:t(), string()) -> [[tuple()]]. +consult_config(State, Filename) -> + Fullpath = filename:join(rebar_dir:root_dir(State), Filename), + ?DEBUG("Loading configuration from ~p", [Fullpath]), + Config = case try_consult(Fullpath) of + [T] -> T; + [] -> [] + end, + SubConfigs = [consult_config(State, Entry ++ ".config") || + Entry <- Config, is_list(Entry) + ], + + [Config | lists:merge(SubConfigs)]. + format_error({bad_term_file, AppFile, Reason}) -> io_lib:format("Error reading file ~s: ~s", [AppFile, file:format_error(Reason)]). @@ -160,11 +177,11 @@ mv(Source, Dest) -> {win32, _} -> Cmd = case filelib:is_dir(Source) of true -> - ?FMT("robocopy /move /s \"~s\" \"~s\" 1> nul", + ?FMT("robocopy /move /e \"~s\" \"~s\" 1> nul", [rebar_utils:escape_double_quotes(filename:nativename(Source)), rebar_utils:escape_double_quotes(filename:nativename(Dest))]); false -> - ?FMT("robocopy /move /s \"~s\" \"~s\" \"~s\" 1> nul", + ?FMT("robocopy /move /e \"~s\" \"~s\" \"~s\" 1> nul", [rebar_utils:escape_double_quotes(filename:nativename(filename:dirname(Source))), rebar_utils:escape_double_quotes(filename:nativename(Dest)), rebar_utils:escape_double_quotes(filename:basename(Source))]) @@ -273,6 +290,22 @@ canonical_path([_|Acc], [".."|Rest]) -> canonical_path(Acc, Rest); canonical_path([], [".."|Rest]) -> canonical_path([], Rest); canonical_path(Acc, [Component|Rest]) -> canonical_path([Component|Acc], Rest). +%% returns canonical target of path if path is a link, otherwise returns path +-spec resolve_link(string()) -> string(). + +resolve_link(Path) -> + case file:read_link(Path) of + {ok, Target} -> + canonical_path(filename:absname(Target, filename:dirname(Path))); + {error, _} -> Path + end. + +%% splits a path into dirname and basename +-spec split_dirname(string()) -> {string(), string()}. + +split_dirname(Path) -> + {filename:dirname(Path), filename:basename(Path)}. + %% =================================================================== %% Internal functions %% =================================================================== diff --git a/src/rebar_git_resource.erl b/src/rebar_git_resource.erl index 5a6a5ef..acb9ec0 100644 --- a/src/rebar_git_resource.erl +++ b/src/rebar_git_resource.erl @@ -17,10 +17,17 @@ lock(AppDir, {git, Url, _}) -> lock(AppDir, {git, Url}); lock(AppDir, {git, Url}) -> - AbortMsg = io_lib:format("Locking of git dependency failed in ~s", [AppDir]), + AbortMsg = lists:flatten(io_lib:format("Locking of git dependency failed in ~s", [AppDir])), + Dir = rebar_utils:escape_double_quotes(AppDir), {ok, VsnString} = - rebar_utils:sh("git --git-dir=\"" ++ rebar_utils:escape_double_quotes(AppDir) ++ "/.git\" rev-parse --verify HEAD", - [{use_stdout, false}, {debug_abort_on_error, AbortMsg}]), + case os:type() of + {win32, _} -> + rebar_utils:sh("git --git-dir=\"" ++ Dir ++ "/.git\" --work-tree=\"" ++ Dir ++ "\" rev-parse --verify HEAD", + [{use_stdout, false}, {debug_abort_on_error, AbortMsg}]); + _ -> + rebar_utils:sh("git --git-dir=\"" ++ Dir ++ "/.git\" rev-parse --verify HEAD", + [{use_stdout, false}, {debug_abort_on_error, AbortMsg}]) + end, Ref = string:strip(VsnString, both, $\n), {git, Url, {ref, Ref}}. diff --git a/src/rebar_log.erl b/src/rebar_log.erl index 23ae81e..b1a70c2 100644 --- a/src/rebar_log.erl +++ b/src/rebar_log.erl @@ -27,6 +27,7 @@ -module(rebar_log). -export([init/2, + crashdump/2, set_level/1, get_level/0, error_level/0, @@ -73,6 +74,7 @@ init(Caller, Verbosity) -> ?DEBUG_LEVEL -> debug end, Intensity = intensity(), + application:set_env(rebar, log_caller, Caller), Log = ec_cmd_log:new(Level, Caller, Intensity), set_level(valid_level(Verbosity)), application:set_env(rebar, log, Log). @@ -95,6 +97,16 @@ log(Level, Str, Args) -> {ok, LogState} = application:get_env(rebar, log), ec_cmd_log:Level(LogState, Str++"~n", Args). +crashdump(Str, Args) -> + crashdump("rebar3.crashdump", Str, Args). +crashdump(File, Str, Args) -> + case application:get_env(rebar, log_caller) of + {ok, api} -> + ok; + _ -> + file:write_file(File, io_lib:fwrite(Str, Args)) + end. + error_level() -> ?ERROR_LEVEL. default_level() -> ?INFO_LEVEL. diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl index d4b8a14..8b4611b 100644 --- a/src/rebar_packages.erl +++ b/src/rebar_packages.erl @@ -122,7 +122,7 @@ package_dir(State) -> Error end. -registry_checksum({pkg, Name, Vsn}, State) -> +registry_checksum({pkg, Name, Vsn, _Hash}, State) -> try ?MODULE:verify_table(State), ets:lookup_element(?PACKAGE_TABLE, {Name, Vsn}, 3) diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl index ec7e09d..5817817 100644 --- a/src/rebar_pkg_resource.erl +++ b/src/rebar_pkg_resource.erl @@ -19,7 +19,7 @@ lock(_AppDir, Source) -> Source. -needs_update(Dir, {pkg, _Name, Vsn}) -> +needs_update(Dir, {pkg, _Name, Vsn, _Hash}) -> [AppInfo] = rebar_app_discover:find_apps([Dir], all), case rebar_app_info:original_vsn(AppInfo) =:= ec_cnv:to_list(Vsn) of true -> @@ -28,7 +28,7 @@ needs_update(Dir, {pkg, _Name, Vsn}) -> true end. -download(TmpDir, Pkg={pkg, Name, Vsn}, State) -> +download(TmpDir, Pkg={pkg, Name, Vsn, _Hash}, State) -> CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN), {ok, PackageDir} = rebar_packages:package_dir(State), Package = binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>), @@ -40,7 +40,7 @@ download(TmpDir, Pkg={pkg, Name, Vsn}, State) -> {fetch_fail, Name, Vsn} end. -cached_download(TmpDir, CachePath, Pkg={pkg, Name, Vsn}, Url, ETag, State) -> +cached_download(TmpDir, CachePath, Pkg={pkg, Name, Vsn, _Hash}, Url, ETag, State) -> case request(Url, ETag) of {ok, cached} -> ?INFO("Version cached at ~s is up to date, reusing it", [CachePath]), @@ -58,17 +58,20 @@ cached_download(TmpDir, CachePath, Pkg={pkg, Name, Vsn}, Url, ETag, State) -> serve_from_cache(TmpDir, CachePath, Pkg, State) -> {Files, Contents, Version, Meta} = extract(TmpDir, CachePath), case checksums(Pkg, Files, Contents, Version, Meta, State) of - {Chk, Chk, Chk} -> + {Chk, Chk, Chk, Chk} -> ok = erl_tar:extract({binary, Contents}, [{cwd, TmpDir}, compressed]), {ok, true}; - {_Bin, Chk, Chk} -> + {_Hash, Chk, Chk, Chk} -> + ?DEBUG("Expected hash ~p does not match checksums ~p", [_Hash, Chk]), + {unexpected_hash, CachePath, _Hash, Chk}; + {Chk, _Bin, Chk, Chk} -> ?DEBUG("Checksums: registry: ~p, pkg: ~p", [Chk, _Bin]), {failed_extract, CachePath}; - {Chk, _Reg, Chk} -> + {Chk, Chk, _Reg, Chk} -> ?DEBUG("Checksums: registry: ~p, pkg: ~p", [_Reg, Chk]), {bad_registry_checksum, CachePath}; - {_Bin, _Reg, _Tar} -> - ?DEBUG("Checksums: registry: ~p, pkg: ~p, meta: ~p", [_Reg, _Bin, _Tar]), + {_Hash, _Bin, _Reg, _Tar} -> + ?DEBUG("Checksums: expected: ~p, registry: ~p, pkg: ~p, meta: ~p", [_Hash, _Reg, _Bin, _Tar]), {bad_checksum, CachePath} end. @@ -92,13 +95,13 @@ extract(TmpDir, CachePath) -> {"metadata.config", Meta} = lists:keyfind("metadata.config", 1, Files), {Files, Contents, Version, Meta}. -checksums(Pkg, Files, Contents, Version, Meta, State) -> +checksums(Pkg={pkg, _Name, _Vsn, Hash}, Files, Contents, Version, Meta, State) -> Blob = <<Version/binary, Meta/binary, Contents/binary>>, <<X:256/big-unsigned>> = crypto:hash(sha256, Blob), BinChecksum = list_to_binary(string:to_upper(lists:flatten(io_lib:format("~64.16.0b", [X])))), RegistryChecksum = rebar_packages:registry_checksum(Pkg, State), {"CHECKSUM", TarChecksum} = lists:keyfind("CHECKSUM", 1, Files), - {BinChecksum, RegistryChecksum, TarChecksum}. + {Hash, BinChecksum, RegistryChecksum, TarChecksum}. make_vsn(_) -> {error, "Replacing version of type pkg not supported."}. diff --git a/src/rebar_prv_common_test.erl b/src/rebar_prv_common_test.erl index 5712fbf..1e0632e 100644 --- a/src/rebar_prv_common_test.erl +++ b/src/rebar_prv_common_test.erl @@ -116,6 +116,7 @@ prepare_tests(State) -> %% rebar.config test options CfgOpts = cfgopts(State), ProjectApps = rebar_state:project_apps(State), + %% prioritize tests to run first trying any command line specified %% tests falling back to tests specified in the config file finally %% running a default set if no other tests are present @@ -215,6 +216,14 @@ add_hooks(Opts, State) -> select_tests(_, _, {error, _} = Error, _) -> Error; select_tests(_, _, _, {error, _} = Error) -> Error; select_tests(State, ProjectApps, CmdOpts, CfgOpts) -> + %% set application env if sys_config argument is provided + SysConfigs = sys_config_list(CmdOpts, CfgOpts), + Configs = lists:flatmap(fun(Filename) -> + rebar_file_utils:consult_config(State, Filename) + end, SysConfigs), + [application:load(Application) || Config <- Configs, {Application, _} <- Config], + rebar_utils:reread_config(Configs), + Merged = lists:ukeymerge(1, lists:ukeysort(1, CmdOpts), lists:ukeysort(1, CfgOpts)), @@ -229,6 +238,17 @@ select_tests(State, ProjectApps, CmdOpts, CfgOpts) -> end, discover_tests(State, ProjectApps, Opts). +sys_config_list(CmdOpts, CfgOpts) -> + CmdSysConfigs = split_string(proplists:get_value(sys_config, CmdOpts, "")), + case proplists:get_value(sys_config, CfgOpts, []) of + [H | _]=Configs when is_list(H) -> + Configs ++ CmdSysConfigs; + [] -> + CmdSysConfigs; + Configs -> + [Configs | CmdSysConfigs] + end. + discover_tests(State, ProjectApps, Opts) -> case {proplists:get_value(suite, Opts), proplists:get_value(dir, Opts)} of %% no dirs or suites defined, try using `$APP/test` and `$ROOT/test` @@ -647,7 +667,8 @@ ct_opts(_State) -> {verbose, $v, "verbose", boolean, help(verbose)}, {name, undefined, "name", atom, help(name)}, {sname, undefined, "sname", atom, help(sname)}, - {setcookie, undefined, "setcookie", atom, help(setcookie)} + {setcookie, undefined, "setcookie", atom, help(setcookie)}, + {sys_config, undefined, "sys_config", string, help(sys_config)} %% comma-seperated list ]. help(dir) -> @@ -662,6 +683,8 @@ help(label) -> "Test label"; help(config) -> "List of config files"; +help(sys_config) -> + "List of application config files"; help(allow_user_terms) -> "Allow user defined config values in config files"; help(logdir) -> diff --git a/src/rebar_prv_cover.erl b/src/rebar_prv_cover.erl index b04b1c1..9772e96 100644 --- a/src/rebar_prv_cover.erl +++ b/src/rebar_prv_cover.erl @@ -298,6 +298,7 @@ cover_compile(State, apps) -> AppDirs = app_dirs(Apps), cover_compile(State, lists:filter(fun(D) -> ec_file:is_dir(D) end, AppDirs)); cover_compile(State, Dirs) -> + rebar_utils:update_code(rebar_state:code_paths(State, all_deps), [soft_purge]), %% start the cover server if necessary {ok, CoverPid} = start_cover(), %% redirect cover output @@ -327,7 +328,9 @@ cover_compile(State, Dirs) -> {error, Reason} -> ?WARN("Directory ~p error ~p", [Dir, Reason]) end - end, Dirs). + end, Dirs), + rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), + ok. app_dirs(Apps) -> lists:foldl(fun app_ebin_dirs/2, [], Apps). diff --git a/src/rebar_prv_deps.erl b/src/rebar_prv_deps.erl index 9ff2bfa..c865276 100644 --- a/src/rebar_prv_deps.erl +++ b/src/rebar_prv_deps.erl @@ -97,7 +97,7 @@ display_dep(_State, {Name, _Vsn, Source}) when is_tuple(Source) -> display_dep(_State, {Name, _Vsn, Source, _Opts}) when is_tuple(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) -> +display_dep(State, {Name, Source={pkg, _, Vsn, _}, Level}) when is_integer(Level) -> 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 diff --git a/src/rebar_prv_deps_tree.erl b/src/rebar_prv_deps_tree.erl index cefa60a..c0c8bab 100644 --- a/src/rebar_prv_deps_tree.erl +++ b/src/rebar_prv_deps_tree.erl @@ -70,7 +70,7 @@ print_children(Prefix, [{Name, Vsn, Source} | Rest], Dict, Verbose) -> [Prefix, " "]; _ -> io:format("~ts~ts", [Prefix, <<226,148,156,226,148,128,32>>]), %Binary for ├─ utf8% - [Prefix, "│ "] + [Prefix, <<226,148,130,32,32>>] %Binary for │ utf8% end, io:format("~ts~ts~ts (~ts)~n", [Name, <<226,148,128>>, Vsn, type(Source, Verbose)]), %Binary for ─ utf8% case dict:find(Name, Dict) of diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 622ee60..fc13de1 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -249,17 +249,32 @@ ebin_files(EbinDir) -> File <- filelib:wildcard(Wildcard, EbinDir)]. read_plt(_State, Plt) -> - case dialyzer:plt_info(Plt) of - {ok, Info} -> - Files = proplists:get_value(files, Info, []), + Vsn = dialyzer_version(), + case plt_files(Plt) of + {ok, Files} when Vsn < {2, 9, 0} -> + % Before dialyzer-2.9 (OTP 18.3) removing a beam file from the PLT + % that no longer exists would crash. Therefore force a rebuild of + % PLT if any files no longer exist. read_plt_files(Plt, Files); + {ok, _} = Result when Vsn >= {2, 9, 0} -> + Result; {error, no_such_file} -> error; + {error, not_valid} -> + error; {error, read_error} -> Error = io_lib:format("Could not read the PLT file ~p", [Plt]), throw({dialyzer_error, Error}) end. +plt_files(Plt) -> + case dialyzer:plt_info(Plt) of + {ok, Info} -> + {ok, proplists:get_value(files, Info, [])}; + {error, _} = Error -> + Error + end. + %% If any file no longer exists dialyzer will fail when updating the PLT. read_plt_files(Plt, Files) -> case [File || File <- Files, not filelib:is_file(File)] of @@ -353,8 +368,19 @@ update_base_plt(State, BasePlt, Output, BaseFiles) -> build_plt(State, BasePlt, Output, BaseFiles) end. +build_plt(State, Plt, _, []) -> + ?INFO("Building with no files in ~p...", [Plt]), + Opts = [{get_warnings, false}, + {output_plt, Plt}, + {apps, [erts]}], + % Create a PLT with erts files and then remove erts files to be left with an + % empty PLT. Dialyzer will crash when trying to build a PLT with an empty + % file list. + _ = dialyzer:run([{analysis_type, plt_build} | Opts]), + _ = dialyzer:run([{analysis_type, plt_remove}, {init_plt, Plt} | Opts]), + {0, State}; build_plt(State, Plt, Output, Files) -> - ?INFO("Adding ~b files to ~p...", [length(Files), Plt]), + ?INFO("Building with ~b files in ~p...", [length(Files), Plt]), GetWarnings = get_config(State, get_warnings, false), Opts = [{analysis_type, plt_build}, {get_warnings, GetWarnings}, @@ -369,12 +395,15 @@ succ_typings(State, Plt, Output) -> {0, State}; _ -> Apps = rebar_state:project_apps(State), - succ_typings(State, Plt, Output, Apps) + ?INFO("Doing success typing analysis...", []), + Files = apps_to_files(Apps), + succ_typings(State, Plt, Output, Files) end. -succ_typings(State, Plt, Output, Apps) -> - ?INFO("Doing success typing analysis...", []), - Files = apps_to_files(Apps), +succ_typings(State, Plt, _, []) -> + ?INFO("Analyzing no files with ~p...", [Plt]), + {0, State}; +succ_typings(State, Plt, Output, Files) -> ?INFO("Analyzing ~b files with ~p...", [length(Files), Plt]), Opts = [{analysis_type, succ_typings}, {get_warnings, true}, diff --git a/src/rebar_prv_edoc.erl b/src/rebar_prv_edoc.erl index 6cefe14..465fc34 100644 --- a/src/rebar_prv_edoc.erl +++ b/src/rebar_prv_edoc.erl @@ -33,17 +33,25 @@ do(State) -> code:add_pathsa(rebar_state:code_paths(State, all_deps)), ProjectApps = rebar_state:project_apps(State), Providers = rebar_state:providers(State), - EDocOpts = rebar_state:get(State, edoc_opts, []), + EdocOpts = rebar_state:get(State, edoc_opts, []), + ShouldAccPaths = not has_configured_paths(EdocOpts), Cwd = rebar_state:dir(State), rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, State), - lists:foreach(fun(AppInfo) -> - rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, AppInfo, State), - AppName = ec_cnv:to_list(rebar_app_info:name(AppInfo)), - ?INFO("Running edoc for ~s", [AppName]), - AppDir = rebar_app_info:dir(AppInfo), - ok = edoc:application(list_to_atom(AppName), AppDir, EDocOpts), - rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, AppInfo, State) - end, ProjectApps), + lists:foldl(fun(AppInfo, EdocOptsAcc) -> + rebar_hooks:run_all_hooks(Cwd, pre, ?PROVIDER, Providers, AppInfo, State), + AppName = ec_cnv:to_list(rebar_app_info:name(AppInfo)), + ?INFO("Running edoc for ~s", [AppName]), + AppDir = rebar_app_info:dir(AppInfo), + ok = edoc:application(list_to_atom(AppName), AppDir, EdocOptsAcc), + rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, AppInfo, State), + case ShouldAccPaths of + true -> + %% edoc wants / on all OSes + add_to_paths(EdocOptsAcc, AppDir++"/doc"); + false -> + EdocOptsAcc + end + end, EdocOpts, ProjectApps), rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, State), rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)), {ok, State}. @@ -55,3 +63,12 @@ format_error(Reason) -> %% =================================================================== %% Internal functions %% =================================================================== +has_configured_paths(EdocOpts) -> + proplists:get_value(dir, EdocOpts) =/= undefined. + +add_to_paths([], Path) -> + [{doc_path, [Path]}]; +add_to_paths([{doc_path, Paths}|T], Path) -> + [{doc_path, [Path | Paths]} | T]; +add_to_paths([H|T], Path) -> + [H | add_to_paths(Path, T)]. diff --git a/src/rebar_prv_escriptize.erl b/src/rebar_prv_escriptize.erl index 32f5508..7ee20c2 100644 --- a/src/rebar_prv_escriptize.erl +++ b/src/rebar_prv_escriptize.erl @@ -61,8 +61,11 @@ desc() -> "the project's and its dependencies' BEAM files.". do(State) -> + Providers = rebar_state:providers(State), + Cwd = rebar_state:dir(State), + rebar_hooks:run_project_and_app_hooks(Cwd, pre, ?PROVIDER, Providers, State), ?INFO("Building escript...", []), - case rebar_state:get(State, escript_main_app, undefined) of + Res = case rebar_state:get(State, escript_main_app, undefined) of undefined -> case rebar_state:project_apps(State) of [App] -> @@ -72,9 +75,15 @@ do(State) -> end; Name -> AllApps = rebar_state:all_deps(State)++rebar_state:project_apps(State), - {ok, AppInfo} = rebar_app_utils:find(ec_cnv:to_binary(Name), AllApps), - escriptize(State, AppInfo) - end. + case rebar_app_utils:find(ec_cnv:to_binary(Name), AllApps) of + {ok, AppInfo} -> + escriptize(State, AppInfo); + _ -> + ?PRV_ERROR({bad_name, Name}) + end + end, + rebar_hooks:run_project_and_app_hooks(Cwd, post, ?PROVIDER, Providers, State), + Res. escriptize(State0, App) -> AppName = rebar_app_info:name(App), @@ -90,9 +99,9 @@ escriptize(State0, App) -> %% Look for a list of other applications (dependencies) to include %% in the output file. We then use the .app files for each of these %% to pull in all the .beam files. - InclApps = lists:usort([ec_cnv:to_atom(AppName) | rebar_state:get(State, escript_incl_apps, []) - ++ all_deps(State)]), + TopInclApps = lists:usort([ec_cnv:to_atom(AppName) | rebar_state:get(State, escript_incl_apps, [])]), AllApps = rebar_state:all_deps(State)++rebar_state:project_apps(State), + InclApps = find_deps(TopInclApps, AllApps), InclBeams = get_apps_beams(InclApps, AllApps), %% Look for a list of extra files to include in the output file. @@ -219,9 +228,25 @@ usort(List) -> get_nonempty(Files) -> [{FName,FBin} || {FName,FBin} <- Files, FBin =/= <<>>]. -all_deps(State) -> - [list_to_existing_atom(binary_to_list(rebar_app_info:name(App))) - || App <- rebar_state:all_deps(State)]. +find_deps(AppNames, AllApps) -> + BinAppNames = [ec_cnv:to_binary(Name) || Name <- AppNames], + [ec_cnv:to_atom(Name) || + Name <- find_deps_of_deps(BinAppNames, AllApps, BinAppNames)]. + +%% Should look at the app files to find direct dependencies +find_deps_of_deps([], _, Acc) -> Acc; +find_deps_of_deps([Name|Names], Apps, Acc) -> + ?DEBUG("processing ~p", [Name]), + {ok, App} = rebar_app_utils:find(Name, Apps), + DepNames = proplists:get_value(applications, rebar_app_info:app_details(App), []), + BinDepNames = [ec_cnv:to_binary(Dep) || Dep <- DepNames, + %% ignore system libs; shouldn't include them. + DepDir <- [code:lib_dir(Dep)], + DepDir =:= {error, bad_name} orelse % those are all local + not lists:prefix(code:root_dir(), DepDir)] + -- ([Name|Names]++Acc), % avoid already seen deps + ?DEBUG("new deps of ~p found to be ~p", [Name, BinDepNames]), + find_deps_of_deps(BinDepNames ++ Names, Apps, BinDepNames ++ Acc). def(Rm, State, Key, Default) -> Value0 = rebar_state:get(State, Key, Default), diff --git a/src/rebar_prv_help.erl b/src/rebar_prv_help.erl index c028264..75cc609 100644 --- a/src/rebar_prv_help.erl +++ b/src/rebar_prv_help.erl @@ -41,7 +41,10 @@ do(State) -> [Name] -> % default namespace task_help(default, list_to_atom(Name), State); [Namespace, Name] -> - task_help(list_to_atom(Namespace), list_to_atom(Name), State) + task_help(list_to_atom(Namespace), list_to_atom(Name), State); + _ -> + {error, "Too many arguments given. " ++ + "Usage: rebar3 help [<namespace>] <task>"} end. -spec format_error(any()) -> iolist(). diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl index 5e6aa4c..a8a7ea0 100644 --- a/src/rebar_prv_install_deps.erl +++ b/src/rebar_prv_install_deps.erl @@ -352,10 +352,14 @@ make_relative_to_root(State, Path) when is_list(Path) -> rebar_dir:make_relative_path(Path, Root). fetch_app(AppInfo, AppDir, State) -> - ?INFO("Fetching ~s (~p)", [rebar_app_info:name(AppInfo), rebar_app_info:source(AppInfo)]), + ?INFO("Fetching ~s (~p)", [rebar_app_info:name(AppInfo), + format_source(rebar_app_info:source(AppInfo))]), Source = rebar_app_info:source(AppInfo), true = rebar_fetch:download_source(AppDir, Source, State). +format_source({pkg, Name, Vsn, _Hash}) -> {pkg, Name, Vsn}; +format_source(Source) -> Source. + %% This is called after the dep has been downloaded and unpacked, if it hadn't been already. %% So this is the first time for newly downloaded apps that its .app/.app.src data can %% be read in an parsed. diff --git a/src/rebar_prv_path.erl b/src/rebar_prv_path.erl index 4e88496..4259eec 100644 --- a/src/rebar_prv_path.erl +++ b/src/rebar_prv_path.erl @@ -77,15 +77,15 @@ paths([{rel, true}|Rest], Apps, State, Acc) -> base_dir(State) -> io_lib:format("~s", [rebar_dir:base_dir(State)]). bin_dir(State) -> io_lib:format("~s/bin", [rebar_dir:base_dir(State)]). -lib_dir(State) -> io_lib:format("~s/lib", [rebar_dir:base_dir(State)]). +lib_dir(State) -> io_lib:format("~s", [rebar_dir:deps_dir(State)]). rel_dir(State) -> io_lib:format("~s/rel", [rebar_dir:base_dir(State)]). ebin_dirs(Apps, State) -> - lists:map(fun(App) -> io_lib:format("~s/lib/~s/ebin", [rebar_dir:base_dir(State), App]) end, Apps). + lists:map(fun(App) -> io_lib:format("~s/~s/ebin", [rebar_dir:deps_dir(State), App]) end, Apps). priv_dirs(Apps, State) -> - lists:map(fun(App) -> io_lib:format("~s/lib/~s/priv", [rebar_dir:base_dir(State), App]) end, Apps). + lists:map(fun(App) -> io_lib:format("~s/~s/priv", [rebar_dir:deps_dir(State), App]) end, Apps). src_dirs(Apps, State) -> - lists:map(fun(App) -> io_lib:format("~s/lib/~s/src", [rebar_dir:base_dir(State), App]) end, Apps). + lists:map(fun(App) -> io_lib:format("~s/~s/src", [rebar_dir:deps_dir(State), App]) end, Apps). print_paths_if_exist(Paths, State) -> {RawOpts, _} = rebar_state:command_parsed_args(State), diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl index a5457ad..b7febf8 100644 --- a/src/rebar_prv_shell.erl +++ b/src/rebar_prv_shell.erl @@ -111,7 +111,7 @@ shell(State) -> %% Hack to fool the init process into thinking we have stopped and the normal %% node start process can go on. Without it, init:get_status() always return %% '{starting, started}' instead of '{started, started}' - init ! {'EXIT', self(), normal}, + init ! {'EXIT', self(), normal}, gen_server:enter_loop(rebar_agent, [], GenState, {local, rebar_agent}, hibernate). info() -> @@ -332,15 +332,7 @@ reread_config(State) -> no_config -> ok; ConfigList -> - try - [application:set_env(Application, Key, Val) - || Config <- ConfigList, - {Application, Items} <- Config, - {Key, Val} <- Items] - catch _:_ -> - ?ERROR("The configuration file submitted could not be read " - "and will be ignored.", []) - end, + _ = rebar_utils:reread_config(ConfigList), ok end. @@ -406,7 +398,7 @@ find_config(State) -> no_value -> no_config; Filename when is_list(Filename) -> - consult_config(State, Filename) + rebar_file_utils:consult_config(State, Filename) end. -spec first_value([Fun], State) -> no_value | Value when @@ -414,7 +406,7 @@ find_config(State) -> State :: rebar_state:t(), Fun :: fun ((State) -> no_value | Value). first_value([], _) -> no_value; -first_value([Fun | Rest], State) -> +first_value([Fun | Rest], State) -> case Fun(State) of no_value -> first_value(Rest, State); @@ -445,18 +437,3 @@ find_config_rebar(State) -> find_config_relx(State) -> debug_get_value(sys_config, rebar_state:get(State, relx, []), no_value, "Found config from relx."). - --spec consult_config(rebar_state:t(), string()) -> [[tuple()]]. -consult_config(State, Filename) -> - Fullpath = filename:join(rebar_dir:root_dir(State), Filename), - ?DEBUG("Loading configuration from ~p", [Fullpath]), - Config = case rebar_file_utils:try_consult(Fullpath) of - [T] -> T; - [] -> [] - end, - SubConfigs = [consult_config(State, Entry ++ ".config") || - Entry <- Config, is_list(Entry) - ], - - [Config | lists:merge(SubConfigs)]. - diff --git a/src/rebar_prv_update.erl b/src/rebar_prv_update.erl index 5e1e253..54b135e 100644 --- a/src/rebar_prv_update.erl +++ b/src/rebar_prv_update.erl @@ -114,14 +114,14 @@ hex_to_index(State) -> ets:foldl(fun({Pkg, [[]]}, _) when is_binary(Pkg) -> true; - ({Pkg, [Vsns=[Vsn | _Rest]]}, _) when is_binary(Pkg) -> + ({Pkg, [Vsns=[_Vsn | _Rest]]}, _) when is_binary(Pkg) -> %% Verify the package is of the right build tool by checking if the first %% version exists in the table from the foldl above - case ets:member(?PACKAGE_TABLE, {Pkg, Vsn}) of - true -> - ets:insert(?PACKAGE_TABLE, {Pkg, Vsns}); - false -> - true + case [V || V <- Vsns, ets:member(?PACKAGE_TABLE, {Pkg, V})] of + [] -> + true; + Vsns1 -> + ets:insert(?PACKAGE_TABLE, {Pkg, Vsns1}) end; (_, _) -> true diff --git a/src/rebar_prv_upgrade.erl b/src/rebar_prv_upgrade.erl index c5c43e4..18c307b 100644 --- a/src/rebar_prv_upgrade.erl +++ b/src/rebar_prv_upgrade.erl @@ -45,7 +45,29 @@ init(State) -> do(State) -> {Args, _} = rebar_state:command_parsed_args(State), Locks = rebar_state:get(State, {locks, default}, []), - Deps = rebar_state:get(State, deps, []), + %% We have 3 sources of dependencies to upgrade from: + %% 1. the top-level rebar.config (in `deps', dep name is an atom) + %% 2. the app-level rebar.config in umbrella apps (in `{deps, default}', + %% where the dep name is an atom) + %% 3. the formatted sources for all after app-parsing (in `{deps, default}', + %% where the reprocessed app name is a binary) + %% + %% The gotcha with these is that the selection of dependencies with a + %% binary name (those that are stable and usable internally) is done with + %% in the profile deps only, but while accounting for locks. + %% Because our job here is to unlock those that have changed, we must + %% instead use the atom-based names, both in `deps' and `{deps, default}', + %% as those are the dependencies that may have changed but have been + %% disregarded by locks. + %% + %% As such, the working set of dependencies is the addition of + %% `deps' and `{deps, default}' entries with an atom name, as those + %% disregard locks and parsed values post-selection altogether. + %% Packages without versions can of course be a single atom. + TopDeps = rebar_state:get(State, deps, []), + ProfileDeps = rebar_state:get(State, {deps, default}, []), + Deps = [Dep || Dep <- TopDeps ++ ProfileDeps, % TopDeps > ProfileDeps + is_atom(Dep) orelse is_atom(element(1, Dep))], Names = parse_names(ec_cnv:to_binary(proplists:get_value(package, Args, <<"">>)), Locks), DepsDict = deps_dict(rebar_state:all_deps(State)), case prepare_locks(Names, Deps, Locks, [], DepsDict) of diff --git a/src/rebar_state.erl b/src/rebar_state.erl index a613a00..bdd4aeb 100644 --- a/src/rebar_state.erl +++ b/src/rebar_state.erl @@ -417,7 +417,8 @@ create_logic_providers(ProviderModules, State0) -> catch C:T -> ?DEBUG("~p: ~p ~p", [C, T, erlang:get_stacktrace()]), - throw({error, "Failed creating providers. Run with DEBUG=1 for stacktrace."}) + ?CRASHDUMP("~p: ~p~n~p~n~n~p", [C, T, erlang:get_stacktrace(), State0]), + throw({error, "Failed creating providers. Run with DEBUG=1 for stacktrace or consult rebar3.crashdump."}) end. to_list(#state_t{} = State) -> diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl index 2f33bfc..299b957 100644 --- a/src/rebar_templater.erl +++ b/src/rebar_templater.erl @@ -326,7 +326,7 @@ find_other_templates(State) -> undefined -> []; TemplateDir -> - rebar_utils:find_files(TemplateDir, ?TEMPLATE_RE) + rebar_utils:find_files(TemplateDir, ?TEMPLATE_RE, true) % recursive end. %% Fetch template indexes that sit on disk in plugins diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index 56a3940..aa9e268 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -69,7 +69,8 @@ check_blacklisted_otp_versions/1, info_useless/2, list_dir/1, - user_agent/0]). + user_agent/0, + reread_config/1]). %% for internal use only -export([otp_release/0]). @@ -412,6 +413,17 @@ user_agent() -> {ok, Vsn} = application:get_key(rebar, vsn), ?FMT("Rebar/~s (OTP/~s)", [Vsn, otp_release()]). +reread_config(ConfigList) -> + try + [application:set_env(Application, Key, Val) + || Config <- ConfigList, + {Application, Items} <- Config, + {Key, Val} <- Items] + catch _:_ -> + ?ERROR("The configuration file submitted could not be read " + "and will be ignored.", []) + end. + %% ==================================================================== %% Internal functions %% ==================================================================== @@ -789,9 +801,13 @@ maybe_ends_in_comma(H) -> end. get_http_vars(Scheme) -> + OS = case os:getenv(atom_to_list(Scheme)) of + Str when is_list(Str) -> Str; + _ -> [] + end, GlobalConfigFile = rebar_dir:global_config(), Config = rebar_config:consult_file(GlobalConfigFile), - proplists:get_value(Scheme, Config, []). + proplists:get_value(Scheme, Config, OS). set_httpc_options() -> set_httpc_options(https_proxy, get_http_vars(https_proxy)), diff --git a/test/mock_pkg_resource.erl b/test/mock_pkg_resource.erl index a94fe2f..f837713 100644 --- a/test/mock_pkg_resource.erl +++ b/test/mock_pkg_resource.erl @@ -22,8 +22,9 @@ mock() -> mock([]). | {not_in_index, [{App, Vsn}]} | {pkgdeps, [{{App,Vsn}, [Dep]}]}, App :: string(), - Dep :: {App, string(), {pkg, App, Vsn}}, - Vsn :: string(). + Dep :: {App, string(), {pkg, App, Vsn, Hash}}, + Vsn :: string(), + Hash :: string() | undefined. mock(Opts) -> meck:new(?MOD, [no_link]), mock_lock(Opts), @@ -51,7 +52,7 @@ mock_update(Opts) -> ToUpdate = proplists:get_value(upgrade, Opts, []), meck:expect( ?MOD, needs_update, - fun(_Dir, {pkg, App, _Vsn}) -> + fun(_Dir, {pkg, App, _Vsn, _Hash}) -> lists:member(binary_to_list(App), ToUpdate) end). @@ -66,7 +67,7 @@ mock_vsn(_Opts) -> %% @doc For each app to download, create a dummy app on disk instead. %% The configuration for this one (passed in from `mock/1') includes: %% -%% - Specify a version with `{pkg, _, Vsn}' +%% - Specify a version with `{pkg, _, Vsn, _}' %% - Dependencies for each application must be passed of the form: %% `{pkgdeps, [{"app1", [{app2, ".*", {pkg, ...}}]}]}' -- basically %% the `pkgdeps' option takes a key/value list of terms to output directly @@ -76,7 +77,7 @@ mock_download(Opts) -> Config = proplists:get_value(config, Opts, []), meck:expect( ?MOD, download, - fun (Dir, {pkg, AppBin, Vsn}, _) -> + fun (Dir, {pkg, AppBin, Vsn, _}, _) -> App = binary_to_list(AppBin), filelib:ensure_dir(Dir), AppDeps = proplists:get_value({App,Vsn}, Deps, []), diff --git a/test/rebar_compile_SUITE.erl b/test/rebar_compile_SUITE.erl index 76a3de5..d9b75e4 100644 --- a/test/rebar_compile_SUITE.erl +++ b/test/rebar_compile_SUITE.erl @@ -23,6 +23,7 @@ paths_extra_dirs_in_project_root/1, clean_extra_dirs_in_project_root/1, recompile_when_hrl_changes/1, + recompile_when_included_hrl_changes/1, recompile_when_opts_change/1, dont_recompile_when_opts_dont_change/1, dont_recompile_yrl_or_xrl/1, @@ -40,7 +41,8 @@ profile_override_deps/1, deps_build_in_prod/1, include_file_relative_to_working_directory/1, - include_file_in_src/1]). + include_file_in_src/1, + always_recompile_when_erl_compiler_options_set/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -55,14 +57,19 @@ all() -> {group, basic_srcdirs}, {group, release_srcdirs}, {group, unbalanced_srcdirs}, {group, basic_extras}, {group, release_extras}, {group, unbalanced_extras}, {group, root_extras}, - recompile_when_hrl_changes, recompile_when_opts_change, + recompile_when_hrl_changes, recompile_when_included_hrl_changes, + recompile_when_opts_change, dont_recompile_when_opts_dont_change, dont_recompile_yrl_or_xrl, delete_beam_if_source_deleted, deps_in_path, checkout_priority, highest_version_of_pkg_dep, parse_transform_test, erl_first_files_test, mib_test, umbrella_mib_first_test, only_default_transitive_deps, clean_all, override_deps, profile_override_deps, deps_build_in_prod, - include_file_relative_to_working_directory, include_file_in_src]. + include_file_relative_to_working_directory, include_file_in_src] ++ + case erlang:function_exported(os, unsetenv, 1) of + true -> [always_recompile_when_erl_compiler_options_set]; + false -> [] + end. groups() -> [{basic_app, [], [build_basic_app, paths_basic_app, clean_basic_app]}, @@ -642,7 +649,6 @@ recompile_when_hrl_changes(Config) -> Vsn = rebar_test_utils:create_random_vsn(), rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), - ExtraSrc = <<"-module(test_header_include).\n" "-export([main/0]).\n" "-include(\"test_header_include.hrl\").\n" @@ -660,10 +666,48 @@ recompile_when_hrl_changes(Config) -> ModTime = [filelib:last_modified(filename:join([EbinDir, F])) || F <- Files, filename:extension(F) == ".beam"], + timer:sleep(1000), + + NewExtraHeader = <<"-define(SOME_DEFINE, false).\n">>, + ok = file:write_file(HeaderFile, NewExtraHeader), + + rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}), + + {ok, NewFiles} = rebar_utils:list_dir(EbinDir), + NewModTime = [filelib:last_modified(filename:join([EbinDir, F])) + || F <- NewFiles, filename:extension(F) == ".beam"], + + ?assert(ModTime =/= NewModTime). + +recompile_when_included_hrl_changes(Config) -> + AppDir = ?config(apps, Config), + + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + + ExtraSrc = <<"-module(test_header_include).\n" + "-export([main/0]).\n" + "-include(\"test_header_include.hrl\").\n" + "main() -> ?SOME_DEFINE.\n">>, + + ExtraHeader = <<"-define(SOME_DEFINE, true).\n">>, + ok = filelib:ensure_dir(filename:join([AppDir, "include", "dummy"])), + HeaderFile = filename:join([AppDir, "include", "test_header_include.hrl"]), + ok = file:write_file(filename:join([AppDir, "src", "test_header_include.erl"]), ExtraSrc), + ok = file:write_file(HeaderFile, ExtraHeader), + + rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}), + + EbinDir = filename:join([AppDir, "_build", "default", "lib", Name, "ebin"]), + {ok, Files} = rebar_utils:list_dir(EbinDir), + ModTime = [filelib:last_modified(filename:join([EbinDir, F])) + || F <- Files, filename:extension(F) == ".beam"], timer:sleep(1000), - rebar_file_utils:touch(HeaderFile), + NewExtraHeader = <<"-define(SOME_DEFINE, false).\n">>, + ok = file:write_file(HeaderFile, NewExtraHeader), rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}), @@ -1265,3 +1309,44 @@ include_file_in_src(Config) -> rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], {ok, [{app, Name}]}). + +always_recompile_when_erl_compiler_options_set(Config) -> + %% save existing env to restore after test + ExistingEnv = os:getenv("ERL_COMPILER_OPTIONS"), + + AppDir = ?config(apps, Config), + + Name = rebar_test_utils:create_random_name("erl_compiler_options_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + + true = os:unsetenv("ERL_COMPILER_OPTIONS"), + + rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}), + + EbinDir = filename:join([AppDir, "_build", "default", "lib", Name, "ebin"]), + + {ok, Files} = rebar_utils:list_dir(EbinDir), + ModTime = [filelib:last_modified(filename:join([EbinDir, F])) + || F <- Files, filename:extension(F) == ".beam"], + + timer:sleep(1000), + + true = os:putenv("ERL_COMPILER_OPTIONS", "[{d, some_macro}]"), + + rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}), + + {ok, NewFiles} = rebar_utils:list_dir(EbinDir), + NewModTime = [filelib:last_modified(filename:join([EbinDir, F])) + || F <- NewFiles, filename:extension(F) == ".beam"], + + ?assert(ModTime =/= NewModTime), + + %% restore existing env + case ExistingEnv of + false -> ok; + _ -> os:putenv("ERL_COMPILER_OPTIONS", ExistingEnv) + end. + + + diff --git a/test/rebar_ct_SUITE.erl b/test/rebar_ct_SUITE.erl index 1da7571..c10875b 100644 --- a/test/rebar_ct_SUITE.erl +++ b/test/rebar_ct_SUITE.erl @@ -41,6 +41,7 @@ cmd_scale_timetraps/1, cmd_create_priv_dir/1, cmd_include_dir/1, + cmd_sys_config/1, cfg_opts/1, cfg_arbitrary_opts/1, cfg_test_spec/1, @@ -51,6 +52,7 @@ misspecified_ct_compile_opts/1, misspecified_ct_first_files/1]). +-include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). all() -> [{group, basic_app}, @@ -102,7 +104,8 @@ groups() -> [{basic_app, [], [basic_app_default_dirs, cmd_multiply_timetraps, cmd_scale_timetraps, cmd_create_priv_dir, - cmd_include_dir]}, + cmd_include_dir, + cmd_sys_config]}, {cover, [], [cover_compiled]}]. init_per_group(basic_app, Config) -> @@ -726,7 +729,7 @@ suite_at_app_root(Config) -> data_dir_correct(Config) -> DataDir = ?config(data_dir, Config), Parts = filename:split(DataDir), - ["rebar_ct_SUITE_data","test","rebar","lib","test","_build"|_] = lists:reverse(Parts). + ["rebar_ct_SUITE_data","test","rebar","lib",_,"_build"|_] = lists:reverse(Parts). cmd_label(Config) -> State = ?config(result, Config), @@ -1020,7 +1023,41 @@ cmd_include_dir(Config) -> CompileOpts = proplists:get_value(options, Info), true = lists:member({i, "foo/bar/baz"}, CompileOpts), true = lists:member({i, "qux"}, CompileOpts). - + +cmd_sys_config(Config) -> + State = ?config(result, Config), + AppDir = ?config(apps, Config), + Name = ?config(name, Config), + AppName = list_to_atom(Name), + + {ok, _} = rebar_prv_common_test:prepare_tests(State), + ?assertEqual(undefined, application:get_env(AppName, key)), + + CfgFile = filename:join([AppDir, "config", "cfg_sys.config"]), + ok = filelib:ensure_dir(CfgFile), + ok = file:write_file(CfgFile, cfg_sys_config_file(AppName)), + RebarConfig = [{ct_opts, [{sys_config, CfgFile}]}], + {ok, State1} = rebar_test_utils:run_and_check(Config, RebarConfig, ["as", "test", "lock"], return), + + {ok, _} = rebar_prv_common_test:prepare_tests(State1), + ?assertEqual({ok, cfg_value}, application:get_env(AppName, key)), + + Providers = rebar_state:providers(State1), + Namespace = rebar_state:namespace(State1), + CommandProvider = providers:get_provider(ct, Providers, Namespace), + GetOptSpec = providers:opts(CommandProvider), + + CmdFile = filename:join([AppDir, "config", "cmd_sys.config"]), + ok = filelib:ensure_dir(CmdFile), + ok = file:write_file(CmdFile, cmd_sys_config_file(AppName)), + {ok, GetOptResult} = getopt:parse(GetOptSpec, ["--sys_config="++CmdFile]), + State2 = rebar_state:command_parsed_args(State1, GetOptResult), + + {ok, _} = rebar_prv_common_test:prepare_tests(State2), + + ?assertEqual({ok ,cmd_value}, application:get_env(AppName, key)). + + cfg_opts(Config) -> C = rebar_test_utils:init_rebar_state(Config, "ct_cfg_opts_"), @@ -1181,10 +1218,15 @@ misspecified_ct_first_files(Config) -> {badconfig, {"Value `~p' of option `~p' must be a list", {some_file, ct_first_files}}} = Error. - %% helper for generating test data test_suite(Name) -> io_lib:format("-module(~ts_SUITE).\n" "-compile(export_all).\n" "all() -> [some_test].\n" "some_test(_) -> ok.\n", [Name]). + +cmd_sys_config_file(AppName) -> + io_lib:format("[{~s, [{key, cmd_value}]}].", [AppName]). + +cfg_sys_config_file(AppName) -> + io_lib:format("[{~s, [{key, cfg_value}]}].", [AppName]). diff --git a/test/rebar_deps_SUITE.erl b/test/rebar_deps_SUITE.erl index c95854a..24bf2a0 100644 --- a/test/rebar_deps_SUITE.erl +++ b/test/rebar_deps_SUITE.erl @@ -3,7 +3,11 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -all() -> [sub_app_deps, newly_added_dep, newly_added_after_empty_lock, http_proxy_settings, https_proxy_settings, semver_matching_lt, semver_matching_lte, semver_matching_gt, valid_version, {group, git}, {group, pkg}]. +all() -> [sub_app_deps, newly_added_dep, newly_added_after_empty_lock, + http_proxy_settings, https_proxy_settings, + http_os_proxy_settings, https_os_proxy_settings, + semver_matching_lt, semver_matching_lte, semver_matching_gt, + valid_version, {group, git}, {group, pkg}]. groups() -> [{all, [], [flat, pick_highest_left, pick_highest_right, @@ -82,6 +86,47 @@ init_per_testcase(https_proxy_settings, Config) -> rebar_test_utils:create_config(GlobalConfigDir, [{https_proxy, "http://localhost:1234"} ]), + %% Add a bad value by default to show config overtakes default + os:putenv("https_proxy", "unparseable-garbage"), + rebar_test_utils:init_rebar_state(Config) + end; +init_per_testcase(http_os_proxy_settings, Config) -> + %% Create private rebar.config + Priv = ?config(priv_dir, Config), + GlobalDir = filename:join(Priv, "global"), + GlobalConfigDir = filename:join([GlobalDir, ".config", "rebar3"]), + GlobalConfig = filename:join([GlobalDir, ".config", "rebar3", "rebar.config"]), + + meck:new(rebar_dir, [passthrough]), + meck:expect(rebar_dir, global_config, fun() -> GlobalConfig end), + meck:expect(rebar_dir, global_cache_dir, fun(_) -> GlobalDir end), + + %% Insert proxy variables into os env, but not config + os:putenv("http_proxy", "http://localhost:1234"), + rebar_test_utils:create_config(GlobalConfigDir, []), + rebar_test_utils:init_rebar_state(Config); +init_per_testcase(https_os_proxy_settings, Config) -> + SupportsHttpsProxy = case erlang:system_info(otp_release) of + "R16"++_ -> true; + "R"++_ -> false; + _ -> true % 17 and up don't have a "R" in the version + end, + if not SupportsHttpsProxy -> + {skip, https_proxy_unsupported_before_R16}; + SupportsHttpsProxy -> + %% Create private rebar.config + Priv = ?config(priv_dir, Config), + GlobalDir = filename:join(Priv, "global"), + GlobalConfigDir = filename:join([GlobalDir, ".config", "rebar3"]), + GlobalConfig = filename:join([GlobalDir, ".config", "rebar3", "rebar.config"]), + + meck:new(rebar_dir, [passthrough]), + meck:expect(rebar_dir, global_config, fun() -> GlobalConfig end), + meck:expect(rebar_dir, global_cache_dir, fun(_) -> GlobalDir end), + + %% Insert proxy variables into os env, not in config + os:putenv("https_proxy", "http://localhost:1234"), + rebar_test_utils:create_config(GlobalConfigDir, []), rebar_test_utils:init_rebar_state(Config) end; init_per_testcase(Case, Config) -> @@ -97,11 +142,20 @@ init_per_testcase(Case, Config) -> | setup_project(Case, Config, rebar_test_utils:expand_deps(DepsType, Deps))]. end_per_testcase(https_proxy_settings, Config) -> + os:putenv("https_proxy", ""), meck:unload(rebar_dir), Config; end_per_testcase(http_proxy_settings, Config) -> meck:unload(rebar_dir), Config; +end_per_testcase(http_os_proxy_settings, Config) -> + os:putenv("http_proxy", ""), + meck:unload(rebar_dir), + Config; +end_per_testcase(https_os_proxy_settings, Config) -> + os:putenv("https_proxy", ""), + meck:unload(rebar_dir), + Config; end_per_testcase(_, Config) -> meck:unload(), Config. @@ -311,6 +365,23 @@ https_proxy_settings(_Config) -> ?assertEqual({ok,{{"localhost", 1234}, []}}, httpc:get_option(https_proxy, rebar)). +http_os_proxy_settings(_Config) -> + %% Load config + rebar_utils:set_httpc_options(), + rebar3:init_config(), + + %% Assert variable is right + ?assertEqual({ok,{{"localhost", 1234}, []}}, + httpc:get_option(proxy, rebar)). + +https_os_proxy_settings(_Config) -> + %% Load config + rebar_utils:set_httpc_options(), + rebar3:init_config(), + + %% Assert variable is right + ?assertEqual({ok,{{"localhost", 1234}, []}}, + httpc:get_option(https_proxy, rebar)). semver_matching_lt(_Config) -> Dep = <<"test">>, @@ -405,5 +476,5 @@ in_warnings(git, Warns, NameRaw, VsnRaw) -> in_warnings(pkg, Warns, NameRaw, VsnRaw) -> Name = iolist_to_binary(NameRaw), Vsn = iolist_to_binary(VsnRaw), - 1 =< length([1 || {_, [AppName, {pkg, _, AppVsn}]} <- Warns, + 1 =< length([1 || {_, [AppName, {pkg, _, AppVsn, _}]} <- Warns, AppName =:= Name, AppVsn =:= Vsn]). diff --git a/test/rebar_dialyzer_SUITE.erl b/test/rebar_dialyzer_SUITE.erl index 22a4894..e5d8c52 100644 --- a/test/rebar_dialyzer_SUITE.erl +++ b/test/rebar_dialyzer_SUITE.erl @@ -3,8 +3,14 @@ -export([suite/0, init_per_suite/1, end_per_suite/1, + init_per_group/2, + end_per_group/2, init_per_testcase/2, all/0, + groups/0, + empty_base_plt/1, + empty_app_plt/1, + empty_app_succ_typings/1, update_base_plt/1, update_app_plt/1, build_release_plt/1, @@ -23,6 +29,14 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. +init_per_group(empty, Config) -> + [{base_plt_apps, []} | Config]; +init_per_group(_Group, Config) -> + [{base_plt_apps, [erts]} | Config]. + +end_per_group(_Group, _Config) -> + ok. + init_per_testcase(Testcase, Config) -> PrivDir = ?config(priv_dir, Config), Prefix = ec_cnv:to_list(Testcase), @@ -31,7 +45,7 @@ init_per_testcase(Testcase, Config) -> {plt_location, PrivDir}, {base_plt_prefix, BasePrefix}, {base_plt_location, PrivDir}, - {base_plt_apps, [erts]}], + {base_plt_apps, ?config(base_plt_apps, Config)}], Suffix = "_" ++ rebar_utils:otp_release() ++ "_plt", [{plt, filename:join(PrivDir, Prefix ++ Suffix)}, {base_plt, filename:join(PrivDir, BasePrefix ++ Suffix)}, @@ -39,7 +53,68 @@ init_per_testcase(Testcase, Config) -> rebar_test_utils:init_rebar_state(Config)]. all() -> - [update_base_plt, update_app_plt, build_release_plt, plt_apps_option]. + [{group, empty}, {group, build_and_check}, {group, update}]. + +groups() -> + [{empty, [empty_base_plt, empty_app_plt, empty_app_succ_typings]}, + {build_and_check, [build_release_plt, plt_apps_option]}, + {update, [update_base_plt, update_app_plt]}]. + +empty_base_plt(Config) -> + AppDir = ?config(apps, Config), + RebarConfig = ?config(rebar_config, Config), + BasePlt = ?config(base_plt, Config), + Plt = ?config(plt, Config), + + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [erts]), + + rebar_test_utils:run_and_check(Config, RebarConfig, ["dialyzer"], + {ok, [{app, Name}]}), + + {ok, BasePltFiles} = plt_files(BasePlt), + ?assertEqual([], BasePltFiles), + + ErtsFiles = erts_files(), + {ok, PltFiles} = plt_files(Plt), + ?assertEqual(ErtsFiles, PltFiles), + + ok. + +empty_app_plt(Config) -> + AppDir = ?config(apps, Config), + RebarConfig = ?config(rebar_config, Config), + BasePlt = ?config(base_plt, Config), + Plt = ?config(plt, Config), + + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, []), + + rebar_test_utils:run_and_check(Config, RebarConfig, ["dialyzer"], + {ok, [{app, Name}]}), + + {ok, BasePltFiles} = plt_files(BasePlt), + ?assertEqual([], BasePltFiles), + + {ok, PltFiles} = plt_files(Plt), + ?assertEqual([], PltFiles), + + ok. + +empty_app_succ_typings(Config) -> + AppDir = ?config(apps, Config), + RebarConfig = ?config(rebar_config, Config), + + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_empty_app(AppDir, Name, Vsn, []), + + rebar_test_utils:run_and_check(Config, RebarConfig, ["dialyzer"], + {ok, [{app, Name}]}), + + ok. update_base_plt(Config) -> AppDir = ?config(apps, Config), diff --git a/test/rebar_edoc_SUITE.erl b/test/rebar_edoc_SUITE.erl new file mode 100644 index 0000000..fded2b0 --- /dev/null +++ b/test/rebar_edoc_SUITE.erl @@ -0,0 +1,52 @@ +-module(rebar_edoc_SUITE). +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-compile(export_all). + +all() -> [multiapp]. + +init_per_testcase(multiapp, Config) -> + application:load(rebar), + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + Name = rebar_test_utils:create_random_name("multiapp"), + AppsDir = filename:join([PrivDir, rebar_test_utils:create_random_name(Name)]), + ec_file:copy(filename:join([DataDir, "foo"]), AppsDir, [recursive]), + Verbosity = rebar3:log_level(), + rebar_log:init(command_line, Verbosity), + State = rebar_state:new([{base_dir, filename:join([AppsDir, "_build"])} + ,{root_dir, AppsDir}]), + [{apps, AppsDir}, {state, State}, {name, Name} | Config]. + +end_per_testcase(_, Config) -> + Config. + +multiapp(Config) -> + %% With an empty config (no `dir'), links are being processed + RebarConfig = [], + rebar_test_utils:run_and_check(Config, RebarConfig, ["edoc"], {ok, []}), + %% validate that all doc entries are generated and links work + AppsDir = ?config(apps, Config), + ct:pal("AppsDir: ~s", [AppsDir]), + ?assert(file_content_matches( + filename:join([AppsDir, "apps", "bar1", "doc", "bar1.html"]), + "barer1")), + ?assert(file_content_matches( + filename:join([AppsDir, "apps", "bar2", "doc", "bar2.html"]), + "barer2")), + %% Links are in place for types + ?assert(file_content_matches( + filename:join([AppsDir, "apps", "foo", "doc", "foo.html"]), + "barer1")), + ?assert(file_content_matches( + filename:join([AppsDir, "apps", "foo", "doc", "foo.html"]), + "apps/bar1/doc/bar1.html")). + + +file_content_matches(Path, Regex) -> + case file:read_file(Path) of + {ok, Bin} -> + nomatch =/= re:run(Bin, Regex); + {error, Reason} -> + Reason + end. diff --git a/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1.app.src b/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1.app.src new file mode 100644 index 0000000..6e7ec24 --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1.app.src @@ -0,0 +1,16 @@ +{application, bar1, + [{description, "An OTP application"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, { bar1_app, []}}, + {applications, + [kernel, + stdlib + ]}, + {env,[]}, + {modules, []}, + + {maintainers, []}, + {licenses, []}, + {links, []} + ]}. diff --git a/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1.erl b/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1.erl new file mode 100644 index 0000000..2700aef --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1.erl @@ -0,0 +1,9 @@ +-module(bar1).
+-export([bar1/0]).
+-export_type([barer1/0]).
+
+-type barer1() :: string().
+
+% @doc Bar1 bars the bar.
+-spec bar1() -> barer1().
+bar1() -> "Barer1".
\ No newline at end of file diff --git a/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1_app.erl b/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1_app.erl new file mode 100644 index 0000000..414ac30 --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1_app.erl @@ -0,0 +1,26 @@ +%%%------------------------------------------------------------------- +%% @doc bar1 public API +%% @end +%%%------------------------------------------------------------------- + +-module(bar1_app). + +-behaviour(application). + +%% Application callbacks +-export([start/2, stop/1]). + +%%==================================================================== +%% API +%%==================================================================== + +start(_StartType, _StartArgs) -> + bar1_sup:start_link(). + +%%-------------------------------------------------------------------- +stop(_State) -> + ok. + +%%==================================================================== +%% Internal functions +%%==================================================================== diff --git a/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1_sup.erl b/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1_sup.erl new file mode 100644 index 0000000..f9d6670 --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/bar1/src/bar1_sup.erl @@ -0,0 +1,35 @@ +%%%------------------------------------------------------------------- +%% @doc bar1 top level supervisor. +%% @end +%%%------------------------------------------------------------------- + +-module(bar1_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%==================================================================== +%% API functions +%%==================================================================== + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%%==================================================================== +%% Supervisor callbacks +%%==================================================================== + +%% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} +init([]) -> + {ok, { {one_for_all, 0, 1}, []} }. + +%%==================================================================== +%% Internal functions +%%==================================================================== diff --git a/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2.app.src b/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2.app.src new file mode 100644 index 0000000..58de8bc --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2.app.src @@ -0,0 +1,16 @@ +{application, bar2, + [{description, "An OTP application"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, { bar2_app, []}}, + {applications, + [kernel, + stdlib + ]}, + {env,[]}, + {modules, []}, + + {maintainers, []}, + {licenses, []}, + {links, []} + ]}. diff --git a/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2.erl b/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2.erl new file mode 100644 index 0000000..c639db0 --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2.erl @@ -0,0 +1,9 @@ +-module(bar2).
+-export([bar2/0]).
+-export_type([barer2/0]).
+
+-type barer2() :: string().
+
+% @doc Bar2 bars the bar2.
+-spec bar2() -> barer2().
+bar2() -> "Barer2".
\ No newline at end of file diff --git a/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2_app.erl b/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2_app.erl new file mode 100644 index 0000000..d0058a0 --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2_app.erl @@ -0,0 +1,26 @@ +%%%------------------------------------------------------------------- +%% @doc bar2 public API +%% @end +%%%------------------------------------------------------------------- + +-module(bar2_app). + +-behaviour(application). + +%% Application callbacks +-export([start/2, stop/1]). + +%%==================================================================== +%% API +%%==================================================================== + +start(_StartType, _StartArgs) -> + bar2_sup:start_link(). + +%%-------------------------------------------------------------------- +stop(_State) -> + ok. + +%%==================================================================== +%% Internal functions +%%==================================================================== diff --git a/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2_sup.erl b/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2_sup.erl new file mode 100644 index 0000000..0bdaf4a --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/bar2/src/bar2_sup.erl @@ -0,0 +1,35 @@ +%%%------------------------------------------------------------------- +%% @doc bar2 top level supervisor. +%% @end +%%%------------------------------------------------------------------- + +-module(bar2_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%==================================================================== +%% API functions +%%==================================================================== + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%%==================================================================== +%% Supervisor callbacks +%%==================================================================== + +%% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} +init([]) -> + {ok, { {one_for_all, 0, 1}, []} }. + +%%==================================================================== +%% Internal functions +%%==================================================================== diff --git a/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo.app.src b/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo.app.src new file mode 100644 index 0000000..9987fd5 --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo.app.src @@ -0,0 +1,17 @@ +{application, foo, + [{description, "An OTP application"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, { foo_app, []}}, + {applications, + [kernel, + stdlib, + bar1, bar2 + ]}, + {env,[]}, + {modules, []}, + + {maintainers, []}, + {licenses, []}, + {links, []} + ]}. diff --git a/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo.erl b/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo.erl new file mode 100644 index 0000000..52e3d0a --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo.erl @@ -0,0 +1,19 @@ +-module(foo).
+
+-export([foo/0, bar1/0, bar2/0]).
+
+-export_type([fooer/0]).
+
+-type fooer() :: string().
+
+% @doc Foo function returns fooer.
+-spec foo() -> fooer().
+foo() -> "fooer".
+
+% @doc Bar1 function returns barer1.
+-spec bar1() -> bar1:barer1().
+bar1() -> bar1:bar1().
+
+% @doc Bar2 functions returns barer2.
+-spec bar2() -> bar2:barer2().
+bar2() -> bar2:bar2().
\ No newline at end of file diff --git a/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo_app.erl b/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo_app.erl new file mode 100644 index 0000000..d0158d7 --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo_app.erl @@ -0,0 +1,26 @@ +%%%------------------------------------------------------------------- +%% @doc foo public API +%% @end +%%%------------------------------------------------------------------- + +-module(foo_app). + +-behaviour(application). + +%% Application callbacks +-export([start/2, stop/1]). + +%%==================================================================== +%% API +%%==================================================================== + +start(_StartType, _StartArgs) -> + foo_sup:start_link(). + +%%-------------------------------------------------------------------- +stop(_State) -> + ok. + +%%==================================================================== +%% Internal functions +%%==================================================================== diff --git a/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo_sup.erl b/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo_sup.erl new file mode 100644 index 0000000..67e88b4 --- /dev/null +++ b/test/rebar_edoc_SUITE_data/foo/apps/foo/src/foo_sup.erl @@ -0,0 +1,35 @@ +%%%------------------------------------------------------------------- +%% @doc foo top level supervisor. +%% @end +%%%------------------------------------------------------------------- + +-module(foo_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%==================================================================== +%% API functions +%%==================================================================== + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%%==================================================================== +%% Supervisor callbacks +%%==================================================================== + +%% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} +init([]) -> + {ok, { {one_for_all, 0, 1}, []} }. + +%%==================================================================== +%% Internal functions +%%==================================================================== diff --git a/test/rebar_escriptize_SUITE.erl b/test/rebar_escriptize_SUITE.erl index 1817d6b..139d5cd 100644 --- a/test/rebar_escriptize_SUITE.erl +++ b/test/rebar_escriptize_SUITE.erl @@ -5,6 +5,8 @@ end_per_suite/1, init_per_testcase/2, all/0, + escriptize_with_name/1, + escriptize_with_bad_name/1, build_and_clean_app/1]). -include_lib("common_test/include/ct.hrl"). @@ -24,7 +26,11 @@ init_per_testcase(_, Config) -> rebar_test_utils:init_rebar_state(Config). all() -> - [build_and_clean_app]. + [ + build_and_clean_app, + escriptize_with_name, + escriptize_with_bad_name + ]. %% Test escriptize builds and runs the app's escript build_and_clean_app(Config) -> @@ -35,3 +41,21 @@ build_and_clean_app(Config) -> rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), rebar_test_utils:run_and_check(Config, [], ["escriptize"], {ok, [{app, Name, valid}]}). + +escriptize_with_name(Config) -> + AppDir = ?config(apps, Config), + + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + rebar_test_utils:run_and_check(Config, [{escript_main_app, Name}], ["escriptize"], + {ok, [{app, Name, valid}]}). + +escriptize_with_bad_name(Config) -> + AppDir = ?config(apps, Config), + + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + rebar_test_utils:run_and_check(Config, [{escript_main_app, boogers}], ["escriptize"], + {error,{rebar_prv_escriptize, {bad_name, boogers}}}). diff --git a/test/rebar_file_utils_SUITE.erl b/test/rebar_file_utils_SUITE.erl index c1f85b3..a44a06d 100644 --- a/test/rebar_file_utils_SUITE.erl +++ b/test/rebar_file_utils_SUITE.erl @@ -12,7 +12,9 @@ reset_empty_dir/1, reset_dir/1, path_from_ancestor/1, - canonical_path/1]). + canonical_path/1, + resolve_link/1, + split_dirname/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -22,7 +24,10 @@ all() -> [{group, tmpdir}, {group, reset_dir}, - path_from_ancestor, canonical_path]. + path_from_ancestor, + canonical_path, + resolve_link, + split_dirname]. groups() -> [{tmpdir, [], [raw_tmpdir, empty_tmpdir, simple_tmpdir, multi_tmpdir]}, @@ -111,3 +116,22 @@ canonical_path(_Config) -> ?assertEqual(Root ++ "foo", rebar_file_utils:canonical_path("/foo/./.")), ?assertEqual(filename:nativename(Root ++ "foo/bar"), rebar_file_utils:canonical_path("/foo/./bar")). + +resolve_link(_Config) -> + TmpDir = rebar_file_utils:system_tmpdir( + ["rebar_file_utils_SUITE", "resolve_link"]), + Link = filename:join(TmpDir, "link"), + Target = filename:join(TmpDir, "link-target"), + ec_file:remove(TmpDir, [recursive]), + ok = filelib:ensure_dir(Target), + ok = file:write_file(Target, <<>>), + ok = file:make_symlink(Target, Link), + ?assertEqual(Target, rebar_file_utils:resolve_link(Link)). + +split_dirname(_Config) -> + ?assertEqual({".", ""}, rebar_file_utils:split_dirname("")), + ?assertEqual({"/", ""}, rebar_file_utils:split_dirname("/")), + ?assertEqual({"/", "foo"}, rebar_file_utils:split_dirname("/foo")), + ?assertEqual({".", "foo"}, rebar_file_utils:split_dirname("foo")), + ?assertEqual({"/foo", "bar"}, rebar_file_utils:split_dirname("/foo/bar")), + ?assertEqual({"foo", "bar"}, rebar_file_utils:split_dirname("foo/bar")). diff --git a/test/rebar_install_deps_SUITE.erl b/test/rebar_install_deps_SUITE.erl index b8b70b3..9ff28c7 100644 --- a/test/rebar_install_deps_SUITE.erl +++ b/test/rebar_install_deps_SUITE.erl @@ -475,5 +475,5 @@ in_warnings(git, Warns, NameRaw, VsnRaw) -> in_warnings(pkg, Warns, NameRaw, VsnRaw) -> Name = iolist_to_binary(NameRaw), Vsn = iolist_to_binary(VsnRaw), - 1 =< length([1 || {_, [AppName, {pkg, _, AppVsn}]} <- Warns, + 1 =< length([1 || {_, [AppName, {pkg, _, AppVsn, _}]} <- Warns, AppName =:= Name, AppVsn =:= Vsn]). diff --git a/test/rebar_lock_SUITE.erl b/test/rebar_lock_SUITE.erl index 00875f7..f1ab3b5 100644 --- a/test/rebar_lock_SUITE.erl +++ b/test/rebar_lock_SUITE.erl @@ -7,7 +7,8 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -all() -> [current_version, future_versions_no_attrs, future_versions_attrs]. +all() -> [current_version, + beta_version, future_versions_no_attrs, future_versions_attrs]. current_version(Config) -> %% Current version just dumps the locks as is on disk. @@ -15,9 +16,60 @@ current_version(Config) -> Locks = [{<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2}, {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, + {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>},3}, + {<<"pkg2">>,{pkg,<<"name1">>,<<"1.1.6">>},2}, + {<<"pkg3">>,{pkg,<<"name2">>,<<"3.0.6">>},1} + ], + ExpandedNull = [ + {<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2}, + {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, + {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, + {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>,undefined},3}, + {<<"pkg2">>,{pkg,<<"name1">>,<<"1.1.6">>,undefined},2}, + {<<"pkg3">>,{pkg,<<"name2">>,<<"3.0.6">>,undefined},1} + ], + %% Simulate a beta lockfile + file:write_file(LockFile, io_lib:format("~p.~n", [Locks])), + %% No properties fetched from a beta lockfile, expand locks + %% to undefined + ?assertEqual(ExpandedNull, + rebar_config:consult_lock_file(LockFile)), + %% Adding hash data + Hashes = [{<<"pkg1">>, <<"tarballhash">>}, + {<<"pkg3">>, <<"otherhash">>}], + ExpandedLocks = [ + {<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2}, + {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, + {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, + {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>,<<"tarballhash">>},3}, + {<<"pkg2">>,{pkg,<<"name1">>,<<"1.1.6">>,undefined},2}, + {<<"pkg3">>,{pkg,<<"name2">>,<<"3.0.6">>,<<"otherhash">>},1} + ], + file:write_file(LockFile, + io_lib:format("~p.~n~p.~n", + [{"1.1.0", Locks}, + [{pkg_hash, Hashes}]])), + ?assertEqual(ExpandedLocks, rebar_config:consult_lock_file(LockFile)), + %% Then check that we can reverse that + ok = rebar_config:write_lock_file(LockFile, ExpandedLocks), + ?assertEqual({ok, [{"1.1.0", Locks}, [{pkg_hash, Hashes}]]}, + file:consult(LockFile)). + +beta_version(Config) -> + %% Current version just dumps the locks as is on disk. + LockFile = filename:join(?config(priv_dir, Config), "current_version"), + Locks = [{<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2}, + {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, + {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>},3}], + ExpandedLocks = [ + {<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2}, + {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, + {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, + {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>,undefined},3} + ], file:write_file(LockFile, io_lib:format("~p.~n", [Locks])), - ?assertEqual(Locks, rebar_config:consult_lock_file(LockFile)). + ?assertEqual(ExpandedLocks, rebar_config:consult_lock_file(LockFile)). future_versions_no_attrs(Config) -> %% Future versions will keep the same core attribute in there, but @@ -27,10 +79,14 @@ future_versions_no_attrs(Config) -> Locks = [{<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2}, {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, - {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>},3}], + {<<"pkg1">>, {pkg,<<"name">>,<<"0.1.6">>},3}], + ExpandedLocks = [{<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2}, + {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, + {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, + {<<"pkg1">>, {pkg,<<"name">>,<<"0.1.6">>,undefined},3}], LockData = {"3.5.2", Locks}, file:write_file(LockFile, io_lib:format("~p.~n", [LockData])), - ?assertEqual(Locks, rebar_config:consult_lock_file(LockFile)). + ?assertEqual(ExpandedLocks, rebar_config:consult_lock_file(LockFile)). future_versions_attrs(Config) -> %% Future versions will keep the same core attribute in there, but @@ -41,6 +97,16 @@ future_versions_attrs(Config) -> {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>},3}], + ExpandedLocks = [{<<"app1">>, {git,"some_url", {ref,"some_ref"}}, 2}, + {<<"app2">>, {git,"some_url", {ref,"some_ref"}}, 0}, + {<<"app3">>, {hg,"some_url", {ref,"some_ref"}}, 1}, + {<<"pkg1">>,{pkg,<<"name">>,<<"0.1.6">>, <<"tarballhash">>},3}], + Hashes = [{<<"pkg1">>, <<"tarballhash">>}], LockData = {"3.5.2", Locks}, - file:write_file(LockFile, io_lib:format("~p.~na.~n{b,c}.~n[d,e,f].~n", [LockData])), - ?assertEqual(Locks, rebar_config:consult_lock_file(LockFile)). + file:write_file(LockFile, + io_lib:format("~p.~n~p.~ngarbage.~n", + [LockData, + [{a, x}, + {pkg_hash, Hashes}, + {b, y}]])), + ?assertEqual(ExpandedLocks, rebar_config:consult_lock_file(LockFile)). diff --git a/test/rebar_pkg_SUITE.erl b/test/rebar_pkg_SUITE.erl index 6a75f32..30cc0a8 100644 --- a/test/rebar_pkg_SUITE.erl +++ b/test/rebar_pkg_SUITE.erl @@ -11,6 +11,7 @@ -define(good_checksum, <<"1C6CE379D191FBAB41B7905075E0BF87CBBE23C77CECE775C5A0B786B2244C35">>). all() -> [good_uncached, good_cached, badindexchk, badpkg, + badhash_nocache, badhash_cache, bad_to_good, good_disconnect, bad_disconnect, pkgs_provider, find_highest_matching]. @@ -58,6 +59,19 @@ init_per_testcase(badpkg=Name, Config0) -> {pkg, {<<"badpkg">>, <<"1.0.0">>}} | Config0], mock_config(Name, Config); +init_per_testcase(badhash_nocache=Name, Config0) -> + Config = [{good_cache, false}, + {pkg, {<<"goodpkg">>, <<"1.0.0">>}} + | Config0], + mock_config(Name, Config); +init_per_testcase(badhash_cache=Name, Config0) -> + Pkg = {<<"goodpkg">>, <<"1.0.0">>}, + Config1 = [{good_cache, true}, + {pkg, Pkg} + | Config0], + Config = mock_config(Name, Config1), + copy_to_cache(Pkg, Config), + Config; init_per_testcase(bad_to_good=Name, Config0) -> Config1 = [{good_cache, false}, {pkg, {<<"goodpkg">>, <<"1.0.0">>}} @@ -103,7 +117,7 @@ good_uncached(Config) -> {Pkg,Vsn} = ?config(pkg, Config), State = ?config(state, Config), ?assertEqual({ok, true}, - rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)), + rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum}, State)), Cache = ?config(cache_dir, Config), ?assert(filelib:is_regular(filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".tar">>))). @@ -116,7 +130,7 @@ good_cached(Config) -> ?assert(filelib:is_regular(CachedFile)), {ok, Content} = file:read_file(CachedFile), ?assertEqual({ok, true}, - rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)), + rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum}, State)), {ok, Content} = file:read_file(CachedFile). badindexchk(Config) -> @@ -124,7 +138,7 @@ badindexchk(Config) -> {Pkg,Vsn} = ?config(pkg, Config), State = ?config(state, Config), ?assertMatch({bad_registry_checksum, _Path}, - rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)), + rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum}, State)), %% The cached file is there for forensic purposes Cache = ?config(cache_dir, Config), ?assert(filelib:is_regular(filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".tar">>))). @@ -134,11 +148,35 @@ badpkg(Config) -> {Pkg,Vsn} = ?config(pkg, Config), State = ?config(state, Config), ?assertMatch({bad_download, _Path}, - rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)), + rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum}, State)), %% The cached file is there for forensic purposes Cache = ?config(cache_dir, Config), ?assert(filelib:is_regular(filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".tar">>))). +badhash_nocache(Config) -> + Tmp = ?config(tmp_dir, Config), + {Pkg,Vsn} = ?config(pkg, Config), + State = ?config(state, Config), + ?assertMatch({unexpected_hash, _Path, ?bad_checksum, ?good_checksum}, + rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?bad_checksum}, State)), + %% The cached file is there for forensic purposes + Cache = ?config(cache_dir, Config), + ?assert(filelib:is_regular(filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".tar">>))). + +badhash_cache(Config) -> + Tmp = ?config(tmp_dir, Config), + {Pkg,Vsn} = ?config(pkg, Config), + Cache = ?config(cache_dir, Config), + State = ?config(state, Config), + CachedFile = filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".tar">>), + ?assert(filelib:is_regular(CachedFile)), + {ok, Content} = file:read_file(CachedFile), + ?assertMatch({unexpected_hash, _Path, ?bad_checksum, ?good_checksum}, + rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?bad_checksum}, State)), + %% The cached file is there still, unchanged. + ?assert(filelib:is_regular(CachedFile)), + ?assertEqual({ok, Content}, file:read_file(CachedFile)). + bad_to_good(Config) -> Tmp = ?config(tmp_dir, Config), {Pkg,Vsn} = ?config(pkg, Config), @@ -148,7 +186,7 @@ bad_to_good(Config) -> ?assert(filelib:is_regular(CachedFile)), {ok, Contents} = file:read_file(CachedFile), ?assertEqual({ok, true}, - rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)), + rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum}, State)), %% Cache has refreshed ?assert({ok, Contents} =/= file:read_file(CachedFile)). @@ -161,7 +199,7 @@ good_disconnect(Config) -> ?assert(filelib:is_regular(CachedFile)), {ok, Content} = file:read_file(CachedFile), ?assertEqual({ok, true}, - rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)), + rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum}, State)), {ok, Content} = file:read_file(CachedFile). bad_disconnect(Config) -> @@ -169,7 +207,7 @@ bad_disconnect(Config) -> {Pkg,Vsn} = ?config(pkg, Config), State = ?config(state, Config), ?assertEqual({fetch_fail, Pkg, Vsn}, - rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)). + rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum}, State)). pkgs_provider(Config) -> Config1 = rebar_test_utils:init_rebar_state(Config), diff --git a/test/rebar_pkg_alias_SUITE.erl b/test/rebar_pkg_alias_SUITE.erl index fef2310..8915357 100644 --- a/test/rebar_pkg_alias_SUITE.erl +++ b/test/rebar_pkg_alias_SUITE.erl @@ -55,7 +55,7 @@ diff_alias(Config) -> Config, RebarConfig, ["lock"], {ok, [{lock, "fakelib"},{dep, "fakelib"}]} ), - {ok, [LockData]} = file:consult(Lockfile), + {ok, [{_Vsn, LockData}|_]} = file:consult(Lockfile), ?assert(lists:any(fun({<<"fakelib">>,{pkg,<<"goodpkg">>,_},_}) -> true ; (_) -> false end, LockData)), %% An second run yields the same @@ -63,13 +63,13 @@ diff_alias(Config) -> Config, RebarConfig, ["lock"], {ok, [{lock, "fakelib"},{dep, "fakelib"}]} ), - {ok, [LockData]} = file:consult(Lockfile), + {ok, [{_Vsn, LockData}|_]} = file:consult(Lockfile), %% So does an upgrade rebar_test_utils:run_and_check( Config, RebarConfig, ["upgrade"], {ok, [{lock, "fakelib"},{dep, "fakelib"}]} ), - {ok, [LockData]} = file:consult(Lockfile). + {ok, [{_Vsn, LockData}|_]} = file:consult(Lockfile). diff_alias_vsn(Config) -> diff_alias(Config). diff --git a/test/rebar_test_utils.erl b/test/rebar_test_utils.erl index 23b0178..8c177c9 100644 --- a/test/rebar_test_utils.erl +++ b/test/rebar_test_utils.erl @@ -7,6 +7,13 @@ create_config/2, create_config/3, package_app/3]). -export([create_random_name/1, create_random_vsn/0, write_src_file/2]). +%% Pick the right random module +-ifdef(rand_only). +-define(random, rand). +-else. +-define(random, random). +-endif. + %%%%%%%%%%%%%% %%% Public %%% %%%%%%%%%%%%%% @@ -126,20 +133,24 @@ create_config(_AppDir, ConfFilename, Contents) -> %% @doc Util to create a random variation of a given name. create_random_name(Name) -> random_seed(), - Name ++ erlang:integer_to_list(random:uniform(1000000)). + Name ++ erlang:integer_to_list(?random:uniform(1000000)). %% @doc Util to create a random variation of a given version. create_random_vsn() -> random_seed(), - lists:flatten([erlang:integer_to_list(random:uniform(100)), - ".", erlang:integer_to_list(random:uniform(100)), - ".", erlang:integer_to_list(random:uniform(100))]). + lists:flatten([erlang:integer_to_list(?random:uniform(100)), + ".", erlang:integer_to_list(?random:uniform(100)), + ".", erlang:integer_to_list(?random:uniform(100))]). +-ifdef(rand_only). +random_seed() -> + %% the rand module self-seeds + ok. +-else. random_seed() -> <<A:32, B:32, C:32>> = crypto:rand_bytes(12), random:seed({A,B,C}). - - +-endif. expand_deps(_, []) -> []; expand_deps(git, [{Name, Deps} | Rest]) -> @@ -149,21 +160,21 @@ expand_deps(git, [{Name, Vsn, Deps} | Rest]) -> Dep = {Name, Vsn, {git, "https://example.org/user/"++Name++".git", {tag, Vsn}}}, [{Dep, expand_deps(git, Deps)} | expand_deps(git, Rest)]; expand_deps(pkg, [{Name, Deps} | Rest]) -> - Dep = {pkg, Name, "0.0.0"}, + Dep = {pkg, Name, "0.0.0", undefined}, [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)]; expand_deps(pkg, [{Name, Vsn, Deps} | Rest]) -> - Dep = {pkg, Name, Vsn}, + Dep = {pkg, Name, Vsn, undefined}, [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)]; expand_deps(mixed, [{Name, Deps} | Rest]) -> Dep = if hd(Name) >= $a, hd(Name) =< $z -> - {pkg, string:to_upper(Name), "0.0.0"} + {pkg, string:to_upper(Name), "0.0.0", undefined} ; hd(Name) >= $A, hd(Name) =< $Z -> {Name, ".*", {git, "https://example.org/user/"++Name++".git", "master"}} end, [{Dep, expand_deps(mixed, Deps)} | expand_deps(mixed, Rest)]; expand_deps(mixed, [{Name, Vsn, Deps} | Rest]) -> Dep = if hd(Name) >= $a, hd(Name) =< $z -> - {pkg, string:to_upper(Name), Vsn} + {pkg, string:to_upper(Name), Vsn, undefined} ; hd(Name) >= $A, hd(Name) =< $Z -> {Name, Vsn, {git, "https://example.org/user/"++Name++".git", {tag, Vsn}}} end, @@ -177,7 +188,7 @@ expand_deps(mixed, [{Name, Vsn, Deps} | Rest]) -> flat_deps(Deps) -> flat_deps(Deps, [], []). flat_deps([], Src, Pkg) -> {Src, Pkg}; -flat_deps([{{pkg, Name, Vsn}, PkgDeps} | Rest], Src, Pkg) -> +flat_deps([{{pkg, Name, Vsn, undefined}, PkgDeps} | Rest], Src, Pkg) -> Current = {{iolist_to_binary(Name), iolist_to_binary(Vsn)}, top_level_deps(PkgDeps)}, {[], FlatPkgDeps} = flat_deps(PkgDeps), @@ -195,7 +206,7 @@ vsn_from_ref({git, _, {_, Vsn}}) -> Vsn; vsn_from_ref({git, _, Vsn}) -> Vsn. top_level_deps([]) -> []; -top_level_deps([{{pkg, Name, Vsn}, _} | Deps]) -> +top_level_deps([{{pkg, Name, Vsn, undefined}, _} | Deps]) -> [{list_to_atom(Name), Vsn} | top_level_deps(Deps)]; top_level_deps([{{Name, Vsn, Ref}, _} | Deps]) -> [{list_to_atom(Name), Vsn, Ref} | top_level_deps(Deps)]. @@ -306,9 +317,10 @@ check_results(AppDir, Expected, ProfileRun) -> case lists:keyfind(iolist_to_binary(Name), 1, Locks) of false -> error({lock_not_found, Name}); - {_LockName, {pkg, _, LockVsn}, _} -> + {_LockName, {pkg, _, LockVsn, Hash}, _} -> ?assertEqual(iolist_to_binary(Vsn), - iolist_to_binary(LockVsn)); + iolist_to_binary(LockVsn)), + ?assertNotEqual(undefined, Hash); {_LockName, {_, _, {ref, LockVsn}}, _} -> ?assertEqual(iolist_to_binary(Vsn), iolist_to_binary(LockVsn)) @@ -318,9 +330,10 @@ check_results(AppDir, Expected, ProfileRun) -> case lists:keyfind(iolist_to_binary(Name), 1, Locks) of false -> error({lock_not_found, Name}); - {_LockName, {pkg, _, LockVsn}, _} -> + {_LockName, {pkg, _, LockVsn, Hash}, _} -> ?assertEqual(iolist_to_binary(Vsn), - iolist_to_binary(LockVsn)); + iolist_to_binary(LockVsn)), + ?assertNotEqual(undefined, Hash); {_LockName, {_, _, {ref, LockVsn}}, _} -> error({source_lock, {Name, LockVsn}}) end @@ -329,7 +342,7 @@ check_results(AppDir, Expected, ProfileRun) -> case lists:keyfind(iolist_to_binary(Name), 1, Locks) of false -> error({lock_not_found, Name}); - {_LockName, {pkg, _, LockVsn}, _} -> + {_LockName, {pkg, _, LockVsn, _}, _} -> error({pkg_lock, {Name, LockVsn}}); {_LockName, {_, _, {ref, LockVsn}}, _} -> ?assertEqual(iolist_to_binary(Vsn), diff --git a/test/rebar_unlock_SUITE.erl b/test/rebar_unlock_SUITE.erl index 31dca72..8dbdb3a 100644 --- a/test/rebar_unlock_SUITE.erl +++ b/test/rebar_unlock_SUITE.erl @@ -3,8 +3,14 @@ -include_lib("eunit/include/eunit.hrl"). -compile(export_all). -all() -> [unlock, unlock_all]. +all() -> [pkgunlock, unlock, unlock_all]. +init_per_testcase(pkgunlock, Config0) -> + Config = rebar_test_utils:init_rebar_state(Config0, "pkgunlock"), + Lockfile = filename:join(?config(apps, Config), "rebar.lock"), + ec_file:copy(filename:join(?config(data_dir, Config), "pkg.rebar.lock"), + Lockfile), + [{lockfile, Lockfile} | Config]; init_per_testcase(Case, Config0) -> Config = rebar_test_utils:init_rebar_state(Config0, atom_to_list(Case)), Lockfile = filename:join(?config(apps, Config), "rebar.lock"), @@ -15,6 +21,23 @@ init_per_testcase(Case, Config0) -> end_per_testcase(_, Config) -> Config. +pkgunlock(Config) -> + Locks = read_locks(Config), + Hashes = read_hashes(Config), + rebar_test_utils:run_and_check(Config, [], ["unlock", "fakeapp"], {ok, []}), + Locks = read_locks(Config), + Hashes = read_hashes(Config), + rebar_test_utils:run_and_check(Config, [], ["unlock", "bbmustache"], {ok, []}), + ?assertEqual(Locks -- ["bbmustache"], read_locks(Config)), + ?assertEqual(Hashes -- ["bbmustache"], read_hashes(Config)), + rebar_test_utils:run_and_check(Config, [], ["unlock", "cf,certifi"], {ok, []}), + ?assertEqual(Locks -- ["bbmustache","cf","certifi"], read_locks(Config)), + ?assertEqual(Hashes -- ["bbmustache","cf","certifi"], read_hashes(Config)), + rebar_test_utils:run_and_check(Config, [], ["unlock", string:join(Locks,",")], {ok, []}), + ?assertEqual({error, enoent}, read_locks(Config)), + ?assertEqual({error, enoent}, read_hashes(Config)), + ok. + unlock(Config) -> Locks = read_locks(Config), rebar_test_utils:run_and_check(Config, [], ["unlock", "fakeapp"], {ok, []}), @@ -35,6 +58,20 @@ unlock_all(Config) -> read_locks(Config) -> case file:consult(?config(lockfile, Config)) of - {ok, [Locks]} -> [binary_to_list(element(1,Lock)) || Lock <- Locks]; - Other -> Other + {ok, _} -> + Locks = rebar_config:consult_lock_file(?config(lockfile, Config)), + [binary_to_list(element(1,Lock)) || Lock <- Locks]; + Other -> + Other + end. + +read_hashes(Config) -> + case file:consult(?config(lockfile, Config)) of + {ok, [{_Vsn, _Locks},Props|_]} -> + Hashes = proplists:get_value(pkg_hash, Props, []), + [binary_to_list(element(1,Hash)) || Hash <- Hashes]; + {ok, [{_Vsn, _Locks}]} -> + []; + Other -> + Other end. diff --git a/test/rebar_unlock_SUITE_data/pkg.rebar.lock b/test/rebar_unlock_SUITE_data/pkg.rebar.lock new file mode 100644 index 0000000..231e266 --- /dev/null +++ b/test/rebar_unlock_SUITE_data/pkg.rebar.lock @@ -0,0 +1,24 @@ +{"1.1.0", +[{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.0.4">>},0}, + {<<"certifi">>,{pkg,<<"certifi">>,<<"0.4.0">>},0}, + {<<"cf">>,{pkg,<<"cf">>,<<"0.2.1">>},0}, + {<<"cth_readable">>,{pkg,<<"cth_readable">>,<<"1.2.3">>},0}, + {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.21.0">>},0}, + {<<"eunit_formatters">>,{pkg,<<"eunit_formatters">>,<<"0.3.1">>},0}, + {<<"getopt">>,{pkg,<<"getopt">>,<<"0.8.2">>},0}, + {<<"providers">>,{pkg,<<"providers">>,<<"1.6.0">>},0}, + {<<"relx">>,{pkg,<<"relx">>,<<"3.20.0">>},0}, + {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.1">>},0}]}. +[ +{pkg_hash,[ + {<<"bbmustache">>, <<"7BA94F971C5AFD7B6617918A4BB74705E36CAB36EB84B19B6A1B7EE06427AA38">>}, + {<<"certifi">>, <<"A7966EFB868B179023618D29A407548F70C52466BF1849B9E8EBD0E34B7EA11F">>}, + {<<"cf">>, <<"69D0B1349FD4D7D4DC55B7F407D29D7A840BF9A1EF5AF529F1EBE0CE153FC2AB">>}, + {<<"cth_readable">>, <<"293120673DFF82F0768612C5282E35C40CACC1B6F94FE99077438FD3749D0E27">>}, + {<<"erlware_commons">>, <<"A04433071AD7D112EDEFC75AC77719DD3E6753E697AC09428FC83D7564B80B15">>}, + {<<"eunit_formatters">>, <<"7A6FC351EB5B873E2356B8852EB751E20C13A72FBCA03393CF682B8483509573">>}, + {<<"getopt">>, <<"B17556DB683000BA50370B16C0619DF1337E7AF7ECBF7D64FBF8D1D6BCE3109B">>}, + {<<"providers">>, <<"DB0E2F9043AE60C0155205FCD238D68516331D0E5146155E33D1E79DC452964A">>}, + {<<"relx">>, <<"B515B8317D25B3A1508699294C3D1FA6DC0527851DFFC87446661BCE21A36710">>}, + {<<"ssl_verify_fun">>, <<"28A4D65B7F59893BC2C7DE786DEC1E1555BD742D336043FE644AE956C3497FBE">>}]} +]. diff --git a/test/rebar_upgrade_SUITE.erl b/test/rebar_upgrade_SUITE.erl index cfe1d8a..66e1fdf 100644 --- a/test/rebar_upgrade_SUITE.erl +++ b/test/rebar_upgrade_SUITE.erl @@ -11,7 +11,7 @@ groups() -> triplet_a, triplet_b, triplet_c, tree_a, tree_b, tree_c, tree_c2, tree_cj, tree_ac, tree_all, delete_d, promote, stable_lock, fwd_lock, - compile_upgrade_parity]}, + compile_upgrade_parity, umbrella_config]}, {git, [], [{group, all}]}, {pkg, [], [{group, all}]}]. @@ -66,6 +66,18 @@ end_per_testcase(_, Config) -> meck:unload(), Config. +setup_project(Case=umbrella_config, Config0, Deps, UpDeps) -> + DepsType = ?config(deps_type, Config0), + NameRoot = atom_to_list(Case)++"_"++atom_to_list(DepsType), + Config = rebar_test_utils:init_rebar_state(Config0, NameRoot++"_"), + AppDir = filename:join([?config(apps, Config), "apps", NameRoot]), + rebar_test_utils:create_app(AppDir, "Root", "0.0.0", [kernel, stdlib]), + TopDeps = rebar_test_utils:top_level_deps(Deps), + TopConf = rebar_test_utils:create_config(AppDir, [{deps, []}]), + RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps}]), + [{rebarconfig, TopConf}, + {rebarumbrella, RebarConf}, + {next_top_deps, rebar_test_utils:top_level_deps(UpDeps)} | Config]; setup_project(Case, Config0, Deps, UpDeps) -> DepsType = ?config(deps_type, Config0), Config = rebar_test_utils:init_rebar_state( @@ -437,7 +449,12 @@ upgrades(compile_upgrade_parity) -> [], {"", [{"A","1"}, "D", "J", "E", {"I","1"}, {"B","1"}, "F", "G", - {"C","1"}, "H"]}}. + {"C","1"}, "H"]}}; +upgrades(umbrella_config) -> + {[{"A", "1", []}], + [{"A", "2", []}], + ["A"], + {"A", [{"A","2"}]}}. %% TODO: add a test that verifies that unlocking files and then %% running the upgrade code is enough to properly upgrade things. @@ -570,9 +587,36 @@ compile_upgrade_parity(Config) -> ?assertEqual(CompileLockData1, CompileLockData2), ?assertEqual(CompileLockData1, UpgradeLockData). +umbrella_config(Config) -> + apply(?config(mock, Config), []), + {ok, TopConfig} = file:consult(?config(rebarconfig, Config)), + %% Install dependencies before re-mocking for an upgrade + rebar_test_utils:run_and_check(Config, TopConfig, ["lock"], {ok, []}), + {App, Unlocks} = ?config(expected, Config), + ct:pal("Upgrades: ~p -> ~p", [App, Unlocks]), + Expectation = case Unlocks of + {error, Term} -> {error, Term}; + _ -> {ok, Unlocks} + end, + + meck:new(rebar_prv_upgrade, [passthrough]), + meck:expect(rebar_prv_upgrade, do, fun(S) -> + apply(?config(mock_update, Config), []), + meck:passthrough([S]) + end), + _NewRebarConf = rebar_test_utils:create_config(filename:dirname(?config(rebarumbrella, Config)), + [{deps, ?config(next_top_deps, Config)}]), + %% re-run from the top-level with the old config still in place; + %% detection must happen when going for umbrella apps! + rebar_test_utils:run_and_check( + Config, TopConfig, ["upgrade", App], Expectation + ), + meck:unload(rebar_prv_upgrade). + run(Config) -> apply(?config(mock, Config), []), - {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), + ConfigPath = ?config(rebarconfig, Config), + {ok, RebarConfig} = file:consult(ConfigPath), %% Install dependencies before re-mocking for an upgrade rebar_test_utils:run_and_check(Config, RebarConfig, ["lock"], {ok, []}), {App, Unlocks} = ?config(expected, Config), @@ -587,7 +631,7 @@ run(Config) -> apply(?config(mock_update, Config), []), meck:passthrough([S]) end), - NewRebarConf = rebar_test_utils:create_config(?config(apps, Config), + NewRebarConf = rebar_test_utils:create_config(filename:dirname(ConfigPath), [{deps, ?config(next_top_deps, Config)}]), {ok, NewRebarConfig} = file:consult(NewRebarConf), rebar_test_utils:run_and_check( @@ -610,7 +654,14 @@ novsn_pkg(Config) -> rewrite_locks({ok, Expectations}, Config) -> AppDir = ?config(apps, Config), LockFile = filename:join([AppDir, "rebar.lock"]), - {ok, [Locks]} = file:consult(LockFile), + Locks = case ?config(deps_type, Config) of + git -> + {ok, [LockData]} = file:consult(LockFile), + LockData; + pkg -> + {ok, [{_Vsn, LockData}|_]} = file:consult(LockFile), + LockData + end, ExpLocks = [{list_to_binary(Name), Vsn} || {lock, Name, Vsn} <- Expectations], NewLocks = lists:foldl( |