Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfe6612e54 | ||
|
|
5230d73968 | ||
|
|
ef36b163b3 | ||
|
|
a622a8ef92 | ||
|
|
66bb6da847 | ||
|
|
eee55adcd0 | ||
|
|
cbd5ffce3e | ||
|
|
a3dda22035 | ||
|
|
7184679788 | ||
|
|
aeb2ea1dda | ||
|
|
7b20c281d5 | ||
|
|
c69f4407fd | ||
|
|
88f65ae1a6 | ||
|
|
14d406fcd9 | ||
|
|
08a9d4b9fc | ||
|
|
b3ab3696ec | ||
|
|
afe4704666 | ||
|
|
c51acfcbc5 | ||
|
|
332ff949a0 | ||
|
|
ab17a3c050 | ||
|
|
2522bba1bf | ||
|
|
d00f172c59 | ||
|
|
0eb5289cd0 | ||
|
|
7e01628b5d | ||
|
|
6432e8655c | ||
|
|
e34495e304 | ||
|
|
64c4ecf9c7 | ||
|
|
ef002e2b15 | ||
|
|
f89c5076f9 | ||
|
|
18d367c01b | ||
|
|
65baed8681 | ||
|
|
76c64a0f32 | ||
|
|
6ac1d10f77 | ||
|
|
ca57cfe6f3 | ||
|
|
c32f72581a | ||
|
|
8115bd38a1 |
@@ -3,12 +3,12 @@ name: CI for Markdown content
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- "main"
|
||||
paths:
|
||||
- 'lib/**/*.md'
|
||||
- "lib/**/*.md"
|
||||
pull_request:
|
||||
paths:
|
||||
- 'lib/**/*.md'
|
||||
- "lib/**/*.md"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out the repository
|
||||
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
name: Windows Server 2019, Erlang/OTP ${{ matrix.otp_version }}
|
||||
strategy:
|
||||
matrix:
|
||||
otp_version: ["25.3", "26.2", "27.1"]
|
||||
otp_version: ["25.3", "26.2", "27.3"]
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: Configure Git
|
||||
|
||||
@@ -10,7 +10,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
name: Notify
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
fetch-depth: 50
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: '25.0'
|
||||
elixir-version: '1.14.0'
|
||||
otp-version: "27.3"
|
||||
elixir-version: "1.18.3"
|
||||
- name: Run Elixir script
|
||||
env:
|
||||
ELIXIR_FORUM_TOKEN: ${{ secrets.ELIXIR_FORUM_TOKEN }}
|
||||
|
||||
@@ -118,6 +118,7 @@ jobs:
|
||||
|
||||
sign:
|
||||
needs: [build]
|
||||
environment: release
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
@@ -129,21 +130,30 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.flavor == 'linux' && 'ubuntu-22.04' || 'windows-2022' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }}
|
||||
|
||||
- name: Log in to Azure
|
||||
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: "Sign files with Trusted Signing"
|
||||
if: github.repository == 'elixir-lang/elixir' && matrix.flavor == 'windows'
|
||||
uses: azure/trusted-signing-action@v0.5.0
|
||||
uses: azure/trusted-signing-action@0d74250c661747df006298d0fb49944c10f16e03 # v0.5.1
|
||||
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
with:
|
||||
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
endpoint: https://eus.codesigning.azure.net/
|
||||
trusted-signing-account-name: trusted-signing-elixir
|
||||
certificate-profile-name: Elixir
|
||||
trusted-signing-account-name: ${{ vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
certificate-profile-name: ${{ vars.AZURE_CERTIFICATE_PROFILE_NAME }}
|
||||
files-folder: ${{ github.workspace }}
|
||||
files-folder-filter: exe
|
||||
file-digest: SHA256
|
||||
@@ -223,16 +233,20 @@ jobs:
|
||||
needs: [build, sign]
|
||||
runs-on: ubuntu-22.04
|
||||
concurrency: builds-hex-pm
|
||||
environment: release
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
|
||||
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
|
||||
AWS_REGION: ${{ vars.HEX_AWS_REGION }}
|
||||
AWS_S3_BUCKET: ${{ vars.HEX_AWS_S3_BUCKET }}
|
||||
OTP_GENERIC_VERSION: "25"
|
||||
steps:
|
||||
- name: "Check if variables are set up"
|
||||
if: "${{ ! vars.HEX_AWS_REGION }}"
|
||||
run: |
|
||||
echo "Required variables for uploading to hex.pm are not set up, skipping..."
|
||||
exit 1
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: "{sign-*-elixir-otp-*,Docs}"
|
||||
@@ -337,3 +351,7 @@ jobs:
|
||||
for key in $(cat purge_keys.txt); do
|
||||
purge "${key}"
|
||||
done
|
||||
env:
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
|
||||
|
||||
@@ -223,6 +223,60 @@ You may also prefer to write using guards:
|
||||
|
||||
def foo(x, y, z) when x == y and y == z
|
||||
|
||||
## v1.18.5 (2026-08-28)
|
||||
|
||||
### 1. Security
|
||||
|
||||
* [List] Avoid recursion when invalid charlists are given to `List.to_string/1` or `List.to_charlist/1` (CVE-2026-75758, GHSA-jf5q-v438-665c)
|
||||
|
||||
## v1.18.4 (2025-05-21)
|
||||
|
||||
This release includes initial support for Erlang/OTP 28, for those who want to try it out. In such cases, you may use Elixir v1.18.4 precompiled for Erlang/OTP 27, as it is binary compatible with Erlang/OTP 28. Note, however, that Erlang/OTP 28 no longer allows regexes to be defined in the module body and interpolated into an attribute. If you do this:
|
||||
|
||||
```elixir
|
||||
@some_attribute ~r/foo/
|
||||
def some_fun, do: @some_attribute
|
||||
```
|
||||
|
||||
You must rewrite it to:
|
||||
|
||||
```elixir
|
||||
def some_fun, do: ~r/foo/
|
||||
```
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Add `IEx.Helpers.process_info/1` which prints process information
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Support the `--no-listeners` option
|
||||
* [mix local] Retry HTTP requests with disabled middlebox comp mode depending on the failure reason
|
||||
* [mix local.hex] Install Hex per OTP release
|
||||
* [mix local.rebar] Install Hex per OTP release
|
||||
* [mix run] Support the `--no-listeners` option
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Emit trace events for `@on_definition` callbacks
|
||||
* [Kernel] Emit trace events for `@on_load` callbacks
|
||||
* [Kernel] Emit trace events for `super` calls
|
||||
* [Kernel] Emit trace events for imported function calls
|
||||
* [Kernel] Optimize map unions to avoid building long lists
|
||||
* [Kernel] Do not crash when type checking nested bitstrings in patterns
|
||||
* [Kernel] Do not crash when non-binary bitstring is given as struct default value
|
||||
* [Kernel] Recompile regexes when escaped from module attributes for Erlang/OTP 28 compatibility
|
||||
* [Kernel] Preserve backwards compatibility in `elixir_erl`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix deps.get] Ensure git checkout works when there are untracked files in the dependency
|
||||
* [mix loadpaths] Do not run listeners when not checking the deps
|
||||
|
||||
## v1.18.3 (2025-03-06)
|
||||
|
||||
### 1. Enhancements
|
||||
@@ -440,6 +494,12 @@ You may also prefer to write using guards:
|
||||
* [mix test] `:warnings_as_errors` configuration in `:test_elixirc_options` is deprecated. Instead pass the `--warnings-as-errors` flag to `mix test`. Alternatively, you might alias the task: `aliases: [test: "test --warnings-as-errors"]`
|
||||
* [Mix.Tasks.Compile] Deprecate `compilers/0` in favor of `Mix.Task.Compiler.compilers/0`
|
||||
|
||||
### 5. Potential breaking changes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Using unquote is deprecated when quote is used inside a pattern or guard
|
||||
|
||||
## v1.17
|
||||
|
||||
The CHANGELOG for v1.17 releases can be found [in the v1.17 branch](https://github.com/elixir-lang/elixir/blob/v1.17/CHANGELOG.md).
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.18.3
|
||||
ELIXIR_VERSION=1.18.5
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
@echo off
|
||||
|
||||
set ELIXIR_VERSION=1.18.3
|
||||
set ELIXIR_VERSION=1.18.5
|
||||
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
|
||||
|
||||
@@ -2530,7 +2530,7 @@ defmodule ErlangError do
|
||||
{:ok, reason, IO.iodata_to_binary([":\n\n" | Enum.map(args_errors, &arg_error/1)])}
|
||||
|
||||
general = extra[:general] ->
|
||||
{:ok, reason, ": " <> general}
|
||||
{:ok, reason, ": " <> IO.chardata_to_string(general)}
|
||||
|
||||
true ->
|
||||
:error
|
||||
|
||||
@@ -343,13 +343,20 @@ defimpl Inspect, for: List do
|
||||
close = color_doc("]", :list, opts)
|
||||
|
||||
cond do
|
||||
lists == :as_charlists or (lists == :infer and List.ascii_printable?(term, printable_limit)) ->
|
||||
inspected =
|
||||
case Identifier.escape(IO.chardata_to_string(term), ?", printable_limit) do
|
||||
{escaped, ""} -> [?~, ?c, ?", escaped, ?"]
|
||||
{escaped, _} -> [?~, ?c, ?", escaped, ?", " ++ ..."]
|
||||
(lists == :as_charlists and unicode_list?(term, printable_limit)) or
|
||||
(lists == :infer and List.ascii_printable?(term, printable_limit)) ->
|
||||
{split, tail} =
|
||||
if is_integer(printable_limit) do
|
||||
case Enum.split(term, printable_limit) do
|
||||
{split, []} -> {split, []}
|
||||
{split, _} -> {split, " ++ ..."}
|
||||
end
|
||||
else
|
||||
{term, []}
|
||||
end
|
||||
|
||||
{escaped, _} = Identifier.escape(IO.chardata_to_string(split), ?")
|
||||
inspected = [?~, ?c, ?", escaped, ?" | tail]
|
||||
color_doc(IO.iodata_to_binary(inspected), :charlist, opts)
|
||||
|
||||
keyword?(term) ->
|
||||
@@ -360,6 +367,19 @@ defimpl Inspect, for: List do
|
||||
end
|
||||
end
|
||||
|
||||
defp unicode_list?(_, 0), do: true
|
||||
|
||||
defp unicode_list?([char | rest], counter)
|
||||
when char in 0..0xD7FF or char in 0xE000..0x10FFFF,
|
||||
do: unicode_list?(rest, decrement(counter))
|
||||
|
||||
defp unicode_list?([], _counter), do: true
|
||||
defp unicode_list?(_, _counter), do: false
|
||||
|
||||
@compile {:inline, decrement: 1}
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
@doc false
|
||||
def keyword({key, value}, opts) do
|
||||
key = color_doc(Macro.inspect_atom(:key, key), :atom, opts)
|
||||
|
||||
@@ -17,8 +17,8 @@ defmodule Inspect.Opts do
|
||||
is `:decimal` and if it is printable, otherwise in bit syntax. See
|
||||
`String.printable?/1` to learn when a string is printable.
|
||||
|
||||
* `:charlists` - when `:as_charlists` all lists will be printed as charlists,
|
||||
non-printable elements will be escaped.
|
||||
* `:charlists` - when `:as_charlists` all charlists will be printed as charlists,
|
||||
non-printable code points will be escaped. Other lists will be printed as lists.
|
||||
|
||||
When `:as_lists` all lists will be printed as lists.
|
||||
|
||||
|
||||
+24
-13
@@ -3716,26 +3716,25 @@ defmodule Kernel do
|
||||
|
||||
case function? do
|
||||
true ->
|
||||
value =
|
||||
case Module.__get_attribute__(env.module, name, line, false) do
|
||||
{_, doc} when doc_attr? -> doc
|
||||
other -> other
|
||||
end
|
||||
case Module.__get_attribute__(env.module, name, line, false) do
|
||||
{_, doc} when doc_attr? ->
|
||||
do_at_escape(name, doc)
|
||||
|
||||
try do
|
||||
:elixir_quote.escape(value, :none, false)
|
||||
rescue
|
||||
ex in [ArgumentError] ->
|
||||
raise ArgumentError,
|
||||
"cannot inject attribute @#{name} into function/macro because " <>
|
||||
Exception.message(ex)
|
||||
%{__struct__: Regex, source: source, opts: opts} = regex ->
|
||||
case :erlang.system_info(:otp_release) < [?2, ?8] do
|
||||
true -> do_at_escape(name, regex)
|
||||
false -> quote(do: Regex.compile!(unquote(source), unquote(opts)))
|
||||
end
|
||||
|
||||
value ->
|
||||
do_at_escape(name, value)
|
||||
end
|
||||
|
||||
false when doc_attr? ->
|
||||
quote do
|
||||
case Module.__get_attribute__(__MODULE__, unquote(name), unquote(line), false) do
|
||||
{_, doc} -> doc
|
||||
other -> other
|
||||
value -> value
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3770,6 +3769,17 @@ defmodule Kernel do
|
||||
raise ArgumentError, "expected 0 or 1 argument for @#{name}, got: #{length(args)}"
|
||||
end
|
||||
|
||||
defp do_at_escape(name, value) do
|
||||
try do
|
||||
:elixir_quote.escape(value, :none, false)
|
||||
rescue
|
||||
ex in [ArgumentError] ->
|
||||
raise ArgumentError,
|
||||
"cannot inject attribute @#{name} into function/macro because " <>
|
||||
Exception.message(ex)
|
||||
end
|
||||
end
|
||||
|
||||
# Those are always compile-time dependencies, so we can skip the trace.
|
||||
defp collect_traces(:before_compile, arg, _env), do: {arg, []}
|
||||
defp collect_traces(:after_compile, arg, _env), do: {arg, []}
|
||||
@@ -6461,6 +6471,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
defp compile_regex(binary_or_tuple, options) do
|
||||
# TODO: Remove this when we require Erlang/OTP 28+
|
||||
case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do
|
||||
true ->
|
||||
Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options)))
|
||||
|
||||
@@ -258,13 +258,6 @@ defmodule Kernel.ParallelCompiler do
|
||||
{status, modules_or_errors, info} =
|
||||
try do
|
||||
spawn_workers(schedulers, cache, files, output, options)
|
||||
else
|
||||
{:ok, outcome, info} ->
|
||||
beam_timestamp = Keyword.get(options, :beam_timestamp)
|
||||
{:ok, write_module_binaries(outcome, output, beam_timestamp), info}
|
||||
|
||||
{:error, errors, info} ->
|
||||
{:error, errors, info}
|
||||
after
|
||||
Module.ParallelChecker.stop(cache)
|
||||
end
|
||||
@@ -288,6 +281,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
{outcome, state} =
|
||||
spawn_workers(files, %{}, %{}, [], %{}, [], [], %{
|
||||
beam_timestamp: Keyword.get(options, :beam_timestamp),
|
||||
dest: Keyword.get(options, :dest),
|
||||
each_cycle: Keyword.get(options, :each_cycle, fn -> {:runtime, [], []} end),
|
||||
each_file: Keyword.get(options, :each_file, fn _, _ -> :ok end) |> each_file(),
|
||||
@@ -345,9 +339,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
## Verification
|
||||
|
||||
defp verify_modules(result, compile_warnings, dependent_modules, state) do
|
||||
modules = write_module_binaries(result, state.output, state.beam_timestamp)
|
||||
runtime_warnings = maybe_check_modules(result, dependent_modules, state)
|
||||
info = %{compile_warnings: Enum.reverse(compile_warnings), runtime_warnings: runtime_warnings}
|
||||
{{:ok, result, info}, state}
|
||||
{{:ok, modules, info}, state}
|
||||
end
|
||||
|
||||
defp maybe_check_modules(result, runtime_modules, state) do
|
||||
|
||||
@@ -1133,7 +1133,7 @@ defmodule List do
|
||||
|
||||
Please check the given list or call inspect/1 to get the list representation, got:
|
||||
|
||||
#{inspect(list)}
|
||||
#{inspect(list, charlists: :as_lists)}
|
||||
"""
|
||||
else
|
||||
result when is_binary(result) ->
|
||||
@@ -1185,7 +1185,7 @@ defmodule List do
|
||||
|
||||
Please check the given list or call inspect/1 to get the list representation, got:
|
||||
|
||||
#{inspect(list)}
|
||||
#{inspect(list, charlists: :as_lists)}
|
||||
"""
|
||||
else
|
||||
result when is_list(result) ->
|
||||
|
||||
@@ -1264,8 +1264,115 @@ defmodule Module.Types.Descr do
|
||||
|
||||
defp map_only?(descr), do: empty?(Map.delete(descr, :map))
|
||||
|
||||
# Union is list concatenation
|
||||
defp map_union(dnf1, dnf2), do: dnf1 ++ (dnf2 -- dnf1)
|
||||
defp map_union(dnf1, dnf2) do
|
||||
# Union is just concatenation, but we rely on some optimization strategies to
|
||||
# avoid the list to grow when possible
|
||||
|
||||
# first pass trying to identify patterns where two maps can be fused as one
|
||||
with [map1] <- dnf1,
|
||||
[map2] <- dnf2,
|
||||
optimized when optimized != nil <- maybe_optimize_map_union(map1, map2) do
|
||||
[optimized]
|
||||
else
|
||||
# otherwise we just concatenate and remove structural duplicates
|
||||
_ -> dnf1 ++ (dnf2 -- dnf1)
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_optimize_map_union({tag1, pos1, []} = map1, {tag2, pos2, []} = map2) do
|
||||
case map_union_optimization_strategy(tag1, pos1, tag2, pos2) do
|
||||
:all_equal ->
|
||||
map1
|
||||
|
||||
:any_map ->
|
||||
{:open, %{}, []}
|
||||
|
||||
{:one_key_difference, key, v1, v2} ->
|
||||
new_pos = Map.put(pos1, key, union(v1, v2))
|
||||
{tag1, new_pos, []}
|
||||
|
||||
:left_subtype_of_right ->
|
||||
map2
|
||||
|
||||
:right_subtype_of_left ->
|
||||
map1
|
||||
|
||||
nil ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_optimize_map_union(_, _), do: nil
|
||||
|
||||
defp map_union_optimization_strategy(tag1, pos1, tag2, pos2)
|
||||
defp map_union_optimization_strategy(tag, pos, tag, pos), do: :all_equal
|
||||
defp map_union_optimization_strategy(:open, empty, _, _) when empty == %{}, do: :any_map
|
||||
defp map_union_optimization_strategy(_, _, :open, empty) when empty == %{}, do: :any_map
|
||||
|
||||
defp map_union_optimization_strategy(tag, pos1, tag, pos2)
|
||||
when map_size(pos1) == map_size(pos2) do
|
||||
:maps.iterator(pos1)
|
||||
|> :maps.next()
|
||||
|> do_map_union_optimization_strategy(pos2, :all_equal)
|
||||
end
|
||||
|
||||
defp map_union_optimization_strategy(:open, pos1, _, pos2)
|
||||
when map_size(pos1) <= map_size(pos2) do
|
||||
:maps.iterator(pos1)
|
||||
|> :maps.next()
|
||||
|> do_map_union_optimization_strategy(pos2, :right_subtype_of_left)
|
||||
end
|
||||
|
||||
defp map_union_optimization_strategy(_, pos1, :open, pos2)
|
||||
when map_size(pos1) >= map_size(pos2) do
|
||||
:maps.iterator(pos2)
|
||||
|> :maps.next()
|
||||
|> do_map_union_optimization_strategy(pos1, :right_subtype_of_left)
|
||||
|> case do
|
||||
:right_subtype_of_left -> :left_subtype_of_right
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp map_union_optimization_strategy(_, _, _, _), do: nil
|
||||
|
||||
defp do_map_union_optimization_strategy(:none, _, status), do: status
|
||||
|
||||
defp do_map_union_optimization_strategy({key, v1, iterator}, pos2, status) do
|
||||
with %{^key => v2} <- pos2,
|
||||
next_status when next_status != nil <- map_union_next_strategy(key, v1, v2, status) do
|
||||
do_map_union_optimization_strategy(:maps.next(iterator), pos2, next_status)
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp map_union_next_strategy(key, v1, v2, status)
|
||||
|
||||
# structurally equal values do not impact the ongoing strategy
|
||||
defp map_union_next_strategy(_key, same, same, status), do: status
|
||||
|
||||
defp map_union_next_strategy(key, v1, v2, :all_equal) do
|
||||
if key != :__struct__, do: {:one_key_difference, key, v1, v2}
|
||||
end
|
||||
|
||||
defp map_union_next_strategy(_key, v1, v2, {:one_key_difference, _, d1, d2}) do
|
||||
# we have at least two key differences now, we switch strategy
|
||||
# if both are subtypes in one direction, keep checking
|
||||
cond do
|
||||
subtype?(d1, d2) and subtype?(v1, v2) -> :left_subtype_of_right
|
||||
subtype?(d2, d1) and subtype?(v2, v1) -> :right_subtype_of_left
|
||||
true -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp map_union_next_strategy(_key, v1, v2, :left_subtype_of_right) do
|
||||
if subtype?(v1, v2), do: :left_subtype_of_right
|
||||
end
|
||||
|
||||
defp map_union_next_strategy(_key, v1, v2, :right_subtype_of_left) do
|
||||
if subtype?(v2, v1), do: :right_subtype_of_left
|
||||
end
|
||||
|
||||
# Given two unions of maps, intersects each pair of maps.
|
||||
defp map_intersection(dnf1, dnf2) do
|
||||
@@ -1275,7 +1382,16 @@ defmodule Module.Types.Descr do
|
||||
acc ->
|
||||
try do
|
||||
{tag, fields} = map_literal_intersection(tag1, pos1, tag2, pos2)
|
||||
[{tag, fields, negs1 ++ negs2} | acc]
|
||||
entry = {tag, fields, negs1 ++ negs2}
|
||||
|
||||
# Imagine a, b, c, where a is closed and b and c are open with
|
||||
# no keys in common. The result in both cases will be a and we
|
||||
# want to avoid adding duplicates, especially as intersection
|
||||
# is a cartesian product.
|
||||
case :lists.member(entry, acc) do
|
||||
true -> acc
|
||||
false -> [entry | acc]
|
||||
end
|
||||
catch
|
||||
:empty -> acc
|
||||
end
|
||||
@@ -1738,29 +1854,19 @@ defmodule Module.Types.Descr do
|
||||
|
||||
defp map_non_negated_fuse(maps) do
|
||||
Enum.reduce(maps, [], fn map, acc ->
|
||||
case Enum.split_while(acc, &non_fusible_maps?(map, &1)) do
|
||||
{_, []} ->
|
||||
[map | acc]
|
||||
|
||||
{others, [match | rest]} ->
|
||||
fused = map_non_negated_fuse_pair(map, match)
|
||||
others ++ [fused | rest]
|
||||
end
|
||||
fuse_with_first_fusible(map, acc)
|
||||
end)
|
||||
end
|
||||
|
||||
# Two maps are fusible if they differ in at most one element.
|
||||
defp non_fusible_maps?({_, fields1, []}, {_, fields2, []}) do
|
||||
Enum.count_until(fields1, fn {key, value} -> Map.fetch!(fields2, key) != value end, 2) > 1
|
||||
end
|
||||
defp fuse_with_first_fusible(map, []), do: [map]
|
||||
|
||||
defp map_non_negated_fuse_pair({tag, fields1, []}, {_, fields2, []}) do
|
||||
fields =
|
||||
symmetrical_merge(fields1, fields2, fn _k, v1, v2 ->
|
||||
if v1 == v2, do: v1, else: union(v1, v2)
|
||||
end)
|
||||
|
||||
{tag, fields, []}
|
||||
defp fuse_with_first_fusible(map, [candidate | rest]) do
|
||||
if fused = maybe_optimize_map_union(map, candidate) do
|
||||
# we found a fusible candidate, we're done
|
||||
[fused | rest]
|
||||
else
|
||||
[candidate | fuse_with_first_fusible(map, rest)]
|
||||
end
|
||||
end
|
||||
|
||||
# If all fields are the same except one, we can optimize map difference.
|
||||
@@ -1931,8 +2037,16 @@ defmodule Module.Types.Descr do
|
||||
reduce: [] do
|
||||
acc ->
|
||||
case tuple_literal_intersection(tag1, elements1, tag2, elements2) do
|
||||
{tag, fields} -> [{tag, fields, negs1 ++ negs2} | acc]
|
||||
:empty -> acc
|
||||
{tag, elements} ->
|
||||
entry = {tag, elements, negs1 ++ negs2}
|
||||
|
||||
case :lists.member(entry, acc) do
|
||||
true -> acc
|
||||
false -> [entry | acc]
|
||||
end
|
||||
|
||||
:empty ->
|
||||
acc
|
||||
end
|
||||
end
|
||||
|> case do
|
||||
|
||||
@@ -335,6 +335,10 @@ defmodule Module.Types.Pattern do
|
||||
{type, context}
|
||||
end
|
||||
|
||||
def of_match_var({:<<>>, _meta, args}, _expected, _expr, stack, context) do
|
||||
{binary(), Of.binary(args, :match, stack, context)}
|
||||
end
|
||||
|
||||
def of_match_var(ast, expected, expr, stack, context) do
|
||||
of_match(ast, expected, expr, :default, stack, context)
|
||||
end
|
||||
|
||||
@@ -110,7 +110,7 @@ When using `IEx`, you may pass `--dbg pry` as an option to "stop" the code execu
|
||||
$ iex --dbg pry
|
||||
```
|
||||
|
||||
Or to debug inside a of a project:
|
||||
Or to debug inside of a project:
|
||||
|
||||
```console
|
||||
$ iex --dbg pry -S mix
|
||||
|
||||
@@ -222,7 +222,7 @@ store_definition(Meta, Kind, CheckClauses, Name, Arity, DefaultsArgs, Guards, Bo
|
||||
[store_definition(none, Kind, Meta, Name, length(DefaultArgs), File,
|
||||
Module, 0, [Default]) || {_, DefaultArgs, _, _} = Default <- Defaults],
|
||||
|
||||
run_on_definition_callbacks(Kind, Module, Name, DefaultsArgs, Guards, Body, E),
|
||||
run_on_definition_callbacks(Meta, Kind, Module, Name, DefaultsArgs, Guards, Body, E),
|
||||
Tuple.
|
||||
|
||||
env_for_expansion(Kind, Tuple, E) when Kind =:= defmacro; Kind =:= defmacrop ->
|
||||
@@ -267,10 +267,13 @@ def_to_clauses(Kind, Meta, Args, Guards, Body, E) ->
|
||||
elixir_errors:file_error(Meta, E, elixir_expand, {missing_option, Kind, [do]})
|
||||
end.
|
||||
|
||||
run_on_definition_callbacks(Kind, Module, Name, Args, Guards, Body, E) ->
|
||||
run_on_definition_callbacks(Meta, Kind, Module, Name, Args, Guards, Body, E) ->
|
||||
{_, Bag} = elixir_module:data_tables(Module),
|
||||
Callbacks = ets:lookup_element(Bag, {accumulate, on_definition}, 2),
|
||||
_ = [Mod:Fun(E, Kind, Name, Args, Guards, Body) || {Mod, Fun} <- lists:reverse(Callbacks)],
|
||||
_ = [begin
|
||||
elixir_env:trace({remote_function, Meta, Mod, Fun, 6}, E),
|
||||
Mod:Fun(E, Kind, Name, Args, Guards, Body)
|
||||
end || {Mod, Fun} <- lists:reverse(Callbacks)],
|
||||
ok.
|
||||
|
||||
store_definition(CheckClauses, Kind, Meta, Name, Arity, File, Module, Defaults, Clauses)
|
||||
|
||||
@@ -204,7 +204,9 @@ do_expand_import(Result, Meta, Name, Arity, Module, E, Trace) ->
|
||||
{import, Receiver} ->
|
||||
case expand_require(true, Meta, Receiver, Name, Arity, E, Trace) of
|
||||
{macro, _, _} = Response -> Response;
|
||||
error -> {function, Receiver, Name}
|
||||
error ->
|
||||
Trace andalso elixir_env:trace({remote_function, Meta, Receiver, Name, Arity}, E),
|
||||
{function, Receiver, Name}
|
||||
end;
|
||||
false when Module == ?kernel ->
|
||||
case elixir_rewrite:inline(Module, Name, Arity) of
|
||||
|
||||
@@ -92,6 +92,9 @@ elixir_to_erl(Tree, Ann) when is_binary(Tree) ->
|
||||
%% considers a string in a binary to be encoded in latin1, so the bytes
|
||||
%% are not changed in any fashion.
|
||||
{bin, Ann, [{bin_element, Ann, {string, Ann, binary_to_list(Tree)}, default, default}]};
|
||||
elixir_to_erl(Tree, Ann) when is_bitstring(Tree) ->
|
||||
Segments = [elixir_to_erl_bitstring_segment(X, Ann) || X <- bitstring_to_list(Tree)],
|
||||
{bin, Ann, Segments};
|
||||
elixir_to_erl(Tree, Ann) when is_function(Tree) ->
|
||||
case (erlang:fun_info(Tree, type) == {type, external}) andalso
|
||||
(erlang:fun_info(Tree, env) == {env, []}) of
|
||||
@@ -111,6 +114,13 @@ elixir_to_erl(Tree, Ann) ->
|
||||
elixir_to_erl_cons([H | T], Ann) -> {cons, Ann, elixir_to_erl(H, Ann), elixir_to_erl_cons(T, Ann)};
|
||||
elixir_to_erl_cons(T, Ann) -> elixir_to_erl(T, Ann).
|
||||
|
||||
elixir_to_erl_bitstring_segment(Int, Ann) when is_integer(Int) ->
|
||||
{bin_element, Ann, {integer, Ann, Int}, default, [integer]};
|
||||
elixir_to_erl_bitstring_segment(Rest, Ann) when is_bitstring(Rest) ->
|
||||
Size = bit_size(Rest),
|
||||
<<Int:Size>> = Rest,
|
||||
{bin_element, Ann, {integer, Ann, Int}, {integer, Ann, Size}, [integer]}.
|
||||
|
||||
%% Returns a scope for translation.
|
||||
|
||||
scope(_Meta, ExpandCaptures) ->
|
||||
|
||||
@@ -20,7 +20,15 @@ reduce_clauses([], Acc, OldStack, SAcc, _S) ->
|
||||
{lists:reverse(Acc), SAcc#elixir_erl{stacktrace=OldStack}}.
|
||||
|
||||
each_clause({'catch', Meta, Raw, Expr}, S) ->
|
||||
{Match, Guards} = elixir_utils:extract_splat_guards(Raw),
|
||||
{Args, Guards} = elixir_utils:extract_splat_guards(Raw),
|
||||
|
||||
Match =
|
||||
%% Elixir v1.17 and earlier emitted single argument
|
||||
%% and may still be processed via debug_info
|
||||
case Args of
|
||||
[X] -> [throw, X];
|
||||
[X, Y] -> [X, Y]
|
||||
end,
|
||||
|
||||
{{clause, Line, [TKind, TMatches], TGuards, TBody}, TS} =
|
||||
elixir_erl_clauses:clause(?ann(Meta), fun elixir_erl_pass:translate_args/3, Match, Expr, Guards, S),
|
||||
|
||||
@@ -293,7 +293,7 @@ expand({'&', Meta, [{'/', ArityMeta, [{super, SuperMeta, Context}, Arity]} = Exp
|
||||
|
||||
case resolve_super(Meta, Arity, E) of
|
||||
{Kind, Name, _} when Kind == def; Kind == defp ->
|
||||
{{'&', Meta, [{'/', ArityMeta, [{Name, SuperMeta, Context}, Arity]}]}, S, E};
|
||||
expand({'&', Meta, [{'/', ArityMeta, [{Name, SuperMeta, Context}, Arity]}]}, S, E);
|
||||
_ ->
|
||||
expand_fn_capture(Meta, Expr, S, E)
|
||||
end;
|
||||
@@ -343,7 +343,9 @@ expand({with, Meta, [_ | _] = Args}, S, E) ->
|
||||
|
||||
expand({super, Meta, Args}, S, E) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, "super", S, E),
|
||||
{Kind, Name, _} = resolve_super(Meta, length(Args), E),
|
||||
Arity = length(Args),
|
||||
{Kind, Name, _} = resolve_super(Meta, Arity, E),
|
||||
elixir_env:trace({local_function, Meta, Name, Arity}, E),
|
||||
{EArgs, SA, EA} = expand_args(Args, S, E),
|
||||
{{super, [{super, {Kind, Name}} | Meta], EArgs}, SA, EA};
|
||||
|
||||
@@ -899,8 +901,7 @@ expand_local(Meta, Name, Args, S, #{function := Function, context := Context} =
|
||||
module_error(Meta, E, ?MODULE, {invalid_local_invocation, elixir_utils:guard_info(S), {Name, Meta, Args}});
|
||||
|
||||
nil ->
|
||||
Arity = length(Args),
|
||||
elixir_env:trace({local_function, Meta, Name, Arity}, E)
|
||||
elixir_env:trace({local_function, Meta, Name, length(Args)}, E)
|
||||
end,
|
||||
|
||||
{EArgs, SA, EA} = expand_args(Args, S, E),
|
||||
|
||||
@@ -155,6 +155,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
|
||||
put_compiler_modules([Module | CompilerModules]),
|
||||
{Result, ModuleE, CallbackE} = eval_form(Line, Module, DataBag, Block, Vars, Prune, E),
|
||||
CheckerInfo = checker_info(),
|
||||
BeamLocation = beam_location(ModuleAsCharlist),
|
||||
|
||||
{Binary, PersistedAttributes, Autoload} =
|
||||
elixir_erl_compiler:spawn(fun() ->
|
||||
@@ -177,7 +178,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
|
||||
'Elixir.Module':'__check_attributes__'(E, DataSet, DataBag),
|
||||
|
||||
AfterVerify = bag_lookup_element(DataBag, {accumulate, after_verify}, 2),
|
||||
[elixir_env:trace({remote_function, [], VerifyMod, VerifyFun, 1}, CallbackE) ||
|
||||
[elixir_env:trace({remote_function, [{line, Line}], VerifyMod, VerifyFun, 1}, CallbackE) ||
|
||||
{VerifyMod, VerifyFun} <- AfterVerify],
|
||||
|
||||
%% Ensure there are no errors before we infer types
|
||||
@@ -219,7 +220,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
|
||||
{Binary, PersistedAttributes, Autoload}
|
||||
end),
|
||||
|
||||
Autoload andalso code:load_binary(Module, beam_location(ModuleAsCharlist), Binary),
|
||||
Autoload andalso code:load_binary(Module, BeamLocation, Binary),
|
||||
put_compiler_modules(CompilerModules),
|
||||
eval_callbacks(Line, DataBag, after_compile, [CallbackE, Binary], CallbackE),
|
||||
elixir_env:trace({on_module, Binary, none}, ModuleE),
|
||||
@@ -294,7 +295,8 @@ validate_on_load_attribute({on_load, Def}, Defs, Bag, Line, E) ->
|
||||
elixir_errors:module_error([{line, Line}], E, ?MODULE, {undefined_function, on_load, Def});
|
||||
{_Def, Kind, _Meta, _Clauses} when Kind == defmacro; Kind == defmacrop ->
|
||||
elixir_errors:module_error([{line, Line}], E, ?MODULE, {bad_macro, on_load, Def});
|
||||
{_Def, Kind, _Meta, _Clauses} ->
|
||||
{{Name, Arity}, Kind, _Meta, _Clauses} ->
|
||||
elixir_env:trace({local_function, [{line, Line}], Name, Arity}, E),
|
||||
(Kind == defp) andalso ets:insert(Bag, {used_private, Def})
|
||||
end;
|
||||
validate_on_load_attribute(false, _Defs, _Bag, _Line, _E) -> ok.
|
||||
|
||||
@@ -160,10 +160,19 @@ do_escape(BitString, _) when is_bitstring(BitString) ->
|
||||
end;
|
||||
|
||||
do_escape(Map, Q) when is_map(Map) ->
|
||||
TT = do_quote(lists:sort(maps:to_list(Map)), Q),
|
||||
TT =
|
||||
[if
|
||||
is_reference(V) ->
|
||||
argument_error(<<('Elixir.Kernel':inspect(Map, []))/binary, " contains a reference (",
|
||||
('Elixir.Kernel':inspect(V, []))/binary, ") and therefore it cannot be escaped ",
|
||||
"(it must be defined within a function instead). ", (bad_escape_hint())/binary>>);
|
||||
true ->
|
||||
{do_quote(K, Q), do_quote(V, Q)}
|
||||
end || {K, V} <- lists:sort(maps:to_list(Map))],
|
||||
{'%{}', [], TT};
|
||||
|
||||
do_escape([], _) -> [];
|
||||
do_escape([], _) ->
|
||||
[];
|
||||
|
||||
do_escape([H | T], #elixir_quote{unquote=false} = Q) ->
|
||||
do_quote_simple_list(T, do_quote(H, Q), Q);
|
||||
@@ -195,8 +204,11 @@ do_escape(Other, _) ->
|
||||
|
||||
bad_escape(Arg) ->
|
||||
argument_error(<<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary, ". ",
|
||||
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
|
||||
"PIDs and remote functions in the format &Mod.fun/arity">>).
|
||||
(bad_escape_hint())/binary>>).
|
||||
|
||||
bad_escape_hint() ->
|
||||
<<"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
|
||||
"PIDs and remote functions in the format &Mod.fun/arity">>.
|
||||
|
||||
%% Quote entry points
|
||||
|
||||
|
||||
@@ -46,6 +46,12 @@ defmodule ExceptionTest do
|
||||
{:io, :put_chars, [self(), <<222>>],
|
||||
[error_info: %{module: __MODULE__, function: :dummy_error_extras}]}
|
||||
])
|
||||
|
||||
assert %ErlangError{original: {:failed_load_cacerts, :enoent}, reason: ": this is chardata"} =
|
||||
Exception.normalize(:error, {:failed_load_cacerts, :enoent}, [
|
||||
{:pubkey_os_cacerts, :get, 0,
|
||||
[error_info: %{module: __MODULE__, function: :dummy_error_chardata}]}
|
||||
])
|
||||
end
|
||||
|
||||
test "format/2 without stacktrace" do
|
||||
@@ -1026,4 +1032,8 @@ defmodule ExceptionTest do
|
||||
end
|
||||
|
||||
def dummy_error_extras(_exception, _stacktrace), do: %{general: "foo"}
|
||||
|
||||
def dummy_error_chardata(_exception, _stacktrace) do
|
||||
%{general: ~c"this is " ++ [~c"chardata"], reason: ~c"this " ++ [~c"too"]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -278,10 +278,15 @@ defmodule Inspect.ListTest do
|
||||
|
||||
test "printable limit" do
|
||||
assert inspect(~c"hello world", printable_limit: 4) == ~s(~c"hell" ++ ...)
|
||||
assert inspect(~c"hello", printable_limit: :infinity) == ~s(~c"hello")
|
||||
# Non printable characters after the limit don't matter
|
||||
assert inspect(~c"hello world" ++ [0], printable_limit: 4) == ~s(~c"hell" ++ ...)
|
||||
# Non printable strings aren't affected by printable limit
|
||||
assert inspect([0, 1, 2, 3, 4], printable_limit: 3) == ~s([0, 1, 2, 3, 4])
|
||||
# Ensure a non-printable value after limit does not crash
|
||||
assert inspect(~c"hello world" ++ [nil], printable_limit: 4) == ~s(~c"hell" ++ ...)
|
||||
# Ensure a non-printable value after default limit does not hang
|
||||
assert String.ends_with?(inspect(List.duplicate(?a, 5000) ++ [nil]), "\" ++ ...")
|
||||
end
|
||||
|
||||
test "keyword" do
|
||||
@@ -306,10 +311,20 @@ defmodule Inspect.ListTest do
|
||||
assert inspect([0], charlists: :infer) == "[0]"
|
||||
end
|
||||
|
||||
test "opt as strings" do
|
||||
test "opt as charlists" do
|
||||
assert inspect(~c"john" ++ [0] ++ ~c"doe", charlists: :as_charlists) == ~s(~c"john\\0doe")
|
||||
assert inspect(~c"john", charlists: :as_charlists) == ~s(~c"john")
|
||||
assert inspect([0], charlists: :as_charlists) == ~s(~c"\\0")
|
||||
assert inspect([nil], charlists: :as_charlists) == "[nil]"
|
||||
assert inspect([0xD800], charlists: :as_charlists) == "[55296]"
|
||||
assert inspect([1_114_112], charlists: :as_charlists) == "[1114112]"
|
||||
assert inspect([[?a]], charlists: :as_charlists) == ~s([~c"a"])
|
||||
|
||||
assert inspect([?a, ?b, ?c | nil], charlists: :as_charlists, printable_limit: 4) ==
|
||||
"[97, 98, 99 | nil]"
|
||||
|
||||
assert inspect([?a, ?b, ?c | nil], charlists: :as_charlists, printable_limit: 3) ==
|
||||
~s(~c"abc" ++ ...)
|
||||
end
|
||||
|
||||
test "opt as lists" do
|
||||
|
||||
@@ -517,5 +517,55 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
refute URI in exports
|
||||
refute URI in runtime
|
||||
end
|
||||
|
||||
test "imported functions from quote adds dependencies" do
|
||||
{{compile, exports, runtime, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
defmodule Kernel.LexicalTrackerTest.QuotedFun do
|
||||
import URI
|
||||
|
||||
defmacro parse_root() do
|
||||
quote do
|
||||
parse("/")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.LexicalTrackerTest.UsingQuotedFun do
|
||||
require Kernel.LexicalTrackerTest.QuotedFun, as: QF
|
||||
QF.parse_root()
|
||||
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
|
||||
end |> elem(3)
|
||||
""")
|
||||
|
||||
assert URI in compile
|
||||
refute URI in exports
|
||||
refute URI in runtime
|
||||
end
|
||||
|
||||
test "imported macro from quote adds dependencies" do
|
||||
{{compile, exports, runtime, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
defmodule Kernel.LexicalTrackerTest.QuotedMacro do
|
||||
import Config
|
||||
|
||||
defmacro config_env() do
|
||||
quote do
|
||||
config_env()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.LexicalTrackerTest.UsingQuotedMacro do
|
||||
require Kernel.LexicalTrackerTest.QuotedMacro, as: QM
|
||||
def fun(), do: QM.config_env()
|
||||
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
|
||||
end |> elem(3)
|
||||
""")
|
||||
|
||||
assert Config in compile
|
||||
refute Config in exports
|
||||
refute Config in runtime
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,11 +10,15 @@ defmodule Kernel.TracersTest do
|
||||
end
|
||||
|
||||
def trace(event, %Macro.Env{} = env) do
|
||||
send(self(), {event, env})
|
||||
for {pid, _} <- Registry.lookup(__MODULE__, :tracers) do
|
||||
send(pid, {event, env})
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
setup_all do
|
||||
start_supervised!({Registry, keys: :duplicate, name: __MODULE__})
|
||||
Code.put_compiler_option(:tracers, [__MODULE__])
|
||||
|
||||
on_exit(fn ->
|
||||
@@ -22,6 +26,11 @@ defmodule Kernel.TracersTest do
|
||||
end)
|
||||
end
|
||||
|
||||
setup do
|
||||
Registry.register(__MODULE__, :tracers, :unused)
|
||||
:ok
|
||||
end
|
||||
|
||||
test "traces start and stop" do
|
||||
compile_string("""
|
||||
Foo
|
||||
@@ -255,7 +264,7 @@ defmodule Kernel.TracersTest do
|
||||
assert meta[:from_brackets]
|
||||
|
||||
compile_string("""
|
||||
defmodule Foo do
|
||||
defmodule TracerBracketAccess do
|
||||
@foo %{bar: 3}
|
||||
def a() do
|
||||
@foo[:bar]
|
||||
@@ -274,6 +283,75 @@ defmodule Kernel.TracersTest do
|
||||
assert meta[:from_brackets]
|
||||
end
|
||||
|
||||
test "traces on_load" do
|
||||
compile_string("""
|
||||
defmodule TracerOnLoad do
|
||||
@on_load :init
|
||||
def init, do: :ok
|
||||
end
|
||||
""")
|
||||
|
||||
assert_received {{:local_function, meta, :init, 0}, _}
|
||||
assert meta[:line] == 1
|
||||
end
|
||||
|
||||
def __before_compile__(_), do: :ok
|
||||
def __after_compile__(_, _), do: :ok
|
||||
def __after_verify__(_), do: :ok
|
||||
def __on_definition__(_, _, _, _, _, _), do: :ok
|
||||
|
||||
test "traces compile time attributes" do
|
||||
compile_string("""
|
||||
defmodule TracerCompileAttributes do
|
||||
@before_compile Kernel.TracersTest
|
||||
@after_compile Kernel.TracersTest
|
||||
@on_definition Kernel.TracersTest
|
||||
@after_verify Kernel.TracersTest
|
||||
def hello, do: :world
|
||||
end
|
||||
""")
|
||||
|
||||
assert_received {{:remote_function, meta, __MODULE__, :__before_compile__, 1}, _}
|
||||
assert meta[:line] == 1
|
||||
|
||||
assert_received {{:remote_function, meta, __MODULE__, :__after_compile__, 2}, _}
|
||||
assert meta[:line] == 1
|
||||
|
||||
assert_received {{:remote_function, meta, __MODULE__, :__after_verify__, 1}, _}
|
||||
assert meta[:line] == 1
|
||||
|
||||
assert_received {{:remote_function, meta, __MODULE__, :__on_definition__, 6}, _}
|
||||
assert meta[:line] == 6
|
||||
end
|
||||
|
||||
test "traces super" do
|
||||
compile_string("""
|
||||
defmodule TracerOverridable do
|
||||
def local(x), do: x
|
||||
defoverridable [local: 1]
|
||||
def local(x), do: super(x)
|
||||
|
||||
defmacro macro(x), do: x
|
||||
defoverridable [macro: 1]
|
||||
defmacro macro(x), do: super(x)
|
||||
|
||||
def capture(x), do: x
|
||||
defoverridable [capture: 1]
|
||||
def capture(x), do: tap(x, &super/1)
|
||||
|
||||
def capture_arg(x), do: x
|
||||
defoverridable [capture_arg: 1]
|
||||
def capture_arg(x), do: tap(x, &super(&1))
|
||||
end
|
||||
""")
|
||||
|
||||
assert_received {{:local_function, _, :"local (overridable 1)", 1}, _}
|
||||
assert_received {{:local_function, _, :"macro (overridable 1)", 1}, _}
|
||||
assert_received {{:local_function, _, :"capture (overridable 1)", 1}, _}
|
||||
assert_received {{:local_function, _, :"capture_arg (overridable 1)", 1}, _}
|
||||
refute_received {{:local_function, _, _, _}, _}
|
||||
end
|
||||
|
||||
test "does not trace bind quoted twice" do
|
||||
compile_string("""
|
||||
quote bind_quoted: [foo: List.flatten([])] do
|
||||
|
||||
@@ -327,6 +327,12 @@ defmodule ListTest do
|
||||
assert_raise ArgumentError, ~r"cannot convert the given list to a string", fn ->
|
||||
List.to_string([:a, :b])
|
||||
end
|
||||
|
||||
invalid = List.duplicate(?a, 4096) ++ [nil]
|
||||
|
||||
assert_raise ArgumentError, ~r/got:\n\n\[97, 97, .*\.\.\.\]\n\z/s, fn ->
|
||||
List.to_string(invalid)
|
||||
end
|
||||
end
|
||||
|
||||
test "to_charlist/1" do
|
||||
@@ -345,6 +351,12 @@ defmodule ListTest do
|
||||
assert_raise ArgumentError, ~r"cannot convert the given list to a charlist", fn ->
|
||||
List.to_charlist([:a, :b])
|
||||
end
|
||||
|
||||
invalid = List.duplicate(?a, 4096) ++ [nil]
|
||||
|
||||
assert_raise ArgumentError, ~r/got:\n\n\[97, 97, .*\.\.\.\]\n\z/s, fn ->
|
||||
List.to_charlist(invalid)
|
||||
end
|
||||
end
|
||||
|
||||
describe "myers_difference/2" do
|
||||
|
||||
@@ -136,6 +136,12 @@ defmodule MacroTest do
|
||||
test "does not add context to quote" do
|
||||
assert Macro.escape({:quote, [], [[do: :foo]]}) == {:{}, [], [:quote, [], [[do: :foo]]]}
|
||||
end
|
||||
|
||||
test "inspects container when a reference cannot be escaped" do
|
||||
assert_raise ArgumentError, ~r"~r/foo/ contains a reference", fn ->
|
||||
Macro.escape(%{~r/foo/ | re_pattern: make_ref()})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "expand_once/2" do
|
||||
|
||||
@@ -395,6 +395,15 @@ defmodule MapTest do
|
||||
assert quoted == {:%, [], [User, {:%{}, [], [{:foo, 1}]}]}
|
||||
end
|
||||
|
||||
test "structs with bitstring defaults" do
|
||||
defmodule WithBitstring do
|
||||
defstruct bitstring: <<255, 127::7>>
|
||||
end
|
||||
|
||||
info = Macro.struct_info!(WithBitstring, __ENV__)
|
||||
assert info == [%{default: <<255, 127::7>>, field: :bitstring}]
|
||||
end
|
||||
|
||||
test "defstruct can only be used once in a module" do
|
||||
message =
|
||||
"defstruct has already been called for TestMod, " <>
|
||||
|
||||
@@ -101,6 +101,46 @@ defmodule Module.Types.DescrTest do
|
||||
assert union(difference(list(term()), list(integer())), list(integer()))
|
||||
|> equal?(list(term()))
|
||||
end
|
||||
|
||||
test "optimizations" do
|
||||
# The tests are checking the actual implementation, not the semantics.
|
||||
# This is why we are using structural comparisons.
|
||||
# It's fine to remove these if the implementation changes, but breaking
|
||||
# these might have an important impact on compile times.
|
||||
|
||||
# Optimization one: same tags, all but one key are structurally equal
|
||||
assert union(
|
||||
open_map(a: float(), b: atom()),
|
||||
open_map(a: integer(), b: atom())
|
||||
) == open_map(a: union(float(), integer()), b: atom())
|
||||
|
||||
assert union(
|
||||
closed_map(a: float(), b: atom()),
|
||||
closed_map(a: integer(), b: atom())
|
||||
) == closed_map(a: union(float(), integer()), b: atom())
|
||||
|
||||
# Optimization two: we can tell that one map is a trivial subtype of the other:
|
||||
|
||||
assert union(
|
||||
closed_map(a: term(), b: term()),
|
||||
closed_map(a: float(), b: binary())
|
||||
) == closed_map(a: term(), b: term())
|
||||
|
||||
assert union(
|
||||
open_map(a: term()),
|
||||
closed_map(a: float(), b: binary())
|
||||
) == open_map(a: term())
|
||||
|
||||
assert union(
|
||||
closed_map(a: float(), b: binary()),
|
||||
open_map(a: term())
|
||||
) == open_map(a: term())
|
||||
|
||||
assert union(
|
||||
closed_map(a: term(), b: tuple([term(), term()])),
|
||||
closed_map(a: float(), b: tuple([atom(), binary()]))
|
||||
) == closed_map(a: term(), b: tuple([term(), term()]))
|
||||
end
|
||||
end
|
||||
|
||||
describe "intersection" do
|
||||
|
||||
@@ -251,6 +251,10 @@ defmodule Module.Types.PatternTest do
|
||||
assert typecheck!([<<x::utf8>>], x) == integer()
|
||||
end
|
||||
|
||||
test "nested" do
|
||||
assert typecheck!([<<0, <<x::bitstring>>::binary>>], x) == binary()
|
||||
end
|
||||
|
||||
test "error" do
|
||||
assert typeerror!([<<x::binary-size(2), x::float>>], x) == ~l"""
|
||||
incompatible types assigned to "x":
|
||||
|
||||
@@ -5,6 +5,23 @@ defmodule RegexTest do
|
||||
|
||||
doctest Regex
|
||||
|
||||
if System.otp_release() >= "28" do
|
||||
test "module attribute" do
|
||||
defmodule ModAttr do
|
||||
@regex ~r/example/
|
||||
def regex, do: @regex
|
||||
|
||||
@bare_regex :erlang.term_to_binary(@regex)
|
||||
def bare_regex, do: :erlang.binary_to_term(@bare_regex)
|
||||
|
||||
# We don't rewrite outside of functions
|
||||
assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern
|
||||
end
|
||||
|
||||
assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern
|
||||
end
|
||||
end
|
||||
|
||||
test "multiline" do
|
||||
refute Regex.match?(~r/^b$/, "a\nb\nc")
|
||||
assert Regex.match?(~r/^b$/m, "a\nb\nc")
|
||||
|
||||
@@ -178,38 +178,38 @@ defmodule ExUnit.CaptureIOTest do
|
||||
|
||||
test "with get password" do
|
||||
capture_io(fn ->
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("", fn ->
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("abc", fn ->
|
||||
assert :io.get_password() == "abc"
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == "abc"
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("abc\n", fn ->
|
||||
assert :io.get_password() == "abc\n"
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == "abc\n"
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("\n", fn ->
|
||||
assert :io.get_password() == "\n"
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == "\n"
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("a\nb", fn ->
|
||||
assert :io.get_password() == "a\n"
|
||||
assert :io.get_password() == "b"
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == "a\n"
|
||||
assert :io.get_password(Process.group_leader()) == "b"
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("あい\nう", fn ->
|
||||
assert :io.get_password() == "あい\n"
|
||||
assert :io.get_password() == "う"
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == "あい\n"
|
||||
assert :io.get_password(Process.group_leader()) == "う"
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ defmodule IEx.Helpers do
|
||||
* `pid/3` - creates a PID with the 3 integer arguments passed
|
||||
* `port/1` - creates a port from a string
|
||||
* `port/2` - creates a port with the 2 non-negative integers passed
|
||||
* `process_info/1` - returns information about the given process
|
||||
* `pwd/0` - prints the current working directory
|
||||
* `r/1` - recompiles the given module's source file
|
||||
* `recompile/0` - recompiles the current project
|
||||
@@ -817,6 +818,168 @@ defmodule IEx.Helpers do
|
||||
|
||||
defp pad_key(key), do: String.pad_trailing(key, 21, " ")
|
||||
|
||||
@process_info_keys_and_labels [
|
||||
{:initial_call, "Initial call"},
|
||||
{:dictionary, "Dictionary"},
|
||||
{:registered_name, "Registered name"},
|
||||
{:current_function, "Current function"},
|
||||
{:status, "Status"},
|
||||
{:message_queue_len, "Message queue length"},
|
||||
{:trap_exit, "Trap exit"},
|
||||
{:priority, "Priority"},
|
||||
{:group_leader, "Group leader"},
|
||||
{:reductions, "Reductions"},
|
||||
{:links, "Links"},
|
||||
{:monitors, "Monitors"},
|
||||
{:memory, "Memory"},
|
||||
{:total_heap_size, "Total heap size"},
|
||||
{:heap_size, "Heap size"},
|
||||
{:stack_size, "Stack size"},
|
||||
{:current_stacktrace, "Current stacktrace"}
|
||||
]
|
||||
@process_info_keys Enum.map(@process_info_keys_and_labels, fn {key, _} -> key end)
|
||||
@process_info_label_mapping Map.new(@process_info_keys_and_labels)
|
||||
|
||||
@doc """
|
||||
Prints information about the given process.
|
||||
|
||||
Includes a generic overview and details such as the linked and monitored processes,
|
||||
the memory usage and the current stacktrace.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> process_info(self())
|
||||
...
|
||||
iex> process_info({:via, Registry, {MyApp.Registry, :name}})
|
||||
...
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
def process_info(pid) do
|
||||
with pid when is_pid(pid) <- GenServer.whereis(pid),
|
||||
info when is_list(info) <-
|
||||
:erpc.call(node(pid), :erlang, :process_info, [pid, @process_info_keys]) do
|
||||
info = Map.new(info)
|
||||
|
||||
IO.puts(IEx.color(:eval_result, ["\n# Process ", inspect(pid)]))
|
||||
|
||||
print_process_overview(info)
|
||||
print_process_links(info[:links])
|
||||
print_process_monitors(info[:monitors])
|
||||
print_process_memory(info)
|
||||
print_process_stacktrace(info[:current_stacktrace])
|
||||
else
|
||||
_ ->
|
||||
IO.puts(
|
||||
IEx.color(
|
||||
:eval_error,
|
||||
"Failed to get process info. Either the process was not found or is not alive."
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
dont_display_result()
|
||||
end
|
||||
|
||||
defp print_process_overview(info) do
|
||||
print_pane("Overview")
|
||||
|
||||
for key <- [
|
||||
:initial_call,
|
||||
:current_function,
|
||||
:registered_name,
|
||||
:status,
|
||||
:message_queue_len,
|
||||
:group_leader,
|
||||
:priority,
|
||||
:trap_exit,
|
||||
:reductions
|
||||
] do
|
||||
print_entry(
|
||||
@process_info_label_mapping[key],
|
||||
inspect(info[key], printable_limit: 256, limit: 5)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp print_process_links([]), do: :ok
|
||||
|
||||
defp print_process_links(ports_and_pids) do
|
||||
print_pane("Links")
|
||||
|
||||
for link <- ports_and_pids do
|
||||
print_entry(inspect(link), pid_or_port_details(link))
|
||||
end
|
||||
end
|
||||
|
||||
defp print_process_monitors([]), do: :ok
|
||||
|
||||
defp print_process_monitors(monitors) do
|
||||
print_pane("Monitors")
|
||||
|
||||
for {_, pid_or_port} <- monitors do
|
||||
print_entry(inspect(pid_or_port), pid_or_port_details(pid_or_port))
|
||||
end
|
||||
end
|
||||
|
||||
defp print_process_memory(info) do
|
||||
print_pane("Memory")
|
||||
|
||||
for key <- [:memory, :total_heap_size, :heap_size, :stack_size] do
|
||||
print_entry(@process_info_label_mapping[key], format_bytes(info[key]))
|
||||
end
|
||||
end
|
||||
|
||||
defp print_process_stacktrace([]), do: :ok
|
||||
|
||||
defp print_process_stacktrace(stacktrace) do
|
||||
print_pane("Current stacktrace")
|
||||
|
||||
IO.puts(IEx.color(:eval_info, Exception.format_stacktrace(stacktrace)))
|
||||
end
|
||||
|
||||
defp pid_or_port_details(pid) when is_pid(pid), do: to_process_details(pid)
|
||||
defp pid_or_port_details(name) when is_atom(name), do: to_process_details(name)
|
||||
defp pid_or_port_details(port) when is_port(port), do: to_port_details(port)
|
||||
defp pid_or_port_details(reference) when is_reference(reference), do: reference
|
||||
|
||||
defp to_process_details(pid) when is_pid(pid) do
|
||||
case Process.info(pid, [:initial_call, :dictionary, :registered_name]) do
|
||||
[{:initial_call, initial_call}, {:dictionary, dictionary}, {:registered_name, name}] ->
|
||||
initial_call = Keyword.get(dictionary, :"$initial_call", initial_call)
|
||||
|
||||
format_registered_name(name) ||
|
||||
format_process_label(Keyword.get(dictionary, :"$process_label")) ||
|
||||
format_initial_call(initial_call)
|
||||
|
||||
_ ->
|
||||
"-"
|
||||
end
|
||||
end
|
||||
|
||||
defp to_process_details(name) when is_atom(name) do
|
||||
Process.whereis(name)
|
||||
|> to_process_details()
|
||||
end
|
||||
|
||||
defp format_process_label(nil), do: nil
|
||||
defp format_process_label(label) when is_binary(label), do: label
|
||||
defp format_process_label(label), do: inspect(label)
|
||||
|
||||
defp format_registered_name([]), do: nil
|
||||
defp format_registered_name(name), do: inspect(name)
|
||||
|
||||
defp format_initial_call({:supervisor, mod, arity}), do: Exception.format_mfa(mod, :init, arity)
|
||||
defp format_initial_call({m, f, a}), do: Exception.format_mfa(m, f, a)
|
||||
defp format_initial_call(nil), do: nil
|
||||
|
||||
defp to_port_details(port) when is_port(port) do
|
||||
case Port.info(port, :name) do
|
||||
{:name, name} -> name
|
||||
_ -> "-"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Clears out all messages sent to the shell's inbox and prints them out.
|
||||
"""
|
||||
|
||||
@@ -1625,6 +1625,37 @@ defmodule IEx.HelpersTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "process_info/1" do
|
||||
test "returns information about a process" do
|
||||
result = capture_io(fn -> process_info(self()) end)
|
||||
|
||||
assert result =~ "Process #{inspect(self())}"
|
||||
assert result =~ "## Overview"
|
||||
assert result =~ "Initial call"
|
||||
assert result =~ "## Memory"
|
||||
assert result =~ "## Current stacktrace"
|
||||
assert result =~ "IEx.Helpers.process_info/1"
|
||||
|
||||
refute result =~ "## Links"
|
||||
refute result =~ "## Monitors"
|
||||
end
|
||||
|
||||
test "includes process links and monitors" do
|
||||
pid =
|
||||
spawn_link(fn ->
|
||||
Process.register(self(), :iex_process_info_link_test)
|
||||
Process.sleep(:infinity)
|
||||
end)
|
||||
|
||||
Process.monitor(pid)
|
||||
|
||||
result = capture_io(fn -> process_info(self()) end)
|
||||
assert result =~ "## Links"
|
||||
assert result =~ "## Monitors"
|
||||
assert result =~ ":iex_process_info_link_test"
|
||||
end
|
||||
end
|
||||
|
||||
defp test_module_code do
|
||||
"""
|
||||
defmodule Sample do
|
||||
|
||||
+2
-2
@@ -894,7 +894,7 @@ defmodule Mix do
|
||||
{deps, config, system_env, consolidate_protocols?}
|
||||
|> :erlang.term_to_binary()
|
||||
|> :erlang.md5()
|
||||
|> Base.encode16(case: :lower)
|
||||
|> Base.url_encode64(case: :lower, padding: false)
|
||||
|
||||
force? = System.get_env("MIX_INSTALL_FORCE") in ["1", "true"] or Keyword.fetch!(opts, :force)
|
||||
|
||||
@@ -1068,7 +1068,7 @@ defmodule Mix do
|
||||
System.get_env("MIX_INSTALL_DIR") ||
|
||||
Path.join(Mix.Utils.mix_cache(), "installs")
|
||||
|
||||
version = "elixir-#{System.version()}-erts-#{:erlang.system_info(:version)}"
|
||||
version = "ex-#{System.version()}-erl-#{:erlang.system_info(:version)}"
|
||||
Path.join([install_root, version, cache_id])
|
||||
end
|
||||
|
||||
|
||||
+18
-23
@@ -232,40 +232,35 @@ defmodule Mix.Local do
|
||||
|
||||
defp find_latest_eligible_version(entries, artifact_version) do
|
||||
elixir_version = Version.parse!(System.version())
|
||||
otp_release = System.otp_release()
|
||||
|
||||
entries
|
||||
|> Enum.reverse()
|
||||
|> find_version(artifact_version, elixir_version)
|
||||
|> find_version(artifact_version, elixir_version, otp_release)
|
||||
end
|
||||
|
||||
defp find_version(entries, _artifact_version = nil, elixir_version) do
|
||||
Enum.find_value(entries, &find_by_elixir_version(&1, elixir_version))
|
||||
defp find_version(entries, artifact_version, elixir_version, otp_release) do
|
||||
entries =
|
||||
if artifact_version do
|
||||
Enum.filter(entries, &(hd(&1) == artifact_version))
|
||||
else
|
||||
entries
|
||||
end
|
||||
|
||||
Enum.find_value(entries, &find_by_elixir_version(&1, elixir_version, otp_release))
|
||||
end
|
||||
|
||||
defp find_version(entries, artifact_version, elixir_version) do
|
||||
Enum.find_value(entries, &find_by_artifact_version(&1, artifact_version, elixir_version))
|
||||
end
|
||||
|
||||
defp find_by_elixir_version([artifact_version, digest | versions], elixir_version) do
|
||||
if version = Enum.find(versions, &(Version.compare(&1, elixir_version) != :gt)) do
|
||||
{version, artifact_version, digest}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_by_artifact_version(
|
||||
[artifact_version, digest | versions],
|
||||
artifact_version,
|
||||
elixir_version
|
||||
defp find_by_elixir_version(
|
||||
[artifact_version, digest, hex_elixir_version, hex_otp_release | _],
|
||||
elixir_version,
|
||||
otp_release
|
||||
) do
|
||||
if version = Enum.find(versions, &(Version.compare(&1, elixir_version) != :gt)) do
|
||||
{version, artifact_version, digest}
|
||||
if Version.compare(hex_elixir_version, elixir_version) != :gt and
|
||||
hex_otp_release <= otp_release do
|
||||
{hex_elixir_version, artifact_version, digest, hex_otp_release}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_by_artifact_version(_entry, _artifact_version, _elixir_version) do
|
||||
nil
|
||||
end
|
||||
|
||||
## Public keys
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -49,12 +49,18 @@ defmodule Mix.Rebar do
|
||||
@doc """
|
||||
Returns the path supposed to host the local copy of `rebar`.
|
||||
|
||||
The rebar3 installation is specific to the Elixir version,
|
||||
The rebar3 installation is specific to the Elixir version and OTP release,
|
||||
in order to force updates when new Elixir versions come out.
|
||||
"""
|
||||
def local_rebar_path(:rebar3) do
|
||||
[major, minor | _] = String.split(System.version(), ".")
|
||||
Path.join([Mix.Utils.mix_home(), "elixir", "#{major}-#{minor}", "rebar3"])
|
||||
|
||||
Path.join([
|
||||
Mix.Utils.mix_home(),
|
||||
"elixir",
|
||||
"#{major}-#{minor}-otp-#{System.otp_release()}",
|
||||
"rebar3"
|
||||
])
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -137,7 +137,7 @@ defmodule Mix.SCM.Git do
|
||||
|
||||
# Migrate the Git repo
|
||||
rev = lock_rev || get_origin_opts_rev(opts) || default_branch()
|
||||
git!(["--git-dir=.git", "checkout", "--quiet", rev])
|
||||
git!(["--git-dir=.git", "checkout", "--force", "--quiet", rev])
|
||||
|
||||
if opts[:submodules] do
|
||||
git!(~w[-c core.hooksPath='' --git-dir=.git submodule update --init --recursive])
|
||||
|
||||
@@ -65,7 +65,13 @@ defmodule Mix.Tasks.Clean do
|
||||
|
||||
# Loadpaths without checks because compilers may be defined in deps.
|
||||
defp loadpaths! do
|
||||
options = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
options = [
|
||||
"--no-elixir-version-check",
|
||||
"--no-deps-check",
|
||||
"--no-archives-check",
|
||||
"--no-listeners"
|
||||
]
|
||||
|
||||
Mix.Task.run("loadpaths", options)
|
||||
Mix.Task.reenable("loadpaths")
|
||||
Mix.Task.reenable("deps.loadpaths")
|
||||
|
||||
@@ -67,6 +67,7 @@ defmodule Mix.Tasks.Compile do
|
||||
* `--no-compile` - does not actually compile, only loads code and perform checks
|
||||
* `--no-deps-check` - skips checking of dependencies
|
||||
* `--no-elixir-version-check` - does not check Elixir version
|
||||
* `--no-listeners` - does not start Mix listeners
|
||||
* `--no-optional-deps` - does not compile or load optional deps. Useful for testing
|
||||
if a library still successfully compiles without optional dependencies (which is the
|
||||
default case with dependencies)
|
||||
@@ -86,7 +87,13 @@ defmodule Mix.Tasks.Compile do
|
||||
@impl true
|
||||
def run(["--list"]) do
|
||||
# Loadpaths without checks because compilers may be defined in deps.
|
||||
args = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
args = [
|
||||
"--no-elixir-version-check",
|
||||
"--no-deps-check",
|
||||
"--no-archives-check",
|
||||
"--no-listeners"
|
||||
]
|
||||
|
||||
Mix.Task.run("loadpaths", args)
|
||||
Mix.Task.reenable("loadpaths")
|
||||
Mix.Task.reenable("deps.loadpaths")
|
||||
|
||||
@@ -119,7 +119,13 @@ defmodule Mix.Tasks.Help do
|
||||
|
||||
# Loadpaths without checks because tasks may be defined in deps.
|
||||
defp loadpaths! do
|
||||
args = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
args = [
|
||||
"--no-elixir-version-check",
|
||||
"--no-deps-check",
|
||||
"--no-archives-check",
|
||||
"--no-listeners"
|
||||
]
|
||||
|
||||
Mix.Task.run("loadpaths", args)
|
||||
Mix.Task.reenable("loadpaths")
|
||||
Mix.Task.reenable("deps.loadpaths")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
defmodule Mix.Tasks.Local.Hex do
|
||||
use Mix.Task
|
||||
|
||||
@hex_list_path "/installs/hex-1.x.csv"
|
||||
@hex_archive_path "/installs/[ELIXIR_VERSION]/hex-[HEX_VERSION].ez"
|
||||
@hex_list_path "/installs/hex.csv"
|
||||
@hex_archive_path "/installs/[ELIXIR_VERSION]/hex-[HEX_VERSION]-otp-[OTP_RELEASE].ez"
|
||||
|
||||
@shortdoc "Installs Hex locally"
|
||||
|
||||
@@ -68,16 +68,13 @@ defmodule Mix.Tasks.Local.Hex do
|
||||
defp run_install(version, argv) do
|
||||
hex_url = Mix.Hex.url()
|
||||
|
||||
{elixir_version, hex_version, sha512} =
|
||||
Mix.Local.find_matching_versions_from_signed_csv!(
|
||||
"Hex",
|
||||
version,
|
||||
hex_url <> @hex_list_path
|
||||
)
|
||||
{elixir_version, hex_version, sha512, otp_release} =
|
||||
Mix.Local.find_matching_versions_from_signed_csv!("Hex", version, hex_url <> @hex_list_path)
|
||||
|
||||
url =
|
||||
(hex_url <> @hex_archive_path)
|
||||
|> String.replace("[ELIXIR_VERSION]", elixir_version)
|
||||
|> String.replace("[OTP_RELEASE]", otp_release)
|
||||
|> String.replace("[HEX_VERSION]", hex_version)
|
||||
|
||||
# Unload the Hex module we loaded earlier to avoid conflicts when Hex is updated
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
defmodule Mix.Tasks.Local.Rebar do
|
||||
use Mix.Task
|
||||
|
||||
@rebar3_list_url "/installs/rebar3-1.x.csv"
|
||||
@rebar3_escript_url "/installs/[ELIXIR_VERSION]/rebar3-[REBAR_VERSION]"
|
||||
@rebar3_list_url "/installs/rebar.csv"
|
||||
@rebar3_escript_url "/installs/[ELIXIR_VERSION]/rebar3-[REBAR_VERSION]-otp-[OTP_RELEASE]"
|
||||
|
||||
@shortdoc "Installs Rebar locally"
|
||||
|
||||
@@ -110,13 +110,14 @@ defmodule Mix.Tasks.Local.Rebar do
|
||||
hex_url = Mix.Hex.url()
|
||||
list_url = hex_url <> list_url
|
||||
|
||||
{elixir_version, rebar_version, sha512} =
|
||||
{elixir_version, rebar_version, sha512, otp_release} =
|
||||
Mix.Local.find_matching_versions_from_signed_csv!("Rebar", _version = nil, list_url)
|
||||
|
||||
url =
|
||||
(hex_url <> escript_url)
|
||||
|> String.replace("[ELIXIR_VERSION]", elixir_version)
|
||||
|> String.replace("[REBAR_VERSION]", rebar_version)
|
||||
|> String.replace("[OTP_RELEASE]", otp_release)
|
||||
|
||||
install_from_path(manager, url, Keyword.put(opts, :sha512, sha512))
|
||||
end
|
||||
|
||||
@@ -53,6 +53,7 @@ defmodule Mix.Tasks.Run do
|
||||
* `--no-deps-check` - does not check dependencies
|
||||
* `--no-elixir-version-check` - does not check the Elixir version from mix.exs
|
||||
* `--no-halt` - does not halt the system after running the command
|
||||
* `--no-listeners` - does not start Mix listeners
|
||||
* `--no-mix-exs` - allows the command to run even if there is no mix.exs
|
||||
* `--no-start` - does not start applications after compilation
|
||||
|
||||
@@ -73,6 +74,7 @@ defmodule Mix.Tasks.Run do
|
||||
halt: :boolean,
|
||||
compile: :boolean,
|
||||
deps_check: :boolean,
|
||||
listeners: :boolean,
|
||||
start: :boolean,
|
||||
archives_check: :boolean,
|
||||
elixir_version_check: :boolean,
|
||||
|
||||
@@ -692,6 +692,11 @@ defmodule Mix.Utils do
|
||||
:httpc.set_options([ipfamily: fallback(inet)], :mix)
|
||||
request |> httpc_request(http_options) |> httpc_response()
|
||||
|
||||
{:error, {:failed_connect, [{:to_address, _}, {inet, _, reason}]}}
|
||||
when inet in [:inet, :inet6] and elem(reason, 0) == :tls_alert ->
|
||||
http_options = put_in(http_options, [:ssl, :middlebox_comp_mode], false)
|
||||
request |> httpc_request(http_options) |> httpc_response()
|
||||
|
||||
response ->
|
||||
httpc_response(response)
|
||||
end
|
||||
|
||||
@@ -3,67 +3,70 @@ Code.require_file("../test_helper.exs", __DIR__)
|
||||
defmodule Mix.LocalTest do
|
||||
use MixTest.Case
|
||||
|
||||
# openssl rsa -in elixirest.pem -pubout > elixirest.pub
|
||||
# openssl rsa -in elixirtest.pem -pubout > elixirtest.pub
|
||||
@public_key """
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA37moKP1dGGLhsP3d8Fwv
|
||||
W25SoYZUY2K+Iq7A0OBV36Rnb8yW3BWjfh5YtmPvUCfYUbNCW2HTMMgBntkQ4YmN
|
||||
B9tHVZazl2uX9lGCfZZPFc/9umvKRojCPkMN81MfTxqnY0oaLHr6DB86RsWHB+ld
|
||||
782Xf+nd9q3LFdUl8SGlKX7uzfVWd4EWYNcL7aLeLSupZWeNg8uVmY3zua0EgIlQ
|
||||
XryalIOZb/R+pwprWZoftCl+20FGYi/mJpo/idFtXsR0sJKF4X0W3NORT9RIRbs9
|
||||
WdjiFi+eIP7Nm8KSF4pbaXCqSmVf9cgvUuGTxc9/P5GcIPAlkcsSrE5peLyUCk5f
|
||||
2QIDAQAB
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkIaIn8uhwSVp9d+aV+GL
|
||||
tFjMt0TylxDu6x5jwjDvXCD3Of6VAriqcdXDJAqGPN1C4HNTxfihYpLZB+qXCNQM
|
||||
oEa/I1h6OB5CXb8zIOl2Yriforr9LS+EkP+1xVmz7w7lNAagD0rJyJrTDbYDV+2Y
|
||||
EnRq84FpZc/+7z6ojc3RMh9x/5t9jDE4Ft3NCujCmGsy2AcBlMpAQkcNveyplNmu
|
||||
fZH2vId8h8t6rABwEZgSazHObkcHwE3bndynFO7zXvBu6ebNUDuU3DrDJ1Iepy9I
|
||||
jEuZ6h13j7hL5l+j+9bGRQtasWgwvCkCGD+/2Lz4Ehox58voDCzAK09rsCwQ6gAT
|
||||
CQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
"""
|
||||
|
||||
# openssl genrsa -aes256 -out elixirtest.pem -passout stdin 2048
|
||||
# openssl genrsa -aes256 -out elixirtest.pem -passout pass:secret12 2048
|
||||
@private_key """
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-256-CBC,48BA5153DA2F120ECE063B33C1204A49
|
||||
|
||||
5gp3daNWujH7o9S/dJQEt9TYTRP0pPZtU55PlZrzWt52optr7XHW/ENOm84g5J70
|
||||
QCPELp12jfQsNiPwbVWXKy2zD3QlNiAelf65hqLWJTWli7XIXfdP46VXOu67OKf9
|
||||
Ziw4HQ+AdBEwFt20wJst77iy17sNlyxp5DhNDonnSizzIowgUAJkoNI5aBUU6D8X
|
||||
KTSIftZW35Z4SudkazdoHepEfItZTI8mB4rvfn71Q4oOBA1rAuUUmdPWoPBfUHDa
|
||||
hvIp2T2Q8zZYqm0+SjDxZUYOOreE7fuf5NSLhHHt7+jyWQmtaVxnOWms72G+9xT0
|
||||
NGmOEB0WEg1kBsUbYOXXwyCAZhNA6MaKCtgjQczRTK+geS1xNaFc9FDEk3ZjN4Z8
|
||||
PxrKQoqo+2aQGVcatZWCom80Dci3bIv7iZNA/y1rjfBn+MeitMOGscP7/CBrJAbI
|
||||
bh1mvCu0McSnqlN0a+EuCVfJQYFMzjibpRVzKAST0QeaxXd5QxHfcPFPBLOpiVWc
|
||||
NjHaZsHORyoJbUKGA4rgOiSB63mv7SDRA2mvxWpwV/+6MuwBah6t6CGoEsAr1Hbn
|
||||
1ySt5w27bw3QEf2KTiuxDubo8UrF0eYzP5A9MH8vRpSRZHg8T3SBVfPJ/pM16Lnn
|
||||
5BaMUdxDFJeet5HUYoke9Zm3udh2BvwGiKhzc9Pbw/EcsCcvChMimRTasqTaRf+S
|
||||
uIm0Un7o+7kTuvBo2y87j2urCEUzft5QqEynbkR7p3vZnwoLLj+supXh3V8ivW4s
|
||||
Z6ql+ukRcWd/ode+lbSiYfAJCLc1tCqJ3kTnMnADJBlL0TX7YnwBwWuwwPuZgeAv
|
||||
F6nnBE1SBQ1WK+bjSVzIqmNFqsZw34wgpnz2heX0q8msF5pzd6EIeA+uz86k8XYh
|
||||
4eVZYGXxa4Exodh/MqEpRuN1ytWDXvHULh0gml7xwZC3R50UD8uBNt5RGjXUkjXc
|
||||
V0atKuvgzVlsB4xbDhVP7EVYHBF02NfNOsvo7kh0Yl1IcT/42UaCGYuU1o9zotPv
|
||||
9b3SHz/HOmBVj2uCdR5XZ4EolP5Iv9vqIDt9DsuDpOyO+AFOww0FnJNCQ1Hmfb0T
|
||||
qBYPv994oSPYLCGR4a8i/xfmmV8KbAIVEgK3AMbz8RxKr3WBWXWnzQdr4+y4EG24
|
||||
hSnR52XQ42edv/fkqf9ez+fKNQ9i7PtlPE96Q21NeLMNKHh43X8hJFDh+oPz3Aio
|
||||
YSNMCZnoyRdrjBRCsVBpnyoLmuhWwG9RlcrEj3G0BxYPh/weaBOAKAHjSr28yuUj
|
||||
yIa8uddszC6XHSiVUgu7SGO8gQmq++eNdckjX/pEug5MjcWLUqaUg6+YLFWY6NLf
|
||||
uDPOYuivq7ErtKTvP2xl3TBEDKhdfqxA2+RFxbBDmKjffZnRkcknQsxhlzAdbg22
|
||||
Jwa2B1nrfjJpX5F+1Av2jHQGbIKMqZzv8fo1binMKpptFzokbWEOjcPCb3tPuomG
|
||||
ZRkW3qO2pdyYX2N7VXYG9tGi2HrN/oFrWnHPoYF23v85V8WxNkODOCpTz85e6R5v
|
||||
PVu+FCNFj5weEOTRhtEQyJo7mU5qIRwYeZvVxiC6W+XeFs95wdBE/Lvpg8yZ8D9d
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQjNQQlREZJuQveXPa
|
||||
0ACpEwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEFoBWTpEyO8y6xdG
|
||||
fuud2AIEggTQDQRXrxBpj1yIbnLcP7i43Gat2tEM+XXkvqbzwL+CdjfEzaqiBGwc
|
||||
FEyWnJj5hENWtNq/AAwzcEALa3QdLJ2W+tX/hc5r1vILpy2oW0vPKfv4yadNyPWq
|
||||
UzolKKU3MbREP4KXsuAVYepolRWAcWSZKozYBm9HUPwsSyM6E9K0aWmNr/YBbZPE
|
||||
UEA949Pq6buQvwT2Gj/6FQlutEvJMuY8woM2I/RKSVHeSoB8FWJGSNd7ZY2DN7DG
|
||||
o6kmhK0OVjxx0SYQwUm++4gh3yFRX6vnTf8ZADOwCOJE/hjMfqivNelJHGojYAD2
|
||||
0c3zdo0HGvVghg2bzUxhc0HJqUFiUDNW1W3AVBrUJiQgHQb/ENYaMrd6klrslHDV
|
||||
cnPkz+dZAq/WC3KFqufkSmHxU97dDs9G6hKdm80JVEdPG4aMJvHzVL6SvS+g/e7r
|
||||
IJGZCFGch9SbikmESKegIfyLJYQrfjuLdXWPHx9J+LAmtRTA1xPwLH/aqCr9ahBB
|
||||
JCl4RaezF0BH2GYLAV+Ailpy2jqsC3v9RW8vpQmU227H8ERFu9ZcWhZITbRhbjRB
|
||||
oh3ERnOmvU3VWIXQ0j1cm9Qjl52y8HZJxef2dYsW8nmTSRj32gi9UnEsyOFL9FB7
|
||||
KcFDq6o+go4nIfsmaAEOF1Hh01JZkbuX1SgmHvGRbNksTuljP/XZn7Wzz7fTcVZR
|
||||
NsTgMbBl1+ctDHUBT7YzsHeEx8tZvLPDP8lO23aEnmRb6bHD4hVorUEM/kOpSm6g
|
||||
DNP0buYGEAu4rZrp+5s46Y/RVAIhU4+663jqzmbmwOwEcuOJJCJn8eUGVweAgWX3
|
||||
1PQTfpkk7RLxxuLeMHxEIynxyZNpSgfX0VGKe5UtplwcTQL8VgeZHayDxnhFwtSm
|
||||
lBfPIM/i4kj7RonuehyPjuBA6n1CyqRVqc49c/3kVMgovqvbUQxQ7TtSfzfkhVyV
|
||||
yRnRxcek+6Gctoahas5DPQl7y8jnwgg5qvUjYo6GIwBoxLUzIw+0MBn7gdMYHNmh
|
||||
Xd9XZpRilAMJF5ynr2QyaE95XaQ3yK62FPgmIDdzfnN7es169O4SiXsgUyaSWsx9
|
||||
VxOHP4r0j6epWLH4W11MQEj4Bb8wG//pCD5Kz2xvwhWaKjkqzuqGDaXSOTKV0FQM
|
||||
NG5GbNNELNCy/ocvlsaC+acWcOfsx4f6yovCGVTpTr+wjbDqd1kEBoRTuyTPY+6P
|
||||
0OU1Vf2GpMA2NDw0ZUOAllGVLrgD+326bu7ipePgiT8SiPNNsagMJNCSk5d3C4+K
|
||||
bc0hakF/MIjPs3a+5/2mwtxk3QYYCIHITN8SIj+kFd7WMXALyClgWt3tcKwLTgMa
|
||||
BSatAvwsoi2kiBu2A49msvka7YD8fENhtUl8vU12ZZdTih1chyPDifgfRgOfkZFW
|
||||
a1XiLKjnWqeBqvJ3hBKuXweN6P7nvZidBYSESl3yrM00pA97Eta2in7ok80MRYH3
|
||||
bsqzC636h43Mu8PbTKGpK66j1ts6uY9YONAGfGCcfj/wyBWuqNViv7vK38th8wj8
|
||||
AG4rKyIlKxbFwo4Oh3jZTbfVF1Azl8pr0rK2P3W/EhfvWIrZVfB4Upy0wL6MjysE
|
||||
RZ/57N2wFq4KBb9x9hCMFxignWmgyzPK+1J0iPRkSmpn9P/SzywDBIg=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
"""
|
||||
|
||||
@csv """
|
||||
1.2.5,ABC,0.9.0
|
||||
1.2.3,DEF,1.0.0
|
||||
1.2.4,GHI,1.0.0
|
||||
1.2.5,ABC,0.9.0,25
|
||||
1.2.5,ABC,0.9.0,26
|
||||
1.2.3,DEF,1.0.0,25
|
||||
1.2.3,DEF,1.0.0,26
|
||||
1.2.4,GHI,1.0.0,25
|
||||
1.2.4,GHI,1.0.0,26
|
||||
"""
|
||||
|
||||
# openssl dgst -sha512 -sign elixirtest.pem hex-1.x.csv | openssl base64 > elixirtest.csv.signed
|
||||
@csv_signed """
|
||||
VRydmXOdEXQcKJu/SK/nKnE00T+s/T4mpXrYROMSXhD/s8ClvdimnGg61ie3YBS6
|
||||
LXOjlEhbtMHRM2rTOUvv4z7FcyzwvSxSjunlVi2g3c1pVOZ78MonnYhGb44tZw/q
|
||||
SOVmV+jJhc9EZFMIAAM3plMoyssyw2pMh7ZB/DxCQTIem3Qf0Ujzc2bYkLVlw7R+
|
||||
1Rn6dcYEgCzyldVkAUMaYBwieyweWALA+YVDCMudJJK2J7p1OnuoPSVV+N3OkB/Z
|
||||
T6Jj5ljD+54XnuxAMcgCoF9lpOwXscnw/Ma+8JqIoWo0jNFE3ji+8dGCUzQUdSe8
|
||||
llLXgJJE2tGpDhEXBA3idg==
|
||||
CVkhTiuCAfooYPhjyynDq40QhmDwLAEJvpwYytPCf6mpLXVrLXo/d/A2L8iBRJVx
|
||||
uk4PNVksLRZ1ChBzGFvEqaFjrH+ndQAYLbwqcaMIn743YNUjGNVfTZkU47nBybtJ
|
||||
BwDSBaAsow0Iitsl+UkDN/QvVoOLiX/x2cpnwCMrCgbTMroTzhH07vfLo3uCf8iY
|
||||
cncImd07ffCewt77AsVPpKgJNOLzn+EBnvh4LbGWQya8EkgyQKuMBuNU86MYtFiW
|
||||
NVpR7vbvqgWpEyr1XeknxKkhzYpna3+irXdMxGZw65WvFNWGJKnpuBTNHnHL+wX8
|
||||
oNQLUfakH8/VMV/8v6Irbg==
|
||||
"""
|
||||
|
||||
# We don't actually use it but it exists for documentation purposes.
|
||||
@@ -83,8 +86,10 @@ defmodule Mix.LocalTest do
|
||||
File.write!("csv", @csv)
|
||||
File.write!("csv.signed", @csv_signed)
|
||||
|
||||
assert {"1.0.0", "1.2.4", "GHI"} =
|
||||
assert {"1.0.0", "1.2.4", "GHI", otp_release} =
|
||||
Mix.Local.find_matching_versions_from_signed_csv!("name", nil, "csv")
|
||||
|
||||
assert otp_release <= System.otp_release()
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -94,12 +99,16 @@ defmodule Mix.LocalTest do
|
||||
File.write!("csv", @csv)
|
||||
File.write!("csv.signed", @csv_signed)
|
||||
|
||||
assert {"0.9.0", "1.2.5", "ABC"} =
|
||||
assert {"0.9.0", "1.2.5", "ABC", otp_release} =
|
||||
Mix.Local.find_matching_versions_from_signed_csv!("name", "1.2.5", "csv")
|
||||
|
||||
assert {"1.0.0", "1.2.3", "DEF"} =
|
||||
assert otp_release <= System.otp_release()
|
||||
|
||||
assert {"1.0.0", "1.2.3", "DEF", otp_release} =
|
||||
Mix.Local.find_matching_versions_from_signed_csv!("name", "1.2.3", "csv")
|
||||
|
||||
assert otp_release <= System.otp_release()
|
||||
|
||||
assert_raise Mix.Error, "Could not find a version of name matching: 1.3.0", fn ->
|
||||
Mix.Local.find_matching_versions_from_signed_csv!("name", "1.3.0", "csv")
|
||||
end
|
||||
@@ -109,7 +118,9 @@ defmodule Mix.LocalTest do
|
||||
@tag :tmp_dir
|
||||
test "raise on bad signature", %{tmp_dir: tmp_dir} do
|
||||
File.cd!(tmp_dir, fn ->
|
||||
csv_signed = String.replace(@csv_signed, "VRy", "BAD")
|
||||
<<_, _, _>> <> rest = @csv_signed
|
||||
csv_signed = "BAD" <> rest
|
||||
|
||||
File.write!("csv", @csv)
|
||||
File.write!("csv.signed", csv_signed)
|
||||
|
||||
|
||||
@@ -396,9 +396,14 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
Mix.State.clear_cache()
|
||||
purge([DepsOnGitRepo.MixProject])
|
||||
|
||||
# Write to the checkout location to ensure it is replaced
|
||||
File.mkdir_p!("deps/deps_on_git_repo/lib")
|
||||
File.write!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex", "# WILL BE OVERRIDDEN")
|
||||
|
||||
Mix.Tasks.Deps.Update.run(["deps_on_git_repo"])
|
||||
assert File.exists?("deps/git_repo/lib/git_repo.ex")
|
||||
assert File.read!("mix.lock") =~ last
|
||||
assert File.read!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex") =~ "GitRepo.hello()"
|
||||
refute File.exists?("_build/dev/lib/git_repo/.mix/compile.fetch")
|
||||
end)
|
||||
after
|
||||
@@ -423,10 +428,15 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
Mix.State.clear_cache()
|
||||
purge([DepsOnGitRepo.MixProject])
|
||||
|
||||
# Write to the checkout location to ensure it is replaced
|
||||
File.mkdir_p!("deps/deps_on_git_repo/lib")
|
||||
File.write!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex", "# WILL BE OVERRIDDEN")
|
||||
|
||||
Mix.Dep.Lock.write(%{deps_on_git_repo: {:git, fixture_path("deps_on_git_repo"), last, []}})
|
||||
Mix.Tasks.Deps.Get.run([])
|
||||
assert File.exists?("deps/git_repo/lib/git_repo.ex")
|
||||
assert File.read!("mix.lock") =~ last
|
||||
assert File.read!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex") =~ "GitRepo.hello()"
|
||||
refute File.exists?("_build/dev/lib/git_repo/.mix/compile.fetch")
|
||||
end)
|
||||
after
|
||||
|
||||
@@ -254,7 +254,8 @@ end
|
||||
|
||||
rebar3_source = System.get_env("REBAR3") || Path.expand("fixtures/rebar3", __DIR__)
|
||||
[major, minor | _] = String.split(System.version(), ".")
|
||||
rebar3_target = Path.join([mix, "elixir", "#{major}-#{minor}", "rebar3"])
|
||||
version_dir = "#{major}-#{minor}-otp-#{System.otp_release()}"
|
||||
rebar3_target = Path.join([mix, "elixir", version_dir, "rebar3"])
|
||||
File.mkdir_p!(Path.dirname(rebar3_target))
|
||||
File.cp!(rebar3_source, rebar3_target)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user