Compare commits

...
10 Commits
Author SHA1 Message Date
José Valim 9decf4c78a Release v1.2.2 2016-01-31 10:17:08 +01:00
José Valim e2463a5589 Limit the list of attributes we consider reserved 2016-01-31 09:30:51 +01:00
José Valim a825f5a0c8 Force recompilation if dependency was recently fetched 2016-01-29 13:20:47 +01:00
José Valim 3c0d26cb74 Raise if trying to override reserved tag, closes #4236 2016-01-29 00:32:00 +01:00
José Valim f7a31ac804 Update CHANGELOG 2016-01-27 13:05:25 +01:00
José Valim 31aebcdc59 Automatically merge manager according to internal priority, closes #4230 2016-01-27 12:56:16 +01:00
José Valim afbac81918 Do not require all compilers available on manifest
Closes #4228

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-27 10:11:38 +01:00
José Valim 0b78f86f08 Do not rely on compiled functions in Mix for autoload
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-25 10:00:55 +01:00
José Valim 7180b98f47 Support disabling autoload after compilation
Useful for delaying loading of modules that may depend
on NIFs until necessary.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-25 00:13:37 +01:00
José Valim 021cca436d Do not include debug_info in metadata String modules
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-25 00:13:28 +01:00
19 changed files with 196 additions and 76 deletions
+13
View File
@@ -85,6 +85,19 @@ ask Mix to use Rebar 3 to compile it by passing the `manager: :rebar3` option.
Once configured, Mix will prompt you to install Rebar 3 if it is not yet
available.
## v1.2.2
### 1. Enhancements
* [Kernel] Support `@compile {:autoload, false}` to disable automatic loading after compilation
### 2. Bug fixes
* [ExUnit] Raise if trying to override reserved tag in `setup` blocks
* [Mix] Ensure retrieve compile manifests do fail if some compilers are not yet available
* [Mix] Automatically merge managers according to the mix > rebar3 > rebar > make order
* [Mix] Force recompilation if dependency was recently fetched
## v1.2.1 (2016-01-14)
### 1. Enhancements
+1 -1
View File
@@ -1 +1 @@
1.2.1
1.2.2
+22 -13
View File
@@ -78,9 +78,8 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
%% Pass {native, false} to speed up bootstrap
%% process when native is set to true
AllOpts = options(),
FinalOpts = AllOpts -- [native, warn_missing_spec],
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
ErlOpts = options() -- [native, warn_missing_spec],
inner_module(Form, ErlOpts, [{bootstrap, true}], E, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the
%% module
@@ -169,22 +168,32 @@ allows_fast_compilation(_) -> false.
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, Opts, E, Callback) ->
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info] ++ options();
false -> options()
ErlOpts =
case proplists:get_value(debug_info, Opts) of
true -> [debug_info];
false -> [];
undefined ->
case get_opt(debug_info) of
true -> [debug_info];
false -> []
end
end,
inner_module(Forms, Final, false, E, Callback).
inner_module(Forms, ErlOpts ++ options(), Opts, E, Callback).
inner_module(Forms, Options, Bootstrap, #{file := File} = E, Callback) when
is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
inner_module(Forms, ErlOpts, ExOpts, #{file := File} = E, Callback) when
is_list(Forms), is_list(ErlOpts), is_list(ExOpts), is_function(Callback) ->
Source = elixir_utils:characters_to_list(File),
Autoload = proplists:get_value(autoload, ExOpts, true),
Bootstrap = proplists:get_value(bootstrap, ExOpts, false),
case compile:noenv_forms([no_auto_import()|Forms], [return, {source, Source}|Options]) of
case compile:noenv_forms([no_auto_import()|Forms], [return, {source, Source}|ErlOpts]) of
{ok, Module, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings),
{module, Module} = code:load_binary(Module, beam_location(E), Binary),
{module, Module} =
case Autoload of
true -> code:load_binary(Module, beam_location(E), Binary);
false -> {module, Module}
end,
Callback(Module, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, Warnings),
+1 -1
View File
@@ -331,7 +331,7 @@ spec_for_macro(Else) -> Else.
compile_opts(Module) ->
case ets:lookup(data_table(Module), compile) of
[{compile, Opts}] when is_list(Opts) -> Opts;
[{compile, Opts}] when is_list(Opts) -> lists:flatten(Opts);
[] -> []
end.
+7
View File
@@ -144,6 +144,13 @@ defmodule ModuleTest do
assert [3, 2, 1] = @other_attribute
end
test "@compile autoload attribute" do
defmodule NoAutoload do
@compile {:autoload, false}
end
refute :code.is_loaded(NoAutoload)
end
## Naming
test "concat" do
+6
View File
@@ -539,4 +539,10 @@ defmodule StringTest do
assert String.jaro_distance("jon", "john") == 0.9166666666666666
assert String.jaro_distance("jon", "jan") == 0.7777777777777777
end
test "skip debug_info in metadata modules" do
assert String.Unicode.__info__(:compile)[:options] == []
assert String.Graphemes.__info__(:compile)[:options] == []
assert String.Normalizer.__info__(:compile)[:options] == []
end
end
+3
View File
@@ -10,6 +10,7 @@ end
defmodule String.Unicode do
@moduledoc false
@compile {:debug_info, false}
def version, do: {7, 0, 0}
data_path = Path.join(__DIR__, "UnicodeData.txt")
@@ -204,6 +205,7 @@ end
defmodule String.Graphemes do
@moduledoc false
@compile {:debug_info, false}
cluster_path = Path.join(__DIR__, "GraphemeBreakProperty.txt")
regex = ~r/(?:^([0-9A-F]+)(?:\.\.([0-9A-F]+))?)\s+;\s(\w+)/m
@@ -413,6 +415,7 @@ end
defmodule String.Normalizer do
@moduledoc false
@compile {:debug_info, false}
decomposition_path = Path.join(__DIR__, "Decomposition.txt")
+16 -5
View File
@@ -146,6 +146,8 @@ defmodule ExUnit.Callbacks do
## Helpers
@reserved ~w(case test line file)a
@doc false
def __merge__(_mod, context, :ok) do
{:ok, context}
@@ -163,12 +165,16 @@ defmodule ExUnit.Callbacks do
raise_merge_failed!(mod, data)
end
defp context_merge(_mod, context, %{} = data) do
Map.merge(context, data)
defp context_merge(mod, context, %{} = data) do
Map.merge(context, data, fn
_, v, v -> v
k, _, v when k in @reserved -> raise_merge_reserved!(mod, k, v)
_, _, v -> v
end)
end
defp context_merge(_mod, context, data) when is_list(data) do
Enum.into(data, context)
defp context_merge(mod, context, data) when is_list(data) do
context_merge(mod, context, Map.new(data))
end
defp context_merge(mod, _context, data) do
@@ -177,7 +183,12 @@ defmodule ExUnit.Callbacks do
defp raise_merge_failed!(mod, data) do
raise "expected ExUnit callback in #{inspect mod} to return :ok " <>
" or {:ok, keyword | map}, got #{inspect data} instead"
"or {:ok, keyword | map}, got #{inspect data} instead"
end
defp raise_merge_reserved!(mod, key, value) do
raise "expected ExUnit callback in #{inspect mod} is trying to set " <>
"reserved field #{inspect key} to #{inspect value}"
end
defp escape(contents) do
+19 -17
View File
@@ -114,10 +114,11 @@ defmodule ExUnit.Case do
The following tags are set automatically by ExUnit and are
therefore reserved:
* `:case` - the test case module
* `:test` - the test name
* `:line` - the line on which the test was defined
* `:file` - the file on which the test was defined
* `:case` - the test case module
* `:test` - the test name
* `:line` - the line on which the test was defined
* `:file` - the file on which the test was defined
* `:async` - if the test case is in async mode
The following tags customize how tests behaves:
@@ -174,7 +175,7 @@ defmodule ExUnit.Case do
This default can be overriden by `@tag capture_log: false` or `@moduletag capture_log: false`.
Since `setup_all` blocks don't belong to a specific test, log messages generated in them (or
Since `setup_all` blocks don't belong to a specific test, log messages generated in them (or
between tests) are never captured. If you want to suppress these messages as well, remove the
console backend globally:
@@ -193,20 +194,16 @@ defmodule ExUnit.Case do
end
quote do
async = !!unquote(async)
unless Module.get_attribute(__MODULE__, :ex_unit_tests) do
Enum.each [:ex_unit_tests, :tag, :moduletag],
&Module.register_attribute(__MODULE__, &1, accumulate: true)
if unquote(async) do
@moduletag async: true
ExUnit.Server.add_async_case(__MODULE__)
else
@moduletag async: false
ExUnit.Server.add_sync_case(__MODULE__)
end
@moduletag async: async
@before_compile ExUnit.Case
@ex_unit_test_names %{}
@ex_unit_async async
use ExUnit.Callbacks
end
@@ -254,7 +251,7 @@ defmodule ExUnit.Case do
quote bind_quoted: binding do
test = :"test #{message}"
ExUnit.Case.__on_definition__(__ENV__, test)
ExUnit.Case.__on_definition__(__ENV__, test, [])
def unquote(test)(unquote(var)), do: unquote(contents)
end
end
@@ -284,6 +281,12 @@ defmodule ExUnit.Case do
@doc false
defmacro __before_compile__(_) do
quote do
if @ex_unit_async do
ExUnit.Server.add_async_case(__MODULE__)
else
ExUnit.Server.add_sync_case(__MODULE__)
end
def __ex_unit__(:case) do
%ExUnit.TestCase{name: __MODULE__, tests: @ex_unit_tests}
end
@@ -291,8 +294,7 @@ defmodule ExUnit.Case do
end
@doc false
def __on_definition__(env, name, tags \\ []) do
mod = env.module
def __on_definition__(%{module: mod, file: file, line: line}, name, tags) do
moduletag = Module.get_attribute(mod, :moduletag)
unless moduletag do
@@ -304,7 +306,7 @@ defmodule ExUnit.Case do
(tags ++ Module.get_attribute(mod, :tag) ++ moduletag)
|> normalize_tags
|> validate_tags
|> Map.merge(%{line: env.line, file: env.file})
|> Map.merge(%{line: line, file: file})
test = %ExUnit.Test{name: name, case: mod, tags: tags}
test_names = Module.get_attribute(mod, :ex_unit_test_names)
+53 -4
View File
@@ -152,7 +152,7 @@ defmodule ExUnitTest do
refute output =~ "[debug] four"
end
test "it supports multi errors" do
test "supports multi errors" do
capture_io :stderr, fn ->
defmodule MultiTest do
use ExUnit.Case
@@ -187,7 +187,7 @@ defmodule ExUnitTest do
assert output =~ "Failure #2"
end
test "it registers only the first test with any given name" do
test "registers only the first test with any given name" do
capture_io :stderr, fn ->
defmodule TestWithSameNames do
use ExUnit.Case
@@ -207,7 +207,7 @@ defmodule ExUnitTest do
end) =~ "1 test, 0 failure"
end
test "it produces error on not implemented tests" do
test "produces error on not implemented tests" do
defmodule TestNotImplemented do
use ExUnit.Case
@@ -227,7 +227,7 @@ defmodule ExUnitTest do
assert output =~ "1 test, 1 failure"
end
test "it skips tagged test with skip" do
test "skips tagged test with skip" do
defmodule TestSkipped do
use ExUnit.Case
@@ -250,6 +250,55 @@ defmodule ExUnitTest do
assert output =~ "2 tests, 0 failures, 2 skipped"
end
test "raises on reserved tag in module" do
assert_raise RuntimeError, "cannot set tag :file because it is reserved by ExUnit", fn ->
defmodule ReservedTag do
use ExUnit.Case
setup do
{:ok, file: :foo}
end
@tag file: "oops"
test "sample", do: :ok
end
end
end
test "raises on reserved tag in setup" do
defmodule ReservedSetupTag do
use ExUnit.Case
setup do
{:ok, file: :foo}
end
test "sample", do: :ok
end
output = capture_io(fn ->
assert ExUnit.run == %{failures: 1, skipped: 0, total: 1}
end)
assert output =~ "trying to set reserved field :file"
end
test "does not raise on reserved tag in setup_all (lower priority)" do
defmodule ReservedSetupAllTag do
use ExUnit.Case
setup_all do
{:ok, file: :foo}
end
test "sample", do: :ok
end
capture_io(fn ->
assert ExUnit.run == %{failures: 0, skipped: 0, total: 1}
end)
end
defp run_with_filter(filters, {async, sync, load_us}) do
opts = Keyword.merge(ExUnit.configuration, filters)
output = capture_io fn ->
+7 -10
View File
@@ -137,29 +137,26 @@ defmodule Mix.Compilers.Elixir do
|> List.delete(module)
|> Enum.reject(&match?("elixir_" <> _, Atom.to_string(&1)))
attributes = module.__info__(:attributes)
kind = detect_kind(attributes)
kind = detect_kind(module)
source = Path.relative_to(source, cwd)
files = get_external_resources(attributes, cwd)
files = get_external_resources(module, cwd)
tuple = {beam, module, kind, source, compile, runtime, files, binary}
Agent.cast pid, &:lists.keystore(beam, 1, &1, tuple)
end
defp detect_kind(attributes) do
defp detect_kind(module) do
cond do
impl = attributes[:impl] ->
impl = Module.get_attribute(module, :impl) ->
{:impl, impl[:protocol]}
attributes[:protocol] ->
Module.get_attribute(module, :protocol) ->
:protocol
true ->
:module
end
end
defp get_external_resources(attributes, cwd) do
for {:external_resource, values} <- attributes,
file <- values,
defp get_external_resources(module, cwd) do
for file <- Module.get_attribute(module, :external_resource),
File.regular?(file),
relative = Path.relative_to(file, cwd),
Path.type(relative) == :relative,
+16 -14
View File
@@ -213,7 +213,8 @@ defmodule Mix.Dep.Converger do
in_upper? && other_opts[:override] ->
{other |> with_matching_only(dep, in_upper?), true}
converge?(other, dep) ->
{other |> with_matching_only(dep, in_upper?) |> with_matching_req(dep) |> merge_manager(dep), true}
{other |> with_matching_only(dep, in_upper?)
|> with_matching_req(dep) |> merge_manager(dep, in_upper?), true}
true ->
tag = if in_upper?, do: :overridden, else: :diverged
{%{other | status: {tag, dep}}, true}
@@ -276,19 +277,10 @@ defmodule Mix.Dep.Converger do
end
end
defp converge?(%Mix.Dep{scm: scm1, manager: manager1, opts: opts1},
%Mix.Dep{scm: scm2, manager: manager2, opts: opts2}) do
scm1 == scm2 and
manager_equal?(manager1, manager2) and
opts_equal?(opts1, opts2) and
scm1.equal?(opts1, opts2)
defp converge?(%Mix.Dep{scm: scm1, opts: opts1}, %Mix.Dep{scm: scm2, opts: opts2}) do
scm1 == scm2 and opts_equal?(opts1, opts2) and scm1.equal?(opts1, opts2)
end
defp manager_equal?(manager, manager), do: true
defp manager_equal?(_, nil), do: true
defp manager_equal?(nil, _), do: true
defp manager_equal?(_, _), do: false
defp opts_equal?(opts1, opts2) do
keys = ~w(app env compile)a
Enum.all?(keys, &(Keyword.fetch(opts1, &1) == Keyword.fetch(opts2, &1)))
@@ -300,8 +292,18 @@ defmodule Mix.Dep.Converger do
end
end
defp merge_manager(other, dep) do
%{other | manager: other.manager || dep.manager}
defp merge_manager(%{manager: other_manager} = other, %{manager: manager}, in_upper?) do
%{other | manager: sort_manager(other_manager, manager, in_upper?)}
end
@managers [:mix, :rebar3, :rebar, :make]
defp sort_manager(other_manager, manager, true) do
other_manager || manager
end
defp sort_manager(other_manager, manager, false) do
priority = @managers -- (@managers -- (List.wrap(other_manager) ++ List.wrap(manager)))
List.first(priority) || other_manager || manager
end
defp with_matching_req(%Mix.Dep{} = other, %Mix.Dep{} = dep) do
+1 -3
View File
@@ -96,9 +96,7 @@ defmodule Mix.Dep.Fetcher do
# it for compilation too, this is our best to try to solve the
# maximum we can at each deps.get and deps.update.
if Enum.all?(all_deps, &available?/1) do
deps = (with_depending(deps, all_deps) ++
Enum.filter(all_deps, fn dep -> not ok?(dep) end))
|> Enum.uniq(&(&1.app))
deps = Enum.uniq_by(with_depending(deps, all_deps), &(&1.app))
end
# Merge the new lock on top of the old to guarantee we don't
+2 -2
View File
@@ -127,8 +127,8 @@ defmodule Mix.Tasks.Compile do
"""
def manifests do
Enum.flat_map(compilers(), fn(compiler) ->
module = Mix.Task.get!("compile.#{compiler}")
if function_exported?(module, :manifests, 0) do
module = Mix.Task.get("compile.#{compiler}")
if module && function_exported?(module, :manifests, 0) do
module.manifests
else
[]
+6 -1
View File
@@ -25,7 +25,7 @@ defmodule Mix.Tasks.Deps.Compile do
"""
import Mix.Dep, only: [loaded: 1, available?: 1, loaded_by_name: 2,
format_dep: 1, make?: 1, mix?: 1, rebar?: 1]
format_dep: 1, make?: 1, mix?: 1]
@spec run(OptionParser.argv) :: :ok
def run(args) do
@@ -100,6 +100,11 @@ defmodule Mix.Tasks.Deps.Compile do
"but you are running on v#{System.version}"
end
# Force recompilation on compile status
if dep.status == :compile do
Mix.Dep.Lock.touch_manifest
end
try do
res = Mix.Task.run("compile", ["--no-deps", "--no-elixir-version-check"])
:ok in List.wrap(res)
+16 -1
View File
@@ -144,7 +144,22 @@ defmodule Mix.DepTest do
end
end
test "nested deps convergence with optional dependencies" do
test "nested deps with convergence and managers" do
Process.put(:custom_deps_git_repo_opts, [manager: :make])
deps = [{:deps_repo, "0.1.0", path: "custom/deps_repo", manager: :rebar},
{:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo")}]
with_deps deps, fn ->
in_fixture "deps_status", fn ->
[dep1, dep2] = Mix.Dep.loaded([])
assert dep1.manager == nil
assert dep2.manager == :rebar
end
end
end
test "nested deps with convergence and optional dependencies" do
deps = [{:deps_repo, "0.1.0", path: "custom/deps_repo"},
{:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo")}]
+6
View File
@@ -26,6 +26,12 @@ defmodule Mix.Tasks.CompileTest do
assert_received {:mix_shell, :info, ["\nEnabled compilers: elixir, app, custom, protocols"]}
end
test "compile does not require all compilers available on manifest" do
Mix.Project.push CustomCompilers
assert Mix.Tasks.Compile.manifests |> Enum.map(&Path.basename/1) ==
[".compile.elixir"]
end
test "compile a project with mixfile" do
in_fixture "no_mixfile", fn ->
assert Mix.Tasks.Compile.run([]) == :ok
-3
View File
@@ -146,9 +146,6 @@ defmodule Mix.Tasks.DepsGitTest do
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
# Clear up to prepare for the update
File.rm("_build/dev/lib/git_repo/ebin/Elixir.GitRepo.beam")
File.rm("_build/dev/lib/git_repo/.compile.elixir")
File.rm("deps/git_repo/.fetch")
Mix.Task.clear
Mix.shell.flush
purge [A, B, C, GitRepo]
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.2.1"},
{vsn, "1.2.2"},
{modules, [
elixir
]},