Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56f1037973 | ||
|
|
e5fcef3832 | ||
|
|
9081a7aad2 | ||
|
|
328f581ef5 | ||
|
|
aa695875fe | ||
|
|
aa039ce9f9 | ||
|
|
2fdf96e747 | ||
|
|
494bded12a | ||
|
|
2b0fc529ee | ||
|
|
aaa775a3bf | ||
|
|
6647b00fcf | ||
|
|
d149c4c1e0 | ||
|
|
58810bd33c | ||
|
|
050cb45664 | ||
|
|
7f59aeeb6f | ||
|
|
e2abd35b3c | ||
|
|
cc35c8fc22 | ||
|
|
2718631eb6 | ||
|
|
9d9aa8bff8 | ||
|
|
65aee00809 | ||
|
|
9f028834d7 | ||
|
|
44ef53ec2b | ||
|
|
d1290d62db | ||
|
|
301aa6eddc | ||
|
|
098b2d7cfd | ||
|
|
d92b7a331b | ||
|
|
8930ea7598 |
@@ -268,6 +268,36 @@ end
|
||||
|
||||
By restricting hierarchies in favor of named setups, it is straight-forward for the developer to glance at each describe block and know exactly the setup steps involved.
|
||||
|
||||
## v1.3.2 (2016-07-15)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Support guards in `else` clauses in `with`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Add `MIX_NO_DEPS` env var for disabling dep loading. Used for third-party scripts and tools like Nix package manager
|
||||
* [Mix] Add `mix test --listen-on-stdin` that automatically reruns tests on stdin
|
||||
* [Mix] Disable `--warnings-as-errors` when compiling dependencies
|
||||
* [Mix] Add `--filter` option to `mix deps.unlock` to unlock only matching dependencies
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Enum] Return `nil` if enumerable halts in `Enum.find_index/3`
|
||||
* [Kernel] Do not attempt to load modules that have not been required when invoking regular functions, otherwise this invalidates the `@compile {:autoload, false}` directive.
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Ensure missing protocol dependencies are discarded in umbrella projects with shared build
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Diff] Ensure no leading or trailing when diffing some maps
|
||||
|
||||
## v1.3.1 (2016-06-28)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -435,6 +435,13 @@ defmodule Access do
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{"john", [%{name: "JOHN"}, %{name: "mary"}]}
|
||||
|
||||
`at/1` can also be used to pop elements out of a list or
|
||||
a key inside of a list:
|
||||
|
||||
iex> list = [%{name: "john"}, %{name: "mary"}]
|
||||
iex> pop_in(list, [Access.at(0)])
|
||||
{%{name: "john"}, [%{name: "mary"}]}
|
||||
iex> pop_in(list, [Access.at(0), :name])
|
||||
{"john", [%{}, %{name: "mary"}]}
|
||||
|
||||
@@ -477,7 +484,7 @@ defmodule Access do
|
||||
defp get_and_update_at([head | rest], 0, next, updates) do
|
||||
case next.(head) do
|
||||
{get, update} -> {get, :lists.reverse([update | updates], rest)}
|
||||
:pop -> {head, :lists.reverse([head | updates], rest)}
|
||||
:pop -> {head, :lists.reverse(updates, rest)}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -861,13 +861,13 @@ defmodule Enum do
|
||||
|
||||
def find_index(enumerable, fun) do
|
||||
res =
|
||||
Enumerable.reduce(enumerable, {:cont, 0}, fn(entry, acc) ->
|
||||
if fun.(entry), do: {:halt, acc}, else: {:cont, acc + 1}
|
||||
Enumerable.reduce(enumerable, {:cont, {:not_found, 0}}, fn(entry, {status, index}) ->
|
||||
if fun.(entry), do: {:halt, {:found, index}}, else: {:cont, {:not_found, index + 1}}
|
||||
end)
|
||||
|
||||
case res do
|
||||
{:halted, entry} -> entry
|
||||
{:done, _} -> nil
|
||||
{_, {:found, index}} -> index
|
||||
{_, {:not_found, _}} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -15,7 +15,11 @@ defmodule Exception do
|
||||
"""
|
||||
|
||||
@typedoc "The exception type"
|
||||
@type t :: %{__struct__: module, __exception__: true}
|
||||
@type t :: %{
|
||||
required(:__struct__) => module,
|
||||
required(:__exception__) => true,
|
||||
atom => any
|
||||
}
|
||||
|
||||
@typedoc "The kind handled by formatting functions"
|
||||
@type kind :: :error | :exit | :throw | {:EXIT, pid}
|
||||
|
||||
@@ -1862,7 +1862,7 @@ defmodule Kernel do
|
||||
In case any entry returns `nil`, its key will be removed
|
||||
and the deletion will be considered a success.
|
||||
"""
|
||||
@spec pop_in(Access.t, nonempty_list(term)) :: Access.t
|
||||
@spec pop_in(Access.t, nonempty_list(term)) :: {term, Access.t}
|
||||
def pop_in(data, keys)
|
||||
def pop_in(nil, [h | _]), do: Access.pop(nil, h)
|
||||
def pop_in(data, keys), do: do_pop_in(data, keys)
|
||||
@@ -4314,7 +4314,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
# TODO: Deprecate by v1.5
|
||||
# @doc false
|
||||
@doc false
|
||||
defmacro to_char_list(arg) do
|
||||
quote do: Kernel.to_charlist(unquote(arg))
|
||||
end
|
||||
|
||||
@@ -448,7 +448,7 @@ defmodule Kernel.Typespec do
|
||||
defp translate_spec(kind, meta, name, args, return, guard, caller) when is_atom(args),
|
||||
do: translate_spec(kind, meta, name, [], return, guard, caller)
|
||||
defp translate_spec(:macrocallback, meta, name, args, return, guard, caller),
|
||||
do: translate_spec(:callback, meta, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t) | args], return, guard, caller)
|
||||
do: translate_spec(:callback, meta, :"MACRO-#{name}", macro_args(args), return, guard, caller)
|
||||
defp translate_spec(kind, meta, name, args, return, guard, caller) do
|
||||
ensure_no_defaults!(args)
|
||||
|
||||
@@ -470,6 +470,10 @@ defmodule Kernel.Typespec do
|
||||
{{kind, {name, arity}, spec}, caller.line}
|
||||
end
|
||||
|
||||
defp macro_args(args) do
|
||||
[quote(do: {line :: Macro.Env.line, env :: Macro.Env.t}) | args]
|
||||
end
|
||||
|
||||
defp ensure_no_defaults!(args) do
|
||||
:lists.foreach fn
|
||||
{:::, _, [left, right]} ->
|
||||
|
||||
@@ -15,13 +15,24 @@ defmodule OptionParser do
|
||||
@doc """
|
||||
Parses `argv` into a keywords list.
|
||||
|
||||
It returns a three-element tuple as follows:
|
||||
It returns a three-element tuple with the form `{parsed, args, invalid}`, where:
|
||||
|
||||
1. parsed switches,
|
||||
2. remaining arguments,
|
||||
3. invalid options.
|
||||
* `parsed` is a keyword list of parsed switches with `{switch_name, value}`
|
||||
tuples in it; `switch_name` is the atom representing the switch name while
|
||||
`value` is the value for that switch parsed according to `opts` (see the
|
||||
"Examples" section for more information)
|
||||
* `args` is a list of the remaining arguments in `argv` as strings
|
||||
* `invalid` is a list of invalid options as `{option_name, value}` where
|
||||
`option_name` is the raw option and `value` is `nil` if the option wasn't
|
||||
expected or the string value if the value didn't have the expected type for
|
||||
the corresponding option
|
||||
|
||||
## Examples
|
||||
Elixir converts switches to underscored atoms, so `--source-path` becomes
|
||||
`:source_path`. This is done to better suit Elixir conventions. However, this
|
||||
means that switches can't contain underscores and switches that do contain
|
||||
underscores are always returned in the list of invalid options.
|
||||
|
||||
Without any options, this function will try to parse all switches in the `argv`.
|
||||
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
{[debug: true], [], []}
|
||||
@@ -32,65 +43,85 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
|
||||
|
||||
By default, Elixir will try to automatically parse all switches.
|
||||
Switches followed by a value will be assigned the value, as a string.
|
||||
Switches without an argument, like `--debug` will automatically
|
||||
be set to `true`.
|
||||
Switches without an argument, like `--debug` in the examples above, will
|
||||
automatically be set to `true`.
|
||||
|
||||
Note: Elixir also converts the switches to underscore atoms, so
|
||||
`--source-path` becomes `:source_path`, to better suit Elixir
|
||||
conventions. This means that option names on the command line cannot
|
||||
contain underscores; such options will be put in the invalid options
|
||||
list.
|
||||
## Options
|
||||
|
||||
## Switch Definitions
|
||||
The following options are supported:
|
||||
|
||||
* `:switches` or `:strict` - see the "Switch definitions" section below
|
||||
* `:aliases` - see the "Aliases" section below
|
||||
|
||||
## Switch definitions
|
||||
|
||||
Often it is better to explicitly list the known
|
||||
switches and their formats. The switches can be specified via two
|
||||
alternative options:
|
||||
switches and their formats. The switches can be specified via one of two
|
||||
options:
|
||||
|
||||
* `:switches` - defines some switches. An attempt is still made to parse
|
||||
switches that do not appear in the list.
|
||||
* `:switches` - defines some switches and their types. This function
|
||||
still attempts to parse switches that are not in this list.
|
||||
* `:strict` - defines strict switches. Any switch in `argv` that is not
|
||||
specified in the list is returned in the invalid options list.
|
||||
|
||||
* `:strict` - the switches are strict. Any switch that is not specified
|
||||
in the list is returned in the invalid options list.
|
||||
Both these options accept a keyword list of `{name, type}` tuples where `name`
|
||||
is an atom defining the name of the switch and `type` is an atom that
|
||||
specifies the type for the value of this switch (see the "Types" section below
|
||||
for the possible types and more information about type casting).
|
||||
|
||||
Note that you should only supply the `:switches` or `:strict` option.
|
||||
If you supply both, an error will be raised.
|
||||
If you supply both, an `ArgumentError` exception will be raised.
|
||||
|
||||
### Types
|
||||
|
||||
Option parser switches may take 0 or 1 argument.
|
||||
Switches parsed by `OptionParser` may take zero or one arguments.
|
||||
|
||||
The following switches take no argument:
|
||||
The following switches types take no arguments:
|
||||
|
||||
* `:boolean` - sets the value to true when given
|
||||
* `:count` - counts the number of times the switch is given
|
||||
* `:boolean` - sets the value to `true` when given (see also the
|
||||
"Negation switches" section below)
|
||||
* `:count` - counts the number of times the switch is given
|
||||
|
||||
The following switches take 1 argument:
|
||||
The following switches take one argument:
|
||||
|
||||
* `:integer` - parses the upcoming value as an integer.
|
||||
* `:float` - parses the upcoming value as a float.
|
||||
* `:string` - parses the upcoming value as a string.
|
||||
* `:integer` - parses the value as an integer
|
||||
* `:float` - parses the value as a float
|
||||
* `:string` - parses the value as a string
|
||||
|
||||
If a switch can't be parsed, it is returned in the invalid
|
||||
options list.
|
||||
If a switch can't be parsed according to the given type, it is returned
|
||||
in the invalid options list.
|
||||
|
||||
### Modifiers
|
||||
|
||||
Switches can be specified with modifiers, which change how
|
||||
they behave. The following modifiers are supported:
|
||||
|
||||
* `:keep` - keeps duplicated items instead of overriding them.
|
||||
Works with all types except `:count`.
|
||||
* `:keep` - keeps duplicated items instead of overriding them; works with
|
||||
all types except `:count`. Specifying `switch_name: :keep` assumes the
|
||||
type of `:switch_name` will be `:string`.
|
||||
|
||||
Note: if you want to use `:keep` with a non-string type, use a list, e.g.
|
||||
`[foo: [:integer, :keep]]`.
|
||||
Note that if you want to use `:keep` with a type other than `:string`, use a list
|
||||
as the type for the switch. For example: `[foo: [:integer, :keep]]`.
|
||||
|
||||
### Examples
|
||||
### Negation switches
|
||||
|
||||
Here are some examples of option parser working with different types
|
||||
and modifiers:
|
||||
In case a switch `SWITCH` is specified to have type `:boolean`, it may be
|
||||
passed as `--no-SWITCH` as well which will set the option to `false`:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{[op: false], ["path/to/file"], []}
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be specified in the `:aliases` option:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{[debug: true], [], []}
|
||||
|
||||
## Examples
|
||||
|
||||
Here are some examples of working with different types and modifiers:
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], strict: [unlock: :boolean])
|
||||
{[unlock: true], ["path/to/file"], []}
|
||||
@@ -121,21 +152,6 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file", "--unlock", "path/to/another/file"], strict: [unlock: :keep])
|
||||
{[unlock: "path/to/file", unlock: "path/to/another/file"], [], []}
|
||||
|
||||
### Negation switches
|
||||
|
||||
In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
|
||||
which will set the option to `false`:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{[op: false], ["path/to/file"], []}
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as options too:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{[debug: true], [], []}
|
||||
|
||||
"""
|
||||
@spec parse(argv, options) :: {parsed, argv, errors}
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
@@ -146,14 +162,16 @@ defmodule OptionParser do
|
||||
The same as `parse/2` but raises an `OptionParser.ParseError`
|
||||
exception if any invalid options are given.
|
||||
|
||||
If there weren't any errors, returns a three-element tuple as follows:
|
||||
If there are no errors, returns a `{parsed, rest}` tuple where:
|
||||
|
||||
1. parsed options,
|
||||
2. remaining arguments,
|
||||
3. empty list.
|
||||
* `parsed` is the list of parsed switches (same as in `parse/2`)
|
||||
* `rest` is the list of arguments (same as in `parse/2`)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.parse!(["--debug", "path/to/file"], strict: [debug: :boolean])
|
||||
{[debug: true], ["path/to/file"]}
|
||||
|
||||
iex> OptionParser.parse!(["--limit", "xyz"], strict: [limit: :integer])
|
||||
** (OptionParser.ParseError) 1 error found!
|
||||
--limit : Expected type integer, got "xyz"
|
||||
@@ -172,7 +190,7 @@ defmodule OptionParser do
|
||||
@spec parse!(argv, options) :: {parsed, argv} | no_return
|
||||
def parse!(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
case parse(argv, opts) do
|
||||
{parsed, argv, []} -> {parsed, argv}
|
||||
{parsed, args, []} -> {parsed, args}
|
||||
{_, _, errors} -> raise ParseError, format_errors(errors, opts)
|
||||
end
|
||||
end
|
||||
@@ -201,14 +219,16 @@ defmodule OptionParser do
|
||||
The same as `parse_head/2` but raises an `OptionParser.ParseError`
|
||||
exception if any invalid options are given.
|
||||
|
||||
If there weren't any errors, returns a three-element tuple as follows:
|
||||
If there are no errors, returns a `{parsed, rest}` tuple where:
|
||||
|
||||
1. parsed options,
|
||||
2. remaining arguments,
|
||||
3. empty list.
|
||||
* `parsed` is the list of parsed switches (same as in `parse_head/2`)
|
||||
* `rest` is the list of arguments (same as in `parse_head/2`)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.parse_head!(["--source", "lib", "path/to/file", "--verbose"])
|
||||
{[source: "lib"], ["path/to/file", "--verbose"]}
|
||||
|
||||
iex> OptionParser.parse_head!(["--number", "lib", "test/enum_test.exs", "--verbose"], strict: [number: :integer])
|
||||
** (OptionParser.ParseError) 1 error found!
|
||||
--number : Expected type integer, got "lib"
|
||||
@@ -219,10 +239,10 @@ defmodule OptionParser do
|
||||
--verbose : Missing argument of type integer
|
||||
--source : Expected type integer, got "lib"
|
||||
"""
|
||||
@spec parse_head!(argv, options) :: {parsed, argv, errors} | no_return
|
||||
@spec parse_head!(argv, options) :: {parsed, argv} | no_return
|
||||
def parse_head!(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
case parse_head(argv, opts) do
|
||||
{parsed, argv, []} -> {parsed, argv}
|
||||
{parsed, args, []} -> {parsed, args}
|
||||
{_, _, errors} -> raise ParseError, format_errors(errors, opts)
|
||||
end
|
||||
end
|
||||
@@ -250,7 +270,7 @@ defmodule OptionParser do
|
||||
{:error, ["--" | rest]} ->
|
||||
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
||||
|
||||
{:error, [arg | rest]=remaining_args} ->
|
||||
{:error, [arg | rest] = remaining_args} ->
|
||||
# there is no option
|
||||
if all? do
|
||||
do_parse(rest, config, opts, [arg | args], invalid, all?)
|
||||
@@ -264,20 +284,20 @@ defmodule OptionParser do
|
||||
Low-level function that parses one option.
|
||||
|
||||
It accepts the same options as `parse/2` and `parse_head/2`
|
||||
as both functions are built on top of next. This function
|
||||
as both functions are built on top of this function. This function
|
||||
may return:
|
||||
|
||||
* `{:ok, key, value, rest}` - the option `key` with `value` was
|
||||
successfully parsed
|
||||
|
||||
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
|
||||
(returned when the switch type does not match the one given via the
|
||||
command line)
|
||||
(returned when the value cannot be parsed according to the switch type)
|
||||
|
||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||
(returned in strict mode when the switch is unknown)
|
||||
|
||||
* `{:error, rest}` - there are no switches at the top of the given argv
|
||||
* `{:error, rest}` - there are no switches at the head of the given `argv`
|
||||
|
||||
"""
|
||||
|
||||
@spec next(argv, options) ::
|
||||
@@ -295,15 +315,15 @@ defmodule OptionParser do
|
||||
{:error, []}
|
||||
end
|
||||
|
||||
defp next(["--" | _]=argv, _aliases, _switches, _strict) do
|
||||
defp next(["--" | _] = argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["-" | _]=argv, _aliases, _switches, _strict) do
|
||||
defp next(["-" | _] = argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["- " <> _ | _]=argv, _aliases, _switches, _strict) do
|
||||
defp next(["- " <> _ | _] = argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
@@ -334,8 +354,9 @@ defmodule OptionParser do
|
||||
@doc """
|
||||
Receives a key-value enumerable and converts it to argv.
|
||||
|
||||
Keys must be atoms. Keys with nil value are discarded,
|
||||
Keys must be atoms. Keys with `nil` value are discarded,
|
||||
boolean values are converted to `--key` or `--no-key`
|
||||
(if the value is `true` or `false`, respectively),
|
||||
and all other values are converted using `to_string/1`.
|
||||
|
||||
## Examples
|
||||
@@ -364,6 +385,9 @@ defmodule OptionParser do
|
||||
@doc ~S"""
|
||||
Splits a string into argv chunks.
|
||||
|
||||
This function splits the given `string` into a list of strings in a similar
|
||||
way to many shells.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.split("foo bar")
|
||||
@@ -371,6 +395,7 @@ defmodule OptionParser do
|
||||
|
||||
iex> OptionParser.split("foo \"bar baz\"")
|
||||
["foo", "bar baz"]
|
||||
|
||||
"""
|
||||
@spec split(String.t) :: argv
|
||||
def split(string) do
|
||||
|
||||
+21
-20
@@ -1,23 +1,24 @@
|
||||
{erl_opts, [
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
warn_shadow_vars,
|
||||
warn_unused_import,
|
||||
warn_unused_function,
|
||||
warn_bif_clash,
|
||||
warn_unused_record,
|
||||
warn_deprecated_function,
|
||||
warn_obsolete_guard,
|
||||
strict_validation,
|
||||
warn_exported_vars,
|
||||
%% warn_export_vars,
|
||||
%% warn_missing_spec,
|
||||
%% warn_untyped_record,
|
||||
%% warnings_as_errors,
|
||||
debug_info
|
||||
]}.
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
warn_shadow_vars,
|
||||
warn_unused_import,
|
||||
warn_unused_function,
|
||||
warn_bif_clash,
|
||||
warn_unused_record,
|
||||
warn_deprecated_function,
|
||||
warn_obsolete_guard,
|
||||
strict_validation,
|
||||
warn_exported_vars,
|
||||
%% warn_export_vars,
|
||||
%% warn_missing_spec,
|
||||
%% warn_untyped_record,
|
||||
%% warnings_as_errors,
|
||||
debug_info,
|
||||
{platform_define, "^18.*", old_map_specs}
|
||||
]}.
|
||||
|
||||
{yrl_opts, [
|
||||
{report, true},
|
||||
{verbose, false}
|
||||
]}.
|
||||
{report, true},
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
@@ -14,11 +14,16 @@
|
||||
-export_type([charlist/0, char_list/0, struct/0, as_boolean/1, keyword/0, keyword/1]).
|
||||
-type charlist() :: string().
|
||||
-type char_list() :: string().
|
||||
-type struct() :: #{'__struct__' => atom()}.
|
||||
-type as_boolean(T) :: T.
|
||||
-type keyword() :: [{atom(), any()}].
|
||||
-type keyword(T) :: [{atom(), T}].
|
||||
|
||||
-ifdef(old_map_specs).
|
||||
-type struct() :: #{'__struct__' => atom(), atom() => any()}.
|
||||
-else.
|
||||
-type struct() :: #{'__struct__' := atom(), atom() => any()}.
|
||||
-endif.
|
||||
|
||||
%% OTP Application API
|
||||
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
@@ -60,7 +60,8 @@ import_function(Meta, Name, Arity, E) ->
|
||||
end.
|
||||
|
||||
require_function(Meta, Receiver, Name, Arity, E) ->
|
||||
case is_element({Name, Arity}, get_optional_macros(Receiver)) of
|
||||
Required = is_element(Receiver, ?m(E, requires)),
|
||||
case is_element({Name, Arity}, get_macros(Receiver, Required)) of
|
||||
true -> false;
|
||||
false ->
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, function), ?m(E, lexical_tracker)),
|
||||
@@ -146,7 +147,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
{import, Receiver} ->
|
||||
case expand_require([{require, false} | Meta], Receiver, Tuple, Args, E) of
|
||||
case expand_require([{required, true} | Meta], Receiver, Tuple, Args, E) of
|
||||
{ok, _, _} = Response -> Response;
|
||||
error -> {ok, Receiver, Name, Args}
|
||||
end;
|
||||
@@ -161,19 +162,15 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
|
||||
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
Module = ?m(E, module),
|
||||
Required = (Receiver == ?m(E, module)) orelse is_element(Receiver, ?m(E, requires)) orelse required(Meta),
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
case is_element(Tuple, get_macros(Receiver, Required)) of
|
||||
true when Required ->
|
||||
elixir_lexical:record_remote(Receiver, Name, Arity, nil, ?line(Meta), ?m(E, lexical_tracker)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
true ->
|
||||
Requires = ?m(E, requires),
|
||||
case (Receiver == Module) orelse is_element(Receiver, Requires) orelse skip_require(Meta) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, Name, Arity, nil, ?line(Meta), ?m(E, lexical_tracker)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
false ->
|
||||
Info = {unrequired_module, {Receiver, Name, length(Args), Requires}},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Info)
|
||||
end;
|
||||
Info = {unrequired_module, {Receiver, Name, length(Args), ?m(E, requires)}},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Info);
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
@@ -220,8 +217,8 @@ caller(Line, E) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
skip_require(Meta) ->
|
||||
lists:keyfind(require, 1, Meta) == {require, false}.
|
||||
required(Meta) ->
|
||||
lists:keyfind(required, 1, Meta) == {required, true}.
|
||||
|
||||
find_dispatch(Meta, Tuple, Extra, E) ->
|
||||
case is_import(Meta) of
|
||||
@@ -289,9 +286,20 @@ format_error({ambiguous_call, {Mod1, Mod2, Name, Arity}}) ->
|
||||
%% INTROSPECTION
|
||||
|
||||
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
|
||||
get_optional_macros(erlang) -> [];
|
||||
get_macros(erlang, _) -> [];
|
||||
|
||||
get_optional_macros(Receiver) ->
|
||||
get_macros(Receiver, false) ->
|
||||
case code:is_loaded(Receiver) of
|
||||
{file, _} ->
|
||||
try
|
||||
Receiver:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end;
|
||||
false -> []
|
||||
end;
|
||||
|
||||
get_macros(Receiver, true) ->
|
||||
case code:ensure_loaded(Receiver) of
|
||||
{module, Receiver} ->
|
||||
try
|
||||
|
||||
@@ -525,7 +525,7 @@ add_beam_chunk(Bin, Id, ChunkData)
|
||||
%% the callback can't be expanded, invokes the given
|
||||
%% fun passing a possibly expanded AM:AF(Args).
|
||||
expand_callback(Line, M, F, Args, E, Fun) ->
|
||||
Meta = [{line, Line}, {require, false}],
|
||||
Meta = [{line, Line}, {required, true}],
|
||||
|
||||
{EE, ET} = elixir_dispatch:dispatch_require(Meta, M, F, Args, E, fun(AM, AF, AA) ->
|
||||
Fun(AM, AF, AA),
|
||||
|
||||
@@ -135,15 +135,15 @@ tokenize([], EndLine, _Column, #elixir_tokenizer{terminators=[{Start, {StartLine
|
||||
% Base integers
|
||||
|
||||
tokenize([$0, $x, H | T], Line, Column, Scope, Tokens) when ?is_hex(H) ->
|
||||
{Rest, Number, Length} = tokenize_hex([H | T], []),
|
||||
{Rest, Number, Length} = tokenize_hex(T, [H], 1),
|
||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||
|
||||
tokenize([$0, $b, H | T], Line, Column, Scope, Tokens) when ?is_bin(H) ->
|
||||
{Rest, Number, Length} = tokenize_bin([H | T], []),
|
||||
{Rest, Number, Length} = tokenize_bin(T, [H], 1),
|
||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||
|
||||
tokenize([$0, $o, H | T], Line, Column, Scope, Tokens) when ?is_octal(H) ->
|
||||
{Rest, Number, Length} = tokenize_octal([H | T], []),
|
||||
{Rest, Number, Length} = tokenize_octal(T, [H], 1),
|
||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||
|
||||
% Comments
|
||||
@@ -413,8 +413,8 @@ tokenize([$. | T], Line, Column, Scope, Tokens) ->
|
||||
|
||||
% Integers and floats
|
||||
|
||||
tokenize([H | _] = String, Line, Column, Scope, Tokens) when ?is_digit(H) ->
|
||||
{Rest, Number, Length} = tokenize_number(String, [], false),
|
||||
tokenize([H | T], Line, Column, Scope, Tokens) when ?is_digit(H) ->
|
||||
{Rest, Number, Length} = tokenize_number(T, [H], 1, false),
|
||||
tokenize(Rest, Line, Column + Length, Scope, [{number, {Line, Column, Column + Length}, Number} | Tokens]);
|
||||
|
||||
% Identifiers (including aliases)
|
||||
@@ -733,46 +733,55 @@ extract_heredoc_line(Marker, Rest, Buffer, _Counter) ->
|
||||
%% At this point, we are at least sure the first digit is a number.
|
||||
|
||||
%% Check if we have a point followed by a number;
|
||||
tokenize_number([$., H | T], Acc, false) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H, $. | Acc], true);
|
||||
tokenize_number([$., H | T], Acc, Length, false) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H, $. | Acc], Length + 2, true);
|
||||
|
||||
%% Check if we have an underscore followed by a number;
|
||||
tokenize_number([$_, H | T], Acc, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Bool);
|
||||
tokenize_number([$_, H | T], Acc, Length, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Length + 2, Bool);
|
||||
|
||||
%% Check if we have e- followed by numbers (valid only for floats);
|
||||
tokenize_number([E, S, H | T], Acc, true)
|
||||
tokenize_number([E, S, H | T], Acc, Length, true)
|
||||
when (E == $E) or (E == $e), ?is_digit(H), S == $+ orelse S == $- ->
|
||||
tokenize_number(T, [H, S, $e | Acc], true);
|
||||
tokenize_number(T, [H, S, $e | Acc], Length + 3, true);
|
||||
|
||||
%% Check if we have e followed by numbers (valid only for floats);
|
||||
tokenize_number([E, H | T], Acc, true)
|
||||
tokenize_number([E, H | T], Acc, Length, true)
|
||||
when (E == $E) or (E == $e), ?is_digit(H) ->
|
||||
tokenize_number(T, [H, $e | Acc], true);
|
||||
tokenize_number(T, [H, $e | Acc], Length + 2, true);
|
||||
|
||||
%% Finally just numbers.
|
||||
tokenize_number([H | T], Acc, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Bool);
|
||||
tokenize_number([H | T], Acc, Length, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Length + 1, Bool);
|
||||
|
||||
%% Cast to float...
|
||||
tokenize_number(Rest, Acc, true) ->
|
||||
{Rest, list_to_float(lists:reverse(Acc)), length(Acc)};
|
||||
tokenize_number(Rest, Acc, Length, true) ->
|
||||
{Rest, list_to_float(lists:reverse(Acc)), Length};
|
||||
|
||||
%% Or integer.
|
||||
tokenize_number(Rest, Acc, false) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc)), length(Acc)}.
|
||||
tokenize_number(Rest, Acc, Length, false) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc)), Length}.
|
||||
|
||||
tokenize_hex([H | T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H | Acc]);
|
||||
tokenize_hex([$_, H | T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H | Acc]);
|
||||
tokenize_hex(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 16), length(Acc)}.
|
||||
tokenize_hex([H | T], Acc, Length) when ?is_hex(H) ->
|
||||
tokenize_hex(T, [H | Acc], Length + 1);
|
||||
tokenize_hex([$_, H | T], Acc, Length) when ?is_hex(H) ->
|
||||
tokenize_hex(T, [H | Acc], Length + 2);
|
||||
tokenize_hex(Rest, Acc, Length) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc), 16), Length}.
|
||||
|
||||
tokenize_octal([H | T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H | Acc]);
|
||||
tokenize_octal([$_, H | T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H | Acc]);
|
||||
tokenize_octal(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 8), length(Acc)}.
|
||||
tokenize_octal([H | T], Acc, Length) when ?is_octal(H) ->
|
||||
tokenize_octal(T, [H | Acc], Length + 1);
|
||||
tokenize_octal([$_, H | T], Acc, Length) when ?is_octal(H) ->
|
||||
tokenize_octal(T, [H | Acc], Length + 2);
|
||||
tokenize_octal(Rest, Acc, Length) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc), 8), Length}.
|
||||
|
||||
tokenize_bin([H | T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H | Acc]);
|
||||
tokenize_bin([$_, H | T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H | Acc]);
|
||||
tokenize_bin(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 2), length(Acc)}.
|
||||
tokenize_bin([H | T], Acc, Length) when ?is_bin(H) ->
|
||||
tokenize_bin(T, [H | Acc], Length + 1);
|
||||
tokenize_bin([$_, H | T], Acc, Length) when ?is_bin(H) ->
|
||||
tokenize_bin(T, [H | Acc], Length + 2);
|
||||
tokenize_bin(Rest, Acc, Length) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc), 2), Length}.
|
||||
|
||||
%% Comments
|
||||
|
||||
|
||||
@@ -107,9 +107,14 @@ build_else(Meta, ElseClauses) ->
|
||||
each_clause_to_error_match(ElseClauses)] ++ [build_raise(Meta)].
|
||||
|
||||
each_clause_to_error_match(Clauses) ->
|
||||
[{match, Meta, [{error, Match}], Expr} ||
|
||||
[{match, Meta, [error_match_for_match(Match)], Expr} ||
|
||||
{'->', Meta, [[Match], Expr]} <- Clauses].
|
||||
|
||||
error_match_for_match({'when', Meta, [Left, Right]}) ->
|
||||
{'when', Meta, [{error, Left}, Right]};
|
||||
error_match_for_match(Match) ->
|
||||
{error, Match}.
|
||||
|
||||
build_raise(Meta) ->
|
||||
Other = {other, Meta, ?MODULE},
|
||||
{match, ?generated, [{error, Other}], {{'.', Meta, [erlang, error]}, Meta, [{with_clause, Other}]}}.
|
||||
|
||||
@@ -195,6 +195,8 @@ defmodule EnumTest do
|
||||
test "find_index/2" do
|
||||
assert Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
|
||||
assert Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end) == 1
|
||||
assert Stream.take(1..3, 3) |> Enum.find_index(fn _ -> false end) == nil
|
||||
assert Stream.take(1..6, 6) |> Enum.find_index(fn x -> x == 5 end) == 4
|
||||
end
|
||||
|
||||
test "find_value/2" do
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
defmodule Dialyzer.Macrocallback do
|
||||
@macrocallback required(atom) :: Macro.t
|
||||
@macrocallback optional(atom) :: Macro.t
|
||||
@optional_callbacks [optional: 1]
|
||||
end
|
||||
|
||||
defmodule Dialyzer.Macrocallback.Impl do
|
||||
@behaviour Dialyzer.Macrocallback
|
||||
defmacro required(var), do: Macro.expand(var, __CALLER__)
|
||||
defmacro optional(var), do: Macro.expand(var, __CALLER__)
|
||||
end
|
||||
@@ -16,8 +16,14 @@ defmodule Kernel.DialyzerTest do
|
||||
|> Path.join("base_plt")
|
||||
|> String.to_charlist()
|
||||
|
||||
# Add a few key elixir modules for types
|
||||
files = Enum.map([Kernel, String, Keyword, Exception], &:code.which/1)
|
||||
# Some OSs (like Windows) do not provide the HOME environment variable.
|
||||
unless System.get_env("HOME") do
|
||||
System.put_env("HOME", System.user_home())
|
||||
end
|
||||
|
||||
# Add a few key elixir modules for types and macro functions
|
||||
mods = [Kernel, String, Keyword, Exception, Macro, Macro.Env, :elixir_env]
|
||||
files = Enum.map(mods, &:code.which/1)
|
||||
:dialyzer.run([analysis_type: :plt_build, output_plt: plt,
|
||||
apps: [:erts], files: files])
|
||||
|
||||
@@ -59,12 +65,17 @@ defmodule Kernel.DialyzerTest do
|
||||
assert_dialyze_no_warnings! context
|
||||
end
|
||||
|
||||
@tag otp19: false
|
||||
test "no warnings on raise", context do
|
||||
copy_beam! context, Dialyzer.Raise
|
||||
assert_dialyze_no_warnings! context
|
||||
end
|
||||
|
||||
test "no warnings on macrocallback", context do
|
||||
copy_beam! context, Dialyzer.Macrocallback
|
||||
copy_beam! context, Dialyzer.Macrocallback.Impl
|
||||
assert_dialyze_no_warnings! context
|
||||
end
|
||||
|
||||
defp copy_beam!(context, module) do
|
||||
name = "#{module}.beam"
|
||||
File.cp! Path.join(context[:base_dir], name),
|
||||
|
||||
@@ -66,6 +66,7 @@ defmodule Kernel.WithTest do
|
||||
|
||||
test "else conditions" do
|
||||
assert with({:ok, res} <- 41, do: res, else: ({:error, error} -> error; res -> res + 1)) == 42
|
||||
assert with({:ok, res} <- 41, do: res, else: (res when res == 41 -> res + 1; res -> res)) == 42
|
||||
assert with({:ok, res} <- 41, do: res, else: (_ -> :error)) == :error
|
||||
end
|
||||
|
||||
|
||||
@@ -25,15 +25,16 @@ op_kw_test() ->
|
||||
[{atom, {1, 1, 5}, foo}, {dual_op, {1, 5, 6}, '+'}, {atom, {1, 6, 10}, bar}] = tokenize(":foo+:bar").
|
||||
|
||||
scientific_test() ->
|
||||
[{number, {1, 1, 7}, 0.1}] = tokenize("1.0e-1").
|
||||
[{number, {1, 1, 7}, 0.1}] = tokenize("1.0e-1"),
|
||||
[{number, {1, 1, 16}, 1.2345678e-7}] = tokenize("1_234.567_8e-10").
|
||||
|
||||
hex_bin_octal_test() ->
|
||||
[{number, {1, 1, 5}, 255}] = tokenize("0xFF"),
|
||||
[{number, {1, 1, 5}, 255}] = tokenize("0xF_F"),
|
||||
[{number, {1, 1, 6}, 255}] = tokenize("0xF_F"),
|
||||
[{number, {1, 1, 5}, 63}] = tokenize("0o77"),
|
||||
[{number, {1, 1, 5}, 63}] = tokenize("0o7_7"),
|
||||
[{number, {1, 1, 6}, 63}] = tokenize("0o7_7"),
|
||||
[{number, {1, 1, 5}, 3}] = tokenize("0b11"),
|
||||
[{number, {1, 1, 5}, 3}] = tokenize("0b1_1").
|
||||
[{number, {1, 1, 6}, 3}] = tokenize("0b1_1").
|
||||
|
||||
unquoted_atom_test() ->
|
||||
[{atom, {1, 1, 3}, '+'}] = tokenize(":+"),
|
||||
|
||||
@@ -367,18 +367,29 @@ defmodule ExUnit.Diff do
|
||||
map_pair = format_key_value(key, val, keyword?)
|
||||
[[ins: ", ", ins: map_pair] | acc]
|
||||
end)
|
||||
result =
|
||||
if same == [] and altered == [] and missing != [] and surplus != [] do
|
||||
[[_ | elem_diff] | rest] = result
|
||||
[elem_diff | rest]
|
||||
else
|
||||
result
|
||||
end
|
||||
|
||||
result = Enum.reduce(surplus, result, fn({key, val}, acc) ->
|
||||
map_pair = format_key_value(key, val, keyword?)
|
||||
[[del: ", ", del: map_pair] | acc]
|
||||
end)
|
||||
|
||||
result = Enum.reduce(altered, result, fn({key, {val1, val2}}, acc) ->
|
||||
value_diff = script_inner(val1, val2)
|
||||
[[{:eq, ", "}, {:eq, format_key(key, keyword?)}, value_diff] | acc]
|
||||
end)
|
||||
|
||||
result = Enum.reduce(same, result, fn({key, val}, acc) ->
|
||||
map_pair = format_key_value(key, val, keyword?)
|
||||
[[eq: ", ", eq: map_pair] | acc]
|
||||
end)
|
||||
|
||||
[[_ | elem_diff] | rest] = result
|
||||
[{:eq, "%" <> name <> "{"}, [elem_diff | rest], {:eq, "}"}]
|
||||
end
|
||||
|
||||
@@ -242,6 +242,8 @@ defmodule ExUnit.DiffTest do
|
||||
assert script(map2, map1) == expected
|
||||
assert script(map1, %{}) == [{:eq, "%{"}, [[del: "baz: 12"]], {:eq, "}"}]
|
||||
assert script(%{}, map1) == [{:eq, "%{"}, [[ins: "baz: 12"]], {:eq, "}"}]
|
||||
expected = [{:eq, "%{"}, [[del: "baz: 12"], [ins: "foo: 12"]], {:eq, "}"}]
|
||||
assert script(map1, %{foo: 12}) == expected
|
||||
|
||||
assert script(%{}, %{}) == [eq: "%{}"]
|
||||
end
|
||||
|
||||
@@ -64,7 +64,7 @@ defmodule Mix.Dep do
|
||||
@doc """
|
||||
Returns loaded dependencies from the cache for the current environment.
|
||||
|
||||
Because the dependencies are cached during deps.check, their
|
||||
Because the dependencies are cached during deps.loadpaths, their
|
||||
status may be outdated (for example, `:compile` did not
|
||||
yet become `:ok`). Therefore it is recommended to not rely
|
||||
on their status, also given they haven't been checked
|
||||
@@ -225,16 +225,16 @@ defmodule Mix.Dep do
|
||||
def format_status(%Mix.Dep{app: app, status: {:divergedonly, other}} = dep) do
|
||||
recommendation =
|
||||
if Keyword.has_key?(other.opts, :only) do
|
||||
"Ensure the parent dependency specifies a superset of the child one in"
|
||||
"Ensure you specify at least the same environments in :only in your dep"
|
||||
else
|
||||
"Remove the :only restriction from"
|
||||
"Remove the :only restriction from your dep"
|
||||
end
|
||||
|
||||
"the dependency #{app}\n" <>
|
||||
"the :only option for dependency #{app}\n" <>
|
||||
"#{dep_status(dep)}" <>
|
||||
"\n does not match the environments calculated for\n" <>
|
||||
"\n does not match the :only option calculated for\n" <>
|
||||
"#{dep_status(other)}" <>
|
||||
"\n #{recommendation} your dep"
|
||||
"\n #{recommendation}"
|
||||
end
|
||||
|
||||
def format_status(%Mix.Dep{app: app, status: {:diverged, other}} = dep) do
|
||||
|
||||
@@ -84,7 +84,7 @@ defmodule Mix.Dep.Converger do
|
||||
if not diverged? && remote do
|
||||
# If there is a lock, it means we are doing a get/update
|
||||
# and we need to hit the remote converger which do external
|
||||
# requests and what not. In case of deps.check, deps and so
|
||||
# requests and what not. In case of deps.loadpaths, deps and so
|
||||
# on, there is no lock, so we won't hit this branch.
|
||||
lock = if lock_given?, do: remote.converge(deps, lock), else: lock
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ defmodule Mix.Task do
|
||||
# 2. Otherwise we look for it in dependencies.
|
||||
# 3. Finally, we compile the current project in hope it is available.
|
||||
module =
|
||||
get_task_or_run(proj, task, fn -> Mix.Task.run("deps.check") end) ||
|
||||
get_task_or_run(proj, task, fn -> Mix.Task.run("deps.loadpaths") end) ||
|
||||
get_task_or_run(proj, task, fn -> Mix.Project.compile([]) end) ||
|
||||
get!(task)
|
||||
|
||||
|
||||
@@ -49,6 +49,6 @@ defmodule Mix.Tasks.Clean do
|
||||
defp loadpaths! do
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
Mix.Task.reenable "loadpaths"
|
||||
Mix.Task.reenable "deps.check"
|
||||
Mix.Task.reenable "deps.loadpaths"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -95,7 +95,7 @@ defmodule Mix.Tasks.Compile do
|
||||
defp loadpaths! do
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
Mix.Task.reenable "loadpaths"
|
||||
Mix.Task.reenable "deps.check"
|
||||
Mix.Task.reenable "deps.loadpaths"
|
||||
end
|
||||
|
||||
defp consolidate_protocols? do
|
||||
|
||||
@@ -44,10 +44,7 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
output = Mix.Project.consolidation_path(config)
|
||||
manifest = Path.join(output, @manifest)
|
||||
|
||||
protocols_and_impls =
|
||||
unless Mix.Project.umbrella?(config) do
|
||||
protocols_and_impls(config)
|
||||
end
|
||||
protocols_and_impls = protocols_and_impls(config)
|
||||
|
||||
cond do
|
||||
opts[:force] || Mix.Utils.stale?(Mix.Project.config_files(), [manifest]) ->
|
||||
@@ -79,10 +76,15 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
not scm.fetchable?,
|
||||
do: opts[:build])
|
||||
|
||||
app = Mix.Project.app_path(config)
|
||||
app =
|
||||
if Mix.Project.umbrella?(config) do
|
||||
[]
|
||||
else
|
||||
[Mix.Project.app_path(config)]
|
||||
end
|
||||
|
||||
protocols_and_impls =
|
||||
for path <- [app | deps] do
|
||||
for path <- app ++ deps do
|
||||
manifest_path = Path.join(path, ".compile.elixir")
|
||||
compile_path = Path.join(path, "ebin")
|
||||
Mix.Compilers.Elixir.protocols_and_impls(manifest_path, compile_path)
|
||||
@@ -120,10 +122,24 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
defp consolidate(protocol, paths, output, opts) do
|
||||
impls = Protocol.extract_impls(protocol, paths)
|
||||
reload(protocol)
|
||||
{:ok, binary} = Protocol.consolidate(protocol, impls)
|
||||
File.write!(Path.join(output, "#{protocol}.beam"), binary)
|
||||
if opts[:verbose] do
|
||||
Mix.shell.info "Consolidated #{inspect protocol}"
|
||||
case Protocol.consolidate(protocol, impls) do
|
||||
{:ok, binary} ->
|
||||
File.write!(Path.join(output, "#{protocol}.beam"), binary)
|
||||
if opts[:verbose] do
|
||||
Mix.shell.info "Consolidated #{inspect protocol}"
|
||||
end
|
||||
|
||||
# If we remove a dependency and we have implemented one of its
|
||||
# protocols locally, we will mark the protocol as needing to be
|
||||
# reconsolidated when the implementation is removed even though
|
||||
# the protocol no longer exists. Although most times removing a
|
||||
# dependency will trigger a full recompilation, such won't happen
|
||||
# in umbrella apps with shared build.
|
||||
{:error, :no_beam_info} ->
|
||||
remove_consolidated(protocol, output)
|
||||
if opts[:verbose] do
|
||||
Mix.shell.info "Unavailable #{inspect protocol}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -133,7 +133,8 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
end
|
||||
|
||||
try do
|
||||
res = Mix.Task.run("compile", ["--no-deps", "--no-archives-check", "--no-elixir-version-check"])
|
||||
res = Mix.Task.run("compile", ["--no-deps", "--no-archives-check",
|
||||
"--no-elixir-version-check", "--no-warnings-as-errors"])
|
||||
:ok in List.wrap(res)
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -191,7 +192,7 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
else
|
||||
"make"
|
||||
end
|
||||
do_command(dep, config, command, true)
|
||||
do_command(dep, config, command, true, [{"IS_DEP", "1"}])
|
||||
end
|
||||
|
||||
defp do_compile(%Mix.Dep{opts: opts} = dep, config) do
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
defmodule Mix.Tasks.Deps.Check do
|
||||
defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
use Mix.Task
|
||||
|
||||
import Mix.Dep, only: [loaded_by_name: 2, format_dep: 1, ok?: 1,
|
||||
format_status: 1, check_lock: 1]
|
||||
|
||||
@moduledoc """
|
||||
Checks if all dependencies are valid,
|
||||
loading them along the way.
|
||||
Checks and loads all dependencies along the way.
|
||||
|
||||
If there is an invalid dependency, its status is printed
|
||||
before aborting.
|
||||
|
||||
Although this task does not show up in `mix help`, it is
|
||||
part of Mix public API and can be depended on.
|
||||
|
||||
## Command line options
|
||||
|
||||
* `--no-deps-check` - do not check or compile deps, only load available ones
|
||||
@@ -16,7 +16,7 @@ defmodule Mix.Tasks.Deps.Unlock do
|
||||
|
||||
"""
|
||||
|
||||
@switches [all: :boolean, unused: :boolean]
|
||||
@switches [all: :boolean, unused: :boolean, filter: :string]
|
||||
|
||||
@spec run(OptionParser.argv) :: :ok
|
||||
def run(args) do
|
||||
@@ -29,6 +29,27 @@ defmodule Mix.Tasks.Deps.Unlock do
|
||||
opts[:unused] ->
|
||||
apps = Mix.Dep.loaded([]) |> Enum.map(& &1.app)
|
||||
Mix.Dep.Lock.read() |> Map.take(apps) |> Mix.Dep.Lock.write()
|
||||
filter = opts[:filter] ->
|
||||
lock = Mix.Dep.Lock.read
|
||||
apps = Map.keys(lock)
|
||||
|
||||
unlock =
|
||||
apps
|
||||
|> Enum.filter(&String.contains?("#{&1}", filter))
|
||||
|
||||
if unlock == [] do
|
||||
Mix.shell.error "warning: no dependencies were matched"
|
||||
else
|
||||
lock =
|
||||
Enum.reject(lock, fn({app, _}) ->
|
||||
app in unlock
|
||||
end)
|
||||
Mix.Dep.Lock.write(lock)
|
||||
Mix.shell.info """
|
||||
Unlocked deps:
|
||||
* #{Enum.join(unlock, "\n* ")}
|
||||
"""
|
||||
end
|
||||
|
||||
apps != [] ->
|
||||
lock =
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule Mix.Tasks.Escript do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "List installed escripts."
|
||||
@shortdoc "Lists installed escripts"
|
||||
|
||||
@moduledoc ~S"""
|
||||
Lists all installed escripts.
|
||||
|
||||
@@ -107,7 +107,7 @@ defmodule Mix.Tasks.Help do
|
||||
defp loadpaths! do
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
Mix.Task.reenable "loadpaths"
|
||||
Mix.Task.reenable "deps.check"
|
||||
Mix.Task.reenable "deps.loadpaths"
|
||||
end
|
||||
|
||||
defp load_tasks() do
|
||||
|
||||
@@ -31,9 +31,9 @@ defmodule Mix.Tasks.Loadpaths do
|
||||
end
|
||||
|
||||
# --no-deps is used only internally. It has no purpose
|
||||
# from Mix.CLI because the CLI itself already loads deps.
|
||||
unless "--no-deps" in args do
|
||||
Mix.Task.run "deps.check", args
|
||||
# from Mix.CLI because running a task may load deps.
|
||||
unless "--no-deps" in args or System.get_env("MIX_NO_DEPS") in ~w(1 true) do
|
||||
Mix.Task.run "deps.loadpaths", args
|
||||
end
|
||||
|
||||
if config[:app] do
|
||||
|
||||
@@ -67,6 +67,9 @@ defmodule Mix.Tasks.Test do
|
||||
* `--no-elixir-version-check` - do not check the Elixir version from mix.exs
|
||||
* `--stale` - run only tests which reference modules that changed since the
|
||||
last `test --stale`. You can read more about this option in the "Stale" section below.
|
||||
* `--listen-on-stdin` - run tests, and then listen on stdin. Receiving a newline will
|
||||
result in the tests being run again. Very useful when combined with `--stale` and
|
||||
external commands which produce output on stdout upon file system modification.
|
||||
|
||||
## Filters
|
||||
|
||||
@@ -164,7 +167,7 @@ defmodule Mix.Tasks.Test do
|
||||
exclude: :keep, seed: :integer, only: :keep, compile: :boolean,
|
||||
start: :boolean, timeout: :integer, raise: :boolean,
|
||||
deps_check: :boolean, archives_check: :boolean, elixir_version_check: :boolean,
|
||||
stale: :boolean]
|
||||
stale: :boolean, listen_on_stdin: :boolean]
|
||||
|
||||
@cover [output: "cover", tool: Cover]
|
||||
|
||||
@@ -172,6 +175,15 @@ defmodule Mix.Tasks.Test do
|
||||
def run(args) do
|
||||
{opts, files} = OptionParser.parse!(args, strict: @switches)
|
||||
|
||||
if opts[:listen_on_stdin] do
|
||||
System.at_exit fn _ ->
|
||||
IO.gets(:stdio, "")
|
||||
Mix.shell.info "Restarting..."
|
||||
:init.restart()
|
||||
:timer.sleep(:infinity)
|
||||
end
|
||||
end
|
||||
|
||||
unless System.get_env("MIX_ENV") || Mix.env == :test do
|
||||
Mix.raise "\"mix test\" is running on environment \"#{Mix.env}\". If you are " <>
|
||||
"running tests along another task, please set MIX_ENV explicitly"
|
||||
|
||||
@@ -346,12 +346,12 @@ defmodule Mix.DepTest do
|
||||
refute_received {:mix_shell, :info, ["* Getting" <> _]}
|
||||
|
||||
assert_raise Mix.Error, "Can't continue due to errors on dependencies", fn ->
|
||||
Mix.Tasks.Deps.Check.run([])
|
||||
Mix.Tasks.Deps.Loadpaths.run([])
|
||||
end
|
||||
|
||||
Mix.ProjectStack.clear_cache()
|
||||
Mix.env(:prod)
|
||||
Mix.Tasks.Deps.Check.run([])
|
||||
Mix.Tasks.Deps.Loadpaths.run([])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -496,7 +496,7 @@ defmodule Mix.DepTest do
|
||||
Mix.Tasks.Deps.run([])
|
||||
assert_received {:mix_shell, :info, ["* git_repo" <> _]}
|
||||
assert_received {:mix_shell, :info, [msg]}
|
||||
assert msg =~ "Ensure the parent dependency specifies a superset of the child one"
|
||||
assert msg =~ "Ensure you specify at least the same environments in :only in your dep"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -90,7 +90,7 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
File.rm_rf!("deps/git_repo/.git")
|
||||
|
||||
assert_raise Mix.Error, "Can't continue due to errors on dependencies", fn ->
|
||||
Mix.Tasks.Deps.Check.run ["git_repo"]
|
||||
Mix.Tasks.Deps.Loadpaths.run ["git_repo"]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -126,7 +126,7 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
Code.delete_path("_build/dev/lib/git_repo/ebin")
|
||||
|
||||
# Deps on Git repo loads it automatically on compile
|
||||
Mix.Task.reenable "deps.check"
|
||||
Mix.Task.reenable "deps.loadpaths"
|
||||
Mix.Tasks.Deps.Compile.run ["deps_on_git_repo"]
|
||||
assert File.exists?("_build/dev/lib/deps_on_git_repo/ebin")
|
||||
end
|
||||
@@ -243,7 +243,7 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
# Update the lock and now we should get an error
|
||||
Mix.Dep.Lock.write %{git_repo: {:git, fixture_path("git_repo"), last, []}}
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
# Flush the errors we got, move to a clean slate
|
||||
|
||||
@@ -121,13 +121,13 @@ defmodule Mix.Tasks.DepsTest do
|
||||
end
|
||||
end
|
||||
|
||||
## deps.check
|
||||
## deps.loadpaths
|
||||
|
||||
test "checks list of dependencies and their status with success" do
|
||||
Mix.Project.push SuccessfulDepsApp
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
end
|
||||
|
||||
@@ -136,7 +136,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, ["* ok (https://github.com/elixir-lang/ok.git)"]}
|
||||
@@ -160,7 +160,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
File.rm_rf("_build")
|
||||
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/ok/priv/sample")
|
||||
|
||||
@@ -174,7 +174,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Project.pop
|
||||
Mix.Project.push SuccessfulDepsApp
|
||||
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
refute to_charlist(Path.expand("_build/dev/lib/ok/ebin/")) in :code.get_path
|
||||
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
|
||||
@@ -185,7 +185,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Project.pop
|
||||
Mix.Project.push SuccessfulDepsApp
|
||||
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
refute File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
|
||||
end
|
||||
@@ -238,6 +238,21 @@ defmodule Mix.Tasks.DepsTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "unlocks filtered deps", context do
|
||||
Mix.Project.push DepsApp
|
||||
in_tmp context.test, fn ->
|
||||
Mix.Dep.Lock.write %{git_repo: "abcdef", another: "hash", another_one: "hash"}
|
||||
Mix.Tasks.Deps.Unlock.run ["--filter", "another"]
|
||||
assert Mix.Dep.Lock.read == %{git_repo: "abcdef"}
|
||||
output = """
|
||||
Unlocked deps:
|
||||
* another
|
||||
* another_one
|
||||
"""
|
||||
assert_received {:mix_shell, :info, [^output]}
|
||||
end
|
||||
end
|
||||
|
||||
## Deps environment
|
||||
|
||||
defmodule DepsEnvApp do
|
||||
@@ -364,7 +379,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
assert_received {:mix_shell, :error, [" the dependency git_repo in mix.exs is overriding a child dependency" <> _]}
|
||||
|
||||
@@ -385,7 +400,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, [" different specs were given for the git_repo app:" <> _ = msg]}
|
||||
@@ -416,7 +431,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, [" the dependency git_repo 0.1.0" <> _ = msg]}
|
||||
@@ -447,7 +462,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, [" the dependency git_repo in mix.exs is overriding" <> _]}
|
||||
@@ -503,7 +518,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
receive do
|
||||
@@ -523,7 +538,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
File.mkdir_p!("_build/dev/lib/ok/ebin")
|
||||
manifest_data = :erlang.term_to_binary({:v1, "the_future", :scm})
|
||||
@@ -535,8 +550,8 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Tasks.Deps.run []
|
||||
assert_received {:mix_shell, :info, [^msg]}
|
||||
|
||||
# deps.check will automatically recompile it
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
# deps.loadpaths will automatically recompile it
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
Mix.Tasks.Deps.run []
|
||||
refute_received {:mix_shell, :info, [^msg]}
|
||||
@@ -548,7 +563,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
File.mkdir_p!("_build/dev/lib/ok/ebin")
|
||||
manifest_data = :erlang.term_to_binary({:v1, System.version, :scm})
|
||||
@@ -560,8 +575,8 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Tasks.Deps.run []
|
||||
assert_received {:mix_shell, :info, [^msg]}
|
||||
|
||||
# deps.check will automatically recompile it
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
# deps.loadpaths will automatically recompile it
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
Mix.Tasks.Deps.run []
|
||||
refute_received {:mix_shell, :info, [^msg]}
|
||||
|
||||
@@ -104,6 +104,84 @@ defmodule Mix.Tasks.TestTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "--listen-on-stdin: runs tests after input" do
|
||||
in_fixture "test_stale", fn ->
|
||||
port = mix_port(~w[test --stale --listen-on-stdin])
|
||||
|
||||
assert receive_until_match(port, "seed", []) =~ "2 tests"
|
||||
|
||||
:erlang.port_command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "No stale tests.", []) =~ "Restarting..."
|
||||
end
|
||||
end
|
||||
|
||||
test "--listen-on-stdin: does not exit on compilation failure" do
|
||||
in_fixture "test_stale", fn ->
|
||||
File.write!("lib/b.ex", """
|
||||
defmodule B do
|
||||
def f, do: error_not_a_var
|
||||
end
|
||||
""")
|
||||
|
||||
port = mix_port(~w[test --listen-on-stdin])
|
||||
|
||||
assert receive_until_match(port, "error", []) =~ "lib/b.ex"
|
||||
|
||||
File.write!("lib/b.ex", """
|
||||
defmodule B do
|
||||
def f, do: A.f
|
||||
end
|
||||
""")
|
||||
|
||||
Port.command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "seed", []) =~ "2 tests"
|
||||
|
||||
File.write!("test/b_test_stale.exs", """
|
||||
defmodule BTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "f" do
|
||||
assert B.f() == error_not_a_var
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
Port.command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "undefined function error_not_a_var", []) =~ "test/b_test_stale.exs"
|
||||
|
||||
File.write!("test/b_test_stale.exs", """
|
||||
defmodule BTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "f" do
|
||||
assert B.f() == :ok
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
Port.command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "seed", []) =~ "2 tests"
|
||||
end
|
||||
end
|
||||
|
||||
defp receive_until_match(port, expected, acc) do
|
||||
receive do
|
||||
{^port, {:data, charlist}} ->
|
||||
string = to_string(charlist)
|
||||
acc = [acc | string]
|
||||
|
||||
if string =~ expected do
|
||||
IO.iodata_to_binary(acc)
|
||||
else
|
||||
receive_until_match(port, expected, acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp set_all_mtimes(time \\ {{2010, 1, 1}, {0, 0, 0}}) do
|
||||
Enum.each(Path.wildcard("**", match_dot: true), &File.touch!(&1, time))
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ defmodule Mix.UmbrellaTest do
|
||||
|
||||
# Ensure we can compile and run checks
|
||||
Mix.Task.run "deps.compile"
|
||||
Mix.Task.run "deps.check"
|
||||
Mix.Task.run "deps.loadpaths"
|
||||
Mix.Task.run "compile", ["--verbose"]
|
||||
|
||||
assert_received {:mix_shell, :info, ["==> bar"]}
|
||||
@@ -364,6 +364,53 @@ defmodule Mix.UmbrellaTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "reconsolidates using umbrella parent information on shared _build" do
|
||||
in_fixture("umbrella_dep/deps/umbrella", fn ->
|
||||
File.write!("apps/bar/lib/bar.ex", """
|
||||
defprotocol Bar do
|
||||
def bar(arg)
|
||||
end
|
||||
defimpl Bar, for: List do
|
||||
def bar(list), do: list
|
||||
end
|
||||
""")
|
||||
|
||||
Mix.Project.in_project(:foo, "apps/foo", [build_path: "../../_build"], fn _ ->
|
||||
Mix.Task.run("compile.protocols")
|
||||
refute Code.ensure_loaded?(Bar)
|
||||
end)
|
||||
|
||||
Mix.Project.in_project(:umbrella, ".", fn _ ->
|
||||
Mix.Task.run("compile.protocols")
|
||||
Mix.Task.run("app.start")
|
||||
assert Protocol.consolidated?(Bar)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "reconsolidates using umbrella child information on shared _build" do
|
||||
in_fixture("umbrella_dep/deps/umbrella", fn ->
|
||||
File.write!("apps/bar/lib/bar.ex", """
|
||||
defprotocol Bar do
|
||||
def foo(arg)
|
||||
end
|
||||
defimpl Bar, for: List do
|
||||
def foo(list), do: list
|
||||
end
|
||||
""")
|
||||
|
||||
Mix.Project.in_project(:umbrella, ".", fn _ ->
|
||||
Mix.Task.run("compile.protocols")
|
||||
end)
|
||||
|
||||
# Emulate the dependency being removed
|
||||
Mix.Project.in_project(:foo, "apps/foo", [build_path: "../../_build", deps: []], fn _ ->
|
||||
File.rm_rf "../../_build/dev/lib/bar"
|
||||
Mix.Task.run("compile.protocols")
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defmodule Selective do
|
||||
def project do
|
||||
[apps_path: "apps",
|
||||
|
||||
@@ -136,6 +136,15 @@ defmodule MixTest.Case do
|
||||
env: envs) |> elem(0)
|
||||
end
|
||||
|
||||
def mix_port(args, envs \\ []) when is_list(args) do
|
||||
:erlang.open_port({:spawn_executable, elixir_executable()}, [
|
||||
{:args, ["-r", mix_executable(), "--" | args]},
|
||||
{:env, envs},
|
||||
:use_stdio,
|
||||
:stderr_to_stdout
|
||||
])
|
||||
end
|
||||
|
||||
defp mix_executable do
|
||||
Path.expand("../../../bin/mix", __DIR__)
|
||||
end
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{application, elixir,
|
||||
[{description, "elixir"},
|
||||
{vsn, "1.3.1"},
|
||||
{vsn, "1.3.2"},
|
||||
{modules, [
|
||||
elixir
|
||||
]},
|
||||
|
||||
Reference in New Issue
Block a user