Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c521bdb91a | ||
|
|
bdbdf52122 | ||
|
|
7ff97fb930 | ||
|
|
af378de03f | ||
|
|
82c264bc36 | ||
|
|
4fbed9fe12 | ||
|
|
1dbf121a78 | ||
|
|
694b9b0218 | ||
|
|
0a3df5068c | ||
|
|
9e177fd592 | ||
|
|
8422af4a2e | ||
|
|
b79c5a3b4c | ||
|
|
3bb0a8379d | ||
|
|
fe0d57c4da | ||
|
|
727a958c48 | ||
|
|
aa93d654ab | ||
|
|
f5b71a79b2 | ||
|
|
173fc52610 | ||
|
|
7f7a8bca99 | ||
|
|
0929a431d4 | ||
|
|
f1e5d770ea | ||
|
|
bceda78a42 | ||
|
|
6d829120c3 | ||
|
|
785c29210e | ||
|
|
7db2daea72 | ||
|
|
0fcee760d8 | ||
|
|
e63b0b6217 | ||
|
|
9e195f5251 | ||
|
|
60320941aa | ||
|
|
871e737583 | ||
|
|
3785e6f71b | ||
|
|
a89f8a9913 | ||
|
|
93bfbdfc4e | ||
|
|
c032839468 | ||
|
|
52cf991302 | ||
|
|
0c6b44798b | ||
|
|
4c12bfcb14 | ||
|
|
fde96ce8dc | ||
|
|
bc40b6ebb5 | ||
|
|
3a34f243b8 | ||
|
|
f322d26b7d | ||
|
|
dcaa468c47 | ||
|
|
cc2d05654f | ||
|
|
c22f7343f0 | ||
|
|
858835f9f7 | ||
|
|
367d906403 | ||
|
|
df5bc5abc8 | ||
|
|
1ca5d789bb | ||
|
|
aab3543dc3 | ||
|
|
9b254e6830 | ||
|
|
0dd933b0a9 | ||
|
|
9fd85b06dc | ||
|
|
ec81110b3e | ||
|
|
094a76d514 | ||
|
|
7d737bc5ec | ||
|
|
3eb074282a | ||
|
|
b8732bd248 | ||
|
|
2252a8ff55 | ||
|
|
9a9cf419a1 | ||
|
|
0f92512030 | ||
|
|
e0806f9d6c | ||
|
|
5db9d55926 | ||
|
|
3cb3d41368 | ||
|
|
a3a081c949 | ||
|
|
207350fb44 | ||
|
|
6f7a68c9e3 | ||
|
|
0936fb45d5 | ||
|
|
bccfca4ffe | ||
|
|
417255fe1b |
@@ -30,7 +30,7 @@ jobs:
|
||||
- otp: 26
|
||||
otp_version: '26.0'
|
||||
build_docs: build_docs
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
@@ -15,7 +15,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
create_draft_release:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
- otp: 26
|
||||
otp_version: '26.0'
|
||||
build_docs: build_docs
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -56,7 +56,9 @@ jobs:
|
||||
run: |
|
||||
gh release upload --clobber "${{ github.ref_name }}" \
|
||||
elixir-otp-${{ matrix.otp }}.zip \
|
||||
elixir-otp-${{ matrix.otp }}.zip.sha{1,256}sum
|
||||
elixir-otp-${{ matrix.otp }}.zip.sha{1,256}sum \
|
||||
elixir-otp-${{ matrix.otp }}.exe \
|
||||
elixir-otp-${{ matrix.otp }}.exe.sha{1,256}sum
|
||||
- name: Upload Docs to GitHub
|
||||
if: ${{ matrix.build_docs }}
|
||||
env:
|
||||
|
||||
@@ -22,6 +22,20 @@ runs:
|
||||
shasum -a 1 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha1sum
|
||||
shasum -a 256 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha256sum
|
||||
echo "$PWD/bin" >> $GITHUB_PATH
|
||||
- name: Install NSIS
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y nsis
|
||||
- name: Build Elixir Windows Installer
|
||||
shell: bash
|
||||
run: |
|
||||
export OTP_VERSION=${{ inputs.otp_version }}
|
||||
export ELIXIR_ZIP=$PWD/elixir-otp-${{ inputs.otp }}.zip
|
||||
(cd lib/elixir/scripts/windows_installer && ./build.sh)
|
||||
mv lib/elixir/scripts/windows_installer/tmp/elixir-otp-${{ inputs.otp }}.exe .
|
||||
shasum -a 1 elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha1sum
|
||||
shasum -a 256 elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha256sum
|
||||
- name: Get latest stable ExDoc version
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
|
||||
+85
-28
@@ -68,12 +68,20 @@ In Elixir v1.15, the new reports will look like:
|
||||
error: undefined function foo/0 (there is no such import)
|
||||
my_file.exs:1
|
||||
|
||||
** (CompileError) nofile: cannot compile file (errors have been logged)
|
||||
** (CompileError) my_file.exs: cannot compile file (errors have been logged)
|
||||
|
||||
A new function, called `Code.with_diagnostics/2`, has been added so this
|
||||
information can be leveraged by editors, allowing them to point to several
|
||||
errors at once.
|
||||
|
||||
### Potential incompatibilities
|
||||
|
||||
As part of this effort, the behaviour where undefined variables were
|
||||
transformed into nullary function calls, often leading to confusing error
|
||||
reports, has been disabled during project compilation. You can invoke
|
||||
`Code.compiler_options(on_undefined_variable: :warn)` at the top of
|
||||
your `mix.exs` to bring the old behaviour back.
|
||||
|
||||
## Integration with Erlang/OTP logger
|
||||
|
||||
This release provides additional features such as global logger
|
||||
@@ -100,61 +108,96 @@ config :logger, :default_formatter,
|
||||
format: "$time $message $metadata"
|
||||
```
|
||||
|
||||
If you use `Logger.Backends.Console` or other backends, they are
|
||||
still fully supported and functional. If you implement your own
|
||||
backends, you want to consider migrating to
|
||||
If you use `Logger.Backends.Console` with a custom device or other
|
||||
backends, they are still fully supported and functional. If you
|
||||
implement your own backends, you want to consider migrating to
|
||||
[`:logger_backends`](https://github.com/elixir-lang/logger_backends)
|
||||
in the long term.
|
||||
|
||||
See the new `Logger` documentation for more information on the
|
||||
new features and on compatibility.
|
||||
|
||||
## v1.15.0-rc.1 (2022-05-29)
|
||||
## v1.15.4 (2023-07-18)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix archive.build] Disable protocol consolidation when building archiveson archive.install
|
||||
* [mix compile] Track removed files per local dependency (this addresses a bug where files depending on modules from path dependencies always recompiled)
|
||||
* [mix release] Do not strip relevant chunks from Erlang/OTP 26
|
||||
|
||||
## v1.15.3 (2023-07-15)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [File] Support distributed `File.Stream`
|
||||
* [Module] Add `Module.get_last_attribute/3`
|
||||
* [Task] Reduce footprint of tasks by avoiding unecessary work during spawning
|
||||
* [Kernel] Improve better stacktraces when executing unnested Elixir code in a file
|
||||
|
||||
#### ExUnit
|
||||
#### Mix
|
||||
|
||||
* [ExUnit.Case] Add `ExUnit.Case.get_last_registered_test/1`
|
||||
* [Mix] Allow to opt out of starting apps in `Mix.install/2`
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Ensure `:on_undefined_variable` option works as advertised (regression)
|
||||
* [Code] Format paths in `Code.with_diagnostic/2` as relative paths (regression)
|
||||
* [Kernel] Raise when macros are given to dialyzer
|
||||
* [Kernel] Support bitstring specifiers as map keys in pattern (regression)
|
||||
* [Module] Ensure that `Module.get_attribute/3` returns `nil` and not the given default value when an attribute has been explicitly set as `nil`
|
||||
* [Task] Do not double log Task failure reports
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.CaptureLog] Allow capturing deprecated log level (regression)
|
||||
* [ExUnit.DocTest] Ensure proper line is returned when failing to parse doctest results
|
||||
* [Code] Ensure `with_diagnostics` propagate warnings from inner Erlang passes
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Fix IO operations not returning when booting IEx (regression)
|
||||
* [IEx] Fix `--remsh` on Erlang/OTP 25 and earlier
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix deps] Ensure dependencies with `included_applications` can be loaded (regression)
|
||||
* [mix format] Ensure proper formatter options are returned for files (regression)
|
||||
* [mix compile.elixir] Ensure `__mix_recompile__?` callbacks are properly invoked
|
||||
|
||||
### 3. Soft deprecations
|
||||
## v1.15.2 (2023-07-01)
|
||||
|
||||
#### Elixir
|
||||
### 1. Bug fixes
|
||||
|
||||
* [Kernel] Require pin variable when accessing variable inside binary size in match
|
||||
#### IEx
|
||||
|
||||
## v1.15.0-rc.0 (2022-05-22)
|
||||
* [IEx] Fix CLI being unable to boot on Windows
|
||||
|
||||
## v1.15.1 (2023-06-30)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
* [Code] `Code.string_to_quoted/2` honors `:static_atoms_encoder` for multi-letter sigils
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.CaptureLog] Fix race condition on concurrent `capture_log`
|
||||
* [ExUnit.CaptureLog] Respect options passed to nested `capture_log` calls
|
||||
* [ExUnit.Doctest] Properly compile doctests without results terminated by fences
|
||||
* [ExUnit.Doctest] Allow variables defined in doctests to be used in expectation
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Ensure `pry` works on Erlang/OTP 25 and earlier while IEx is booting
|
||||
* [IEx] `Code.Fragment.surround_context` considers surround context around spaces and parens
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Do not assume Logger has been loaded at compile-time
|
||||
* [Logger.Formatter] Properly handle `:function` as metadata
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Ensure the current project is available on the code path after its Elixir sources are compiled
|
||||
* [mix compile] Guarantee yecc/leex are available when emitting warnings from previous runs
|
||||
* [mix compile] Fix bug where an external resource was deleted after its
|
||||
mtime was successfully retrieved
|
||||
* [mix compile] Track removed modules and exports across local deps
|
||||
* [mix deps] Fix an issue where dependencies could not be started in an umbrella projects
|
||||
* [mix release] Properly handle optional dependencies when there is a conflict in the application start mode
|
||||
* [mix release] Remove `--werl` from release scripts on Erlang/OTP 26
|
||||
|
||||
## v1.15.0 (2023-06-19)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -175,6 +218,7 @@ new features and on compatibility.
|
||||
* [Date] Add `Date.before?/2` and `Date.after?/2`
|
||||
* [DateTime] Add `DateTime.before?/2` and `DateTime.after?/2`
|
||||
* [DateTime] Support precision in `DateTime.utc_now/2`
|
||||
* [File] Support distributed `File.Stream`
|
||||
* [Inspect] `Inspect` now renders `'charlists'` as `~c"charlists"` by default
|
||||
* [Kernel] Break down `case` and `cond` inside `dbg/2`
|
||||
* [Kernel] Add `t:nonempty_binary/0` and `t:nonempty_bitstring/0`
|
||||
@@ -195,6 +239,8 @@ new features and on compatibility.
|
||||
* [NaiveDateTime] Add `NaiveDateTime.beginning_of_day/1` and `NaiveDateTime.end_of_day/1`
|
||||
* [NaiveDateTime] Add `NaiveDateTime.before?/2` and `NaiveDateTime.after?/2`
|
||||
* [NaiveDateTime] Support precision in `NaiveDateTime.utc_now/2`
|
||||
* [Module] Mark functions as generated in "Docs" chunk
|
||||
* [Module] Add `Module.get_last_attribute/3`
|
||||
* [OptionParser] Support `:return_separator` option
|
||||
* [Process] Add `Process.alias/0,1` and `Process.unalias/1`
|
||||
* [Range] Add `Range.split/2`
|
||||
@@ -204,6 +250,7 @@ new features and on compatibility.
|
||||
* [System] Support `:lines` in `System.cmd/3` to capture output line by line
|
||||
* [Task] Remove head of line blocking on `Task.yield_many/2`
|
||||
* [Task] Enable selective receive optimizations in Erlang/OTP 26+
|
||||
* [Task] Reduce tasks footprint by avoiding unecessary work during spawning
|
||||
* [Task.Supervisor] Do not copy args on temporary `Task.Supervisor.start_child/2`
|
||||
* [Time] Add `Time.before?/2` and `Time.after?/2`
|
||||
* [URI] Add `URI.append_path/2`
|
||||
@@ -212,7 +259,9 @@ new features and on compatibility.
|
||||
|
||||
* [ExUnit] Add more color configuration to ExUnit CLI formatter
|
||||
* [ExUnit.Callbacks] Accept `{module, function}` tuples in ExUnit `setup` callbacks
|
||||
* [ExUnit.Case] Add `ExUnit.Case.get_last_registered_test/1`
|
||||
* [ExUnit.Doctest] Add `ExUnit.DocTest.doctest_file/2`
|
||||
* [ExUnit.Doctest] Include `doctest_data` in doctest tags
|
||||
* [ExUnit.Formatter] When comparing two anonymous functions, defined at the same place but capturing a different environment, we will now also diff the environments
|
||||
|
||||
#### IEx
|
||||
@@ -267,16 +316,22 @@ new features and on compatibility.
|
||||
* [Kernel] Expand macros on the left side of -> in `try/rescue`
|
||||
* [Kernel] Raise on misplaced `...` inside typespecs
|
||||
* [Kernel] Do not import `behaviour_info` and `module_info` functions from Erlang modules
|
||||
* [Kernel] Raise when macros are given to dialyzer
|
||||
* [Kernel.ParallelCompiler] Make sure compiler doesn't crash when there are stray messages in the inbox
|
||||
* [Kernel.ParallelCompiler] Track compile and runtime warnings separately
|
||||
* [Module] Ensure that `Module.get_attribute/3` returns `nil` and not the given default value when an attribute has been explicitly set as `nil`
|
||||
* [System] Fix race condition when a script would terminate before `System.stop/1` executes
|
||||
* [Task] Do not double log Task failure reports
|
||||
* [URI] Make sure `URI.merge/2` works accordingly with relative paths
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Fix crash when `@tag capture_log: true` was set to true and the Logger application was shut down in the middle of the test
|
||||
* [ExUnit] Do not merge context as tags inside the runner to reduce memory usage when emitting events to formatters
|
||||
* [ExUnit] Mark test cases as invalid when an exit occurs during `setup_all`
|
||||
* [ExUnit] Do not expand or collect vars from quote in ExUnit assertions
|
||||
* [ExUnit.DocTest] Ensure proper line is returned when failing to parse doctest results
|
||||
* [ExUnit.Doctest] Fix line information when a doctest with multiple assertions fails
|
||||
|
||||
#### IEx
|
||||
|
||||
@@ -288,6 +343,7 @@ new features and on compatibility.
|
||||
|
||||
* [mix compile] Include `cwd` in compiler cache key
|
||||
* [mix release] Fix Windows service when invoking `erlsrv.exe` in path with spaces
|
||||
* [mix xref] Raise early if `mix xref` is used at the umbrella root
|
||||
|
||||
### 3. Soft deprecations (no warnings emitted)
|
||||
|
||||
@@ -295,6 +351,7 @@ new features and on compatibility.
|
||||
|
||||
* [File] `File.cp/3` and `File.cp_r/3` with a function as third argument
|
||||
is deprecated in favor of a keyword list
|
||||
* [Kernel] Require pin variable when accessing variable inside binary size in match
|
||||
* [Kernel.ParallelCompiler] Require the `:return_diagnostics` option to be
|
||||
set to true when compiling or requiring code
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ PREFIX ?= /usr/local
|
||||
TEST_FILES ?= "*_test.exs"
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
MAN_PREFIX ?= $(SHARE_PREFIX)/man
|
||||
#CANONICAL := MAJOR.MINOR/
|
||||
CANONICAL := 1.15/
|
||||
CANONICAL ?= main/
|
||||
DOCS_FORMAT ?= html
|
||||
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
|
||||
6. Copy the relevant bits from /CHANGELOG.md to the GitHub release and publish it
|
||||
|
||||
7. Add the release to `elixir.csv` with the minimum supported OTP version (all releases), update `erlang.csv` to the latest supported OTP version, and `_data/elixir-versions.yml` (except for RCs) files in `elixir-lang/elixir-lang.github.com`
|
||||
7. Update `_data/elixir-versions.yml` (except for RCs) in `elixir-lang/elixir-lang.github.com`
|
||||
|
||||
## Creating a new vMAJOR.MINOR branch (after first rc)
|
||||
|
||||
|
||||
+2
-3
@@ -6,12 +6,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
|
||||
|
||||
Elixir version | Support
|
||||
:------------- | :-----------------------------
|
||||
1.15 | Development
|
||||
1.14 | Bug fixes and security patches
|
||||
1.15 | Bug fixes and security patches
|
||||
1.14 | Security patches only
|
||||
1.13 | Security patches only
|
||||
1.12 | Security patches only
|
||||
1.11 | Security patches only
|
||||
1.10 | Security patches only
|
||||
|
||||
## Announcements
|
||||
|
||||
|
||||
+9
-4
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.15.0-rc.1
|
||||
ELIXIR_VERSION=1.15.4
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
@@ -94,6 +94,7 @@ starts_with () {
|
||||
}
|
||||
|
||||
ERL_EXEC="erl"
|
||||
MODE="cli"
|
||||
I=1
|
||||
E=0
|
||||
LENGTH=$#
|
||||
@@ -104,9 +105,13 @@ while [ $I -le $LENGTH ]; do
|
||||
S=0
|
||||
C=0
|
||||
case "$1" in
|
||||
+elixirc|+iex)
|
||||
+elixirc)
|
||||
C=1
|
||||
;;
|
||||
+iex)
|
||||
C=1
|
||||
MODE="iex"
|
||||
;;
|
||||
-v|--no-halt|--dbg)
|
||||
C=1
|
||||
;;
|
||||
@@ -223,12 +228,12 @@ fi
|
||||
ERTS_BIN=
|
||||
ERTS_BIN="$ERTS_BIN"
|
||||
|
||||
set -- "$ERTS_BIN$ERL_EXEC" -noshell -elixir_root "$SCRIPT_PATH"/../lib -pa "$SCRIPT_PATH"/../lib/elixir/ebin $ELIXIR_ERL_OPTIONS -s elixir start_cli $ERL "$@"
|
||||
set -- "$ERTS_BIN$ERL_EXEC" -noshell -elixir_root "$SCRIPT_PATH"/../lib -pa "$SCRIPT_PATH"/../lib/elixir/ebin $ELIXIR_ERL_OPTIONS -s elixir start_$MODE $ERL "$@"
|
||||
|
||||
if [ -n "$RUN_ERL_PIPE" ]; then
|
||||
ESCAPED=""
|
||||
for PART in "$@"; do
|
||||
ESCAPED="$ESCAPED $(echo "$PART" | sed 's@[^a-zA-Z0-9_/-]@\\&@g')"
|
||||
ESCAPED="$ESCAPED $(printf '%s' "$PART" | sed 's@[^a-zA-Z0-9_/-]@\\&@g')"
|
||||
done
|
||||
mkdir -p "$RUN_ERL_PIPE"
|
||||
mkdir -p "$RUN_ERL_LOG"
|
||||
|
||||
+8
-6
@@ -1,6 +1,6 @@
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
|
||||
set ELIXIR_VERSION=1.15.0-rc.1
|
||||
set ELIXIR_VERSION=1.15.4
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
@@ -81,9 +81,6 @@ set beforeExtra=
|
||||
rem Option which determines whether the loop is over
|
||||
set endLoop=0
|
||||
|
||||
rem Designates which mode / Elixir component to run as
|
||||
set runMode="elixir"
|
||||
|
||||
rem Designates the path to the current script
|
||||
set SCRIPT_PATH=%~dp0
|
||||
|
||||
@@ -106,7 +103,7 @@ if !endLoop! == 1 (
|
||||
)
|
||||
rem ******* EXECUTION OPTIONS **********************
|
||||
if !par!=="--werl" (set useWerl=1 && goto startloop)
|
||||
if !par!=="+iex" (set parsElixir=!parsElixir! +iex && goto startloop)
|
||||
if !par!=="+iex" (set parsElixir=!parsElixir! +iex && set useIEx=1 && goto startloop)
|
||||
if !par!=="+elixirc" (set parsElixir=!parsElixir! +elixirc && goto startloop)
|
||||
rem ******* EVAL PARAMETERS ************************
|
||||
if ""==!par:-e=! (
|
||||
@@ -164,8 +161,13 @@ reg query HKCU\Console /v VirtualTerminalLevel 2>nul | findstr /e "0x1" >nul 2>n
|
||||
if %errorlevel% == 0 (
|
||||
set beforeExtra=-elixir ansi_enabled true !beforeExtra!
|
||||
)
|
||||
if defined useIEx (
|
||||
set beforeExtra=-s elixir start_iex !beforeExtra!
|
||||
) else (
|
||||
set beforeExtra=-s elixir start_cli !beforeExtra!
|
||||
)
|
||||
|
||||
set beforeExtra=-noshell -elixir_root !SCRIPT_PATH!..\lib -pa !SCRIPT_PATH!..\lib\elixir\ebin -s elixir start_cli !beforeExtra!
|
||||
set beforeExtra=-noshell -elixir_root "!SCRIPT_PATH!..\lib" -pa "!SCRIPT_PATH!..\lib\elixir\ebin" !beforeExtra!
|
||||
|
||||
if defined ELIXIR_CLI_DRY_RUN (
|
||||
if defined useWerl (
|
||||
|
||||
@@ -30,4 +30,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user elixir" -e ":elixir.start_iex()" +iex "$@"
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user elixir" +iex "$@"
|
||||
|
||||
+1
-1
@@ -24,6 +24,6 @@ goto end
|
||||
|
||||
:run
|
||||
if defined IEX_WITH_WERL (set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
|
||||
call "%~dp0\elixir.bat" --no-halt --erl "-user elixir" -e ":elixir.start_iex()" +iex %__ELIXIR_IEX_FLAGS% %*
|
||||
call "%~dp0\elixir.bat" --no-halt --erl "-user elixir" +iex %__ELIXIR_IEX_FLAGS% %*
|
||||
:end
|
||||
endlocal
|
||||
|
||||
@@ -1136,7 +1136,10 @@ defmodule Code do
|
||||
* syntax keywords (`fn`, `do`, `else`, and so on)
|
||||
|
||||
* atoms containing interpolation (`:"#{1 + 1} is two"`), as these
|
||||
atoms are constructed at runtime.
|
||||
atoms are constructed at runtime
|
||||
|
||||
* atoms used to represent single-letter sigils like `:sigil_X`
|
||||
(but multi-letter sigils like `:sigil_XYZ` are encoded).
|
||||
|
||||
"""
|
||||
@spec string_to_quoted(List.Chars.t(), keyword) ::
|
||||
|
||||
@@ -3,10 +3,6 @@ defmodule Code.Fragment do
|
||||
This module provides conveniences for analyzing fragments of
|
||||
textual code and extract available information whenever possible.
|
||||
|
||||
Most of the functions in this module provide a best-effort
|
||||
and may not be accurate under all circumstances. Read each
|
||||
documentation for more information.
|
||||
|
||||
This module should be considered experimental.
|
||||
"""
|
||||
|
||||
@@ -623,7 +619,7 @@ defmodule Code.Fragment do
|
||||
{reversed_pre, post} = adjust_position(reversed_pre, post)
|
||||
|
||||
case take_identifier(post, []) do
|
||||
{_, [], _} ->
|
||||
:none ->
|
||||
maybe_operator(reversed_pre, post, line, opts)
|
||||
|
||||
{:identifier, reversed_post, rest} ->
|
||||
@@ -631,7 +627,7 @@ defmodule Code.Fragment do
|
||||
reversed = reversed_post ++ reversed_pre
|
||||
|
||||
case codepoint_cursor_context(reversed, opts) do
|
||||
{{:struct, acc}, offset} ->
|
||||
{{:struct, acc}, offset} when acc != [] ->
|
||||
build_surround({:struct, acc}, reversed, line, offset)
|
||||
|
||||
{{:alias, acc}, offset} ->
|
||||
@@ -733,15 +729,31 @@ defmodule Code.Fragment do
|
||||
do: take_identifier(t, [h | acc])
|
||||
|
||||
defp take_identifier(rest, acc) do
|
||||
with {[?. | t], _} <- strip_spaces(rest, 0),
|
||||
{stripped, _} = strip_spaces(rest, 0)
|
||||
|
||||
with [?. | t] <- stripped,
|
||||
{[h | _], _} when h in ?A..?Z <- strip_spaces(t, 0) do
|
||||
take_alias(rest, acc)
|
||||
else
|
||||
_ -> {:identifier, acc, rest}
|
||||
# Consider it an identifier if we are at the end of line
|
||||
# or if we have spaces not followed by . (call) or / (arity)
|
||||
_ when acc == [] and (rest == [] or (hd(rest) in @space and hd(stripped) not in ~c"/.")) ->
|
||||
{:identifier, acc, rest}
|
||||
|
||||
# If we are immediately followed by a container, we are still part of the identifier.
|
||||
# We don't consider << as it _may_ be an operator.
|
||||
_ when acc == [] and hd(stripped) in ~c"({[" ->
|
||||
{:identifier, acc, rest}
|
||||
|
||||
_ when acc == [] ->
|
||||
:none
|
||||
|
||||
_ ->
|
||||
{:identifier, acc, rest}
|
||||
end
|
||||
end
|
||||
|
||||
defp take_alias([h | t], acc) when h not in @non_identifier,
|
||||
defp take_alias([h | t], acc) when h in ?A..?Z or h in ?a..?z or h in ?0..9 or h == ?_,
|
||||
do: take_alias(t, [h | acc])
|
||||
|
||||
defp take_alias(rest, acc) do
|
||||
|
||||
@@ -342,7 +342,7 @@ defmodule Config.Provider do
|
||||
* Make the runtime value match the compile time one
|
||||
|
||||
* Recompile your project. If the misconfigured application is a dependency, \
|
||||
you may need to run "mix deps.compile #{app} --force"
|
||||
you may need to run "mix deps.clean #{app} --build"
|
||||
|
||||
* Alternatively, you can disable this check. If you are using releases, you can \
|
||||
set :validate_compile_env to false in your release configuration. If you are \
|
||||
|
||||
@@ -3009,13 +3009,16 @@ defmodule Enum do
|
||||
|
||||
if first < count and amount > 0 do
|
||||
amount = Kernel.min(amount, count - first)
|
||||
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
|
||||
amount = amount_with_step(amount, step)
|
||||
fun.(first, amount, step)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp amount_with_step(amount, 1), do: amount
|
||||
defp amount_with_step(amount, step), do: div(amount - 1, step) + 1
|
||||
|
||||
@doc """
|
||||
Returns a subset list of the given `enumerable`, from `start_index` (zero-based)
|
||||
with `amount` number of elements if available.
|
||||
@@ -4440,7 +4443,7 @@ defmodule Enum do
|
||||
|
||||
if start >= 0 do
|
||||
amount = Kernel.min(amount, count - start)
|
||||
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
|
||||
amount = amount_with_step(amount, step)
|
||||
fun.(start, amount, step)
|
||||
else
|
||||
[]
|
||||
@@ -4448,7 +4451,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp slice_forward(list, start, amount, step) when is_list(list) do
|
||||
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
|
||||
amount = amount_with_step(amount, step)
|
||||
slice_list(list, start, amount, step)
|
||||
end
|
||||
|
||||
@@ -4458,7 +4461,7 @@ defmodule Enum do
|
||||
[]
|
||||
|
||||
{:ok, count, fun} when is_function(fun, 1) ->
|
||||
amount = Kernel.min(amount, count - start)
|
||||
amount = Kernel.min(amount, count - start) |> amount_with_step(step)
|
||||
enumerable |> fun.() |> slice_exact(start, amount, step, count)
|
||||
|
||||
# TODO: Deprecate me in Elixir v1.18.
|
||||
@@ -4473,8 +4476,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
{:ok, count, fun} when is_function(fun, 3) ->
|
||||
amount = Kernel.min(amount, count - start)
|
||||
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
|
||||
amount = Kernel.min(amount, count - start) |> amount_with_step(step)
|
||||
fun.(start, amount, step)
|
||||
|
||||
{:error, module} ->
|
||||
|
||||
@@ -4,6 +4,9 @@ defmodule Float do
|
||||
@moduledoc """
|
||||
Functions for working with floating-point numbers.
|
||||
|
||||
For mathematical operations on top of floating-points,
|
||||
see Erlang's [`:math`](`:math`) module.
|
||||
|
||||
## Kernel functions
|
||||
|
||||
There are functions related to floating-point numbers on the `Kernel` module
|
||||
|
||||
@@ -593,11 +593,11 @@ defmodule IO do
|
||||
|
||||
Another example where you might want to collect a user input
|
||||
every new line and break on an empty line, followed by removing
|
||||
redundant new line characters (`"\n"`):
|
||||
redundant new line characters (`"\\n"`):
|
||||
|
||||
IO.stream(:stdio, :line)
|
||||
|> Enum.take_while(&(&1 != "\n"))
|
||||
|> Enum.map(&String.replace(&1, "\n", ""))
|
||||
|> Enum.take_while(&(&1 != "\\n"))
|
||||
|> Enum.map(&String.replace(&1, "\\n", ""))
|
||||
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t()
|
||||
|
||||
@@ -3555,10 +3555,12 @@ defmodule Kernel do
|
||||
not bootstrapped?(Macro) ->
|
||||
nil
|
||||
|
||||
not function? and __CALLER__.context == :match ->
|
||||
not function? and (__CALLER__.context == :match or __CALLER__.context == :guard) ->
|
||||
raise ArgumentError,
|
||||
"""
|
||||
invalid write attribute syntax. If you want to define an attribute, don't do this:
|
||||
invalid usage of module attributes. Module attributes cannot be used inside \
|
||||
pattern matching (and guards) outside of a function. If you are trying to \
|
||||
define an attribute, do not do this:
|
||||
|
||||
@foo = :value
|
||||
|
||||
@@ -4901,17 +4903,6 @@ defmodule Kernel do
|
||||
{expanded, nil}
|
||||
end
|
||||
|
||||
# We do this so that the block is not tail-call optimized and stacktraces
|
||||
# are not messed up. Basically, we just insert something between the return
|
||||
# value of the block and what is returned by defmodule. Using just ":ok" or
|
||||
# similar doesn't work because it's likely optimized away by the compiler.
|
||||
block =
|
||||
quote do
|
||||
result = unquote(block)
|
||||
:elixir_utils.noop()
|
||||
result
|
||||
end
|
||||
|
||||
escaped =
|
||||
case env do
|
||||
%{function: nil, lexical_tracker: pid} when is_pid(pid) ->
|
||||
|
||||
@@ -295,7 +295,7 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated "Use Kernel.is_struct/2 or pattern match on %Regex{} instead"
|
||||
@deprecated "Use Kernel.is_struct(term, Regex) or pattern match on %Regex{} instead"
|
||||
def regex?(term)
|
||||
def regex?(%Regex{}), do: true
|
||||
def regex?(_), do: false
|
||||
|
||||
@@ -8,12 +8,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
|
||||
|
||||
Elixir version | Support
|
||||
:------------- | :-----------------------------
|
||||
1.15 | Development
|
||||
1.14 | Bug fixes and security patches
|
||||
1.15 | Bug fixes and security patches
|
||||
1.14 | Security patches only
|
||||
1.13 | Security patches only
|
||||
1.12 | Security patches only
|
||||
1.11 | Security patches only
|
||||
1.10 | Security patches only
|
||||
|
||||
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
|
||||
|
||||
@@ -82,7 +81,7 @@ Version | Deprecated feature | Replaced by (ava
|
||||
:-------| :-------------------------------------------------- | :---------------------------------------------------------------
|
||||
[v1.15] | `Calendar.ISO.day_of_week/3` | `Calendar.ISO.day_of_week/4` (v1.11)
|
||||
[v1.15] | `Exception.exception?/1` | `Kernel.is_exception/1` (v1.11)
|
||||
[v1.15] | `Regex.regex?/1` | `Kernel.is_struct/2` (v1.11)
|
||||
[v1.15] | `Regex.regex?/1` | `Kernel.is_struct/2` (`Kernel.is_struct(term, Regex)`) (v1.11)
|
||||
[v1.15] | `Logger.warn/2` | `Logger.warning/2` (v1.11)
|
||||
[v1.14] | `use Bitwise` | `import Bitwise` (v1.0)
|
||||
[v1.14] | `~~~/1` | `bnot/2` (v1.0)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
tmp/
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# Usage:
|
||||
#
|
||||
# With Elixir archive:
|
||||
#
|
||||
# ELIXIR_ZIP=Precompiled.zip OTP_VERSION=25.3.2.2 ./build.sh
|
||||
#
|
||||
# With Elixir version:
|
||||
#
|
||||
# ELIXIR_VERSION=1.14.5 OTP_VERSION=25.3.2.2 ./build.sh
|
||||
set -euo pipefail
|
||||
|
||||
OTP_VERSION="${OTP_VERSION:-26.0}"
|
||||
otp_release=`echo "${OTP_VERSION}" | cut -d. -f1`
|
||||
|
||||
mkdir -p tmp
|
||||
|
||||
ELIXIR_VERSION="${ELIXIR_VERSION:-}"
|
||||
if [ -n "${ELIXIR_VERSION}" ]; then
|
||||
ELIXIR_ZIP="tmp/elixir-${ELIXIR_VERSION}-otp-${otp_release}.zip"
|
||||
if [ ! -f "${ELIXIR_ZIP}" ]; then
|
||||
url="https://github.com/elixir-lang/elixir/releases/download/v${ELIXIR_VERSION}/elixir-otp-${otp_release}.zip"
|
||||
echo "downloading ${url}"
|
||||
curl --fail -L -o "${ELIXIR_ZIP}" "${url}"
|
||||
fi
|
||||
basename=elixir-${ELIXIR_VERSION}-otp-${otp_release}
|
||||
else
|
||||
basename=elixir-otp-${otp_release}
|
||||
fi
|
||||
|
||||
if [ ! -d "tmp/${basename}" ]; then
|
||||
unzip -d "tmp/${basename}" "${ELIXIR_ZIP}"
|
||||
fi
|
||||
|
||||
# brew install makensis
|
||||
# apt install -y nsis
|
||||
# choco install -y nsis
|
||||
export PATH="/c/Program Files (x86)/NSIS:${PATH}"
|
||||
makensis \
|
||||
-X"OutFile tmp\\${basename}.exe" \
|
||||
-DOTP_VERSION=${OTP_VERSION} \
|
||||
-DOTP_RELEASE="${otp_release}" \
|
||||
-DELIXIR_DIR=tmp\\${basename} \
|
||||
installer.nsi
|
||||
|
||||
echo "Installer path: tmp/${basename}.exe"
|
||||
@@ -0,0 +1,209 @@
|
||||
!include "MUI2.nsh"
|
||||
!include "StrFunc.nsh"
|
||||
${Using:StrFunc} StrStr
|
||||
${Using:StrFunc} UnStrStr
|
||||
${Using:StrFunc} UnStrRep
|
||||
|
||||
Name "Elixir"
|
||||
ManifestDPIAware true
|
||||
Unicode True
|
||||
InstallDir "$PROGRAMFILES64\Elixir"
|
||||
!define MUI_ICON "assets\drop.ico"
|
||||
|
||||
; Install Page: Install Erlang/OTP
|
||||
|
||||
Page custom CheckOTPPageShow CheckOTPPageLeave
|
||||
|
||||
var Dialog
|
||||
var DownloadOTPLink
|
||||
var InstalledERTSVersion
|
||||
var InstalledOTPRelease
|
||||
var OTPPath
|
||||
Function CheckOTPPageShow
|
||||
!insertmacro MUI_HEADER_TEXT "Checking Erlang/OTP" ""
|
||||
|
||||
nsDialogs::Create 1018
|
||||
Pop $Dialog
|
||||
|
||||
${If} $Dialog == error
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
EnumRegKey $0 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang" 0
|
||||
ReadRegStr $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang\$0" ""
|
||||
StrCpy $InstalledERTSVersion $0
|
||||
StrCpy $OTPPath $1
|
||||
|
||||
${If} $1 == ""
|
||||
${NSD_CreateLabel} 0 0 100% 20u "Couldn't find existing Erlang/OTP installation. Click the link below to download and install it before proceeding."
|
||||
${NSD_CreateLink} 0 25u 100% 20u "Download Erlang/OTP ${OTP_RELEASE}"
|
||||
Pop $DownloadOTPLink
|
||||
${NSD_OnClick} $DownloadOTPLink OpenOTPDownloads
|
||||
${Else}
|
||||
nsExec::ExecToStack `$OTPPath\bin\erl.exe -noinput -eval "\
|
||||
io:put_chars(erlang:system_info(otp_release)),\
|
||||
halt()."`
|
||||
Pop $0
|
||||
Pop $1
|
||||
|
||||
${If} $0 == 0
|
||||
StrCpy $InstalledOTPRelease $1
|
||||
${If} $InstalledOTPRelease == ${OTP_RELEASE}
|
||||
${NSD_CreateLabel} 0 0 100% 20u "Found existing Erlang/OTP $InstalledOTPRelease installation at $OTPPath. Please proceed."
|
||||
${ElseIf} $2 < ${OTP_RELEASE}
|
||||
${NSD_CreateLabel} 0 0 100% 30u "Found existing Erlang/OTP $InstalledOTPRelease installation at $OTPPath but this Elixir installer was precompiled for Erlang/OTP ${OTP_RELEASE}. \
|
||||
We recommend checking if there is an Elixir version precompiled for Erlang/OTP $InstalledOTPRelease. Otherwise, proceed."
|
||||
${Else}
|
||||
SetErrorlevel 5
|
||||
MessageBox MB_ICONSTOP "Found existing Erlang/OTP $InstalledOTPRelease installation at $OTPPath but this Elixir version was precompiled for Erlang/OTP ${OTP_RELEASE}. \
|
||||
Please upgrade your Erlang/OTP version or choose an Elixir installer matching your Erlang/OTP version"
|
||||
Quit
|
||||
${EndIf}
|
||||
${Else}
|
||||
SetErrorlevel 5
|
||||
MessageBox MB_ICONSTOP "Found existing Erlang/OTP installation at $OTPPath but checking it exited with $0: $1"
|
||||
Quit
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
nsDialogs::Show
|
||||
FunctionEnd
|
||||
|
||||
Function OpenOTPDownloads
|
||||
ExecShell "open" "https://www.erlang.org/downloads/${OTP_RELEASE}"
|
||||
FunctionEnd
|
||||
|
||||
Function CheckOTPPageLeave
|
||||
FunctionEnd
|
||||
|
||||
; Install Page: Files
|
||||
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
; Install Page: Finish
|
||||
|
||||
Page custom FinishPageShow FinishPageLeave
|
||||
|
||||
var AddOTPToPathCheckbox
|
||||
var AddElixirToPathCheckbox
|
||||
Function FinishPageShow
|
||||
!insertmacro MUI_HEADER_TEXT "Finish Setup" ""
|
||||
|
||||
nsDialogs::Create 1018
|
||||
Pop $Dialog
|
||||
|
||||
${If} $Dialog == error
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
${NSD_CreateCheckbox} 0 0 195u 10u "&Add $INSTDIR\bin to %PATH%"
|
||||
Pop $AddElixirToPathCheckbox
|
||||
SendMessage $AddElixirToPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||
|
||||
EnumRegKey $0 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang" 0
|
||||
ReadRegStr $0 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang\$0" ""
|
||||
StrCpy $OTPPath $0
|
||||
${If} $0 != ""
|
||||
${NSD_CreateCheckbox} 0 20u 195u 10u "&Add $0\bin to %PATH%"
|
||||
Pop $AddOTPToPathCheckbox
|
||||
SendMessage $AddOTPToPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||
${EndIf}
|
||||
|
||||
nsDialogs::Show
|
||||
FunctionEnd
|
||||
|
||||
Function FinishPageLeave
|
||||
${NSD_GetState} $AddOTPToPathCheckbox $0
|
||||
${If} $0 <> ${BST_UNCHECKED}
|
||||
ReadRegStr $0 HKCU "Environment" "Path"
|
||||
${StrStr} $1 "$0" "$OTPPath\bin"
|
||||
${If} $1 == ""
|
||||
WriteRegExpandStr HKCU "Environment" "Path" "$OTPPath\bin;$0"
|
||||
${Else}
|
||||
MessageBox MB_OK "$OTPPath\bin already in %PATH%"
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
${NSD_GetState} $AddElixirToPathCheckbox $0
|
||||
${If} $0 <> ${BST_UNCHECKED}
|
||||
ReadRegStr $0 HKCU "Environment" "Path"
|
||||
${StrStr} $1 "$0" "$INSTDIR\bin"
|
||||
${If} $1 == ""
|
||||
WriteRegExpandStr HKCU "Environment" "Path" "$INSTDIR\bin;$0"
|
||||
${Else}
|
||||
MessageBox MB_OK "$INSTDIR\bin already in %PATH%"
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Section "Install Elixir" SectionElixir
|
||||
SetOutPath "$INSTDIR"
|
||||
File /r "${ELIXIR_DIR}\"
|
||||
|
||||
WriteUninstaller "Uninstall.exe"
|
||||
SectionEnd
|
||||
|
||||
; Uninstall Page: Files
|
||||
|
||||
!insertmacro MUI_UNPAGE_DIRECTORY
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
|
||||
Section "Uninstall"
|
||||
RMDir /r "$INSTDIR"
|
||||
SectionEnd
|
||||
|
||||
; Uninstall Page: Finish
|
||||
|
||||
var RemoveOTPFromPathCheckbox
|
||||
var RemoveElixirFromPathCheckbox
|
||||
Function un.FinishPageShow
|
||||
!insertmacro MUI_HEADER_TEXT "Finish Setup" ""
|
||||
|
||||
nsDialogs::Create 1018
|
||||
Pop $Dialog
|
||||
|
||||
${If} $Dialog == error
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
ReadRegStr $0 HKCU "Environment" "Path"
|
||||
${UnStrStr} $1 "$0" "$INSTDIR\bin"
|
||||
${If} $1 != ""
|
||||
${NSD_CreateCheckbox} 0 0 195u 10u "&Remove $INSTDIR\bin from %PATH%"
|
||||
Pop $RemoveElixirFromPathCheckbox
|
||||
SendMessage $RemoveElixirFromPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||
${EndIf}
|
||||
|
||||
EnumRegKey $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang" 0
|
||||
ReadRegStr $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang\$1" ""
|
||||
StrCpy $OTPPath $1
|
||||
${UnStrStr} $1 "$0" "$OTPPath\bin"
|
||||
${If} $1 != ""
|
||||
${NSD_CreateCheckbox} 0 20u 195u 10u "&Remove $OTPPath\bin from %PATH%"
|
||||
Pop $RemoveOTPFromPathCheckbox
|
||||
SendMessage $RemoveOTPFromPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||
${EndIf}
|
||||
|
||||
nsDialogs::Show
|
||||
FunctionEnd
|
||||
|
||||
Function un.FinishPageLeave
|
||||
ReadRegStr $0 HKCU "Environment" "Path"
|
||||
|
||||
${NSD_GetState} $RemoveOTPFromPathCheckbox $1
|
||||
${If} $1 <> ${BST_UNCHECKED}
|
||||
${UnStrRep} $0 "$0" "$OTPPath\bin;" ""
|
||||
${EndIf}
|
||||
|
||||
${NSD_GetState} $RemoveElixirFromPathCheckbox $1
|
||||
${If} $1 <> ${BST_UNCHECKED}
|
||||
${UnStrRep} $0 "$0" "$INSTDIR\bin;" ""
|
||||
${EndIf}
|
||||
|
||||
WriteRegExpandStr HKCU "Environment" "Path" "$0"
|
||||
FunctionEnd
|
||||
|
||||
UninstPage custom un.FinishPageShow un.FinishPageLeave
|
||||
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
@@ -192,7 +192,8 @@ start_cli() ->
|
||||
'Elixir.Kernel.CLI':main(init:get_plain_arguments()),
|
||||
elixir_config:booted().
|
||||
|
||||
%% TODO: Delete prim_tty branches and -user on Erlang/OTP 26.
|
||||
%% TODO: Delete prim_tty branches and -user on Erlang/OTP 26
|
||||
%% and add "-e :elixir.start_iex" to bin/iex instead of $MODE.
|
||||
start() ->
|
||||
case code:ensure_loaded(prim_tty) of
|
||||
{module, _} ->
|
||||
@@ -207,6 +208,7 @@ start() ->
|
||||
start_iex() ->
|
||||
case code:ensure_loaded(prim_tty) of
|
||||
{module, _} ->
|
||||
start_cli(),
|
||||
spawn(fun() ->
|
||||
elixir_config:wait_until_booted(),
|
||||
(shell:whereis() =:= undefined) andalso 'Elixir.IEx':cli()
|
||||
|
||||
@@ -16,7 +16,7 @@ quoted(Forms, File, Callback) ->
|
||||
|
||||
elixir_lexical:run(
|
||||
Env,
|
||||
fun (LexicalEnv) -> eval_or_compile(Forms, [], LexicalEnv) end,
|
||||
fun (LexicalEnv) -> maybe_fast_compile(Forms, [], LexicalEnv) end,
|
||||
fun (#{lexical_tracker := Pid}) -> Callback(File, Pid) end
|
||||
),
|
||||
|
||||
@@ -32,7 +32,7 @@ file(File, Callback) ->
|
||||
%% Evaluates the given code through the Erlang compiler.
|
||||
%% It may end-up evaluating the code if it is deemed a
|
||||
%% more efficient strategy depending on the code snippet.
|
||||
eval_or_compile(Forms, Args, E) ->
|
||||
maybe_fast_compile(Forms, Args, E) ->
|
||||
case (?key(E, module) == nil) andalso allows_fast_compilation(Forms) andalso
|
||||
(not elixir_config:is_bootstrap()) of
|
||||
true -> fast_compile(Forms, E);
|
||||
@@ -40,8 +40,9 @@ eval_or_compile(Forms, Args, E) ->
|
||||
end,
|
||||
ok.
|
||||
|
||||
compile(Quoted, ArgsList, E) ->
|
||||
{Expanded, SE, EE} = elixir_expand:expand(Quoted, elixir_env:env_to_ex(E), E),
|
||||
compile(Quoted, ArgsList, #{line := Line} = E) ->
|
||||
Block = no_tail_optimize([{line, Line}], Quoted),
|
||||
{Expanded, SE, EE} = elixir_expand:expand(Block, elixir_env:env_to_ex(E), E),
|
||||
elixir_env:check_unused_vars(SE, EE),
|
||||
|
||||
{Module, Fun, Purgeable} =
|
||||
@@ -55,7 +56,7 @@ spawned_compile(ExExprs, #{line := Line, file := File} = E) ->
|
||||
{ErlExprs, _} = elixir_erl_pass:translate(ExExprs, erl_anno:new(Line), S),
|
||||
|
||||
Module = retrieve_compiler_module(),
|
||||
Fun = code_fun(?key(E, module)),
|
||||
Fun = code_fun(?key(E, module)),
|
||||
Forms = code_mod(Fun, ErlExprs, Line, File, Module, Vars),
|
||||
|
||||
{Module, Binary} = elixir_erl_compiler:noenv_forms(Forms, File, [nowarn_nomatch, no_bool_opt, no_ssa_opt]),
|
||||
@@ -106,15 +107,9 @@ allows_fast_compilation(_) ->
|
||||
|
||||
fast_compile({'__block__', _, Exprs}, E) ->
|
||||
lists:foldl(fun(Expr, _) -> fast_compile(Expr, E) end, nil, Exprs);
|
||||
fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
|
||||
fast_compile({defmodule, Meta, [Mod, [{do, Block}]]}, NoLineE) ->
|
||||
E = NoLineE#{line := ?line(Meta)},
|
||||
|
||||
Block = {'__block__', Meta, [
|
||||
{'=', Meta, [{result, Meta, ?MODULE}, TailBlock]},
|
||||
{{'.', Meta, [elixir_utils, noop]}, Meta, []},
|
||||
{result, Meta, ?MODULE}
|
||||
]},
|
||||
|
||||
Expanded = case Mod of
|
||||
{'__aliases__', _, _} ->
|
||||
case elixir_aliases:expand_or_concat(Mod, E) of
|
||||
@@ -129,6 +124,13 @@ fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
|
||||
ContextModules = [Expanded | ?key(E, context_modules)],
|
||||
elixir_module:compile(Expanded, Block, [], false, E#{context_modules := ContextModules}).
|
||||
|
||||
no_tail_optimize(Meta, Block) ->
|
||||
{'__block__', Meta, [
|
||||
{'=', Meta, [{result, Meta, ?MODULE}, Block]},
|
||||
{{'.', Meta, [elixir_utils, noop]}, Meta, []},
|
||||
{result, Meta, ?MODULE}
|
||||
]}.
|
||||
|
||||
%% Bootstrapper
|
||||
|
||||
bootstrap() ->
|
||||
|
||||
@@ -510,12 +510,15 @@ get_moduledoc_meta(Set) ->
|
||||
|
||||
get_docs(Set, Module, Definitions, Kind) ->
|
||||
[{Key,
|
||||
erl_anno:new(Line),
|
||||
maybe_generated(erl_anno:new(Line), Ctx),
|
||||
[signature_to_binary(Module, Name, Signature)],
|
||||
doc_value(Doc, Name),
|
||||
Meta
|
||||
} || {Name, Arity} <- Definitions,
|
||||
{Key, _Ctx, Line, Signature, Doc, Meta} <- ets:lookup(Set, {Kind, Name, Arity})].
|
||||
{Key, Ctx, Line, Signature, Doc, Meta} <- ets:lookup(Set, {Kind, Name, Arity})].
|
||||
|
||||
maybe_generated(Ann, nil) -> Ann;
|
||||
maybe_generated(Ann, _Ctx) -> erl_anno:set_generated(true, Ann).
|
||||
|
||||
get_callback_docs(Set, Callbacks) ->
|
||||
[{Key,
|
||||
|
||||
@@ -5,25 +5,42 @@
|
||||
spawn(Fun) ->
|
||||
CompilerInfo = get(elixir_compiler_info),
|
||||
|
||||
CodeDiagnostics =
|
||||
case get(elixir_code_diagnostics) of
|
||||
undefined -> undefined;
|
||||
{_Tail, Log} -> {[], Log}
|
||||
end,
|
||||
|
||||
{_, Ref} =
|
||||
spawn_monitor(fun() ->
|
||||
put(elixir_compiler_info, CompilerInfo),
|
||||
put(elixir_code_diagnostics, CodeDiagnostics),
|
||||
|
||||
try Fun() of
|
||||
Result -> exit({ok, Result})
|
||||
Result -> exit({ok, Result, get(elixir_code_diagnostics)})
|
||||
catch
|
||||
Kind:Reason:Stack ->
|
||||
exit({Kind, Reason, Stack})
|
||||
exit({Kind, Reason, Stack, get(elixir_code_diagnostics)})
|
||||
end
|
||||
end),
|
||||
|
||||
receive
|
||||
{'DOWN', Ref, process, _, {ok, Result}} ->
|
||||
{'DOWN', Ref, process, _, {ok, Result, Diagnostics}} ->
|
||||
copy_diagnostics(Diagnostics),
|
||||
Result;
|
||||
{'DOWN', Ref, process, _, {Kind, Reason, Stack}} ->
|
||||
{'DOWN', Ref, process, _, {Kind, Reason, Stack, Diagnostics}} ->
|
||||
copy_diagnostics(Diagnostics),
|
||||
erlang:raise(Kind, Reason, Stack)
|
||||
end.
|
||||
|
||||
copy_diagnostics(undefined) ->
|
||||
ok;
|
||||
copy_diagnostics({Head, _}) ->
|
||||
case get(elixir_code_diagnostics) of
|
||||
undefined -> ok;
|
||||
{Tail, Log} -> put(elixir_code_diagnostics, {Head ++ Tail, Log})
|
||||
end.
|
||||
|
||||
forms(Forms, File, Opts) ->
|
||||
compile(Forms, File, Opts ++ compile:env_compiler_options()).
|
||||
|
||||
|
||||
@@ -170,13 +170,22 @@ parse_error(Location, File, <<"syntax error before: ">>, Keyword, Input)
|
||||
|
||||
%% Produce a human-readable message for errors before a sigil
|
||||
parse_error(Location, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full, Input) ->
|
||||
{sigil, _, Sigil, [Content | _], _, _, _} = parse_erl_term(Full),
|
||||
{sigil, _, Atom, [Content | _], _, _, _} = parse_erl_term(Full),
|
||||
Content2 = case is_binary(Content) of
|
||||
true -> Content;
|
||||
false -> <<>>
|
||||
end,
|
||||
SigilName = list_to_binary(Sigil),
|
||||
Message = <<"syntax error before: sigil \~", SigilName/binary, " starting with content '", Content2/binary, "'">>,
|
||||
|
||||
% :static_atoms_encoder might encode :sigil_ atoms as arbitrary terms
|
||||
MaybeSigil = case is_atom(Atom) of
|
||||
true -> case atom_to_binary(Atom) of
|
||||
<<"sigil_", Chars/binary>> -> <<"\~", Chars/binary, " ">>;
|
||||
_ -> <<>>
|
||||
end;
|
||||
false -> <<>>
|
||||
end,
|
||||
|
||||
Message = <<"syntax error before: sigil ", MaybeSigil/binary, "starting with content '", Content2/binary, "'">>,
|
||||
raise_snippet(Location, File, Input, 'Elixir.SyntaxError', Message);
|
||||
|
||||
%% Binaries (and interpolation) are wrapped in [<<...>>]
|
||||
|
||||
@@ -508,19 +508,17 @@ beam_location(ModuleAsCharlist) ->
|
||||
checker_info() ->
|
||||
case get(elixir_checker_info) of
|
||||
undefined -> undefined;
|
||||
_ ->
|
||||
Log =
|
||||
case erlang:get(elixir_code_diagnostics) of
|
||||
{_, false} -> false;
|
||||
_ -> true
|
||||
end,
|
||||
|
||||
{'Elixir.Module.ParallelChecker':get(), Log}
|
||||
_ -> 'Elixir.Module.ParallelChecker':get()
|
||||
end.
|
||||
|
||||
spawn_parallel_checker(undefined, _Module, _ModuleMap) ->
|
||||
nil;
|
||||
spawn_parallel_checker({CheckerInfo, Log}, Module, ModuleMap) ->
|
||||
ok;
|
||||
spawn_parallel_checker(CheckerInfo, Module, ModuleMap) ->
|
||||
Log =
|
||||
case erlang:get(elixir_code_diagnostics) of
|
||||
{_, false} -> false;
|
||||
_ -> true
|
||||
end,
|
||||
'Elixir.Module.ParallelChecker':spawn(CheckerInfo, Module, ModuleMap, Log).
|
||||
|
||||
make_module_available(Module, Binary) ->
|
||||
|
||||
@@ -939,11 +939,11 @@ build_access(Expr, {List, Meta}) ->
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
build_sigil({sigil, Location, Sigil, Parts, Modifiers, Indentation, Delimiter}) ->
|
||||
build_sigil({sigil, Location, Atom, Parts, Modifiers, Indentation, Delimiter}) ->
|
||||
Meta = meta_from_location(Location),
|
||||
MetaWithDelimiter = [{delimiter, Delimiter} | Meta],
|
||||
MetaWithIndentation = meta_with_indentation(Meta, Indentation),
|
||||
{list_to_atom("sigil_" ++ Sigil),
|
||||
{Atom,
|
||||
MetaWithDelimiter,
|
||||
[{'<<>>', MetaWithIndentation, string_parts(Parts)}, Modifiers]}.
|
||||
|
||||
|
||||
@@ -1030,8 +1030,8 @@ extract_heredoc_with_interpolation(Line, Column, Scope, Interpol, T, H) ->
|
||||
end;
|
||||
|
||||
error ->
|
||||
Message = "heredoc allows only zero or more whitespace characters followed by a new line after ",
|
||||
{error, {Line, Column, io_lib:format(Message, []), [H, H, H]}}
|
||||
Message = "heredoc allows only whitespace characters followed by a new line after opening ",
|
||||
{error, {Line, Column + 3, io_lib:format(Message, []), [H, H, H]}}
|
||||
end.
|
||||
|
||||
extract_heredoc_header("\r\n" ++ Rest) ->
|
||||
@@ -1550,7 +1550,7 @@ tokenize_sigil_name([S | T], NameAcc, Line, Column, Scope, Tokens) when ?is_upca
|
||||
tokenize_sigil_name(T, [S | NameAcc], Line, Column + 1, Scope, Tokens);
|
||||
% With a lowercase letter and a non-empty NameAcc we return an error.
|
||||
tokenize_sigil_name([S | _T] = Original, [_ | _] = NameAcc, _Line, _Column, _Scope, _Tokens) when ?is_downcase(S) ->
|
||||
Message = "invalid sigil name, it should be either a one-letter lowercase letter or a" ++
|
||||
Message = "invalid sigil name, it should be either a one-letter lowercase letter or a" ++
|
||||
" sequence of uppercase letters only, got: ",
|
||||
{error, Message, [$~] ++ lists:reverse(NameAcc) ++ Original};
|
||||
% We finished the letters, so the name is over.
|
||||
@@ -1561,12 +1561,8 @@ tokenize_sigil_contents([H, H, H | T] = Original, [S | _] = SigilName, Line, Col
|
||||
when ?is_quote(H) ->
|
||||
case extract_heredoc_with_interpolation(Line, Column, Scope, ?is_downcase(S), T, H) of
|
||||
{ok, NewLine, NewColumn, Parts, Rest, NewScope} ->
|
||||
{Final, Modifiers} = collect_modifiers(Rest, []),
|
||||
Indentation = NewColumn - 4,
|
||||
TokenColumn = Column - 1 - length(SigilName),
|
||||
Token = {sigil, {Line, TokenColumn, nil}, SigilName, Parts, Modifiers, Indentation, <<H, H, H>>},
|
||||
NewColumnWithModifiers = NewColumn + length(Modifiers),
|
||||
tokenize(Final, NewLine, NewColumnWithModifiers, NewScope, [Token | Tokens]);
|
||||
add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, Parts, Rest, NewScope, Tokens, Indentation, <<H, H, H>>);
|
||||
|
||||
{error, Reason} ->
|
||||
error(Reason, [$~] ++ SigilName ++ Original, Scope, Tokens)
|
||||
@@ -1576,12 +1572,8 @@ tokenize_sigil_contents([H | T] = Original, [S | _] = SigilName, Line, Column, S
|
||||
when ?is_sigil(H) ->
|
||||
case elixir_interpolation:extract(Line, Column + 1, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
|
||||
{NewLine, NewColumn, Parts, Rest, NewScope} ->
|
||||
{Final, Modifiers} = collect_modifiers(Rest, []),
|
||||
Indentation = nil,
|
||||
TokenColumn = Column - 1 - length(SigilName),
|
||||
Token = {sigil, {Line, TokenColumn, nil}, SigilName, tokens_to_binary(Parts), Modifiers, Indentation, <<H>>},
|
||||
NewColumnWithModifiers = NewColumn + length(Modifiers),
|
||||
tokenize(Final, NewLine, NewColumnWithModifiers, NewScope, [Token | Tokens]);
|
||||
add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, tokens_to_binary(Parts), Rest, NewScope, Tokens, Indentation, <<H>>);
|
||||
|
||||
{error, Reason} ->
|
||||
Sigil = [$~, S, H],
|
||||
@@ -1601,6 +1593,24 @@ tokenize_sigil_contents([H | _] = Original, SigilName, Line, Column, Scope, Toke
|
||||
tokenize_sigil_contents([], _SigilName, Line, Column, Scope, Tokens) ->
|
||||
tokenize([], Line, Column, Scope, Tokens).
|
||||
|
||||
add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, Parts, Rest, Scope, Tokens, Indentation, Delimiter) ->
|
||||
TokenColumn = Column - 1 - length(SigilName),
|
||||
MaybeEncoded = case SigilName of
|
||||
% Single-letter sigils present no risk of atom exhaustion (limited possibilities)
|
||||
[_Char] -> {ok, list_to_atom("sigil_" ++ SigilName)};
|
||||
_ -> unsafe_to_atom("sigil_" ++ SigilName, Line, TokenColumn, Scope)
|
||||
end,
|
||||
case MaybeEncoded of
|
||||
{ok, Atom} ->
|
||||
{Final, Modifiers} = collect_modifiers(Rest, []),
|
||||
Token = {sigil, {Line, TokenColumn, nil}, Atom, Parts, Modifiers, Indentation, Delimiter},
|
||||
NewColumnWithModifiers = NewColumn + length(Modifiers),
|
||||
tokenize(Final, NewLine, NewColumnWithModifiers, Scope, [Token | Tokens]);
|
||||
|
||||
{error, Reason} ->
|
||||
error(Reason, Rest, Scope, Tokens)
|
||||
end.
|
||||
|
||||
%% Fail early on invalid do syntax. For example, after
|
||||
%% most keywords, after comma and so on.
|
||||
tokenize_keyword_terminator(DoLine, DoColumn, do, [{identifier, {Line, Column, Meta}, Atom} | T]) ->
|
||||
@@ -1796,7 +1806,7 @@ prune_tokens([{OpType, _, _} | _] = Tokens, [], Terminators)
|
||||
OpType =:= in_match_op; OpType =:= type_op; OpType =:= dual_op; OpType =:= mult_op;
|
||||
OpType =:= power_op; OpType =:= concat_op; OpType =:= range_op; OpType =:= xor_op;
|
||||
OpType =:= pipe_op; OpType =:= stab_op; OpType =:= when_op; OpType =:= assoc_op;
|
||||
OpType =:= rel_op; OpType =:= ternary_op ->
|
||||
OpType =:= rel_op; OpType =:= ternary_op; OpType =:= capture_op ->
|
||||
{Tokens, Terminators};
|
||||
%%% or we traverse until the end.
|
||||
prune_tokens([_ | Tokens], Opener, Terminators) ->
|
||||
|
||||
@@ -428,11 +428,12 @@ defmodule CodeFragmentTest do
|
||||
end
|
||||
|
||||
test "column out of range" do
|
||||
assert CF.surround_context("hello", {1, 20}) == :none
|
||||
assert CF.surround_context("hello", {1, 20}) ==
|
||||
%{begin: {1, 1}, context: {:local_or_var, ~c"hello"}, end: {1, 6}}
|
||||
end
|
||||
|
||||
test "local_or_var" do
|
||||
for i <- 1..8 do
|
||||
for i <- 1..9 do
|
||||
assert CF.surround_context("hello_wo", {1, i}) == %{
|
||||
context: {:local_or_var, ~c"hello_wo"},
|
||||
begin: {1, 1},
|
||||
@@ -440,9 +441,9 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("hello_wo", {1, 9}) == :none
|
||||
assert CF.surround_context("hello_wo ", {1, 10}) == :none
|
||||
|
||||
for i <- 2..9 do
|
||||
for i <- 2..10 do
|
||||
assert CF.surround_context(" hello_wo", {1, i}) == %{
|
||||
context: {:local_or_var, ~c"hello_wo"},
|
||||
begin: {1, 2},
|
||||
@@ -450,9 +451,9 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context(" hello_wo", {1, 10}) == :none
|
||||
assert CF.surround_context(" hello_wo ", {1, 11}) == :none
|
||||
|
||||
for i <- 1..6 do
|
||||
for i <- 1..7 do
|
||||
assert CF.surround_context("hello!", {1, i}) == %{
|
||||
context: {:local_or_var, ~c"hello!"},
|
||||
begin: {1, 1},
|
||||
@@ -460,9 +461,9 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("hello!", {1, 7}) == :none
|
||||
assert CF.surround_context("hello! ", {1, 8}) == :none
|
||||
|
||||
for i <- 1..5 do
|
||||
for i <- 1..6 do
|
||||
assert CF.surround_context("안녕_세상", {1, i}) == %{
|
||||
context: {:local_or_var, ~c"안녕_세상"},
|
||||
begin: {1, 1},
|
||||
@@ -470,7 +471,7 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("안녕_세상", {1, 6}) == :none
|
||||
assert CF.surround_context("안녕_세상 ", {1, 6}) == :none
|
||||
|
||||
# Keywords are not local or var
|
||||
for keyword <- ~w(do end after catch else rescue fn true false nil)c do
|
||||
@@ -484,8 +485,38 @@ defmodule CodeFragmentTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "local call" do
|
||||
test "local + operator" do
|
||||
for i <- 1..8 do
|
||||
assert CF.surround_context("hello_wo+", {1, i}) == %{
|
||||
context: {:local_or_var, ~c"hello_wo"},
|
||||
begin: {1, 1},
|
||||
end: {1, 9}
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("hello_wo+", {1, 9}) == %{
|
||||
begin: {1, 9},
|
||||
context: {:operator, ~c"+"},
|
||||
end: {1, 10}
|
||||
}
|
||||
|
||||
for i <- 1..9 do
|
||||
assert CF.surround_context("hello_wo +", {1, i}) == %{
|
||||
context: {:local_or_var, ~c"hello_wo"},
|
||||
begin: {1, 1},
|
||||
end: {1, 9}
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("hello_wo +", {1, 10}) == %{
|
||||
begin: {1, 10},
|
||||
context: {:operator, ~c"+"},
|
||||
end: {1, 11}
|
||||
}
|
||||
end
|
||||
|
||||
test "local call" do
|
||||
for i <- 1..9 do
|
||||
assert CF.surround_context("hello_wo(", {1, i}) == %{
|
||||
context: {:local_call, ~c"hello_wo"},
|
||||
begin: {1, 1},
|
||||
@@ -493,9 +524,9 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("hello_wo(", {1, 9}) == :none
|
||||
assert CF.surround_context("hello_wo(", {1, 10}) == :none
|
||||
|
||||
for i <- 1..8 do
|
||||
for i <- 1..9 do
|
||||
assert CF.surround_context("hello_wo (", {1, i}) == %{
|
||||
context: {:local_call, ~c"hello_wo"},
|
||||
begin: {1, 1},
|
||||
@@ -503,9 +534,10 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("hello_wo (", {1, 9}) == :none
|
||||
assert CF.surround_context("hello_wo (", {1, 10}) == :none
|
||||
assert CF.surround_context("hello_wo (", {1, 11}) == :none
|
||||
|
||||
for i <- 1..6 do
|
||||
for i <- 1..7 do
|
||||
assert CF.surround_context("hello!(", {1, i}) == %{
|
||||
context: {:local_call, ~c"hello!"},
|
||||
begin: {1, 1},
|
||||
@@ -513,9 +545,9 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("hello!(", {1, 7}) == :none
|
||||
assert CF.surround_context("hello!(", {1, 8}) == :none
|
||||
|
||||
for i <- 1..5 do
|
||||
for i <- 1..6 do
|
||||
assert CF.surround_context("안녕_세상(", {1, i}) == %{
|
||||
context: {:local_call, ~c"안녕_세상"},
|
||||
begin: {1, 1},
|
||||
@@ -523,7 +555,7 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("안녕_세상(", {1, 6}) == :none
|
||||
assert CF.surround_context("안녕_세상(", {1, 7}) == :none
|
||||
end
|
||||
|
||||
test "local arity" do
|
||||
@@ -651,7 +683,7 @@ defmodule CodeFragmentTest do
|
||||
end
|
||||
|
||||
test "alias" do
|
||||
for i <- 1..8 do
|
||||
for i <- 1..9 do
|
||||
assert CF.surround_context("HelloWor", {1, i}) == %{
|
||||
context: {:alias, ~c"HelloWor"},
|
||||
begin: {1, 1},
|
||||
@@ -659,9 +691,9 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("HelloWor", {1, 9}) == :none
|
||||
assert CF.surround_context("HelloWor ", {1, 10}) == :none
|
||||
|
||||
for i <- 2..9 do
|
||||
for i <- 2..10 do
|
||||
assert CF.surround_context(" HelloWor", {1, i}) == %{
|
||||
context: {:alias, ~c"HelloWor"},
|
||||
begin: {1, 2},
|
||||
@@ -669,9 +701,9 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context(" HelloWor", {1, 10}) == :none
|
||||
assert CF.surround_context(" HelloWor ", {1, 11}) == :none
|
||||
|
||||
for i <- 1..9 do
|
||||
for i <- 1..10 do
|
||||
assert CF.surround_context("Hello.Wor", {1, i}) == %{
|
||||
context: {:alias, ~c"Hello.Wor"},
|
||||
begin: {1, 1},
|
||||
@@ -679,9 +711,9 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("Hello.Wor", {1, 10}) == :none
|
||||
assert CF.surround_context("Hello.Wor ", {1, 11}) == :none
|
||||
|
||||
for i <- 1..11 do
|
||||
for i <- 1..12 do
|
||||
assert CF.surround_context("Hello . Wor", {1, i}) == %{
|
||||
context: {:alias, ~c"Hello.Wor"},
|
||||
begin: {1, 1},
|
||||
@@ -689,9 +721,9 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("Hello . Wor", {1, 12}) == :none
|
||||
assert CF.surround_context("Hello . Wor ", {1, 13}) == :none
|
||||
|
||||
for i <- 1..15 do
|
||||
for i <- 1..16 do
|
||||
assert CF.surround_context("Foo . Bar . Baz", {1, i}) == %{
|
||||
context: {:alias, ~c"Foo.Bar.Baz"},
|
||||
begin: {1, 1},
|
||||
@@ -858,7 +890,7 @@ defmodule CodeFragmentTest do
|
||||
end: {1, 15}
|
||||
}
|
||||
|
||||
for i <- 2..9 do
|
||||
for i <- 2..10 do
|
||||
assert CF.surround_context("%HelloWor", {1, i}) == %{
|
||||
context: {:struct, ~c"HelloWor"},
|
||||
begin: {1, 1},
|
||||
@@ -866,7 +898,7 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("%HelloWor", {1, 10}) == :none
|
||||
assert CF.surround_context("%HelloWor ", {1, 11}) == :none
|
||||
|
||||
# With dot
|
||||
assert CF.surround_context("%Hello.Wor", {1, 1}) == %{
|
||||
@@ -875,7 +907,7 @@ defmodule CodeFragmentTest do
|
||||
end: {1, 11}
|
||||
}
|
||||
|
||||
for i <- 2..10 do
|
||||
for i <- 2..11 do
|
||||
assert CF.surround_context("%Hello.Wor", {1, i}) == %{
|
||||
context: {:struct, ~c"Hello.Wor"},
|
||||
begin: {1, 1},
|
||||
@@ -883,7 +915,7 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("%Hello.Wor", {1, 11}) == :none
|
||||
assert CF.surround_context("%Hello.Wor ", {1, 12}) == :none
|
||||
|
||||
# With spaces
|
||||
assert CF.surround_context("% Hello . Wor", {1, 1}) == %{
|
||||
@@ -892,7 +924,7 @@ defmodule CodeFragmentTest do
|
||||
end: {1, 14}
|
||||
}
|
||||
|
||||
for i <- 2..13 do
|
||||
for i <- 2..14 do
|
||||
assert CF.surround_context("% Hello . Wor", {1, i}) == %{
|
||||
context: {:struct, ~c"Hello.Wor"},
|
||||
begin: {1, 1},
|
||||
@@ -900,7 +932,7 @@ defmodule CodeFragmentTest do
|
||||
}
|
||||
end
|
||||
|
||||
assert CF.surround_context("% Hello . Wor", {1, 14}) == :none
|
||||
assert CF.surround_context("% Hello . Wor ", {1, 15}) == :none
|
||||
end
|
||||
|
||||
test "module attributes" do
|
||||
@@ -1098,6 +1130,8 @@ defmodule CodeFragmentTest do
|
||||
|
||||
test "keeps operators" do
|
||||
assert cc2q("1 + 2") == s2q("1 + __cursor__()")
|
||||
assert cc2q("&foo") == s2q("&__cursor__()")
|
||||
assert cc2q("&foo/") == s2q("&foo/__cursor__()")
|
||||
end
|
||||
|
||||
test "keeps function calls without parens" do
|
||||
@@ -1200,11 +1234,6 @@ defmodule CodeFragmentTest do
|
||||
assert cc2q("(fn x, y -> x + y end") == s2q("(__cursor__())")
|
||||
end
|
||||
|
||||
test "removes captures" do
|
||||
assert cc2q("[& &1") == s2q("[__cursor__()]")
|
||||
assert cc2q("[&(&1") == s2q("[__cursor__()]")
|
||||
end
|
||||
|
||||
test "removes closed terminators" do
|
||||
assert cc2q("foo([1, 2, 3]") == s2q("foo(__cursor__())")
|
||||
assert cc2q("foo({1, 2, 3}") == s2q("foo(__cursor__())")
|
||||
|
||||
@@ -90,6 +90,28 @@ defmodule CodeTest do
|
||||
Code.eval_quoted(quoted, [])
|
||||
end)
|
||||
end
|
||||
|
||||
test "captures unknown local calls" do
|
||||
sample = """
|
||||
defmodule CodeTest.UnknownLocalCall do
|
||||
def perform do
|
||||
foo()
|
||||
end
|
||||
end
|
||||
"""
|
||||
|
||||
assert {:rescued, [%{message: message}]} =
|
||||
Code.with_diagnostics(fn ->
|
||||
try do
|
||||
quoted = Code.string_to_quoted!(sample, columns: true)
|
||||
Code.eval_quoted(quoted, [])
|
||||
rescue
|
||||
_ -> :rescued
|
||||
end
|
||||
end)
|
||||
|
||||
assert message =~ "undefined function foo/0"
|
||||
end
|
||||
end
|
||||
|
||||
describe "eval_string/1,2,3" do
|
||||
@@ -325,7 +347,12 @@ defmodule CodeTest do
|
||||
assert env.versioned_vars == %{}
|
||||
|
||||
assert_receive {:trace, {:on_module, _, _}, %{module: CodeTest.TracingPruning} = trace_env}
|
||||
assert trace_env.versioned_vars == %{{:result, Kernel} => 5, {:x, nil} => 1, {:y, nil} => 4}
|
||||
|
||||
assert trace_env.versioned_vars == %{
|
||||
{:result, :elixir_compiler} => 5,
|
||||
{:x, nil} => 1,
|
||||
{:y, nil} => 4
|
||||
}
|
||||
end
|
||||
|
||||
test "with defguard" do
|
||||
@@ -474,6 +501,14 @@ defmodule CodeTest do
|
||||
:code.purge(CompileCrossSample)
|
||||
:code.delete(CompileCrossSample)
|
||||
end
|
||||
|
||||
test "disables tail call optimization at the root" do
|
||||
try do
|
||||
Code.compile_string("List.flatten(123)")
|
||||
rescue
|
||||
_ -> assert Enum.any?(__STACKTRACE__, &match?({_, :__FILE__, 1, _}, &1))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "format_string/2 returns empty iodata for empty string" do
|
||||
|
||||
@@ -1102,6 +1102,16 @@ defmodule EnumTest do
|
||||
[2, 1, 3]
|
||||
end
|
||||
|
||||
test "slice on MapSets" do
|
||||
assert MapSet.new(1..10) |> Enum.slice(0, 2) |> Enum.count() == 2
|
||||
assert MapSet.new(1..3) |> Enum.slice(0, 10) |> Enum.count() == 3
|
||||
assert MapSet.new(1..10) |> Enum.slice(0..1) |> Enum.count() == 2
|
||||
assert MapSet.new(1..3) |> Enum.slice(0..10) |> Enum.count() == 3
|
||||
|
||||
assert MapSet.new(1..10) |> Enum.slice(0..4//2) |> Enum.count() == 3
|
||||
assert MapSet.new(1..10) |> Enum.slice(0..5//2) |> Enum.count() == 3
|
||||
end
|
||||
|
||||
test "sort/1" do
|
||||
assert Enum.sort([5, 3, 2, 4, 1]) == [1, 2, 3, 4, 5]
|
||||
end
|
||||
|
||||
@@ -381,4 +381,30 @@ defmodule Kernel.DocsTest do
|
||||
{{:fuz, 0}, :none}
|
||||
] = Enum.sort(function_docs)
|
||||
end
|
||||
|
||||
test "generated functions are annotated as such" do
|
||||
write_beam(
|
||||
defmodule ToBeUsed do
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@doc "Hello"
|
||||
def foo, do: :bar
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
write_beam(
|
||||
defmodule WillBeUsing do
|
||||
use ToBeUsed
|
||||
end
|
||||
)
|
||||
|
||||
{:docs_v1, _, _, _, _, _, docs} = Code.fetch_docs(WillBeUsing)
|
||||
|
||||
assert [
|
||||
{{:function, :foo, 0}, [generated: true, location: 399], ["foo()"],
|
||||
%{"en" => "Hello"}, %{}}
|
||||
] = docs
|
||||
end
|
||||
end
|
||||
|
||||
@@ -182,6 +182,9 @@ defmodule Kernel.ParserTest do
|
||||
assert Code.string_to_quoted(":there_is_no_such_atom", existing_atoms_only: true) ==
|
||||
{:error,
|
||||
{[line: 1, column: 1], "unsafe atom does not exist: ", "there_is_no_such_atom"}}
|
||||
|
||||
assert Code.string_to_quoted("~UNKNOWN'foo bar'", existing_atoms_only: true) ==
|
||||
{:error, {[line: 1, column: 1], "unsafe atom does not exist: ", "sigil_UNKNOWN"}}
|
||||
end
|
||||
|
||||
test "encodes atoms" do
|
||||
@@ -228,6 +231,20 @@ defmodule Kernel.ParserTest do
|
||||
)
|
||||
end
|
||||
|
||||
test "encodes multi-letter sigils" do
|
||||
ref = make_ref()
|
||||
|
||||
encoder = fn atom, meta ->
|
||||
assert atom == "sigil_UNKNOWN"
|
||||
assert meta[:line] == 1
|
||||
assert meta[:column] == 1
|
||||
{:ok, ref}
|
||||
end
|
||||
|
||||
assert {:ok, {^ref, [delimiter: "'", line: 1], [{:<<>>, [line: 1], ["abc"]}, []]}} =
|
||||
Code.string_to_quoted("~UNKNOWN'abc'", static_atoms_encoder: encoder)
|
||||
end
|
||||
|
||||
test "addresses ambiguities" do
|
||||
encoder = fn string, _meta -> {:ok, {:atom, string}} end
|
||||
|
||||
@@ -254,6 +271,16 @@ defmodule Kernel.ParserTest do
|
||||
Code.string_to_quoted("[do: 1, true: 2, end: 3]", static_atoms_encoder: encoder)
|
||||
end
|
||||
|
||||
test "does not encode one-letter sigils" do
|
||||
encoder = fn atom, _meta -> raise "shouldn't be invoked for #{atom}" end
|
||||
|
||||
assert {:ok, {:sigil_z, [{:delimiter, "'"}, {:line, 1}], [{:<<>>, [line: 1], ["foo"]}, []]}} =
|
||||
Code.string_to_quoted("~z'foo'", static_atoms_encoder: encoder)
|
||||
|
||||
assert {:ok, {:sigil_Z, [{:delimiter, "'"}, {:line, 1}], [{:<<>>, [line: 1], ["foo"]}, []]}} =
|
||||
Code.string_to_quoted("~Z'foo'", static_atoms_encoder: encoder)
|
||||
end
|
||||
|
||||
test "returns errors on long atoms even when using static_atoms_encoder" do
|
||||
atom = String.duplicate("a", 256)
|
||||
|
||||
@@ -271,6 +298,9 @@ defmodule Kernel.ParserTest do
|
||||
|
||||
assert {:error, {[line: 1, column: 1], "Invalid atom name: ", "there_is_no_such_atom"}} =
|
||||
Code.string_to_quoted(":there_is_no_such_atom", static_atoms_encoder: encoder)
|
||||
|
||||
assert {:error, {[line: 1, column: 1], "Invalid atom name: ", "sigil_UNKNOWN"}} =
|
||||
Code.string_to_quoted("~UNKNOWN'foo bar'", static_atoms_encoder: encoder)
|
||||
end
|
||||
|
||||
test "may return tuples" do
|
||||
@@ -538,7 +568,7 @@ defmodule Kernel.ParserTest do
|
||||
describe "syntax errors" do
|
||||
test "invalid heredoc start" do
|
||||
assert_syntax_error(
|
||||
~r/nofile:1:1: heredoc allows only zero or more whitespace characters followed by a new line after \"\"\"/,
|
||||
~r/nofile:1:4: heredoc allows only whitespace characters followed by a new line after opening \"\"\"/,
|
||||
~c"\"\"\"bar\n\"\"\""
|
||||
)
|
||||
end
|
||||
|
||||
@@ -831,11 +831,25 @@ defmodule KernelTest do
|
||||
end
|
||||
|
||||
test "matching attribute" do
|
||||
assert_raise ArgumentError, ~r"invalid write attribute syntax", fn ->
|
||||
assert_raise ArgumentError, ~r"invalid usage of module attributes", fn ->
|
||||
defmodule MatchAttributeInModule do
|
||||
@foo = 42
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, ~r"invalid usage of module attributes", fn ->
|
||||
defmodule MatchAttributeInModule do
|
||||
@foo 16
|
||||
<<_::@foo>> = "ab"
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, ~r"invalid usage of module attributes", fn ->
|
||||
defmodule MatchAttributeInModule do
|
||||
@foo 16
|
||||
<<_::size(@foo)>> = "ab"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -261,22 +261,22 @@ vc_merge_conflict_test() ->
|
||||
tokenize_error("<<<<<<< HEAD\n[1, 2, 3]").
|
||||
|
||||
sigil_terminator_test() ->
|
||||
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"/">>}] = tokenize("~r/foo/"),
|
||||
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"[">>}] = tokenize("~r[foo]"),
|
||||
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"\"">>}] = tokenize("~r\"foo\""),
|
||||
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"/">>},
|
||||
[{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"/">>}] = tokenize("~r/foo/"),
|
||||
[{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"[">>}] = tokenize("~r[foo]"),
|
||||
[{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"\"">>}] = tokenize("~r\"foo\""),
|
||||
[{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"/">>},
|
||||
{comp_op, {1, 9, nil}, '=='},
|
||||
{identifier, {1, 12, _}, bar}] = tokenize("~r/foo/ == bar"),
|
||||
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "iu", nil, <<"/">>},
|
||||
[{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "iu", nil, <<"/">>},
|
||||
{comp_op, {1, 11, nil}, '=='},
|
||||
{identifier, {1, 14, _}, bar}] = tokenize("~r/foo/iu == bar"),
|
||||
[{sigil, {1, 1, nil}, "M", [<<"1 2 3">>], "u8", nil, <<"[">>}] = tokenize("~M[1 2 3]u8").
|
||||
[{sigil, {1, 1, nil}, sigil_M, [<<"1 2 3">>], "u8", nil, <<"[">>}] = tokenize("~M[1 2 3]u8").
|
||||
|
||||
sigil_heredoc_test() ->
|
||||
[{sigil, {1, 1, nil}, "S", [<<"sigil heredoc\n">>], "", 0, <<"\"\"\"">>}] = tokenize("~S\"\"\"\nsigil heredoc\n\"\"\""),
|
||||
[{sigil, {1, 1, nil}, "S", [<<"sigil heredoc\n">>], "", 0, <<"'''">>}] = tokenize("~S'''\nsigil heredoc\n'''"),
|
||||
[{sigil, {1, 1, nil}, "S", [<<"sigil heredoc\n">>], "", 2, <<"\"\"\"">>}] = tokenize("~S\"\"\"\n sigil heredoc\n \"\"\""),
|
||||
[{sigil, {1, 1, nil}, "s", [<<"sigil heredoc\n">>], "", 2, <<"\"\"\"">>}] = tokenize("~s\"\"\"\n sigil heredoc\n \"\"\"").
|
||||
[{sigil, {1, 1, nil}, sigil_S, [<<"sigil heredoc\n">>], "", 0, <<"\"\"\"">>}] = tokenize("~S\"\"\"\nsigil heredoc\n\"\"\""),
|
||||
[{sigil, {1, 1, nil}, sigil_S, [<<"sigil heredoc\n">>], "", 0, <<"'''">>}] = tokenize("~S'''\nsigil heredoc\n'''"),
|
||||
[{sigil, {1, 1, nil}, sigil_S, [<<"sigil heredoc\n">>], "", 2, <<"\"\"\"">>}] = tokenize("~S\"\"\"\n sigil heredoc\n \"\"\""),
|
||||
[{sigil, {1, 1, nil}, sigil_s, [<<"sigil heredoc\n">>], "", 2, <<"\"\"\"">>}] = tokenize("~s\"\"\"\n sigil heredoc\n \"\"\"").
|
||||
|
||||
invalid_sigil_delimiter_test() ->
|
||||
{1, 1, "invalid sigil delimiter: ", Message} = tokenize_error("~s\\"),
|
||||
|
||||
@@ -71,11 +71,11 @@ defmodule ExUnit.CaptureServer do
|
||||
refs = Map.put(config.log_captures, ref, true)
|
||||
|
||||
{level, opts} = Keyword.pop(opts, :level)
|
||||
true = :ets.insert(@ets, {ref, string_io, level || :all})
|
||||
{formatter_mod, formatter_config} = Logger.default_formatter(opts)
|
||||
true = :ets.insert(@ets, {ref, string_io, level || :all, formatter_mod, formatter_config})
|
||||
|
||||
if map_size(refs) == 1 do
|
||||
formatter = Logger.default_formatter(opts)
|
||||
:ok = :logger.add_handler(@name, __MODULE__, %{formatter: formatter})
|
||||
:ok = :logger.add_handler(@name, __MODULE__, %{})
|
||||
|
||||
status =
|
||||
with {:ok, config} <- :logger.get_handler_config(:default),
|
||||
@@ -226,13 +226,20 @@ defmodule ExUnit.CaptureServer do
|
||||
|
||||
## :logger handler callback.
|
||||
|
||||
def log(event, %{} = config) do
|
||||
%{formatter: {formatter_mod, formatter_config}} = config
|
||||
chardata = formatter_mod.format(event, formatter_config)
|
||||
|
||||
for [string_io, level] <- :ets.match(@ets, {:_, :"$1", :"$2"}),
|
||||
def log(event, _config) do
|
||||
for {_ref, string_io, level, formatter_mod, formatter_config} <- :ets.tab2list(@ets),
|
||||
:logger.compare_levels(event.level, level) in [:gt, :eq] do
|
||||
:ok = IO.write(string_io, chardata)
|
||||
chardata = formatter_mod.format(event, formatter_config)
|
||||
# There is a race condition where the capture_log is removed
|
||||
# but another process is attempting to log to string io device
|
||||
# that no longer exists, so we wrap it in try/catch.
|
||||
try do
|
||||
IO.write(string_io, chardata)
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -167,6 +167,9 @@ defmodule ExUnit.Case do
|
||||
|
||||
* `:doctest_data` - additional metadata about doctests (if a doctest)
|
||||
|
||||
* `:test_type` - the test type used when printing test results.
|
||||
It is set by ExUnit to `:test`, `:doctest` and so on, but is customizable.
|
||||
|
||||
The following tags customize how tests behave:
|
||||
|
||||
* `:capture_log` - see the "Log Capture" section below
|
||||
@@ -178,9 +181,6 @@ defmodule ExUnit.Case do
|
||||
|
||||
* `:tmp_dir` - (since v1.11.0) see the "Tmp Dir" section below
|
||||
|
||||
* `:test_type` - the test type used when printing test results.
|
||||
It is set by ExUnit to `:test`, `:doctest` and so on, but is customizable.
|
||||
|
||||
## Filters
|
||||
|
||||
Tags can also be used to identify specific tests, which can then
|
||||
|
||||
@@ -130,7 +130,7 @@ defmodule ExUnit.CaseTemplate do
|
||||
The second argument passed to `use MyCase` gets forwarded to `using/2` too:
|
||||
|
||||
defmodule SomeTestCase do
|
||||
use MyCase, async: true, import_helpers: true, async: true
|
||||
use MyCase, async: true, import_helpers: true
|
||||
|
||||
test "the truth" do
|
||||
# truth/0 comes from MyApp.TestHelpers:
|
||||
|
||||
@@ -382,9 +382,15 @@ defmodule ExUnit.DocTest do
|
||||
last_expr = Macro.to_string(last_expr(expr_ast))
|
||||
|
||||
quote do
|
||||
# `expr_ast` may introduce variables that may be
|
||||
# used within `expected_ast` so they both need to
|
||||
# unquoted together here
|
||||
value = unquote(expr_ast)
|
||||
expected_value = unquote(expected_ast)
|
||||
|
||||
ExUnit.DocTest.__test__(
|
||||
unquote(expr_ast),
|
||||
unquote(expected_ast),
|
||||
value,
|
||||
expected_value,
|
||||
unquote(doctest),
|
||||
unquote(last_expr),
|
||||
unquote(expected),
|
||||
@@ -624,57 +630,37 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
end
|
||||
|
||||
defp adjust_indent(kind, [line | rest], line_no, adjusted_lines, indent, module)
|
||||
when kind in [:prompt, :after_prompt] do
|
||||
defp adjust_indent(kind, [line | rest], line_no, adjusted_lines, indent, module) do
|
||||
stripped_line = strip_indent(line, indent)
|
||||
trimmed_line = String.trim_leading(line)
|
||||
|
||||
case String.trim_leading(line) do
|
||||
"" ->
|
||||
:ok
|
||||
if kind != :code and trimmed_line != stripped_line do
|
||||
n_spaces = if indent == 1, do: "#{indent} space", else: "#{indent} spaces"
|
||||
|
||||
^stripped_line ->
|
||||
:ok
|
||||
raise Error,
|
||||
line: line_no,
|
||||
module: module,
|
||||
message: """
|
||||
indentation level mismatch on doctest line: #{inspect(line)}
|
||||
|
||||
_ ->
|
||||
n_spaces = if indent == 1, do: "#{indent} space", else: "#{indent} spaces"
|
||||
If you are planning to assert on the result of an `iex>` expression, \
|
||||
make sure the result is indented at the beginning of `iex>`, which \
|
||||
in this case is exactly #{n_spaces}.
|
||||
|
||||
raise Error,
|
||||
line: line_no,
|
||||
module: module,
|
||||
message: """
|
||||
indentation level mismatch on doctest line: #{inspect(line)}
|
||||
|
||||
If you are planning to assert on the result of an `iex>` expression, \
|
||||
make sure the result is indented at the beginning of `iex>`, which \
|
||||
in this case is exactly #{n_spaces}.
|
||||
|
||||
If instead you have an `iex>` expression that spans over multiple lines, \
|
||||
please make sure that each line after the first one begins with `...>`.
|
||||
"""
|
||||
If instead you have an `iex>` expression that spans over multiple lines, \
|
||||
please make sure that each line after the first one begins with `...>`.
|
||||
"""
|
||||
end
|
||||
|
||||
adjusted_lines = [{adjust_prompt(stripped_line, line_no, module), line_no} | adjusted_lines]
|
||||
|
||||
next =
|
||||
cond do
|
||||
kind == :prompt -> :after_prompt
|
||||
String.starts_with?(stripped_line, @iex_prompt ++ @dot_prompt) -> :after_prompt
|
||||
true -> :code
|
||||
end
|
||||
|
||||
adjust_indent(next, rest, line_no + 1, adjusted_lines, indent, module)
|
||||
end
|
||||
|
||||
defp adjust_indent(:code, [line | rest], line_no, adjusted_lines, indent, module) do
|
||||
stripped_line = strip_indent(line, indent)
|
||||
|
||||
cond do
|
||||
stripped_line == "" or String.starts_with?(stripped_line, @fences) ->
|
||||
adjusted_lines = [{"", line_no} | adjusted_lines]
|
||||
adjust_indent(:text, rest, line_no + 1, adjusted_lines, 0, module)
|
||||
|
||||
String.starts_with?(String.trim_leading(stripped_line), @iex_prompt) ->
|
||||
adjust_indent(:prompt, [line | rest], line_no, adjusted_lines, indent, module)
|
||||
kind == :prompt or String.starts_with?(trimmed_line, @iex_prompt) or
|
||||
(kind == :maybe_prompt and String.starts_with?(trimmed_line, @dot_prompt)) ->
|
||||
line = {adjust_prompt(stripped_line, line_no, module), line_no}
|
||||
adjust_indent(:maybe_prompt, rest, line_no + 1, [line | adjusted_lines], indent, module)
|
||||
|
||||
true ->
|
||||
adjusted_lines = [{stripped_line, line_no} | adjusted_lines]
|
||||
|
||||
@@ -293,8 +293,9 @@ defmodule ExUnit.Runner do
|
||||
{test_module, invalid_tests, []}
|
||||
|
||||
{:DOWN, ^module_ref, :process, ^module_pid, error} ->
|
||||
invalid_tests = Enum.map(tests, &%{&1 | state: {:invalid, test_module}})
|
||||
test_module = %{test_module | state: failed({:EXIT, module_pid}, error, [])}
|
||||
{test_module, [], []}
|
||||
{test_module, invalid_tests, []}
|
||||
end
|
||||
|
||||
timeout = get_timeout(config, %{})
|
||||
|
||||
@@ -124,6 +124,22 @@ defmodule ExUnit.CallbacksTest do
|
||||
"** (MatchError) no match of right hand side value: :error"
|
||||
end
|
||||
|
||||
test "doesn't choke on setup_all exits" do
|
||||
defmodule SetupAllExitTest do
|
||||
use ExUnit.Case
|
||||
|
||||
setup_all _ do
|
||||
Process.exit(self(), :error)
|
||||
end
|
||||
|
||||
test "ok" do
|
||||
assert true
|
||||
end
|
||||
end
|
||||
|
||||
assert capture_io(fn -> ExUnit.run() end) =~ "1 test, 0 failures, 1 invalid"
|
||||
end
|
||||
|
||||
test "doesn't choke on dead supervisor" do
|
||||
defmodule StartSupervisedErrorTest do
|
||||
use ExUnit.Case
|
||||
|
||||
@@ -114,6 +114,19 @@ defmodule ExUnit.CaptureLogTest do
|
||||
|
||||
assert log == "id=123 | hello"
|
||||
end
|
||||
|
||||
@tag capture_log: true
|
||||
test "respect options with capture_log: true" do
|
||||
options = [format: "$metadata| $message", metadata: [:id], colors: [enabled: false]]
|
||||
|
||||
assert {4, log} =
|
||||
with_log(options, fn ->
|
||||
Logger.info("hello", id: 123)
|
||||
2 + 2
|
||||
end)
|
||||
|
||||
assert log == "id=123 | hello"
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_capture_removal() do
|
||||
|
||||
@@ -317,8 +317,7 @@ defmodule ExUnit.DocTestTest.FencedHeredocs do
|
||||
```
|
||||
|
||||
```
|
||||
iex> 1 + 2
|
||||
3
|
||||
iex> 3 = 1 + 2
|
||||
```
|
||||
|
||||
```
|
||||
@@ -444,6 +443,16 @@ defmodule ExUnit.DocTestTest.Haiku do
|
||||
end
|
||||
|> ExUnit.BeamHelpers.write_beam()
|
||||
|
||||
defmodule ExUnit.DocTestTest.VariableInExpectation do
|
||||
@doc """
|
||||
iex> num = 1
|
||||
iex> ExUnit.DocTestTest.VariableInExpectation.inc(num)
|
||||
num + 1
|
||||
"""
|
||||
def inc(num), do: num + 1
|
||||
end
|
||||
|> ExUnit.BeamHelpers.write_beam()
|
||||
|
||||
defmodule ExUnit.DocTestTest.PatternMatching do
|
||||
def starting_line, do: __ENV__.line + 2
|
||||
|
||||
@@ -507,6 +516,7 @@ defmodule ExUnit.DocTestTest do
|
||||
doctest ExUnit.DocTestTest.IndentationHeredocs
|
||||
doctest ExUnit.DocTestTest.FencedHeredocs
|
||||
doctest ExUnit.DocTestTest.Haiku
|
||||
doctest ExUnit.DocTestTest.VariableInExpectation
|
||||
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
|
||||
+27
-12
@@ -536,6 +536,9 @@ defmodule IEx do
|
||||
|
||||
@doc """
|
||||
Returns `true` if IEx was started, `false` otherwise.
|
||||
|
||||
This means the IEx application was started, but not
|
||||
that its CLI interface is running.
|
||||
"""
|
||||
@spec started?() :: boolean()
|
||||
def started? do
|
||||
@@ -579,9 +582,6 @@ defmodule IEx do
|
||||
@doc """
|
||||
Pries into the process environment.
|
||||
|
||||
When you start `iex`, IEx will set this function to be the
|
||||
default `dbg/2` backend unless the `--no-pry` flag is given.
|
||||
|
||||
This function is useful for debugging a particular chunk of code
|
||||
when executed by a particular process. The process becomes
|
||||
the evaluator of IEx commands and is temporarily changed to
|
||||
@@ -597,6 +597,11 @@ defmodule IEx do
|
||||
|
||||
See also `break!/4` for others ways to pry.
|
||||
|
||||
> #### `dbg/0` integration
|
||||
>
|
||||
> By calling `iex --dbg pry`, `iex` will set this function
|
||||
> as the default backend for `dbg/0` calls.
|
||||
|
||||
## Examples
|
||||
|
||||
Let's suppose you want to investigate what is happening
|
||||
@@ -857,6 +862,8 @@ defmodule IEx do
|
||||
|
||||
# This is a callback invoked by Erlang shell utilities
|
||||
# when someone presses Ctrl+G and adds `s 'Elixir.IEx'`.
|
||||
# Let's consider exposing this as iex:start() and rename
|
||||
# elixir:start_iex() to iex:cli().
|
||||
@doc false
|
||||
def start(opts \\ [], mfa \\ {IEx, :dont_display_result, []}) do
|
||||
# TODO: Keep only this branch, delete optional args and mfa,
|
||||
@@ -869,10 +876,13 @@ defmodule IEx do
|
||||
end)
|
||||
else
|
||||
spawn(fn ->
|
||||
{:ok, _} = Application.ensure_all_started(:elixir)
|
||||
System.wait_until_booted()
|
||||
:ok = :io.setopts(binary: true, encoding: :unicode)
|
||||
case :init.notify_when_started(self()) do
|
||||
:started -> :ok
|
||||
_ -> :init.wait_until_started()
|
||||
end
|
||||
|
||||
{:ok, _} = Application.ensure_all_started(:iex)
|
||||
:ok = :io.setopts(binary: true, encoding: :unicode)
|
||||
_ = for fun <- Enum.reverse(after_spawn()), do: fun.()
|
||||
IEx.Server.run_from_shell(opts, mfa)
|
||||
end)
|
||||
@@ -920,13 +930,18 @@ defmodule IEx do
|
||||
mfa
|
||||
end
|
||||
|
||||
:ok = :shell.start_interactive(shell)
|
||||
case :shell.start_interactive(shell) do
|
||||
:ok ->
|
||||
receive do
|
||||
{^ref, shell} -> shell
|
||||
after
|
||||
15_000 ->
|
||||
IO.puts(:stderr, "Could not start IEx CLI due to reason: :boot_timeout")
|
||||
System.halt(1)
|
||||
end
|
||||
|
||||
receive do
|
||||
{^ref, shell} -> shell
|
||||
after
|
||||
15_000 ->
|
||||
IO.puts(:stderr, "Could not start the shell after 15 seconds, aborting...")
|
||||
{:error, reason} ->
|
||||
IO.puts(:stderr, "Could not start IEx CLI due to reason: #{inspect(reason)}")
|
||||
System.halt(1)
|
||||
end
|
||||
end
|
||||
|
||||
+5
-10
@@ -6,7 +6,7 @@ defmodule IEx.CLI do
|
||||
|
||||
def deprecated do
|
||||
if tty_works?() do
|
||||
:user_drv.start([:"tty_sl -c -e", old_tty_args()])
|
||||
:user_drv.start([:"tty_sl -c -e", tty_args()])
|
||||
else
|
||||
if get_remsh(:init.get_plain_arguments()) do
|
||||
IO.puts(
|
||||
@@ -20,14 +20,10 @@ defmodule IEx.CLI do
|
||||
# IEx.Broker is capable of considering all groups under user_drv but
|
||||
# when we use :user.start(), we need to explicitly register it instead.
|
||||
# If we don't register, pry doesn't work.
|
||||
IEx.start([register: true] ++ options())
|
||||
IEx.start([register: true] ++ options(), {:elixir, :start_cli, []})
|
||||
end
|
||||
end
|
||||
|
||||
def prompt(_n) do
|
||||
[]
|
||||
end
|
||||
|
||||
# Check if tty works. If it does not, we fall back to the
|
||||
# simple/dumb terminal. This is starting the linked in
|
||||
# driver twice, it would be nice and appropriate if we had
|
||||
@@ -41,7 +37,7 @@ defmodule IEx.CLI do
|
||||
end
|
||||
end
|
||||
|
||||
defp old_tty_args do
|
||||
defp tty_args do
|
||||
if remote = get_remsh(:init.get_plain_arguments()) do
|
||||
remote = List.to_atom(append_hostname(remote))
|
||||
|
||||
@@ -77,7 +73,7 @@ defmodule IEx.CLI do
|
||||
end
|
||||
|
||||
defp local_start_mfa do
|
||||
{IEx, :start, [options()]}
|
||||
{IEx, :start, [options(), {:elixir, :start_cli, []}]}
|
||||
end
|
||||
|
||||
def remote_start(parent, ref) do
|
||||
@@ -93,8 +89,7 @@ defmodule IEx.CLI do
|
||||
spawn_link(fn ->
|
||||
receive do
|
||||
{:begin, ^ref, other} ->
|
||||
{:ok, _} = Application.ensure_all_started(:elixir)
|
||||
System.wait_until_booted()
|
||||
:elixir.start_cli()
|
||||
send(other, {:done, ref})
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -99,7 +99,7 @@ defmodule IEx.Helpers do
|
||||
reenable_tasks(config)
|
||||
|
||||
force? = Keyword.get(options, :force, false)
|
||||
args = ["--purge-consolidation-path-if-stale", consolidation]
|
||||
args = ["--purge-consolidation-path-if-stale", "--return-errors", consolidation]
|
||||
args = if force?, do: ["--force" | args], else: args
|
||||
|
||||
{result, _} = Mix.Task.run("compile", args)
|
||||
|
||||
@@ -293,6 +293,7 @@ defmodule IEx.Introspection do
|
||||
module.module_info(:exports)
|
||||
end
|
||||
|> Enum.sort()
|
||||
|> Enum.dedup()
|
||||
|
||||
result =
|
||||
for {^function, arity} <- exports,
|
||||
|
||||
@@ -689,6 +689,8 @@ defmodule IEx.Pry do
|
||||
[asts_string, :faint, " #=> ", :reset, inspect(value, options), "\n\n"]
|
||||
|> IO.ANSI.format()
|
||||
|> IO.write()
|
||||
|
||||
value
|
||||
end
|
||||
|
||||
defp chunk_pipeline_asts_by_line(asts, %Macro.Env{line: env_line}) do
|
||||
|
||||
+27
-20
@@ -210,7 +210,7 @@ defmodule Logger do
|
||||
metadata: [:error_code, :file]
|
||||
|
||||
Or to configure default handler, for instance, to log into a file with
|
||||
built-in support for log rotation:
|
||||
built-in support for log rotation and compression:
|
||||
|
||||
config :logger, :default_handler,
|
||||
config: [
|
||||
@@ -218,9 +218,18 @@ defmodule Logger do
|
||||
filesync_repeat_interval: 5000,
|
||||
file_check: 5000,
|
||||
max_no_bytes: 10_000_000,
|
||||
max_no_files: 5
|
||||
max_no_files: 5,
|
||||
compress_on_rotate: true
|
||||
]
|
||||
|
||||
See [`:logger_std_h`](`:logger_std_h`) for all relevant configuration,
|
||||
including overload protection. Or set `:default_handler` to false to
|
||||
disable the default logging altogether:
|
||||
|
||||
config :logger, :default_handler, false
|
||||
|
||||
How to add new handlers is covered in later sections.
|
||||
|
||||
> #### Keywords or maps {: .tip}
|
||||
>
|
||||
> While Erlang's logger expects `:config` to be a map, Elixir's Logger
|
||||
@@ -231,14 +240,6 @@ defmodule Logger do
|
||||
> When reading the handler configuration using Erlang's APIs,
|
||||
> the configuration will always be read (and written) as a map.
|
||||
|
||||
See [`:logger_std_h`](`:logger_std_h`) for all relevant configuration,
|
||||
including overload protection. Or set `:default_handler` to false to
|
||||
disable the default logging altogether:
|
||||
|
||||
config :logger, :default_handler, false
|
||||
|
||||
How to add new handlers is covered in later sections.
|
||||
|
||||
### Compile configuration
|
||||
|
||||
The following configuration must be set via config files (such as
|
||||
@@ -329,16 +330,19 @@ defmodule Logger do
|
||||
default handler, but you can use Erlang's [`:logger`](`:logger`) module
|
||||
to add other handlers too.
|
||||
|
||||
Erlang/OTP handlers must be listed under your own application. For example,
|
||||
to setup an additional handler that writes to disk:
|
||||
Erlang/OTP handlers must be listed under your own application.
|
||||
For example, to setup an additional handler, so you write to
|
||||
console and file:
|
||||
|
||||
config :my_app, :logger, [
|
||||
{:handler, :disk_log, :logger_disk_log_h, %{
|
||||
{:handler, :file_log, :logger_std_h, %{
|
||||
config: %{
|
||||
file: 'system.log',
|
||||
file: ~c"system.log",
|
||||
filesync_repeat_interval: 5000,
|
||||
file_check: 5000,
|
||||
max_no_bytes: 10_000_000,
|
||||
max_no_files: 5
|
||||
max_no_files: 5,
|
||||
compress_on_rotate: true
|
||||
},
|
||||
formatter: Logger.Formatter.new()
|
||||
}}
|
||||
@@ -355,9 +359,12 @@ defmodule Logger do
|
||||
flexibility but they should avoid performing any long running action in
|
||||
such handlers, as it may slow down the action being executed considerably.
|
||||
At the moment, there is no built-in overload protection for Erlang handlers,
|
||||
so it is your responsibility to implement it. Alternatively, you can use the
|
||||
[`:logger_backends`](https://github.com/elixir-lang/logger_backends)
|
||||
project.
|
||||
so it is your responsibility to implement it.
|
||||
|
||||
Alternatively, you can use the
|
||||
[`:logger_backends`](https://github.com/elixir-lang/logger_backends) project.
|
||||
It sets up a log handler with overload protection and allows incoming events
|
||||
to be dispatched to multiple backends.
|
||||
|
||||
## Backends and backwards compatibility
|
||||
|
||||
@@ -1002,7 +1009,7 @@ defmodule Logger do
|
||||
compile_time_purge_matching?(compile_level, compile_metadata) ->
|
||||
no_log(data, quoted_metadata)
|
||||
|
||||
Application.fetch_env!(:logger, :always_evaluate_messages) ->
|
||||
Application.get_env(:logger, :always_evaluate_messages, false) ->
|
||||
quote do
|
||||
data = Logger.__evaluate_log__(unquote(data))
|
||||
metadata = unquote(quoted_metadata)
|
||||
@@ -1110,7 +1117,7 @@ defmodule Logger do
|
||||
end
|
||||
|
||||
defp no_log(data, metadata) do
|
||||
if Application.fetch_env!(:logger, :always_evaluate_messages) do
|
||||
if Application.get_env(:logger, :always_evaluate_messages, false) do
|
||||
quote do
|
||||
Logger.__evaluate_log__(unquote(data))
|
||||
unquote(metadata)
|
||||
|
||||
@@ -203,7 +203,7 @@ defmodule Logger.Formatter do
|
||||
|
||||
def format(_event, _config) do
|
||||
raise "invalid configuration for Logger.Formatter. " <>
|
||||
"Use Logger.Formatter.init/1 to define a formatter"
|
||||
"Use Logger.Formatter.new/1 to define a formatter"
|
||||
end
|
||||
|
||||
defp compute_meta(:module, %{mfa: {mod, _, _}}), do: mod
|
||||
@@ -498,8 +498,6 @@ defmodule Logger.Formatter do
|
||||
rest
|
||||
end
|
||||
|
||||
defp metadata(:file, file) when is_list(file), do: file
|
||||
|
||||
defp metadata(:domain, [head | tail]) when is_atom(head) do
|
||||
Enum.map_intersperse([head | tail], ?., &Atom.to_string/1)
|
||||
end
|
||||
@@ -514,6 +512,8 @@ defmodule Logger.Formatter do
|
||||
Exception.format_mfa(mod, fun, arity)
|
||||
end
|
||||
|
||||
defp metadata(:function, function) when is_list(function), do: function
|
||||
defp metadata(:file, file) when is_list(file), do: file
|
||||
defp metadata(_, list) when is_list(list), do: nil
|
||||
|
||||
defp metadata(_, other) do
|
||||
|
||||
@@ -54,6 +54,28 @@ defmodule Logger.FormatterTest do
|
||||
assert format_time(time) == ["12", ?:, "30", ?:, "10", ?., [?0, "10"]]
|
||||
end
|
||||
|
||||
describe "log" do
|
||||
test "handles :module and :function" do
|
||||
{_, formatter} =
|
||||
new(
|
||||
format: "\n$time $metadata[$level] $message\n",
|
||||
metadata: [:module, :function, :mfa],
|
||||
colors: [enabled: false]
|
||||
)
|
||||
|
||||
assert %{
|
||||
level: :warn,
|
||||
msg: {:string, "foo"},
|
||||
meta: %{
|
||||
mfa: {Logger.Formatter, :compile, 1}
|
||||
}
|
||||
}
|
||||
|> format(formatter)
|
||||
|> IO.chardata_to_string() =~
|
||||
"module=Logger.Formatter function=compile/1 mfa=Logger.Formatter.compile/1"
|
||||
end
|
||||
end
|
||||
|
||||
describe "compile + format" do
|
||||
defmodule CompileMod do
|
||||
def format(_level, _msg, _ts, _md) do
|
||||
@@ -66,7 +88,7 @@ defmodule Logger.FormatterTest do
|
||||
["\n", :time, " ", :metadata, "[", :level, "] ", :message, "\n"]
|
||||
end
|
||||
|
||||
test "compile with str" do
|
||||
test "compile with string" do
|
||||
assert compile("$level $time $date $metadata $message $node") ==
|
||||
Enum.intersperse([:level, :time, :date, :metadata, :message, :node], " ")
|
||||
|
||||
|
||||
+20
-11
@@ -600,26 +600,29 @@ defmodule Mix do
|
||||
"""
|
||||
@doc since: "1.15.0"
|
||||
def ensure_application!(app) when is_atom(app) do
|
||||
ensure_application!(app, Mix.State.builtin_apps())
|
||||
ensure_application!(app, Mix.State.builtin_apps(), [])
|
||||
:ok
|
||||
end
|
||||
|
||||
defp ensure_application!(app, builtin_apps) do
|
||||
defp ensure_application!(app, builtin_apps, optional) do
|
||||
case builtin_apps do
|
||||
%{^app => path} ->
|
||||
Code.prepend_path(path, cache: true)
|
||||
Application.load(app)
|
||||
optional = List.wrap(Application.spec(app, :optional_applications))
|
||||
|
||||
Application.spec(app, :applications)
|
||||
|> List.wrap()
|
||||
|> Enum.each(&ensure_application!(&1, builtin_apps))
|
||||
|> Enum.each(&ensure_application!(&1, builtin_apps, optional))
|
||||
|
||||
%{} ->
|
||||
Mix.raise(
|
||||
"The application \"#{app}\" could not be found. This may happen if your " <>
|
||||
"Operating System broke Erlang into multiple packages and may be fixed " <>
|
||||
"by installing the missing \"erlang-dev\" and \"erlang-#{app}\" packages"
|
||||
)
|
||||
unless app in optional do
|
||||
Mix.raise(
|
||||
"The application \"#{app}\" could not be found. This may happen if your " <>
|
||||
"Operating System broke Erlang into multiple packages and may be fixed " <>
|
||||
"by installing the missing \"erlang-dev\" and \"erlang-#{app}\" packages"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -674,6 +677,9 @@ defmodule Mix do
|
||||
* `:lockfile` (since v1.14.0) - path to a lockfile to be used as a basis of
|
||||
dependency resolution.
|
||||
|
||||
* `:start_applications` (since v1.15.3) - if `true`, ensures that installed app
|
||||
and its dependencies are started after install (Default: `true`)
|
||||
|
||||
## Examples
|
||||
|
||||
Installing `:decimal` and `:jason`:
|
||||
@@ -794,6 +800,7 @@ defmodule Mix do
|
||||
config_path = expand_path(opts[:config_path], deps, :config_path, "config/config.exs")
|
||||
system_env = Keyword.get(opts, :system_env, [])
|
||||
consolidate_protocols? = Keyword.get(opts, :consolidate_protocols, true)
|
||||
start_applications? = Keyword.get(opts, :start_applications, true)
|
||||
|
||||
id =
|
||||
{deps, config, system_env, consolidate_protocols?}
|
||||
@@ -893,9 +900,11 @@ defmodule Mix do
|
||||
end
|
||||
end)
|
||||
|
||||
for %{app: app, opts: opts} <- Mix.Dep.cached(),
|
||||
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true) do
|
||||
Application.ensure_all_started(app)
|
||||
if start_applications? do
|
||||
for %{app: app, opts: opts} <- Mix.Dep.cached(),
|
||||
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true) do
|
||||
Application.ensure_all_started(app)
|
||||
end
|
||||
end
|
||||
|
||||
Mix.State.put(:installed, id)
|
||||
|
||||
@@ -136,8 +136,12 @@ defmodule Mix.AppLoader do
|
||||
end
|
||||
|
||||
# We have processed all apps.
|
||||
defp traverse_apps([], _seen, _deps_children, _builtin_paths, _lib_path) do
|
||||
[]
|
||||
defp traverse_apps([], seen, _deps_children, builtin_paths, _lib_path) do
|
||||
# We want to keep erts in the load path but it doesn't require to be loaded.
|
||||
case builtin_paths do
|
||||
%{erts: path} when not is_map_key(seen, :erts) -> [{:erts, path}]
|
||||
%{} -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp app_children(app) do
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule Mix.Compilers.Elixir do
|
||||
@moduledoc false
|
||||
|
||||
@manifest_vsn 19
|
||||
@manifest_vsn 20
|
||||
@checkpoint_vsn 2
|
||||
|
||||
import Record
|
||||
@@ -46,6 +46,11 @@ defmodule Mix.Compilers.Elixir do
|
||||
{all_modules, all_sources, all_local_exports, old_parents, old_cache_key, old_deps_config} =
|
||||
parse_manifest(manifest, dest)
|
||||
|
||||
# Prepend ourselves early because of __mix_recompile__? checks
|
||||
# and also that, in case of nothing compiled, we already need
|
||||
# ourselves available in the path.
|
||||
Code.prepend_path(dest)
|
||||
|
||||
# If modules have been added or removed from the Erlang compiler,
|
||||
# we need to recompile all references to old and new modules.
|
||||
stale =
|
||||
@@ -56,7 +61,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
[]
|
||||
end
|
||||
|
||||
local_deps = Enum.reject(Mix.Dep.cached(), & &1.scm.fetchable?)
|
||||
local_deps = Enum.reject(Mix.Dep.cached(), & &1.scm.fetchable?())
|
||||
|
||||
# If mix.exs has changed, recompile anything that calls Mix.Project.
|
||||
stale =
|
||||
@@ -393,7 +398,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
Enum.any?(modules, &Map.has_key?(modules_to_recompile, &1)) or
|
||||
Enum.any?(external, &stale_external?(&1, modified, sources_stats)) or
|
||||
(last_mtime > modified and
|
||||
(missing_beam_file?(dest, modules) or digest != digest_file!(source))),
|
||||
(missing_beam_file?(dest, modules) or digest_changed?(source, digest))),
|
||||
do: source
|
||||
|
||||
changed = new_paths ++ changed
|
||||
@@ -423,7 +428,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
defp stale_external?({external, digest}, modified, sources_stats) do
|
||||
case sources_stats do
|
||||
%{^external => {0, 0}} -> digest != nil
|
||||
%{^external => {mtime, _}} -> mtime > modified and digest != digest_file!(external)
|
||||
%{^external => {mtime, _}} -> mtime > modified and digest_changed?(external, digest)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -437,8 +442,11 @@ defmodule Mix.Compilers.Elixir do
|
||||
end)
|
||||
end
|
||||
|
||||
defp digest_file!(file) do
|
||||
file |> File.read!() |> digest_contents()
|
||||
defp digest_changed?(file, digest) do
|
||||
case File.read(file) do
|
||||
{:ok, binary} -> digest != digest_contents(binary)
|
||||
{:error, _} -> true
|
||||
end
|
||||
end
|
||||
|
||||
defp digest_contents(contents) do
|
||||
@@ -558,18 +566,18 @@ defmodule Mix.Compilers.Elixir do
|
||||
Enum.any?(enumerable, &Map.has_key?(map, &1))
|
||||
end
|
||||
|
||||
defp stale_local_deps(local_deps, manifest, stale_modules, modified, old_exports) do
|
||||
defp stale_local_deps(local_deps, manifest, stale_modules, modified, deps_exports) do
|
||||
base = Path.basename(manifest)
|
||||
|
||||
# The stale modules so far will become both stale_modules and stale_exports,
|
||||
# as any export from a dependency needs to be recompiled.
|
||||
stale_modules = Map.from_keys(stale_modules, true)
|
||||
|
||||
for %{opts: opts} <- local_deps,
|
||||
for %{app: app, opts: opts} <- local_deps,
|
||||
manifest = Path.join([opts[:build], ".mix", base]),
|
||||
Mix.Utils.last_modified(manifest) > modified,
|
||||
reduce: {stale_modules, stale_modules, old_exports} do
|
||||
{modules, exports, new_exports} ->
|
||||
reduce: {stale_modules, stale_modules, deps_exports} do
|
||||
{modules, exports, deps_exports} ->
|
||||
{manifest_modules, manifest_sources} = read_manifest(manifest)
|
||||
|
||||
dep_modules =
|
||||
@@ -584,9 +592,11 @@ defmodule Mix.Compilers.Elixir do
|
||||
{dep_modules, _, _} =
|
||||
fixpoint_runtime_modules(manifest_sources, Map.from_keys(dep_modules, true))
|
||||
|
||||
old_exports = Map.get(deps_exports, app, %{})
|
||||
|
||||
# Update exports
|
||||
{exports, new_exports} =
|
||||
for {module, _} <- dep_modules, reduce: {exports, new_exports} do
|
||||
for {module, _} <- dep_modules, reduce: {exports, []} do
|
||||
{exports, new_exports} ->
|
||||
export = exports_md5(module, false)
|
||||
|
||||
@@ -600,17 +610,27 @@ defmodule Mix.Compilers.Elixir do
|
||||
do: exports,
|
||||
else: Map.put(exports, module, true)
|
||||
|
||||
# In any case, we always store it as the most update export
|
||||
# that we have, otherwise we delete it.
|
||||
# Then we store the new export if any
|
||||
new_exports =
|
||||
if export,
|
||||
do: Map.put(new_exports, module, export),
|
||||
else: Map.delete(new_exports, module)
|
||||
do: [{module, export} | new_exports],
|
||||
else: new_exports
|
||||
|
||||
{exports, new_exports}
|
||||
end
|
||||
|
||||
{Map.merge(modules, dep_modules), exports, new_exports}
|
||||
new_exports = Map.new(new_exports)
|
||||
|
||||
removed =
|
||||
for {module, _} <- old_exports,
|
||||
not is_map_key(new_exports, module),
|
||||
do: {module, true},
|
||||
into: %{}
|
||||
|
||||
modules = modules |> Map.merge(dep_modules) |> Map.merge(removed)
|
||||
exports = Map.merge(exports, removed)
|
||||
deps_exports = Map.put(deps_exports, app, new_exports)
|
||||
{modules, exports, deps_exports}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1081,7 +1101,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
source(
|
||||
source,
|
||||
# We preserve the digest if the file is recompiled but not changed
|
||||
digest: source(source, :digest) || digest_file!(file),
|
||||
digest: source(source, :digest) || file |> File.read!() |> digest_contents(),
|
||||
compile_references: compile_references,
|
||||
export_references: export_references,
|
||||
runtime_references: runtime_references,
|
||||
|
||||
@@ -4,13 +4,13 @@ defmodule Mix.Compilers.Erlang do
|
||||
@manifest_vsn 1
|
||||
|
||||
@doc """
|
||||
Compiles the files in `mappings` with given extensions into
|
||||
the destination, automatically invoking the callback for each
|
||||
stale input and output pair (or for all if `force` is `true`) and
|
||||
removing files that no longer have a source, while keeping the
|
||||
`manifest` up to date.
|
||||
Compiles the given `mappings`.
|
||||
|
||||
`mappings` should be a list of tuples in the form of `{src, dest}` paths.
|
||||
`mappings` is a list of `{src, dest}` pairs, where the source
|
||||
extensions are compiled into the destination extension,
|
||||
automatically invoking the callback for each stale pair (or for
|
||||
all if `force` is `true`) and removing files that no longer have
|
||||
a source, while keeping the `manifest` up to date.
|
||||
|
||||
## Options
|
||||
|
||||
@@ -18,6 +18,9 @@ defmodule Mix.Compilers.Erlang do
|
||||
|
||||
* `:parallel` - a mapset of files to compile in parallel
|
||||
|
||||
* `:preload` - any code that must be preloaded if any pending
|
||||
entry needs to be compiled
|
||||
|
||||
## Examples
|
||||
|
||||
For example, a simple compiler for Lisp Flavored Erlang
|
||||
@@ -54,22 +57,25 @@ defmodule Mix.Compilers.Erlang do
|
||||
def compile(manifest, mappings, src_ext, dest_ext, opts, callback) when is_list(opts) do
|
||||
force = opts[:force]
|
||||
|
||||
files =
|
||||
entries =
|
||||
for {src, dest} <- mappings,
|
||||
target <- extract_targets(src, src_ext, dest, dest_ext, force),
|
||||
target <- extract_entries(src, src_ext, dest, dest_ext, force),
|
||||
do: target
|
||||
|
||||
compile(manifest, files, src_ext, opts, callback)
|
||||
if preload = entries != [] && opts[:preload] do
|
||||
preload.()
|
||||
end
|
||||
|
||||
compile(manifest, entries, src_ext, opts, callback)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given `mappings`.
|
||||
Compiles the given `entries`.
|
||||
|
||||
`mappings` should be a list of tuples in the form of `{src, dest}`.
|
||||
`entries` are a list of `{:ok | :stale, src, dest}` tuples.
|
||||
|
||||
A `manifest` file and a `callback` to be invoked for each src/dest pair
|
||||
must be given. A src/dest pair where destination is `nil` is considered
|
||||
to be up to date and won't be (re-)compiled.
|
||||
A `manifest` file and a `callback` to be invoked for each stale
|
||||
src/dest pair must also be given.
|
||||
|
||||
## Options
|
||||
|
||||
@@ -78,8 +84,8 @@ defmodule Mix.Compilers.Erlang do
|
||||
* `:parallel` - a mapset of files to compile in parallel
|
||||
|
||||
"""
|
||||
def compile(manifest, mappings, opts \\ [], callback) do
|
||||
compile(manifest, mappings, :erl, opts, callback)
|
||||
def compile(manifest, entries, opts \\ [], callback) do
|
||||
compile(manifest, entries, :erl, opts, callback)
|
||||
end
|
||||
|
||||
defp compile(manifest, mappings, ext, opts, callback) do
|
||||
@@ -102,7 +108,7 @@ defmodule Mix.Compilers.Erlang do
|
||||
# Clear stale and removed files from manifest
|
||||
entries =
|
||||
Enum.reject(entries, fn {dest, _warnings} ->
|
||||
dest in removed || Enum.any?(stale, fn {_, stale_dest} -> dest == stale_dest end)
|
||||
dest in removed || List.keymember?(stale, dest, 1)
|
||||
end)
|
||||
|
||||
if Keyword.get(opts, :all_warnings, true), do: show_warnings(entries)
|
||||
@@ -156,9 +162,8 @@ defmodule Mix.Compilers.Erlang do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the native OTP application is available.
|
||||
"""
|
||||
# TODO: Deprecate this in favor of `Mix.ensure_application!/1` in Elixir v1.19.
|
||||
@doc false
|
||||
def ensure_application!(app, _input) do
|
||||
Mix.ensure_application!(app)
|
||||
{:ok, _} = Application.ensure_all_started(app)
|
||||
@@ -202,7 +207,7 @@ defmodule Mix.Compilers.Erlang do
|
||||
manifest |> read_manifest() |> Enum.map(&elem(&1, 0))
|
||||
end
|
||||
|
||||
defp extract_targets(src_dir, src_ext, dest_dir, dest_ext, force) do
|
||||
defp extract_entries(src_dir, src_ext, dest_dir, dest_ext, force) do
|
||||
files = Mix.Utils.extract_files(List.wrap(src_dir), List.wrap(src_ext))
|
||||
|
||||
for file <- files do
|
||||
|
||||
@@ -72,8 +72,31 @@ defmodule Mix.Dep.Converger do
|
||||
"""
|
||||
def converge(acc, lock, opts, callback) do
|
||||
{deps, acc, lock} = all(acc, lock, opts, callback)
|
||||
if remote = Mix.RemoteConverger.get(), do: remote.post_converge
|
||||
{topological_sort(deps), acc, lock}
|
||||
if remote = Mix.RemoteConverger.get(), do: remote.post_converge()
|
||||
|
||||
deps =
|
||||
for %{app: app, opts: opts} = dep <- topological_sort(deps) do
|
||||
case Keyword.pop(opts, :app_properties) do
|
||||
{nil, _opts} ->
|
||||
dep
|
||||
|
||||
{app_properties, opts} ->
|
||||
case :application.load({:application, app, app_properties}) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
{:error, {:already_loaded, _}} ->
|
||||
:ok
|
||||
|
||||
{:error, error} ->
|
||||
Mix.raise("Could not load application #{inspect(app)}: #{inspect(error)}")
|
||||
end
|
||||
|
||||
%{dep | opts: opts}
|
||||
end
|
||||
end
|
||||
|
||||
{deps, acc, lock}
|
||||
end
|
||||
|
||||
defp all(acc, lock, opts, callback) do
|
||||
@@ -205,21 +228,20 @@ defmodule Mix.Dep.Converger do
|
||||
all(t, [dep | acc], upper, breadths, optional, rest, lock, state)
|
||||
|
||||
:nomatch ->
|
||||
{%{app: app, deps: deps, opts: opts} = dep, app_properties, rest, lock} =
|
||||
{%{app: app, deps: deps, opts: opts} = dep, rest, lock} =
|
||||
case state.cache.(dep) do
|
||||
{:loaded, cached_dep} ->
|
||||
{cached_dep, nil, rest, lock}
|
||||
{cached_dep, rest, lock}
|
||||
|
||||
{:unloaded, dep, children} ->
|
||||
{dep, rest, lock} = state.callback.(put_lock(dep, lock), rest, lock)
|
||||
|
||||
Mix.Dep.Loader.with_system_env(dep, fn ->
|
||||
# After we invoke the callback (which may actually check out the
|
||||
# dependency), we load the dependency including its latest info
|
||||
# and children information.
|
||||
{dep, app_properties} = Mix.Dep.Loader.load(dep, children, state.locked?)
|
||||
{dep, app_properties, rest, lock}
|
||||
end)
|
||||
dep =
|
||||
Mix.Dep.Loader.with_system_env(dep, fn ->
|
||||
Mix.Dep.Loader.load(dep, children, state.locked?)
|
||||
end)
|
||||
|
||||
{dep, rest, lock}
|
||||
end
|
||||
|
||||
# Something that we previously ruled out as an optional dependency is
|
||||
@@ -234,24 +256,7 @@ defmodule Mix.Dep.Converger do
|
||||
split_non_fulfilled_optional(deps, Enum.map(acc, & &1.app), opts[:from_umbrella])
|
||||
|
||||
new_breadths = Enum.map(deps, & &1.app) ++ breadths
|
||||
res = all(deps, acc, breadths, new_breadths, discarded ++ optional, rest, lock, state)
|
||||
|
||||
# After we traverse all of our children, we can load ourselves.
|
||||
# This is important in case of included application.
|
||||
if app_properties do
|
||||
case :application.load({:application, app, app_properties}) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
{:error, {:already_loaded, _}} ->
|
||||
:ok
|
||||
|
||||
{:error, error} ->
|
||||
Mix.raise("Could not start application #{inspect(app)}: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
res
|
||||
all(deps, acc, breadths, new_breadths, discarded ++ optional, rest, lock, state)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -383,21 +383,21 @@ defmodule Mix.Dep.Loader do
|
||||
|
||||
cond do
|
||||
not ok?(dep) ->
|
||||
{dep, nil}
|
||||
dep
|
||||
|
||||
recently_fetched?(dep) ->
|
||||
{%{dep | status: :compile}, nil}
|
||||
%{dep | status: :compile}
|
||||
|
||||
opts_app == false ->
|
||||
{dep, nil}
|
||||
dep
|
||||
|
||||
true ->
|
||||
path = if is_binary(opts_app), do: opts_app, else: "ebin/#{app}.app"
|
||||
path = Path.expand(path, opts[:build])
|
||||
|
||||
case app_status(path, app, req) do
|
||||
{:ok, vsn, app} -> {%{dep | status: {:ok, vsn}}, app}
|
||||
status -> {%{dep | status: status}, nil}
|
||||
{:ok, vsn, app} -> %{dep | status: {:ok, vsn}, opts: [app_properties: app] ++ opts}
|
||||
status -> %{dep | status: status}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -59,7 +59,7 @@ defmodule Mix.Dep.Umbrella do
|
||||
apps = Enum.map(deps, & &1.app)
|
||||
|
||||
Enum.map(deps, fn umbrella_dep ->
|
||||
{umbrella_dep, _} = Mix.Dep.Loader.load(umbrella_dep, nil, false)
|
||||
umbrella_dep = Mix.Dep.Loader.load(umbrella_dep, nil, false)
|
||||
|
||||
deps =
|
||||
Enum.filter(umbrella_dep.deps, fn dep ->
|
||||
|
||||
@@ -67,7 +67,7 @@ defmodule Mix.Release do
|
||||
@default_apps [kernel: :permanent, stdlib: :permanent, elixir: :permanent, sasl: :permanent]
|
||||
@safe_modes [:permanent, :temporary, :transient]
|
||||
@unsafe_modes [:load, :none]
|
||||
@significant_chunks ~w(Atom AtU8 Attr Code StrT ImpT ExpT FunT LitT Line)c
|
||||
@additional_chunks ~w(Attr)c
|
||||
@copy_app_dirs ["priv"]
|
||||
|
||||
@doc false
|
||||
@@ -285,7 +285,8 @@ defmodule Mix.Release do
|
||||
|> Enum.map(&{&1, new_mode})
|
||||
|
||||
seen = put_in(seen[app][:mode], new_mode)
|
||||
load_apps(apps, deps_apps, seen, otp_root, [], overrides)
|
||||
optional = Keyword.get(properties, :optional_applications, [])
|
||||
load_apps(apps, deps_apps, seen, otp_root, optional, overrides)
|
||||
|
||||
true ->
|
||||
seen
|
||||
@@ -878,7 +879,7 @@ defmodule Mix.Release do
|
||||
@spec strip_beam(binary(), keyword()) :: {:ok, binary()} | {:error, :beam_lib, term()}
|
||||
def strip_beam(binary, options \\ []) when is_list(options) do
|
||||
chunks_to_keep = options[:keep] |> List.wrap() |> Enum.map(&String.to_charlist/1)
|
||||
all_chunks = Enum.uniq(@significant_chunks ++ chunks_to_keep)
|
||||
all_chunks = Enum.uniq(@additional_chunks ++ :beam_lib.significant_chunks() ++ chunks_to_keep)
|
||||
compress? = Keyword.get(options, :compress, false)
|
||||
|
||||
case :beam_lib.chunks(binary, all_chunks, [:allow_missing_chunks]) do
|
||||
|
||||
@@ -61,7 +61,7 @@ defmodule Mix.Tasks.Archive.Build do
|
||||
project = Mix.Project.get()
|
||||
|
||||
if project && Keyword.get(opts, :compile, true) do
|
||||
Mix.Task.run(:compile, args)
|
||||
Mix.Task.run(:compile, ["--no-protocol-consolidation" | args])
|
||||
end
|
||||
|
||||
source =
|
||||
|
||||
@@ -31,7 +31,7 @@ defmodule Mix.Tasks.Compile.All do
|
||||
{loaded_paths, loaded_modules} =
|
||||
Mix.AppLoader.load_apps(apps, deps, config, {[], []}, fn {app, path}, {paths, mods} ->
|
||||
paths = if path, do: [path | paths], else: paths
|
||||
mods = if app_cache, do: [{app, Application.spec(app, :modules)} | mods], else: mods
|
||||
mods = if app_cache, do: [{app, Application.spec(app, :modules) || []} | mods], else: mods
|
||||
{paths, mods}
|
||||
end)
|
||||
|
||||
|
||||
@@ -146,9 +146,9 @@ defmodule Mix.Tasks.Compile do
|
||||
config = Mix.Project.config()
|
||||
|
||||
# If we are in an umbrella project, now load paths from all children.
|
||||
if Mix.Project.umbrella?(config) do
|
||||
if apps_paths = Mix.Project.apps_paths(config) do
|
||||
loaded_paths =
|
||||
Mix.Project.apps_paths(config)
|
||||
apps_paths
|
||||
|> Map.keys()
|
||||
|> Mix.AppLoader.load_apps(Mix.Dep.cached(), config, [], fn
|
||||
{_app, path}, acc -> if path, do: [path | acc], else: acc
|
||||
|
||||
@@ -53,15 +53,19 @@ defmodule Mix.Tasks.Compile.Leex do
|
||||
Mix.raise(":leex_options should be a list of options, got: #{inspect(options)}")
|
||||
end
|
||||
|
||||
opts = [parallel: true] ++ opts
|
||||
opts = [parallel: true, preload: &preload/0] ++ opts
|
||||
|
||||
Erlang.compile(manifest(), mappings, :xrl, :erl, opts, fn input, output ->
|
||||
Erlang.ensure_application!(:parsetools, input)
|
||||
options = options ++ @forced_opts ++ [scannerfile: Erlang.to_erl_file(output)]
|
||||
:leex.file(Erlang.to_erl_file(input), options)
|
||||
end)
|
||||
end
|
||||
|
||||
defp preload do
|
||||
Mix.ensure_application!(:parsetools)
|
||||
{:ok, _} = Application.ensure_all_started(:parsetools)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def manifests, do: [manifest()]
|
||||
defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)
|
||||
|
||||
@@ -53,15 +53,19 @@ defmodule Mix.Tasks.Compile.Yecc do
|
||||
Mix.raise(":yecc_options should be a list of options, got: #{inspect(options)}")
|
||||
end
|
||||
|
||||
opts = [parallel: true] ++ opts
|
||||
opts = [parallel: true, preload: &preload/0] ++ opts
|
||||
|
||||
Erlang.compile(manifest(), mappings, :yrl, :erl, opts, fn input, output ->
|
||||
Erlang.ensure_application!(:parsetools, input)
|
||||
options = options ++ @forced_opts ++ [parserfile: Erlang.to_erl_file(output)]
|
||||
:yecc.file(Erlang.to_erl_file(input), options)
|
||||
end)
|
||||
end
|
||||
|
||||
defp preload do
|
||||
Mix.ensure_application!(:parsetools)
|
||||
{:ok, _} = Application.ensure_all_started(:parsetools)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def manifests, do: [manifest()]
|
||||
defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)
|
||||
|
||||
@@ -17,7 +17,6 @@ defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
* `--no-archives-check` - does not check archives
|
||||
* `--no-compile` - does not compile even if files require compilation
|
||||
* `--no-deps-check` - does not check or compile deps, only load available ones
|
||||
* `--no-deps-loading` - does not add deps loadpaths to the code path
|
||||
* `--no-elixir-version-check` - does not check Elixir version
|
||||
* `--no-optional-deps` - does not compile or load optional deps
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ defmodule Mix.Tasks.Loadpaths do
|
||||
* `--no-archives-check` - does not check archives
|
||||
* `--no-compile` - does not compile dependencies, only check and load them
|
||||
* `--no-deps-check` - does not check dependencies, only load available ones
|
||||
* `--no-deps-loading` - does not add deps loadpaths to the code path
|
||||
* `--no-elixir-version-check` - does not check Elixir version
|
||||
* `--no-optional-deps` - does not compile or load optional deps
|
||||
|
||||
|
||||
@@ -1425,7 +1425,16 @@ defmodule Mix.Tasks.Release do
|
||||
|
||||
defp cli_for(:windows, release) do
|
||||
{"env.bat", &env_bat_template(release: &1),
|
||||
[{"#{release.name}.bat", cli_bat_template(release: release)}]}
|
||||
[{"#{release.name}.bat", cli_bat_template(release: release) |> maybe_replace_werl()}]}
|
||||
end
|
||||
|
||||
defp maybe_replace_werl(contents) do
|
||||
# TODO: Remove me when we require Erlang/OTP 26+
|
||||
if :erlang.system_info(:otp_release) >= ~c"26" do
|
||||
String.replace(contents, "--werl", "")
|
||||
else
|
||||
contents
|
||||
end
|
||||
end
|
||||
|
||||
defp elixir_cli_for(:unix, release) do
|
||||
|
||||
@@ -38,8 +38,9 @@ defmodule Mix.Tasks.Xref do
|
||||
$ mix xref trace lib/my_app/router.ex --label compile
|
||||
|
||||
If you have an umbrella application, we also recommend using the
|
||||
`--include-siblings` flag to see the dependencies on other
|
||||
umbrella applications.
|
||||
`--include-siblings` flag to see the dependencies from sibling
|
||||
applications. The `trace` command is not currently supported at the
|
||||
umbrella root.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -222,6 +223,12 @@ defmodule Mix.Tasks.Xref do
|
||||
lib/a.ex
|
||||
└── lib/b.ex (compile)
|
||||
|
||||
If you have an umbrella application, we also recommend using the
|
||||
`--include-siblings` flag to see the dependencies from sibling
|
||||
applications. When invoked at the umbrella root, the `graph`
|
||||
command will list all files from all umbrella children, without
|
||||
any namespacing.
|
||||
|
||||
### Dependency types
|
||||
|
||||
Elixir tracks three types of dependencies between modules: compile,
|
||||
@@ -289,12 +296,6 @@ defmodule Mix.Tasks.Xref do
|
||||
|
||||
@impl true
|
||||
def run(args) do
|
||||
if Mix.Project.umbrella?() do
|
||||
Mix.raise(
|
||||
"mix xref is not supported in the umbrella root. Please run it inside the umbrella applications instead"
|
||||
)
|
||||
end
|
||||
|
||||
Mix.Task.run("compile", args)
|
||||
Mix.Task.reenable("xref")
|
||||
|
||||
@@ -302,9 +303,11 @@ defmodule Mix.Tasks.Xref do
|
||||
|
||||
case args do
|
||||
["callers", module] ->
|
||||
no_umbrella!("callers")
|
||||
handle_callers(module, opts)
|
||||
|
||||
["trace", file] ->
|
||||
no_umbrella!("trace")
|
||||
handle_trace(file, opts)
|
||||
|
||||
["graph"] ->
|
||||
@@ -327,6 +330,14 @@ defmodule Mix.Tasks.Xref do
|
||||
end
|
||||
end
|
||||
|
||||
defp no_umbrella!(task) do
|
||||
if Mix.Project.umbrella?() do
|
||||
Mix.raise(
|
||||
"mix xref #{task} is not supported in the umbrella root. Please run it inside the umbrella applications instead"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of information of all the runtime function calls in the project.
|
||||
|
||||
@@ -1058,12 +1069,18 @@ defmodule Mix.Tasks.Xref do
|
||||
|
||||
defp manifests(opts) do
|
||||
siblings =
|
||||
if opts[:include_siblings] do
|
||||
for %{scm: Mix.SCM.Path, opts: opts} <- Mix.Dep.cached(),
|
||||
opts[:in_umbrella],
|
||||
do: Path.join([opts[:build], ".mix", @manifest])
|
||||
else
|
||||
[]
|
||||
cond do
|
||||
Mix.Project.umbrella?() ->
|
||||
for %{opts: opts} <- Mix.Dep.Umbrella.cached(),
|
||||
do: Path.join([opts[:build], ".mix", @manifest])
|
||||
|
||||
opts[:include_siblings] ->
|
||||
for %{scm: Mix.SCM.Path, opts: opts} <- Mix.Dep.cached(),
|
||||
opts[:in_umbrella],
|
||||
do: Path.join([opts[:build], ".mix", @manifest])
|
||||
|
||||
true ->
|
||||
[]
|
||||
end
|
||||
|
||||
[Path.join(Mix.Project.manifest_path(), @manifest) | siblings]
|
||||
|
||||
@@ -337,17 +337,14 @@ defmodule Mix.ReleaseTest do
|
||||
|
||||
test "configures other applications in cascade", context do
|
||||
in_tmp(context.test, fn ->
|
||||
app =
|
||||
write_app!(
|
||||
"my_sample_mode/ebin/my_sample_mode.app",
|
||||
{:application, :my_sample_mode,
|
||||
applications: [:kernel, :stdlib, :elixir, :runtime_tools, :compiler],
|
||||
description: ~c"my_sample_mode",
|
||||
modules: [],
|
||||
vsn: ~c"1.0.0"}
|
||||
|
||||
File.mkdir_p!("my_sample_mode/ebin")
|
||||
Code.prepend_path("my_sample_mode/ebin")
|
||||
format = :io_lib.format("%% coding: utf-8~n~p.~n", [app])
|
||||
File.write!("my_sample_mode/ebin/my_sample_mode.app", format)
|
||||
)
|
||||
|
||||
apps = [my_sample_mode: :temporary]
|
||||
release = release(applications: apps)
|
||||
@@ -802,18 +799,15 @@ defmodule Mix.ReleaseTest do
|
||||
describe "included applications" do
|
||||
test "are included in the release", context do
|
||||
in_tmp(context.test, fn ->
|
||||
app =
|
||||
write_app!(
|
||||
"my_sample1/ebin/my_sample1.app",
|
||||
{:application, :my_sample1,
|
||||
applications: [:kernel, :stdlib, :elixir],
|
||||
description: ~c"my_sample1",
|
||||
modules: [],
|
||||
vsn: ~c"1.0.0",
|
||||
included_applications: [:runtime_tools]}
|
||||
|
||||
File.mkdir_p!("my_sample1/ebin")
|
||||
Code.prepend_path("my_sample1/ebin")
|
||||
format = :io_lib.format("%% coding: utf-8~n~p.~n", [app])
|
||||
File.write!("my_sample1/ebin/my_sample1.app", format)
|
||||
)
|
||||
|
||||
release = release(applications: [my_sample1: :permanent])
|
||||
assert release.boot_scripts.start[:runtime_tools] == :load
|
||||
@@ -825,18 +819,15 @@ defmodule Mix.ReleaseTest do
|
||||
|
||||
test "raise on conflict", context do
|
||||
in_tmp(context.test, fn ->
|
||||
app =
|
||||
write_app!(
|
||||
"my_sample2/ebin/my_sample2.app",
|
||||
{:application, :my_sample2,
|
||||
applications: [:kernel, :stdlib, :elixir, :runtime_tools],
|
||||
description: ~c"my_sample",
|
||||
modules: [],
|
||||
vsn: ~c"1.0.0",
|
||||
included_applications: [:runtime_tools]}
|
||||
|
||||
File.mkdir_p!("my_sample2/ebin")
|
||||
Code.prepend_path("my_sample2/ebin")
|
||||
format = :io_lib.format("%% coding: utf-8~n~p.~n", [app])
|
||||
File.write!("my_sample2/ebin/my_sample2.app", format)
|
||||
)
|
||||
|
||||
assert_raise Mix.Error,
|
||||
":runtime_tools is listed both as a regular application and as an included application",
|
||||
@@ -848,23 +839,70 @@ defmodule Mix.ReleaseTest do
|
||||
describe "optional applications" do
|
||||
test "are ignored if not available", context do
|
||||
in_tmp(context.test, fn ->
|
||||
app =
|
||||
{:application, :my_sample1,
|
||||
write_app!(
|
||||
"my_sample3/ebin/my_sample3.app",
|
||||
{:application, :my_sample3,
|
||||
applications: [:kernel, :stdlib, :elixir, :unknown],
|
||||
optional_applications: [:unknown],
|
||||
description: ~c"my_sample1",
|
||||
description: ~c"my_sample3",
|
||||
modules: [],
|
||||
vsn: ~c"1.0.0"}
|
||||
)
|
||||
|
||||
File.mkdir_p!("my_sample1/ebin")
|
||||
Code.prepend_path("my_sample1/ebin")
|
||||
format = :io_lib.format("%% coding: utf-8~n~p.~n", [app])
|
||||
File.write!("my_sample1/ebin/my_sample1.app", format)
|
||||
|
||||
release = release(applications: [my_sample1: :permanent])
|
||||
release = release(applications: [my_sample3: :permanent])
|
||||
assert release.boot_scripts.start[:unknown] == nil
|
||||
end)
|
||||
end
|
||||
|
||||
test "are ignored even if mode changes", context do
|
||||
in_tmp(context.test, fn ->
|
||||
write_app!(
|
||||
"has_optional/ebin/has_optional.app",
|
||||
{:application, :has_optional,
|
||||
applications: [:kernel, :stdlib, :elixir, :unknown],
|
||||
optional_applications: [:unknown],
|
||||
description: ~c"has_optional",
|
||||
modules: [],
|
||||
vsn: ~c"1.0.0"}
|
||||
)
|
||||
|
||||
write_app!(
|
||||
"points_as_permanent/ebin/points_as_permanent.app",
|
||||
{:application, :points_as_permanent,
|
||||
applications: [:kernel, :stdlib, :elixir, :has_optional],
|
||||
optional_applications: [:unknown],
|
||||
description: ~c"points_as_permanent",
|
||||
modules: [],
|
||||
vsn: ~c"1.0.0"}
|
||||
)
|
||||
|
||||
write_app!(
|
||||
"points_as_temporary/ebin/points_as_temporary.app",
|
||||
{:application, :points_as_temporary,
|
||||
applications: [:kernel, :stdlib, :elixir, :has_optional],
|
||||
optional_applications: [:unknown],
|
||||
description: ~c"points_as_temporary",
|
||||
modules: [],
|
||||
vsn: ~c"1.0.0"}
|
||||
)
|
||||
|
||||
release =
|
||||
release(
|
||||
applications: [points_as_permanent: :permanent, points_as_temporary: :temporary]
|
||||
)
|
||||
|
||||
assert release.boot_scripts.start[:has_optional] == :permanent
|
||||
assert release.boot_scripts.start[:unknown] == nil
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp write_app!(path, app) do
|
||||
dir = Path.dirname(path)
|
||||
File.mkdir_p!(dir)
|
||||
Code.prepend_path(dir)
|
||||
format = :io_lib.format("%% coding: utf-8~n~p.~n", [app])
|
||||
File.write!(path, format)
|
||||
end
|
||||
|
||||
defp size!(path) do
|
||||
|
||||
@@ -32,6 +32,7 @@ defmodule Mix.Tasks.ArchiveTest do
|
||||
message = "Generated archive \"archive-0.1.0.ez\" with MIX_ENV=dev"
|
||||
assert_received {:mix_shell, :info, [^message]}
|
||||
assert File.regular?(~c"archive-0.1.0.ez")
|
||||
assert to_charlist(Mix.Project.consolidation_path()) not in :code.get_path()
|
||||
|
||||
assert_archive_content_default()
|
||||
refute has_in_zip_file?(~c"archive-0.1.0.ez", ~c"archive-0.1.0/priv/.dot_file")
|
||||
|
||||
@@ -1263,6 +1263,11 @@ defmodule Mix.Tasks.Compile.ElixirTest do
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/c.ex"]}
|
||||
|
||||
# Mix recompile should work even if the compile path
|
||||
# was removed and the module purged
|
||||
Code.delete_path(Mix.Project.compile_path())
|
||||
purge([A])
|
||||
|
||||
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
|
||||
assert_received {:mix_shell, :info, ["Compiling 1 file (.ex)"]}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
|
||||
@@ -60,6 +60,23 @@ defmodule Mix.Tasks.Compile.YeccTest do
|
||||
severity: :warning
|
||||
} = diagnostic
|
||||
end)
|
||||
|
||||
# Removing parse tools re-add it later even if only to show warnings
|
||||
:code.del_path(:parsetools)
|
||||
purge([:yecc])
|
||||
refute Code.ensure_loaded?(:yecc)
|
||||
|
||||
capture_io(fn ->
|
||||
assert {:noop, [diagnostic]} = Mix.Tasks.Compile.Yecc.run([])
|
||||
|
||||
assert %Mix.Task.Compiler.Diagnostic{
|
||||
compiler_name: "yecc",
|
||||
file: ^file,
|
||||
message: "conflicts: 1 shift/reduce, 0 reduce/reduce",
|
||||
position: 0,
|
||||
severity: :warning
|
||||
} = diagnostic
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -5,12 +5,6 @@ defmodule Mix.Tasks.CompileTest do
|
||||
|
||||
defmacro position(line, column), do: {line, column}
|
||||
|
||||
defmodule CustomCompilers do
|
||||
def project do
|
||||
[compilers: [:elixir, :app, :custom]]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule DepsApp do
|
||||
def project do
|
||||
[app: :deps_app, version: "0.1.0", deps: [{:ok, "0.1.0", path: "deps/ok"}]]
|
||||
@@ -23,7 +17,8 @@ defmodule Mix.Tasks.CompileTest do
|
||||
end
|
||||
end
|
||||
|
||||
setup do
|
||||
setup tags do
|
||||
Mix.ProjectStack.post_config(Map.get(tags, :project, []))
|
||||
Mix.Project.push(MixTest.Case.Sample)
|
||||
:ok
|
||||
end
|
||||
@@ -37,16 +32,14 @@ defmodule Mix.Tasks.CompileTest do
|
||||
assert_received {:mix_shell, :info, ["mix compile.elixir # " <> _]}
|
||||
end
|
||||
|
||||
@tag project: [compilers: [:elixir, :app, :custom]]
|
||||
test "compiles --list with custom mixfile" do
|
||||
Mix.Project.pop()
|
||||
Mix.Project.push(CustomCompilers)
|
||||
Mix.Task.run("compile", ["--list"])
|
||||
assert_received {:mix_shell, :info, ["\nEnabled compilers: elixir, app, custom, protocols"]}
|
||||
end
|
||||
|
||||
@tag project: [compilers: [:elixir, :app, :custom]]
|
||||
test "compiles does not require all compilers available on manifest" do
|
||||
Mix.Project.pop()
|
||||
Mix.Project.push(CustomCompilers)
|
||||
assert Mix.Tasks.Compile.manifests() |> Enum.map(&Path.basename/1) == ["compile.elixir"]
|
||||
end
|
||||
|
||||
@@ -101,6 +94,34 @@ defmodule Mix.Tasks.CompileTest do
|
||||
end)
|
||||
end
|
||||
|
||||
@tag project: [compilers: Mix.compilers() ++ [:my_custom_compiler]]
|
||||
test "compiles a project with custom in-project compiler" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
File.mkdir_p!("lib")
|
||||
|
||||
File.write!("lib/a.ex", """
|
||||
defmodule Mix.Tasks.Compile.MyCustomCompiler do
|
||||
use Mix.Task.Compiler
|
||||
|
||||
@impl true
|
||||
def run(_args) do
|
||||
Mix.shell().info("Compiling...")
|
||||
:ok
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
assert Mix.Task.run("compile") == {:ok, []}
|
||||
assert_receive {:mix_shell, :info, ["Compiling..."]}
|
||||
Code.delete_path(Mix.Project.compile_path())
|
||||
purge([Mix.Tasks.Compile.MyCustomCompiler])
|
||||
false = Code.ensure_loaded?(Mix.Tasks.Compile.MyCustomCompiler)
|
||||
|
||||
Mix.Task.clear()
|
||||
assert Mix.Task.rerun("compile")
|
||||
end)
|
||||
end
|
||||
|
||||
test "recompiles app cache if manifest changes" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
Mix.Tasks.Compile.run(["--force"])
|
||||
@@ -329,12 +350,22 @@ defmodule Mix.Tasks.CompileTest do
|
||||
Application.delete_env(:sample, :hello, persistent: true)
|
||||
end
|
||||
|
||||
test "code path prunning" do
|
||||
test "code path pruning" do
|
||||
Mix.ensure_application!(:parsetools)
|
||||
otp_docs? = match?({:docs_v1, _, _, _, _, _, _}, Code.fetch_docs(:zlib))
|
||||
|
||||
in_fixture("no_mixfile", fn ->
|
||||
assert Mix.Task.run("compile", []) == {:ok, []}
|
||||
assert :code.where_is_file(~c"parsetools.app") == :non_existing
|
||||
|
||||
# Make sure erts is also kept but not loaded
|
||||
assert Application.spec(:erts, :vsn) == nil
|
||||
|
||||
if otp_docs? do
|
||||
assert {:docs_v1, _, _, _, _, _, _} = Code.fetch_docs(:zlib)
|
||||
else
|
||||
IO.warn("Erlang/OTP was not compiled with docs, skipping assertion")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -915,6 +915,43 @@ defmodule Mix.Tasks.XrefTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "generates reports from the umbrella root" do
|
||||
Mix.Project.pop()
|
||||
|
||||
in_fixture("umbrella_dep/deps/umbrella", fn ->
|
||||
Mix.Project.in_project(:umbrella, ".", fn _ ->
|
||||
File.write!("apps/bar/lib/bar.ex", """
|
||||
defmodule Bar do
|
||||
def bar do
|
||||
Foo.foo()
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
Mix.Task.run("compile")
|
||||
Mix.shell().flush()
|
||||
|
||||
Mix.Tasks.Xref.run(["graph", "--format", "stats", "--include-siblings"])
|
||||
|
||||
assert receive_until_no_messages([]) == """
|
||||
Tracked files: 2 (nodes)
|
||||
Compile dependencies: 0 (edges)
|
||||
Exports dependencies: 0 (edges)
|
||||
Runtime dependencies: 1 (edges)
|
||||
Cycles: 0
|
||||
|
||||
Top 2 files with most outgoing dependencies:
|
||||
* lib/bar.ex (1)
|
||||
* lib/foo.ex (0)
|
||||
|
||||
Top 2 files with most incoming dependencies:
|
||||
* lib/foo.ex (1)
|
||||
* lib/bar.ex (0)
|
||||
"""
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "generates reports considering siblings inside umbrellas" do
|
||||
Mix.Project.pop()
|
||||
|
||||
|
||||
@@ -252,8 +252,6 @@ defmodule Mix.UmbrellaTest do
|
||||
end)
|
||||
end
|
||||
|
||||
## Umbrellas as a dependency
|
||||
|
||||
test "list deps for umbrella as dependency" do
|
||||
in_fixture("umbrella_dep", fn ->
|
||||
Mix.Project.in_project(:umbrella_dep, ".", fn _ ->
|
||||
@@ -373,7 +371,7 @@ defmodule Mix.UmbrellaTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "recompiles after compile time path dependency changes" do
|
||||
test "recompiles when compile-time path dependencies change" do
|
||||
in_fixture("umbrella_dep/deps/umbrella/apps", fn ->
|
||||
Mix.Project.in_project(:bar, "bar", fn _ ->
|
||||
Mix.Task.run("compile", [])
|
||||
@@ -385,30 +383,13 @@ defmodule Mix.UmbrellaTest do
|
||||
assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []}
|
||||
assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
|
||||
# Emulate local recompilation
|
||||
# Compile-time dependencies are recompiled
|
||||
File.write!("../foo/lib/foo.ex", File.read!("../foo/lib/foo.ex") <> "\n")
|
||||
mtime = File.stat!("_build/dev/lib/bar/.mix/compile.elixir").mtime
|
||||
ensure_touched("../foo/lib/foo.ex", mtime)
|
||||
ensure_touched("../foo/lib/foo.ex", "_build/dev/lib/bar/.mix/compile.elixir")
|
||||
|
||||
Mix.Task.clear()
|
||||
assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
|
||||
# But exports dependencies are not recompiled
|
||||
File.write!("lib/bar.ex", "defmodule Bar, do: (require Foo)")
|
||||
|
||||
Mix.Task.clear()
|
||||
assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
|
||||
# Touch to emulate local recompilation
|
||||
File.write!("../foo/lib/foo.ex", File.read!("../foo/lib/foo.ex") <> "\n")
|
||||
mtime = File.stat!("_build/dev/lib/bar/.mix/compile.elixir").mtime
|
||||
ensure_touched("../foo/lib/foo.ex", mtime)
|
||||
|
||||
Mix.Task.clear()
|
||||
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:noop, []}
|
||||
refute_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
end)
|
||||
|
||||
# Now let's add a new file to foo
|
||||
@@ -446,18 +427,40 @@ defmodule Mix.UmbrellaTest do
|
||||
File.write!("../foo/lib/foo.ex", "defmodule Foo, do: defstruct [:bar]")
|
||||
|
||||
# Add struct dependency
|
||||
File.write!("lib/bar.ex", "defmodule Bar, do: %Foo{bar: true}")
|
||||
Mix.Task.run("compile", ["--verbose"])
|
||||
assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
File.write!("lib/bar.ex", """
|
||||
defmodule Bar do
|
||||
def is_foo_bar(%Foo{bar: true}), do: true
|
||||
end
|
||||
""")
|
||||
|
||||
# Recompiles for struct dependencies
|
||||
Mix.Task.run("compile", ["--verbose"])
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
|
||||
# Does not recompiles if export dependency does not change
|
||||
File.write!("../foo/lib/foo.ex", File.read!("../foo/lib/foo.ex") <> "\n")
|
||||
mtime = File.stat!("_build/dev/lib/bar/.mix/compile.elixir").mtime
|
||||
ensure_touched("../foo/lib/foo.ex", mtime)
|
||||
ensure_touched("../foo/lib/foo.ex", "_build/dev/lib/bar/.mix/compile.elixir")
|
||||
|
||||
Mix.Task.clear()
|
||||
assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []}
|
||||
assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
refute_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
|
||||
# Recompiles if export dependency changes
|
||||
File.write!("../foo/lib/foo.ex", "defmodule Foo, do: defstruct [:bar, :baz]")
|
||||
ensure_touched("../foo/lib/foo.ex", "_build/dev/lib/bar/.mix/compile.elixir")
|
||||
|
||||
Mix.Task.clear()
|
||||
assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
|
||||
# Recompiles if export dependency is removed
|
||||
File.write!("../foo/lib/foo.ex", "")
|
||||
ensure_touched("../foo/lib/foo.ex", "_build/dev/lib/bar/.mix/compile.elixir")
|
||||
Mix.Task.clear()
|
||||
|
||||
ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||
Process.flag(:trap_exit, true)
|
||||
catch_exit(Mix.Task.run("compile", ["--verbose"]))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -480,16 +483,15 @@ defmodule Mix.UmbrellaTest do
|
||||
# Add compile time to Foo.Bar
|
||||
File.write!("lib/bar.ex", "defmodule Bar, do: Foo.Bar.hello()")
|
||||
Mix.Task.run("compile", ["--verbose"])
|
||||
assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
|
||||
# Recompiles for due to compile dependency via runtime dependencies
|
||||
File.write!("../foo/lib/foo.baz.ex", File.read!("../foo/lib/foo.baz.ex") <> "\n")
|
||||
mtime = File.stat!("_build/dev/lib/bar/.mix/compile.elixir").mtime
|
||||
ensure_touched("../foo/lib/foo.ex", mtime)
|
||||
ensure_touched("../foo/lib/foo.ex", "_build/dev/lib/bar/.mix/compile.elixir")
|
||||
|
||||
Mix.Task.clear()
|
||||
assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []}
|
||||
assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
home = Path.expand("../tmp/.home", __DIR__)
|
||||
File.mkdir_p!(home)
|
||||
System.put_env("HOME", home)
|
||||
|
||||
mix = Path.expand("../tmp/.mix", __DIR__)
|
||||
File.mkdir_p!(mix)
|
||||
System.put_env("MIX_HOME", mix)
|
||||
|
||||
System.delete_env("XDG_DATA_HOME")
|
||||
System.delete_env("XDG_CONFIG_HOME")
|
||||
|
||||
## Setup Mix
|
||||
|
||||
Mix.start()
|
||||
Mix.shell(Mix.Shell.Process)
|
||||
Application.put_env(:mix, :colors, enabled: false)
|
||||
@@ -5,6 +18,8 @@ Application.put_env(:mix, :colors, enabled: false)
|
||||
Logger.remove_backend(:console)
|
||||
Application.put_env(:logger, :backends, [])
|
||||
|
||||
## Setup ExUnit
|
||||
|
||||
os_exclude = if match?({:win32, _}, :os.type()), do: [unix: true], else: [windows: true]
|
||||
epmd_exclude = if match?({:win32, _}, :os.type()), do: [epmd: true], else: []
|
||||
git_exclude = if Mix.SCM.Git.git_version() <= {1, 7, 4}, do: [git_sparse: true], else: []
|
||||
@@ -212,18 +227,7 @@ defmodule MixTest.Case do
|
||||
end
|
||||
end
|
||||
|
||||
## Set up globals
|
||||
|
||||
home = MixTest.Case.tmp_path(".home")
|
||||
File.mkdir_p!(home)
|
||||
System.put_env("HOME", home)
|
||||
|
||||
mix = MixTest.Case.tmp_path(".mix")
|
||||
File.mkdir_p!(mix)
|
||||
System.put_env("MIX_HOME", mix)
|
||||
|
||||
System.delete_env("XDG_DATA_HOME")
|
||||
System.delete_env("XDG_CONFIG_HOME")
|
||||
## Set up Rebar fixtures
|
||||
|
||||
rebar3_source = System.get_env("REBAR3") || Path.expand("fixtures/rebar3", __DIR__)
|
||||
[major, minor | _] = String.split(System.version(), ".")
|
||||
@@ -231,8 +235,6 @@ rebar3_target = Path.join([mix, "elixir", "#{major}-#{minor}", "rebar3"])
|
||||
File.mkdir_p!(Path.dirname(rebar3_target))
|
||||
File.cp!(rebar3_source, rebar3_target)
|
||||
|
||||
## Copy fixtures to tmp
|
||||
|
||||
fixtures = ~w(rebar_dep rebar_override)
|
||||
|
||||
Enum.each(fixtures, fn fixture ->
|
||||
@@ -242,12 +244,13 @@ Enum.each(fixtures, fn fixture ->
|
||||
File.cp_r!(source, dest)
|
||||
end)
|
||||
|
||||
## Generate Git repo fixtures
|
||||
## Set up Git fixtures
|
||||
|
||||
System.cmd("git", ~w[config --global user.email mix@example.com])
|
||||
System.cmd("git", ~w[config --global user.name mix-repo])
|
||||
System.cmd("git", ~w[config --global init.defaultBranch not-main])
|
||||
|
||||
# Git repo
|
||||
### Git repo
|
||||
target = Path.expand("fixtures/git_repo", __DIR__)
|
||||
|
||||
unless File.dir?(target) do
|
||||
@@ -329,7 +332,7 @@ unless File.dir?(target) do
|
||||
end)
|
||||
end
|
||||
|
||||
# Deps on Git repo
|
||||
### Deps on Git repo
|
||||
target = Path.expand("fixtures/deps_on_git_repo", __DIR__)
|
||||
|
||||
unless File.dir?(target) do
|
||||
@@ -423,7 +426,7 @@ Enum.each([:invalidapp, :invalidvsn, :noappfile, :nosemver, :ok], fn dep ->
|
||||
File.mkdir_p!(Path.expand("fixtures/deps_status/deps/#{dep}/.git", __DIR__))
|
||||
end)
|
||||
|
||||
# Archive ebin
|
||||
### Archive ebin
|
||||
target = Path.expand("fixtures/archive", __DIR__)
|
||||
|
||||
unless File.dir?(Path.join(target, "ebin")) do
|
||||
|
||||
Reference in New Issue
Block a user