Compare commits

...
61 Commits
Author SHA1 Message Date
José Valim 29fdd09c0f Release v1.15.8 2024-05-21 00:16:44 +02:00
José Valim a1934d6cd5 Fix --dbg handling in bin/elixir, closes #13482 2024-05-06 12:51:31 +02:00
José Valim 26627272a9 Add .bat/.com disclaimers to System.cmd and Port 2024-05-06 12:51:27 +02:00
Theodor Fiedler 0c4cc6c992 Translate :undefined URI port to nil (#13464)
Resolves #13462

When a url string is schema less and the host is followed by a colon
without setting the actual port, `:uri_string.parse/1` returns the
port to be `:undefined`. As long as the url string is parseable,
we should translate `:undefined` to `nil`, in order to ensure we return
a valid URI struct.
2024-05-06 12:50:50 +02:00
José Valim a5b730a900 Ensure translators are persisted across logger restarts 2024-05-02 00:36:16 +02:00
José Valim 4afc03bece Ensure compile paths are available during compilation, closes #13458 2024-05-02 00:35:51 +02:00
Łukasz Samson e236cbb164 Elixir 1.14.5 supports Erlang/OTP 26 (#13071) 2023-11-03 11:54:14 +01:00
José Valim 927b10df80 Release v1.15.7 2023-10-14 12:15:24 +02:00
José Valim bbe04b4ab9 Address compilation on Erlang/OTP 24 2023-10-14 11:38:28 +02:00
José Valim 9153d73f2b Address compilation on Erlang/OTP 24 2023-10-14 11:35:31 +02:00
José Valim 6d4811ad1c Use remote version of external handler 2023-10-14 11:28:19 +02:00
Juha 94326a3544 Document need of Tasks to trap exits for Task.Supervisor :shutdown to have effect (#13004) 2023-10-13 22:49:50 +02:00
José Valim ef527cad43 Address compilation warnings on Erlang/OTP 24 2023-10-13 22:49:39 +02:00
José Valim e91b1fa66b Do not tie eval functions to Elixir version 2023-10-11 10:36:16 +02:00
José Valim 863e66884d Clarify docs on tasks requirements 2023-10-11 10:33:38 +02:00
José Valim 54516c5684 Address tests on Erlang/OTP 26.1, closes #12975 2023-10-01 15:46:26 +02:00
José Valim 08d2bb170d Ensure full directory match on mix format, closes #12969 2023-09-28 21:34:22 +02:00
José Valim c2a0d8ead6 Do not emit duplicate warnings from tokenizer, closes #12961 2023-09-25 12:56:29 +02:00
José Valim a16517edfb Release v1.15.6 2023-09-20 10:32:46 +02:00
Wojtek Mach 26136e53c6 Improve Windows Installer (#12945)
1. If we couldn't verify installed OTP, don't quit but let user proceed with installing just Elixir

2. When checking installed OTPs, pick the recent most installed. Previously we were picking the first one which was basically guaranteed to be too old.

3. If we find an existing Elixir installation but it doesn't match the OTP version, offer to download the OTP version this Elixir installer was compiled against (which usually would be the newer version)

4. Add "Verify Erlang/OTP" button which re-checks for installed OTP.

Closes #12678.
2023-09-20 10:30:17 +02:00
José Valim 6654f1b922 Load plugins on formatter_for_file, closes #12930 2023-09-14 09:13:14 +02:00
José Valim e30f8df8fe Do not loop when invalid block is given to formatter, closes #12922 2023-09-13 09:50:05 +02:00
José Valim 2fe5b77498 Trace functions before they are inlined, closes #12925 2023-09-13 09:39:03 +02:00
José Valim f1a5e6a0b1 Properly print warnings from tokenizer in EEx, closes #12926 2023-09-13 09:17:45 +02:00
José Valim fadf5c6519 Do not always load applications during convergence, closes #12682 2023-09-12 11:54:57 +02:00
José Valim e975613c9d Load extra applications for umbrellas 2023-09-12 10:12:01 +02:00
Jean Klingler b1a1dd04a5 Fix formatter for :* in bitstring modifiers (#12923) 2023-09-12 08:12:09 +02:00
José Valim e4e2f1a79e Include environment in mix format missing dependency error 2023-09-11 17:36:21 +02:00
José Valim 138b442e07 Improve error message on Access module 2023-09-04 14:01:09 +02:00
Wojtek Mach 119580f544 Fix PATH handling on Windows Installer (#12900) 2023-08-30 17:09:56 +02:00
José Valim 9fd97c4663 Release v1.15.5 2023-08-28 13:45:12 +02:00
José Valim 78cee0e384 Speed up loading of struct suggestions, closes #12674 2023-08-27 10:12:24 +02:00
Zach Allaun fb0379287c Fix Code.Fragment.surround_context/2 for submodules of non-aliases (#12890) 2023-08-24 20:55:53 +02:00
José Valim def65ab120 Do not store anonymous functions in cache 2023-08-23 14:15:55 +02:00
José Valim dc8cfcd4c9 Load and compile plugins from cache, closes #12880 2023-08-23 12:24:22 +02:00
sabiwara 84c8d23710 Always pass stracktrace when necessary 2023-08-23 12:06:07 +02:00
Jean Klingler 36c3f7594d Fix bug in surround_context alias handling (#12830) 2023-08-03 23:22:18 +09:00
José Valim bdaffc380b Fix infinite loop when diffing functions, closes #12828 2023-08-02 20:45:57 +02:00
José Valim 5d79b34cb5 Force group leader to run as a binary and unicode in IEx 2023-07-31 15:46:39 +02:00
José Valim 14cddf357b Also handle charlist returns from stdio
Closes #12687.
2023-07-28 12:24:26 +02:00
José Valim 92eee10bcf Optional arguments must be counted from the end, closes #10095 2023-07-19 13:52:17 +02:00
José Valim 9b7b7d655d Do not assume blake is always available
Closes #12809.
2023-07-19 00:22:32 +02:00
José Valim f40aa10436 Fix type in changelog 2023-07-18 13:21:23 +02:00
José Valim c521bdb91a Release v1.15.4 2023-07-18 11:38:35 +02:00
José Valim bdbdf52122 Disable consolidation on archive.install 2023-07-17 19:14:29 +02:00
José Valim 7ff97fb930 Compute remove entries per dependency, closes #12801 2023-07-17 09:40:31 +02:00
Santiago Ferreira af378de03f Remove duplicated async: true option from example (#12798) 2023-07-17 00:13:06 +02:00
Jean Klingler 82c264bc36 Remove erlang doc duplicates in IEx (#12797) 2023-07-17 00:12:51 +02:00
José Valim 4fbed9fe12 Consider significant chunks from Erlang/OTP 26 2023-07-17 00:12:24 +02:00
Ievgen Pyrogov 1dbf121a78 Update io.ex (#12800)
Fix newlines in documentation for IO module
2023-07-17 00:12:24 +02:00
José Valim 694b9b0218 Release v1.15.3 2023-07-15 09:50:33 +02:00
José Valim 0a3df5068c Remove unsupport --no-deps-loading 2023-07-15 09:38:48 +02:00
José Valim 9e177fd592 Ensure load path is enabled on __mix_recompile__? 2023-07-15 09:23:32 +02:00
Thibaut Barrère 8422af4a2e Clarify upgrade path for Regex.regex? (#12785) (#12786) 2023-07-12 18:05:00 +02:00
José Valim b79c5a3b4c Consider optional apps in Mix.ensure_application! 2023-07-12 09:27:40 +02:00
José Valim 3bb0a8379d Disable tail call optimization on file root entries 2023-07-07 13:37:57 +02:00
Łukasz Samson fe0d57c4da Allow to opt out of starting apps in Mix.install (#12766) 2023-07-07 08:39:53 +02:00
José Valim 727a958c48 Clarify --no-pry vs --dbg pry 2023-07-06 12:36:21 +02:00
José Valim aa93d654ab Propagate diagnostics from inner compiler process 2023-07-06 09:42:54 +02:00
José Valim f5b71a79b2 Improve error message when IEx cannot boot 2023-07-03 16:33:43 +02:00
José Valim 173fc52610 Fix IEx --remsh on Erlang/OTP 25-
Closes #12746.
2023-07-03 16:33:43 +02:00
73 changed files with 1144 additions and 602 deletions
+125
View File
@@ -117,6 +117,131 @@ in the long term.
See the new `Logger` documentation for more information on the
new features and on compatibility.
## v1.15.8 (2024-05-21)
### 1. Bug fixes
#### Elixir
* [bin/elixir] Properly handle the `--dbg` flag in Elixir's CLI
* [System] Add a note that arguments are unsafe when invoking .bat/.com scripts on Windows via `System.cmd/3`
* [Port] Add a note that arguments are unsafe when invoking .bat/.com scripts on Windows
* [URI] Ensure `:undefined` fields are properly converted to `nil` when invoking Erlang's API
#### Logger
* [Logger] Ensure translators are persisted across logger restarts
#### Mix
* [mix compile] Ensure compile paths are accessible during compilation
## v1.15.7 (2023-10-14)
### 1. Enhancements
#### Elixir
* [Elixir] Allow code evaluation across Elixir versions
### 2. Bug fixes
#### EEx
* [EEx] Do not emit duplicate warnings from tokenizer
#### Mix
* [mix format] Correctly match file to subdirectory in `Mix.Tasks.Format.formatter_for_file/2`
## v1.15.6 (2023-09-20)
This release also includes fixes to the Windows installer.
### 1. Bug fixes
#### EEx
* [EEx] Do not crash when printing tokenizer warnings
#### Elixir
* [Code] Fix formatter for nested `*` in bitstrings
* [Code] Improve feedback when an invalid block is given `Code.quoted_to_algebra/2`
* [Kernel] Trace functions before they are inlined
#### Mix
* [mix compile] Ensure `:extra_applications` declare in umbrella projects are loaded
* [mix deps.get] Do not check for invalid applications before deps.get
* [mix deps.update] Do not check for invalid applications before deps.update
* [mix format] Load plugins when invoking the formatter from an IDE
## v1.15.5 (2023-08-28)
### 1. Enhancements
#### IEx
* [IEx.Autocomplete] Speed up loading of struct suggestions
### 2. Bug fixes
#### Elixir
* [Code.Fragment] Fix `Code.Fragment.surround_context/2` for aliases and submodules of non-aliases
* [Kernel] Ensure stacktrace is included when necessary when rescuing multiple exceptions in the same branch
* [Kernel] Fix index in error message for unused optional arguments
#### ExUnit
* [ExUnit.Diff] Fix scenario where diff would not show up due to a timed-out loop
#### IEx
* [IEx] Force group leader to run as a binary and unicode in IEx
#### Mix
* [mix compile] Do not assume `blake` is always available
* [mix format] Load and compile plugins if specified in subdirectories
## v1.15.4 (2023-07-18)
### 1. Bug fixes
#### Mix
* [mix archive.build] Disable protocol consolidation when building archives on archive.install
* [mix compile] Track removed files per local dependency (this addresses a bug where files depending on modules from path dependencies always recompiled)
* [mix release] Do not strip relevant chunks from Erlang/OTP 26
## v1.15.3 (2023-07-15)
### 1. Enhancements
#### Elixir
* [Kernel] Improve stacktraces when executing unnested Elixir code in a file
#### Mix
* [Mix] Allow to opt-out of starting apps in `Mix.install/2`
### 2. Bug fixes
#### Elixir
* [Code] Ensure `with_diagnostics` propagate warnings from inner Erlang passes
#### IEx
* [IEx] Fix `--remsh` on Erlang/OTP 25 and earlier
#### Mix
* [mix compile.elixir] Ensure `__mix_recompile__?` callbacks are properly invoked
## v1.15.2 (2023-07-01)
### 1. Bug fixes
+1 -1
View File
@@ -1 +1 @@
1.15.2
1.15.8
+3 -3
View File
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
ELIXIR_VERSION=1.15.2
ELIXIR_VERSION=1.15.8
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
@@ -112,10 +112,10 @@ while [ $I -le $LENGTH ]; do
C=1
MODE="iex"
;;
-v|--no-halt|--dbg)
-v|--no-halt)
C=1
;;
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex)
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg)
C=2
;;
--rpc-eval)
+1 -1
View File
@@ -1,6 +1,6 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
set ELIXIR_VERSION=1.15.2
set ELIXIR_VERSION=1.15.8
setlocal enabledelayedexpansion
if ""%1""=="""" if ""%2""=="""" goto documentation
+3 -5
View File
@@ -71,11 +71,9 @@ defmodule EEx.Compiler do
{:ok, expr, new_line, new_column, rest} ->
{key, expr} =
case :elixir_tokenizer.tokenize(expr, 1, file: "eex", check_terminators: false) do
{:ok, _line, _column, warnings, tokens} ->
Enum.each(Enum.reverse(warnings), fn {location, file, msg} ->
:elixir_errors.erl_warn(location, file, msg)
end)
{:ok, _line, _column, _warnings, tokens} ->
# We ignore warnings because the code will be tokenized
# again later with the right line+column info
token_key(tokens, expr)
{:error, _, _, _, _} ->
+1 -1
View File
@@ -5,7 +5,7 @@ defmodule EEx.TokenizerTest do
@opts [indentation: 0, trim: false]
test "simple chars lists" do
test "simple charlists" do
assert EEx.tokenize(~c"foo", @opts) ==
{:ok, [{:text, ~c"foo", %{column: 1, line: 1}}, {:eof, %{column: 4, line: 1}}]}
end
+28 -31
View File
@@ -463,17 +463,13 @@ defmodule EExTest do
end
end
test "when middle expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<%= else %>false<% end %>")
end) =~ ~s[unexpected beginning of EEx tag \"<%=\" on \"<%= else %>\"]
end
test "when trying to use marker '|' without implementation" do
msg =
~r/unsupported EEx syntax <%| %> \(the syntax is valid but not supported by the current EEx engine\)/
test "when end expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<% else %>false<%= end %>")
end) =~
~s[unexpected beginning of EEx tag \"<%=\" on \"<%= end %>\"]
assert_raise EEx.SyntaxError, msg, fn ->
EEx.compile_string("<%| true %>")
end
end
test "when trying to use marker '/' without implementation" do
@@ -485,17 +481,6 @@ defmodule EExTest do
end
end
test "when trying to use marker '|' without implementation" do
msg =
~r/unsupported EEx syntax <%| %> \(the syntax is valid but not supported by the current EEx engine\)/
assert_raise EEx.SyntaxError, msg, fn ->
EEx.compile_string("<%| true %>")
end
end
end
describe "error messages" do
test "honor line numbers" do
assert_raise EEx.SyntaxError,
"nofile:100:6: expected closing '%>' for EEx expression",
@@ -516,18 +501,30 @@ defmodule EExTest do
EEx.compile_string("foo <%= bar", file: "my_file.eex")
end
end
end
test "when <%!-- is not closed" do
message = """
my_file.eex:1:5: expected closing '--%>' for EEx expression
|
1 | foo <%!-- bar
| ^\
"""
describe "warnings" do
test "when middle expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<%= else %>false<% end %>")
end) =~ ~s[unexpected beginning of EEx tag \"<%=\" on \"<%= else %>\"]
end
assert_raise EEx.SyntaxError, message, fn ->
EEx.compile_string("foo <%!-- bar", file: "my_file.eex")
end
test "when end expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<% else %>false<%= end %>")
end) =~
~s[unexpected beginning of EEx tag \"<%=\" on \"<%= end %>\"]
end
test "from tokenizer" do
warning =
ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string(~s'<%= :"foo" %>', file: "tokenizer.ex")
end)
assert warning =~ "found quoted atom \"foo\" but the quotes are not required"
assert warning =~ "tokenizer.ex:1:5"
end
end
+55 -6
View File
@@ -191,9 +191,13 @@ defmodule Access do
case __STACKTRACE__ do
[unquote(top) | _] ->
reason =
"#{inspect(unquote(module))} does not implement the Access behaviour. " <>
"If you are using get_in/put_in/update_in, you can specify the field " <>
"to be accessed using Access.key!/1"
"""
#{inspect(unquote(module))} does not implement the Access behaviour
You can use the "struct.field" syntax to access struct fields. \
You can also use Access.key!/1 to access struct fields dynamically \
inside get_in/put_in/update_in\
"""
%{unquote(exception) | reason: reason}
@@ -326,9 +330,23 @@ defmodule Access do
end
end
def get(list, key, _default) when is_list(list) and is_integer(key) do
raise ArgumentError, """
the Access module does not support accessing lists by index, got: #{inspect(key)}
Accessing a list by index is typically discouraged in Elixir, \
instead we prefer to use the Enum module to manipulate lists \
as a whole. If you really must access a list element by index, \
you can Enum.at/1 or the functions in the List module\
"""
end
def get(list, key, _default) when is_list(list) do
raise ArgumentError,
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
raise ArgumentError, """
the Access module supports only keyword lists (with atom keys), got: #{inspect(key)}
If you want to search lists of tuples, use List.keyfind/3\
"""
end
def get(nil, _key, default) do
@@ -377,10 +395,26 @@ defmodule Access do
Map.get_and_update(map, key, fun)
end
def get_and_update(list, key, fun) when is_list(list) do
def get_and_update(list, key, fun) when is_list(list) and is_atom(key) do
Keyword.get_and_update(list, key, fun)
end
def get_and_update(list, key, _fun) when is_list(list) and is_integer(key) do
raise ArgumentError, """
the Access module does not support accessing lists by index, got: #{inspect(key)}
Accessing a list by index is typically discouraged in Elixir, \
instead we prefer to use the Enum module to manipulate lists \
as a whole. If you really must mostify a list element by index, \
you can Access.at/1 or the functions in the List module\
"""
end
def get_and_update(list, key, _fun) when is_list(list) do
raise ArgumentError,
"the Access module supports only keyword lists (with atom keys), got: " <> inspect(key)
end
def get_and_update(nil, key, _fun) do
raise ArgumentError, "could not put/update key #{inspect(key)} on a nil value"
end
@@ -507,6 +541,21 @@ defmodule Access do
iex> get_in(map, [Access.key!(:user), Access.key!(:unknown)])
** (KeyError) key :unknown not found in: %{name: \"john\"}
The examples above could be partially written as:
iex> map = %{user: %{name: "john"}}
iex> map.user.name
"john"
iex> get_and_update_in(map.user.name, fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{"john", %{user: %{name: "JOHN"}}}
However, it is not possible to remove fields using the dot notation,
as it is implified those fields must also be present. In any case,
`Access.key!/1` is useful when the key is not known in advance
and must be accessed dynamically.
An error is raised if the accessed structure is not a map/struct:
iex> get_in([], [Access.key!(:foo)])
+10 -6
View File
@@ -431,7 +431,7 @@ defmodule Code.Formatter do
{color("nil", nil, state.inspect_opts), state}
end
defp quoted_to_algebra({:__block__, meta, _} = block, _context, state) do
defp quoted_to_algebra({:__block__, meta, args} = block, _context, state) when is_list(args) do
{block, state} = block_to_algebra(block, line(meta), closing_line(meta), state)
{surround("(", block, ")"), state}
end
@@ -1419,7 +1419,9 @@ defmodule Code.Formatter do
defp bitstring_segment_to_algebra({{:"::", _, [segment, spec]}, i}, state, last) do
{doc, state} = quoted_to_algebra(segment, :parens_arg, state)
{spec, state} = bitstring_spec_to_algebra(spec, state, state.normalize_bitstring_modifiers)
{spec, state} =
bitstring_spec_to_algebra(spec, state, state.normalize_bitstring_modifiers, :"::")
spec = wrap_in_parens_if_inspected_atom(spec)
spec = if i == last, do: bitstring_wrap_parens(spec, i, last), else: spec
@@ -1438,15 +1440,17 @@ defmodule Code.Formatter do
{bitstring_wrap_parens(doc, i, last), state}
end
defp bitstring_spec_to_algebra({op, _, [left, right]}, state, normalize_modifiers)
defp bitstring_spec_to_algebra({op, _, [left, right]}, state, normalize_modifiers, paren_op)
when op in [:-, :*] do
normalize_modifiers = normalize_modifiers && op != :*
{left, state} = bitstring_spec_to_algebra(left, state, normalize_modifiers)
{left, state} = bitstring_spec_to_algebra(left, state, normalize_modifiers, op)
{right, state} = bitstring_spec_element_to_algebra(right, state, normalize_modifiers)
{concat(concat(left, Atom.to_string(op)), right), state}
doc = concat(concat(left, Atom.to_string(op)), right)
doc = if paren_op == :*, do: wrap_in_parens(doc), else: doc
{doc, state}
end
defp bitstring_spec_to_algebra(spec, state, normalize_modifiers) do
defp bitstring_spec_to_algebra(spec, state, normalize_modifiers, _paren_op) do
bitstring_spec_element_to_algebra(spec, state, normalize_modifiers)
end
+4 -1
View File
@@ -677,6 +677,9 @@ defmodule Code.Fragment do
{{:alias, acc}, offset} ->
build_surround({:alias, acc}, reversed, line, offset)
{{:alias, parent, acc}, offset} ->
build_surround({:alias, parent, acc}, reversed, line, offset)
{{:struct, acc}, offset} ->
build_surround({:struct, acc}, reversed, line, offset)
@@ -753,7 +756,7 @@ defmodule Code.Fragment do
end
end
defp take_alias([h | t], acc) when h in ?A..?Z or h in ?a..?z or h in ?0..9 or h == ?_,
defp take_alias([h | t], acc) when h in ?A..?Z or h in ?a..?z or h in ?0..?9 or h == ?_,
do: take_alias(t, [h | acc])
defp take_alias(rest, acc) do
+3 -3
View File
@@ -593,11 +593,11 @@ defmodule IO do
Another example where you might want to collect a user input
every new line and break on an empty line, followed by removing
redundant new line characters (`"\n"`):
redundant new line characters (`"\\n"`):
IO.stream(:stdio, :line)
|> Enum.take_while(&(&1 != "\n"))
|> Enum.map(&String.replace(&1, "\n", ""))
|> Enum.take_while(&(&1 != "\\n"))
|> Enum.map(&String.replace(&1, "\\n", ""))
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t()
-11
View File
@@ -4903,17 +4903,6 @@ defmodule Kernel do
{expanded, nil}
end
# We do this so that the block is not tail-call optimized and stacktraces
# are not messed up. Basically, we just insert something between the return
# value of the block and what is returned by defmodule. Using just ":ok" or
# similar doesn't work because it's likely optimized away by the compiler.
block =
quote do
result = unquote(block)
:elixir_utils.noop()
result
end
escaped =
case env do
%{function: nil, lexical_tracker: pid} when is_pid(pid) ->
+21
View File
@@ -79,6 +79,27 @@ defmodule Port do
are for advanced usage within the VM. Also consider using `System.cmd/3`
if all you want is to execute a program and retrieve its return value.
> #### Windows argument splitting and untrusted arguments {: .warning}
>
> On Unix systems, arguments are passed to a new operating system
> process as an array of strings but on Windows it is up to the child
> process to parse them and some Windows programs may apply their own
> rules, which are inconsistent with the standard C runtime `argv` parsing
>
> This is particularly troublesome when invoking `.bat` or `.com` files
> as these run implicitly through `cmd.exe`, whose argument parsing is
> vulnerable to malicious input and can be used to run arbitrary shell
> commands.
>
> Therefore, if you are running on Windows and you execute batch
> files or `.com` applications, you must not pass untrusted input as
> arguments to the program. You may avoid accidentally executing them
> by explicitly passing the extension of the program you want to run,
> such as `.exe`, and double check the program is indeed not a batch
> file or `.com` application.
>
> This affects both `spawn` and `spawn_executable`.
### spawn
The `:spawn` tuple receives a binary that is going to be executed as a
+1 -1
View File
@@ -295,7 +295,7 @@ defmodule Regex do
end
@doc false
@deprecated "Use Kernel.is_struct/2 or pattern match on %Regex{} instead"
@deprecated "Use Kernel.is_struct(term, Regex) or pattern match on %Regex{} instead"
def regex?(term)
def regex?(%Regex{}), do: true
def regex?(_), do: false
+19
View File
@@ -996,6 +996,25 @@ defmodule System do
`Port` module describes this problem and possible solutions under
the "Zombie processes" section.
> #### Windows argument splitting and untrusted arguments {: .warning}
>
> On Unix systems, arguments are passed to a new operating system
> process as an array of strings but on Windows it is up to the child
> process to parse them and some Windows programs may apply their own
> rules, which are inconsistent with the standard C runtime `argv` parsing
>
> This is particularly troublesome when invoking `.bat` or `.com` files
> as these run implicitly through `cmd.exe`, whose argument parsing is
> vulnerable to malicious input and can be used to run arbitrary shell
> commands.
>
> Therefore, if you are running on Windows and you execute batch
> files or `.com` applications, you must not pass untrusted input as
> arguments to the program. You may avoid accidentally executing them
> by explicitly passing the extension of the program you want to run,
> such as `.exe`, and double check the program is indeed not a batch
> file or `.com` application.
## Examples
iex> System.cmd("echo", ["hello"])
+5
View File
@@ -162,6 +162,7 @@ defmodule Task.Supervisor do
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds.
The tasks must trap exits for the timeout to have an effect.
"""
@spec async(Supervisor.supervisor(), (-> any), Keyword.t()) :: Task.t()
@@ -183,6 +184,7 @@ defmodule Task.Supervisor do
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds.
The tasks must trap exits for the timeout to have an effect.
"""
@spec async(Supervisor.supervisor(), module, atom, [term], Keyword.t()) :: Task.t()
@@ -208,6 +210,7 @@ defmodule Task.Supervisor do
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds.
The tasks must trap exits for the timeout to have an effect.
## Compatibility with OTP behaviours
@@ -337,6 +340,7 @@ defmodule Task.Supervisor do
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value. Defaults to `5000` milliseconds.
The tasks must trap exits for the timeout to have an effect.
## Examples
@@ -455,6 +459,7 @@ defmodule Task.Supervisor do
* `:shutdown` - `:brutal_kill` if the task must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds.
The task must trap exits for the timeout to have an effect.
"""
@spec start_child(Supervisor.supervisor(), (-> any), keyword) ::
+5 -5
View File
@@ -648,16 +648,16 @@ defmodule URI do
scheme = String.downcase(scheme, :ascii)
case map do
%{port: port} when port != :undefined ->
%{port: port} when is_integer(port) ->
%{uri | scheme: scheme}
%{} ->
case default_port(scheme) do
nil -> %{uri | scheme: scheme}
port -> %{uri | scheme: scheme, port: port}
end
%{uri | scheme: scheme, port: default_port(scheme)}
end
%{port: :undefined} ->
%{uri | port: nil}
%{} ->
uri
end
@@ -43,7 +43,7 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea
Elixir version | Supported Erlang/OTP versions
:------------- | :-------------------------------
1.15 | 24 - 26
1.14 | 23 - 25
1.14 | 23 - 25 (and Erlang/OTP 26 from v1.14.5)
1.13 | 22 - 24 (and Erlang/OTP 25 from v1.13.4)
1.12 | 22 - 24
1.11 | 21 - 23 (and Erlang/OTP 24 from v1.11.4)
@@ -81,7 +81,7 @@ Version | Deprecated feature | Replaced by (ava
:-------| :-------------------------------------------------- | :---------------------------------------------------------------
[v1.15] | `Calendar.ISO.day_of_week/3` | `Calendar.ISO.day_of_week/4` (v1.11)
[v1.15] | `Exception.exception?/1` | `Kernel.is_exception/1` (v1.11)
[v1.15] | `Regex.regex?/1` | `Kernel.is_struct/2` (v1.11)
[v1.15] | `Regex.regex?/1` | `Kernel.is_struct/2` (`Kernel.is_struct(term, Regex)`) (v1.11)
[v1.15] | `Logger.warn/2` | `Logger.warning/2` (v1.11)
[v1.14] | `use Bitwise` | `import Bitwise` (v1.0)
[v1.14] | `~~~/1` | `bnot/2` (v1.0)
@@ -1,8 +1,6 @@
!include "MUI2.nsh"
!include "StrFunc.nsh"
${Using:StrFunc} StrStr
${Using:StrFunc} UnStrStr
${Using:StrFunc} UnStrRep
Name "Elixir"
ManifestDPIAware true
@@ -14,11 +12,18 @@ InstallDir "$PROGRAMFILES64\Elixir"
Page custom CheckOTPPageShow CheckOTPPageLeave
var Dialog
var DownloadOTPLink
var InstalledERTSVersion
var InstalledOTPRelease
var OTPPath
var Dialog
var NoOTPLabel
var NoOTPLabelCreated
var OTPMismatchLabel
var OTPMismatchLabelCreated
var DownloadOTPLink
var DownloadOTPLinkCreated
var VerifyOTPButton
var VerifyOTPButtonCreated
Function CheckOTPPageShow
!insertmacro MUI_HEADER_TEXT "Checking Erlang/OTP" ""
@@ -29,16 +34,61 @@ Function CheckOTPPageShow
Abort
${EndIf}
EnumRegKey $0 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang" 0
ReadRegStr $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang\$0" ""
StrCpy $InstalledERTSVersion $0
StrCpy $OTPPath $1
Call VerifyOTP
${If} $1 == ""
${NSD_CreateLabel} 0 0 100% 20u "Couldn't find existing Erlang/OTP installation. Click the link below to download and install it before proceeding."
${NSD_CreateLink} 0 25u 100% 20u "Download Erlang/OTP ${OTP_RELEASE}"
nsDialogs::Show
FunctionEnd
Function VerifyOTP
${If} $NoOTPLabelCreated == "true"
ShowWindow $NoOTPLabel ${SW_HIDE}
${EndIf}
${If} $OTPMismatchLabelCreated == "true"
ShowWindow $OTPMismatchLabel ${SW_HIDE}
${EndIf}
${If} $DownloadOTPLinkCreated == "true"
ShowWindow $DownloadOTPLink ${SW_HIDE}
${Else}
StrCpy $DownloadOTPLinkCreated "true"
${NSD_CreateLink} 0 60u 100% 20u "Download Erlang/OTP ${OTP_RELEASE}"
Pop $DownloadOTPLink
${NSD_OnClick} $DownloadOTPLink OpenOTPDownloads
ShowWindow $DownloadOTPLink ${SW_HIDE}
${EndIf}
${If} $VerifyOTPButtonCreated == "true"
ShowWindow $VerifyOTPButton ${SW_HIDE}
${Else}
StrCpy $VerifyOTPButtonCreated "true"
${NSD_CreateButton} 0 80u 25% 12u "Verify Erlang/OTP"
Pop $VerifyOTPButton
${NSD_OnClick} $VerifyOTPButton VerifyOTP
ShowWindow $VerifyOTPButton ${SW_HIDE}
${EndIf}
StrCpy $0 0
loop:
EnumRegKey $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang" $0
StrCmp $1 "" done
ReadRegStr $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang\$1" ""
StrCpy $OTPPath $1
IntOp $0 $0 + 1
goto loop
done:
${If} $OTPPath == ""
${If} $NoOTPLabelCreated != "true"
StrCpy $NoOTPLabelCreated "true"
${NSD_CreateLabel} 0 0 100% 20u "Couldn't find existing Erlang/OTP installation. Click the link below to download and install it before proceeding."
Pop $NoOTPLabel
${EndIf}
ShowWindow $NoOTPLabel ${SW_SHOW}
ShowWindow $DownloadOTPLink ${SW_SHOW}
ShowWindow $VerifyOTPButton ${SW_SHOW}
${Else}
nsExec::ExecToStack `$OTPPath\bin\erl.exe -noinput -eval "\
io:put_chars(erlang:system_info(otp_release)),\
@@ -49,24 +99,25 @@ Function CheckOTPPageShow
${If} $0 == 0
StrCpy $InstalledOTPRelease $1
${If} $InstalledOTPRelease == ${OTP_RELEASE}
${NSD_CreateLabel} 0 0 100% 20u "Found existing Erlang/OTP $InstalledOTPRelease installation at $OTPPath. Please proceed."
${ElseIf} $2 < ${OTP_RELEASE}
${NSD_CreateLabel} 0 0 100% 30u "Found existing Erlang/OTP $InstalledOTPRelease installation at $OTPPath but this Elixir installer was precompiled for Erlang/OTP ${OTP_RELEASE}. \
We recommend checking if there is an Elixir version precompiled for Erlang/OTP $InstalledOTPRelease. Otherwise, proceed."
${NSD_CreateLabel} 0 0 100% 60u "Found existing Erlang/OTP $InstalledOTPRelease installation at $OTPPath. Please proceed."
${Else}
SetErrorlevel 5
MessageBox MB_ICONSTOP "Found existing Erlang/OTP $InstalledOTPRelease installation at $OTPPath but this Elixir version was precompiled for Erlang/OTP ${OTP_RELEASE}. \
Please upgrade your Erlang/OTP version or choose an Elixir installer matching your Erlang/OTP version"
Quit
${If} $OTPMismatchLabelCreated != "true"
StrCpy $OTPMismatchLabelCreated "true"
${NSD_CreateLabel} 0 0 100% 60u "Found existing Erlang/OTP $InstalledOTPRelease installation at $OTPPath but this Elixir installer was precompiled for Erlang/OTP ${OTP_RELEASE}. \
$\r$\n$\r$\nYou can either search for another Elixir installer precompiled for Erlang/OTP $InstalledOTPRelease or download Erlang/OTP ${OTP_RELEASE} and install before proceeding."
Pop $OTPMismatchLabel
${EndIf}
ShowWindow $OTPMismatchLabel ${SW_SHOW}
ShowWindow $DownloadOTPLink ${SW_SHOW}
ShowWindow $VerifyOTPButton ${SW_SHOW}
${EndIf}
${Else}
SetErrorlevel 5
MessageBox MB_ICONSTOP "Found existing Erlang/OTP installation at $OTPPath but checking it exited with $0: $1"
Quit
${EndIf}
${EndIf}
nsDialogs::Show
FunctionEnd
Function OpenOTPDownloads
@@ -97,42 +148,42 @@ Function FinishPageShow
Abort
${EndIf}
${NSD_CreateCheckbox} 0 0 195u 10u "&Add $INSTDIR\bin to %PATH%"
Pop $AddElixirToPathCheckbox
SendMessage $AddElixirToPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
; we add to PATH using erlang, so there must be an OTP installed to do so.
${If} "$OTPPath" != ""
${NSD_CreateCheckbox} 0 0 195u 10u "&Add $INSTDIR\bin to %PATH%"
Pop $AddElixirToPathCheckbox
SendMessage $AddElixirToPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
EnumRegKey $0 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang" 0
ReadRegStr $0 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang\$0" ""
StrCpy $OTPPath $0
${If} $0 != ""
${NSD_CreateCheckbox} 0 20u 195u 10u "&Add $0\bin to %PATH%"
${NSD_CreateCheckbox} 0 20u 195u 10u "&Add $OTPPath\bin to %PATH%"
Pop $AddOTPToPathCheckbox
SendMessage $AddOTPToPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
${NSD_CreateLabel} 0 40u 100% 20u "Note: you need to restart your shell for the environment variable changes to take effect."
${EndIf}
nsDialogs::Show
FunctionEnd
var PathsToAdd
Function FinishPageLeave
${NSD_GetState} $AddOTPToPathCheckbox $0
${If} $0 <> ${BST_UNCHECKED}
ReadRegStr $0 HKCU "Environment" "Path"
${StrStr} $1 "$0" "$OTPPath\bin"
${If} $1 == ""
WriteRegExpandStr HKCU "Environment" "Path" "$OTPPath\bin;$0"
${Else}
MessageBox MB_OK "$OTPPath\bin already in %PATH%"
${EndIf}
StrCpy $PathsToAdd ";$OTPPath\bin"
${EndIf}
${NSD_GetState} $AddElixirToPathCheckbox $0
${If} $0 <> ${BST_UNCHECKED}
ReadRegStr $0 HKCU "Environment" "Path"
${StrStr} $1 "$0" "$INSTDIR\bin"
${If} $1 == ""
WriteRegExpandStr HKCU "Environment" "Path" "$INSTDIR\bin;$0"
${Else}
MessageBox MB_OK "$INSTDIR\bin already in %PATH%"
StrCpy $PathsToAdd "$PathsToAdd;$INSTDIR\bin"
${EndIf}
${If} "$PathsToAdd" != ""
nsExec::ExecToStack `"$OTPPath\bin\escript.exe" "$INSTDIR\update_system_path.erl" add "$PathsToAdd"`
Pop $0
Pop $1
${If} $0 != 0
SetErrorlevel 5
MessageBox MB_ICONSTOP "adding paths failed with $0: $1"
Quit
${EndIf}
${EndIf}
FunctionEnd
@@ -140,25 +191,27 @@ FunctionEnd
Section "Install Elixir" SectionElixir
SetOutPath "$INSTDIR"
File /r "${ELIXIR_DIR}\"
File "update_system_path.erl"
WriteUninstaller "Uninstall.exe"
SectionEnd
; Uninstall Page: Files
!insertmacro MUI_UNPAGE_DIRECTORY
!insertmacro MUI_UNPAGE_INSTFILES
Section "Uninstall"
RMDir /r "$INSTDIR"
SectionEnd
; Uninstall Page: Finish
; Uninstall Page: Remove from %PATH%
var RemoveOTPFromPathCheckbox
var RemoveElixirFromPathCheckbox
Function un.FinishPageShow
!insertmacro MUI_HEADER_TEXT "Finish Setup" ""
!insertmacro MUI_HEADER_TEXT "Remove from %PATH%" ""
StrCpy $0 0
loop:
EnumRegKey $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang" $0
StrCmp $1 "" done
ReadRegStr $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang\$1" ""
StrCpy $OTPPath $1
IntOp $0 $0 + 1
goto loop
done:
nsDialogs::Create 1018
Pop $Dialog
@@ -168,6 +221,7 @@ Function un.FinishPageShow
${EndIf}
ReadRegStr $0 HKCU "Environment" "Path"
${UnStrStr} $1 "$0" "$INSTDIR\bin"
${If} $1 != ""
${NSD_CreateCheckbox} 0 0 195u 10u "&Remove $INSTDIR\bin from %PATH%"
@@ -175,9 +229,6 @@ Function un.FinishPageShow
SendMessage $RemoveElixirFromPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
${EndIf}
EnumRegKey $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang" 0
ReadRegStr $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang\$1" ""
StrCpy $OTPPath $1
${UnStrStr} $1 "$0" "$OTPPath\bin"
${If} $1 != ""
${NSD_CreateCheckbox} 0 20u 195u 10u "&Remove $OTPPath\bin from %PATH%"
@@ -188,22 +239,37 @@ Function un.FinishPageShow
nsDialogs::Show
FunctionEnd
var PathsToRemove
Function un.FinishPageLeave
ReadRegStr $0 HKCU "Environment" "Path"
${NSD_GetState} $RemoveOTPFromPathCheckbox $1
${If} $1 <> ${BST_UNCHECKED}
${UnStrRep} $0 "$0" "$OTPPath\bin;" ""
StrCpy $PathsToRemove ";$OTPPath\bin"
${EndIf}
${NSD_GetState} $RemoveElixirFromPathCheckbox $1
${If} $1 <> ${BST_UNCHECKED}
${UnStrRep} $0 "$0" "$INSTDIR\bin;" ""
StrCpy $PathsToRemove "$PathsToRemove;$INSTDIR\bin"
${EndIf}
WriteRegExpandStr HKCU "Environment" "Path" "$0"
${If} "$PathsToRemove" != ""
nsExec::ExecToStack `"$OTPPath\bin\escript.exe" "$INSTDIR\update_system_path.erl" remove "$PathsToRemove"`
Pop $0
Pop $1
${If} $0 != 0
SetErrorlevel 5
MessageBox MB_ICONSTOP "removing paths failed with $0: $1"
Quit
${EndIf}
${EndIf}
FunctionEnd
UninstPage custom un.FinishPageShow un.FinishPageLeave
!insertmacro MUI_UNPAGE_DIRECTORY
!insertmacro MUI_UNPAGE_INSTFILES
Section "Uninstall"
RMDir /r "$INSTDIR"
SectionEnd
!insertmacro MUI_LANGUAGE "English"
@@ -0,0 +1,42 @@
#!/usr/bin/env escript
%%! -noinput
%% This file is used by the Elixir installer and uninstaller.
main(["add", ";" ++ PathsToAdd]) ->
{ok, Reg} = win32reg:open([read, write]),
ok = win32reg:change_key(Reg, "\\hkey_current_user\\environment"),
{ok, SystemPath} = win32reg:value(Reg, "path"),
NewSystemPath =
lists:foldl(
fun(Elem, Acc) ->
Elem ++ ";" ++
binary_to_list(
iolist_to_binary(
string:replace(Acc, Elem ++ ";", "", all)))
end,
SystemPath,
string:split(PathsToAdd, ";", all)
),
ok = win32reg:set_value(Reg, "Path", NewSystemPath),
ok;
main(["remove", ";" ++ PathsToRemove]) ->
{ok, Reg} = win32reg:open([read, write]),
ok = win32reg:change_key(Reg, "\\hkey_current_user\\environment"),
{ok, SystemPath} = win32reg:value(Reg, "path"),
NewSystemPath =
lists:foldl(
fun(Elem, Acc) ->
binary_to_list(
iolist_to_binary(
string:replace(Acc, Elem ++ ";", "", all)))
end,
SystemPath,
string:split(PathsToRemove, ";", all)
),
ok = win32reg:set_value(Reg, "Path", NewSystemPath),
ok.
+75 -49
View File
@@ -7,10 +7,11 @@
-export([
string_to_tokens/5, tokens_to_quoted/3, 'string_to_quoted!'/5,
env_for_eval/1, quoted_to_erl/2, eval_forms/3, eval_quoted/3,
eval_quoted/4
eval_quoted/4, eval_external_handler/3
]).
-include("elixir.hrl").
-define(system, 'Elixir.System').
-define(elixir_eval_env, {elixir, eval_env}).
%% Top level types
%% TODO: Remove char_list type on v2.0
@@ -357,8 +358,25 @@ eval_forms(Tree, Binding, OrigE, Opts) ->
_ -> [Erl]
end,
ExternalHandler = eval_external_handler(NewE),
{value, Value, NewBinding} = erl_eval:exprs(Exprs, ErlBinding, none, ExternalHandler),
ExternalHandler = eval_external_handler(),
{value, Value, NewBinding} =
try
%% ?elixir_eval_env is used by the external handler.
%%
%% The reason why we use the process dictionary to pass the environment
%% is because we want to avoid passing closures to erl_eval, as that
%% would effectively tie the eval code to the Elixir version and it is
%% best if it depends solely on Erlang/OTP.
%%
%% The downside is that functions that escape the eval context will no
%% longer have the original environment they came from.
erlang:put(?elixir_eval_env, NewE),
erl_eval:exprs(Exprs, ErlBinding, none, ExternalHandler)
after
erlang:erase(?elixir_eval_env)
end,
PruneBefore = if Prune -> length(Binding); true -> -1 end,
{DumpedBinding, DumpedVars} =
@@ -369,52 +387,60 @@ eval_forms(Tree, Binding, OrigE, Opts) ->
%% TODO: Remove conditional once we require Erlang/OTP 25+.
-if(?OTP_RELEASE >= 25).
eval_external_handler(Env) ->
Fun = fun(Ann, FunOrModFun, Args) ->
try
case FunOrModFun of
{Mod, Fun} -> apply(Mod, Fun, Args);
Fun -> apply(Fun, Args)
end
catch
Kind:Reason:Stacktrace ->
%% Take everything up to the Elixir module
Pruned =
lists:takewhile(fun
({elixir,_,_,_}) -> false;
(_) -> true
end, Stacktrace),
eval_external_handler() ->
{value, fun ?MODULE:eval_external_handler/3}.
Caller =
lists:dropwhile(fun
({elixir,_,_,_}) -> false;
(_) -> true
end, Stacktrace),
%% Now we prune any shared code path from erl_eval
{current_stacktrace, Current} =
erlang:process_info(self(), current_stacktrace),
%% We need to make sure that we don't generate more
%% frames than supported. So we do our best to drop
%% from the Caller, but if the caller has no frames,
%% we need to drop from Pruned.
{DroppedCaller, ToDrop} =
case Caller of
[] -> {[], true};
_ -> {lists:droplast(Caller), false}
end,
Reversed = drop_common(lists:reverse(Current), lists:reverse(Pruned), ToDrop),
File = elixir_utils:characters_to_list(?key(Env, file)),
Location = [{file, File}, {line, erl_anno:line(Ann)}],
%% Add file+line information at the bottom
Custom = lists:reverse([{elixir_eval, '__FILE__', 1, Location} | Reversed], DroppedCaller),
erlang:raise(Kind, Reason, Custom)
eval_external_handler(Ann, FunOrModFun, Args) ->
try
case FunOrModFun of
{Mod, Fun} -> apply(Mod, Fun, Args);
Fun -> apply(Fun, Args)
end
end,
{value, Fun}.
catch
Kind:Reason:Stacktrace ->
%% Take everything up to the Elixir module
Pruned =
lists:takewhile(fun
({elixir,_,_,_}) -> false;
(_) -> true
end, Stacktrace),
Caller =
lists:dropwhile(fun
({elixir,_,_,_}) -> false;
(_) -> true
end, Stacktrace),
%% Now we prune any shared code path from erl_eval
{current_stacktrace, Current} =
erlang:process_info(self(), current_stacktrace),
%% We need to make sure that we don't generate more
%% frames than supported. So we do our best to drop
%% from the Caller, but if the caller has no frames,
%% we need to drop from Pruned.
{DroppedCaller, ToDrop} =
case Caller of
[] -> {[], true};
_ -> {lists:droplast(Caller), false}
end,
Reversed = drop_common(lists:reverse(Current), lists:reverse(Pruned), ToDrop),
%% Add file+line information at the bottom
Bottom =
case erlang:get(?elixir_eval_env) of
#{file := File} ->
[{elixir_eval, '__FILE__', 1,
[{file, elixir_utils:characters_to_list(File)}, {line, erl_anno:line(Ann)}]}];
_ ->
[]
end,
Custom = lists:reverse(Bottom ++ Reversed, DroppedCaller),
erlang:raise(Kind, Reason, Custom)
end.
%% We need to check if we have dropped any frames.
%% If we have not dropped frames, then we need to drop one
@@ -427,8 +453,8 @@ drop_common([], [{?MODULE, _, _, _} | T2], _ToDrop) -> T2;
drop_common([], [_ | T2], true) -> T2;
drop_common([], T2, _) -> T2.
-else.
eval_external_handler(_Env) ->
none.
eval_external_handler() -> none.
eval_external_handler(_Ann, _FunOrModFun, _Args) -> error(unused).
-endif.
%% Converts a quoted expression to Erlang abstract format
+14 -12
View File
@@ -16,7 +16,7 @@ quoted(Forms, File, Callback) ->
elixir_lexical:run(
Env,
fun (LexicalEnv) -> eval_or_compile(Forms, [], LexicalEnv) end,
fun (LexicalEnv) -> maybe_fast_compile(Forms, [], LexicalEnv) end,
fun (#{lexical_tracker := Pid}) -> Callback(File, Pid) end
),
@@ -32,7 +32,7 @@ file(File, Callback) ->
%% Evaluates the given code through the Erlang compiler.
%% It may end-up evaluating the code if it is deemed a
%% more efficient strategy depending on the code snippet.
eval_or_compile(Forms, Args, E) ->
maybe_fast_compile(Forms, Args, E) ->
case (?key(E, module) == nil) andalso allows_fast_compilation(Forms) andalso
(not elixir_config:is_bootstrap()) of
true -> fast_compile(Forms, E);
@@ -40,8 +40,9 @@ eval_or_compile(Forms, Args, E) ->
end,
ok.
compile(Quoted, ArgsList, E) ->
{Expanded, SE, EE} = elixir_expand:expand(Quoted, elixir_env:env_to_ex(E), E),
compile(Quoted, ArgsList, #{line := Line} = E) ->
Block = no_tail_optimize([{line, Line}], Quoted),
{Expanded, SE, EE} = elixir_expand:expand(Block, elixir_env:env_to_ex(E), E),
elixir_env:check_unused_vars(SE, EE),
{Module, Fun, Purgeable} =
@@ -55,7 +56,7 @@ spawned_compile(ExExprs, #{line := Line, file := File} = E) ->
{ErlExprs, _} = elixir_erl_pass:translate(ExExprs, erl_anno:new(Line), S),
Module = retrieve_compiler_module(),
Fun = code_fun(?key(E, module)),
Fun = code_fun(?key(E, module)),
Forms = code_mod(Fun, ErlExprs, Line, File, Module, Vars),
{Module, Binary} = elixir_erl_compiler:noenv_forms(Forms, File, [nowarn_nomatch, no_bool_opt, no_ssa_opt]),
@@ -106,15 +107,9 @@ allows_fast_compilation(_) ->
fast_compile({'__block__', _, Exprs}, E) ->
lists:foldl(fun(Expr, _) -> fast_compile(Expr, E) end, nil, Exprs);
fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
fast_compile({defmodule, Meta, [Mod, [{do, Block}]]}, NoLineE) ->
E = NoLineE#{line := ?line(Meta)},
Block = {'__block__', Meta, [
{'=', Meta, [{result, Meta, ?MODULE}, TailBlock]},
{{'.', Meta, [elixir_utils, noop]}, Meta, []},
{result, Meta, ?MODULE}
]},
Expanded = case Mod of
{'__aliases__', _, _} ->
case elixir_aliases:expand_or_concat(Mod, E) of
@@ -129,6 +124,13 @@ fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
ContextModules = [Expanded | ?key(E, context_modules)],
elixir_module:compile(Expanded, Block, [], false, E#{context_modules := ContextModules}).
no_tail_optimize(Meta, Block) ->
{'__block__', Meta, [
{'=', Meta, [{result, Meta, ?MODULE}, Block]},
{{'.', Meta, [elixir_utils, noop]}, Meta, []},
{result, Meta, ?MODULE}
]}.
%% Bootstrapper
bootstrap() ->
+6 -2
View File
@@ -130,11 +130,15 @@ dispatch_require(Meta, Receiver, Name, Args, S, E, Callback) when is_atom(Receiv
case elixir_rewrite:inline(Receiver, Name, Arity) of
{AR, AN} ->
elixir_env:trace({remote_function, Meta, Receiver, Name, Arity}, E),
Callback(AR, AN, Args);
false ->
case expand_require(Meta, Receiver, {Name, Arity}, Args, S, E) of
{ok, Receiver, Quoted} -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, S, E);
error -> Callback(Receiver, Name, Args)
{ok, Receiver, Quoted} ->
expand_quoted(Meta, Receiver, Name, Arity, Quoted, S, E);
error ->
elixir_env:trace({remote_function, Meta, Receiver, Name, Arity}, E),
Callback(Receiver, Name, Args)
end
end;
+21 -4
View File
@@ -5,25 +5,42 @@
spawn(Fun) ->
CompilerInfo = get(elixir_compiler_info),
CodeDiagnostics =
case get(elixir_code_diagnostics) of
undefined -> undefined;
{_Tail, Log} -> {[], Log}
end,
{_, Ref} =
spawn_monitor(fun() ->
put(elixir_compiler_info, CompilerInfo),
put(elixir_code_diagnostics, CodeDiagnostics),
try Fun() of
Result -> exit({ok, Result})
Result -> exit({ok, Result, get(elixir_code_diagnostics)})
catch
Kind:Reason:Stack ->
exit({Kind, Reason, Stack})
exit({Kind, Reason, Stack, get(elixir_code_diagnostics)})
end
end),
receive
{'DOWN', Ref, process, _, {ok, Result}} ->
{'DOWN', Ref, process, _, {ok, Result, Diagnostics}} ->
copy_diagnostics(Diagnostics),
Result;
{'DOWN', Ref, process, _, {Kind, Reason, Stack}} ->
{'DOWN', Ref, process, _, {Kind, Reason, Stack, Diagnostics}} ->
copy_diagnostics(Diagnostics),
erlang:raise(Kind, Reason, Stack)
end.
copy_diagnostics(undefined) ->
ok;
copy_diagnostics({Head, _}) ->
case get(elixir_code_diagnostics) of
undefined -> ok;
{Tail, Log} -> put(elixir_code_diagnostics, {Head ++ Tail, Log})
end.
forms(Forms, File, Opts) ->
compile(Forms, File, Opts ++ compile:env_compiler_options()).
+1 -1
View File
@@ -55,7 +55,7 @@ normalize_rescue(Meta, Var, Pattern, Expr, ErlangAliases) ->
dynamic_normalize(Meta, Var, ?REQUIRES_STACKTRACE);
false ->
case lists:splitwith(fun is_normalized_with_stacktrace/1, ErlangAliases) of
case lists:partition(fun is_normalized_with_stacktrace/1, ErlangAliases) of
{[], _} -> [];
{_, []} -> {'__STACKTRACE__', Meta, nil};
{Some, _} -> dynamic_normalize(Meta, Var, Some)
-4
View File
@@ -856,10 +856,6 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, S, SL, #{context := Context}
_ ->
AttachedDotMeta = attach_context_module(Receiver, DotMeta, E),
is_atom(Receiver) andalso
elixir_env:trace({remote_function, Meta, Receiver, Right, length(Args)}, E),
{EArgs, {SA, _}, EA} = mapfold(fun expand_arg/3, {SL, S}, E, Args),
case rewrite(Context, Receiver, AttachedDotMeta, Right, Meta, EArgs, S) of
+3 -3
View File
@@ -118,11 +118,11 @@ format_error({function_conflict, {Receiver, {Name, Arity}}}) ->
format_error({unused_args, {Name, Arity}}) ->
io_lib:format("default values for the optional arguments in ~ts/~B are never used", [Name, Arity]);
format_error({unused_args, {Name, Arity}, 1}) ->
io_lib:format("the default value for the first optional argument in ~ts/~B is never used", [Name, Arity]);
format_error({unused_args, {Name, Arity}, Count}) when Arity - Count == 1 ->
io_lib:format("the default value for the last optional argument in ~ts/~B is never used", [Name, Arity]);
format_error({unused_args, {Name, Arity}, Count}) ->
io_lib:format("the default values for the first ~B optional arguments in ~ts/~B are never used", [Count, Name, Arity]);
io_lib:format("the default values for the last ~B optional arguments in ~ts/~B are never used", [Arity - Count, Name, Arity]);
format_error({unused_def, {Name, Arity}, defp}) ->
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
+8 -10
View File
@@ -508,19 +508,17 @@ beam_location(ModuleAsCharlist) ->
checker_info() ->
case get(elixir_checker_info) of
undefined -> undefined;
_ ->
Log =
case erlang:get(elixir_code_diagnostics) of
{_, false} -> false;
_ -> true
end,
{'Elixir.Module.ParallelChecker':get(), Log}
_ -> 'Elixir.Module.ParallelChecker':get()
end.
spawn_parallel_checker(undefined, _Module, _ModuleMap) ->
nil;
spawn_parallel_checker({CheckerInfo, Log}, Module, ModuleMap) ->
ok;
spawn_parallel_checker(CheckerInfo, Module, ModuleMap) ->
Log =
case erlang:get(elixir_code_diagnostics) of
{_, false} -> false;
_ -> true
end,
'Elixir.Module.ParallelChecker':spawn(CheckerInfo, Module, ModuleMap, Log).
make_module_available(Module, Binary) ->
@@ -275,6 +275,8 @@ defmodule Code.Formatter.ContainersTest do
assert_format "<< 1 :: 2 - unit(3) >>", "<<1::2-unit(3)>>"
assert_format "<< 1 :: 2 * 3 - unit(4) >>", "<<1::2*3-unit(4)>>"
assert_format "<< 1 :: 2 - unit(3) - 4 / 5 >>", "<<1::2-unit(3)-(4 / 5)>>"
assert_format "<<0 :: ( x - 1 ) * 5>>", "<<0::(x-1)*5>>"
assert_format "<<0 :: 2 * 3 * 4>>", "<<0::(2*3)*4>>"
end
test "in comprehensions" do
+36 -20
View File
@@ -738,6 +738,14 @@ defmodule CodeFragmentTest do
end: {3, 5}
}
end
for i <- 1..11 do
assert CF.surround_context("Foo.Bar.Baz.foo(bar)", {1, i}) == %{
context: {:alias, ~c"Foo.Bar.Baz"},
begin: {1, 1},
end: {1, 12}
}
end
end
test "underscored special forms" do
@@ -747,17 +755,21 @@ defmodule CodeFragmentTest do
end: {1, 11}
}
assert CF.surround_context("__MODULE__.Foo", {1, 12}) == %{
context: {:alias, {:local_or_var, ~c"__MODULE__"}, ~c"Foo"},
begin: {1, 1},
end: {1, 15}
}
for i <- 1..15 do
assert CF.surround_context("__MODULE__.Foo", {1, i}) == %{
context: {:alias, {:local_or_var, ~c"__MODULE__"}, ~c"Foo"},
begin: {1, 1},
end: {1, 15}
}
end
assert CF.surround_context("__MODULE__.Foo.Sub", {1, 16}) == %{
context: {:alias, {:local_or_var, ~c"__MODULE__"}, ~c"Foo.Sub"},
begin: {1, 1},
end: {1, 19}
}
for i <- 1..19 do
assert CF.surround_context("__MODULE__.Foo.Sub", {1, i}) == %{
context: {:alias, {:local_or_var, ~c"__MODULE__"}, ~c"Foo.Sub"},
begin: {1, 1},
end: {1, 19}
}
end
assert CF.surround_context("%__MODULE__{}", {1, 5}) == %{
context: {:struct, {:local_or_var, ~c"__MODULE__"}},
@@ -803,17 +815,21 @@ defmodule CodeFragmentTest do
end
test "attribute submodules" do
assert CF.surround_context("@some.Foo", {1, 8}) == %{
context: {:alias, {:module_attribute, ~c"some"}, ~c"Foo"},
begin: {1, 1},
end: {1, 10}
}
for i <- 1..10 do
assert CF.surround_context("@some.Foo", {1, i}) == %{
context: {:alias, {:module_attribute, ~c"some"}, ~c"Foo"},
begin: {1, 1},
end: {1, 10}
}
end
assert CF.surround_context("@some.Foo.Sub", {1, 12}) == %{
context: {:alias, {:module_attribute, ~c"some"}, ~c"Foo.Sub"},
begin: {1, 1},
end: {1, 14}
}
for i <- 1..14 do
assert CF.surround_context("@some.Foo.Sub", {1, i}) == %{
context: {:alias, {:module_attribute, ~c"some"}, ~c"Foo.Sub"},
begin: {1, 1},
end: {1, 14}
}
end
assert CF.surround_context("%@some{}", {1, 5}) == %{
context: {:struct, {:module_attribute, ~c"some"}},
@@ -226,6 +226,11 @@ defmodule Code.Normalizer.QuotedASTTest do
assert quoted_to_string(quoted) <> "\n" == expected
end
test "invalid block" do
assert quoted_to_string({:__block__, [], {:bar, [], []}}) ==
"{:__block__, [], {:bar, [], []}}"
end
test "not in" do
assert quoted_to_string(quote(do: false not in [])) == "false not in []"
end
+36 -1
View File
@@ -90,6 +90,28 @@ defmodule CodeTest do
Code.eval_quoted(quoted, [])
end)
end
test "captures unknown local calls" do
sample = """
defmodule CodeTest.UnknownLocalCall do
def perform do
foo()
end
end
"""
assert {:rescued, [%{message: message}]} =
Code.with_diagnostics(fn ->
try do
quoted = Code.string_to_quoted!(sample, columns: true)
Code.eval_quoted(quoted, [])
rescue
_ -> :rescued
end
end)
assert message =~ "undefined function foo/0"
end
end
describe "eval_string/1,2,3" do
@@ -325,7 +347,12 @@ defmodule CodeTest do
assert env.versioned_vars == %{}
assert_receive {:trace, {:on_module, _, _}, %{module: CodeTest.TracingPruning} = trace_env}
assert trace_env.versioned_vars == %{{:result, Kernel} => 5, {:x, nil} => 1, {:y, nil} => 4}
assert trace_env.versioned_vars == %{
{:result, :elixir_compiler} => 5,
{:x, nil} => 1,
{:y, nil} => 4
}
end
test "with defguard" do
@@ -474,6 +501,14 @@ defmodule CodeTest do
:code.purge(CompileCrossSample)
:code.delete(CompileCrossSample)
end
test "disables tail call optimization at the root" do
try do
Code.compile_string("List.flatten(123)")
rescue
_ -> assert Enum.any?(__STACKTRACE__, &match?({_, :__FILE__, 1, _}, &1))
end
end
end
test "format_string/2 returns empty iodata for empty string" do
@@ -224,6 +224,17 @@ defmodule Kernel.RaiseTest do
assert result == "an exception"
end
test "named function clause (stacktrace) or runtime (no stacktrace) error" do
result =
try do
Access.get("foo", 0)
rescue
x in [FunctionClauseError, CaseClauseError] -> Exception.message(x)
end
assert result == "no function clause matching in Access.get/3"
end
test "with higher precedence than catch" do
result =
try do
@@ -118,6 +118,7 @@ defmodule Kernel.TracersTest do
require Integer
true = Integer.is_odd(1)
{1, ""} = Integer.parse("1")
"foo" = Atom.to_string(:foo)
""")
assert_receive {{:remote_macro, meta, Integer, :is_odd, 1}, _}
@@ -127,6 +128,10 @@ defmodule Kernel.TracersTest do
assert_receive {{:remote_function, meta, Integer, :parse, 1}, _}
assert meta[:line] == 3
assert meta[:column] == 19
assert_receive {{:remote_function, meta, Atom, :to_string, 1}, _}
assert meta[:line] == 4
assert meta[:column] == 14
end
test "traces remote via captures" do
+6 -17
View File
@@ -563,17 +563,17 @@ defmodule Kernel.WarningTest do
end
""")
end) =~
"the default values for the first 2 optional arguments in b/3 are never used\n nofile:3"
"the default value for the last optional argument in b/3 is never used\n nofile:3"
assert capture_err(fn ->
Code.eval_string(~S"""
defmodule Sample3 do
def a, do: b(1)
defp b(arg1 \\ 1, arg2 \\ 2, arg3 \\ 3), do: [arg1, arg2, arg3]
def a, do: b(1, 2)
defp b(arg1, arg2 \\ 2, arg3 \\ 3, arg4 \\ 4), do: [arg1, arg2, arg3, arg4]
end
""")
end) =~
"the default value for the first optional argument in b/3 is never used\n nofile:3"
"the default values for the last 2 optional arguments in b/4 are never used"
assert capture_err(fn ->
Code.eval_string(~S"""
@@ -587,17 +587,6 @@ defmodule Kernel.WarningTest do
assert capture_err(fn ->
Code.eval_string(~S"""
defmodule Sample5 do
def a, do: b(1, 2, 3)
defp b(arg1 \\ 1, arg2 \\ 2, arg3 \\ 3)
defp b(arg1, arg2, arg3), do: [arg1, arg2, arg3]
end
""")
end) =~ "default values for the optional arguments in b/3 are never used\n nofile:3"
assert capture_err(fn ->
Code.eval_string(~S"""
defmodule Sample6 do
def a, do: b(1, 2)
defp b(arg1 \\ 1, arg2 \\ 2, arg3 \\ 3)
@@ -605,9 +594,9 @@ defmodule Kernel.WarningTest do
end
""")
end) =~
"the default values for the first 2 optional arguments in b/3 are never used\n nofile:3"
"the default value for the last optional argument in b/3 is never used\n nofile:3"
after
purge([Sample1, Sample2, Sample3, Sample4, Sample5, Sample6])
purge([Sample1, Sample2, Sample3, Sample4, Sample5])
end
test "unused import" do
+26
View File
@@ -277,6 +277,32 @@ defmodule URITest do
test "preserves an empty query" do
assert URI.new!("http://foo.com/?").query == ""
end
test "without scheme, undefined port after host translates to nil" do
assert URI.new!("//https://www.example.com") ==
%URI{
scheme: nil,
userinfo: nil,
host: "https",
port: nil,
path: "//www.example.com",
query: nil,
fragment: nil
}
end
test "with scheme, undefined port after host translates to nil" do
assert URI.new!("myscheme://myhost:/path/info") ==
%URI{
scheme: "myscheme",
userinfo: nil,
host: "myhost",
port: nil,
path: "/path/info",
query: nil,
fragment: nil
}
end
end
test "http://http://http://@http://http://?http://#http://" do
+1 -8
View File
@@ -549,14 +549,7 @@ defmodule ExUnit.Callbacks do
end
child_spec = Supervisor.child_spec(child_spec_or_module, opts)
case Supervisor.start_child(sup, child_spec) do
{:error, {:already_started, _pid}} ->
{:error, {:duplicate_child_name, child_spec.id}}
other ->
other
end
Supervisor.start_child(sup, child_spec)
end
@doc """
+1 -1
View File
@@ -130,7 +130,7 @@ defmodule ExUnit.CaseTemplate do
The second argument passed to `use MyCase` gets forwarded to `using/2` too:
defmodule SomeTestCase do
use MyCase, async: true, import_helpers: true, async: true
use MyCase, async: true, import_helpers: true
test "the truth" do
# truth/0 comes from MyApp.TestHelpers:
+5 -1
View File
@@ -200,11 +200,15 @@ defmodule ExUnit.Diff do
{diff, env}
else
diff_value(left, right, env)
non_recursive_diff_value(left, right, env)
end
end
defp diff_value(left, right, env) do
non_recursive_diff_value(left, right, env)
end
defp non_recursive_diff_value(left, right, env) do
diff_left = escape(left) |> update_diff_meta(true)
diff_right = escape(right) |> update_diff_meta(true)
diff = %__MODULE__{equivalent?: false, left: diff_left, right: diff_right}
+8 -2
View File
@@ -1121,6 +1121,8 @@ defmodule ExUnit.DiffTest do
)
end
@compile {:no_warn_undefined, String}
test "functions" do
identity = & &1
inspect = inspect(identity)
@@ -1130,11 +1132,15 @@ defmodule ExUnit.DiffTest do
refute_diff(identity == :a, "-#{inspect}-", "+:a+")
refute_diff({identity, identity} == :a, "-{#{inspect}, #{inspect}}", "+:a+")
refute_diff({identity, :a} == {:a, identity}, "{-#{inspect}-, -:a-}", "{+:a+, +#{inspect}+}")
refute_diff(%{identity => identity} == :a, "-%{#{inspect} => #{inspect}}", "+:a+")
refute_diff(
(&String.to_charlist/1) == (&String.unknown/1),
"-&String.to_charlist/1-",
"+&String.unknown/1"
)
refute_diff(
%Opaque{data: identity} == :a,
"-#Opaque<???>-",
+14 -17
View File
@@ -491,28 +491,25 @@ defmodule ExUnit.FormatterTest do
test "inspect failure" do
failure = [{:error, catch_assertion(assert :will_fail == %BadInspect{}), []}]
message = ~S'''
got FunctionClauseError with message:
"""
no function clause matching in Inspect.ExUnit.FormatterTest.BadInspect.inspect/2
"""
while inspecting:
%{__struct__: ExUnit.FormatterTest.BadInspect, key: 0}
Stacktrace:
'''
assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """
assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ ~s'''
1) world (Hello)
test/ex_unit/formatter_test.exs:1
Assertion with == failed
code: assert :will_fail == %BadInspect{}
left: :will_fail
right: #Inspect.Error<\n#{message}\
"""
right: #Inspect.Error<
got FunctionClauseError with message:
"""
no function clause matching in Inspect.ExUnit.FormatterTest.BadInspect.inspect/2
"""
while inspecting:
#{inspect(%BadInspect{}, structs: false)}
Stacktrace:
'''
end
defmodule BadMessage do
+3 -8
View File
@@ -73,19 +73,14 @@ defmodule ExUnit.SupervisedTest do
test "starts a supervised process with ID checks" do
{:ok, pid} = start_supervised({MyAgent, 0})
assert is_pid(pid)
assert {:error, {:duplicate_child_name, ExUnit.SupervisedTest.MyAgent}} =
start_supervised({MyAgent, 0})
assert {:error, {{:already_started, ^pid}, _}} = start_supervised({MyAgent, 0}, id: :another)
assert {:error, _} = start_supervised({MyAgent, 0})
assert {:error, _} = start_supervised({MyAgent, 0}, id: :another)
assert_raise RuntimeError, ~r"Reason: bad child specification", fn ->
start_supervised!(%{id: 1, start: :oops})
end
assert_raise RuntimeError, ~r"Reason: already started", fn ->
start_supervised!({MyAgent, 0}, id: :another)
end
end
test "stops a supervised process" do
+17 -9
View File
@@ -582,9 +582,6 @@ defmodule IEx do
@doc """
Pries into the process environment.
When you start `iex`, IEx will set this function to be the
default `dbg/2` backend unless the `--no-pry` flag is given.
This function is useful for debugging a particular chunk of code
when executed by a particular process. The process becomes
the evaluator of IEx commands and is temporarily changed to
@@ -600,6 +597,11 @@ defmodule IEx do
See also `break!/4` for others ways to pry.
> #### `dbg/0` integration
>
> By calling `iex --dbg pry`, `iex` will set this function
> as the default backend for `dbg/0` calls.
## Examples
Let's suppose you want to investigate what is happening
@@ -869,6 +871,7 @@ defmodule IEx do
if Code.ensure_loaded?(:prim_tty) do
spawn(fn ->
{:ok, _} = Application.ensure_all_started(:iex)
:ok = :io.setopts(binary: true, encoding: :unicode)
_ = for fun <- Enum.reverse(after_spawn()), do: fun.()
IEx.Server.run([register: false] ++ opts)
end)
@@ -928,13 +931,18 @@ defmodule IEx do
mfa
end
:ok = :shell.start_interactive(shell)
case :shell.start_interactive(shell) do
:ok ->
receive do
{^ref, shell} -> shell
after
15_000 ->
IO.puts(:stderr, "Could not start IEx CLI due to reason: :boot_timeout")
System.halt(1)
end
receive do
{^ref, shell} -> shell
after
15_000 ->
IO.puts(:stderr, "Could not start the shell after 15 seconds, aborting...")
{:error, reason} ->
IO.puts(:stderr, "Could not start IEx CLI due to reason: #{inspect(reason)}")
System.halt(1)
end
end
+14 -10
View File
@@ -146,7 +146,7 @@ defmodule IEx.Autocomplete do
@doc false
def exports(mod) do
if Code.ensure_loaded?(mod) and function_exported?(mod, :__info__, 1) do
if ensure_loaded?(mod) and function_exported?(mod, :__info__, 1) do
mod.__info__(:macros) ++ (mod.__info__(:functions) -- [__info__: 1])
else
mod.module_info(:exports) -- [module_info: 0, module_info: 1]
@@ -313,21 +313,23 @@ defmodule IEx.Autocomplete do
for {alias, mod} <- aliases_from_env(shell),
[name] = Module.split(alias),
String.starts_with?(name, hint),
struct?(mod) and not function_exported?(mod, :exception, 1),
do: %{kind: :struct, name: name}
do: {mod, name}
modules =
for "Elixir." <> name = full_name <- match_modules("Elixir." <> hint, true),
String.starts_with?(name, hint),
mod = String.to_atom(full_name),
struct?(mod) and not function_exported?(mod, :exception, 1),
do: {mod, name}
all = aliases ++ modules
Code.ensure_all_loaded(Enum.map(all, &elem(&1, 0)))
refs =
for {mod, name} <- all,
function_exported?(mod, :__struct__, 1) and not function_exported?(mod, :exception, 1),
do: %{kind: :struct, name: name}
format_expansion(aliases ++ modules, hint)
end
defp struct?(mod) do
Code.ensure_loaded?(mod) and function_exported?(mod, :__struct__, 1)
format_expansion(refs, hint)
end
defp expand_container_context(code, context, hint, shell) do
@@ -420,7 +422,9 @@ defmodule IEx.Autocomplete do
defp container_context_struct(cursor, pairs, aliases, shell) do
with {pairs, [^cursor]} <- Enum.split(pairs, -1),
alias = value_from_alias(aliases, shell),
true <- Keyword.keyword?(pairs) and struct?(alias) do
true <-
Keyword.keyword?(pairs) and ensure_loaded?(alias) and
function_exported?(alias, :__struct__, 1) do
{:struct, alias, pairs}
else
_ -> nil
+7 -3
View File
@@ -85,9 +85,13 @@ defmodule IEx.Broker do
yes?(IO.gets(:stdio, interrupt))
end
defp yes?(string) do
is_binary(string) and String.trim(string) in ["", "y", "Y", "yes", "YES", "Yes"]
end
defp yes?(string) when is_binary(string),
do: String.trim(string) in ["", "y", "Y", "yes", "YES", "Yes"]
defp yes?(charlist) when is_list(charlist),
do: yes?(List.to_string(charlist))
defp yes?(_), do: false
@doc """
Client requests a takeover.
+1 -2
View File
@@ -89,8 +89,7 @@ defmodule IEx.CLI do
spawn_link(fn ->
receive do
{:begin, ^ref, other} ->
{:ok, _} = Application.ensure_all_started(:elixir)
System.wait_until_booted()
:elixir.start_cli()
send(other, {:done, ref})
end
end)
+1
View File
@@ -293,6 +293,7 @@ defmodule IEx.Introspection do
module.module_info(:exports)
end
|> Enum.sort()
|> Enum.dedup()
result =
for {^function, arity} <- exports,
+4 -1
View File
@@ -831,9 +831,12 @@ defmodule Logger do
defp update_translators(updater) do
:elixir_config.serial(fn ->
translators = updater.(Application.fetch_env!(:logger, :translators))
Application.put_env(:logger, :translators, translators)
with %{filters: filters} <- :logger.get_primary_config(),
{{_, {fun, config}}, filters} <- List.keytake(filters, :logger_translator, 0) do
config = update_in(config.translators, updater)
config = %{config | translators: translators}
:ok = :logger.set_primary_config(:filters, filters ++ [logger_translator: {fun, config}])
end
end)
@@ -58,7 +58,9 @@ defmodule Logger.Backends.HandlerTest do
end
test "add_translator/1 and remove_translator/1 for logger formats" do
refute {CustomTranslator, :t} in Application.fetch_env!(:logger, :translators)
assert Logger.add_translator({CustomTranslator, :t})
assert {CustomTranslator, :t} in Application.fetch_env!(:logger, :translators)
assert capture_log(fn ->
:logger.info(~c"hello: ~p", [:ok])
+20 -11
View File
@@ -600,26 +600,29 @@ defmodule Mix do
"""
@doc since: "1.15.0"
def ensure_application!(app) when is_atom(app) do
ensure_application!(app, Mix.State.builtin_apps())
ensure_application!(app, Mix.State.builtin_apps(), [])
:ok
end
defp ensure_application!(app, builtin_apps) do
defp ensure_application!(app, builtin_apps, optional) do
case builtin_apps do
%{^app => path} ->
Code.prepend_path(path, cache: true)
Application.load(app)
optional = List.wrap(Application.spec(app, :optional_applications))
Application.spec(app, :applications)
|> List.wrap()
|> Enum.each(&ensure_application!(&1, builtin_apps))
|> Enum.each(&ensure_application!(&1, builtin_apps, optional))
%{} ->
Mix.raise(
"The application \"#{app}\" could not be found. This may happen if your " <>
"Operating System broke Erlang into multiple packages and may be fixed " <>
"by installing the missing \"erlang-dev\" and \"erlang-#{app}\" packages"
)
unless app in optional do
Mix.raise(
"The application \"#{app}\" could not be found. This may happen if your " <>
"Operating System broke Erlang into multiple packages and may be fixed " <>
"by installing the missing \"erlang-dev\" and \"erlang-#{app}\" packages"
)
end
end
end
@@ -674,6 +677,9 @@ defmodule Mix do
* `:lockfile` (since v1.14.0) - path to a lockfile to be used as a basis of
dependency resolution.
* `:start_applications` (since v1.15.3) - if `true`, ensures that installed app
and its dependencies are started after install (Default: `true`)
## Examples
Installing `:decimal` and `:jason`:
@@ -794,6 +800,7 @@ defmodule Mix do
config_path = expand_path(opts[:config_path], deps, :config_path, "config/config.exs")
system_env = Keyword.get(opts, :system_env, [])
consolidate_protocols? = Keyword.get(opts, :consolidate_protocols, true)
start_applications? = Keyword.get(opts, :start_applications, true)
id =
{deps, config, system_env, consolidate_protocols?}
@@ -893,9 +900,11 @@ defmodule Mix do
end
end)
for %{app: app, opts: opts} <- Mix.Dep.cached(),
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true) do
Application.ensure_all_started(app)
if start_applications? do
for %{app: app, opts: opts} <- Mix.Dep.cached(),
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true) do
Application.ensure_all_started(app)
end
end
Mix.State.put(:installed, id)
+59 -55
View File
@@ -1,7 +1,7 @@
defmodule Mix.Compilers.Elixir do
@moduledoc false
@manifest_vsn 19
@manifest_vsn 20
@checkpoint_vsn 2
import Record
@@ -46,6 +46,11 @@ defmodule Mix.Compilers.Elixir do
{all_modules, all_sources, all_local_exports, old_parents, old_cache_key, old_deps_config} =
parse_manifest(manifest, dest)
# Prepend ourselves early because of __mix_recompile__? checks
# and also that, in case of nothing compiled, we already need
# ourselves available in the path.
Code.prepend_path(dest)
# If modules have been added or removed from the Erlang compiler,
# we need to recompile all references to old and new modules.
stale =
@@ -205,10 +210,6 @@ defmodule Mix.Compilers.Elixir do
Code.purge_compiler_modules()
end
else
# Prepend ourselves, even if we did no work, as we may have defined
# future compilers.
Code.prepend_path(dest)
# We need to return ok if deps_changed? or stale_modules changed,
# even if no code was compiled, because we need to propagate the changed
# status to compile.protocols. This will be the case whenever:
@@ -453,6 +454,9 @@ defmodule Mix.Compilers.Elixir do
8 -> :crypto.hash(:blake2b, contents)
_ -> :crypto.hash(:blake2s, contents)
end
rescue
# Blake may not be available on all OpenSSL distribution
_ -> :erlang.md5(contents)
end
defp set_compiler_opts(opts) do
@@ -565,72 +569,72 @@ defmodule Mix.Compilers.Elixir do
Enum.any?(enumerable, &Map.has_key?(map, &1))
end
defp stale_local_deps(local_deps, manifest, stale_modules, modified, old_exports) do
defp stale_local_deps(local_deps, manifest, stale_modules, modified, deps_exports) do
base = Path.basename(manifest)
# The stale modules so far will become both stale_modules and stale_exports,
# as any export from a dependency needs to be recompiled.
stale_modules = Map.from_keys(stale_modules, true)
{stale_modules, stale_exports, new_exports} =
for %{opts: opts} <- local_deps,
manifest = Path.join([opts[:build], ".mix", base]),
Mix.Utils.last_modified(manifest) > modified,
reduce: {stale_modules, stale_modules, []} do
{modules, exports, new_exports} ->
{manifest_modules, manifest_sources} = read_manifest(manifest)
for %{app: app, opts: opts} <- local_deps,
manifest = Path.join([opts[:build], ".mix", base]),
Mix.Utils.last_modified(manifest) > modified,
reduce: {stale_modules, stale_modules, deps_exports} do
{modules, exports, deps_exports} ->
{manifest_modules, manifest_sources} = read_manifest(manifest)
dep_modules =
for module(module: module, timestamp: timestamp) <- manifest_modules,
timestamp > modified,
do: module
dep_modules =
for module(module: module, timestamp: timestamp) <- manifest_modules,
timestamp > modified,
do: module
# If any module has a compile time dependency on a changed module
# within the dependency, they will be recompiled. However, export
# and runtime dependencies won't have recompiled so we need to
# propagate them to the parent app.
{dep_modules, _, _} =
fixpoint_runtime_modules(manifest_sources, Map.from_keys(dep_modules, true))
# If any module has a compile time dependency on a changed module
# within the dependency, they will be recompiled. However, export
# and runtime dependencies won't have recompiled so we need to
# propagate them to the parent app.
{dep_modules, _, _} =
fixpoint_runtime_modules(manifest_sources, Map.from_keys(dep_modules, true))
# Update exports
{exports, new_exports} =
for {module, _} <- dep_modules, reduce: {exports, new_exports} do
{exports, new_exports} ->
export = exports_md5(module, false)
old_exports = Map.get(deps_exports, app, %{})
# If the exports are the same, then the API did not change,
# so we do not mark the export as stale. Note this has to
# be very conservative. If the module is not loaded or if
# the exports were not there, we need to consider it a stale
# export.
exports =
if export && old_exports[module] == export,
do: exports,
else: Map.put(exports, module, true)
# Update exports
{exports, new_exports} =
for {module, _} <- dep_modules, reduce: {exports, []} do
{exports, new_exports} ->
export = exports_md5(module, false)
# Then we store the new export if any
new_exports =
if export,
do: [{module, export} | new_exports],
else: new_exports
# If the exports are the same, then the API did not change,
# so we do not mark the export as stale. Note this has to
# be very conservative. If the module is not loaded or if
# the exports were not there, we need to consider it a stale
# export.
exports =
if export && old_exports[module] == export,
do: exports,
else: Map.put(exports, module, true)
{exports, new_exports}
end
# Then we store the new export if any
new_exports =
if export,
do: [{module, export} | new_exports],
else: new_exports
{Map.merge(modules, dep_modules), exports, new_exports}
end
{exports, new_exports}
end
# Any dependency in old export but not in new export
# was removed so we need to mark them as stale too.
new_exports = Map.new(new_exports)
new_exports = Map.new(new_exports)
removed =
for {module, _} <- old_exports,
not is_map_key(new_exports, module),
do: {module, true},
into: %{}
removed =
for {module, _} <- old_exports,
not is_map_key(new_exports, module),
do: {module, true},
into: %{}
{Map.merge(stale_modules, removed), Map.merge(stale_exports, removed), new_exports}
modules = modules |> Map.merge(dep_modules) |> Map.merge(removed)
exports = Map.merge(exports, removed)
deps_exports = Map.put(deps_exports, app, new_exports)
{modules, exports, deps_exports}
end
end
defp fixpoint_runtime_modules(sources, modules) when modules != %{} do
+23 -21
View File
@@ -114,12 +114,12 @@ defmodule Mix.Dep do
write_cached_deps(top, {env, target}, load_and_cache(config, top, bottom, env, target))
_ ->
converge(env: env, target: target)
converge_and_load(env: env, target: target)
end
end
defp load_and_cache(_config, top, top, env, target) do
converge(env: env, target: target)
converge_and_load(env: env, target: target)
end
defp load_and_cache(config, _top, bottom, _env, _target) do
@@ -152,6 +152,20 @@ defmodule Mix.Dep do
end)
end
defp converge_and_load(opts) do
for %{app: app, opts: opts} = dep <- Mix.Dep.Converger.converge(opts) do
case Keyword.pop(opts, :app_properties) do
{nil, _opts} ->
dep
{app_properties, opts} ->
# We don't raise because child dependencies may be missing if manually cleaned
:application.load({:application, app, app_properties})
%{dep | opts: opts}
end
end
end
defp read_cached_deps(project, env_target) do
case Mix.State.read_cache({:cached_deps, project}) do
{^env_target, deps} -> deps
@@ -164,6 +178,13 @@ defmodule Mix.Dep do
deps
end
@doc """
Although private API, this is kept to avoid breaking code on patch releases.
"""
def load_on_environment(opts) do
Mix.Dep.Converger.converge(opts)
end
@doc """
Clears loaded dependencies from the cache for the current environment.
"""
@@ -174,25 +195,6 @@ defmodule Mix.Dep do
end
end
@doc """
Returns loaded dependencies recursively on the given environment.
If no environment is passed, dependencies are loaded across all
environments. The result is not cached.
## Exceptions
This function raises an exception if any of the dependencies
provided in the project are in the wrong format.
"""
def load_on_environment(opts) do
converge(opts)
end
defp converge(opts) do
Mix.Dep.Converger.converge(nil, nil, opts, &{&1, &2, &3}) |> elem(0)
end
@doc """
Filters the given dependencies by name.
+16 -24
View File
@@ -60,6 +60,18 @@ defmodule Mix.Dep.Converger do
)
end
@doc """
Converge without lock and accumulator updates.
Note the dependencies returned from converge are not yet loaded.
The relevant app keys are found under `dep.opts[:app_properties]`.
See `Mix.Dep.Loader.children/1` for options.
"""
def converge(opts \\ []) do
converge(nil, nil, opts, &{&1, &2, &3}) |> elem(0)
end
@doc """
Converges all dependencies from the current project,
including nested dependencies.
@@ -68,35 +80,15 @@ defmodule Mix.Dep.Converger do
must return an updated dependency in case some processing
is done.
Note the dependencies returned from converge are not yet loaded.
The relevant app keys are found under `dep.opts[:app_properties]`.
See `Mix.Dep.Loader.children/1` for options.
"""
def converge(acc, lock, opts, callback) do
{deps, acc, lock} = all(acc, lock, opts, callback)
if remote = Mix.RemoteConverger.get(), do: remote.post_converge()
deps =
for %{app: app, opts: opts} = dep <- topological_sort(deps) do
case Keyword.pop(opts, :app_properties) do
{nil, _opts} ->
dep
{app_properties, opts} ->
case :application.load({:application, app, app_properties}) do
:ok ->
:ok
{:error, {:already_loaded, _}} ->
:ok
{:error, error} ->
Mix.raise("Could not load application #{inspect(app)}: #{inspect(error)}")
end
%{dep | opts: opts}
end
end
{deps, acc, lock}
{topological_sort(deps), acc, lock}
end
defp all(acc, lock, opts, callback) do
+2 -2
View File
@@ -67,7 +67,7 @@ defmodule Mix.Release do
@default_apps [kernel: :permanent, stdlib: :permanent, elixir: :permanent, sasl: :permanent]
@safe_modes [:permanent, :temporary, :transient]
@unsafe_modes [:load, :none]
@significant_chunks ~w(Atom AtU8 Attr Code StrT ImpT ExpT FunT LitT Line)c
@additional_chunks ~w(Attr)c
@copy_app_dirs ["priv"]
@doc false
@@ -879,7 +879,7 @@ defmodule Mix.Release do
@spec strip_beam(binary(), keyword()) :: {:ok, binary()} | {:error, :beam_lib, term()}
def strip_beam(binary, options \\ []) when is_list(options) do
chunks_to_keep = options[:keep] |> List.wrap() |> Enum.map(&String.to_charlist/1)
all_chunks = Enum.uniq(@significant_chunks ++ chunks_to_keep)
all_chunks = Enum.uniq(@additional_chunks ++ :beam_lib.significant_chunks() ++ chunks_to_keep)
compress? = Keyword.get(options, :compress, false)
case :beam_lib.chunks(binary, all_chunks, [:allow_missing_chunks]) do
+14 -6
View File
@@ -58,14 +58,22 @@ defmodule Mix.Task do
@requirements ["app.config"]
Tasks typically depend on the `"app.config"` task, when they
need to access code from the current project with all apps
already configured, or the "app.start" task, when they also
need those apps to be already started:
A task will typically depend on one of the following tasks:
@requirements ["app.start"]
* "loadpaths" - this ensures dependencies are available
and compiled. If you are publishing a task as part of
a library to be used by others, and your task does not
need to interact with the user code in any way, this is
the recommended requirement
You can also run tasks directly with `run/2`.
* "app.config" - additionally compiles and loads the runtime
configuration for the current project. If you are creating
a task to be used within your application or as part of a
library, which must invoke or interact with the user code,
this is the minimum recommended requirement
* "app.start" - additionally starts the supervision tree of
the current project and its dependencies
Finally, set `@recursive true` if you want the task to run
on each umbrella child in an umbrella project.
+1 -1
View File
@@ -61,7 +61,7 @@ defmodule Mix.Tasks.Archive.Build do
project = Mix.Project.get()
if project && Keyword.get(opts, :compile, true) do
Mix.Task.run(:compile, args)
Mix.Task.run(:compile, ["--no-protocol-consolidation" | args])
end
source =
+6 -8
View File
@@ -43,7 +43,11 @@ defmodule Mix.Tasks.Compile.All do
Code.delete_paths(current_paths -- loaded_paths)
end
Code.prepend_paths(loaded_paths -- current_paths, cache: true)
# Add the current compilation path. compile.elixir and compile.erlang
# will also add this path, but only if they run, so we always add it
# here too. Furthermore, we don't cache it as we may still write to it.
compile_path = to_charlist(Mix.Project.compile_path())
Code.prepend_paths([compile_path | loaded_paths -- current_paths], cache: true)
result =
if "--no-compile" in args do
@@ -64,12 +68,6 @@ defmodule Mix.Tasks.Compile.All do
Mix.AppLoader.write_cache(app_cache, Map.new(loaded_modules))
end
# Add the current compilation path. compile.elixir and compile.erlang
# will also add this path, but only if they run, so we always add it
# here too. Furthermore, we don't cache it as we may still write to it.
compile_path = to_charlist(Mix.Project.compile_path())
_ = Code.prepend_path(compile_path)
unless "--no-app-loading" in args do
app = config[:app]
@@ -125,7 +123,7 @@ defmodule Mix.Tasks.Compile.All do
Enum.reduce(Mix.ProjectStack.pop_after_compiler(compiler), result, & &1.(&2))
end
defp project_apps(config) do
def project_apps(config) do
project = Mix.Project.get!()
properties =
+1 -2
View File
@@ -148,8 +148,7 @@ defmodule Mix.Tasks.Compile do
# If we are in an umbrella project, now load paths from all children.
if apps_paths = Mix.Project.apps_paths(config) do
loaded_paths =
apps_paths
|> Map.keys()
(Mix.Tasks.Compile.All.project_apps(config) ++ Map.keys(apps_paths))
|> Mix.AppLoader.load_apps(Mix.Dep.cached(), config, [], fn
{_app, path}, acc -> if path, do: [path | acc], else: acc
end)
+1 -1
View File
@@ -41,7 +41,7 @@ defmodule Mix.Tasks.Deps.Clean do
value = opts[switch],
do: {key, :"#{value}"}
loaded_deps = Mix.Dep.load_on_environment(loaded_opts)
loaded_deps = Mix.Dep.Converger.converge(loaded_opts)
apps_to_clean =
cond do
-3
View File
@@ -57,9 +57,6 @@ defmodule Mix.Tasks.Deps.Compile do
case OptionParser.parse(args, switches: @switches) do
{opts, [], _} ->
# Because this command may be invoked explicitly with
# deps.compile, we simply try to compile any available
# or local dependency.
compile(filter_available_and_local_deps(deps), opts)
{opts, tail, _} ->
+2 -2
View File
@@ -1,7 +1,7 @@
defmodule Mix.Tasks.Deps do
use Mix.Task
import Mix.Dep, only: [load_on_environment: 1, format_dep: 1, format_status: 1, check_lock: 1]
import Mix.Dep, only: [format_dep: 1, format_status: 1, check_lock: 1]
@shortdoc "Lists dependencies and their status"
@@ -167,7 +167,7 @@ defmodule Mix.Tasks.Deps do
shell = Mix.shell()
load_on_environment(loaded_opts)
Mix.Dep.Converger.converge(loaded_opts)
|> Enum.sort_by(& &1.app)
|> Enum.each(fn dep ->
%Mix.Dep{scm: scm, manager: manager} = dep
-1
View File
@@ -17,7 +17,6 @@ defmodule Mix.Tasks.Deps.Loadpaths do
* `--no-archives-check` - does not check archives
* `--no-compile` - does not compile even if files require compilation
* `--no-deps-check` - does not check or compile deps, only load available ones
* `--no-deps-loading` - does not add deps loadpaths to the code path
* `--no-elixir-version-check` - does not check Elixir version
* `--no-optional-deps` - does not compile or load optional deps
+1 -1
View File
@@ -44,7 +44,7 @@ defmodule Mix.Tasks.Deps.Tree do
value = opts[switch],
do: {key, :"#{value}"}
deps = Mix.Dep.load_on_environment(deps_opts)
deps = Mix.Dep.Converger.converge(deps_opts)
root =
case args do
+1 -1
View File
@@ -83,7 +83,7 @@ defmodule Mix.Tasks.Deps.Unlock do
end
defp unused_apps(lock) do
apps = Mix.Dep.load_on_environment([]) |> Enum.map(& &1.app)
apps = Mix.Dep.Converger.converge([]) |> Enum.map(& &1.app)
lock
|> Map.drop(apps)
+78 -65
View File
@@ -233,6 +233,8 @@ defmodule Mix.Tasks.Format do
{formatter_opts_and_subs, _sources} =
eval_deps_and_subdirectories(cwd, dot_formatter, formatter_opts, [dot_formatter])
formatter_opts_and_subs = load_plugins(formatter_opts_and_subs)
args
|> expand_args(cwd, dot_formatter, formatter_opts_and_subs, opts)
|> Task.async_stream(&format_file(&1, opts), ordered: false, timeout: :infinity)
@@ -240,67 +242,11 @@ defmodule Mix.Tasks.Format do
|> check!(opts)
end
@doc """
Returns a formatter function and the formatter options to
be used for the given file.
The function must be called with the contents of the file
to be formatted. The options are returned for reflection
purposes.
"""
@doc since: "1.13.0"
def formatter_for_file(file, opts \\ []) do
cwd = Keyword.get_lazy(opts, :root, &File.cwd!/0)
{dot_formatter, formatter_opts} = eval_dot_formatter(cwd, opts)
{formatter_opts_and_subs, _sources} =
eval_deps_and_subdirectories(cwd, dot_formatter, formatter_opts, [dot_formatter])
find_formatter_and_opts_for_file(Path.expand(file, cwd), formatter_opts_and_subs)
end
@doc """
Returns formatter options to be used for the given file.
"""
# TODO: Deprecate on Elixir v1.17
@doc deprecated: "Use formatter_for_file/2 instead"
def formatter_opts_for_file(file, opts \\ []) do
{_, formatter_opts} = formatter_for_file(file, opts)
formatter_opts
end
defp eval_dot_formatter(cwd, opts) do
cond do
dot_formatter = opts[:dot_formatter] ->
{dot_formatter, eval_file_with_keyword_list(dot_formatter)}
File.regular?(Path.join(cwd, ".formatter.exs")) ->
dot_formatter = Path.join(cwd, ".formatter.exs")
{".formatter.exs", eval_file_with_keyword_list(dot_formatter)}
true ->
{".formatter.exs", []}
end
end
# This function reads exported configuration from the imported
# dependencies and subdirectories and deals with caching the result
# of reading such configuration in a manifest file.
defp eval_deps_and_subdirectories(cwd, dot_formatter, formatter_opts, sources) do
deps = Keyword.get(formatter_opts, :import_deps, [])
subs = Keyword.get(formatter_opts, :subdirectories, [])
defp load_plugins({formatter_opts, subs}) do
plugins = Keyword.get(formatter_opts, :plugins, [])
if not is_list(deps) do
Mix.raise("Expected :import_deps to return a list of dependencies, got: #{inspect(deps)}")
end
if not is_list(subs) do
Mix.raise("Expected :subdirectories to return a list of directories, got: #{inspect(subs)}")
end
if not is_list(plugins) do
Mix.raise("Expected :plugins to return a list of modules, got: #{inspect(plugins)}")
Mix.raise("Expected :plugins to return a list of directories, got: #{inspect(plugins)}")
end
if plugins != [] do
@@ -341,10 +287,69 @@ defmodule Mix.Tasks.Format do
end}
end)
formatter_opts =
formatter_opts
|> Keyword.put(:plugins, plugins)
|> Keyword.put(:sigils, sigils)
{Keyword.put(formatter_opts, :sigils, sigils),
Enum.map(subs, fn {path, opts} -> {path, load_plugins(opts)} end)}
end
@doc """
Returns a formatter function and the formatter options to
be used for the given file.
The function must be called with the contents of the file
to be formatted. The options are returned for reflection
purposes.
"""
@doc since: "1.13.0"
def formatter_for_file(file, opts \\ []) do
cwd = Keyword.get_lazy(opts, :root, &File.cwd!/0)
{dot_formatter, formatter_opts} = eval_dot_formatter(cwd, opts)
{formatter_opts_and_subs, _sources} =
eval_deps_and_subdirectories(cwd, dot_formatter, formatter_opts, [dot_formatter])
formatter_opts_and_subs = load_plugins(formatter_opts_and_subs)
find_formatter_and_opts_for_file(Path.expand(file, cwd), formatter_opts_and_subs)
end
@doc """
Returns formatter options to be used for the given file.
"""
# TODO: Deprecate on Elixir v1.17
@doc deprecated: "Use formatter_for_file/2 instead"
def formatter_opts_for_file(file, opts \\ []) do
{_, formatter_opts} = formatter_for_file(file, opts)
formatter_opts
end
defp eval_dot_formatter(cwd, opts) do
cond do
dot_formatter = opts[:dot_formatter] ->
{dot_formatter, eval_file_with_keyword_list(dot_formatter)}
File.regular?(Path.join(cwd, ".formatter.exs")) ->
dot_formatter = Path.join(cwd, ".formatter.exs")
{".formatter.exs", eval_file_with_keyword_list(dot_formatter)}
true ->
{".formatter.exs", []}
end
end
# This function reads exported configuration from the imported
# dependencies and subdirectories and deals with caching the result
# of reading such configuration in a manifest file.
defp eval_deps_and_subdirectories(cwd, dot_formatter, formatter_opts, sources) do
deps = Keyword.get(formatter_opts, :import_deps, [])
subs = Keyword.get(formatter_opts, :subdirectories, [])
if not is_list(deps) do
Mix.raise("Expected :import_deps to return a list of dependencies, got: #{inspect(deps)}")
end
if not is_list(subs) do
Mix.raise("Expected :subdirectories to return a list of directories, got: #{inspect(subs)}")
end
if deps == [] and subs == [] do
{{formatter_opts, []}, sources}
@@ -448,7 +453,8 @@ defmodule Mix.Tasks.Format do
_ ->
Mix.raise(
"Unknown dependency #{inspect(dep)} given to :import_deps in the formatter configuration. " <>
"Make sure the dependency is listed in your mix.exs and you have run \"mix deps.get\""
"Make sure the dependency is listed in your mix.exs for environment #{inspect(Mix.env())} " <>
"and you have run \"mix deps.get\""
)
end
end
@@ -578,8 +584,15 @@ defmodule Mix.Tasks.Format do
defp recur_formatter_opts_for_file(file, {formatter_opts, subs}) do
Enum.find_value(subs, formatter_opts, fn {sub, formatter_opts_and_subs} ->
if String.starts_with?(file, sub) do
recur_formatter_opts_for_file(file, formatter_opts_and_subs)
size = byte_size(sub)
case file do
<<prefix::binary-size(size), dir_separator, _::binary>>
when prefix == sub and dir_separator in [?\\, ?/] ->
recur_formatter_opts_for_file(file, formatter_opts_and_subs)
_ ->
nil
end
end)
end
-1
View File
@@ -20,7 +20,6 @@ defmodule Mix.Tasks.Loadpaths do
* `--no-archives-check` - does not check archives
* `--no-compile` - does not compile dependencies, only check and load them
* `--no-deps-check` - does not check dependencies, only load available ones
* `--no-deps-loading` - does not add deps loadpaths to the code path
* `--no-elixir-version-check` - does not check Elixir version
* `--no-optional-deps` - does not compile or load optional deps
@@ -4,4 +4,8 @@ defmodule Umbrella.MixProject do
def project do
[apps_path: "apps"]
end
def application do
[extra_applications: [:runtime_tools]]
end
end
+58 -58
View File
@@ -36,7 +36,7 @@ defmodule Mix.DepTest do
defp assert_wrong_dependency(deps) do
with_deps(deps, fn ->
assert_raise Mix.Error, ~r"Dependency specified in the wrong format", fn ->
Mix.Dep.load_on_environment([])
Mix.Dep.Converger.converge([])
end
end)
end
@@ -59,7 +59,7 @@ defmodule Mix.DepTest do
in_fixture("deps_status", fn ->
Mix.Project.push(DepsApp)
deps = Mix.Dep.load_on_environment([])
deps = Mix.Dep.Converger.converge([])
assert length(deps) == 6
assert Enum.find(deps, &match?(%Mix.Dep{app: :ok, status: {:ok, _}}, &1))
assert Enum.find(deps, &match?(%Mix.Dep{app: :invalidvsn, status: {:invalidvsn, :ok}}, &1))
@@ -102,7 +102,7 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
deps = Mix.Dep.load_on_environment([])
deps = Mix.Dep.Converger.converge([])
assert Enum.find(deps, &match?(%Mix.Dep{app: :ok, status: {:ok, _}}, &1))
end)
end)
@@ -115,7 +115,7 @@ defmodule Mix.DepTest do
in_fixture("deps_status", fn ->
send(self(), {:mix_shell_input, :yes?, false})
msg = "Could not find an SCM for dependency :ok from Mix.DepTest.ProcessDepsApp"
assert_raise Mix.Error, msg, fn -> Mix.Dep.load_on_environment([]) end
assert_raise Mix.Error, msg, fn -> Mix.Dep.Converger.converge([]) end
end)
end)
end
@@ -139,7 +139,7 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
assert_raise Mix.Error, ~r"Invalid requirement", fn ->
Mix.Dep.load_on_environment([])
Mix.Dep.Converger.converge([])
end
end)
end)
@@ -150,7 +150,7 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == [:git_repo, :deps_repo]
assert Enum.map(Mix.Dep.Converger.converge([]), & &1.app) == [:git_repo, :deps_repo]
end)
end)
end
@@ -172,7 +172,7 @@ defmodule Mix.DepTest do
end
""")
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == [:deps_repo]
assert Enum.map(Mix.Dep.Converger.converge([]), & &1.app) == [:deps_repo]
end)
end)
end
@@ -185,7 +185,7 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == [:git_repo, :deps_repo]
assert Enum.map(Mix.Dep.Converger.converge([]), & &1.app) == [:git_repo, :deps_repo]
end)
end)
end
@@ -200,7 +200,7 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
[dep1, dep2] = Mix.Dep.load_on_environment([])
[dep1, dep2] = Mix.Dep.Converger.converge([])
assert dep1.manager == nil
assert dep2.manager == :rebar3
end)
@@ -252,7 +252,7 @@ defmodule Mix.DepTest do
end
""")
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == [:git_repo, :deps_repo]
assert Enum.map(Mix.Dep.Converger.converge([]), & &1.app) == [:git_repo, :deps_repo]
end)
end)
end
@@ -362,7 +362,7 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_tmp("load dependency with env vars", fn ->
Mix.Dep.load_on_environment([])
Mix.Dep.Converger.converge([])
assert {:ok, "contents dep test"} = File.read(file_path)
end)
end)
@@ -378,7 +378,7 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
[git_repo, _] = Mix.Dep.load_on_environment([])
[git_repo, _] = Mix.Dep.Converger.converge([])
%{app: :git_repo, status: {:overridden, _}} = git_repo
end)
end)
@@ -549,7 +549,7 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
# Both orders below are valid after topological sort
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) in [
assert Enum.map(Mix.Dep.Converger.converge([]), & &1.app) in [
[:git_repo, :abc_repo, :deps_repo],
[:abc_repo, :git_repo, :deps_repo]
]
@@ -604,7 +604,7 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
# Both orders below are valid after topological sort
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) in [
assert Enum.map(Mix.Dep.Converger.converge([]), & &1.app) in [
[:git_repo, :abc_repo, :deps_repo],
[:abc_repo, :git_repo, :deps_repo]
]
@@ -639,13 +639,13 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
deps = Mix.Dep.load_on_environment(env: :other_env)
deps = Mix.Dep.Converger.converge(env: :other_env)
assert length(deps) == 2
deps = Mix.Dep.load_on_environment([])
deps = Mix.Dep.Converger.converge([])
assert length(deps) == 2
assert [dep] = Mix.Dep.load_on_environment(env: :prod)
assert [dep] = Mix.Dep.Converger.converge(env: :prod)
assert dep.app == :foo
end)
end)
@@ -676,10 +676,10 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
assert [:deps_repo] = Enum.map(loaded, & &1.app)
loaded = Mix.Dep.load_on_environment(env: :test)
loaded = Mix.Dep.Converger.converge(env: :test)
assert [:deps_repo] = Enum.map(loaded, & &1.app)
end)
end)
@@ -694,15 +694,15 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [divergedonly: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(env: :dev)
loaded = Mix.Dep.Converger.converge(env: :dev)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [divergedonly: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(env: :test)
loaded = Mix.Dep.Converger.converge(env: :test)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [divergedonly: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
@@ -727,15 +727,15 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(env: :dev)
loaded = Mix.Dep.Converger.converge(env: :dev)
assert [:deps_repo] = Enum.map(loaded, & &1.app)
assert [noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(env: :test)
loaded = Mix.Dep.Converger.converge(env: :test)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
end)
@@ -751,18 +751,18 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(env: :dev)
loaded = Mix.Dep.Converger.converge(env: :dev)
assert [] = Enum.map(loaded, & &1.app)
loaded = Mix.Dep.load_on_environment(env: :test)
loaded = Mix.Dep.Converger.converge(env: :test)
assert [:git_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(env: :prod)
loaded = Mix.Dep.Converger.converge(env: :prod)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
end)
@@ -778,15 +778,15 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [divergedonly: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(env: :dev)
loaded = Mix.Dep.Converger.converge(env: :dev)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [divergedonly: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(env: :test)
loaded = Mix.Dep.Converger.converge(env: :test)
assert [:git_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _] = Enum.map(loaded, & &1.status)
@@ -811,19 +811,19 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(env: :dev)
loaded = Mix.Dep.Converger.converge(env: :dev)
assert [:deps_repo] = Enum.map(loaded, & &1.app)
assert [noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(env: :test)
loaded = Mix.Dep.Converger.converge(env: :test)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(env: :prod)
loaded = Mix.Dep.Converger.converge(env: :prod)
assert [] = Enum.map(loaded, & &1.app)
end)
end)
@@ -848,7 +848,7 @@ defmodule Mix.DepTest do
""")
Mix.State.clear_cache()
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
Enum.map(loaded, &{&1.app, &1.opts[:only]})
end)
end)
@@ -933,13 +933,13 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
deps = Mix.Dep.load_on_environment(target: :rpi3)
deps = Mix.Dep.Converger.converge(target: :rpi3)
assert length(deps) == 2
deps = Mix.Dep.load_on_environment([])
deps = Mix.Dep.Converger.converge([])
assert length(deps) == 2
assert [dep] = Mix.Dep.load_on_environment(target: :host)
assert [dep] = Mix.Dep.Converger.converge(target: :host)
assert dep.app == :foo
end)
end)
@@ -973,15 +973,15 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [divergedtargets: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(target: :host)
loaded = Mix.Dep.Converger.converge(target: :host)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [divergedtargets: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(target: :rpi3)
loaded = Mix.Dep.Converger.converge(target: :rpi3)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [divergedtargets: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
@@ -1006,15 +1006,15 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(target: :host)
loaded = Mix.Dep.Converger.converge(target: :host)
assert [:deps_repo] = Enum.map(loaded, & &1.app)
assert [noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(target: :rpi3)
loaded = Mix.Dep.Converger.converge(target: :rpi3)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
end)
@@ -1030,18 +1030,18 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(target: :host)
loaded = Mix.Dep.Converger.converge(target: :host)
assert [] = Enum.map(loaded, & &1.app)
loaded = Mix.Dep.load_on_environment(target: :bbb)
loaded = Mix.Dep.Converger.converge(target: :bbb)
assert [:git_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(target: :rpi3)
loaded = Mix.Dep.Converger.converge(target: :rpi3)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
end)
@@ -1057,15 +1057,15 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [divergedtargets: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(target: :host)
loaded = Mix.Dep.Converger.converge(target: :host)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [divergedtargets: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(target: :rpi3)
loaded = Mix.Dep.Converger.converge(target: :rpi3)
assert [:git_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _] = Enum.map(loaded, & &1.status)
@@ -1090,19 +1090,19 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(target: :host)
loaded = Mix.Dep.Converger.converge(target: :host)
assert [:deps_repo] = Enum.map(loaded, & &1.app)
assert [noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(target: :bbb)
loaded = Mix.Dep.Converger.converge(target: :bbb)
assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app)
assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status)
loaded = Mix.Dep.load_on_environment(target: :rpi3)
loaded = Mix.Dep.Converger.converge(target: :rpi3)
assert [] = Enum.map(loaded, & &1.app)
end)
end)
@@ -1127,7 +1127,7 @@ defmodule Mix.DepTest do
""")
Mix.State.clear_cache()
loaded = Mix.Dep.load_on_environment([])
loaded = Mix.Dep.Converger.converge([])
Enum.map(loaded, &{&1.app, &1.opts[:targets]})
end)
end)
+4 -4
View File
@@ -149,14 +149,14 @@ defmodule Mix.RebarTest do
describe "integration with Mix" do
test "inherits Rebar manager" do
Mix.Project.push(RebarAsDep)
deps = Mix.Dep.load_on_environment([])
deps = Mix.Dep.Converger.converge([])
assert Enum.all?(deps, &(&1.manager == :rebar3))
end
test "parses Rebar dependencies from rebar.config" do
Mix.Project.push(RebarAsDep)
deps = Mix.Dep.load_on_environment([])
deps = Mix.Dep.Converger.converge([])
assert Enum.all?(deps, &(&1.manager == :rebar3))
assert Enum.find(deps, &(&1.app == :rebar_dep))
@@ -175,7 +175,7 @@ defmodule Mix.RebarTest do
Mix.Tasks.Deps.Get.run([])
assert Mix.Dep.load_on_environment([]) |> Enum.map(& &1.app) ==
assert Mix.Dep.Converger.converge([]) |> Enum.map(& &1.app) ==
[:git_repo, :git_rebar, :rebar_override]
end)
after
@@ -199,7 +199,7 @@ defmodule Mix.RebarTest do
assert :rebar_dep.any_function() == :ok
load_paths =
Mix.Dep.load_on_environment([])
Mix.Dep.Converger.converge([])
|> Enum.map(&Mix.Dep.load_paths(&1))
|> Enum.concat()
+1
View File
@@ -32,6 +32,7 @@ defmodule Mix.Tasks.ArchiveTest do
message = "Generated archive \"archive-0.1.0.ez\" with MIX_ENV=dev"
assert_received {:mix_shell, :info, [^message]}
assert File.regular?(~c"archive-0.1.0.ez")
assert to_charlist(Mix.Project.consolidation_path()) not in :code.get_path()
assert_archive_content_default()
refute has_in_zip_file?(~c"archive-0.1.0.ez", ~c"archive-0.1.0/priv/.dot_file")
+27 -6
View File
@@ -19,6 +19,14 @@ defmodule Mix.Tasks.Compile.ElixirTest do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
File.write!("lib/a.ex", """
defmodule A, do: :ok
# Also make sure that we access the ebin directory during compilation
true = to_charlist(Mix.Project.compile_path()) in :code.get_path()
""")
Mix.Tasks.Compile.Elixir.run(["--verbose"])
assert File.regular?("_build/shared/lib/sample/ebin/Elixir.A.beam")
@@ -32,6 +40,14 @@ defmodule Mix.Tasks.Compile.ElixirTest do
test "compiles a project with per environment build" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
File.write!("lib/a.ex", """
defmodule A, do: :ok
# Also make sure that we access the ebin directory during compilation
true = to_charlist(Mix.Project.compile_path()) in :code.get_path()
""")
Mix.Tasks.Compile.Elixir.run(["--verbose"])
assert File.regular?("_build/dev/lib/sample/ebin/Elixir.A.beam")
@@ -772,7 +788,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "compiles mtime changed files if content changed but not length" do
test "recompiles mtime changed files if content changed but not length" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
@@ -872,7 +888,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "compiles size changed files" do
test "recompiles size changed files" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
past = @old_time
@@ -894,7 +910,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "compiles dependent changed modules" do
test "recompiles dependent changed modules" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
File.write!("lib/a.ex", "defmodule A, do: B.module_info()")
@@ -914,7 +930,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "compiles dependent changed modules without beam files" do
test "recompiles dependent changed modules without beam files" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
@@ -942,7 +958,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
Code.put_compiler_option(:ignore_module_conflict, false)
end
test "compiles dependent changed modules even on removal" do
test "recompiles dependent changed modules even on removal" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
File.write!("lib/a.ex", "defmodule A, do: B.module_info()")
@@ -963,7 +979,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "compiles dependent changed external resources" do
test "recompiles dependent changed external resources" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
tmp = tmp_path("c.eex")
@@ -1263,6 +1279,11 @@ defmodule Mix.Tasks.Compile.ElixirTest do
assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
assert_received {:mix_shell, :info, ["Compiled lib/c.ex"]}
# Mix recompile should work even if the compile path
# was removed and the module purged
Code.delete_path(Mix.Project.compile_path())
purge([A])
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
assert_received {:mix_shell, :info, ["Compiling 1 file (.ex)"]}
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
+26 -1
View File
@@ -308,6 +308,24 @@ defmodule Mix.Tasks.FormatTest do
'''abc
end
"""
{formatter_function, _options} = Mix.Tasks.Format.formatter_for_file("a.ex")
assert formatter_function.("""
if true do
~W'''
foo bar baz
'''abc
end
""") == """
if true do
~W'''
foo
bar
baz
'''abc
end
"""
end)
end
@@ -555,11 +573,18 @@ defmodule Mix.Tasks.FormatTest do
test "reads exported configuration from subdirectories", context do
in_tmp(context.test, fn ->
File.write!(".formatter.exs", """
[subdirectories: ["lib"]]
[subdirectories: ["li", "lib"]]
""")
# We also create a directory called li to ensure files
# from lib won't accidentally match on li.
File.mkdir_p!("li")
File.mkdir_p!("lib")
File.write!("li/.formatter.exs", """
[inputs: "**/*", locals_without_parens: [other_fun: 2]]
""")
File.write!("lib/.formatter.exs", """
[inputs: "a.ex", locals_without_parens: [my_fun: 2]]
""")
+8 -3
View File
@@ -64,6 +64,10 @@ defmodule Mix.UmbrellaTest do
Mix.Task.run("deps.loadpaths")
Mix.Task.run("compile", ["--verbose"])
# Extra applications are picked even for umbrellas
assert :code.where_is_file(~c"runtime_tools.app") != :non_existing
assert :code.where_is_file(~c"observer.app") == :non_existing
assert_received {:mix_shell, :info, ["==> bar"]}
assert_received {:mix_shell, :info, ["Generated bar app"]}
assert File.regular?("_build/dev/lib/bar/ebin/Elixir.Bar.beam")
@@ -290,7 +294,7 @@ defmodule Mix.UmbrellaTest do
in_fixture("umbrella_dep", fn ->
Mix.Project.push(CycleDeps)
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == [:foo, :bar, :umbrella]
assert Enum.map(Mix.Dep.Converger.converge([]), & &1.app) == [:foo, :bar, :umbrella]
end)
end
@@ -328,7 +332,7 @@ defmodule Mix.UmbrellaTest do
end
""")
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == [:a, :b, :bar, :foo]
assert Enum.map(Mix.Dep.Converger.converge([]), & &1.app) == [:a, :b, :bar, :foo]
end)
end)
end
@@ -453,7 +457,8 @@ defmodule Mix.UmbrellaTest do
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
# Recompiles if export dependency is removed
File.rm!("../foo/lib/foo.ex")
File.write!("../foo/lib/foo.ex", "")
ensure_touched("../foo/lib/foo.ex", "_build/dev/lib/bar/.mix/compile.elixir")
Mix.Task.clear()
ExUnit.CaptureIO.capture_io(:stderr, fn ->