コミットを比較

...

6 コミット

作成者 SHA1 メッセージ 日付
  Tristan Sloughter 09013eb898 append organization name to parent's repo_url when parsing repos 6年前
  Tristan Sloughter 16fb726e31 move packages to top level of of hexpm cache dir 6年前
  Tristan Sloughter 4381ee9fd9 ensure dir for auth config file 6年前
  Tristan Sloughter fbf74f04b2
add hex auth handling for repos (#1874) 6年前
  Tristan Sloughter d7e0f09461
wip: support list of repos for hex packages (#1866) 6年前
  Tristan Sloughter 09d459dc87
update to hex_core for hex-v2 repo support (#1865) 6年前
38個のファイルの変更1963行の追加1202行の削除
分割表示
  1. +1
    -0
      .travis.yml
  2. +2
    -1
      bootstrap
  3. +2
    -1
      rebar.config
  4. +2
    -0
      rebar.lock
  5. +2
    -0
      src/rebar.app.src
  6. +17
    -5
      src/rebar.hrl
  7. +16
    -5
      src/rebar3.erl
  8. +1
    -1
      src/rebar_api.erl
  9. +8
    -8
      src/rebar_app_info.erl
  10. +22
    -50
      src/rebar_app_utils.erl
  11. +1
    -1
      src/rebar_config.erl
  12. +21
    -20
      src/rebar_fetch.erl
  13. +6
    -1
      src/rebar_git_resource.erl
  14. +140
    -0
      src/rebar_hex_repos.erl
  15. +6
    -1
      src/rebar_hg_resource.erl
  16. +366
    -135
      src/rebar_packages.erl
  17. +73
    -191
      src/rebar_pkg_resource.erl
  18. +3
    -5
      src/rebar_prv_install_deps.erl
  19. +27
    -1
      src/rebar_prv_local_upgrade.erl
  20. +67
    -38
      src/rebar_prv_packages.erl
  21. +47
    -0
      src/rebar_prv_repos.erl
  22. +2
    -0
      src/rebar_prv_shell.erl
  23. +13
    -225
      src/rebar_prv_update.erl
  24. +34
    -1
      src/rebar_prv_upgrade.erl
  25. +47
    -3
      src/rebar_resource.erl
  26. +32
    -22
      src/rebar_state.erl
  27. +4
    -1
      src/rebar_string.erl
  28. +117
    -15
      src/rebar_utils.erl
  29. +1
    -1
      test/mock_git_resource.erl
  30. +41
    -19
      test/mock_pkg_resource.erl
  31. +62
    -68
      test/rebar_deps_SUITE.erl
  32. +54
    -54
      test/rebar_install_deps_SUITE.erl
  33. +6
    -1
      test/rebar_localfs_resource.erl
  34. +75
    -78
      test/rebar_pkg_SUITE.erl
  35. +89
    -28
      test/rebar_pkg_alias_SUITE.erl
  36. +327
    -0
      test/rebar_pkg_repos_SUITE.erl
  37. +8
    -1
      test/rebar_test_utils.erl
  38. +221
    -221
      test/rebar_upgrade_SUITE.erl

+ 1
- 0
.travis.yml ファイルの表示

@ -22,6 +22,7 @@ script: "./bootstrap && ./rebar3 ct"
branches:
only:
- master
- hex_core
cache:
directories:
- "$HOME/.cache/rebar3/hex/default"

+ 2
- 1
bootstrap ファイルの表示

@ -18,7 +18,8 @@ main(_) ->
,{erlware_commons, ["ec_dictionary.erl", "ec_vsn.erl"]}
,{parse_trans, ["parse_trans.erl", "parse_trans_pp.erl",
"parse_trans_codegen.erl"]}
,{certifi, []}],
,{certifi, []}
,{hex_core, []}],
Deps = get_deps(),
[fetch_and_compile(Dep, Deps) || Dep <- BaseDeps],

+ 2
- 1
rebar.config ファイルの表示

@ -11,6 +11,7 @@
{relx, "3.26.0"},
{cf, "0.2.2"},
{cth_readable, "1.4.2"},
{hex_core, "0.2.0"},
{eunit_formatters, "0.5.0"}]}.
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
@ -43,7 +44,7 @@
%% Profiles
{profiles, [{test, [
{deps, [{meck, "0.8.7"}]},
{deps, [{meck, "0.8.12"}]},
{erl_opts, [debug_info, nowarn_export_all]}
]
},

+ 2
- 0
rebar.lock ファイルの表示

@ -6,6 +6,7 @@
{<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"1.2.0">>},0},
{<<"eunit_formatters">>,{pkg,<<"eunit_formatters">>,<<"0.5.0">>},0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"hex_core">>,{pkg,<<"hex_core">>,<<"0.2.0">>},0},
{<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.0">>},0},
{<<"providers">>,{pkg,<<"providers">>,<<"1.7.0">>},0},
{<<"relx">>,{pkg,<<"relx">>,<<"3.26.0">>},0},
@ -19,6 +20,7 @@
{<<"erlware_commons">>, <<"2BAB99CF88941145767A502F1209886F1F0D31695EEF21978A30F15E645721E0">>},
{<<"eunit_formatters">>, <<"6A9133943D36A465D804C1C5B6E6839030434B8879C5600D7DDB5B3BAD4CCB59">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>},
{<<"hex_core">>, <<"3A7EACCFB8ADD3FF05D950C10ED5BDB5D0C48C988EBBC5D7AE2A55498F0EFF1B">>},
{<<"parse_trans">>, <<"09765507A3C7590A784615CFD421D101AEC25098D50B89D7AA1D66646BC571C1">>},
{<<"providers">>, <<"BBF730563914328EC2511D205E6477A94831DB7297DE313B3872A2B26C562EAB">>},
{<<"relx">>, <<"DD645ECAA1AB1647DB80D3E9BCAE0B39ED0A536EF37245F6A74B114C6D0F4E87">>},

+ 2
- 0
src/rebar.app.src ファイルの表示

@ -30,6 +30,7 @@
relx,
cf,
inets,
hex_core,
eunit_formatters]},
{env, [
%% Default log level
@ -67,6 +68,7 @@
rebar_prv_release,
rebar_prv_relup,
rebar_prv_report,
rebar_prv_repos,
rebar_prv_shell,
rebar_prv_state,
rebar_prv_tar,

+ 17
- 5
src/rebar.hrl ファイルの表示

@ -25,14 +25,26 @@
-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").
-define(LOCK_FILE, "rebar.lock").
-define(DEFAULT_COMPILER_SOURCE_FORMAT, relative).
-define(PACKAGE_INDEX_VERSION, 4).
-define(PACKAGE_TABLE, package_index_v4).
-define(INDEX_FILE, "packages-v4.idx").
-define(HEX_AUTH_FILE, "hex.config").
-define(PUBLIC_HEX_REPO, <<"hexpm">>).
-define(PACKAGE_INDEX_VERSION, 3).
-define(PACKAGE_TABLE, package_index).
-define(INDEX_FILE, "packages.idx").
-define(REGISTRY_FILE, "registry").
%% the package record is used in a select match spec which upsets dialyzer
%% this is the suggested workaround from Tobias
%% http://erlang.org/pipermail/erlang-questions/2009-February/041445.html
-type ms_field() :: '$1' | '_'.
%% TODO: change package and requirement keys to be required (:=) after dropping support for OTP-18
-record(package, {key :: {unicode:unicode_binary() | ms_field(), unicode:unicode_binary() | ms_field(),
unicode:unicode_binary() | ms_field()},
checksum :: binary() | ms_field(),
retired :: boolean() | ms_field(),
dependencies :: [#{package => unicode:unicode_binary(),
requirement => unicode:unicode_binary()}] | ms_field()}).
-ifdef(namespaced_types).
-type rebar_dict() :: dict:dict().

+ 16
- 5
src/rebar3.erl ファイルの表示

@ -103,7 +103,7 @@ run(RawArgs) ->
case erlang:system_info(version) of
"6.1" ->
?WARN("Due to a filelib bug in Erlang 17.1 it is recommended"
"you update to a newer release.", []);
"you update to a newer release.", []);
_ ->
ok
end,
@ -139,8 +139,14 @@ run_aux(State, RawArgs) ->
rebar_state:set(State1, rebar_packages_cdn, CDN)
end,
%% TODO: this means use of REBAR_PROFILE=profile will replace the repos with
%% the repos defined in the profile. But it will not work with `as profile`.
%% Maybe it shouldn't work with either to be consistent?
Resources = application:get_env(rebar, resources, []),
State2_ = rebar_state:create_resources(Resources, State2),
%% bootstrap test profile
State3 = rebar_state:add_to_profile(State2, test, test_state(State1)),
State3 = rebar_state:add_to_profile(State2_, test, test_state(State1)),
%% Process each command, resetting any state between each one
BaseDir = rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR),
@ -188,7 +194,7 @@ init_config() ->
?DEBUG("Load global config file ~ts", [GlobalConfigFile]),
try state_from_global_config(Config1, GlobalConfigFile)
catch
_:_ ->
_:_->
?WARN("Global config ~ts exists but can not be read. Ignoring global config values.", [GlobalConfigFile]),
rebar_state:new(Config1)
end;
@ -375,7 +381,11 @@ state_from_global_config(Config, GlobalConfigFile) ->
%% We don't want to worry about global plugin install state effecting later
%% usage. So we throw away the global profile state used for plugin install.
GlobalConfigThrowAway = rebar_state:current_profiles(GlobalConfig, [global]),
GlobalConfigThrowAway0 = rebar_state:current_profiles(GlobalConfig, [global]),
Resources = application:get_env(rebar, resources, []),
GlobalConfigThrowAway = rebar_state:create_resources(Resources, GlobalConfigThrowAway0),
GlobalState = case rebar_state:get(GlobalConfigThrowAway, plugins, []) of
[] ->
GlobalConfigThrowAway;
@ -386,7 +396,8 @@ state_from_global_config(Config, GlobalConfigFile) ->
end,
GlobalPlugins = rebar_state:providers(GlobalState),
GlobalConfig2 = rebar_state:set(GlobalConfig, plugins, []),
GlobalConfig3 = rebar_state:set(GlobalConfig2, {plugins, global}, rebar_state:get(GlobalConfigThrowAway, plugins, [])),
GlobalConfig3 = rebar_state:set(GlobalConfig2, {plugins, global},
rebar_state:get(GlobalConfigThrowAway, plugins, [])),
rebar_state:providers(rebar_state:new(GlobalConfig3, Config), GlobalPlugins).
-spec test_state(rebar_state:t()) -> [{'extra_src_dirs',[string()]} | {'erl_opts',[any()]}].

+ 1
- 1
src/rebar_api.erl ファイルの表示

@ -88,4 +88,4 @@ processing_base_dir(State) ->
%% its configuration, including for validation of certs.
-spec ssl_opts(string()) -> [term()].
ssl_opts(Url) ->
rebar_pkg_resource:ssl_opts(Url).
rebar_utils:ssl_opts(Url).

+ 8
- 8
src/rebar_app_info.erl ファイルの表示

@ -72,7 +72,7 @@
app_file_src :: file:filename_all() | undefined,
app_file_src_script:: file:filename_all() | undefined,
app_file :: file:filename_all() | undefined,
original_vsn :: binary() | string() | undefined,
original_vsn :: binary() | undefined,
parent=root :: binary() | root,
app_details=[] :: list(),
applications=[] :: list(),
@ -114,14 +114,14 @@ new(AppName) ->
{ok, t()}.
new(AppName, Vsn) ->
{ok, #app_info_t{name=rebar_utils:to_binary(AppName),
original_vsn=Vsn}}.
original_vsn=rebar_utils:to_binary(Vsn)}}.
%% @doc build a complete version of the app info with all fields set.
-spec new(atom() | binary() | string(), binary() | string(), file:name()) ->
{ok, t()}.
new(AppName, Vsn, Dir) ->
{ok, #app_info_t{name=rebar_utils:to_binary(AppName),
original_vsn=Vsn,
original_vsn=rebar_utils:to_binary(Vsn),
dir=rebar_utils:to_list(Dir),
out_dir=rebar_utils:to_list(Dir)}}.
@ -130,7 +130,7 @@ new(AppName, Vsn, Dir) ->
{ok, t()}.
new(AppName, Vsn, Dir, Deps) ->
{ok, #app_info_t{name=rebar_utils:to_binary(AppName),
original_vsn=Vsn,
original_vsn=rebar_utils:to_binary(Vsn),
dir=rebar_utils:to_list(Dir),
out_dir=rebar_utils:to_list(Dir),
deps=Deps}}.
@ -141,7 +141,7 @@ new(AppName, Vsn, Dir, Deps) ->
new(Parent, AppName, Vsn, Dir, Deps) ->
{ok, #app_info_t{name=rebar_utils:to_binary(AppName),
parent=Parent,
original_vsn=Vsn,
original_vsn=rebar_utils:to_binary(Vsn),
dir=rebar_utils:to_list(Dir),
out_dir=rebar_utils:to_list(Dir),
deps=Deps}}.
@ -350,15 +350,15 @@ parent(AppInfo=#app_info_t{}, Parent) ->
%% @doc returns the original version of the app (unevaluated if
%% asking for a semver)
-spec original_vsn(t()) -> string().
-spec original_vsn(t()) -> binary().
original_vsn(#app_info_t{original_vsn=Vsn}) ->
Vsn.
%% @doc stores the original version of the app (unevaluated if
%% asking for a semver)
-spec original_vsn(t(), string()) -> t().
-spec original_vsn(t(), binary() | string()) -> t().
original_vsn(AppInfo=#app_info_t{}, Vsn) ->
AppInfo#app_info_t{original_vsn=Vsn}.
AppInfo#app_info_t{original_vsn=rebar_utils:to_binary(Vsn)}.
%% @doc returns the list of applications the app depends on.
-spec applications(t()) -> list().

+ 22
- 50
src/rebar_app_utils.erl ファイルの表示

@ -250,52 +250,36 @@ expand_deps_sources(Dep, State) ->
%% around version if required.
-spec update_source(rebar_app_info:t(), Source, rebar_state:t()) ->
rebar_app_info:t() when
Source :: tuple() | atom() | binary(). % TODO: meta to source()
Source :: rebar_resource:source().
update_source(AppInfo, {pkg, PkgName, PkgVsn, Hash}, State) ->
{PkgName1, PkgVsn1} = case PkgVsn of
undefined ->
get_package(PkgName, "0", State);
<<"~>", Vsn/binary>> ->
[Vsn1] = [X || X <- binary:split(Vsn, [<<" ">>], [global]), X =/= <<>>],
get_package(PkgName, Vsn1, State);
_ ->
{PkgName, PkgVsn}
end,
%% store the expected hash for the dependency
Hash1 = case Hash of
undefined -> % unknown, define the hash since we know the dep
fetch_checksum(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),
AppInfo2 = rebar_app_info:resource_type(rebar_app_info:deps(AppInfo1, Deps), pkg),
rebar_app_info:original_vsn(AppInfo2, PkgVsn1);
case rebar_packages:resolve_version(PkgName, PkgVsn, Hash,
?PACKAGE_TABLE, State) of
{ok, Package, RepoConfig} ->
#package{key = {_, PkgVsn1, _},
checksum = Hash1,
dependencies = Deps} = Package,
AppInfo1 = rebar_app_info:source(AppInfo, {pkg, PkgName, PkgVsn1, Hash1, RepoConfig}),
AppInfo2 = rebar_app_info:resource_type(rebar_app_info:deps(AppInfo1, Deps), pkg),
rebar_app_info:original_vsn(AppInfo2, PkgVsn1);
not_found ->
throw(?PRV_ERROR({missing_package, PkgName, PkgVsn}));
{error, {invalid_vsn, InvalidVsn}} ->
throw(?PRV_ERROR({invalid_vsn, PkgName, InvalidVsn}))
end;
update_source(AppInfo, Source, _State) ->
rebar_app_info:source(AppInfo, Source).
%% @doc grab the checksum for a given package
-spec fetch_checksum(atom(), string(), iodata() | undefined, rebar_state:t()) ->
iodata() | no_return().
fetch_checksum(PkgName, PkgVsn, Hash, State) ->
try
rebar_packages:registry_checksum({pkg, PkgName, PkgVsn, Hash}, State)
catch
_:_ ->
?INFO("Package ~ts-~ts not found. Fetching registry updates and trying again...", [PkgName, PkgVsn]),
{ok, _} = rebar_prv_update:do(State),
rebar_packages:registry_checksum({pkg, PkgName, PkgVsn, Hash}, State)
end.
%% @doc convert a given exception's payload into an io description.
-spec format_error(any()) -> iolist().
format_error({missing_package, Package}) ->
io_lib:format("Package not found in registry: ~ts", [Package]);
format_error({missing_package, Name, undefined}) ->
io_lib:format("Package not found in any repo: ~ts.", [rebar_utils:to_binary(Name)]);
format_error({missing_package, Name, Vsn}) ->
io_lib:format("Package not found in any repo: ~ts-~ts.", [rebar_utils:to_binary(Name),
rebar_utils:to_binary(Vsn)]);
format_error({parse_dep, Dep}) ->
io_lib:format("Failed parsing dep ~p", [Dep]);
format_error({invalid_vsn, Dep, InvalidVsn}) ->
io_lib:format("Dep ~ts has invalid version ~ts", [Dep, InvalidVsn]);
format_error(Error) ->
io_lib:format("~p", [Error]).
@ -303,18 +287,6 @@ format_error(Error) ->
%% Internal functions
%% ===================================================================
%% @private find the correct version of a package based on the version
%% and name passed in.
-spec get_package(binary(), binary() | string(), rebar_state:t()) ->
term() | no_return().
get_package(Dep, Vsn, State) ->
case rebar_packages:find_highest_matching(Dep, Vsn, ?PACKAGE_TABLE, State) of
{ok, HighestDepVsn} ->
{Dep, HighestDepVsn};
none ->
throw(?PRV_ERROR({missing_package, rebar_utils:to_binary(Dep)}))
end.
%% @private checks that all the beam files have been properly
%% created.
-spec has_all_beams(file:filename_all(), [module()]) ->

+ 1
- 1
src/rebar_config.erl ファイルの表示

@ -74,7 +74,7 @@ consult_lock_file(File) ->
read_attrs(beta, Locks, []);
[{Vsn, Locks}|Attrs] when is_list(Locks) -> % versioned lock file
%% Because this is the first version of rebar3 to introduce a lock
%% file, all versionned lock files with a different versions have
%% file, all versioned lock files with a different version have
%% to be newer.
case Vsn of
?CONFIG_VERSION ->

+ 21
- 20
src/rebar_fetch.erl ファイルの表示

@ -16,15 +16,15 @@
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
-spec lock_source(file:filename_all(), rebar_resource:resource(), rebar_state:t()) ->
rebar_resource:resource() | {error, string()}.
-spec lock_source(file:filename_all(), rebar_resource:source(), rebar_state:t())
-> rebar_resource:source() | {error, string()}.
lock_source(AppDir, Source, State) ->
Resources = rebar_state:resources(State),
Module = get_resource_type(Source, Resources),
Module:lock(AppDir, Source).
-spec download_source(file:filename_all(), rebar_resource:resource(), rebar_state:t()) ->
true | {error, any()}.
-spec download_source(file:filename_all(), rebar_resource:source(), rebar_state:t())
-> true | {error, any()}.
download_source(AppDir, Source, State) ->
try download_source_(AppDir, Source, State) of
true ->
@ -32,6 +32,8 @@ download_source(AppDir, Source, State) ->
Error ->
throw(?PRV_ERROR(Error))
catch
throw:{no_resource, Type, Location} ->
throw(?PRV_ERROR({no_resource, Location, Type}));
?WITH_STACKTRACE(C,T,S)
?DEBUG("rebar_fetch exception ~p ~p ~p", [C, T, S]),
throw(?PRV_ERROR({fetch_fail, Source}))
@ -54,7 +56,8 @@ download_source_(AppDir, Source, State) ->
Error
end.
-spec needs_update(file:filename_all(), rebar_resource:resource(), rebar_state:t()) -> boolean() | {error, string()}.
-spec needs_update(file:filename_all(), rebar_resource:source(), rebar_state:t())
-> boolean() | {error, string()}.
needs_update(AppDir, Source, State) ->
Resources = rebar_state:resources(State),
Module = get_resource_type(Source, Resources),
@ -84,27 +87,25 @@ format_error({fetch_fail, Source}) ->
format_error({bad_checksum, File}) ->
io_lib:format("Checksum mismatch against tarball in ~ts", [File]);
format_error({bad_registry_checksum, File}) ->
io_lib:format("Checksum mismatch against registry in ~ts", [File]).
io_lib:format("Checksum mismatch against registry in ~ts", [File]);
format_error({no_resource, Location, Type}) ->
io_lib:format("Cannot handle dependency ~ts.~n"
" No module for resource type ~p", [Location, Type]).
get_resource_type({Type, Location}, Resources) ->
find_resource_module(Type, Location, Resources);
get_resource_module(Type, Location, Resources);
get_resource_type({Type, Location, _}, Resources) ->
find_resource_module(Type, Location, Resources);
get_resource_module(Type, Location, Resources);
get_resource_type({Type, _, _, Location}, Resources) ->
find_resource_module(Type, Location, Resources);
get_resource_module(Type, Location, Resources);
get_resource_type(_, _) ->
rebar_pkg_resource.
find_resource_module(Type, Location, Resources) ->
case lists:keyfind(Type, 1, Resources) of
false ->
case code:which(Type) of
non_existing ->
{error, io_lib:format("Cannot handle dependency ~ts.~n"
" No module for resource type ~p", [Location, Type])};
_ ->
Type
end;
{Type, Module} ->
get_resource_module(Type, Location, Resources) ->
case rebar_resource:find_resource_module(Type, Resources) of
{error, not_found} ->
throw({no_resource, Location, Type});
{ok, Module} ->
Module
end.

+ 6
- 1
src/rebar_git_resource.erl ファイルの表示

@ -4,7 +4,8 @@
-behaviour(rebar_resource).
-export([lock/2
-export([init/1
,lock/2
,download/3
,needs_update/2
,make_vsn/1]).
@ -14,6 +15,10 @@
%% Regex used for parsing scp style remote url
-define(SCP_PATTERN, "\\A(?<username>[^@]+)@(?<host>[^:]+):(?<path>.+)\\z").
-spec init(rebar_state:t()) -> {ok, term()}.
init(_State) ->
{ok, #{}}.
lock(AppDir, {git, Url, _}) ->
lock(AppDir, {git, Url});
lock(AppDir, {git, Url}) ->

+ 140
- 0
src/rebar_hex_repos.erl ファイルの表示

@ -0,0 +1,140 @@
-module(rebar_hex_repos).
-export([from_state/2,
get_repo_config/2,
auth_config/1,
update_auth_config/2,
format_error/1]).
-ifdef(TEST).
%% exported for test purposes
-export([repos/1, merge_repos/1]).
-endif.
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
-type repo() :: #{name => unicode:unicode_binary(),
api_url => binary(),
api_key => binary(),
repo_url => binary(),
repo_public_key => binary(),
repo_verify => binary()}.
from_state(BaseConfig, State) ->
HexConfig = rebar_state:get(State, hex, []),
Repos = repos(HexConfig),
%% auth is stored in a separate config file since the plugin generates and modifies it
Auth = ?MODULE:auth_config(State),
%% add base config entries that are specific to use by rebar3 and not overridable
Repos1 = merge_with_base_and_auth(Repos, BaseConfig, Auth),
%% merge organizations parent repo options into each oraganization repo
update_organizations(Repos1).
-spec get_repo_config(unicode:unicode_binary(), rebar_state:t() | [hex_core:config()])
-> {ok, hex_core:config()} | error.
get_repo_config(RepoName, Repos) when is_list(Repos) ->
case ec_lists:find(fun(#{name := N}) -> N =:= RepoName end, Repos) of
error ->
throw(?PRV_ERROR({repo_not_found, RepoName}));
{ok, RepoConfig} ->
{ok, RepoConfig}
end;
get_repo_config(RepoName, State) ->
Resources = rebar_state:resources(State),
#{repos := Repos} = rebar_resource:find_resource_state(pkg, Resources),
get_repo_config(RepoName, Repos).
merge_with_base_and_auth(Repos, BaseConfig, Auth) ->
[maps:merge(maps:get(maps:get(name, Repo), Auth, #{}),
maps:merge(Repo, BaseConfig)) || Repo <- Repos].
%% A user's list of repos are merged by name while keeping the order
%% intact. The order is based on the first use of a repo by name in the
%% list. The default repo is appended to the user's list.
repos(HexConfig) ->
HexDefaultConfig = default_repo(),
case [R || R <- HexConfig, element(1, R) =:= repos] of
[] ->
[HexDefaultConfig];
%% we only care if the first element is a replace entry
[{repos, replace, Repos} | _]->
merge_repos(Repos);
Repos ->
RepoList = repo_list(Repos),
merge_repos(RepoList ++ [HexDefaultConfig])
end.
-spec merge_repos([repo()]) -> [repo()].
merge_repos(Repos) ->
lists:foldl(fun(R=#{name := Name}, ReposAcc) ->
%% private organizations include the parent repo before a :
case rebar_string:split(Name, <<":">>) of
[Repo, Org] ->
update_repo_list(R#{name => Name,
organization => Org,
parent => Repo}, ReposAcc);
_ ->
update_repo_list(R, ReposAcc)
end
end, [], Repos).
update_organizations(Repos) ->
lists:map(fun(Repo=#{organization := Organization,
parent := ParentName}) ->
{ok, Parent} = get_repo_config(ParentName, Repos),
ParentRepoUrl = rebar_utils:to_list(maps:get(repo_url, Parent)),
{ok, RepoUrl} =
rebar_utils:url_append_path(ParentRepoUrl,
filename:join("repos", rebar_utils:to_list(Organization))),
%% still let the organization config override this constructed repo url
maps:merge(Parent#{repo_url => rebar_utils:to_binary(RepoUrl)}, Repo);
(Repo) ->
Repo
end, Repos).
update_repo_list(R=#{name := N}, [H=#{name := HN} | Rest]) when N =:= HN ->
[maps:merge(R, H) | Rest];
update_repo_list(R, [H | Rest]) ->
[H | update_repo_list(R, Rest)];
update_repo_list(R, []) ->
[R].
default_repo() ->
HexDefaultConfig = hex_core:default_config(),
HexDefaultConfig#{name => ?PUBLIC_HEX_REPO}.
repo_list([]) ->
[];
repo_list([{repos, Repos} | T]) ->
Repos ++ repo_list(T);
repo_list([{repos, replace, Repos} | T]) ->
Repos ++ repo_list(T).
format_error({repo_not_found, RepoName}) ->
io_lib:format("The repo ~ts was not found in the configuration.", [RepoName]).
%% auth functions
%% authentication is in a separate config file because the hex plugin updates it
-spec auth_config_file(rebar_state:t()) -> file:filename_all().
auth_config_file(State) ->
filename:join(rebar_dir:global_config_dir(State), ?HEX_AUTH_FILE).
-spec auth_config(rebar_state:t()) -> map().
auth_config(State) ->
case file:consult(auth_config_file(State)) of
{ok, [Config]} ->
Config;
_ ->
#{}
end.
-spec update_auth_config(map(), rebar_state:t()) -> ok.
update_auth_config(Updates, State) ->
Config = auth_config(State),
AuthConfigFile = auth_config_file(State),
ok = filelib:ensure_dir(AuthConfigFile),
NewConfig = iolist_to_binary([io_lib:print(maps:merge(Config, Updates)) | ".\n"]),
ok = file:write_file(AuthConfigFile, NewConfig).

+ 6
- 1
src/rebar_hg_resource.erl ファイルの表示

@ -4,13 +4,18 @@
-behaviour(rebar_resource).
-export([lock/2
-export([init/1
,lock/2
,download/3
,needs_update/2
,make_vsn/1]).
-include("rebar.hrl").
-spec init(rebar_state:t()) -> {ok, term()}.
init(_State) ->
{ok, #{}}.
lock(AppDir, {hg, Url, _}) ->
lock(AppDir, {hg, Url});
lock(AppDir, {hg, Url}) ->

+ 366
- 135
src/rebar_packages.erl ファイルの表示

@ -1,18 +1,24 @@
-module(rebar_packages).
-export([packages/1
,close_packages/0
,load_and_verify_version/1
,deps/3
-export([get/2
,get_all_names/1
,get_package_versions/4
,get_package_deps/4
,new_package_table/0
,load_and_verify_version/1
,registry_dir/1
,package_dir/1
,registry_checksum/2
,find_highest_matching/6
,find_highest_matching/4
,find_highest_matching_/6
,find_all/3
,package_dir/2
,registry_checksum/4
,find_highest_matching/5
,find_highest_matching_/5
,verify_table/1
,format_error/1]).
,format_error/1
,update_package/3
,resolve_version/5]).
-ifdef(TEST).
-export([cmp_/4, cmpl_/4, valid_vsn/1]).
-endif.
-export_type([package/0]).
@ -23,120 +29,159 @@
-type vsn() :: binary().
-type package() :: pkg_name() | {pkg_name(), vsn()}.
-spec packages(rebar_state:t()) -> ets:tid().
packages(State) ->
catch ets:delete(?PACKAGE_TABLE),
case load_and_verify_version(State) of
true ->
ok;
false ->
?DEBUG("Error loading package index.", []),
handle_bad_index(State)
end.
format_error({missing_package, Name, Vsn}) ->
io_lib:format("Package not found in any repo: ~ts-~ts.", [rebar_utils:to_binary(Name),
rebar_utils:to_binary(Vsn)]);
format_error({missing_package, Pkg}) ->
io_lib:format("Package not found in any repo: ~p.", [Pkg]).
handle_bad_index(State) ->
?ERROR("Bad packages index. Trying to fix by updating the registry.", []),
{ok, State1} = rebar_prv_update:do(State),
case load_and_verify_version(State1) of
true ->
ok;
false ->
%% Still unable to load after an update, create an empty registry
ets:new(?PACKAGE_TABLE, [named_table, public])
-spec get(hex_core:config(), binary()) -> {ok, map()} | {error, term()}.
get(Config, Name) ->
try hex_api_package:get(Config, Name) of
{ok, {200, _Headers, PkgInfo}} ->
{ok, PkgInfo};
{ok, {404, _, _}} ->
{error, not_found};
Error ->
?DEBUG("Hex api request failed: ~p", [Error]),
{error, unknown}
catch
error:{badmatch, {error, {failed_connect, _}}} ->
{error, failed_to_connect};
_:Exception ->
?DEBUG("hex_api_package:get failed: ~p", [Exception]),
{error, unknown}
end.
close_packages() ->
catch ets:delete(?PACKAGE_TABLE).
-spec get_all_names(rebar_state:t()) -> [binary()].
get_all_names(State) ->
verify_table(State),
lists:usort(ets:select(?PACKAGE_TABLE, [{#package{key={'$1', '_', '_'},
_='_'},
[], ['$1']}])).
-spec get_package_versions(unicode:unicode_binary(), unicode:unicode_binary(), ets:tid(), rebar_state:t())
-> [vsn()].
get_package_versions(Dep, Repo, Table, State) ->
?MODULE:verify_table(State),
ets:select(Table, [{#package{key={Dep,'$1', Repo},
_='_'},
[], ['$1']}]).
get_package(Dep, Vsn, Hash, Repo, Table, State) ->
get_package(Dep, Vsn, Hash, false, [Repo], Table, State).
-spec get_package(unicode:unicode_binary(), unicode:unicode_binary(),
binary() | undefined | '_', boolean() | '_',
unicode:unicode_binary() | '_' | list(), ets:tab(), rebar_state:t())
-> {ok, #package{}} | not_found.
get_package(Dep, Vsn, undefined, Retired, Repo, Table, State) ->
get_package(Dep, Vsn, '_', Retired, Repo, Table, State);
get_package(Dep, Vsn, Hash, Retired, Repos, Table, State) when is_list(Repos) ->
?MODULE:verify_table(State),
case ets:select(Table, [{#package{key={Dep, Vsn, Repo},
checksum=Hash,
retired=Retired,
_='_'}, [], ['$_']} || Repo <- Repos]) of
%% have to allow multiple matches in the list for cases that Repo is `_`
[Package | _] ->
{ok, Package};
_ ->
not_found
end;
get_package(Dep, Vsn, Hash, Retired, Repo, Table, State) ->
get_package(Dep, Vsn, Hash, Retired, [Repo], Table, State).
new_package_table() ->
?PACKAGE_TABLE = ets:new(?PACKAGE_TABLE, [named_table, public, ordered_set, {keypos, 2}]),
ets:insert(?PACKAGE_TABLE, {?PACKAGE_INDEX_VERSION, package_index_version}).
-spec get_package_deps(unicode:unicode_binary(), unicode:unicode_binary(), vsn(), rebar_state:t())
-> [map()].
get_package_deps(Name, Vsn, Repo, State) ->
try_lookup(?PACKAGE_TABLE, {Name, Vsn, Repo}, #package.dependencies, State).
-spec registry_checksum(unicode:unicode_binary(), vsn(), unicode:unicode_binary(), rebar_state:t())
-> binary().
registry_checksum(Name, Vsn, Repo, State) ->
try_lookup(?PACKAGE_TABLE, {Name, Vsn, Repo}, #package.checksum, State).
try_lookup(Table, Key={_, _, Repo}, Element, State) ->
?MODULE:verify_table(State),
try
ets:lookup_element(Table, Key, Element)
catch
_:_ ->
handle_missing_package(Key, Repo, State, fun(_) ->
ets:lookup_element(Table, Key, Element)
end)
end.
load_and_verify_version(State) ->
{ok, RegistryDir} = registry_dir(State),
case ets:file2tab(filename:join(RegistryDir, ?INDEX_FILE)) of
{ok, _} ->
case ets:lookup_element(?PACKAGE_TABLE, package_index_version, 2) of
case ets:lookup_element(?PACKAGE_TABLE, package_index_version, 1) of
?PACKAGE_INDEX_VERSION ->
true;
_ ->
V ->
%% no reason to confuse the user since we just start fresh and they
%% shouldn't notice, so log as a debug message only
?DEBUG("Package index version mismatch. Current version ~p, this rebar3 expecting ~p",
[V, ?PACKAGE_INDEX_VERSION]),
(catch ets:delete(?PACKAGE_TABLE)),
rebar_prv_update:hex_to_index(State)
new_package_table()
end;
_ ->
rebar_prv_update:hex_to_index(State)
_ ->
new_package_table()
end.
deps(Name, Vsn, State) ->
try
deps_(Name, Vsn, State)
catch
_:_ ->
handle_missing_package({Name, Vsn}, State, fun(State1) -> deps_(Name, Vsn, State1) end)
end.
handle_missing_package(PkgKey, Repo, State, Fun) ->
Name =
case PkgKey of
{N, Vsn, _Repo} ->
?DEBUG("Package ~ts-~ts not found. Fetching registry updates for "
"package and trying again...", [N, Vsn]),
N;
_ ->
?DEBUG("Package ~p not found. Fetching registry updates for "
"package and trying again...", [PkgKey]),
PkgKey
end,
deps_(Name, Vsn, State) ->
?MODULE:verify_table(State),
ets:lookup_element(?PACKAGE_TABLE, {rebar_utils:to_binary(Name), rebar_utils:to_binary(Vsn)}, 2).
handle_missing_package(Dep, State, Fun) ->
case Dep of
{Name, Vsn} ->
?INFO("Package ~ts-~ts not found. Fetching registry updates and trying again...", [Name, Vsn]);
_ ->
?INFO("Package ~p not found. Fetching registry updates and trying again...", [Dep])
end,
{ok, State1} = rebar_prv_update:do(State),
try
Fun(State1)
update_package(Name, Repo, State),
try
Fun(State)
catch
_:_ ->
%% Even after an update the package is still missing, time to error out
throw(?PRV_ERROR({missing_package, Dep}))
throw(?PRV_ERROR({missing_package, PkgKey}))
end.
registry_dir(State) ->
CacheDir = rebar_dir:global_cache_dir(rebar_state:opts(State)),
case rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN) of
?DEFAULT_CDN ->
RegistryDir = filename:join([CacheDir, "hex", "default"]),
case filelib:ensure_dir(filename:join(RegistryDir, "placeholder")) of
ok -> ok;
{error, Posix} when Posix == eaccess; Posix == enoent ->
?ABORT("Could not write to ~p. Please ensure the path is writeable.",
[RegistryDir])
end,
{ok, RegistryDir};
CDN ->
case rebar_utils:url_append_path(CDN, ?REMOTE_PACKAGE_DIR) of
{ok, Parsed} ->
{ok, {_, _, Host, _, Path, _}} = http_uri:parse(Parsed),
CDNHostPath = lists:reverse(rebar_string:lexemes(Host, ".")),
CDNPath = tl(filename:split(Path)),
RegistryDir = filename:join([CacheDir, "hex"] ++ CDNHostPath ++ CDNPath),
ok = filelib:ensure_dir(filename:join(RegistryDir, "placeholder")),
{ok, RegistryDir};
_ ->
{uri_parse_error, CDN}
end
end.
RegistryDir = filename:join([CacheDir, "hex"]),
case filelib:ensure_dir(filename:join(RegistryDir, "placeholder")) of
ok -> ok;
{error, Posix} when Posix == eaccess; Posix == enoent ->
?ABORT("Could not write to ~p. Please ensure the path is writeable.",
[RegistryDir])
end,
{ok, RegistryDir}.
package_dir(State) ->
package_dir(Repo, State) ->
case registry_dir(State) of
{ok, RegistryDir} ->
PackageDir = filename:join([RegistryDir, "packages"]),
RepoName = maps:get(name, Repo),
PackageDir = filename:join([RegistryDir, rebar_utils:to_list(RepoName), "packages"]),
ok = filelib:ensure_dir(filename:join(PackageDir, "placeholder")),
{ok, PackageDir};
Error ->
Error
end.
registry_checksum({pkg, Name, Vsn, _Hash}, State) ->
try
?MODULE:verify_table(State),
ets:lookup_element(?PACKAGE_TABLE, {Name, Vsn}, 3)
catch
_:_ ->
throw(?PRV_ERROR({missing_package, rebar_utils:to_binary(Name), rebar_utils:to_binary(Vsn)}))
end.
%% Hex supports use of ~> to specify the version required for a dependency.
%% Since rebar3 requires exact versions to choose from we find the highest
%% available version of the dep that passes the constraint.
@ -152,31 +197,28 @@ registry_checksum({pkg, Name, Vsn, _Hash}, State) ->
%% `~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
%% `~> 2.0` | `>= 2.0.0 and < 3.0.0`
%% `~> 2.1` | `>= 2.1.0 and < 3.0.0`
find_highest_matching(Dep, Constraint, Table, State) ->
find_highest_matching(undefined, undefined, Dep, Constraint, Table, State).
find_highest_matching(Pkg, PkgVsn, Dep, Constraint, Table, State) ->
try find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State) of
find_highest_matching(Dep, Constraint, Repo, Table, State) ->
try find_highest_matching_(Dep, Constraint, Repo, Table, State) of
none ->
handle_missing_package(Dep, State,
handle_missing_package(Dep, Repo, State,
fun(State1) ->
find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State1)
find_highest_matching_(Dep, Constraint, Repo, Table, State1)
end);
Result ->
Result
catch
_:_ ->
handle_missing_package(Dep, State,
handle_missing_package(Dep, Repo, State,
fun(State1) ->
find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State1)
find_highest_matching_(Dep, Constraint, Repo, Table, State1)
end)
end.
find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State) ->
try find_all(Dep, Table, State) of
{ok, [Vsn]} ->
handle_single_vsn(Pkg, PkgVsn, Dep, Vsn, Constraint);
{ok, Vsns} ->
find_highest_matching_(Dep, Constraint, #{name := Repo}, Table, State) ->
try get_package_versions(Dep, Repo, Table, State) of
[Vsn] ->
handle_single_vsn(Vsn, Constraint);
Vsns ->
case handle_vsns(Constraint, Vsns) of
none ->
none;
@ -188,18 +230,6 @@ find_highest_matching_(Pkg, PkgVsn, Dep, Constraint, Table, State) ->
none
end.
find_all(Dep, Table, State) ->
?MODULE:verify_table(State),
try ets:lookup_element(Table, Dep, 2) of
[Vsns] when is_list(Vsns)->
{ok, Vsns};
Vsns ->
{ok, Vsns}
catch
error:badarg ->
none
end.
handle_vsns(Constraint, Vsns) ->
lists:foldl(fun(Version, Highest) ->
case ec_semver:pes(Version, Constraint) andalso
@ -211,26 +241,227 @@ handle_vsns(Constraint, Vsns) ->
end
end, none, Vsns).
handle_single_vsn(Pkg, PkgVsn, Dep, Vsn, Constraint) ->
handle_single_vsn(Vsn, Constraint) ->
case ec_semver:pes(Vsn, Constraint) of
true ->
{ok, Vsn};
false ->
case {Pkg, PkgVsn} of
{undefined, undefined} ->
?DEBUG("Only existing version of ~ts is ~ts which does not match constraint ~~> ~ts. "
"Using anyway, but it is not guaranteed to work.", [Dep, Vsn, Constraint]);
_ ->
?DEBUG("[~ts:~ts] Only existing version of ~ts is ~ts which does not match constraint ~~> ~ts. "
"Using anyway, but it is not guaranteed to work.", [Pkg, PkgVsn, Dep, Vsn, Constraint])
end,
{ok, Vsn}
none
end.
format_error({missing_package, Name, Vsn}) ->
io_lib:format("Package not found in registry: ~ts-~ts.", [rebar_utils:to_binary(Name), rebar_utils:to_binary(Vsn)]);
format_error({missing_package, Dep}) ->
io_lib:format("Package not found in registry: ~p.", [Dep]).
verify_table(State) ->
ets:info(?PACKAGE_TABLE, named_table) =:= true orelse load_and_verify_version(State).
parse_deps(Deps) ->
[{maps:get(app, D, Name), {pkg, Name, Constraint, undefined}}
|| D=#{package := Name,
requirement := Constraint} <- Deps].
parse_checksum(<<Checksum:256/big-unsigned>>) ->
list_to_binary(
rebar_string:uppercase(
lists:flatten(io_lib:format("~64.16.0b", [Checksum]))));
parse_checksum(Checksum) ->
Checksum.
update_package(Name, RepoConfig=#{name := Repo}, State) ->
?MODULE:verify_table(State),
try hex_repo:get_package(RepoConfig#{repo_key => maps:get(read_key, RepoConfig, <<>>)}, Name) of
{ok, {200, _Headers, #{releases := Releases}}} ->
_ = insert_releases(Name, Releases, Repo, ?PACKAGE_TABLE),
{ok, RegistryDir} = rebar_packages:registry_dir(State),
PackageIndex = filename:join(RegistryDir, ?INDEX_FILE),
ok = ets:tab2file(?PACKAGE_TABLE, PackageIndex);
{ok, {403, _Headers, <<>>}} ->
not_found;
{ok, {404, _Headers, _}} ->
not_found;
Error ->
?DEBUG("Hex get_package request failed: ~p", [Error]),
%% TODO: add better log message. hex_core should export a format_error
?WARN("Failed to update package from repo ~ts", [Repo]),
fail
catch
_:Exception ->
?DEBUG("hex_repo:get_package failed for package ~p: ~p", [Name, Exception]),
fail
end.
insert_releases(Name, Releases, Repo, Table) ->
[true = ets:insert(Table,
#package{key={Name, Version, Repo},
checksum=parse_checksum(Checksum),
retired=maps:get(retired, Release, false),
dependencies=parse_deps(Dependencies)})
|| Release=#{checksum := Checksum,
version := Version,
dependencies := Dependencies} <- Releases].
-spec resolve_version(unicode:unicode_binary(), unicode:unicode_binary() | undefined,
binary() | undefined,
ets:tab(), rebar_state:t())
-> {error, {invalid_vsn, unicode:unicode_binary()}} |
not_found |
{ok, #package{}, map()}.
%% if checksum is defined search for any matching repo matching pkg-vsn and checksum
resolve_version(Dep, DepVsn, Hash, HexRegistry, State) when is_binary(Hash) ->
Resources = rebar_state:resources(State),
#{repos := RepoConfigs} = rebar_resource:find_resource_state(pkg, Resources),
RepoNames = [RepoName || #{name := RepoName} <- RepoConfigs],
%% allow retired packages when we have a checksum
case get_package(Dep, DepVsn, Hash, '_', RepoNames, HexRegistry, State) of
{ok, Package=#package{key={_, _, RepoName}}} ->
{ok, RepoConfig} = rebar_hex_repos:get_repo_config(RepoName, RepoConfigs),
{ok, Package, RepoConfig};
_ ->
Fun = fun(Repo) ->
case resolve_version_(Dep, DepVsn, Repo, HexRegistry, State) of
none ->
not_found;
{ok, Vsn} ->
get_package(Dep, Vsn, Hash, Repo, HexRegistry, State)
end
end,
handle_missing_no_exception(Fun, Dep, State)
end;
resolve_version(Dep, undefined, Hash, HexRegistry, State) ->
Fun = fun(Repo) ->
case highest_matching(Dep, "0", Repo, HexRegistry, State) of
none ->
not_found;
{ok, Vsn} ->
get_package(Dep, Vsn, Hash, Repo, HexRegistry, State)
end
end,
handle_missing_no_exception(Fun, Dep, State);
resolve_version(Dep, DepVsn, Hash, HexRegistry, State) ->
case valid_vsn(DepVsn) of
false ->
{error, {invalid_vsn, DepVsn}};
_ ->
Fun = fun(Repo) ->
case resolve_version_(Dep, DepVsn, Repo, HexRegistry, State) of
none ->
not_found;
{ok, Vsn} ->
get_package(Dep, Vsn, Hash, Repo, HexRegistry, State)
end
end,
handle_missing_no_exception(Fun, Dep, State)
end.
check_all_repos(Fun, RepoConfigs) ->
ec_lists:search(fun(#{name := R}) ->
Fun(R)
end, RepoConfigs).
handle_missing_no_exception(Fun, Dep, State) ->
Resources = rebar_state:resources(State),
#{repos := RepoConfigs} = rebar_resource:find_resource_state(pkg, Resources),
%% first check all repos in order for a local match
%% if none is found then we step through checking after updating the repo registry
case check_all_repos(Fun, RepoConfigs) of
not_found ->
ec_lists:search(fun(Config=#{name := R}) ->
case ?MODULE:update_package(Dep, Config, State) of
ok ->
Fun(R);
_ ->
not_found
end
end, RepoConfigs);
Result ->
Result
end.
resolve_version_(Dep, DepVsn, Repo, HexRegistry, State) ->
case DepVsn of
<<"~>", Vsn/binary>> ->
highest_matching(Dep, rm_ws(Vsn), Repo, HexRegistry, State);
<<">=", Vsn/binary>> ->
cmp(Dep, rm_ws(Vsn), Repo, HexRegistry, State, fun ec_semver:gte/2);
<<">", Vsn/binary>> ->
cmp(Dep, rm_ws(Vsn), Repo, HexRegistry, State, fun ec_semver:gt/2);
<<"<=", Vsn/binary>> ->
cmpl(Dep, rm_ws(Vsn), Repo, HexRegistry, State, fun ec_semver:lte/2);
<<"<", Vsn/binary>> ->
cmpl(Dep, rm_ws(Vsn), Repo, HexRegistry, State, fun ec_semver:lt/2);
<<"==", Vsn/binary>> ->
{ok, Vsn};
Vsn ->
{ok, Vsn}
end.
rm_ws(<<" ", R/binary>>) ->
rm_ws(R);
rm_ws(R) ->
R.
valid_vsn(Vsn) ->
%% Regepx from https://github.com/sindresorhus/semver-regex/blob/master/index.js
SemVerRegExp = "v?(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))?"
"(-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9a-z-]+(\\.[0-9a-z-]+)*)?",
SupportedVersions = "^(>=?|<=?|~>|==)?\\s*" ++ SemVerRegExp ++ "$",
re:run(Vsn, SupportedVersions, [unicode]) =/= nomatch.
highest_matching(Dep, Vsn, Repo, HexRegistry, State) ->
find_highest_matching_(Dep, Vsn, #{name => Repo}, HexRegistry, State).
cmp(Dep, Vsn, Repo, HexRegistry, State, CmpFun) ->
case get_package_versions(Dep, Repo, HexRegistry, State) of
[] ->
none;
Vsns ->
cmp_(undefined, Vsn, Vsns, CmpFun)
end.
cmp_(undefined, MinVsn, [], _CmpFun) ->
{ok, MinVsn};
cmp_(HighestDepVsn, _MinVsn, [], _CmpFun) ->
{ok, HighestDepVsn};
cmp_(BestMatch, MinVsn, [Vsn | R], CmpFun) ->
case CmpFun(Vsn, MinVsn) of
true ->
cmp_(Vsn, Vsn, R, CmpFun);
false ->
cmp_(BestMatch, MinVsn, R, CmpFun)
end.
%% We need to treat this differently since we want a version that is LOWER but
%% the higest possible one.
cmpl(Dep, Vsn, Repo, HexRegistry, State, CmpFun) ->
case get_package_versions(Dep, Repo, HexRegistry, State) of
[] ->
none;
Vsns ->
cmpl_(undefined, Vsn, Vsns, CmpFun)
end.
cmpl_(undefined, MaxVsn, [], _CmpFun) ->
{ok, MaxVsn};
cmpl_(HighestDepVsn, _MaxVsn, [], _CmpFun) ->
{ok, HighestDepVsn};
cmpl_(undefined, MaxVsn, [Vsn | R], CmpFun) ->
case CmpFun(Vsn, MaxVsn) of
true ->
cmpl_(Vsn, MaxVsn, R, CmpFun);
false ->
cmpl_(undefined, MaxVsn, R, CmpFun)
end;
cmpl_(BestMatch, MaxVsn, [Vsn | R], CmpFun) ->
case CmpFun(Vsn, MaxVsn) of
true ->
case ec_semver:gte(Vsn, BestMatch) of
true ->
cmpl_(Vsn, MaxVsn, R, CmpFun);
false ->
cmpl_(BestMatch, MaxVsn, R, CmpFun)
end;
false ->
cmpl_(BestMatch, MaxVsn, R, CmpFun)
end.

+ 73
- 191
src/rebar_pkg_resource.erl ファイルの表示

@ -4,21 +4,22 @@
-behaviour(rebar_resource).
-export([lock/2
-export([init/1
,lock/2
,download/3
,download/4
,needs_update/2
,make_vsn/1]).
-export([request/2
,etag/1
,ssl_opts/1]).
-export([request/4
,etag/1]).
%% Exported for ct
-ifdef(TEST).
%% exported for test purposes
-export([store_etag_in_cache/2]).
-endif.
-include("rebar.hrl").
-include_lib("public_key/include/OTP-PUB-KEY.hrl").
-type cached_result() :: {'bad_checksum',string()} |
{'bad_registry_checksum',string()} |
@ -32,12 +33,24 @@
%%==============================================================================
%% Public API
%%==============================================================================
-spec init(rebar_state:t()) -> {ok, term()}.
init(State) ->
{ok, Vsn} = application:get_key(rebar, vsn),
BaseConfig = #{http_adapter => hex_http_httpc,
http_user_agent_fragment =>
<<"(rebar3/", (list_to_binary(Vsn))/binary, ") (httpc)">>,
http_adapter_config => #{profile => rebar}},
Repos = rebar_hex_repos:from_state(BaseConfig, State),
{ok, #{repos => Repos,
base_config => BaseConfig}}.
-spec lock(AppDir, Source) -> Res when
AppDir :: file:name(),
Source :: tuple(),
Res :: {atom(), string(), any()}.
lock(_AppDir, Source) ->
Source.
Res :: {atom(), string(), any(), binary()}.
lock(_AppDir, {pkg, Name, Vsn, Hash, _RepoConfig}) ->
{pkg, Name, Vsn, Hash}.
%%------------------------------------------------------------------------------
%% @doc
@ -47,11 +60,11 @@ lock(_AppDir, Source) ->
%%------------------------------------------------------------------------------
-spec needs_update(Dir, Pkg) -> Res when
Dir :: file:name(),
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary()},
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary(), RepoConfig :: hex_core:config()},
Res :: boolean().
needs_update(Dir, {pkg, _Name, Vsn, _Hash}) ->
needs_update(Dir, {pkg, _Name, Vsn, _Hash, _}) ->
[AppInfo] = rebar_app_discover:find_apps([Dir], all),
case rebar_app_info:original_vsn(AppInfo) =:= rebar_utils:to_list(Vsn) of
case rebar_app_info:original_vsn(AppInfo) =:= rebar_utils:to_binary(Vsn) of
true ->
false;
false ->
@ -65,7 +78,7 @@ needs_update(Dir, {pkg, _Name, Vsn, _Hash}) ->
%%------------------------------------------------------------------------------
-spec download(TmpDir, Pkg, State) -> Res when
TmpDir :: file:name(),
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary()},
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary(), RepoConfig :: hex_core:config()},
State :: rebar_state:t(),
Res :: {'error',_} | {'ok',_} | {'tarball',binary() | string()}.
download(TmpDir, Pkg, State) ->
@ -80,25 +93,18 @@ download(TmpDir, Pkg, State) ->
%%------------------------------------------------------------------------------
-spec download(TmpDir, Pkg, State, UpdateETag) -> Res when
TmpDir :: file:name(),
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary()},
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary(), RepoConfig :: hex_core:config()},
State :: rebar_state:t(),
UpdateETag :: boolean(),
Res :: download_result().
download(TmpDir, Pkg={pkg, Name, Vsn, _Hash}, State, UpdateETag) ->
CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN),
{ok, PackageDir} = rebar_packages:package_dir(State),
download(TmpDir, Pkg={pkg, Name, Vsn, _Hash, Repo}, State, UpdateETag) ->
{ok, PackageDir} = rebar_packages:package_dir(Repo, State),
Package = binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>),
ETagFile = binary_to_list(<<Name/binary, "-", Vsn/binary, ".etag">>),
CachePath = filename:join(PackageDir, Package),
ETagPath = filename:join(PackageDir, ETagFile),
case rebar_utils:url_append_path(CDN, filename:join(?REMOTE_PACKAGE_DIR,
Package)) of
{ok, Url} ->
cached_download(TmpDir, CachePath, Pkg, Url, etag(ETagPath), State,
ETagPath, UpdateETag);
_ ->
{fetch_fail, Name, Vsn}
end.
cached_download(TmpDir, CachePath, Pkg, etag(ETagPath),
State, ETagPath, UpdateETag).
%%------------------------------------------------------------------------------
%% @doc
@ -120,29 +126,24 @@ make_vsn(_) ->
%% {ok, Contents, NewEtag}, otherwise if some error occured return error.
%% @end
%%------------------------------------------------------------------------------
-spec request(Url, ETag) -> Res when
Url :: string(),
ETag :: false | string(),
Res :: 'error' | {ok, cached} | {ok, any(), string()}.
request(Url, ETag) ->
HttpOptions = [{ssl, ssl_opts(Url)},
{relaxed, true} | rebar_utils:get_proxy_auth()],
case httpc:request(get, {Url, [{"if-none-match", "\"" ++ ETag ++ "\""}
|| ETag =/= false] ++
[{"User-Agent", rebar_utils:user_agent()}]},
HttpOptions, [{body_format, binary}], rebar) of
{ok, {{_Version, 200, _Reason}, Headers, Body}} ->
?DEBUG("Successfully downloaded ~ts", [Url]),
{"etag", ETag1} = lists:keyfind("etag", 1, Headers),
{ok, Body, rebar_string:trim(ETag1, both, [$"])};
{ok, {{_Version, 304, _Reason}, _Headers, _Body}} ->
?DEBUG("Cached copy of ~ts still valid", [Url]),
-spec request(hex_core:config(), binary(), binary(), false | binary())
-> {ok, cached} | {ok, binary(), binary()} | error.
request(Config, Name, Version, ETag) ->
Config1 = Config#{http_etag => ETag},
try hex_repo:get_tarball(Config1, Name, Version) of
{ok, {200, #{<<"etag">> := ETag1}, Tarball}} ->
{ok, Tarball, rebar_utils:to_binary(rebar_string:trim(rebar_utils:to_list(ETag1), both, [$"]))};
{ok, {304, _Headers, _}} ->
{ok, cached};
{ok, {{_Version, Code, _Reason}, _Headers, _Body}} ->
?DEBUG("Request to ~p failed: status code ~p", [Url, Code]),
{ok, {Code, _Headers, _Body}} ->
?DEBUG("Request for package ~s-~s failed: status code ~p", [Name, Version, Code]),
error;
{error, Reason} ->
?DEBUG("Request to ~p failed: ~p", [Url, Reason]),
?DEBUG("Request for package ~s-~s failed: ~p", [Name, Version, Reason]),
error
catch
_:Exception ->
?DEBUG("hex_repo:get_tarball failed: ~p", [Exception]),
error
end.
@ -155,30 +156,13 @@ request(Url, ETag) ->
%%------------------------------------------------------------------------------
-spec etag(Path) -> Res when
Path :: file:name(),
Res :: false | string().
Res :: binary().
etag(Path) ->
case file:read_file(Path) of
{ok, Bin} ->
binary_to_list(Bin);
Bin;
{error, _} ->
false
end.
%%------------------------------------------------------------------------------
%% @doc
%% Return the SSL options adequate for the project based on
%% its configuration, including for validation of certs.
%% @end
%%------------------------------------------------------------------------------
-spec ssl_opts(Url) -> Res when
Url :: string(),
Res :: proplists:proplist().
ssl_opts(Url) ->
case get_ssl_config() of
ssl_verify_enabled ->
ssl_opts(ssl_verify_enabled, Url);
ssl_verify_disabled ->
[{verify, verify_none}]
<<>>
end.
%%------------------------------------------------------------------------------
@ -188,7 +172,7 @@ ssl_opts(Url) ->
%%------------------------------------------------------------------------------
-spec store_etag_in_cache(File, ETag) -> Res when
File :: file:name(),
ETag :: string(),
ETag :: binary(),
Res :: ok.
store_etag_in_cache(Path, ETag) ->
_ = file:write_file(Path, ETag).
@ -196,29 +180,28 @@ store_etag_in_cache(Path, ETag) ->
%%%=============================================================================
%%% Private functions
%%%=============================================================================
-spec cached_download(TmpDir, CachePath, Pkg, Url, ETag, State, ETagPath,
-spec cached_download(TmpDir, CachePath, Pkg, ETag, State, ETagPath,
UpdateETag) -> Res when
TmpDir :: file:name(),
CachePath :: file:name(),
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary()},
Url :: string(),
ETag :: false | string(),
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary(), RepoConfig :: hex_core:config()},
ETag :: binary(),
State :: rebar_state:t(),
ETagPath :: file:name(),
UpdateETag :: boolean(),
Res :: download_result().
cached_download(TmpDir, CachePath, Pkg={pkg, Name, Vsn, _Hash}, Url, ETag,
cached_download(TmpDir, CachePath, Pkg={pkg, Name, Vsn, _Hash, RepoConfig}, ETag,
State, ETagPath, UpdateETag) ->
case request(Url, ETag) of
case request(RepoConfig, Name, Vsn, ETag) of
{ok, cached} ->
?INFO("Version cached at ~ts is up to date, reusing it", [CachePath]),
serve_from_cache(TmpDir, CachePath, Pkg, State);
{ok, Body, NewETag} ->
?INFO("Downloaded package, caching at ~ts", [CachePath]),
maybe_store_etag_in_cache(UpdateETag, ETagPath, NewETag),
serve_from_download(TmpDir, CachePath, Pkg, NewETag, Body, State,
ETagPath);
error when ETag =/= false ->
serve_from_download(TmpDir, CachePath, Pkg, NewETag, Body,
State, ETagPath);
error when ETag =/= <<>> ->
store_etag_in_cache(ETagPath, ETag),
?INFO("Download error, using cached file at ~ts", [CachePath]),
serve_from_cache(TmpDir, CachePath, Pkg, State);
@ -229,27 +212,24 @@ cached_download(TmpDir, CachePath, Pkg={pkg, Name, Vsn, _Hash}, Url, ETag,
-spec serve_from_cache(TmpDir, CachePath, Pkg, State) -> Res when
TmpDir :: file:name(),
CachePath :: file:name(),
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary()},
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary(), RepoConfig :: hex_core:config()},
State :: rebar_state:t(),
Res :: cached_result().
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};
{_Hash, Chk, Chk, Chk} ->
{_Hash, Chk, Chk} ->
?DEBUG("Expected hash ~p does not match checksums ~p", [_Hash, Chk]),
{unexpected_hash, CachePath, _Hash, Chk};
{Chk, _Bin, Chk, Chk} ->
{Chk, _Bin, Chk} ->
?DEBUG("Checksums: registry: ~p, pkg: ~p", [Chk, _Bin]),
{failed_extract, CachePath};
{Chk, Chk, _Reg, Chk} ->
?DEBUG("Checksums: registry: ~p, pkg: ~p", [_Reg, Chk]),
{bad_registry_checksum, CachePath};
{_Hash, _Bin, _Reg, _Tar} ->
?DEBUG("Checksums: expected: ~p, registry: ~p, pkg: ~p, meta: ~p",
[_Hash, _Reg, _Bin, _Tar]),
{_Hash, _Bin, _Tar} ->
?DEBUG("Checksums: expected: ~p, pkg: ~p, meta: ~p",
[_Hash, _Bin, _Tar]),
{bad_checksum, CachePath}
end.
@ -257,8 +237,8 @@ serve_from_cache(TmpDir, CachePath, Pkg, State) ->
ETagPath) -> Res when
TmpDir :: file:name(),
CachePath :: file:name(),
Package :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary()},
ETag :: string(),
Package :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary(), RepoConfig :: hex_core:config()},
ETag :: binary(),
Binary :: binary(),
State :: rebar_state:t(),
ETagPath :: file:name(),
@ -269,7 +249,7 @@ serve_from_download(TmpDir, CachePath, Package, ETag, Binary, State, ETagPath) -
case etag(ETagPath) of
ETag ->
serve_from_cache(TmpDir, CachePath, Package, State);
FileETag ->
FileETag ->
?DEBUG("Downloaded file ~ts ETag ~ts doesn't match returned ETag ~ts",
[CachePath, ETag, FileETag]),
{bad_download, CachePath}
@ -292,127 +272,29 @@ extract(TmpDir, CachePath) ->
{Files, Contents, Version, Meta}.
-spec checksums(Pkg, Files, Contents, Version, Meta, State) -> Res when
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary()},
Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary(), RepoConfig :: hex_core:config()},
Files :: list({file:name(), binary()}),
Contents :: binary(),
Version :: binary(),
Meta :: binary(),
State :: rebar_state:t(),
Res :: {Hash, BinChecksum, RegistryChecksum, TarChecksum},
Res :: {Hash, BinChecksum, TarChecksum},
Hash :: binary(),
BinChecksum :: binary(),
RegistryChecksum :: any(),
TarChecksum :: binary().
checksums(Pkg={pkg, _Name, _ Vsn, Hash}, Files, Contents, Version, Meta, State) ->
checksums({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(
rebar_string:uppercase(
lists:flatten(io_lib:format("~64.16.0b", [X])))),
RegistryChecksum = rebar_packages:registry_checksum(Pkg, State),
{"CHECKSUM", TarChecksum} = lists:keyfind("CHECKSUM", 1, Files),
{Hash, BinChecksum, RegistryChecksum, TarChecksum}.
%%------------------------------------------------------------------------------
%% @doc
%% Return the SSL options adequate for the project based on
%% its configuration, including for validation of certs.
%% @end
%%------------------------------------------------------------------------------
-spec ssl_opts(Enabled, Url) -> Res when
Enabled :: atom(),
Url :: string(),
Res :: proplists:proplist().
ssl_opts(ssl_verify_enabled, Url) ->
case check_ssl_version() of
true ->
{ok, {_, _, Hostname, _, _, _}} =
http_uri:parse(rebar_utils:to_list(Url)),
VerifyFun = {fun ssl_verify_hostname:verify_fun/3,
[{check_hostname, Hostname}]},
CACerts = certifi:cacerts(),
[{verify, verify_peer}, {depth, 2}, {cacerts, CACerts},
{partial_chain, fun partial_chain/1}, {verify_fun, VerifyFun}];
false ->
?WARN("Insecure HTTPS request (peer verification disabled), "
"please update to OTP 17.4 or later", []),
[{verify, verify_none}]
end.
-spec partial_chain(Certs) -> Res when
Certs :: list(any()),
Res :: unknown_ca | {trusted_ca, any()}.
partial_chain(Certs) ->
Certs1 = [{Cert, public_key:pkix_decode_cert(Cert, otp)} || Cert <- Certs],
CACerts = certifi:cacerts(),
CACerts1 = [public_key:pkix_decode_cert(Cert, otp) || Cert <- CACerts],
case ec_lists:find(fun({_, Cert}) ->
check_cert(CACerts1, Cert)
end, Certs1) of
{ok, Trusted} ->
{trusted_ca, element(1, Trusted)};
_ ->
unknown_ca
end.
-spec extract_public_key_info(Cert) -> Res when
Cert :: #'OTPCertificate'{tbsCertificate::#'OTPTBSCertificate'{}},
Res :: any().
extract_public_key_info(Cert) ->
((Cert#'OTPCertificate'.tbsCertificate)#'OTPTBSCertificate'.subjectPublicKeyInfo).
-spec check_cert(CACerts, Cert) -> Res when
CACerts :: list(any()),
Cert :: any(),
Res :: boolean().
check_cert(CACerts, Cert) ->
lists:any(fun(CACert) ->
extract_public_key_info(CACert) == extract_public_key_info(Cert)
end, CACerts).
-spec check_ssl_version() ->
boolean().
check_ssl_version() ->
case application:get_key(ssl, vsn) of
{ok, Vsn} ->
parse_vsn(Vsn) >= {5, 3, 6};
_ ->
false
end.
-spec get_ssl_config() ->
ssl_verify_disabled | ssl_verify_enabled.
get_ssl_config() ->
GlobalConfigFile = rebar_dir:global_config(),
Config = rebar_config:consult_file(GlobalConfigFile),
case proplists:get_value(ssl_verify, Config, []) of
false ->
ssl_verify_disabled;
_ ->
ssl_verify_enabled
end.
-spec parse_vsn(Vsn) -> Res when
Vsn :: string(),
Res :: {integer(), integer(), integer()}.
parse_vsn(Vsn) ->
version_pad(rebar_string:lexemes(Vsn, ".-")).
-spec version_pad(list(nonempty_string())) -> Res when
Res :: {integer(), integer(), integer()}.
version_pad([Major]) ->
{list_to_integer(Major), 0, 0};
version_pad([Major, Minor]) ->
{list_to_integer(Major), list_to_integer(Minor), 0};
version_pad([Major, Minor, Patch]) ->
{list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)};
version_pad([Major, Minor, Patch | _]) ->
{list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)}.
{Hash, BinChecksum, TarChecksum}.
-spec maybe_store_etag_in_cache(UpdateETag, Path, ETag) -> Res when
UpdateETag :: boolean(),
Path :: file:name(),
ETag :: string(),
ETag :: binary(),
Res :: ok.
maybe_store_etag_in_cache(false = _UpdateETag, _Path, _ETag) ->
ok;

+ 3
- 5
src/rebar_prv_install_deps.erl ファイルの表示

@ -374,13 +374,10 @@ make_relative_to_root(State, Path) when is_list(Path) ->
fetch_app(AppInfo, AppDir, State) ->
?INFO("Fetching ~ts (~p)", [rebar_app_info:name(AppInfo),
format_source(rebar_app_info:source(AppInfo))]),
rebar_resource: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.
@ -398,7 +395,8 @@ maybe_upgrade(AppInfo, AppDir, Upgrade, State) ->
true ->
case rebar_fetch:needs_update(AppDir, Source, State) of
true ->
?INFO("Upgrading ~ts (~p)", [rebar_app_info:name(AppInfo), rebar_app_info:source(AppInfo)]),
?INFO("Upgrading ~ts (~p)", [rebar_app_info:name(AppInfo),
rebar_resource:format_source(rebar_app_info:source(AppInfo))]),
true = rebar_fetch:download_source(AppDir, Source, State);
false ->
case Upgrade of

+ 27
- 1
src/rebar_prv_local_upgrade.erl ファイルの表示

@ -77,7 +77,7 @@ get_md5(Rebar3Path) ->
maybe_fetch_rebar3(Rebar3Md5) ->
TmpDir = ec_file:insecure_mkdtemp(),
TmpFile = filename:join(TmpDir, "rebar3"),
case rebar_pkg_resource:request("https://s3.amazonaws.com/rebar3/rebar3", Rebar3Md5) of
case request("https://s3.amazonaws.com/rebar3/rebar3", Rebar3Md5) of
{ok, Binary, ETag} ->
file:write_file(TmpFile, Binary),
case etag(TmpFile) of
@ -101,3 +101,29 @@ etag(Path) ->
{error, _} ->
false
end.
-spec request(Url, ETag) -> Res when
Url :: string(),
ETag :: false | string(),
Res :: 'error' | {ok, cached} | {ok, any(), string()}.
request(Url, ETag) ->
HttpOptions = [{ssl, rebar_utils:ssl_opts(Url)},
{relaxed, true} | rebar_utils:get_proxy_auth()],
case httpc:request(get, {Url, [{"if-none-match", "\"" ++ ETag ++ "\""}
|| ETag =/= false] ++
[{"User-Agent", rebar_utils:user_agent()}]},
HttpOptions, [{body_format, binary}], rebar) of
{ok, {{_Version, 200, _Reason}, Headers, Body}} ->
?DEBUG("Successfully downloaded ~ts", [Url]),
{"etag", ETag1} = lists:keyfind("etag", 1, Headers),
{ok, Body, rebar_string:trim(ETag1, both, [$"])};
{ok, {{_Version, 304, _Reason}, _Headers, _Body}} ->
?DEBUG("Cached copy of ~ts still valid", [Url]),
{ok, cached};
{ok, {{_Version, Code, _Reason}, _Headers, _Body}} ->
?DEBUG("Request to ~p failed: status code ~p", [Url, Code]),
error;
{error, Reason} ->
?DEBUG("Request to ~p failed: ~p", [Url, Reason]),
error
end.

+ 67
- 38
src/rebar_prv_packages.erl ファイルの表示

@ -15,53 +15,74 @@
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER},
{module, ?MODULE},
{bare, true},
{deps, ?DEPS},
{example, "rebar3 pkgs"},
{short_desc, "List available packages."},
{desc, info("List available packages")},
{opts, []}])),
State1 = rebar_state:add_provider(State,
providers:create([{name, ?PROVIDER},
{module, ?MODULE},
{bare, true},
{deps, ?DEPS},
{example, "rebar3 pkgs elli"},
{short_desc, "List information for a package."},
{desc, info("List information for a package")},
{opts, [{package, undefined, undefined, string,
"Package to fetch information for."}]}])),
{ok, State1}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
rebar_packages:packages(State),
case rebar_state:command_args(State) of
[Name] ->
print_packages(get_packages(rebar_utils:to_binary(Name)));
_ ->
print_packages(sort_packages())
end,
{ok, State}.
{Args, _} = rebar_state:command_parsed_args(State),
case proplists:get_value(package, Args, undefined) of
undefined ->
?PRV_ERROR(no_package_arg);
Name ->
Resources = rebar_state:resources(State),
#{repos := Repos} = rebar_resource:find_resource_state(pkg, Resources),
Results = get_package(rebar_utils:to_binary(Name), Repos),
case lists:all(fun({_, {error, not_found}}) -> true; (_) -> false end, Results) of
true ->
?PRV_ERROR({not_found, Name});
false ->
[print_packages(Result) || Result <- Results],
{ok, State}
end
end.
-spec format_error(any()) -> iolist().
format_error(load_registry_fail) ->
"Failed to load package regsitry. Try running 'rebar3 update' to fix".
get_package(Name, Repos) ->
lists:foldl(fun(RepoConfig, Acc) ->
[{maps:get(name, RepoConfig), rebar_packages:get(RepoConfig, Name)} | Acc]
end, [], Repos).
print_packages(Pkgs) ->
orddict:map(fun(Name, Vsns) ->
SortedVsns = lists:sort(fun(A, B) ->
ec_semver:lte(ec_semver:parse(A)
,ec_semver:parse(B))
end, Vsns),
VsnStr = join(SortedVsns, <<", ">>),
?CONSOLE("~ts:~n Versions: ~ts~n", [Name, VsnStr])
end, Pkgs).
sort_packages() ->
ets:foldl(fun({package_index_version, _}, Acc) ->
Acc;
({Pkg, Vsns}, Acc) ->
orddict:store(Pkg, Vsns, Acc);
(_, Acc) ->
Acc
end, orddict:new(), ?PACKAGE_TABLE).
-spec format_error(any()) -> iolist().
format_error(no_package_arg) ->
"Missing package argument to `rebar3 pkgs` command.";
format_error({not_found, Name}) ->
io_lib:format("Package ~ts not found in any repo.", [Name]);
format_error(unknown) ->
"Something went wrong with fetching package metadata.".
get_packages(Name) ->
ets:lookup(?PACKAGE_TABLE, Name).
print_packages({RepoName, {error, not_found}}) ->
?CONSOLE("~ts: Package not found in this repo.~n", [RepoName]);
print_packages({RepoName, {error, _}}) ->
?CONSOLE("~ts: Error fetching from this repo.~n", [RepoName]);
print_packages({RepoName, {ok, #{<<"name">> := Name,
<<"meta">> := Meta,
<<"releases">> := Releases}}}) ->
Description = maps:get(<<"description">>, Meta, ""),
Licenses = join(maps:get(<<"licenses">>, Meta, []), <<", ">>),
Links = join_map(maps:get(<<"links">>, Meta, []), <<"\n ">>),
Maintainers = join(maps:get(<<"maintainers">>, Meta, []), <<", ">>),
Versions = [V || #{<<"version">> := V} <- Releases],
VsnStr = join(Versions, <<", ">>),
?CONSOLE("~ts:~n"
" Name: ~ts~n"
" Description: ~ts~n"
" Licenses: ~ts~n"
" Maintainers: ~ts~n"
" Links:~n ~ts~n"
" Versions: ~ts~n", [RepoName, Name, Description, Licenses, Maintainers, Links, VsnStr]);
print_packages(_) ->
ok.
-spec join([binary()], binary()) -> binary().
join([Bin], _Sep) ->
@ -69,6 +90,14 @@ join([Bin], _Sep) ->
join([Bin | T], Sep) ->
<<Bin/binary, Sep/binary, (join(T, Sep))/binary>>.
-spec join_map(map(), binary()) -> binary().
join_map(Map, Sep) ->
join_tuple_list(maps:to_list(Map), Sep).
join_tuple_list([{K, V}], _Sep) ->
<<K/binary, ": ", V/binary>>;
join_tuple_list([{K, V} | T], Sep) ->
<<K/binary, ": ", V/binary, Sep/binary, (join_tuple_list(T, Sep))/binary>>.
info(Description) ->
io_lib:format("~ts.~n", [Description]).

+ 47
- 0
src/rebar_prv_repos.erl ファイルの表示

@ -0,0 +1,47 @@
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ts=4 sw=4 et
-module(rebar_prv_repos).
-behaviour(provider).
-export([init/1,
do/1,
format_error/1]).
-include("rebar.hrl").
-define(PROVIDER, repos).
-define(DEPS, []).
%% ===================================================================
%% Public API
%% ===================================================================
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
Provider = providers:create(
[{name, ?PROVIDER},
{module, ?MODULE},
{bare, false},
{deps, ?DEPS},
{example, "rebar3 repos"},
{short_desc, "Print current package repository configuration"},
{desc, "Display repository configuration for debugging purpose"},
{opts, []}]),
State1 = rebar_state:add_provider(State, Provider),
{ok, State1}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
Resources = rebar_state:resources(State),
#{repos := Repos} = rebar_resource:find_resource_state(pkg, Resources),
?CONSOLE("Repos:", []),
%%TODO: do some formatting
?CONSOLE("~p", [Repos]),
{ok, State}.
-spec format_error(any()) -> iolist().
format_error(Reason) ->
io_lib:format("~p", [Reason]).

+ 2
- 0
src/rebar_prv_shell.erl ファイルの表示

@ -40,6 +40,8 @@
-define(PROVIDER, shell).
-define(DEPS, [compile]).
-dialyzer({nowarn_function, rewrite_leaders/2}).
%% ===================================================================
%% Public API
%% ===================================================================

+ 13
- 225
src/rebar_prv_update.erl ファイルの表示

@ -9,12 +9,6 @@
do/1,
format_error/1]).
-export([hex_to_index/1]).
-ifdef(TEST).
-export([cmp_/6, cmpl_/6, valid_vsn/1]).
-endif.
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
@ -39,44 +33,13 @@ init(State) ->
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
try
case rebar_packages:registry_dir(State) of
{ok, RegistryDir} ->
filelib:ensure_dir(filename:join(RegistryDir, "dummy")),
HexFile = filename:join(RegistryDir, "registry"),
?INFO("Updating package registry...", []),
TmpDir = ec_file:insecure_mkdtemp(),
TmpFile = filename:join(TmpDir, "packages.gz"),
CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN),
case rebar_utils:url_append_path(CDN, ?REMOTE_REGISTRY_FILE) of
{ok, Url} ->
HttpOptions = [{relaxed, true} | rebar_utils:get_proxy_auth()],
?DEBUG("Fetching registry from ~p", [Url]),
case httpc:request(get, {Url, [{"User-Agent", rebar_utils:user_agent()}]},
HttpOptions, [{stream, TmpFile}, {sync, true}],
rebar) of
{ok, saved_to_file} ->
{ok, Data} = file:read_file(TmpFile),
Unzipped = zlib:gunzip(Data),
ok = file:write_file(HexFile, Unzipped),
?INFO("Writing registry to ~ts", [HexFile]),
hex_to_index(State),
{ok, State};
_ ->
?PRV_ERROR(package_index_download)
end;
_ ->
?PRV_ERROR({package_parse_cdn, CDN})
end;
{uri_parse_error, CDN} ->
?PRV_ERROR({package_parse_cdn, CDN})
end
catch
?WITH_STACKTRACE(_E, C, S)
?DEBUG("Error creating package index: ~p ~p", [C, S]),
throw(?PRV_ERROR(package_index_write))
end.
Names = rebar_packages:get_all_names(State),
Resources = rebar_state:resources(State),
#{repos := RepoConfigs} = rebar_resource:find_resource_state(pkg, Resources),
[[update_package(Name, RepoConfig, State)
|| Name <- Names]
|| RepoConfig <- RepoConfigs],
{ok, State}.
-spec format_error(any()) -> iolist().
format_error({package_parse_cdn, Uri}) ->
@ -86,186 +49,11 @@ format_error(package_index_download) ->
format_error(package_index_write) ->
"Failed to write package index.".
is_supported(<<"make">>) -> true;
is_supported(<<"rebar">>) -> true;
is_supported(<<"rebar3">>) -> true;
is_supported(_) -> false.
hex_to_index(State) ->
{ok, RegistryDir} = rebar_packages:registry_dir(State),
HexFile = filename:join(RegistryDir, "registry"),
try ets:file2tab(HexFile) of
{ok, Registry} ->
try
PackageIndex = filename:join(RegistryDir, "packages.idx"),
?INFO("Generating package index...", []),
(catch ets:delete(?PACKAGE_TABLE)),
ets:new(?PACKAGE_TABLE, [named_table, public]),
ets:foldl(fun({{Pkg, PkgVsn}, [Deps, Checksum, BuildTools | _]}, _) when is_list(BuildTools) ->
case lists:any(fun is_supported/1, BuildTools) of
true ->
DepsList = update_deps_list(Pkg, PkgVsn, Deps, Registry, State),
HashedDeps = update_deps_hashes(DepsList),
ets:insert(?PACKAGE_TABLE, {{Pkg, PkgVsn}, HashedDeps, Checksum});
false ->
true
end;
(_, _) ->
true
end, true, Registry),
ets:foldl(fun({Pkg, [[]]}, _) when is_binary(Pkg) ->
true;
({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 [V || V <- Vsns, ets:member(?PACKAGE_TABLE, {Pkg, V})] of
[] ->
true;
Vsns1 ->
ets:insert(?PACKAGE_TABLE, {Pkg, Vsns1})
end;
(_, _) ->
true
end, true, Registry),
ets:insert(?PACKAGE_TABLE, {package_index_version, ?PACKAGE_INDEX_VERSION}),
?INFO("Writing index to ~ts", [PackageIndex]),
ets:tab2file(?PACKAGE_TABLE, PackageIndex),
true
after
catch ets:delete(Registry)
end;
{error, Reason} ->
?DEBUG("Error loading package registry: ~p", [Reason]),
false
catch
_:_ ->
fail
end.
update_deps_list(Pkg, PkgVsn, Deps, HexRegistry, State) ->
lists:foldl(fun([Dep, DepVsn, false, AppName | _], DepsListAcc) ->
Dep1 = {Pkg, PkgVsn, Dep, AppName},
case {valid_vsn(DepVsn), DepVsn} of
%% Those are all not perfectly implemented!
%% and doubled since spaces seem not to be
%% enforced
{false, Vsn} ->
?DEBUG("[~ts:~ts], Bad dependency version for ~ts: ~ts.",
[Pkg, PkgVsn, Dep, Vsn]),
DepsListAcc;
{_, <<"~>", Vsn/binary>>} ->
highest_matching(Dep1, rm_ws(Vsn), HexRegistry,
State, DepsListAcc);
{_, <<">=", Vsn/binary>>} ->
cmp(Dep1, rm_ws(Vsn), HexRegistry, State,
DepsListAcc, fun ec_semver:gte/2);
{_, <<">", Vsn/binary>>} ->
cmp(Dep1, rm_ws(Vsn), HexRegistry, State,
DepsListAcc, fun ec_semver:gt/2);
{_, <<"<=", Vsn/binary>>} ->
cmpl(Dep1, rm_ws(Vsn), HexRegistry, State,
DepsListAcc, fun ec_semver:lte/2);
{_, <<"<", Vsn/binary>>} ->
cmpl(Dep1, rm_ws(Vsn), HexRegistry, State,
DepsListAcc, fun ec_semver:lt/2);
{_, <<"==", Vsn/binary>>} ->
[{AppName, {pkg, Dep, Vsn, undefined}} | DepsListAcc];
{_, Vsn} ->
[{AppName, {pkg, Dep, Vsn, undefined}} | DepsListAcc]
end;
([_Dep, _DepVsn, true, _AppName | _], DepsListAcc) ->
DepsListAcc
end, [], Deps).
update_deps_hashes(List) ->
[{Name, {pkg, PkgName, Vsn, lookup_hash(PkgName, Vsn, Hash)}}
|| {Name, {pkg, PkgName, Vsn, Hash}} <- List].
lookup_hash(Name, Vsn, undefined) ->
try
ets:lookup_element(?PACKAGE_TABLE, {Name, Vsn}, 3)
catch
_:_ ->
undefined
end;
lookup_hash(_, _, Hash) ->
Hash.
rm_ws(<<" ", R/binary>>) ->
rm_ws(R);
rm_ws(R) ->
R.
valid_vsn(Vsn) ->
%% Regepx from https://github.com/sindresorhus/semver-regex/blob/master/index.js
SemVerRegExp = "v?(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))?"
"(-[0-9a-z-]+(\\.[0-9a-z-]+)*)?(\\+[0-9a-z-]+(\\.[0-9a-z-]+)*)?",
SupportedVersions = "^(>=?|<=?|~>|==)?\\s*" ++ SemVerRegExp ++ "$",
re:run(Vsn, SupportedVersions, [unicode]) =/= nomatch.
highest_matching({Pkg, PkgVsn, Dep, App}, Vsn, HexRegistry, State, DepsListAcc) ->
case rebar_packages:find_highest_matching_(Pkg, PkgVsn, Dep, Vsn, HexRegistry, State) of
{ok, HighestDepVsn} ->
[{App, {pkg, Dep, HighestDepVsn, undefined}} | DepsListAcc];
none ->
?DEBUG("[~ts:~ts] Missing registry entry for package ~ts. Try to fix with `rebar3 update`",
[Pkg, PkgVsn, Dep]),
DepsListAcc
end.
cmp({_Pkg, _PkgVsn, Dep, _App} = Dep1, Vsn, HexRegistry, State, DepsListAcc, CmpFun) ->
{ok, Vsns} = rebar_packages:find_all(Dep, HexRegistry, State),
cmp_(undefined, Vsn, Vsns, DepsListAcc, Dep1, CmpFun).
cmp_(undefined, _MinVsn, [], DepsListAcc, {Pkg, PkgVsn, Dep, _App}, _CmpFun) ->
?DEBUG("[~ts:~ts] Missing registry entry for package ~ts. Try to fix with `rebar3 update`",
[Pkg, PkgVsn, Dep]),
DepsListAcc;
cmp_(HighestDepVsn, _MinVsn, [], DepsListAcc, {_Pkg, _PkgVsn, Dep, App}, _CmpFun) ->
[{App, {pkg, Dep, HighestDepVsn, undefined}} | DepsListAcc];
cmp_(BestMatch, MinVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) ->
case CmpFun(Vsn, MinVsn) of
true ->
cmp_(Vsn, Vsn, R, DepsListAcc, Dep, CmpFun);
false ->
cmp_(BestMatch, MinVsn, R, DepsListAcc, Dep, CmpFun)
end.
%% We need to treat this differently since we want a version that is LOWER but
%% the higest possible one.
cmpl({_Pkg, _PkgVsn, Dep, _App} = Dep1, Vsn, HexRegistry, State, DepsListAcc, CmpFun) ->
{ok, Vsns} = rebar_packages:find_all(Dep, HexRegistry, State),
cmpl_(undefined, Vsn, Vsns, DepsListAcc, Dep1, CmpFun).
cmpl_(undefined, _MaxVsn, [], DepsListAcc, {Pkg, PkgVsn, Dep, _App}, _CmpFun) ->
?DEBUG("[~ts:~ts] Missing registry entry for package ~ts. Try to fix with `rebar3 update`",
[Pkg, PkgVsn, Dep]),
DepsListAcc;
cmpl_(HighestDepVsn, _MaxVsn, [], DepsListAcc, {_Pkg, _PkgVsn, Dep, App}, _CmpFun) ->
[{App, {pkg, Dep, HighestDepVsn, undefined}} | DepsListAcc];
cmpl_(undefined, MaxVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) ->
case CmpFun(Vsn, MaxVsn) of
true ->
cmpl_(Vsn, MaxVsn, R, DepsListAcc, Dep, CmpFun);
false ->
cmpl_(undefined, MaxVsn, R, DepsListAcc, Dep, CmpFun)
end;
cmpl_(BestMatch, MaxVsn, [Vsn | R], DepsListAcc, Dep, CmpFun) ->
case CmpFun(Vsn, MaxVsn) of
true ->
case ec_semver:gte(Vsn, BestMatch) of
true ->
cmpl_(Vsn, MaxVsn, R, DepsListAcc, Dep, CmpFun);
false ->
cmpl_(BestMatch, MaxVsn, R, DepsListAcc, Dep, CmpFun)
end;
false ->
cmpl_(BestMatch, MaxVsn, R, DepsListAcc, Dep, CmpFun)
update_package(Name, RepoConfig, State) ->
case rebar_packages:update_package(Name, RepoConfig, State) of
fail ->
?WARN("Failed to fetch updates for package ~ts from repo ~ts", [Name, maps:get(name, RepoConfig)]);
_ ->
ok
end.

+ 34
- 1
src/rebar_prv_upgrade.erl ファイルの表示

@ -82,17 +82,22 @@ do_(State) ->
Deps = [Dep || Dep <- TopDeps ++ ProfileDeps, % TopDeps > ProfileDeps
is_atom(Dep) orelse is_atom(element(1, Dep))],
Names = parse_names(rebar_utils:to_binary(proplists:get_value(package, Args, <<"">>)), Locks),
DepsDict = deps_dict(rebar_state:all_deps(State)),
AltDeps = find_non_default_deps(Deps, State),
FilteredNames = cull_default_names_if_profiles(Names, Deps, State),
case prepare_locks(FilteredNames, Deps, Locks, [], DepsDict, AltDeps) of
{error, Reason} ->
{error, Reason};
{Locks0, _Unlocks0} ->
{Locks0, Unlocks0} ->
Deps0 = top_level_deps(Deps, Locks),
State1 = rebar_state:set(State, {deps, default}, Deps0),
DepsDir = rebar_prv_install_deps:profile_dep_dir(State, default),
D = rebar_app_utils:parse_deps(root, DepsDir, Deps0, State1, Locks0, 0),
%% first update the package index for the packages to be upgraded
update_pkg_deps(Unlocks0, D, State1),
State2 = rebar_state:set(State1, {parsed_deps, default}, D),
State3 = rebar_state:set(State2, {locks, default}, Locks0),
State4 = rebar_state:set(State3, upgrade, true),
@ -121,6 +126,34 @@ format_error({transitive_dependency, Name}) ->
format_error(Reason) ->
io_lib:format("~p", [Reason]).
%% fetch updates for package deps that have been unlocked for upgrade
update_pkg_deps([], _, _) ->
ok;
update_pkg_deps([{Name, _, _} | Rest], AppInfos, State) ->
case rebar_app_utils:find(Name, AppInfos) of
{ok, AppInfo} ->
case element(1, rebar_app_info:source(AppInfo)) of
pkg ->
Resources = rebar_state:resources(State),
#{repos := RepoConfigs} = rebar_resource:find_resource_state(pkg, Resources),
[update_package(Name, RepoConfig, State) || RepoConfig <- RepoConfigs];
_ ->
skip
end;
_ ->
%% this should be impossible...
skip
end,
update_pkg_deps(Rest, AppInfos, State).
update_package(Name, RepoConfig, State) ->
case rebar_packages:update_package(Name, RepoConfig, State) of
fail ->
?WARN("Failed to fetch updates for package ~ts from repo ~ts", [Name, maps:get(name, RepoConfig)]);
_ ->
ok
end.
parse_names(Bin, Locks) ->
case lists:usort(re:split(Bin, <<" *, *">>, [trim, unicode])) of
%% Nothing submitted, use *all* apps

+ 47
- 3
src/rebar_resource.erl ファイルの表示

@ -2,23 +2,67 @@
%% ex: ts=4 sw=4 et
-module(rebar_resource).
-export([]).
-export([new/3,
find_resource_module/2,
find_resource_state/2,
format_source/1]).
-export_type([resource/0
,source/0
,type/0
,location/0
,ref/0]).
-type resource() :: {type(), location(), ref()}.
-record(resource, {type :: atom(),
module :: module(),
state :: term()}).
-type resource() :: #resource{}.
-type source() :: {type(), location(), ref()} | {type(), location(), ref(), binary()}.
-type type() :: atom().
-type location() :: string().
-type ref() :: any().
-callback init(rebar_state:t()) -> {ok, term()}.
-callback lock(file:filename_all(), tuple()) ->
rebar_resource:resource().
source().
-callback download(file:filename_all(), tuple(), rebar_state:t()) ->
{tarball, file:filename_all()} | {ok, any()} | {error, any()}.
-callback needs_update(file:filename_all(), tuple()) ->
boolean().
-callback make_vsn(file:filename_all()) ->
{plain, string()} | {error, string()}.
-optional_callbacks([init/1]).
-spec new(type(), module(), term()) -> resource().
new(Type, Module, State) ->
#resource{type=Type,
module=Module,
state=State}.
find_resource_module(Type, Resources) ->
case lists:keyfind(Type, #resource.type, Resources) of
false when is_atom(Type) ->
case code:which(Type) of
non_existing ->
{error, not_found};
_ ->
{ok, Type}
end;
false ->
{error, not_found};
#resource{module=Module} ->
{ok, Module}
end.
find_resource_state(Type, Resources) ->
case lists:keyfind(Type, #resource.type, Resources) of
false ->
{error, not_found};
#resource{state=State} ->
State
end.
format_source({pkg, Name, Vsn, _Hash, _}) -> {pkg, Name, Vsn};
format_source(Source) -> Source.

+ 32
- 22
src/rebar_state.erl ファイルの表示

@ -38,6 +38,7 @@
to_list/1,
create_resources/2,
resources/1, resources/2, add_resource/2,
providers/1, providers/2, add_provider/2,
allow_provider_overrides/1, allow_provider_overrides/2
@ -75,26 +76,24 @@
-spec new() -> t().
new() ->
BaseState = base_state(),
BaseState = base_state(dict:new()),
BaseState#state_t{dir = rebar_dir:get_cwd()}.
-spec new(list()) -> t().
new(Config) when is_list(Config) ->
BaseState = base_state(),
Opts = base_opts(Config),
BaseState#state_t { dir = rebar_dir:get_cwd(),
default = Opts,
opts = Opts }.
BaseState = base_state(Opts),
BaseState#state_t{dir=rebar_dir:get_cwd(),
default=Opts}.
-spec new(t() | atom(), list()) -> t().
new(Profile, Config) when is_atom(Profile)
, is_list(Config) ->
BaseState = base_state(),
Opts = base_opts(Config),
BaseState#state_t { dir = rebar_dir:get_cwd(),
current_profiles = [Profile],
default = Opts,
opts = Opts };
BaseState = base_state(Opts),
BaseState#state_t{dir = rebar_dir:get_cwd(),
current_profiles = [Profile],
default = Opts};
new(ParentState=#state_t{}, Config) ->
%% Load terms from rebar.config, if it exists
Dir = rebar_dir:get_cwd(),
@ -129,20 +128,15 @@ deps_from_config(Dir, Config) ->
[{{locks, default}, D}, {{deps, default}, Deps}]
end.
base_state() ->
case application:get_env(rebar, resources) of
undefined ->
Resources = [];
{ok, Resources} ->
Resources
end,
#state_t{resources=Resources}.
base_state(Opts) ->
#state_t{opts=Opts}.
base_opts(Config) ->
Deps = proplists:get_value(deps, Config, []),
Plugins = proplists:get_value(plugins, Config, []),
ProjectPlugins = proplists:get_value(project_plugins, Config, []),
Terms = [{{deps, default}, Deps}, {{plugins, default}, Plugins}, {{project_plugins, default}, ProjectPlugins} | Config],
Terms = [{{deps, default}, Deps}, {{plugins, default}, Plugins},
{{project_plugins, default}, ProjectPlugins} | Config],
true = rebar_config:verify_config_format(Terms),
dict:from_list(Terms).
@ -365,11 +359,27 @@ resources(#state_t{resources=Resources}) ->
-spec resources(t(), [{rebar_resource:type(), module()}]) -> t().
resources(State, NewResources) ->
State#state_t{resources=NewResources}.
lists:foldl(fun(Resource, StateAcc) ->
add_resource(StateAcc, Resource)
end, State, NewResources).
-spec add_resource(t(), {rebar_resource:type(), module()}) -> t().
add_resource(State=#state_t{resources=Resources}, Resource) ->
State#state_t{resources=[Resource | Resources]}.
add_resource(State=#state_t{resources=Resources}, {ResourceType, ResourceModule}) ->
_ = code:ensure_loaded(ResourceModule),
{ok, ResourceState} = case erlang:function_exported(ResourceModule, init, 1) of
true ->
ResourceModule:init(State);
false ->
{ok, #{}}
end,
State#state_t{resources=[rebar_resource:new(ResourceType,
ResourceModule,
ResourceState) | Resources]}.
create_resources(Resources, State) ->
lists:foldl(fun(R, StateAcc) ->
add_resource(StateAcc, R)
end, State, Resources).
providers(#state_t{providers=Providers}) ->
Providers.

+ 4
- 1
src/rebar_string.erl ファイルの表示

@ -1,7 +1,7 @@
%%% @doc Compatibility module for string functionality
%%% for pre- and post-unicode support.
-module(rebar_string).
-export([join/2, lexemes/2, trim/3, uppercase/1, lowercase/1, chr/2]).
-export([join/2, split/2, lexemes/2, trim/3, uppercase/1, lowercase/1, chr/2]).
-ifdef(unicode_str).
@ -15,6 +15,7 @@ join([], Sep) when is_list(Sep) ->
join([H|T], Sep) ->
H ++ lists:append([Sep ++ X || X <- T]).
split(Str, SearchPattern) -> string:split(Str, SearchPattern).
lexemes(Str, SepList) -> string:lexemes(Str, SepList).
trim(Str, Direction, Cluster=[_]) -> string:trim(Str, Direction, Cluster).
uppercase(Str) -> string:uppercase(Str).
@ -27,6 +28,8 @@ chr([], _C, _I) -> 0.
-else.
join(Strings, Separator) -> string:join(Strings, Separator).
split(Str, SearchPattern) when is_list(Str) -> string:split(Str, SearchPattern);
split(Str, SearchPattern) when is_binary(Str) -> binary:split(Str, SearchPattern).
lexemes(Str, SepList) -> string:tokens(Str, SepList).
trim(Str, Direction, [Char]) ->
Dir = case Direction of

+ 117
- 15
src/rebar_utils.erl ファイルの表示

@ -74,13 +74,15 @@
user_agent/0,
reread_config/1,
get_proxy_auth/0,
is_list_of_strings/1]).
is_list_of_strings/1,
ssl_opts/1]).
%% for internal use only
-export([otp_release/0]).
-include("rebar.hrl").
-include_lib("public_key/include/OTP-PUB-KEY.hrl").
-define(ONE_LEVEL_INDENT, " ").
-define(APP_NAME_INDEX, 2).
@ -681,7 +683,7 @@ vcs_vsn_cmd(VCS, Dir, Resources) when VCS =:= semver ; VCS =:= "semver" ->
vcs_vsn_cmd({cmd, _Cmd}=Custom, _, _) ->
Custom;
vcs_vsn_cmd(VCS, Dir, Resources) when is_atom(VCS) ->
case find_resource_module(VCS, Resources) of
case rebar_resource:find_resource_module(VCS, Resources) of
{ok, Module} ->
Module:make_vsn(Dir);
{error, _} ->
@ -705,19 +707,6 @@ vcs_vsn_invoke(Cmd, Dir) ->
{ok, VsnString} = rebar_utils:sh(Cmd, [{cd, Dir}, {use_stdout, false}]),
rebar_string:trim(VsnString, trailing, "\n").
find_resource_module(Type, Resources) ->
case lists:keyfind(Type, 1, Resources) of
false ->
case code:which(Type) of
non_existing ->
{error, unknown};
_ ->
{ok, Type}
end;
{Type, Module} ->
{ok, Module}
end.
%% @doc ident to the level specified
-spec indent(non_neg_integer()) -> iolist().
indent(Amount) when erlang:is_integer(Amount) ->
@ -928,3 +917,116 @@ is_list_of_strings(List) when is_list(hd(List)) ->
true;
is_list_of_strings(List) when is_list(List) ->
true.
%%------------------------------------------------------------------------------
%% @doc
%% Return the SSL options adequate for the project based on
%% its configuration, including for validation of certs.
%% @end
%%------------------------------------------------------------------------------
-spec ssl_opts(Url) -> Res when
Url :: string(),
Res :: proplists:proplist().
ssl_opts(Url) ->
case get_ssl_config() of
ssl_verify_enabled ->
ssl_opts(ssl_verify_enabled, Url);
ssl_verify_disabled ->
[{verify, verify_none}]
end.
%%------------------------------------------------------------------------------
%% @doc
%% Return the SSL options adequate for the project based on
%% its configuration, including for validation of certs.
%% @end
%%------------------------------------------------------------------------------
-spec ssl_opts(Enabled, Url) -> Res when
Enabled :: atom(),
Url :: string(),
Res :: proplists:proplist().
ssl_opts(ssl_verify_enabled, Url) ->
case check_ssl_version() of
true ->
{ok, {_, _, Hostname, _, _, _}} =
http_uri:parse(rebar_utils:to_list(Url)),
VerifyFun = {fun ssl_verify_hostname:verify_fun/3,
[{check_hostname, Hostname}]},
CACerts = certifi:cacerts(),
[{verify, verify_peer}, {depth, 2}, {cacerts, CACerts},
{partial_chain, fun partial_chain/1}, {verify_fun, VerifyFun}];
false ->
?WARN("Insecure HTTPS request (peer verification disabled), "
"please update to OTP 17.4 or later", []),
[{verify, verify_none}]
end.
-spec partial_chain(Certs) -> Res when
Certs :: list(any()),
Res :: unknown_ca | {trusted_ca, any()}.
partial_chain(Certs) ->
Certs1 = [{Cert, public_key:pkix_decode_cert(Cert, otp)} || Cert <- Certs],
CACerts = certifi:cacerts(),
CACerts1 = [public_key:pkix_decode_cert(Cert, otp) || Cert <- CACerts],
case ec_lists:find(fun({_, Cert}) ->
check_cert(CACerts1, Cert)
end, Certs1) of
{ok, Trusted} ->
{trusted_ca, element(1, Trusted)};
_ ->
unknown_ca
end.
-spec extract_public_key_info(Cert) -> Res when
Cert :: #'OTPCertificate'{tbsCertificate::#'OTPTBSCertificate'{}},
Res :: any().
extract_public_key_info(Cert) ->
((Cert#'OTPCertificate'.tbsCertificate)#'OTPTBSCertificate'.subjectPublicKeyInfo).
-spec check_cert(CACerts, Cert) -> Res when
CACerts :: list(any()),
Cert :: any(),
Res :: boolean().
check_cert(CACerts, Cert) ->
lists:any(fun(CACert) ->
extract_public_key_info(CACert) == extract_public_key_info(Cert)
end, CACerts).
-spec check_ssl_version() ->
boolean().
check_ssl_version() ->
case application:get_key(ssl, vsn) of
{ok, Vsn} ->
parse_vsn(Vsn) >= {5, 3, 6};
_ ->
false
end.
-spec get_ssl_config() ->
ssl_verify_disabled | ssl_verify_enabled.
get_ssl_config() ->
GlobalConfigFile = rebar_dir:global_config(),
Config = rebar_config:consult_file(GlobalConfigFile),
case proplists:get_value(ssl_verify, Config, []) of
false ->
ssl_verify_disabled;
_ ->
ssl_verify_enabled
end.
-spec parse_vsn(Vsn) -> Res when
Vsn :: string(),
Res :: {integer(), integer(), integer()}.
parse_vsn(Vsn) ->
version_pad(rebar_string:lexemes(Vsn, ".-")).
-spec version_pad(list(nonempty_string())) -> Res when
Res :: {integer(), integer(), integer()}.
version_pad([Major]) ->
{list_to_integer(Major), 0, 0};
version_pad([Major, Minor]) ->
{list_to_integer(Major), list_to_integer(Minor), 0};
version_pad([Major, Minor, Patch]) ->
{list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)};
version_pad([Major, Minor, Patch | _]) ->
{list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)}.

+ 1
- 1
test/mock_git_resource.erl ファイルの表示

@ -27,7 +27,7 @@ mock(Opts) ->
mock(Opts, create_app).
mock(Opts, CreateType) ->
meck:new(?MOD, [no_link]),
meck:new(?MOD, [no_link, passthrough]),
mock_lock(Opts),
mock_update(Opts),
mock_vsn(Opts),

+ 41
- 19
test/mock_pkg_resource.erl ファイルの表示

@ -3,6 +3,8 @@
-export([mock/0, mock/1, unmock/0]).
-define(MOD, rebar_pkg_resource).
-include("rebar.hrl").
%%%%%%%%%%%%%%%%%
%%% Interface %%%
%%%%%%%%%%%%%%%%%
@ -26,7 +28,7 @@ mock() -> mock([]).
Vsn :: string(),
Hash :: string() | undefined.
mock(Opts) ->
meck:new(?MOD, [no_link]),
meck:new(?MOD, [no_link, passthrough]),
mock_lock(Opts),
mock_update(Opts),
mock_vsn(Opts),
@ -44,7 +46,7 @@ unmock() ->
%% @doc creates values for a lock file.
mock_lock(_) ->
meck:expect(?MOD, lock, fun(_AppDir, Source) -> Source end).
meck:expect(?MOD, lock, fun(_AppDir, {pkg, Name, Vsn, Hash, _RepoConfig}) -> {pkg, Name, Vsn, Hash} end).
%% @doc The config passed to the `mock/2' function can specify which apps
%% should be updated on a per-name basis: `{update, ["App1", "App3"]}'.
@ -52,7 +54,7 @@ mock_update(Opts) ->
ToUpdate = proplists:get_value(upgrade, Opts, []),
meck:expect(
?MOD, needs_update,
fun(_Dir, {pkg, App, _Vsn, _Hash}) ->
fun(_Dir, {pkg, App, _Vsn, _Hash, _}) ->
lists:member(binary_to_list(App), ToUpdate)
end).
@ -77,7 +79,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, []),
@ -110,16 +112,18 @@ mock_download(Opts) ->
%% specific applications otherwise listed.
mock_pkg_index(Opts) ->
Deps = proplists:get_value(pkgdeps, Opts, []),
Repos = proplists:get_value(repos, Opts, [<<"hexpm">>]),
Skip = proplists:get_value(not_in_index, Opts, []),
%% Dict: {App, Vsn}: [{<<"link">>, <<>>}, {<<"deps">>, []}]
%% Index: all apps and deps in the index
Dict = find_parts(Deps, Skip),
to_index(Deps, Dict, Repos),
meck:new(rebar_packages, [passthrough, no_link]),
meck:expect(rebar_packages, packages,
fun(_State) -> to_index(Deps, Dict) end),
meck:expect(rebar_packages, update_package,
fun(_, _, _State) -> ok end),
meck:expect(rebar_packages, verify_table,
fun(_State) -> to_index(Deps, Dict), true end).
fun(_State) -> true end).
%%%%%%%%%%%%%%%
%%% Helpers %%%
@ -143,24 +147,42 @@ find_parts([{AppName, Deps}|Rest], Skip, Acc) ->
Acc),
find_parts(Rest, Skip, AccNew)
end.
parse_deps(Deps) ->
[{maps:get(app, D, Name), {pkg, Name, Constraint, undefined}} || D=#{package := Name,
requirement := Constraint} <- Deps].
to_index(AllDeps, Dict, Repos) ->
catch ets:delete(?PACKAGE_TABLE),
rebar_packages:new_package_table(),
to_index(AllDeps, Dict) ->
catch ets:delete(package_index),
ets:new(package_index, [named_table, public]),
dict:fold(
fun(K, Deps, _) ->
DepsList = [{DKB, {pkg, DKB, DVB, undefined}}
fun({N, V}, Deps, _) ->
DepsList = [#{package => DKB,
app => DKB,
requirement => DVB,
source => {pkg, DKB, DVB, undefined}}
|| {DK, DV} <- Deps,
DKB <- [ec_cnv:to_binary(DK)],
DVB <- [ec_cnv:to_binary(DV)]],
ets:insert(package_index, {K, DepsList, <<"checksum">>})
Repo = rebar_test_utils:random_element(Repos),
ets:insert(?PACKAGE_TABLE, #package{key={N, V, Repo},
dependencies=parse_deps(DepsList),
retired=false,
checksum = <<"checksum">>})
end, ok, Dict),
ets:insert(package_index, {package_index_version, 3}),
lists:foreach(fun({{Name, Vsn}, _}) ->
case ets:lookup(package_index, ec_cnv:to_binary(Name)) of
[{_, Vsns}] ->
ets:insert(package_index, {ec_cnv:to_binary(Name), [ec_cnv:to_binary(Vsn) | Vsns]});
_ ->
ets:insert(package_index, {ec_cnv:to_binary(Name), [ec_cnv:to_binary(Vsn)]})
case lists:any(fun(R) ->
ets:member(?PACKAGE_TABLE, {ec_cnv:to_binary(Name), Vsn, R})
end, Repos) of
false ->
Repo = rebar_test_utils:random_element(Repos),
ets:insert(?PACKAGE_TABLE, #package{key={ec_cnv:to_binary(Name), Vsn, Repo},
dependencies=[],
retired=false,
checksum = <<"checksum">>});
true ->
ok
end
end, AllDeps).

+ 62
- 68
test/rebar_deps_SUITE.erl ファイルの表示

@ -188,27 +188,27 @@ deps(flat) ->
[],
{ok, ["B", "C"]}};
deps(pick_highest_left) ->
{[{"B", [{"C", "2", []}]},
{"C", "1", []}],
[{"C","2"}],
{ok, ["B", {"C", "1"}]}};
{[{"B", [{"C", "2.0.0", []}]},
{"C", "1.0.0", []}],
[{"C","2.0.0"}],
{ok, ["B", {"C", "1.0.0"}]}};
deps(pick_highest_right) ->
{[{"B", "1", []},
{"C", [{"B", "2", []}]}],
[{"B","2"}],
{ok, [{"B","1"}, "C"]}};
{[{"B", "1.0.0", []},
{"C", [{"B", "2.0.0", []}]}],
[{"B","2.0.0"}],
{ok, [{"B","1.0.0"}, "C"]}};
deps(pick_smallest1) ->
{[{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}],
[{"D","2"}],
{[{"B", [{"D", "1.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}],
[{"D","2.0.0"}],
%% we pick D1 because B < C
{ok, ["B","C",{"D","1"}]}};
{ok, ["B","C",{"D","1.0.0"}]}};
deps(pick_smallest2) ->
{[{"C", [{"D", "2", []}]},
{"B", [{"D", "1", []}]}],
[{"D","2"}],
{[{"C", [{"D", "2.0.0", []}]},
{"B", [{"D", "1.0.0", []}]}],
[{"D","2.0.0"}],
%% we pick D1 because B < C
{ok, ["B","C",{"D","1"}]}};
{ok, ["B","C",{"D","1.0.0"}]}};
deps(circular1) ->
{[{"B", [{"A", []}]}, % A is the top-level app
{"C", []}],
@ -222,15 +222,17 @@ deps(circular2) ->
deps(circular_skip) ->
%% Never spot the circular dep due to being to low in the deps tree
%% in source deps
{[{"B", [{"C", "2", [{"B", []}]}]},
{"C", "1", [{"D",[]}]}],
[{"C","2"}],
{ok, ["B", {"C","1"}, "D"]}}.
{[{"B", [{"C", "2.0.0", [{"B", []}]}]},
{"C", "1.0.0", [{"D",[]}]}],
[{"C","2.0.0"}],
{ok, ["B", {"C","1.0.0"}, "D"]}}.
setup_project(Case, Config0, Deps) ->
DepsType = ?config(deps_type, Config0),
%% spread packages across 3 repos randomly
Repos = [<<"test-repo-1">>, <<"test-repo-2">>, <<"hexpm">>],
Config = rebar_test_utils:init_rebar_state(
Config0,
[{repos, Repos} | Config0],
atom_to_list(Case)++"_"++atom_to_list(DepsType)++"_"
),
AppDir = ?config(apps, Config),
@ -239,7 +241,7 @@ setup_project(Case, Config0, Deps) ->
RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps}]),
{SrcDeps, PkgDeps} = rebar_test_utils:flat_deps(Deps),
mock_git_resource:mock([{deps, SrcDeps}]),
mock_pkg_resource:mock([{pkgdeps, PkgDeps}]),
mock_pkg_resource:mock([{pkgdeps, PkgDeps}, {repos, Repos}]),
[{rebarconfig, RebarConf} | Config].
mock_warnings() ->
@ -412,70 +414,62 @@ https_os_proxy_settings(_Config) ->
httpc:get_option(https_proxy, rebar)).
semver_matching_lt(_Config) ->
Dep = <<"test">>,
Dep1 = {Dep, <<"1.0.0">>, Dep, Dep},
MaxVsn = <<"0.2.0">>,
Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>, <<"0.2.1">>],
?assertEqual([{Dep, {pkg, Dep, <<"0.1.9">>, undefined}}],
rebar_prv_update:cmpl_(undefined, MaxVsn, Vsns, [], Dep1,
?assertEqual({ok, <<"0.1.9">>},
rebar_packages:cmpl_(undefined, MaxVsn, Vsns,
fun ec_semver:lt/2)).
semver_matching_lte(_Config) ->
Dep = <<"test">>,
Dep1 = {Dep, <<"1.0.0">>, Dep, Dep},
MaxVsn = <<"0.2.0">>,
Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>, <<"0.2.1">>],
?assertEqual([{Dep, {pkg, Dep, <<"0.2.0">>, undefined}}],
rebar_prv_update:cmpl_(undefined, MaxVsn, Vsns, [], Dep1,
?assertEqual({ok, <<"0.2.0">>},
rebar_packages:cmpl_(undefined, MaxVsn, Vsns,
fun ec_semver:lte/2)).
semver_matching_gt(_Config) ->
Dep = <<"test">>,
Dep1 = {Dep, <<"1.0.0">>, Dep, Dep},
MaxVsn = <<"0.2.0">>,
Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>, <<"0.2.1">>],
?assertEqual([{Dep, {pkg, Dep, <<"0.2.1">>, undefined}}],
rebar_prv_update:cmp_(undefined, MaxVsn, Vsns, [], Dep1,
?assertEqual({ok, <<"0.2.1">>},
rebar_packages:cmp_(undefined, MaxVsn, Vsns,
fun ec_semver:gt/2)).
semver_matching_gte(_Config) ->
Dep = <<"test">>,
Dep1 = {Dep, <<"1.0.0">>, Dep, Dep},
MaxVsn = <<"0.2.0">>,
Vsns = [<<"0.1.7">>, <<"0.1.9">>, <<"0.1.8">>, <<"0.2.0">>],
?assertEqual([{Dep, {pkg, Dep, <<"0.2.0">>, undefined}}],
rebar_prv_update:cmp_(undefined, MaxVsn, Vsns, [], Dep1,
?assertEqual({ok, <<"0.2.0">>},
rebar_packages:cmp_(undefined, MaxVsn, Vsns,
fun ec_semver:gt/2)).
valid_version(_Config) ->
?assert(rebar_prv_update:valid_vsn(<<"0.1">>)),
?assert(rebar_prv_update:valid_vsn(<<"0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<" 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<" 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"<0.1">>)),
?assert(rebar_prv_update:valid_vsn(<<"<0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"< 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"< 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<">0.1">>)),
?assert(rebar_prv_update:valid_vsn(<<">0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"> 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"> 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"<=0.1">>)),
?assert(rebar_prv_update:valid_vsn(<<"<=0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"<= 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"<= 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<">=0.1">>)),
?assert(rebar_prv_update:valid_vsn(<<">=0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<">= 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<">= 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"==0.1">>)),
?assert(rebar_prv_update:valid_vsn(<<"==0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"== 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"== 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"~>0.1">>)),
?assert(rebar_prv_update:valid_vsn(<<"~>0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"~> 0.1.0">>)),
?assert(rebar_prv_update:valid_vsn(<<"~> 0.1.0">>)),
?assertNot(rebar_prv_update:valid_vsn(<<"> 0.1.0 and < 0.2.0">>)),
?assert(rebar_packages:valid_vsn(<<"0.1">>)),
?assert(rebar_packages:valid_vsn(<<"0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<" 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<" 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"<0.1">>)),
?assert(rebar_packages:valid_vsn(<<"<0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"< 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"< 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<">0.1">>)),
?assert(rebar_packages:valid_vsn(<<">0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"> 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"> 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"<=0.1">>)),
?assert(rebar_packages:valid_vsn(<<"<=0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"<= 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"<= 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<">=0.1">>)),
?assert(rebar_packages:valid_vsn(<<">=0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<">= 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<">= 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"==0.1">>)),
?assert(rebar_packages:valid_vsn(<<"==0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"== 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"== 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"~>0.1">>)),
?assert(rebar_packages:valid_vsn(<<"~>0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"~> 0.1.0">>)),
?assert(rebar_packages:valid_vsn(<<"~> 0.1.0">>)),
?assertNot(rebar_packages:valid_vsn(<<"> 0.1.0 and < 0.2.0">>)),
ok.
@ -504,5 +498,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]).

+ 54
- 54
test/rebar_install_deps_SUITE.erl ファイルの表示

@ -121,27 +121,27 @@ deps(flat) ->
[],
{ok, ["B", "C"]}};
deps(pick_highest_left) ->
{[{"B", [{"C", "2", []}]},
{"C", "1", []}],
[{"C","2"}],
{ok, ["B", {"C", "1"}]}};
{[{"B", [{"C", "2.0.0", []}]},
{"C", "1.0.0", []}],
[{"C","2.0.0"}],
{ok, ["B", {"C", "1.0.0"}]}};
deps(pick_highest_right) ->
{[{"B", "1", []},
{"C", [{"B", "2", []}]}],
[{"B","2"}],
{ok, [{"B","1"}, "C"]}};
{[{"B", "1.0.0", []},
{"C", [{"B", "2.0.0", []}]}],
[{"B","2.0.0"}],
{ok, [{"B","1.0.0"}, "C"]}};
deps(pick_smallest1) ->
{[{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}],
[{"D","2"}],
{[{"B", [{"D", "1.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}],
[{"D","2.0.0"}],
%% we pick D1 because B < C
{ok, ["B","C",{"D","1"}]}};
{ok, ["B","C",{"D","1.0.0"}]}};
deps(pick_smallest2) ->
{[{"C", [{"D", "2", []}]},
{"B", [{"D", "1", []}]}],
[{"D","2"}],
{[{"C", [{"D", "2.0.0", []}]},
{"B", [{"D", "1.0.0", []}]}],
[{"D","2.0.0"}],
%% we pick D1 because B < C
{ok, ["B","C",{"D","1"}]}};
{ok, ["B","C",{"D","1.0.0"}]}};
deps(circular1) ->
{[{"B", [{"A", []}]}, % A is the top-level app
{"C", []}],
@ -155,14 +155,14 @@ deps(circular2) ->
deps(circular_skip) ->
%% Never spot the circular dep due to being to low in the deps tree
%% in source deps
{[{"B", [{"C", "2", [{"B", []}]}]},
{"C", "1", [{"D",[]}]}],
[{"C","2"}],
{ok, ["B", {"C","1"}, "D"]}};
{[{"B", [{"C", "2.0.0", [{"B", []}]}]},
{"C", "1.0.0", [{"D",[]}]}],
[{"C","2.0.0"}],
{ok, ["B", {"C","1.0.0"}, "D"]}};
deps(fail_conflict) ->
{[{"B", [{"C", "2", []}]},
{"C", "1", []}],
[{"C","2"}],
{[{"B", [{"C", "2.0.0", []}]},
{"C", "1.0.0", []}],
[{"C","2.0.0"}],
rebar_abort};
deps(default_profile) ->
{[{"B", []},
@ -216,39 +216,39 @@ mdeps(m_pick_source3) ->
[],
{ok, ["B"]}};
mdeps(m_pick_source4) ->
{[{"b", [{"d", "1", []}]},
{"C", [{"D", "1", []}]}],
[{"D", "1"}],
{ok, ["b", "C", {"d", "1"}]}};
{[{"b", [{"d", "1.0.0", []}]},
{"C", [{"D", "1.0.0", []}]}],
[{"D", "1.0.0"}],
{ok, ["b", "C", {"d", "1.0.0"}]}};
mdeps(m_pick_source5) ->
{[{"B", [{"d", "1", []}]},
{"C", [{"D", "1", []}]}],
[{"D", "1"}],
{ok, ["B", "C", {"d", "1"}]}};
{[{"B", [{"d", "1.0.0", []}]},
{"C", [{"D", "1.0.0", []}]}],
[{"D", "1.0.0"}],
{ok, ["B", "C", {"d", "1.0.0"}]}};
mdeps(m_source_to_pkg) ->
{[{"B", [{"c",[{"d", []}]}]}],
[],
{ok, ["B", "c", "d"]}};
mdeps(m_pkg_level1) ->
{[{"B", [{"D", [{"e", "2", []}]}]},
{"C", [{"e", "1", []}]}],
[{"e","2"}],
{ok, ["B","C","D",{"e","1"}]}};
{[{"B", [{"D", [{"e", "2.0.0", []}]}]},
{"C", [{"e", "1.0.0", []}]}],
[{"e","2.0.0"}],
{ok, ["B","C","D",{"e","1.0.0"}]}};
mdeps(m_pkg_level2) ->
{[{"B", [{"e", "1", []}]},
{"C", [{"D", [{"e", "2", []}]}]}],
[{"e","2"}],
{ok, ["B","C","D",{"e","1"}]}};
{[{"B", [{"e", "1.0.0", []}]},
{"C", [{"D", [{"e", "2.0.0", []}]}]}],
[{"e","2.0.0"}],
{ok, ["B","C","D",{"e","1.0.0"}]}};
mdeps(m_pkg_level3_alpha_order) ->
{[{"B", [{"d", [{"f", "1", []}]}]},
{"C", [{"E", [{"f", "2", []}]}]}],
[{"f","2"}],
{ok, ["B","C","d","E",{"f","1"}]}};
{[{"B", [{"d", [{"f", "1.0.0", []}]}]},
{"C", [{"E", [{"f", "2.0.0", []}]}]}],
[{"f","2.0.0"}],
{ok, ["B","C","d","E",{"f","1.0.0"}]}};
mdeps(m_pkg_level3) ->
{[{"B", [{"d", [{"f", "1", []}]}]},
{"C", [{"E", [{"G", [{"f", "2", []}]}]}]}],
[{"f","2"}],
{ok, ["B","C","d","E","G",{"f","1"}]}}.
{[{"B", [{"d", [{"f", "1.0.0", []}]}]},
{"C", [{"E", [{"G", [{"f", "2.0.0", []}]}]}]}],
[{"f","2.0.0"}],
{ok, ["B","C","d","E","G",{"f","1.0.0"}]}}.
setup_project(fail_conflict, Config0, Deps) ->
DepsType = ?config(deps_type, Config0),
@ -289,8 +289,8 @@ setup_project(nondefault_pick_highest, Config0, _) ->
),
AppDir = ?config(apps, Config),
rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
DefaultDeps = rebar_test_utils:expand_deps(DepsType, [{"B", [{"C", "1", []}]}]),
ProfileDeps = rebar_test_utils:expand_deps(DepsType, [{"C", "2", []}]),
DefaultDeps = rebar_test_utils:expand_deps(DepsType, [{"B", [{"C", "1.0.0", []}]}]),
ProfileDeps = rebar_test_utils:expand_deps(DepsType, [{"C", "2.0.0", []}]),
DefaultTop = rebar_test_utils:top_level_deps(DefaultDeps),
ProfileTop = rebar_test_utils:top_level_deps(ProfileDeps),
RebarConf = rebar_test_utils:create_config(
@ -412,19 +412,19 @@ nondefault_pick_highest(Config) ->
{ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
rebar_test_utils:run_and_check(
Config, RebarConfig, ["lock"],
{ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1"}, {dep, "C", "1"}], "default"}
{ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1.0.0"}, {dep, "C", "1.0.0"}], "default"}
),
rebar_test_utils:run_and_check(
Config, RebarConfig, ["as", "nondef", "lock"],
{ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1"}, {dep, "C", "2"}], "nondef"}
{ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1.0.0"}, {dep, "C", "2.0.0"}], "nondef"}
),
rebar_test_utils:run_and_check(
Config, RebarConfig, ["lock"],
{ok, [{dep, "B"}, {lock, "B"}, {dep, "C", "1"}, {lock, "C", "1"}], "default"}
{ok, [{dep, "B"}, {lock, "B"}, {dep, "C", "1.0.0"}, {lock, "C", "1.0.0"}], "default"}
),
rebar_test_utils:run_and_check(
Config, RebarConfig, ["as", "nondef", "lock"],
{ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1"}, {dep, "C", "2"}], "nondef"}
{ok, [{dep, "B"}, {lock, "B"}, {lock, "C", "1.0.0"}, {dep, "C", "2.0.0"}], "nondef"}
).
m_flat1(Config) -> run(Config).
@ -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]).

+ 6
- 1
test/rebar_localfs_resource.erl ファイルの表示

@ -13,13 +13,18 @@
-behaviour(rebar_resource).
-export([lock/2
-export([init/1
,lock/2
,download/3
,needs_update/2
,make_vsn/1]).
-include_lib("eunit/include/eunit.hrl").
-spec init(rebar_state:t()) -> {ok, term()}.
init(_State) ->
{ok, #{}}.
lock(AppDir, {localfs, Path, _Ref}) ->
lock(AppDir, {localfs, Path});
lock(_AppDir, {localfs, Path}) ->

+ 75
- 78
test/rebar_pkg_SUITE.erl ファイルの表示

@ -4,15 +4,16 @@
-compile(export_all).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include("rebar.hrl").
-define(bad_etag, "abcdef").
-define(good_etag, "22e1d7387c9085a462340088a2a8ba67").
-define(bad_etag, <<"abcdef";>>;).
-define(good_etag, <<"22e1d7387c9085a462340088a2a8ba67";>>;).
-define(bad_checksum, <<"D576B442A68C7B92BACDE1EFE9C6E54D8D6C74BDB71D8175B9D3C6EC8C7B62A7">>).
-define(good_checksum, <<"1C6CE379D191FBAB41B7905075E0BF87CBBE23C77CECE775C5A0B786B2244C35">>).
-define(BADPKG_ETAG, <<"BADETAG">>).
all() -> [good_uncached, good_cached, badindexchk, badpkg,
badhash_nocache, badhash_cache,
all() -> [good_uncached, good_cached, badpkg,
%% badindexchk, badhash_nocache, badhash_cache,
bad_to_good, good_disconnect, bad_disconnect, pkgs_provider,
find_highest_matching].
@ -32,10 +33,6 @@ init_per_testcase(pkgs_provider=Name, Config) ->
CacheDir = filename:join([CacheRoot, "hex", "com", "test", "packages"]),
filelib:ensure_dir(filename:join([CacheDir, "registry"])),
ok = ets:tab2file(Tid, filename:join([CacheDir, "registry"])),
meck:new(rebar_packages, [passthrough]),
meck:expect(rebar_packages, registry_dir, fun(_) -> {ok, CacheDir} end),
meck:expect(rebar_packages, package_dir, fun(_) -> {ok, CacheDir} end),
rebar_prv_update:hex_to_index(rebar_state:new()),
Config;
init_per_testcase(good_uncached=Name, Config0) ->
Config = [{good_cache, false},
@ -89,9 +86,9 @@ init_per_testcase(good_disconnect=Name, Config0) ->
| Config0],
Config = mock_config(Name, Config1),
copy_to_cache(Pkg, Config),
meck:unload(httpc),
%% meck:unload(httpc),
meck:new(httpc, [passthrough, unsticky]),
meck:expect(httpc, request, fun(_, _, _, _, _) -> {error, econnrefused} end),
meck:expect(httpc, request, fun(_, _, _, _) -> {error, econnrefused} end),
Config;
init_per_testcase(bad_disconnect=Name, Config0) ->
Pkg = {<<"goodpkg">>, <<"1.0.0">>},
@ -99,9 +96,12 @@ init_per_testcase(bad_disconnect=Name, Config0) ->
{pkg, Pkg}
| Config0],
Config = mock_config(Name, Config1),
meck:unload(httpc),
meck:new(httpc, [passthrough, unsticky]),
meck:expect(httpc, request, fun(_, _, _, _, _) -> {error, econnrefused} end),
%% meck:unload(httpc),
%% meck:new(httpc, [passthrough, unsticky]),
%% meck:expect(httpc, request, fun(_, _, _, _) -> {error, econnrefused} end),
meck:expect(hex_repo, get_tarball, fun(_, _, _) ->
{error, econnrefused}
end),
Config;
init_per_testcase(Name, Config0) ->
Config = [{good_cache, false},
@ -118,7 +118,7 @@ good_uncached(Config) ->
{Pkg,Vsn} = ?config(pkg, Config),
State = ?config(state, Config),
?assertEqual({ok, true},
rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum}, 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">>))).
@ -131,19 +131,9 @@ 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, ?good_checksum}, State)),
rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum, #{}}, State)),
{ok, Content} = file:read_file(CachedFile).
badindexchk(Config) ->
Tmp = ?config(tmp_dir, Config),
{Pkg,Vsn} = ?config(pkg, Config),
State = ?config(state, Config),
?assertMatch({bad_registry_checksum, _Path},
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">>))).
badpkg(Config) ->
Tmp = ?config(tmp_dir, Config),
{Pkg,Vsn} = ?config(pkg, Config),
@ -153,35 +143,11 @@ badpkg(Config) ->
ETagPath = filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".etag">>),
rebar_pkg_resource:store_etag_in_cache(ETagPath, ?BADPKG_ETAG),
?assertMatch({bad_download, _Path},
rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum}, State, false)),
rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum, #{}}, State, false)),
%% The cached/etag files are there for forensic purposes
?assert(filelib:is_regular(ETagPath)),
?assert(filelib:is_regular(CachePath)).
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),
@ -191,7 +157,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, ?good_checksum}, State)),
rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum, #{}}, State)),
%% Cache has refreshed
?assert({ok, Contents} =/= file:read_file(CachedFile)).
@ -206,7 +172,7 @@ good_disconnect(Config) ->
{ok, Content} = file:read_file(CachedFile),
rebar_pkg_resource:store_etag_in_cache(ETagFile, ?BADPKG_ETAG),
?assertEqual({ok, true},
rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum}, State)),
rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum, #{}}, State)),
{ok, Content} = file:read_file(CachedFile).
bad_disconnect(Config) ->
@ -214,32 +180,31 @@ 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, ?good_checksum}, State)).
rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn, ?good_checksum, #{}}, State)).
pkgs_provider(Config) ->
Config1 = rebar_test_utils:init_rebar_state(Config),
rebar_test_utils:run_and_check(
Config1, [], ["pkgs"],
Config1, [], ["pkgs", "relx"],
{ok, []}
).
find_highest_matching(_Config) ->
State = rebar_state:new(),
{ok, Vsn} = rebar_packages:find_highest_matching(
<<"test">>, <<"1.0.0">>, <<"goodpkg">>, <<"1.0.0">>, package_index, State),
{ok, Vsn} = rebar_packages:find_highest_matching_(
<<"goodpkg">>, <<"1.0.0">>, #{name => <<"hexpm">>}, ?PACKAGE_TABLE, State),
?assertEqual(<<"1.0.1">>, Vsn),
{ok, Vsn1} = rebar_packages:find_highest_matching(
<<"test">>, <<"1.0.0">>, <<"goodpkg">>, <<"1.0">>, package_index, State),
<<"goodpkg">>, <<"1.0">>, #{name => <<"hexpm">>}, ?PACKAGE_TABLE, State),
?assertEqual(<<"1.1.1">>, Vsn1),
{ok, Vsn2} = rebar_packages:find_highest_matching(
<<"test">>, <<"1.0.0">>, <<"goodpkg">>, <<"2.0">>, package_index, State),
<<"goodpkg">>, <<"2.0">>, #{name => <<"hexpm">>}, ?PACKAGE_TABLE, State),
?assertEqual(<<"2.0.0">>, Vsn2),
%% regression test. ~> constraints higher than the available packages would result
%% in returning the first package version instead of 'none'.
?assertEqual(none, rebar_packages:find_highest_matching(<<"test">>, <<"1.0.0">>, <<"goodpkg">>,
<<"~> 5.0">>, package_index, State)).
?assertEqual(none, rebar_packages:find_highest_matching_(<<"goodpkg">>, <<"~> 5.0">>,
#{name => <<"hexpm">>}, ?PACKAGE_TABLE, State)).
%%%%%%%%%%%%%%%
%%% Helpers %%%
@ -249,35 +214,67 @@ mock_config(Name, Config) ->
CacheRoot = filename:join([Priv, "cache", atom_to_list(Name)]),
TmpDir = filename:join([Priv, "tmp", atom_to_list(Name)]),
Tid = ets:new(registry_table, [public]),
ets:insert_new(Tid, [
{<<"badindexchk">>,[[<<"1.0.0">>]]},
{<<"goodpkg">>,[[<<"1.0.0">>, <<"1.0.1">>, <<"1.1.1">>, <<"2.0.0">>]]},
{<<"badpkg">>,[[<<"1.0.0">>]]},
AllDeps = [
{{<<"badindexchk">>,<<"1.0.0">>}, [[], ?bad_checksum, [<<"rebar3">>]]},
{{<<"goodpkg">>,<<"1.0.0">>}, [[], ?good_checksum, [<<"rebar3">>]]},
{{<<"goodpkg">>,<<"1.0.1">>}, [[], ?good_checksum, [<<"rebar3">>]]},
{{<<"goodpkg">>,<<"1.1.1">>}, [[], ?good_checksum, [<<"rebar3">>]]},
{{<<"goodpkg">>,<<"2.0.0">>}, [[], ?good_checksum, [<<"rebar3">>]]},
{{<<"badpkg">>,<<"1.0.0">>}, [[], ?good_checksum, [<<"rebar3">>]]}
]),
],
ets:insert_new(Tid, AllDeps),
CacheDir = filename:join([CacheRoot, "hex", "com", "test", "packages"]),
filelib:ensure_dir(filename:join([CacheDir, "registry"])),
ok = ets:tab2file(Tid, filename:join([CacheDir, "registry"])),
catch ets:delete(?PACKAGE_TABLE),
rebar_packages:new_package_table(),
lists:foreach(fun({{N, Vsn}, [Deps, Checksum, _]}) ->
case ets:member(?PACKAGE_TABLE, {ec_cnv:to_binary(N), Vsn, <<"hexpm">>}) of
false ->
ets:insert(?PACKAGE_TABLE, #package{key={ec_cnv:to_binary(N), Vsn, <<"hexpm">>},
dependencies=Deps,
retired=false,
checksum=Checksum});
true ->
ok
end
end, AllDeps),
meck:new(hex_repo, [passthrough]),
meck:expect(hex_repo, get_package,
fun(_Config, PkgName) ->
Matches = ets:match_object(Tid, {{PkgName,'_'}, '_'}),
Releases =
[#{checksum => Checksum,
version => Vsn,
dependencies => Deps} ||
{{_, Vsn}, [Deps, Checksum, _]} <- Matches],
{ok, {200, #{}, #{releases => Releases}}}
end),
%% The state returns us a fake registry
meck:new(rebar_state, [passthrough]),
meck:expect(rebar_state, get,
fun(_State, rebar_packages_cdn, _Default) ->
"http://test.com/"
"http://test.com/";
(_, _, Default) ->
Default
end),
meck:expect(rebar_state, resources,
fun(_State) ->
DefaultConfig = hex_core:default_config(),
[rebar_resource:new(pkg, rebar_pkg_resource,
#{repos => [DefaultConfig#{name => <<"hexpm">>}],
base_config => #{}})]
end),
meck:new(rebar_dir, [passthrough]),
meck:expect(rebar_dir, global_cache_dir, fun(_) -> CacheRoot end),
meck:new(rebar_packages, [passthrough]),
meck:expect(rebar_packages, registry_dir, fun(_) -> {ok, CacheDir} end),
meck:expect(rebar_packages, package_dir, fun(_) -> {ok, CacheDir} end),
rebar_prv_update:hex_to_index(rebar_state:new()),
meck:expect(rebar_packages, package_dir, fun(_, _) -> {ok, CacheDir} end),
meck:new(rebar_prv_update, [passthrough]),
meck:expect(rebar_prv_update, do, fun(State) -> {ok, State} end),
@ -285,16 +282,16 @@ mock_config(Name, Config) ->
%% Cache fetches are mocked -- we assume the server and clients are
%% correctly used.
GoodCache = ?config(good_cache, Config),
{Pkg,Vsn} = ?config(pkg, Config),
{Pkg,Vsn} = ?config(pkg, Config),
PkgFile = <<Pkg/binary, "-", Vsn/binary, ".tar">>,
{ok, PkgContents} = file:read_file(filename:join(?config(data_dir, Config), PkgFile)),
meck:new(httpc, [passthrough, unsticky]),
meck:expect(httpc, request,
fun(get, {_Url, _Opts}, _, _, _) when GoodCache ->;
{ok, {{Vsn, 304, <<"Not Modified">>}, [{"etag", ?good_etag}], <<>>}};
(get, {_Url, _Opts}, _, _, _) ->
{ok, {{Vsn, 200, <<"OK">>}, [{"etag", ?good_etag}], PkgContents}}
end),
meck:expect(hex_repo, get_tarball, fun(_, _, _) when GoodCache ->
{ok, {304, #{<<"etag">> => ?good_etag}, <<>>}};
(_, _, _) ->;
{ok, {200, #{<<"etag">> => ?good_etag}, PkgContents}}
end),
[{cache_root, CacheRoot},
{cache_dir, CacheDir},
{tmp_dir, TmpDir},

+ 89
- 28
test/rebar_pkg_alias_SUITE.erl ファイルの表示

@ -3,44 +3,53 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("kernel/include/file.hrl").
-include("rebar.hrl").
all() -> [same_alias, diff_alias, diff_alias_vsn, transitive_alias,
transitive_hash_mismatch].
all() -> [same_alias, diff_alias, diff_alias_vsn, transitive_alias%% ,
%% transitive_hash_mismatch
].
%% {uuid, {pkg, uuid}} = uuid
%% {uuid, {pkg, alias}} = uuid on disk
%% another run should yield the same lock file without error
init_per_suite(Config) ->
mock_config(?MODULE, Config).
Config.
%% mock_config(?MODULE, Config).
end_per_suite(Config) ->
unmock_config(Config).
Config.
%% unmock_config(Config).
init_per_testcase(same_alias, Config0) ->
mock_config(?MODULE, Config0),
Config = rebar_test_utils:init_rebar_state(Config0,"same_alias_"),
AppDir = ?config(apps, Config),
rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
RebarConf = rebar_test_utils:create_config(AppDir, [{deps, [{fakelib, {pkg, fakelib}}]}]),
[{rebarconfig, RebarConf} | Config];
init_per_testcase(diff_alias, Config0) ->
mock_config(?MODULE, Config0),
Config = rebar_test_utils:init_rebar_state(Config0,"diff_alias_"),
AppDir = ?config(apps, Config),
rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
RebarConf = rebar_test_utils:create_config(AppDir, [{deps, [{fakelib, {pkg, goodpkg}}]}]),
[{rebarconfig, RebarConf} | Config];
init_per_testcase(diff_alias_vsn, Config0) ->
mock_config(?MODULE, Config0),
Config = rebar_test_utils:init_rebar_state(Config0,"diff_alias_vsn_"),
AppDir = ?config(apps, Config),
rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
RebarConf = rebar_test_utils:create_config(AppDir, [{deps, [{fakelib, "1.0.0", {pkg, goodpkg}}]}]),
[{rebarconfig, RebarConf} | Config];
init_per_testcase(transitive_alias, Config0) ->
mock_config(?MODULE, Config0),
Config = rebar_test_utils:init_rebar_state(Config0,"transitive_alias_vsn_"),
AppDir = ?config(apps, Config),
rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
RebarConf = rebar_test_utils:create_config(AppDir, [{deps, [{topdep, "1.0.0", {pkg, topdep}}]}]),
[{rebarconfig, RebarConf} | Config];
init_per_testcase(transitive_hash_mismatch, Config0) ->
mock_config(?MODULE, Config0),
Config = rebar_test_utils:init_rebar_state(Config0,"transitive_alias_vsn_"),
AppDir = ?config(apps, Config),
rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]),
@ -48,6 +57,7 @@ init_per_testcase(transitive_hash_mismatch, Config0) ->
[{rebarconfig, RebarConf} | Config].
end_per_testcase(_, Config) ->
unmock_config(Config),
Config.
same_alias(Config) ->
@ -162,6 +172,10 @@ transitive_hash_mismatch(Config) ->
),
ok.
parse_deps(Deps) ->
[{maps:get(app, D, Name), {pkg, Name, Constraint, undefined}} || D=#{package := Name,
requirement := Constraint} <- Deps].
mock_config(Name, Config) ->
{ChkFake, Etag} = create_lib(Name, Config, "fakelib"),
{ChkTop, _} = create_lib(Name, Config, "topdep"),
@ -178,38 +192,85 @@ mock_config(Name, Config) ->
ct:pal("{~p, ~p}",[ChkFake, Etag]),
{ChkFake, Etag} = rebar_test_utils:package_app(AppDir, CacheDir, "goodpkg-1.0.0"),
AllDeps = [
{<<"fakelib">>,[[<<"1.0.0">>]]},
{<<"goodpkg">>,[[<<"1.0.0">>]]},
{<<"topdep">>,[[<<"1.0.0">>]]},
{<<"transitive">>, [[<<"1.0.0">>]]},
{{<<"fakelib">>,<<"1.0.0">>}, [[], ChkFake, [<<"rebar3">>]]},
{{<<"goodpkg">>,<<"1.0.0">>}, [[], ChkFake, [<<"rebar3">>]]},
{{<<"topdep">>,<<"1.0.0">>},
[[
{<<"transitive">>, <<"1.0.0">>, false, <<"transitive_app">>}
], ChkTop, [<<"rebar3">>]]},
{{<<"transitive">>,<<"1.0.0">>}, [[], ChkTrans, [<<"rebar3">>]]}
],
Tid = ets:new(registry_table, [public]),
ets:insert_new(Tid, [
{<<"fakelib">>,[[<<"1.0.0">>]]},
{<<"goodpkg">>,[[<<"1.0.0">>]]},
{<<"topdep">>,[[<<"1.0.0">>]]},
{<<"transitive">>, [[<<"1.0.0">>]]},
{{<<"fakelib">>,<<"1.0.0">>}, [[], ChkFake, [<<"rebar3">>]]},
{{<<"goodpkg">>,<<"1.0.0">>}, [[], ChkFake, [<<"rebar3">>]]},
{{<<"topdep">>,<<"1.0.0">>},
[[
[<<"transitive">>, <<"1.0.0">>, false, <<"transitive_app">>]
], ChkTop, [<<"rebar3">>]]},
{{<<"transitive">>,<<"1.0.0">>}, [[], ChkTrans, [<<"rebar3">>]]}
]),
ets:insert_new(Tid, AllDeps),
ok = ets:tab2file(Tid, filename:join([CacheDir, "registry"])),
ets:delete(Tid),
%% ets:delete(Tid),
%% The state returns us a fake registry
meck:new(rebar_dir, [passthrough, no_link]),
meck:expect(rebar_dir, global_cache_dir, fun(_) -> CacheRoot end),
meck:new(rebar_packages, [passthrough, no_link]),
meck:expect(rebar_packages, registry_dir, fun(_) -> {ok, CacheDir} end),
meck:expect(rebar_packages, package_dir, fun(_) -> {ok, CacheDir} end),
rebar_prv_update:hex_to_index(rebar_state:new()),
%% Cache fetches are mocked -- we assume the server and clients are
%% correctly used.
meck:new(httpc, [passthrough, unsticky, no_link]),
meck:expect(httpc, request,
fun(get, {_Url, _Opts}, _, _, _) ->
{ok, {{<<"1.0.0">>, 304, <<"Not Modified">>}, [{"etag", Etag}], <<>>}}
end),
meck:expect(rebar_packages, package_dir, fun(_, _) -> {ok, CacheDir} end),
%% TODO: is something else wrong that we need this for transitive_alias to pass
meck:expect(rebar_packages, update_package, fun(_, _, _) -> ok end),
meck:new(rebar_prv_update, [passthrough]),
meck:expect(rebar_prv_update, do, fun(State) -> {ok, State} end),
catch ets:delete(?PACKAGE_TABLE),
rebar_packages:new_package_table(),
lists:foreach(fun({{N, Vsn}, [Deps, Checksum, _]}) ->
case ets:member(?PACKAGE_TABLE, {ec_cnv:to_binary(N), Vsn, <<"hexpm">>}) of
false ->
ets:insert(?PACKAGE_TABLE, #package{key={ec_cnv:to_binary(N), Vsn, <<"hexpm">>},
dependencies=[{DAppName, {pkg, DN, DV, undefined}} || {DN, DV, _, DAppName} <- Deps],
retired=false,
checksum=Checksum});
true ->
ok
end;
({_N, _Vsns}) ->
ok
end, AllDeps),
meck:expect(rebar_packages, registry_checksum,
fun(N, V, _, _) ->
case ets:match_object(Tid, {{N, V}, '_'}) of
[{{_, _}, [_, Checksum, _]}] ->
Checksum
%% _ ->
%% {ok, {200, #{}, #{releases => []}}}
end
end),
meck:new(hex_repo, [passthrough]),
meck:expect(hex_repo, get_package,
fun(_Config, PkgName) ->
case ets:match_object(Tid, {{PkgName,'_'}, '_'}) of
Matches ->
Releases =
[#{checksum => Checksum,
version => Vsn,
dependencies => [{DAppName, {pkg, DN, DV, undefined}} || {DN, DV, _, DAppName} <- Deps]} ||
{{_, Vsn}, [Deps, Checksum, _]} <- Matches],
{ok, {200, #{}, #{releases => Releases}}}%% ;
%% _ ->
%% {ok, {200, #{}, #{releases => []}}}
end
end),
meck:expect(hex_repo, get_tarball, fun(_, _, _) ->
{ok, {304, #{<<"etag">> => Etag}, <<>>}}
end),
%% Move all packages to cache
NewConf = [{cache_root, CacheRoot},
{cache_dir, CacheDir},

+ 327
- 0
test/rebar_pkg_repos_SUITE.erl ファイルの表示

@ -0,0 +1,327 @@
%% Test suite for the handling hexpm repo configurations
-module(rebar_pkg_repos_SUITE).
-compile(export_all).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include("rebar.hrl").
all() ->
[default_repo, repo_merging, repo_replacing,
auth_merging, organization_merging, {group, resolve_version}].
groups() ->
[{resolve_version, [use_first_repo_match, use_exact_with_hash, fail_repo_update,
ignore_match_in_excluded_repo]}].
init_per_group(resolve_version, Config) ->
Repo1 = <<"test-repo-1">>,
Repo2 = <<"test-repo-2">>,
Repo3 = <<"test-repo-3">>,
Hexpm = <<"hexpm">>,
Repos = [Repo1, Repo2, Repo3, Hexpm],
Deps = [{"A", "0.1.1", <<"good checksum">>, Repo1},
{"A", "0.1.1", <<"good checksum">>, Repo2},
{"B", "1.0.0", Repo1},
{"B", "2.0.0", Repo2},
{"B", "1.4.0", Repo3},
{"B", "1.4.3", Hexpm},
{"C", "1.3.1", <<"bad checksum">>, Repo1},
{"C", "1.3.1", <<"good checksum">>, Repo2}],
[{deps, Deps}, {repos, Repos} | Config];
init_per_group(_, Config) ->
Config.
end_per_group(_, _) ->
ok.
init_per_testcase(use_first_repo_match, Config) ->
Deps = ?config(deps, Config),
Repos = ?config(repos, Config),
State = setup_deps_and_repos(Deps, Repos),
meck:new(rebar_packages, [passthrough, no_link]),
%% fail when the first repo is updated since it doesn't have a matching package
%% should continue anyway
meck:expect(rebar_packages, update_package,
fun(_, _, _State) -> ok end),
meck:expect(rebar_packages, verify_table,
fun(_State) -> true end),
[{state, State} | Config];
init_per_testcase(use_exact_with_hash, Config) ->
Deps = ?config(deps, Config),
Repos = ?config(repos, Config),
State = setup_deps_and_repos(Deps, Repos),
meck:new(rebar_packages, [passthrough, no_link]),
%% fail when the first repo is updated since it doesn't have a matching package
%% should continue anyway
meck:expect(rebar_packages, update_package,
fun(_, _, _State) -> ok end),
meck:expect(rebar_packages, verify_table,
fun(_State) -> true end),
[{state, State} | Config];
init_per_testcase(fail_repo_update, Config) ->
Deps = ?config(deps, Config),
Repos = ?config(repos, Config),
State = setup_deps_and_repos(Deps, Repos),
meck:new(rebar_packages, [passthrough, no_link]),
%% fail when the first repo is updated since it doesn't have a matching package
%% should continue anyway
[Repo1 | _] = Repos,
meck:expect(rebar_packages, update_package,
fun(_, #{name := Repo}, _State) when Repo =:= Repo1 -> fail;
(_, _, _State) -> ok end),
meck:expect(rebar_packages, verify_table,
fun(_State) -> true end),
[{state, State} | Config];
init_per_testcase(ignore_match_in_excluded_repo, Config) ->
Deps = ?config(deps, Config),
Repos = [Repo1, _, Repo3 | _] = ?config(repos, Config),
%% drop repo1 and repo2 from the repos to be used by the pkg resource
State = setup_deps_and_repos(Deps, [R || R <- Repos, R =/= Repo3, R =/= Repo1]),
meck:new(rebar_packages, [passthrough, no_link]),
%% fail when the first repo is updated since it doesn't have a matching package
%% should continue anyway
[_, _, Repo3 | _] = Repos,
meck:expect(rebar_packages, update_package,
fun(_, _, _State) -> ok end),
meck:expect(rebar_packages, verify_table,
fun(_State) -> true end),
[{state, State} | Config];
init_per_testcase(auth_merging, Config) ->
meck:new(file, [passthrough, no_link, unstick]),
meck:new(rebar_packages, [passthrough, no_link]),
Config;
init_per_testcase(organization_merging, Config) ->
meck:new(file, [passthrough, no_link, unstick]),
meck:new(rebar_packages, [passthrough, no_link]),
Config;
init_per_testcase(_, Config) ->
Config.
end_per_testcase(Case, _Config) when Case =:= auth_merging ;
Case =:= organization_merging ->
meck:unload(file),
meck:unload(rebar_packages);
end_per_testcase(Case, _Config) when Case =:= use_first_repo_match ;
Case =:= use_exact_with_hash ;
Case =:= fail_repo_update ;
Case =:= ignore_match_in_excluded_repo ->
meck:unload(rebar_packages);
end_per_testcase(_, _) ->
ok.
default_repo(_Config) ->
Repo1 = #{name => <<"hexpm">>,
api_key => <<"asdf">>},
MergedRepos = rebar_hex_repos:repos([{repos, [Repo1]}]),
?assertMatch([#{name := <<"hexpm">>,
api_key := <<"asdf">>,
api_url := <<"https://hex.pm/api">>}], MergedRepos).
repo_merging(_Config) ->
Repo1 = #{name => <<"repo-1">>,
api_url => <<"repo-1/api">>},
Repo2 = #{name => <<"repo-2">>,
repo_url => <<"repo-2/repo">>,
repo_verify => false},
Result = rebar_hex_repos:merge_repos([Repo1, Repo2,
#{name => <<"repo-2">>,
api_url => <<"repo-2/api">>,
repo_url => <<"bad url">>,
repo_verify => true},
#{name => <<"repo-1">>,
api_url => <<"bad url">>,
repo_verify => true},
#{name => <<"repo-2">>,
api_url => <<"repo-2/api-2">>,
repo_url => <<"other/repo">>}]),
?assertMatch([#{name := <<"repo-1">>,
api_url := <<"repo-1/api">>,
repo_verify := true},
#{name := <<"repo-2">>,
api_url := <<"repo-2/api">>,
repo_url := <<"repo-2/repo">>,
repo_verify := false}], Result).
repo_replacing(_Config) ->
Repo1 = #{name => <<"repo-1">>,
api_url => <<"repo-1/api">>},
Repo2 = #{name => <<"repo-2">>,
repo_url => <<"repo-2/repo">>,
repo_verify => false},
?assertMatch([Repo1, Repo2, #{name := <<"hexpm">>}],
rebar_hex_repos:repos([{repos, [Repo1]},
{repos, [Repo2]}])),
%% use of replace is ignored if found in later entries than the first
?assertMatch([Repo1, Repo2, #{name := <<"hexpm">>}],
rebar_hex_repos:repos([{repos, [Repo1]},
{repos, replace, [Repo2]}])),
?assertMatch([Repo1],
rebar_hex_repos:repos([{repos, replace, [Repo1]},
{repos, [Repo2]}])).
auth_merging(_Config) ->
Repo1 = #{name => <<"repo-1">>,
api_url => <<"repo-1/api">>},
Repo2 = #{name => <<"repo-2">>,
repo_url => <<"repo-2/repo">>,
repo_verify => false},
State = rebar_state:new([{hex, [{repos, [Repo1, Repo2]}]}]),
meck:expect(file, consult,
fun(_) ->
{ok, [#{<<"repo-1">> => #{read_key => <<"read key">>,
write_key => <<"write key">>},
<<"repo-2">> => #{read_key => <<"read key 2">>,
repos_key => <<"repos key 2">>,
write_key => <<"write key 2">>},
<<"hexpm">> => #{write_key => <<"write key hexpm">>}}]}
end),
?assertMatch({ok, #{repos := [#{name := <<"repo-1">>,
read_key := <<"read key">>,
write_key := <<"write key">>},
#{name := <<"repo-2">>,
read_key := <<"read key 2">>,
repos_key := <<"repos key 2">>,
write_key := <<"write key 2">>},
#{name := <<"hexpm">>,
write_key := <<"write key hexpm">>}]}}, rebar_pkg_resource:init(State)),
ok.
organization_merging(_Config) ->
Repo1 = #{name => <<"hexpm:repo-1">>,
api_url => <<"repo-1/api">>},
Repo2 = #{name => <<"hexpm:repo-2">>,
repo_url => <<"repo-2/repo">>,
repo_verify => false},
State = rebar_state:new([{hex, [{repos, [Repo1, Repo2]}]}]),
meck:expect(file, consult,
fun(_) ->
{ok, [#{<<"hexpm:repo-1">> => #{read_key => <<"read key">>},
<<"hexpm:repo-2">> => #{read_key => <<"read key 2">>,
repos_key => <<"repos key 2">>,
write_key => <<"write key 2">>},
<<"hexpm">> => #{write_key => <<"write key hexpm">>}}]}
end),
?assertMatch({ok, #{repos := [#{name := <<"hexpm:repo-1">>,
parent := <<"hexpm">>,
read_key := <<"read key">>,
write_key := <<"write key hexpm">>},
#{name := <<"hexpm:repo-2">>,
parent := <<"hexpm">>,
read_key := <<"read key 2">>,
repos_key := <<"repos key 2">>,
write_key := <<"write key 2">>},
#{name := <<"hexpm">>,
write_key := <<"write key hexpm">>}]}}, rebar_pkg_resource:init(State)),
ok.
use_first_repo_match(Config) ->
State = ?config(state, Config),
?assertMatch({ok,{package,{<<"B">>, <<"2.0.0">>, Repo2},
<<"some checksum">>, false, []},
#{name := Repo2,
http_adapter_config := #{profile := rebar}}},
rebar_packages:resolve_version(<<"B">>, <<"> 1.4.0">>, undefined,
?PACKAGE_TABLE, State)),
?assertMatch({ok,{package,{<<"B">>, <<"1.4.0">>, Repo3},
<<"some checksum">>, false, []},
#{name := Repo3,
http_adapter_config := #{profile := rebar}}},
rebar_packages:resolve_version(<<"B">>, <<"~> 1.4.0">>, undefined,
?PACKAGE_TABLE, State)).
%% tests that even though an easier repo has C-1.3.1 it doesn't use it since its hash is different
use_exact_with_hash(Config) ->
State = ?config(state, Config),
?assertMatch({ok,{package,{<<"C">>, <<"1.3.1">>, Repo2},
<<"good checksum">>, false, []},
#{name := Repo2,
http_adapter_config := #{profile := rebar}}},
rebar_packages:resolve_version(<<"C">>, <<"1.3.1">>, <<"good checksum">>,
?PACKAGE_TABLE, State)).
fail_repo_update(Config) ->
State = ?config(state, Config),
?assertMatch({ok,{package,{<<"B">>, <<"1.4.0">>, Repo3},
<<"some checksum">>, false, []},
#{name := Repo3,
http_adapter_config := #{profile := rebar}}},
rebar_packages:resolve_version(<<"B">>, <<"~> 1.4.0">>, undefined,
?PACKAGE_TABLE, State)).
ignore_match_in_excluded_repo(Config) ->
State = ?config(state, Config),
Repos = ?config(repos, Config),
?assertMatch({ok,{package,{<<"B">>, <<"1.4.3">>, Hexpm},
<<"some checksum">>, false, []},
#{name := Hexpm,
http_adapter_config := #{profile := rebar}}},
rebar_packages:resolve_version(<<"B">>, <<"~> 1.4.0">>, undefined,
?PACKAGE_TABLE, State)),
[_, Repo2 | _] = Repos,
?assertMatch({ok,{package,{<<"A">>, <<"0.1.1">>, Repo2},
<<"good checksum">>, false, []},
#{name := Repo2,
http_adapter_config := #{profile := rebar}}},
rebar_packages:resolve_version(<<"A">>, <<"0.1.1">>, <<"good checksum">>,
?PACKAGE_TABLE, State)).
%%
setup_deps_and_repos(Deps, Repos) ->
true = rebar_packages:new_package_table(),
insert_deps(Deps),
State = rebar_state:new([{hex, [{repos, [#{name => R} || R <- Repos]}]}]),
rebar_state:create_resources([{pkg, rebar_pkg_resource}], State).
insert_deps(Deps) ->
lists:foreach(fun({Name, Version, Repo}) ->
ets:insert(?PACKAGE_TABLE, #package{key={rebar_utils:to_binary(Name),
rebar_utils:to_binary(Version),
rebar_utils:to_binary(Repo)},
dependencies=[],
retired=false,
checksum = <<"some checksum">>});
({Name, Version, Checksum, Repo}) ->
ets:insert(?PACKAGE_TABLE, #package{key={rebar_utils:to_binary(Name),
rebar_utils:to_binary(Version),
rebar_utils:to_binary(Repo)},
dependencies=[],
retired=false,
checksum = Checksum})
end, Deps).

+ 8
- 1
test/rebar_test_utils.erl ファイルの表示

@ -5,7 +5,8 @@
-export([expand_deps/2, flat_deps/1, top_level_deps/1]).
-export([create_app/4, create_plugin/4, create_eunit_app/4, create_empty_app/4,
create_config/2, create_config/3, package_app/3]).
-export([create_random_name/1, create_random_vsn/0, write_src_file/2]).
-export([create_random_name/1, create_random_vsn/0, write_src_file/2,
random_element/1]).
%% Pick the right random module
-ifdef(rand_only).
@ -34,8 +35,10 @@ init_rebar_state(Config, Name) ->
Verbosity = rebar3:log_level(),
rebar_log:init(command_line, Verbosity),
GlobalDir = filename:join([DataDir, "cache"]),
Repos = proplists:get_value(repos, Config, []),
State = rebar_state:new([{base_dir, filename:join([AppsDir, "_build"])}
,{global_rebar_dir, GlobalDir}
,{hex, [{repos, [#{name => R} || R <- Repos]}]}
,{root_dir, AppsDir}]),
[{apps, AppsDir}, {checkouts, CheckoutsDir}, {state, State} | Config].
@ -488,3 +491,7 @@ package_app(AppDir, DestDir, PkgName) ->
<<E:128/big-unsigned-integer>> = crypto:hash(md5, BinFull),
Etag = rebar_string:lowercase(lists:flatten(io_lib:format("~32.16.0b", [E]))),
{BinChecksum, Etag}.
random_element(Repos) ->
Index = ?random:uniform(length(Repos)),
lists:nth(Index, Repos).

+ 221
- 221
test/rebar_upgrade_SUITE.erl ファイルの表示

@ -112,25 +112,25 @@ setup_project(Case, Config0, Deps, UpDeps) ->
upgrades(top_a) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
{[{"A", "1.0.0", [{"B", [{"D", "1.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
[{"A", "1.0.0", [{"B", [{"D", "3.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
%% upgrade vs. new tree
{"A", [{"A","1"}, "B", "C", {"D","3"}]}};
{"A", [{"A","1.0.0"}, "B", "C", {"D","3.0.0"}]}};
upgrades(top_b) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
{[{"A", "1.0.0", [{"B", [{"D", "1.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
[{"A", "1.0.0", [{"B", [{"D", "3.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
@ -138,12 +138,12 @@ upgrades(top_b) ->
{"B", {error, {rebar_prv_upgrade, {transitive_dependency, <<"B">>}}}}};
upgrades(top_c) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
{[{"A", "1.0.0", [{"B", [{"D", "1.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
[{"A", "1.0.0", [{"B", [{"D", "3.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
@ -151,12 +151,12 @@ upgrades(top_c) ->
{"C", {error, {rebar_prv_upgrade, {transitive_dependency, <<"C">>}}}}};
upgrades(top_d1) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
{[{"A", "1.0.0", [{"B", [{"D", "1.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
[{"A", "1.0.0", [{"B", [{"D", "3.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
@ -164,12 +164,12 @@ upgrades(top_d1) ->
{"D", {error, {rebar_prv_upgrade, {transitive_dependency, <<"D">>}}}}};
upgrades(top_d2) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
{[{"A", "1.0.0", [{"B", [{"D", "1.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
[{"A", "1.0.0", [{"B", [{"D", "3.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
@ -177,342 +177,342 @@ upgrades(top_d2) ->
{"D", {error, {rebar_prv_upgrade, {transitive_dependency, <<"D">>}}}}};
upgrades(top_e) ->
%% Original tree
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
{[{"A", "1.0.0", [{"B", [{"D", "1.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Updated tree
[{"A", "1", [{"B", [{"D", "3", []}]},
{"C", [{"D", "2", []}]}]}
[{"A", "1.0.0", [{"B", [{"D", "3.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
%% Modified apps, gobally
["A","B","D"],
%% upgrade vs. new tree
{"E", {error, {rebar_prv_upgrade, {unknown_dependency, <<"E">>}}}}};
upgrades(pair_a) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
{[{"A", "1.0.0", [{"C", "1.0.0", []}]},
{"B", "1.0.0", [{"D", "1.0.0", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
[{"A", "2.0.0", [{"C", "2.0.0", []}]},
{"B", "2.0.0", [{"D", "2.0.0", []}]}
],
["A","B","C","D"],
{"A", [{"A","2"},{"C","2"},{"B","1"},{"D","1"}]}};
{"A", [{"A","2.0.0"},{"C","2.0.0"},{"B","1.0.0"},{"D","1.0.0"}]}};
upgrades(pair_b) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
{[{"A", "1.0.0", [{"C", "1.0.0", []}]},
{"B", "1.0.0", [{"D", "1.0.0", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
[{"A", "2.0.0", [{"C", "2.0.0", []}]},
{"B", "2.0.0", [{"D", "2.0.0", []}]}
],
["A","B","C","D"],
{"B", [{"A","1"},{"C","1"},{"B","2"},{"D","2"}]}};
{"B", [{"A","1.0.0"},{"C","1.0.0"},{"B","2.0.0"},{"D","2.0.0"}]}};
upgrades(pair_ab) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
{[{"A", "1.0.0", [{"C", "1.0.0", []}]},
{"B", "1.0.0", [{"D", "1.0.0", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
[{"A", "2.0.0", [{"C", "2.0.0", []}]},
{"B", "2.0.0", [{"D", "2.0.0", []}]}
],
["A","B","C","D"],
{"A,B", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}};
{"A,B", [{"A","2.0.0"},{"C","2.0.0"},{"B","2.0.0"},{"D","2.0.0"}]}};
upgrades(pair_c) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
{[{"A", "1.0.0", [{"C", "1.0.0", []}]},
{"B", "1.0.0", [{"D", "1.0.0", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
[{"A", "2.0.0", [{"C", "2.0.0", []}]},
{"B", "2.0.0", [{"D", "2.0.0", []}]}
],
["A","B","C","D"],
{"C", {error, {rebar_prv_upgrade, {transitive_dependency, <<"C">>}}}}};
upgrades(pair_all) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
{[{"A", "1.0.0", [{"C", "1.0.0", []}]},
{"B", "1.0.0", [{"D", "1.0.0", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
[{"A", "2.0.0", [{"C", "2.0.0", []}]},
{"B", "2.0.0", [{"D", "2.0.0", []}]}
],
["A","B","C","D"],
{"", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}};
{"", [{"A","2.0.0"},{"C","2.0.0"},{"B","2.0.0"},{"D","2.0.0"}]}};
upgrades(triplet_a) ->
{[{"A", "1", [{"D",[]},
{"E","3",[]}]},
{"B", "1", [{"F","1",[]},
{[{"A", "1.0.0", [{"D",[]},
{"E","3.0.0",[]}]},
{"B", "1.0.0", [{"F","1.0.0",[]},
{"G",[]}]},
{"C", "0", [{"H","3",[]},
{"C", "0.0.0", [{"H","3.0.0",[]},
{"I",[]}]}],
[{"A", "1", [{"D",[]},
{"E","2",[]}]},
{"B", "1", [{"F","1",[]},
[{"A", "1.0.0", [{"D",[]},
{"E","2.0.0",[]}]},
{"B", "1.0.0", [{"F","1.0.0",[]},
{"G",[]}]},
{"C", "1", [{"H","4",[]},
{"C", "1.0.0", [{"H","4.0.0",[]},
{"I",[]}]}],
["A","C","E","H"],
{"A", [{"A","1"}, "D", {"E","2"},
{"B","1"}, {"F","1"}, "G",
{"C","0"}, {"H","3"}, "I"]}};
{"A", [{"A","1.0.0"}, "D", {"E","2.0.0"},
{"B","1.0.0"}, {"F","1.0.0"}, "G",
{"C","0.0.0"}, {"H","3.0.0"}, "I"]}};
upgrades(triplet_b) ->
{[{"A", "1", [{"D",[]},
{"E","3",[]}]},
{"B", "1", [{"F","1",[]},
{[{"A", "1.0.0", [{"D",[]},
{"E","3.0.0",[]}]},
{"B", "1.0.0", [{"F","1.0.0",[]},
{"G",[]}]},
{"C", "0", [{"H","3",[]},
{"C", "0.0.0", [{"H","3.0.0",[]},
{"I",[]}]}],
[{"A", "2", [{"D",[]},
{"E","2",[]}]},
{"B", "1", [{"F","1",[]},
[{"A", "2.0.0", [{"D",[]},
{"E","2.0.0",[]}]},
{"B", "1.0.0", [{"F","1.0.0",[]},
{"G",[]}]},
{"C", "1", [{"H","4",[]},
{"C", "1.0.0", [{"H","4.0.0",[]},
{"I",[]}]}],
["A","C","E","H"],
{"B", [{"A","1"}, "D", {"E","3"},
{"B","1"}, {"F","1"}, "G",
{"C","0"}, {"H","3"}, "I"]}};
{"B", [{"A","1.0.0"}, "D", {"E","3.0.0"},
{"B","1.0.0"}, {"F","1.0.0"}, "G",
{"C","0.0.0"}, {"H","3.0.0"}, "I"]}};
upgrades(triplet_c) ->
{[{"A", "1", [{"D",[]},
{"E","3",[]}]},
{"B", "1", [{"F","1",[]},
{[{"A", "1.0.0", [{"D",[]},
{"E","3.0.0",[]}]},
{"B", "1.0.0", [{"F","1.0.0",[]},
{"G",[]}]},
{"C", "0", [{"H","3",[]},
{"C", "0.0.0", [{"H","3.0.0",[]},
{"I",[]}]}],
[{"A", "2", [{"D",[]},
{"E","2",[]}]},
{"B", "1", [{"F","1",[]},
[{"A", "2.0.0", [{"D",[]},
{"E","2.0.0",[]}]},
{"B", "1.0.0", [{"F","1.0.0",[]},
{"G",[]}]},
{"C", "1", [{"H","4",[]},
{"C", "1.0.0", [{"H","4.0.0",[]},
{"I",[]}]}],
["A","C","E","H"],
{"C", [{"A","1"}, "D", {"E","3"},
{"B","1"}, {"F","1"}, "G",
{"C","1"}, {"H","4"}, "I"]}};
{"C", [{"A","1.0.0"}, "D", {"E","3.0.0"},
{"B","1.0.0"}, {"F","1.0.0"}, "G",
{"C","1.0.0"}, {"H","4.0.0"}, "I"]}};
upgrades(tree_a) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
{"C", "1.0.0", [{"H",[]},
{"I","2.0.0",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "2", [{"H",[]}]}
{"C", "2.0.0", [{"H",[]}]}
],
["C"],
{"A", [{"A","1"}, "D", "J", "E",
{"B","1"}, "F", "G",
{"C","1"}, "H", {"I","2"}]}};
{"A", [{"A","1.0.0"}, "D", "J", "E",
{"B","1.0.0"}, "F", "G",
{"C","1.0.0"}, "H", {"I","2.0.0"}]}};
upgrades(tree_b) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
{"C", "1.0.0", [{"H",[]},
{"I","2.0.0",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "2", [{"H",[]}]}
{"C", "2.0.0", [{"H",[]}]}
],
["C"],
{"B", [{"A","1"}, "D", "J", "E",
{"B","1"}, "F", "G",
{"C","1"}, "H", {"I","2"}]}};
{"B", [{"A","1.0.0"}, "D", "J", "E",
{"B","1.0.0"}, "F", "G",
{"C","1.0.0"}, "H", {"I","2.0.0"}]}};
upgrades(tree_c) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
{"C", "1.0.0", [{"H",[]},
{"I","2.0.0",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]}]}
{"C", "1.0.0", [{"H",[]}]}
],
["C","I"],
{"C", [{"A","1"}, "D", "J", "E", {"I","1"},
{"B","1"}, "F", "G",
{"C","1"}, "H"]}};
{"C", [{"A","1.0.0"}, "D", "J", "E", {"I","1.0.0"},
{"B","1.0.0"}, "F", "G",
{"C","1.0.0"}, "H"]}};
upgrades(tree_c2) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
{"C", "1.0.0", [{"H",[]},
{"I","2.0.0",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[{"K",[]}]},
{"I","2",[]}]}
{"C", "1.0.0", [{"H",[{"K",[]}]},
{"I","2.0.0",[]}]}
],
["C", "H"],
{"C", [{"A","1"}, "D", "J", "E",
{"B","1"}, "F", "G",
{"C","1"}, "H", {"I", "2"}, "K"]}};
{"C", [{"A","1.0.0"}, "D", "J", "E",
{"B","1.0.0"}, "F", "G",
{"C","1.0.0"}, "H", {"I", "2.0.0"}, "K"]}};
upgrades(tree_cj) ->
{[{"A", "1", [{"D",[{"J", "1",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{[{"A", "1.0.0", [{"D",[{"J", "1.0.0",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","1",[]}]}
{"C", "1.0.0", [{"H",[]},
{"I","1.0.0",[]}]}
],
[{"A", "1", [{"D",[{"J", "2", []}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
[{"A", "1.0.0", [{"D",[{"J", "2.0.0", []}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","1",[]}]}
{"C", "1.0.0", [{"H",[]},
{"I","1.0.0",[]}]}
],
["C","J"],
{"C", [{"A","1"}, "D", {"J", "1"}, "E", {"I","1"},
{"B","1"}, "F", "G",
{"C","1"}, "H"]}};
{"C", [{"A","1.0.0"}, "D", {"J", "1.0.0"}, "E", {"I","1.0.0"},
{"B","1.0.0"}, "F", "G",
{"C","1.0.0"}, "H"]}};
upgrades(tree_ac) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
{"C", "1.0.0", [{"H",[]},
{"I","2.0.0",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]}]}
{"C", "1.0.0", [{"H",[]}]}
],
["C","I"],
{"C, A", [{"A","1"}, "D", "J", "E", {"I","1"},
{"B","1"}, "F", "G",
{"C","1"}, "H"]}};
{"C, A", [{"A","1.0.0"}, "D", "J", "E", {"I","1.0.0"},
{"B","1.0.0"}, "F", "G",
{"C","1.0.0"}, "H"]}};
upgrades(tree_all) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
{"C", "1.0.0", [{"H",[]},
{"I","2.0.0",[]}]}
],
[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]}]}
{"C", "1.0.0", [{"H",[]}]}
],
["C","I"],
{"", [{"A","1"}, "D", "J", "E", {"I","1"},
{"B","1"}, "F", "G",
{"C","1"}, "H"]}};
{"", [{"A","1.0.0"}, "D", "J", "E", {"I","1.0.0"},
{"B","1.0.0"}, "F", "G",
{"C","1.0.0"}, "H"]}};
upgrades(delete_d) ->
{[{"A", "1", [{"B", [{"D", "1", []}]},
{"C", [{"D", "2", []}]}]}
{[{"A", "1.0.0", [{"B", [{"D", "1.0.0", []}]},
{"C", [{"D", "2.0.0", []}]}]}
],
[{"A", "2", [{"B", []},
[{"A", "2.0.0", [{"B", []},
{"C", []}]}
],
["A","B", "C"],
%% upgrade vs. new tree
{"", [{"A","2"}, "B", "C"]}};
{"", [{"A","2.0.0"}, "B", "C"]}};
upgrades(promote) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
{[{"A", "1.0.0", [{"C", "1.0.0", []}]},
{"B", "1.0.0", [{"D", "1.0.0", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]},
{"C", "3", []}
[{"A", "2.0.0", [{"C", "2.0.0", []}]},
{"B", "2.0.0", [{"D", "2.0.0", []}]},
{"C", "3.0.0", []}
],
["A","B","C","D"],
{"C", [{"A","1"},{"C","3"},{"B","1"},{"D","1"}]}};
{"C", [{"A","1.0.0"},{"C","3.0.0"},{"B","1.0.0"},{"D","1.0.0"}]}};
upgrades(stable_lock) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
{[{"A", "1.0.0", [{"C", "1.0.0", []}]},
{"B", "1.0.0", [{"D", "1.0.0", []}]}
], % lock after this
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
[{"A", "2.0.0", [{"C", "2.0.0", []}]},
{"B", "2.0.0", [{"D", "2.0.0", []}]}
],
[],
%% Run a regular lock and no app should be upgraded
{"any", [{"A","1"},{"C","1"},{"B","1"},{"D","1"}]}};
{"any", [{"A","1.0.0"},{"C","1.0.0"},{"B","1.0.0"},{"D","1.0.0"}]}};
upgrades(fwd_lock) ->
{[{"A", "1", [{"C", "1", []}]},
{"B", "1", [{"D", "1", []}]}
{[{"A", "1.0.0", [{"C", "1.0.0", []}]},
{"B", "1.0.0", [{"D", "1.0.0", []}]}
],
[{"A", "2", [{"C", "2", []}]},
{"B", "2", [{"D", "2", []}]}
[{"A", "2.0.0", [{"C", "2.0.0", []}]},
{"B", "2.0.0", [{"D", "2.0.0", []}]}
],
["A","B","C","D"],
%% For this one, we should build, rewrite the lock
%% file to include the result post-upgrade, and then
%% run a regular lock to see that the lock file is respected
%% in deps.
{"any", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}};
{"any", [{"A","2.0.0"},{"C","2.0.0"},{"B","2.0.0"},{"D","2.0.0"}]}};
upgrades(compile_upgrade_parity) ->
{[{"A", "1", [{"D",[{"J",[]}]},
{"E",[{"I","1",[]}]}]},
{"B", "1", [{"F",[]},
{[{"A", "1.0.0", [{"D",[{"J",[]}]},
{"E",[{"I","1.0.0",[]}]}]},
{"B", "1.0.0", [{"F",[]},
{"G",[]}]},
{"C", "1", [{"H",[]},
{"I","2",[]}]}
{"C", "1.0.0", [{"H",[]},
{"I","2.0.0",[]}]}
],
[],
[],
{"", [{"A","1"}, "D", "J", "E", {"I","1"},
{"B","1"}, "F", "G",
{"C","1"}, "H"]}};
{"", [{"A","1.0.0"}, "D", "J", "E", {"I","1.0.0"},
{"B","1.0.0"}, "F", "G",
{"C","1.0.0"}, "H"]}};
upgrades(umbrella_config) ->
{[{"A", "1", []}],
[{"A", "2", []}],
{[{"A", "1.0.0", []}],
[{"A", "2.0.0", []}],
["A"],
{"A", [{"A","2"}]}};
{"A", [{"A","2.0.0"}]}};
upgrades(profiles) ->
%% Ensure that we can unlock deps under a given profile;
%% B and C should both be in a custom profile
%% and must not be locked.
{[{"A", "1", [{"D",[]},
{"E","3",[]}]},
{"B", "1", [{"F","1",[]},
{[{"A", "1.0.0", [{"D",[]},
{"E","3.0.0",[]}]},
{"B", "1.0.0", [{"F","1.0.0",[]},
{"G",[]}]},
{"C", "0", [{"H","3",[]},
{"C", "0.0.0", [{"H","3.0.0",[]},
{"I",[]}]}],
[{"A", "2", [{"D",[]},
{"E","2",[]}]},
{"B", "2", [{"F","2",[]},
[{"A", "2.0.0", [{"D",[]},
{"E","2.0.0",[]}]},
{"B", "2.0.0", [{"F","2.0.0",[]},
{"G",[]}]},
{"C", "1", [{"H","4",[]},
{"C", "1.0.0", [{"H","4.0.0",[]},
{"I",[]}]}],
["A","B","C","E","F","H"],
{"C", [{"A","1"}, "D", {"E","3"},
{"B","2"}, {"F","2"}, "G",
{"C","1"}, {"H","4"}, "I"]}};
{"C", [{"A","1.0.0"}, "D", {"E","3.0.0"},
{"B","2.0.0"}, {"F","2.0.0"}, "G",
{"C","1.0.0"}, {"H","4.0.0"}, "I"]}};
upgrades(profiles_exclusion) ->
%% Ensure that we can unlock deps under a given profile;
%% B and C should both be in a custom profile
%% and must not be locked.
{[{"A", "1", [{"D",[]},
{"E","3",[]}]},
{"B", "1", [{"F","1",[]},
{[{"A", "1.0.0", [{"D",[]},
{"E","3.0.0",[]}]},
{"B", "1.0.0", [{"F","1.0.0",[]},
{"G",[]}]},
{"C", "0", [{"H","3",[]},
{"C", "0.0.0", [{"H","3.0.0",[]},
{"I",[]}]}],
[{"A", "2", [{"D",[]},
{"E","2",[]}]},
{"B", "2", [{"F","2",[]},
[{"A", "2.0.0", [{"D",[]},
{"E","2.0.0",[]}]},
{"B", "2.0.0", [{"F","2.0.0",[]},
{"G",[]}]},
{"C", "1", [{"H","4",[]},
{"C", "1.0.0", [{"H","4.0.0",[]},
{"I",[]}]}],
["A","B","C","E","F","H"],
{"A", [{"A","1"}, "D", {"E","3"},
{"B","2"}, {"F","2"}, "G",
{"C","1"}, {"H","4"}, "I"]}}.
{"A", [{"A","1.0.0"}, "D", {"E","3.0.0"},
{"B","2.0.0"}, {"F","2.0.0"}, "G",
{"C","1.0.0"}, {"H","4.0.0"}, "I"]}}.
%% TODO: add a test that verifies that unlocking files and then
%% running the upgrade code is enough to properly upgrade things.

読み込み中…
キャンセル
保存