Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c521bdb91a | ||
|
|
bdbdf52122 | ||
|
|
7ff97fb930 | ||
|
|
af378de03f | ||
|
|
82c264bc36 | ||
|
|
4fbed9fe12 | ||
|
|
1dbf121a78 | ||
|
|
694b9b0218 | ||
|
|
0a3df5068c | ||
|
|
9e177fd592 | ||
|
|
8422af4a2e | ||
|
|
b79c5a3b4c | ||
|
|
3bb0a8379d | ||
|
|
fe0d57c4da | ||
|
|
727a958c48 | ||
|
|
aa93d654ab | ||
|
|
f5b71a79b2 | ||
|
|
173fc52610 |
@@ -117,6 +117,42 @@ in the long term.
|
||||
See the new `Logger` documentation for more information on the
|
||||
new features and on compatibility.
|
||||
|
||||
## v1.15.4 (2023-07-18)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix archive.build] Disable protocol consolidation when building archiveson 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 better 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
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.15.2
|
||||
ELIXIR_VERSION=1.15.4
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
|
||||
+1
-1
@@ -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.4
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() ->
|
||||
|
||||
@@ -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()).
|
||||
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
+16
-9
@@ -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
|
||||
@@ -928,13 +930,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -293,6 +293,7 @@ defmodule IEx.Introspection do
|
||||
module.module_info(:exports)
|
||||
end
|
||||
|> Enum.sort()
|
||||
|> Enum.dedup()
|
||||
|
||||
result =
|
||||
for {^function, arity} <- exports,
|
||||
|
||||
+20
-11
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
@@ -565,72 +566,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1263,6 +1263,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"]}
|
||||
|
||||
@@ -453,7 +453,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 ->
|
||||
|
||||
Reference in New Issue
Block a user