Compare commits

...
Author SHA1 Message Date
José Valim 7f7a8bca99 Release v1.15.2 2023-07-01 22:15:55 +02:00
José Valim 0929a431d4 Check for variable definition instead of expansion 2023-07-01 19:28:28 +02:00
José Valim f1e5d770ea Release v1.15.1 2023-06-30 17:03:41 +02:00
José Valim bceda78a42 Consider surround context until end whenever possible 2023-06-30 16:57:48 +02:00
Rich Cavanaugh 6d829120c3 Allow variables defined in doctests to be used in expectation (#12730) 2023-06-28 22:32:21 +02:00
José Valim 785c29210e Do not assume Logger has been loaded at compile-time 2023-06-27 15:02:26 +02:00
José Valim 7db2daea72 mix format 2023-06-27 14:58:50 +02:00
José Valim 0fcee760d8 Remove remaining reference to digest_file! 2023-06-27 14:50:01 +02:00
José Valim e63b0b6217 Do not assume external resources are available 2023-06-27 14:29:25 +02:00
José Valim 9e195f5251 Mention Erlang's :math module in Float 2023-06-27 14:01:05 +02:00
Jean Klingler 60320941aa Always respect options passed to capture_log (#12721) 2023-06-27 14:01:05 +02:00
José Valim 871e737583 Fix heisentest 2023-06-25 09:49:58 +02:00
José Valim 3785e6f71b Return errors on mix compile instead of raising 2023-06-24 20:18:18 +02:00
José Valim a89f8a9913 Track removed modules and exports across local deps, closes #12707 2023-06-24 17:06:18 +02:00
José Valim 93bfbdfc4e Remove --werl from Erlang/OTP 26 release scripts 2023-06-24 13:08:40 +02:00
Jean Klingler c032839468 Parser honors :static_atoms_encoder for multi-letter sigils (#12706)
* Unify sigil token generation

* Parser honors :static_atoms_encoder for multi-letter sigils

* Remove chars from token, reverse engineer from atom
2023-06-24 13:08:32 +02:00
José Valim 52cf991302 Update CHANGELOG 2023-06-23 16:04:56 +02:00
cjschneider2 0c6b44798b Use printf instead of echo to output user flags (#12704)
Fixes #12677.

Using printf here prevents an error when outputting the filtered flag
`-e` as doing with with `echo "-e"` doesn't output anything
(deleting the option we wanted to filter). 

More info: https://unix.stackexchange.com/a/65819
2023-06-23 16:04:50 +02:00
José Valim 4c12bfcb14 Ensure included deps transitive from path deps can be loaded, closes #12682 2023-06-23 15:45:35 +02:00
José Valim fde96ce8dc Handle :function metadata properly, closes #12703 2023-06-23 13:07:14 +02:00
Jannik Becher bc40b6ebb5 :test_type does not affect the test execution (#12701) 2023-06-23 10:12:30 +02:00
Aaron Tinio 3a34f243b8 Provide compile_env tip that fixes the error across all environments (#12702) 2023-06-23 10:12:23 +02:00
José Valim f322d26b7d Include optional dependencies when mode changes, closes #12694 2023-06-23 10:12:23 +02:00
José Valim dcaa468c47 Extract app writing into its own function 2023-06-23 10:12:23 +02:00
José Valim cc2d05654f Fix dbg on Erlang/OTP 25- (#12697) 2023-06-22 20:53:30 +02:00
José Valim c22f7343f0 Fix non-result doctest terminated with fences, closes #12695 2023-06-22 19:06:01 +02:00
José Valim 858835f9f7 yecc and leex are not available on custom compilers 2023-06-21 20:25:15 +02:00
José Valim 367d906403 Assert current project is in the path before invoking compilers
Closes #12686.
2023-06-21 20:24:00 +02:00
José Valim df5bc5abc8 Ensure yecc is available to emit warnings, closes #12691 2023-06-21 19:33:44 +02:00
José Valim 1ca5d789bb Fix race condition on concurrent capture_log, closes #12692 2023-06-21 16:44:48 +02:00
José Valim aab3543dc3 Add release note about potential compatibilities to CHANGELOG, closes #12688 2023-06-21 09:37:28 +02:00
José Valim 9b254e6830 Do not expect OTP to be compiled with docs, closes #12677 2023-06-20 12:33:41 +02:00
José Valim 0dd933b0a9 Fix path on Windows 2023-06-19 16:11:28 +02:00
José Valim 9fd85b06dc Release v1.15.0 2023-06-19 13:37:46 +02:00
José Valim ec81110b3e Improve Logger docs 2023-06-18 17:12:23 +02:00
José Valim 094a76d514 Update release instructions 2023-06-18 10:44:40 +02:00
sabiwara 7d737bc5ec Fix slicing MapSet with stepped range (#12667) 2023-06-14 12:04:51 +02:00
José Valim 3eb074282a Return the value on each step of dbg+pry+pipe integration, closes #12657 2023-06-13 17:11:34 +02:00
José Valim b8732bd248 Improve error message for invalid module attribute usage, closes #12656 2023-06-13 16:54:33 +02:00
José Valim 2252a8ff55 Fix erts handling when loading apps 2023-06-13 15:29:17 +02:00
José Valim 9a9cf419a1 Release v1.15.0-rc.2 2023-06-12 15:10:17 +02:00
Wojtek Mach 0f92512030 Bump Ubuntu version for builds.hex.pm.yml (#12652)
We need nsis 3.08 (not 3.06)
2023-06-12 13:05:11 +02:00
nico piderman e0806f9d6c Mark test cases as invalid when an exit occurs during setup_all (#12651) 2023-06-12 12:54:05 +02:00
Wojtek Mach 5db9d55926 Add offline Windows installer to releases (#12640) 2023-06-12 12:54:05 +02:00
José Valim 3cb3d41368 Keep capture operator on container cursor to quoted 2023-06-12 10:44:16 +02:00
José Valim a3a081c949 Keep erts when pruning load paths (#12642) 2023-06-11 11:27:07 +02:00
José Valim 207350fb44 Improve heredoc warning 2023-06-08 16:15:03 +02:00
José Valim 6f7a68c9e3 Support mix xref graph at umbrella roots, closes #12629 2023-06-07 14:13:11 +02:00
José Valim 0936fb45d5 Mark functions as generated in Docs chunk 2023-06-07 13:14:17 +02:00
José Valim bccfca4ffe Purge Hex before running Mix tests 2023-06-03 20:59:52 +02:00
José Valim 417255fe1b Branch out v1.15 2023-06-03 20:24:12 +02:00
67 changed files with 1182 additions and 466 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
- otp: 26 - otp: 26
otp_version: '26.0' otp_version: '26.0'
build_docs: build_docs build_docs: build_docs
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
+5 -3
View File
@@ -15,7 +15,7 @@ permissions:
jobs: jobs:
create_draft_release: create_draft_release:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps: steps:
@@ -40,7 +40,7 @@ jobs:
- otp: 26 - otp: 26
otp_version: '26.0' otp_version: '26.0'
build_docs: build_docs build_docs: build_docs
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
@@ -56,7 +56,9 @@ jobs:
run: | run: |
gh release upload --clobber "${{ github.ref_name }}" \ gh release upload --clobber "${{ github.ref_name }}" \
elixir-otp-${{ matrix.otp }}.zip \ 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 - name: Upload Docs to GitHub
if: ${{ matrix.build_docs }} if: ${{ matrix.build_docs }}
env: env:
@@ -22,6 +22,20 @@ runs:
shasum -a 1 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha1sum 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 shasum -a 256 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha256sum
echo "$PWD/bin" >> $GITHUB_PATH 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 - name: Get latest stable ExDoc version
if: ${{ inputs.build_docs }} if: ${{ inputs.build_docs }}
shell: bash shell: bash
+63 -42
View File
@@ -68,12 +68,20 @@ In Elixir v1.15, the new reports will look like:
error: undefined function foo/0 (there is no such import) error: undefined function foo/0 (there is no such import)
my_file.exs:1 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 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 information can be leveraged by editors, allowing them to point to several
errors at once. 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 ## Integration with Erlang/OTP logger
This release provides additional features such as global logger This release provides additional features such as global logger
@@ -100,61 +108,60 @@ config :logger, :default_formatter,
format: "$time $message $metadata" format: "$time $message $metadata"
``` ```
If you use `Logger.Backends.Console` or other backends, they are If you use `Logger.Backends.Console` with a custom device or other
still fully supported and functional. If you implement your own backends, they are still fully supported and functional. If you
backends, you want to consider migrating to implement your own backends, you want to consider migrating to
[`:logger_backends`](https://github.com/elixir-lang/logger_backends) [`:logger_backends`](https://github.com/elixir-lang/logger_backends)
in the long term. in the long term.
See the new `Logger` documentation for more information on the See the new `Logger` documentation for more information on the
new features and on compatibility. new features and on compatibility.
## v1.15.0-rc.1 (2022-05-29) ## v1.15.2 (2023-07-01)
### 1. Enhancements ### 1. Bug fixes
#### Elixir
* [File] Support distributed `File.Stream`
* [Module] Add `Module.get_last_attribute/3`
* [Task] Reduce footprint of tasks by avoiding unecessary work during spawning
#### ExUnit
* [ExUnit.Case] Add `ExUnit.Case.get_last_registered_test/1`
### 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
#### IEx #### IEx
* [IEx] Fix IO operations not returning when booting IEx (regression) * [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
* [mix deps] Ensure dependencies with `included_applications` can be loaded (regression) * [mix compile] Ensure the current project is available on the code path after its Elixir sources are compiled
* [mix format] Ensure proper formatter options are returned for files (regression) * [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
### 3. Soft deprecations ## v1.15.0 (2023-06-19)
#### Elixir
* [Kernel] Require pin variable when accessing variable inside binary size in match
## v1.15.0-rc.0 (2022-05-22)
### 1. Enhancements ### 1. Enhancements
@@ -175,6 +182,7 @@ new features and on compatibility.
* [Date] Add `Date.before?/2` and `Date.after?/2` * [Date] Add `Date.before?/2` and `Date.after?/2`
* [DateTime] Add `DateTime.before?/2` and `DateTime.after?/2` * [DateTime] Add `DateTime.before?/2` and `DateTime.after?/2`
* [DateTime] Support precision in `DateTime.utc_now/2` * [DateTime] Support precision in `DateTime.utc_now/2`
* [File] Support distributed `File.Stream`
* [Inspect] `Inspect` now renders `'charlists'` as `~c"charlists"` by default * [Inspect] `Inspect` now renders `'charlists'` as `~c"charlists"` by default
* [Kernel] Break down `case` and `cond` inside `dbg/2` * [Kernel] Break down `case` and `cond` inside `dbg/2`
* [Kernel] Add `t:nonempty_binary/0` and `t:nonempty_bitstring/0` * [Kernel] Add `t:nonempty_binary/0` and `t:nonempty_bitstring/0`
@@ -195,6 +203,8 @@ new features and on compatibility.
* [NaiveDateTime] Add `NaiveDateTime.beginning_of_day/1` and `NaiveDateTime.end_of_day/1` * [NaiveDateTime] Add `NaiveDateTime.beginning_of_day/1` and `NaiveDateTime.end_of_day/1`
* [NaiveDateTime] Add `NaiveDateTime.before?/2` and `NaiveDateTime.after?/2` * [NaiveDateTime] Add `NaiveDateTime.before?/2` and `NaiveDateTime.after?/2`
* [NaiveDateTime] Support precision in `NaiveDateTime.utc_now/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 * [OptionParser] Support `:return_separator` option
* [Process] Add `Process.alias/0,1` and `Process.unalias/1` * [Process] Add `Process.alias/0,1` and `Process.unalias/1`
* [Range] Add `Range.split/2` * [Range] Add `Range.split/2`
@@ -204,6 +214,7 @@ new features and on compatibility.
* [System] Support `:lines` in `System.cmd/3` to capture output line by line * [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] Remove head of line blocking on `Task.yield_many/2`
* [Task] Enable selective receive optimizations in Erlang/OTP 26+ * [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` * [Task.Supervisor] Do not copy args on temporary `Task.Supervisor.start_child/2`
* [Time] Add `Time.before?/2` and `Time.after?/2` * [Time] Add `Time.before?/2` and `Time.after?/2`
* [URI] Add `URI.append_path/2` * [URI] Add `URI.append_path/2`
@@ -212,7 +223,9 @@ new features and on compatibility.
* [ExUnit] Add more color configuration to ExUnit CLI formatter * [ExUnit] Add more color configuration to ExUnit CLI formatter
* [ExUnit.Callbacks] Accept `{module, function}` tuples in ExUnit `setup` callbacks * [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] 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 * [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 #### IEx
@@ -267,16 +280,22 @@ new features and on compatibility.
* [Kernel] Expand macros on the left side of -> in `try/rescue` * [Kernel] Expand macros on the left side of -> in `try/rescue`
* [Kernel] Raise on misplaced `...` inside typespecs * [Kernel] Raise on misplaced `...` inside typespecs
* [Kernel] Do not import `behaviour_info` and `module_info` functions from Erlang modules * [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] Make sure compiler doesn't crash when there are stray messages in the inbox
* [Kernel.ParallelCompiler] Track compile and runtime warnings separately * [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 * [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 * [URI] Make sure `URI.merge/2` works accordingly with relative paths
#### ExUnit #### 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] 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] 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] 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 #### IEx
@@ -288,6 +307,7 @@ new features and on compatibility.
* [mix compile] Include `cwd` in compiler cache key * [mix compile] Include `cwd` in compiler cache key
* [mix release] Fix Windows service when invoking `erlsrv.exe` in path with spaces * [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) ### 3. Soft deprecations (no warnings emitted)
@@ -295,6 +315,7 @@ new features and on compatibility.
* [File] `File.cp/3` and `File.cp_r/3` with a function as third argument * [File] `File.cp/3` and `File.cp_r/3` with a function as third argument
is deprecated in favor of a keyword list 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 * [Kernel.ParallelCompiler] Require the `:return_diagnostics` option to be
set to true when compiling or requiring code set to true when compiling or requiring code
+1 -1
View File
@@ -2,7 +2,7 @@ PREFIX ?= /usr/local
TEST_FILES ?= "*_test.exs" TEST_FILES ?= "*_test.exs"
SHARE_PREFIX ?= $(PREFIX)/share SHARE_PREFIX ?= $(PREFIX)/share
MAN_PREFIX ?= $(SHARE_PREFIX)/man MAN_PREFIX ?= $(SHARE_PREFIX)/man
#CANONICAL := MAJOR.MINOR/ CANONICAL := 1.15/
CANONICAL ?= main/ CANONICAL ?= main/
DOCS_FORMAT ?= html DOCS_FORMAT ?= html
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS) ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
+1 -1
View File
@@ -14,7 +14,7 @@
6. Copy the relevant bits from /CHANGELOG.md to the GitHub release and publish it 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) ## Creating a new vMAJOR.MINOR branch (after first rc)
+2 -3
View File
@@ -6,12 +6,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
Elixir version | Support Elixir version | Support
:------------- | :----------------------------- :------------- | :-----------------------------
1.15 | Development 1.15 | Bug fixes and security patches
1.14 | Bug fixes and security patches 1.14 | Security patches only
1.13 | Security patches only 1.13 | Security patches only
1.12 | Security patches only 1.12 | Security patches only
1.11 | Security patches only 1.11 | Security patches only
1.10 | Security patches only
## Announcements ## Announcements
+1 -1
View File
@@ -1 +1 @@
1.15.0-rc.1 1.15.2
+9 -4
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
set -e set -e
ELIXIR_VERSION=1.15.0-rc.1 ELIXIR_VERSION=1.15.2
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2 cat <<USAGE >&2
@@ -94,6 +94,7 @@ starts_with () {
} }
ERL_EXEC="erl" ERL_EXEC="erl"
MODE="cli"
I=1 I=1
E=0 E=0
LENGTH=$# LENGTH=$#
@@ -104,9 +105,13 @@ while [ $I -le $LENGTH ]; do
S=0 S=0
C=0 C=0
case "$1" in case "$1" in
+elixirc|+iex) +elixirc)
C=1 C=1
;; ;;
+iex)
C=1
MODE="iex"
;;
-v|--no-halt|--dbg) -v|--no-halt|--dbg)
C=1 C=1
;; ;;
@@ -223,12 +228,12 @@ fi
ERTS_BIN= ERTS_BIN=
ERTS_BIN="$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 if [ -n "$RUN_ERL_PIPE" ]; then
ESCAPED="" ESCAPED=""
for PART in "$@"; do 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 done
mkdir -p "$RUN_ERL_PIPE" mkdir -p "$RUN_ERL_PIPE"
mkdir -p "$RUN_ERL_LOG" mkdir -p "$RUN_ERL_LOG"
+8 -6
View File
@@ -1,6 +1,6 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off) @if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
set ELIXIR_VERSION=1.15.0-rc.1 set ELIXIR_VERSION=1.15.2
setlocal enabledelayedexpansion setlocal enabledelayedexpansion
if ""%1""=="""" if ""%2""=="""" goto documentation if ""%1""=="""" if ""%2""=="""" goto documentation
@@ -81,9 +81,6 @@ set beforeExtra=
rem Option which determines whether the loop is over rem Option which determines whether the loop is over
set endLoop=0 set endLoop=0
rem Designates which mode / Elixir component to run as
set runMode="elixir"
rem Designates the path to the current script rem Designates the path to the current script
set SCRIPT_PATH=%~dp0 set SCRIPT_PATH=%~dp0
@@ -106,7 +103,7 @@ if !endLoop! == 1 (
) )
rem ******* EXECUTION OPTIONS ********************** rem ******* EXECUTION OPTIONS **********************
if !par!=="--werl" (set useWerl=1 && goto startloop) 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) if !par!=="+elixirc" (set parsElixir=!parsElixir! +elixirc && goto startloop)
rem ******* EVAL PARAMETERS ************************ rem ******* EVAL PARAMETERS ************************
if ""==!par:-e=! ( if ""==!par:-e=! (
@@ -164,8 +161,13 @@ reg query HKCU\Console /v VirtualTerminalLevel 2>nul | findstr /e "0x1" >nul 2>n
if %errorlevel% == 0 ( if %errorlevel% == 0 (
set beforeExtra=-elixir ansi_enabled true !beforeExtra! 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 ELIXIR_CLI_DRY_RUN (
if defined useWerl ( if defined useWerl (
+1 -1
View File
@@ -30,4 +30,4 @@ readlink_f () {
SELF=$(readlink_f "$0") SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF") 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
View File
@@ -24,6 +24,6 @@ goto end
:run :run
if defined IEX_WITH_WERL (set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=) 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 :end
endlocal endlocal
+4 -1
View File
@@ -1136,7 +1136,10 @@ defmodule Code do
* syntax keywords (`fn`, `do`, `else`, and so on) * syntax keywords (`fn`, `do`, `else`, and so on)
* atoms containing interpolation (`:"#{1 + 1} is two"`), as these * 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) :: @spec string_to_quoted(List.Chars.t(), keyword) ::
+21 -9
View File
@@ -3,10 +3,6 @@ defmodule Code.Fragment do
This module provides conveniences for analyzing fragments of This module provides conveniences for analyzing fragments of
textual code and extract available information whenever possible. 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. This module should be considered experimental.
""" """
@@ -623,7 +619,7 @@ defmodule Code.Fragment do
{reversed_pre, post} = adjust_position(reversed_pre, post) {reversed_pre, post} = adjust_position(reversed_pre, post)
case take_identifier(post, []) do case take_identifier(post, []) do
{_, [], _} -> :none ->
maybe_operator(reversed_pre, post, line, opts) maybe_operator(reversed_pre, post, line, opts)
{:identifier, reversed_post, rest} -> {:identifier, reversed_post, rest} ->
@@ -631,7 +627,7 @@ defmodule Code.Fragment do
reversed = reversed_post ++ reversed_pre reversed = reversed_post ++ reversed_pre
case codepoint_cursor_context(reversed, opts) do case codepoint_cursor_context(reversed, opts) do
{{:struct, acc}, offset} -> {{:struct, acc}, offset} when acc != [] ->
build_surround({:struct, acc}, reversed, line, offset) build_surround({:struct, acc}, reversed, line, offset)
{{:alias, acc}, offset} -> {{:alias, acc}, offset} ->
@@ -733,15 +729,31 @@ defmodule Code.Fragment do
do: take_identifier(t, [h | acc]) do: take_identifier(t, [h | acc])
defp take_identifier(rest, acc) do 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 {[h | _], _} when h in ?A..?Z <- strip_spaces(t, 0) do
take_alias(rest, acc) take_alias(rest, acc)
else 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
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]) do: take_alias(t, [h | acc])
defp take_alias(rest, acc) do defp take_alias(rest, acc) do
+1 -1
View File
@@ -342,7 +342,7 @@ defmodule Config.Provider do
* Make the runtime value match the compile time one * Make the runtime value match the compile time one
* Recompile your project. If the misconfigured application is a dependency, \ * 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 \ * 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 \ set :validate_compile_env to false in your release configuration. If you are \
+8 -6
View File
@@ -3009,13 +3009,16 @@ defmodule Enum do
if first < count and amount > 0 do if first < count and amount > 0 do
amount = Kernel.min(amount, count - first) 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) fun.(first, amount, step)
else else
[] []
end end
end end
defp amount_with_step(amount, 1), do: amount
defp amount_with_step(amount, step), do: div(amount - 1, step) + 1
@doc """ @doc """
Returns a subset list of the given `enumerable`, from `start_index` (zero-based) Returns a subset list of the given `enumerable`, from `start_index` (zero-based)
with `amount` number of elements if available. with `amount` number of elements if available.
@@ -4440,7 +4443,7 @@ defmodule Enum do
if start >= 0 do if start >= 0 do
amount = Kernel.min(amount, count - start) 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) fun.(start, amount, step)
else else
[] []
@@ -4448,7 +4451,7 @@ defmodule Enum do
end end
defp slice_forward(list, start, amount, step) when is_list(list) do 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) slice_list(list, start, amount, step)
end end
@@ -4458,7 +4461,7 @@ defmodule Enum do
[] []
{:ok, count, fun} when is_function(fun, 1) -> {: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) enumerable |> fun.() |> slice_exact(start, amount, step, count)
# TODO: Deprecate me in Elixir v1.18. # TODO: Deprecate me in Elixir v1.18.
@@ -4473,8 +4476,7 @@ defmodule Enum do
end end
{:ok, count, fun} when is_function(fun, 3) -> {:ok, count, fun} when is_function(fun, 3) ->
amount = Kernel.min(amount, count - start) amount = Kernel.min(amount, count - start) |> amount_with_step(step)
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
fun.(start, amount, step) fun.(start, amount, step)
{:error, module} -> {:error, module} ->
+3
View File
@@ -4,6 +4,9 @@ defmodule Float do
@moduledoc """ @moduledoc """
Functions for working with floating-point numbers. Functions for working with floating-point numbers.
For mathematical operations on top of floating-points,
see Erlang's [`:math`](`:math`) module.
## Kernel functions ## Kernel functions
There are functions related to floating-point numbers on the `Kernel` module There are functions related to floating-point numbers on the `Kernel` module
+4 -2
View File
@@ -3555,10 +3555,12 @@ defmodule Kernel do
not bootstrapped?(Macro) -> not bootstrapped?(Macro) ->
nil nil
not function? and __CALLER__.context == :match -> not function? and (__CALLER__.context == :match or __CALLER__.context == :guard) ->
raise ArgumentError, 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 @foo = :value
@@ -8,12 +8,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
Elixir version | Support Elixir version | Support
:------------- | :----------------------------- :------------- | :-----------------------------
1.15 | Development 1.15 | Bug fixes and security patches
1.14 | Bug fixes and security patches 1.14 | Security patches only
1.13 | Security patches only 1.13 | Security patches only
1.12 | Security patches only 1.12 | Security patches only
1.11 | 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). 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).
@@ -0,0 +1 @@
tmp/
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

+46
View File
@@ -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"
+3 -1
View File
@@ -192,7 +192,8 @@ start_cli() ->
'Elixir.Kernel.CLI':main(init:get_plain_arguments()), 'Elixir.Kernel.CLI':main(init:get_plain_arguments()),
elixir_config:booted(). 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() -> start() ->
case code:ensure_loaded(prim_tty) of case code:ensure_loaded(prim_tty) of
{module, _} -> {module, _} ->
@@ -207,6 +208,7 @@ start() ->
start_iex() -> start_iex() ->
case code:ensure_loaded(prim_tty) of case code:ensure_loaded(prim_tty) of
{module, _} -> {module, _} ->
start_cli(),
spawn(fun() -> spawn(fun() ->
elixir_config:wait_until_booted(), elixir_config:wait_until_booted(),
(shell:whereis() =:= undefined) andalso 'Elixir.IEx':cli() (shell:whereis() =:= undefined) andalso 'Elixir.IEx':cli()
+5 -2
View File
@@ -510,12 +510,15 @@ get_moduledoc_meta(Set) ->
get_docs(Set, Module, Definitions, Kind) -> get_docs(Set, Module, Definitions, Kind) ->
[{Key, [{Key,
erl_anno:new(Line), maybe_generated(erl_anno:new(Line), Ctx),
[signature_to_binary(Module, Name, Signature)], [signature_to_binary(Module, Name, Signature)],
doc_value(Doc, Name), doc_value(Doc, Name),
Meta Meta
} || {Name, Arity} <- Definitions, } || {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) -> get_callback_docs(Set, Callbacks) ->
[{Key, [{Key,
+12 -3
View File
@@ -170,13 +170,22 @@ parse_error(Location, File, <<"syntax error before: ">>, Keyword, Input)
%% Produce a human-readable message for errors before a sigil %% Produce a human-readable message for errors before a sigil
parse_error(Location, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full, Input) -> 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 Content2 = case is_binary(Content) of
true -> Content; true -> Content;
false -> <<>> false -> <<>>
end, 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); raise_snippet(Location, File, Input, 'Elixir.SyntaxError', Message);
%% Binaries (and interpolation) are wrapped in [<<...>>] %% Binaries (and interpolation) are wrapped in [<<...>>]
+2 -2
View File
@@ -939,11 +939,11 @@ build_access(Expr, {List, Meta}) ->
%% Interpolation aware %% 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), Meta = meta_from_location(Location),
MetaWithDelimiter = [{delimiter, Delimiter} | Meta], MetaWithDelimiter = [{delimiter, Delimiter} | Meta],
MetaWithIndentation = meta_with_indentation(Meta, Indentation), MetaWithIndentation = meta_with_indentation(Meta, Indentation),
{list_to_atom("sigil_" ++ Sigil), {Atom,
MetaWithDelimiter, MetaWithDelimiter,
[{'<<>>', MetaWithIndentation, string_parts(Parts)}, Modifiers]}. [{'<<>>', MetaWithIndentation, string_parts(Parts)}, Modifiers]}.
+24 -14
View File
@@ -1030,8 +1030,8 @@ extract_heredoc_with_interpolation(Line, Column, Scope, Interpol, T, H) ->
end; end;
error -> error ->
Message = "heredoc allows only zero or more whitespace characters followed by a new line after ", Message = "heredoc allows only whitespace characters followed by a new line after opening ",
{error, {Line, Column, io_lib:format(Message, []), [H, H, H]}} {error, {Line, Column + 3, io_lib:format(Message, []), [H, H, H]}}
end. end.
extract_heredoc_header("\r\n" ++ Rest) -> 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); tokenize_sigil_name(T, [S | NameAcc], Line, Column + 1, Scope, Tokens);
% With a lowercase letter and a non-empty NameAcc we return an error. % 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) -> 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: ", " sequence of uppercase letters only, got: ",
{error, Message, [$~] ++ lists:reverse(NameAcc) ++ Original}; {error, Message, [$~] ++ lists:reverse(NameAcc) ++ Original};
% We finished the letters, so the name is over. % 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) -> when ?is_quote(H) ->
case extract_heredoc_with_interpolation(Line, Column, Scope, ?is_downcase(S), T, H) of case extract_heredoc_with_interpolation(Line, Column, Scope, ?is_downcase(S), T, H) of
{ok, NewLine, NewColumn, Parts, Rest, NewScope} -> {ok, NewLine, NewColumn, Parts, Rest, NewScope} ->
{Final, Modifiers} = collect_modifiers(Rest, []),
Indentation = NewColumn - 4, Indentation = NewColumn - 4,
TokenColumn = Column - 1 - length(SigilName), add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, Parts, Rest, NewScope, Tokens, Indentation, <<H, H, H>>);
Token = {sigil, {Line, TokenColumn, nil}, SigilName, Parts, Modifiers, Indentation, <<H, H, H>>},
NewColumnWithModifiers = NewColumn + length(Modifiers),
tokenize(Final, NewLine, NewColumnWithModifiers, NewScope, [Token | Tokens]);
{error, Reason} -> {error, Reason} ->
error(Reason, [$~] ++ SigilName ++ Original, Scope, Tokens) 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) -> when ?is_sigil(H) ->
case elixir_interpolation:extract(Line, Column + 1, Scope, ?is_downcase(S), T, sigil_terminator(H)) of case elixir_interpolation:extract(Line, Column + 1, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
{NewLine, NewColumn, Parts, Rest, NewScope} -> {NewLine, NewColumn, Parts, Rest, NewScope} ->
{Final, Modifiers} = collect_modifiers(Rest, []),
Indentation = nil, Indentation = nil,
TokenColumn = Column - 1 - length(SigilName), add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, tokens_to_binary(Parts), Rest, NewScope, Tokens, Indentation, <<H>>);
Token = {sigil, {Line, TokenColumn, nil}, SigilName, tokens_to_binary(Parts), Modifiers, Indentation, <<H>>},
NewColumnWithModifiers = NewColumn + length(Modifiers),
tokenize(Final, NewLine, NewColumnWithModifiers, NewScope, [Token | Tokens]);
{error, Reason} -> {error, Reason} ->
Sigil = [$~, S, H], 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_sigil_contents([], _SigilName, Line, Column, Scope, Tokens) ->
tokenize([], 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 %% Fail early on invalid do syntax. For example, after
%% most keywords, after comma and so on. %% most keywords, after comma and so on.
tokenize_keyword_terminator(DoLine, DoColumn, do, [{identifier, {Line, Column, Meta}, Atom} | T]) -> 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 =:= 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 =:= power_op; OpType =:= concat_op; OpType =:= range_op; OpType =:= xor_op;
OpType =:= pipe_op; OpType =:= stab_op; OpType =:= when_op; OpType =:= assoc_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}; {Tokens, Terminators};
%%% or we traverse until the end. %%% or we traverse until the end.
prune_tokens([_ | Tokens], Opener, Terminators) -> prune_tokens([_ | Tokens], Opener, Terminators) ->
+66 -37
View File
@@ -428,11 +428,12 @@ defmodule CodeFragmentTest do
end end
test "column out of range" do 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 end
test "local_or_var" do test "local_or_var" do
for i <- 1..8 do for i <- 1..9 do
assert CF.surround_context("hello_wo", {1, i}) == %{ assert CF.surround_context("hello_wo", {1, i}) == %{
context: {:local_or_var, ~c"hello_wo"}, context: {:local_or_var, ~c"hello_wo"},
begin: {1, 1}, begin: {1, 1},
@@ -440,9 +441,9 @@ defmodule CodeFragmentTest do
} }
end 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}) == %{ assert CF.surround_context(" hello_wo", {1, i}) == %{
context: {:local_or_var, ~c"hello_wo"}, context: {:local_or_var, ~c"hello_wo"},
begin: {1, 2}, begin: {1, 2},
@@ -450,9 +451,9 @@ defmodule CodeFragmentTest do
} }
end 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}) == %{ assert CF.surround_context("hello!", {1, i}) == %{
context: {:local_or_var, ~c"hello!"}, context: {:local_or_var, ~c"hello!"},
begin: {1, 1}, begin: {1, 1},
@@ -460,9 +461,9 @@ defmodule CodeFragmentTest do
} }
end 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}) == %{ assert CF.surround_context("안녕_세상", {1, i}) == %{
context: {:local_or_var, ~c"안녕_세상"}, context: {:local_or_var, ~c"안녕_세상"},
begin: {1, 1}, begin: {1, 1},
@@ -470,7 +471,7 @@ defmodule CodeFragmentTest do
} }
end end
assert CF.surround_context("안녕_세상", {1, 6}) == :none assert CF.surround_context("안녕_세상 ", {1, 6}) == :none
# Keywords are not local or var # Keywords are not local or var
for keyword <- ~w(do end after catch else rescue fn true false nil)c do for keyword <- ~w(do end after catch else rescue fn true false nil)c do
@@ -484,8 +485,38 @@ defmodule CodeFragmentTest do
end end
end end
test "local call" do test "local + operator" do
for i <- 1..8 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}) == %{ assert CF.surround_context("hello_wo(", {1, i}) == %{
context: {:local_call, ~c"hello_wo"}, context: {:local_call, ~c"hello_wo"},
begin: {1, 1}, begin: {1, 1},
@@ -493,9 +524,9 @@ defmodule CodeFragmentTest do
} }
end 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}) == %{ assert CF.surround_context("hello_wo (", {1, i}) == %{
context: {:local_call, ~c"hello_wo"}, context: {:local_call, ~c"hello_wo"},
begin: {1, 1}, begin: {1, 1},
@@ -503,9 +534,10 @@ defmodule CodeFragmentTest do
} }
end 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}) == %{ assert CF.surround_context("hello!(", {1, i}) == %{
context: {:local_call, ~c"hello!"}, context: {:local_call, ~c"hello!"},
begin: {1, 1}, begin: {1, 1},
@@ -513,9 +545,9 @@ defmodule CodeFragmentTest do
} }
end 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}) == %{ assert CF.surround_context("안녕_세상(", {1, i}) == %{
context: {:local_call, ~c"안녕_세상"}, context: {:local_call, ~c"안녕_세상"},
begin: {1, 1}, begin: {1, 1},
@@ -523,7 +555,7 @@ defmodule CodeFragmentTest do
} }
end end
assert CF.surround_context("안녕_세상(", {1, 6}) == :none assert CF.surround_context("안녕_세상(", {1, 7}) == :none
end end
test "local arity" do test "local arity" do
@@ -651,7 +683,7 @@ defmodule CodeFragmentTest do
end end
test "alias" do test "alias" do
for i <- 1..8 do for i <- 1..9 do
assert CF.surround_context("HelloWor", {1, i}) == %{ assert CF.surround_context("HelloWor", {1, i}) == %{
context: {:alias, ~c"HelloWor"}, context: {:alias, ~c"HelloWor"},
begin: {1, 1}, begin: {1, 1},
@@ -659,9 +691,9 @@ defmodule CodeFragmentTest do
} }
end 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}) == %{ assert CF.surround_context(" HelloWor", {1, i}) == %{
context: {:alias, ~c"HelloWor"}, context: {:alias, ~c"HelloWor"},
begin: {1, 2}, begin: {1, 2},
@@ -669,9 +701,9 @@ defmodule CodeFragmentTest do
} }
end 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}) == %{ assert CF.surround_context("Hello.Wor", {1, i}) == %{
context: {:alias, ~c"Hello.Wor"}, context: {:alias, ~c"Hello.Wor"},
begin: {1, 1}, begin: {1, 1},
@@ -679,9 +711,9 @@ defmodule CodeFragmentTest do
} }
end 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}) == %{ assert CF.surround_context("Hello . Wor", {1, i}) == %{
context: {:alias, ~c"Hello.Wor"}, context: {:alias, ~c"Hello.Wor"},
begin: {1, 1}, begin: {1, 1},
@@ -689,9 +721,9 @@ defmodule CodeFragmentTest do
} }
end 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}) == %{ assert CF.surround_context("Foo . Bar . Baz", {1, i}) == %{
context: {:alias, ~c"Foo.Bar.Baz"}, context: {:alias, ~c"Foo.Bar.Baz"},
begin: {1, 1}, begin: {1, 1},
@@ -858,7 +890,7 @@ defmodule CodeFragmentTest do
end: {1, 15} end: {1, 15}
} }
for i <- 2..9 do for i <- 2..10 do
assert CF.surround_context("%HelloWor", {1, i}) == %{ assert CF.surround_context("%HelloWor", {1, i}) == %{
context: {:struct, ~c"HelloWor"}, context: {:struct, ~c"HelloWor"},
begin: {1, 1}, begin: {1, 1},
@@ -866,7 +898,7 @@ defmodule CodeFragmentTest do
} }
end end
assert CF.surround_context("%HelloWor", {1, 10}) == :none assert CF.surround_context("%HelloWor ", {1, 11}) == :none
# With dot # With dot
assert CF.surround_context("%Hello.Wor", {1, 1}) == %{ assert CF.surround_context("%Hello.Wor", {1, 1}) == %{
@@ -875,7 +907,7 @@ defmodule CodeFragmentTest do
end: {1, 11} end: {1, 11}
} }
for i <- 2..10 do for i <- 2..11 do
assert CF.surround_context("%Hello.Wor", {1, i}) == %{ assert CF.surround_context("%Hello.Wor", {1, i}) == %{
context: {:struct, ~c"Hello.Wor"}, context: {:struct, ~c"Hello.Wor"},
begin: {1, 1}, begin: {1, 1},
@@ -883,7 +915,7 @@ defmodule CodeFragmentTest do
} }
end end
assert CF.surround_context("%Hello.Wor", {1, 11}) == :none assert CF.surround_context("%Hello.Wor ", {1, 12}) == :none
# With spaces # With spaces
assert CF.surround_context("% Hello . Wor", {1, 1}) == %{ assert CF.surround_context("% Hello . Wor", {1, 1}) == %{
@@ -892,7 +924,7 @@ defmodule CodeFragmentTest do
end: {1, 14} end: {1, 14}
} }
for i <- 2..13 do for i <- 2..14 do
assert CF.surround_context("% Hello . Wor", {1, i}) == %{ assert CF.surround_context("% Hello . Wor", {1, i}) == %{
context: {:struct, ~c"Hello.Wor"}, context: {:struct, ~c"Hello.Wor"},
begin: {1, 1}, begin: {1, 1},
@@ -900,7 +932,7 @@ defmodule CodeFragmentTest do
} }
end end
assert CF.surround_context("% Hello . Wor", {1, 14}) == :none assert CF.surround_context("% Hello . Wor ", {1, 15}) == :none
end end
test "module attributes" do test "module attributes" do
@@ -1098,6 +1130,8 @@ defmodule CodeFragmentTest do
test "keeps operators" do test "keeps operators" do
assert cc2q("1 + 2") == s2q("1 + __cursor__()") assert cc2q("1 + 2") == s2q("1 + __cursor__()")
assert cc2q("&foo") == s2q("&__cursor__()")
assert cc2q("&foo/") == s2q("&foo/__cursor__()")
end end
test "keeps function calls without parens" do test "keeps function calls without parens" do
@@ -1200,11 +1234,6 @@ defmodule CodeFragmentTest do
assert cc2q("(fn x, y -> x + y end") == s2q("(__cursor__())") assert cc2q("(fn x, y -> x + y end") == s2q("(__cursor__())")
end end
test "removes captures" do
assert cc2q("[& &1") == s2q("[__cursor__()]")
assert cc2q("[&(&1") == s2q("[__cursor__()]")
end
test "removes closed terminators" do test "removes closed terminators" do
assert cc2q("foo([1, 2, 3]") == s2q("foo(__cursor__())") assert cc2q("foo([1, 2, 3]") == s2q("foo(__cursor__())")
assert cc2q("foo({1, 2, 3}") == s2q("foo(__cursor__())") assert cc2q("foo({1, 2, 3}") == s2q("foo(__cursor__())")
+10
View File
@@ -1102,6 +1102,16 @@ defmodule EnumTest do
[2, 1, 3] [2, 1, 3]
end 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 test "sort/1" do
assert Enum.sort([5, 3, 2, 4, 1]) == [1, 2, 3, 4, 5] assert Enum.sort([5, 3, 2, 4, 1]) == [1, 2, 3, 4, 5]
end end
@@ -381,4 +381,30 @@ defmodule Kernel.DocsTest do
{{:fuz, 0}, :none} {{:fuz, 0}, :none}
] = Enum.sort(function_docs) ] = Enum.sort(function_docs)
end 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 end
+31 -1
View File
@@ -182,6 +182,9 @@ defmodule Kernel.ParserTest do
assert Code.string_to_quoted(":there_is_no_such_atom", existing_atoms_only: true) == assert Code.string_to_quoted(":there_is_no_such_atom", existing_atoms_only: true) ==
{:error, {:error,
{[line: 1, column: 1], "unsafe atom does not exist: ", "there_is_no_such_atom"}} {[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 end
test "encodes atoms" do test "encodes atoms" do
@@ -228,6 +231,20 @@ defmodule Kernel.ParserTest do
) )
end 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 test "addresses ambiguities" do
encoder = fn string, _meta -> {:ok, {:atom, string}} end 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) Code.string_to_quoted("[do: 1, true: 2, end: 3]", static_atoms_encoder: encoder)
end 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 test "returns errors on long atoms even when using static_atoms_encoder" do
atom = String.duplicate("a", 256) 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"}} = 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) 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 end
test "may return tuples" do test "may return tuples" do
@@ -538,7 +568,7 @@ defmodule Kernel.ParserTest do
describe "syntax errors" do describe "syntax errors" do
test "invalid heredoc start" do test "invalid heredoc start" do
assert_syntax_error( 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\"\"\"" ~c"\"\"\"bar\n\"\"\""
) )
end end
+15 -1
View File
@@ -831,11 +831,25 @@ defmodule KernelTest do
end end
test "matching attribute" do 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 defmodule MatchAttributeInModule do
@foo = 42 @foo = 42
end end
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
end end
+10 -10
View File
@@ -261,22 +261,22 @@ vc_merge_conflict_test() ->
tokenize_error("<<<<<<< HEAD\n[1, 2, 3]"). tokenize_error("<<<<<<< HEAD\n[1, 2, 3]").
sigil_terminator_test() -> sigil_terminator_test() ->
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"/">>}] = tokenize("~r/foo/"), [{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"/">>}] = tokenize("~r/foo/"),
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"[">>}] = tokenize("~r[foo]"), [{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"[">>}] = tokenize("~r[foo]"),
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"\"">>}] = tokenize("~r\"foo\""), [{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"\"">>}] = tokenize("~r\"foo\""),
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"/">>}, [{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"/">>},
{comp_op, {1, 9, nil}, '=='}, {comp_op, {1, 9, nil}, '=='},
{identifier, {1, 12, _}, bar}] = tokenize("~r/foo/ == bar"), {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}, '=='}, {comp_op, {1, 11, nil}, '=='},
{identifier, {1, 14, _}, bar}] = tokenize("~r/foo/iu == bar"), {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_heredoc_test() ->
[{sigil, {1, 1, nil}, "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}, "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}, "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 \"\"\""),
[{sigil, {1, 1, nil}, "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() -> invalid_sigil_delimiter_test() ->
{1, 1, "invalid sigil delimiter: ", Message} = tokenize_error("~s\\"), {1, 1, "invalid sigil delimiter: ", Message} = tokenize_error("~s\\"),
+16 -9
View File
@@ -71,11 +71,11 @@ defmodule ExUnit.CaptureServer do
refs = Map.put(config.log_captures, ref, true) refs = Map.put(config.log_captures, ref, true)
{level, opts} = Keyword.pop(opts, :level) {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 if map_size(refs) == 1 do
formatter = Logger.default_formatter(opts) :ok = :logger.add_handler(@name, __MODULE__, %{})
:ok = :logger.add_handler(@name, __MODULE__, %{formatter: formatter})
status = status =
with {:ok, config} <- :logger.get_handler_config(:default), with {:ok, config} <- :logger.get_handler_config(:default),
@@ -226,13 +226,20 @@ defmodule ExUnit.CaptureServer do
## :logger handler callback. ## :logger handler callback.
def log(event, %{} = config) do def log(event, _config) do
%{formatter: {formatter_mod, formatter_config}} = config for {_ref, string_io, level, formatter_mod, formatter_config} <- :ets.tab2list(@ets),
chardata = formatter_mod.format(event, formatter_config)
for [string_io, level] <- :ets.match(@ets, {:_, :"$1", :"$2"}),
:logger.compare_levels(event.level, level) in [:gt, :eq] do :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 end
:ok
end end
end end
+3 -3
View File
@@ -167,6 +167,9 @@ defmodule ExUnit.Case do
* `:doctest_data` - additional metadata about doctests (if a doctest) * `: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: The following tags customize how tests behave:
* `:capture_log` - see the "Log Capture" section below * `: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 * `: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 ## Filters
Tags can also be used to identify specific tests, which can then Tags can also be used to identify specific tests, which can then
+27 -41
View File
@@ -382,9 +382,15 @@ defmodule ExUnit.DocTest do
last_expr = Macro.to_string(last_expr(expr_ast)) last_expr = Macro.to_string(last_expr(expr_ast))
quote do 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__( ExUnit.DocTest.__test__(
unquote(expr_ast), value,
unquote(expected_ast), expected_value,
unquote(doctest), unquote(doctest),
unquote(last_expr), unquote(last_expr),
unquote(expected), unquote(expected),
@@ -624,57 +630,37 @@ defmodule ExUnit.DocTest do
end end
end end
defp adjust_indent(kind, [line | rest], line_no, adjusted_lines, indent, module) defp adjust_indent(kind, [line | rest], line_no, adjusted_lines, indent, module) do
when kind in [:prompt, :after_prompt] do
stripped_line = strip_indent(line, indent) stripped_line = strip_indent(line, indent)
trimmed_line = String.trim_leading(line)
case String.trim_leading(line) do if kind != :code and trimmed_line != stripped_line do
"" -> n_spaces = if indent == 1, do: "#{indent} space", else: "#{indent} spaces"
:ok
^stripped_line -> raise Error,
:ok 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, \
n_spaces = if indent == 1, do: "#{indent} space", else: "#{indent} spaces" make sure the result is indented at the beginning of `iex>`, which \
in this case is exactly #{n_spaces}.
raise Error, If instead you have an `iex>` expression that spans over multiple lines, \
line: line_no, please make sure that each line after the first one begins with `...>`.
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 `...>`.
"""
end 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 cond do
stripped_line == "" or String.starts_with?(stripped_line, @fences) -> stripped_line == "" or String.starts_with?(stripped_line, @fences) ->
adjusted_lines = [{"", line_no} | adjusted_lines] adjusted_lines = [{"", line_no} | adjusted_lines]
adjust_indent(:text, rest, line_no + 1, adjusted_lines, 0, module) adjust_indent(:text, rest, line_no + 1, adjusted_lines, 0, module)
String.starts_with?(String.trim_leading(stripped_line), @iex_prompt) -> kind == :prompt or String.starts_with?(trimmed_line, @iex_prompt) or
adjust_indent(:prompt, [line | rest], line_no, adjusted_lines, indent, module) (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 -> true ->
adjusted_lines = [{stripped_line, line_no} | adjusted_lines] adjusted_lines = [{stripped_line, line_no} | adjusted_lines]
+2 -1
View File
@@ -293,8 +293,9 @@ defmodule ExUnit.Runner do
{test_module, invalid_tests, []} {test_module, invalid_tests, []}
{:DOWN, ^module_ref, :process, ^module_pid, error} -> {: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 | state: failed({:EXIT, module_pid}, error, [])}
{test_module, [], []} {test_module, invalid_tests, []}
end end
timeout = get_timeout(config, %{}) timeout = get_timeout(config, %{})
@@ -124,6 +124,22 @@ defmodule ExUnit.CallbacksTest do
"** (MatchError) no match of right hand side value: :error" "** (MatchError) no match of right hand side value: :error"
end 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 test "doesn't choke on dead supervisor" do
defmodule StartSupervisedErrorTest do defmodule StartSupervisedErrorTest do
use ExUnit.Case use ExUnit.Case
@@ -114,6 +114,19 @@ defmodule ExUnit.CaptureLogTest do
assert log == "id=123 | hello" assert log == "id=123 | hello"
end 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 end
defp wait_capture_removal() do defp wait_capture_removal() do
+12 -2
View File
@@ -317,8 +317,7 @@ defmodule ExUnit.DocTestTest.FencedHeredocs do
``` ```
``` ```
iex> 1 + 2 iex> 3 = 1 + 2
3
``` ```
``` ```
@@ -444,6 +443,16 @@ defmodule ExUnit.DocTestTest.Haiku do
end end
|> ExUnit.BeamHelpers.write_beam() |> 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 defmodule ExUnit.DocTestTest.PatternMatching do
def starting_line, do: __ENV__.line + 2 def starting_line, do: __ENV__.line + 2
@@ -507,6 +516,7 @@ defmodule ExUnit.DocTestTest do
doctest ExUnit.DocTestTest.IndentationHeredocs doctest ExUnit.DocTestTest.IndentationHeredocs
doctest ExUnit.DocTestTest.FencedHeredocs doctest ExUnit.DocTestTest.FencedHeredocs
doctest ExUnit.DocTestTest.Haiku doctest ExUnit.DocTestTest.Haiku
doctest ExUnit.DocTestTest.VariableInExpectation
import ExUnit.CaptureIO import ExUnit.CaptureIO
+11 -3
View File
@@ -536,6 +536,9 @@ defmodule IEx do
@doc """ @doc """
Returns `true` if IEx was started, `false` otherwise. 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() @spec started?() :: boolean()
def started? do def started? do
@@ -857,6 +860,8 @@ defmodule IEx do
# This is a callback invoked by Erlang shell utilities # This is a callback invoked by Erlang shell utilities
# when someone presses Ctrl+G and adds `s 'Elixir.IEx'`. # 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 @doc false
def start(opts \\ [], mfa \\ {IEx, :dont_display_result, []}) do def start(opts \\ [], mfa \\ {IEx, :dont_display_result, []}) do
# TODO: Keep only this branch, delete optional args and mfa, # TODO: Keep only this branch, delete optional args and mfa,
@@ -869,10 +874,13 @@ defmodule IEx do
end) end)
else else
spawn(fn -> spawn(fn ->
{:ok, _} = Application.ensure_all_started(:elixir) case :init.notify_when_started(self()) do
System.wait_until_booted() :started -> :ok
:ok = :io.setopts(binary: true, encoding: :unicode) _ -> :init.wait_until_started()
end
{:ok, _} = Application.ensure_all_started(:iex) {:ok, _} = Application.ensure_all_started(:iex)
:ok = :io.setopts(binary: true, encoding: :unicode)
_ = for fun <- Enum.reverse(after_spawn()), do: fun.() _ = for fun <- Enum.reverse(after_spawn()), do: fun.()
IEx.Server.run_from_shell(opts, mfa) IEx.Server.run_from_shell(opts, mfa)
end) end)
+4 -8
View File
@@ -6,7 +6,7 @@ defmodule IEx.CLI do
def deprecated do def deprecated do
if tty_works?() 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 else
if get_remsh(:init.get_plain_arguments()) do if get_remsh(:init.get_plain_arguments()) do
IO.puts( IO.puts(
@@ -20,14 +20,10 @@ defmodule IEx.CLI do
# IEx.Broker is capable of considering all groups under user_drv but # IEx.Broker is capable of considering all groups under user_drv but
# when we use :user.start(), we need to explicitly register it instead. # when we use :user.start(), we need to explicitly register it instead.
# If we don't register, pry doesn't work. # If we don't register, pry doesn't work.
IEx.start([register: true] ++ options()) IEx.start([register: true] ++ options(), {:elixir, :start_cli, []})
end end
end end
def prompt(_n) do
[]
end
# Check if tty works. If it does not, we fall back to the # Check if tty works. If it does not, we fall back to the
# simple/dumb terminal. This is starting the linked in # simple/dumb terminal. This is starting the linked in
# driver twice, it would be nice and appropriate if we had # driver twice, it would be nice and appropriate if we had
@@ -41,7 +37,7 @@ defmodule IEx.CLI do
end end
end end
defp old_tty_args do defp tty_args do
if remote = get_remsh(:init.get_plain_arguments()) do if remote = get_remsh(:init.get_plain_arguments()) do
remote = List.to_atom(append_hostname(remote)) remote = List.to_atom(append_hostname(remote))
@@ -77,7 +73,7 @@ defmodule IEx.CLI do
end end
defp local_start_mfa do defp local_start_mfa do
{IEx, :start, [options()]} {IEx, :start, [options(), {:elixir, :start_cli, []}]}
end end
def remote_start(parent, ref) do def remote_start(parent, ref) do
+1 -1
View File
@@ -99,7 +99,7 @@ defmodule IEx.Helpers do
reenable_tasks(config) reenable_tasks(config)
force? = Keyword.get(options, :force, false) 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 args = if force?, do: ["--force" | args], else: args
{result, _} = Mix.Task.run("compile", args) {result, _} = Mix.Task.run("compile", args)
+2
View File
@@ -689,6 +689,8 @@ defmodule IEx.Pry do
[asts_string, :faint, " #=> ", :reset, inspect(value, options), "\n\n"] [asts_string, :faint, " #=> ", :reset, inspect(value, options), "\n\n"]
|> IO.ANSI.format() |> IO.ANSI.format()
|> IO.write() |> IO.write()
value
end end
defp chunk_pipeline_asts_by_line(asts, %Macro.Env{line: env_line}) do defp chunk_pipeline_asts_by_line(asts, %Macro.Env{line: env_line}) do
+27 -20
View File
@@ -210,7 +210,7 @@ defmodule Logger do
metadata: [:error_code, :file] metadata: [:error_code, :file]
Or to configure default handler, for instance, to log into a file with 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 :logger, :default_handler,
config: [ config: [
@@ -218,9 +218,18 @@ defmodule Logger do
filesync_repeat_interval: 5000, filesync_repeat_interval: 5000,
file_check: 5000, file_check: 5000,
max_no_bytes: 10_000_000, 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} > #### Keywords or maps {: .tip}
> >
> While Erlang's logger expects `:config` to be a map, Elixir's Logger > 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, > When reading the handler configuration using Erlang's APIs,
> the configuration will always be read (and written) as a map. > 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 ### Compile configuration
The following configuration must be set via config files (such as 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 default handler, but you can use Erlang's [`:logger`](`:logger`) module
to add other handlers too. to add other handlers too.
Erlang/OTP handlers must be listed under your own application. For example, Erlang/OTP handlers must be listed under your own application.
to setup an additional handler that writes to disk: For example, to setup an additional handler, so you write to
console and file:
config :my_app, :logger, [ config :my_app, :logger, [
{:handler, :disk_log, :logger_disk_log_h, %{ {:handler, :file_log, :logger_std_h, %{
config: %{ config: %{
file: 'system.log', file: ~c"system.log",
filesync_repeat_interval: 5000, filesync_repeat_interval: 5000,
file_check: 5000,
max_no_bytes: 10_000_000, max_no_bytes: 10_000_000,
max_no_files: 5 max_no_files: 5,
compress_on_rotate: true
}, },
formatter: Logger.Formatter.new() formatter: Logger.Formatter.new()
}} }}
@@ -355,9 +359,12 @@ defmodule Logger do
flexibility but they should avoid performing any long running action in flexibility but they should avoid performing any long running action in
such handlers, as it may slow down the action being executed considerably. 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, 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 so it is your responsibility to implement it.
[`:logger_backends`](https://github.com/elixir-lang/logger_backends)
project. 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 ## Backends and backwards compatibility
@@ -1002,7 +1009,7 @@ defmodule Logger do
compile_time_purge_matching?(compile_level, compile_metadata) -> compile_time_purge_matching?(compile_level, compile_metadata) ->
no_log(data, quoted_metadata) no_log(data, quoted_metadata)
Application.fetch_env!(:logger, :always_evaluate_messages) -> Application.get_env(:logger, :always_evaluate_messages, false) ->
quote do quote do
data = Logger.__evaluate_log__(unquote(data)) data = Logger.__evaluate_log__(unquote(data))
metadata = unquote(quoted_metadata) metadata = unquote(quoted_metadata)
@@ -1110,7 +1117,7 @@ defmodule Logger do
end end
defp no_log(data, metadata) do 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 quote do
Logger.__evaluate_log__(unquote(data)) Logger.__evaluate_log__(unquote(data))
unquote(metadata) unquote(metadata)
+3 -3
View File
@@ -203,7 +203,7 @@ defmodule Logger.Formatter do
def format(_event, _config) do def format(_event, _config) do
raise "invalid configuration for Logger.Formatter. " <> 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 end
defp compute_meta(:module, %{mfa: {mod, _, _}}), do: mod defp compute_meta(:module, %{mfa: {mod, _, _}}), do: mod
@@ -498,8 +498,6 @@ defmodule Logger.Formatter do
rest rest
end end
defp metadata(:file, file) when is_list(file), do: file
defp metadata(:domain, [head | tail]) when is_atom(head) do defp metadata(:domain, [head | tail]) when is_atom(head) do
Enum.map_intersperse([head | tail], ?., &Atom.to_string/1) Enum.map_intersperse([head | tail], ?., &Atom.to_string/1)
end end
@@ -514,6 +512,8 @@ defmodule Logger.Formatter do
Exception.format_mfa(mod, fun, arity) Exception.format_mfa(mod, fun, arity)
end 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(_, list) when is_list(list), do: nil
defp metadata(_, other) do defp metadata(_, other) do
+23 -1
View File
@@ -54,6 +54,28 @@ defmodule Logger.FormatterTest do
assert format_time(time) == ["12", ?:, "30", ?:, "10", ?., [?0, "10"]] assert format_time(time) == ["12", ?:, "30", ?:, "10", ?., [?0, "10"]]
end 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 describe "compile + format" do
defmodule CompileMod do defmodule CompileMod do
def format(_level, _msg, _ts, _md) do def format(_level, _msg, _ts, _md) do
@@ -66,7 +88,7 @@ defmodule Logger.FormatterTest do
["\n", :time, " ", :metadata, "[", :level, "] ", :message, "\n"] ["\n", :time, " ", :metadata, "[", :level, "] ", :message, "\n"]
end end
test "compile with str" do test "compile with string" do
assert compile("$level $time $date $metadata $message $node") == assert compile("$level $time $date $metadata $message $node") ==
Enum.intersperse([:level, :time, :date, :metadata, :message, :node], " ") Enum.intersperse([:level, :time, :date, :metadata, :message, :node], " ")
+6 -2
View File
@@ -136,8 +136,12 @@ defmodule Mix.AppLoader do
end end
# We have processed all apps. # 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 end
defp app_children(app) do defp app_children(app) do
+65 -46
View File
@@ -56,7 +56,7 @@ defmodule Mix.Compilers.Elixir do
[] []
end 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. # If mix.exs has changed, recompile anything that calls Mix.Project.
stale = stale =
@@ -205,6 +205,10 @@ defmodule Mix.Compilers.Elixir do
Code.purge_compiler_modules() Code.purge_compiler_modules()
end end
else else
# Prepend ourselves, even if we did no work, as we may have defined
# future compilers.
Code.prepend_path(dest)
# We need to return ok if deps_changed? or stale_modules changed, # We need to return ok if deps_changed? or stale_modules changed,
# even if no code was compiled, because we need to propagate the changed # even if no code was compiled, because we need to propagate the changed
# status to compile.protocols. This will be the case whenever: # status to compile.protocols. This will be the case whenever:
@@ -393,7 +397,7 @@ defmodule Mix.Compilers.Elixir do
Enum.any?(modules, &Map.has_key?(modules_to_recompile, &1)) or Enum.any?(modules, &Map.has_key?(modules_to_recompile, &1)) or
Enum.any?(external, &stale_external?(&1, modified, sources_stats)) or Enum.any?(external, &stale_external?(&1, modified, sources_stats)) or
(last_mtime > modified and (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 do: source
changed = new_paths ++ changed changed = new_paths ++ changed
@@ -423,7 +427,7 @@ defmodule Mix.Compilers.Elixir do
defp stale_external?({external, digest}, modified, sources_stats) do defp stale_external?({external, digest}, modified, sources_stats) do
case sources_stats do case sources_stats do
%{^external => {0, 0}} -> digest != nil %{^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
end end
@@ -437,8 +441,11 @@ defmodule Mix.Compilers.Elixir do
end) end)
end end
defp digest_file!(file) do defp digest_changed?(file, digest) do
file |> File.read!() |> digest_contents() case File.read(file) do
{:ok, binary} -> digest != digest_contents(binary)
{:error, _} -> true
end
end end
defp digest_contents(contents) do defp digest_contents(contents) do
@@ -565,53 +572,65 @@ defmodule Mix.Compilers.Elixir do
# as any export from a dependency needs to be recompiled. # as any export from a dependency needs to be recompiled.
stale_modules = Map.from_keys(stale_modules, true) stale_modules = Map.from_keys(stale_modules, true)
for %{opts: opts} <- local_deps, {stale_modules, stale_exports, new_exports} =
manifest = Path.join([opts[:build], ".mix", base]), for %{opts: opts} <- local_deps,
Mix.Utils.last_modified(manifest) > modified, manifest = Path.join([opts[:build], ".mix", base]),
reduce: {stale_modules, stale_modules, old_exports} do Mix.Utils.last_modified(manifest) > modified,
{modules, exports, new_exports} -> reduce: {stale_modules, stale_modules, []} do
{manifest_modules, manifest_sources} = read_manifest(manifest) {modules, exports, new_exports} ->
{manifest_modules, manifest_sources} = read_manifest(manifest)
dep_modules = dep_modules =
for module(module: module, timestamp: timestamp) <- manifest_modules, for module(module: module, timestamp: timestamp) <- manifest_modules,
timestamp > modified, timestamp > modified,
do: module do: module
# If any module has a compile time dependency on a changed module # If any module has a compile time dependency on a changed module
# within the dependency, they will be recompiled. However, export # within the dependency, they will be recompiled. However, export
# and runtime dependencies won't have recompiled so we need to # and runtime dependencies won't have recompiled so we need to
# propagate them to the parent app. # propagate them to the parent app.
{dep_modules, _, _} = {dep_modules, _, _} =
fixpoint_runtime_modules(manifest_sources, Map.from_keys(dep_modules, true)) fixpoint_runtime_modules(manifest_sources, Map.from_keys(dep_modules, true))
# Update exports # Update exports
{exports, new_exports} = {exports, new_exports} =
for {module, _} <- dep_modules, reduce: {exports, new_exports} do for {module, _} <- dep_modules, reduce: {exports, new_exports} do
{exports, new_exports} -> {exports, new_exports} ->
export = exports_md5(module, false) export = exports_md5(module, false)
# If the exports are the same, then the API did not change, # If the exports are the same, then the API did not change,
# so we do not mark the export as stale. Note this has to # so we do not mark the export as stale. Note this has to
# be very conservative. If the module is not loaded or if # be very conservative. If the module is not loaded or if
# the exports were not there, we need to consider it a stale # the exports were not there, we need to consider it a stale
# export. # export.
exports = exports =
if export && old_exports[module] == export, if export && old_exports[module] == export,
do: exports, do: exports,
else: Map.put(exports, module, true) else: Map.put(exports, module, true)
# In any case, we always store it as the most update export # Then we store the new export if any
# that we have, otherwise we delete it. new_exports =
new_exports = if export,
if export, do: [{module, export} | new_exports],
do: Map.put(new_exports, module, export), else: new_exports
else: Map.delete(new_exports, module)
{exports, new_exports} {exports, new_exports}
end end
{Map.merge(modules, dep_modules), exports, new_exports} {Map.merge(modules, dep_modules), exports, new_exports}
end end
# Any dependency in old export but not in new export
# was removed so we need to mark them as stale too.
new_exports = Map.new(new_exports)
removed =
for {module, _} <- old_exports,
not is_map_key(new_exports, module),
do: {module, true},
into: %{}
{Map.merge(stale_modules, removed), Map.merge(stale_exports, removed), new_exports}
end end
defp fixpoint_runtime_modules(sources, modules) when modules != %{} do defp fixpoint_runtime_modules(sources, modules) when modules != %{} do
@@ -1081,7 +1100,7 @@ defmodule Mix.Compilers.Elixir do
source( source(
source, source,
# We preserve the digest if the file is recompiled but not changed # 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, compile_references: compile_references,
export_references: export_references, export_references: export_references,
runtime_references: runtime_references, runtime_references: runtime_references,
+26 -21
View File
@@ -4,13 +4,13 @@ defmodule Mix.Compilers.Erlang do
@manifest_vsn 1 @manifest_vsn 1
@doc """ @doc """
Compiles the files in `mappings` with given extensions into Compiles the given `mappings`.
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.
`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 ## Options
@@ -18,6 +18,9 @@ defmodule Mix.Compilers.Erlang do
* `:parallel` - a mapset of files to compile in parallel * `: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 ## Examples
For example, a simple compiler for Lisp Flavored Erlang 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 def compile(manifest, mappings, src_ext, dest_ext, opts, callback) when is_list(opts) do
force = opts[:force] force = opts[:force]
files = entries =
for {src, dest} <- mappings, 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 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 end
@doc """ @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 A `manifest` file and a `callback` to be invoked for each stale
must be given. A src/dest pair where destination is `nil` is considered src/dest pair must also be given.
to be up to date and won't be (re-)compiled.
## Options ## Options
@@ -78,8 +84,8 @@ defmodule Mix.Compilers.Erlang do
* `:parallel` - a mapset of files to compile in parallel * `:parallel` - a mapset of files to compile in parallel
""" """
def compile(manifest, mappings, opts \\ [], callback) do def compile(manifest, entries, opts \\ [], callback) do
compile(manifest, mappings, :erl, opts, callback) compile(manifest, entries, :erl, opts, callback)
end end
defp compile(manifest, mappings, ext, opts, callback) do defp compile(manifest, mappings, ext, opts, callback) do
@@ -102,7 +108,7 @@ defmodule Mix.Compilers.Erlang do
# Clear stale and removed files from manifest # Clear stale and removed files from manifest
entries = entries =
Enum.reject(entries, fn {dest, _warnings} -> 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) end)
if Keyword.get(opts, :all_warnings, true), do: show_warnings(entries) if Keyword.get(opts, :all_warnings, true), do: show_warnings(entries)
@@ -156,9 +162,8 @@ defmodule Mix.Compilers.Erlang do
end end
end end
@doc """ # TODO: Deprecate this in favor of `Mix.ensure_application!/1` in Elixir v1.19.
Ensures the native OTP application is available. @doc false
"""
def ensure_application!(app, _input) do def ensure_application!(app, _input) do
Mix.ensure_application!(app) Mix.ensure_application!(app)
{:ok, _} = Application.ensure_all_started(app) {:ok, _} = Application.ensure_all_started(app)
@@ -202,7 +207,7 @@ defmodule Mix.Compilers.Erlang do
manifest |> read_manifest() |> Enum.map(&elem(&1, 0)) manifest |> read_manifest() |> Enum.map(&elem(&1, 0))
end 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)) files = Mix.Utils.extract_files(List.wrap(src_dir), List.wrap(src_ext))
for file <- files do for file <- files do
+34 -29
View File
@@ -72,8 +72,31 @@ defmodule Mix.Dep.Converger do
""" """
def converge(acc, lock, opts, callback) do def converge(acc, lock, opts, callback) do
{deps, acc, lock} = all(acc, lock, opts, callback) {deps, acc, lock} = all(acc, lock, opts, callback)
if remote = Mix.RemoteConverger.get(), do: remote.post_converge if remote = Mix.RemoteConverger.get(), do: remote.post_converge()
{topological_sort(deps), acc, lock}
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 end
defp all(acc, lock, opts, callback) do 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) all(t, [dep | acc], upper, breadths, optional, rest, lock, state)
:nomatch -> :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 case state.cache.(dep) do
{:loaded, cached_dep} -> {:loaded, cached_dep} ->
{cached_dep, nil, rest, lock} {cached_dep, rest, lock}
{:unloaded, dep, children} -> {:unloaded, dep, children} ->
{dep, rest, lock} = state.callback.(put_lock(dep, lock), rest, lock) {dep, rest, lock} = state.callback.(put_lock(dep, lock), rest, lock)
Mix.Dep.Loader.with_system_env(dep, fn -> dep =
# After we invoke the callback (which may actually check out the Mix.Dep.Loader.with_system_env(dep, fn ->
# dependency), we load the dependency including its latest info Mix.Dep.Loader.load(dep, children, state.locked?)
# and children information. end)
{dep, app_properties} = Mix.Dep.Loader.load(dep, children, state.locked?)
{dep, app_properties, rest, lock} {dep, rest, lock}
end)
end end
# Something that we previously ruled out as an optional dependency is # 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]) split_non_fulfilled_optional(deps, Enum.map(acc, & &1.app), opts[:from_umbrella])
new_breadths = Enum.map(deps, & &1.app) ++ breadths new_breadths = Enum.map(deps, & &1.app) ++ breadths
res = all(deps, acc, breadths, new_breadths, discarded ++ optional, rest, lock, state) 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
end end
end end
+5 -5
View File
@@ -383,21 +383,21 @@ defmodule Mix.Dep.Loader do
cond do cond do
not ok?(dep) -> not ok?(dep) ->
{dep, nil} dep
recently_fetched?(dep) -> recently_fetched?(dep) ->
{%{dep | status: :compile}, nil} %{dep | status: :compile}
opts_app == false -> opts_app == false ->
{dep, nil} dep
true -> true ->
path = if is_binary(opts_app), do: opts_app, else: "ebin/#{app}.app" path = if is_binary(opts_app), do: opts_app, else: "ebin/#{app}.app"
path = Path.expand(path, opts[:build]) path = Path.expand(path, opts[:build])
case app_status(path, app, req) do case app_status(path, app, req) do
{:ok, vsn, app} -> {%{dep | status: {:ok, vsn}}, app} {:ok, vsn, app} -> %{dep | status: {:ok, vsn}, opts: [app_properties: app] ++ opts}
status -> {%{dep | status: status}, nil} status -> %{dep | status: status}
end end
end end
end end
+1 -1
View File
@@ -59,7 +59,7 @@ defmodule Mix.Dep.Umbrella do
apps = Enum.map(deps, & &1.app) apps = Enum.map(deps, & &1.app)
Enum.map(deps, fn umbrella_dep -> 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 = deps =
Enum.filter(umbrella_dep.deps, fn dep -> Enum.filter(umbrella_dep.deps, fn dep ->
+2 -1
View File
@@ -285,7 +285,8 @@ defmodule Mix.Release do
|> Enum.map(&{&1, new_mode}) |> Enum.map(&{&1, new_mode})
seen = put_in(seen[app][:mode], 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 -> true ->
seen seen
+1 -1
View File
@@ -31,7 +31,7 @@ defmodule Mix.Tasks.Compile.All do
{loaded_paths, loaded_modules} = {loaded_paths, loaded_modules} =
Mix.AppLoader.load_apps(apps, deps, config, {[], []}, fn {app, path}, {paths, mods} -> Mix.AppLoader.load_apps(apps, deps, config, {[], []}, fn {app, path}, {paths, mods} ->
paths = if path, do: [path | paths], else: paths 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} {paths, mods}
end) end)
+2 -2
View File
@@ -146,9 +146,9 @@ defmodule Mix.Tasks.Compile do
config = Mix.Project.config() config = Mix.Project.config()
# If we are in an umbrella project, now load paths from all children. # 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 = loaded_paths =
Mix.Project.apps_paths(config) apps_paths
|> Map.keys() |> Map.keys()
|> Mix.AppLoader.load_apps(Mix.Dep.cached(), config, [], fn |> Mix.AppLoader.load_apps(Mix.Dep.cached(), config, [], fn
{_app, path}, acc -> if path, do: [path | acc], else: acc {_app, path}, acc -> if path, do: [path | acc], else: acc
+6 -2
View File
@@ -53,15 +53,19 @@ defmodule Mix.Tasks.Compile.Leex do
Mix.raise(":leex_options should be a list of options, got: #{inspect(options)}") Mix.raise(":leex_options should be a list of options, got: #{inspect(options)}")
end end
opts = [parallel: true] ++ opts opts = [parallel: true, preload: &preload/0] ++ opts
Erlang.compile(manifest(), mappings, :xrl, :erl, opts, fn input, output -> 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)] options = options ++ @forced_opts ++ [scannerfile: Erlang.to_erl_file(output)]
:leex.file(Erlang.to_erl_file(input), options) :leex.file(Erlang.to_erl_file(input), options)
end) end)
end end
defp preload do
Mix.ensure_application!(:parsetools)
{:ok, _} = Application.ensure_all_started(:parsetools)
end
@impl true @impl true
def manifests, do: [manifest()] def manifests, do: [manifest()]
defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest) defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)
+6 -2
View File
@@ -53,15 +53,19 @@ defmodule Mix.Tasks.Compile.Yecc do
Mix.raise(":yecc_options should be a list of options, got: #{inspect(options)}") Mix.raise(":yecc_options should be a list of options, got: #{inspect(options)}")
end end
opts = [parallel: true] ++ opts opts = [parallel: true, preload: &preload/0] ++ opts
Erlang.compile(manifest(), mappings, :yrl, :erl, opts, fn input, output -> 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)] options = options ++ @forced_opts ++ [parserfile: Erlang.to_erl_file(output)]
:yecc.file(Erlang.to_erl_file(input), options) :yecc.file(Erlang.to_erl_file(input), options)
end) end)
end end
defp preload do
Mix.ensure_application!(:parsetools)
{:ok, _} = Application.ensure_all_started(:parsetools)
end
@impl true @impl true
def manifests, do: [manifest()] def manifests, do: [manifest()]
defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest) defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)
+10 -1
View File
@@ -1425,7 +1425,16 @@ defmodule Mix.Tasks.Release do
defp cli_for(:windows, release) do defp cli_for(:windows, release) do
{"env.bat", &env_bat_template(release: &1), {"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 end
defp elixir_cli_for(:unix, release) do defp elixir_cli_for(:unix, release) do
+31 -14
View File
@@ -38,8 +38,9 @@ defmodule Mix.Tasks.Xref do
$ mix xref trace lib/my_app/router.ex --label compile $ mix xref trace lib/my_app/router.ex --label compile
If you have an umbrella application, we also recommend using the If you have an umbrella application, we also recommend using the
`--include-siblings` flag to see the dependencies on other `--include-siblings` flag to see the dependencies from sibling
umbrella applications. applications. The `trace` command is not currently supported at the
umbrella root.
### Example ### Example
@@ -222,6 +223,12 @@ defmodule Mix.Tasks.Xref do
lib/a.ex lib/a.ex
└── lib/b.ex (compile) └── 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 ### Dependency types
Elixir tracks three types of dependencies between modules: compile, Elixir tracks three types of dependencies between modules: compile,
@@ -289,12 +296,6 @@ defmodule Mix.Tasks.Xref do
@impl true @impl true
def run(args) do 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.run("compile", args)
Mix.Task.reenable("xref") Mix.Task.reenable("xref")
@@ -302,9 +303,11 @@ defmodule Mix.Tasks.Xref do
case args do case args do
["callers", module] -> ["callers", module] ->
no_umbrella!("callers")
handle_callers(module, opts) handle_callers(module, opts)
["trace", file] -> ["trace", file] ->
no_umbrella!("trace")
handle_trace(file, opts) handle_trace(file, opts)
["graph"] -> ["graph"] ->
@@ -327,6 +330,14 @@ defmodule Mix.Tasks.Xref do
end end
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 """ @doc """
Returns a list of information of all the runtime function calls in the project. 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 defp manifests(opts) do
siblings = siblings =
if opts[:include_siblings] do cond do
for %{scm: Mix.SCM.Path, opts: opts} <- Mix.Dep.cached(), Mix.Project.umbrella?() ->
opts[:in_umbrella], for %{opts: opts} <- Mix.Dep.Umbrella.cached(),
do: Path.join([opts[:build], ".mix", @manifest]) do: Path.join([opts[:build], ".mix", @manifest])
else
[] 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 end
[Path.join(Mix.Project.manifest_path(), @manifest) | siblings] [Path.join(Mix.Project.manifest_path(), @manifest) | siblings]
+65 -27
View File
@@ -337,17 +337,14 @@ defmodule Mix.ReleaseTest do
test "configures other applications in cascade", context do test "configures other applications in cascade", context do
in_tmp(context.test, fn -> in_tmp(context.test, fn ->
app = write_app!(
"my_sample_mode/ebin/my_sample_mode.app",
{:application, :my_sample_mode, {:application, :my_sample_mode,
applications: [:kernel, :stdlib, :elixir, :runtime_tools, :compiler], applications: [:kernel, :stdlib, :elixir, :runtime_tools, :compiler],
description: ~c"my_sample_mode", description: ~c"my_sample_mode",
modules: [], modules: [],
vsn: ~c"1.0.0"} 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] apps = [my_sample_mode: :temporary]
release = release(applications: apps) release = release(applications: apps)
@@ -802,18 +799,15 @@ defmodule Mix.ReleaseTest do
describe "included applications" do describe "included applications" do
test "are included in the release", context do test "are included in the release", context do
in_tmp(context.test, fn -> in_tmp(context.test, fn ->
app = write_app!(
"my_sample1/ebin/my_sample1.app",
{:application, :my_sample1, {:application, :my_sample1,
applications: [:kernel, :stdlib, :elixir], applications: [:kernel, :stdlib, :elixir],
description: ~c"my_sample1", description: ~c"my_sample1",
modules: [], modules: [],
vsn: ~c"1.0.0", vsn: ~c"1.0.0",
included_applications: [:runtime_tools]} 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]) release = release(applications: [my_sample1: :permanent])
assert release.boot_scripts.start[:runtime_tools] == :load assert release.boot_scripts.start[:runtime_tools] == :load
@@ -825,18 +819,15 @@ defmodule Mix.ReleaseTest do
test "raise on conflict", context do test "raise on conflict", context do
in_tmp(context.test, fn -> in_tmp(context.test, fn ->
app = write_app!(
"my_sample2/ebin/my_sample2.app",
{:application, :my_sample2, {:application, :my_sample2,
applications: [:kernel, :stdlib, :elixir, :runtime_tools], applications: [:kernel, :stdlib, :elixir, :runtime_tools],
description: ~c"my_sample", description: ~c"my_sample",
modules: [], modules: [],
vsn: ~c"1.0.0", vsn: ~c"1.0.0",
included_applications: [:runtime_tools]} 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, assert_raise Mix.Error,
":runtime_tools is listed both as a regular application and as an included application", ":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 describe "optional applications" do
test "are ignored if not available", context do test "are ignored if not available", context do
in_tmp(context.test, fn -> in_tmp(context.test, fn ->
app = write_app!(
{:application, :my_sample1, "my_sample3/ebin/my_sample3.app",
{:application, :my_sample3,
applications: [:kernel, :stdlib, :elixir, :unknown], applications: [:kernel, :stdlib, :elixir, :unknown],
optional_applications: [:unknown], optional_applications: [:unknown],
description: ~c"my_sample1", description: ~c"my_sample3",
modules: [], modules: [],
vsn: ~c"1.0.0"} vsn: ~c"1.0.0"}
)
File.mkdir_p!("my_sample1/ebin") release = release(applications: [my_sample3: :permanent])
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[:unknown] == nil assert release.boot_scripts.start[:unknown] == nil
end) end)
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 end
defp size!(path) do defp size!(path) do
@@ -60,6 +60,23 @@ defmodule Mix.Tasks.Compile.YeccTest do
severity: :warning severity: :warning
} = diagnostic } = diagnostic
end) 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)
end end
+43 -12
View File
@@ -5,12 +5,6 @@ defmodule Mix.Tasks.CompileTest do
defmacro position(line, column), do: {line, column} defmacro position(line, column), do: {line, column}
defmodule CustomCompilers do
def project do
[compilers: [:elixir, :app, :custom]]
end
end
defmodule DepsApp do defmodule DepsApp do
def project do def project do
[app: :deps_app, version: "0.1.0", deps: [{:ok, "0.1.0", path: "deps/ok"}]] [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
end end
setup do setup tags do
Mix.ProjectStack.post_config(Map.get(tags, :project, []))
Mix.Project.push(MixTest.Case.Sample) Mix.Project.push(MixTest.Case.Sample)
:ok :ok
end end
@@ -37,16 +32,14 @@ defmodule Mix.Tasks.CompileTest do
assert_received {:mix_shell, :info, ["mix compile.elixir # " <> _]} assert_received {:mix_shell, :info, ["mix compile.elixir # " <> _]}
end end
@tag project: [compilers: [:elixir, :app, :custom]]
test "compiles --list with custom mixfile" do test "compiles --list with custom mixfile" do
Mix.Project.pop()
Mix.Project.push(CustomCompilers)
Mix.Task.run("compile", ["--list"]) Mix.Task.run("compile", ["--list"])
assert_received {:mix_shell, :info, ["\nEnabled compilers: elixir, app, custom, protocols"]} assert_received {:mix_shell, :info, ["\nEnabled compilers: elixir, app, custom, protocols"]}
end end
@tag project: [compilers: [:elixir, :app, :custom]]
test "compiles does not require all compilers available on manifest" do 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"] assert Mix.Tasks.Compile.manifests() |> Enum.map(&Path.basename/1) == ["compile.elixir"]
end end
@@ -101,6 +94,34 @@ defmodule Mix.Tasks.CompileTest do
end) end)
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 test "recompiles app cache if manifest changes" do
in_fixture("no_mixfile", fn -> in_fixture("no_mixfile", fn ->
Mix.Tasks.Compile.run(["--force"]) Mix.Tasks.Compile.run(["--force"])
@@ -329,12 +350,22 @@ defmodule Mix.Tasks.CompileTest do
Application.delete_env(:sample, :hello, persistent: true) Application.delete_env(:sample, :hello, persistent: true)
end end
test "code path prunning" do test "code path pruning" do
Mix.ensure_application!(:parsetools) Mix.ensure_application!(:parsetools)
otp_docs? = match?({:docs_v1, _, _, _, _, _, _}, Code.fetch_docs(:zlib))
in_fixture("no_mixfile", fn -> in_fixture("no_mixfile", fn ->
assert Mix.Task.run("compile", []) == {:ok, []} assert Mix.Task.run("compile", []) == {:ok, []}
assert :code.where_is_file(~c"parsetools.app") == :non_existing 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)
end end
+37
View File
@@ -915,6 +915,43 @@ defmodule Mix.Tasks.XrefTest do
end) end)
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 test "generates reports considering siblings inside umbrellas" do
Mix.Project.pop() Mix.Project.pop()
+34 -33
View File
@@ -252,8 +252,6 @@ defmodule Mix.UmbrellaTest do
end) end)
end end
## Umbrellas as a dependency
test "list deps for umbrella as dependency" do test "list deps for umbrella as dependency" do
in_fixture("umbrella_dep", fn -> in_fixture("umbrella_dep", fn ->
Mix.Project.in_project(:umbrella_dep, ".", fn _ -> Mix.Project.in_project(:umbrella_dep, ".", fn _ ->
@@ -373,7 +371,7 @@ defmodule Mix.UmbrellaTest do
end) end)
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 -> in_fixture("umbrella_dep/deps/umbrella/apps", fn ->
Mix.Project.in_project(:bar, "bar", fn _ -> Mix.Project.in_project(:bar, "bar", fn _ ->
Mix.Task.run("compile", []) Mix.Task.run("compile", [])
@@ -385,30 +383,13 @@ defmodule Mix.UmbrellaTest do
assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []} assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []}
assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]} 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") 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", "_build/dev/lib/bar/.mix/compile.elixir")
ensure_touched("../foo/lib/foo.ex", mtime)
Mix.Task.clear() Mix.Task.clear()
assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []} assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []}
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]} 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) end)
# Now let's add a new file to foo # Now let's add a new file to foo
@@ -446,18 +427,39 @@ defmodule Mix.UmbrellaTest do
File.write!("../foo/lib/foo.ex", "defmodule Foo, do: defstruct [:bar]") File.write!("../foo/lib/foo.ex", "defmodule Foo, do: defstruct [:bar]")
# Add struct dependency # Add struct dependency
File.write!("lib/bar.ex", "defmodule Bar, do: %Foo{bar: true}") File.write!("lib/bar.ex", """
Mix.Task.run("compile", ["--verbose"]) defmodule Bar do
assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]} 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") 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", "_build/dev/lib/bar/.mix/compile.elixir")
ensure_touched("../foo/lib/foo.ex", mtime)
Mix.Task.clear() Mix.Task.clear()
assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []} 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.rm!("../foo/lib/foo.ex")
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) end)
end end
@@ -480,16 +482,15 @@ defmodule Mix.UmbrellaTest do
# Add compile time to Foo.Bar # Add compile time to Foo.Bar
File.write!("lib/bar.ex", "defmodule Bar, do: Foo.Bar.hello()") File.write!("lib/bar.ex", "defmodule Bar, do: Foo.Bar.hello()")
Mix.Task.run("compile", ["--verbose"]) 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 # Recompiles for due to compile dependency via runtime dependencies
File.write!("../foo/lib/foo.baz.ex", File.read!("../foo/lib/foo.baz.ex") <> "\n") 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", "_build/dev/lib/bar/.mix/compile.elixir")
ensure_touched("../foo/lib/foo.ex", mtime)
Mix.Task.clear() Mix.Task.clear()
assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []} 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) end)
end end
+21 -18
View File
@@ -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.start()
Mix.shell(Mix.Shell.Process) Mix.shell(Mix.Shell.Process)
Application.put_env(:mix, :colors, enabled: false) Application.put_env(:mix, :colors, enabled: false)
@@ -5,6 +18,8 @@ Application.put_env(:mix, :colors, enabled: false)
Logger.remove_backend(:console) Logger.remove_backend(:console)
Application.put_env(:logger, :backends, []) Application.put_env(:logger, :backends, [])
## Setup ExUnit
os_exclude = if match?({:win32, _}, :os.type()), do: [unix: true], else: [windows: true] os_exclude = if match?({:win32, _}, :os.type()), do: [unix: true], else: [windows: true]
epmd_exclude = if match?({:win32, _}, :os.type()), do: [epmd: true], else: [] 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: [] 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
end end
## Set up globals ## Set up Rebar fixtures
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")
rebar3_source = System.get_env("REBAR3") || Path.expand("fixtures/rebar3", __DIR__) rebar3_source = System.get_env("REBAR3") || Path.expand("fixtures/rebar3", __DIR__)
[major, minor | _] = String.split(System.version(), ".") [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.mkdir_p!(Path.dirname(rebar3_target))
File.cp!(rebar3_source, rebar3_target) File.cp!(rebar3_source, rebar3_target)
## Copy fixtures to tmp
fixtures = ~w(rebar_dep rebar_override) fixtures = ~w(rebar_dep rebar_override)
Enum.each(fixtures, fn fixture -> Enum.each(fixtures, fn fixture ->
@@ -242,12 +244,13 @@ Enum.each(fixtures, fn fixture ->
File.cp_r!(source, dest) File.cp_r!(source, dest)
end) 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.email mix@example.com])
System.cmd("git", ~w[config --global user.name mix-repo]) System.cmd("git", ~w[config --global user.name mix-repo])
System.cmd("git", ~w[config --global init.defaultBranch not-main]) System.cmd("git", ~w[config --global init.defaultBranch not-main])
# Git repo ### Git repo
target = Path.expand("fixtures/git_repo", __DIR__) target = Path.expand("fixtures/git_repo", __DIR__)
unless File.dir?(target) do unless File.dir?(target) do
@@ -329,7 +332,7 @@ unless File.dir?(target) do
end) end)
end end
# Deps on Git repo ### Deps on Git repo
target = Path.expand("fixtures/deps_on_git_repo", __DIR__) target = Path.expand("fixtures/deps_on_git_repo", __DIR__)
unless File.dir?(target) do 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__)) File.mkdir_p!(Path.expand("fixtures/deps_status/deps/#{dep}/.git", __DIR__))
end) end)
# Archive ebin ### Archive ebin
target = Path.expand("fixtures/archive", __DIR__) target = Path.expand("fixtures/archive", __DIR__)
unless File.dir?(Path.join(target, "ebin")) do unless File.dir?(Path.join(target, "ebin")) do