Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
175c8243b2 | ||
|
|
2c1a836db3 | ||
|
|
f29e18bca1 | ||
|
|
329442c481 | ||
|
|
01a88e7137 | ||
|
|
4def31f8ab | ||
|
|
88c75bcbf5 | ||
|
|
786f3ce797 | ||
|
|
98aeee34b6 | ||
|
|
6f7eaf1122 | ||
|
|
9593cef2f5 | ||
|
|
c78f426bf5 | ||
|
|
ff29628602 | ||
|
|
f5e42ba11a | ||
|
|
b0dae83353 | ||
|
|
40da7e8857 | ||
|
|
ac910c1cc2 | ||
|
|
5c340e488e | ||
|
|
defef37856 | ||
|
|
432d6fa1c2 | ||
|
|
24714c68a7 | ||
|
|
f088fc9cdb | ||
|
|
dddb8f7b19 | ||
|
|
2da3300bfc | ||
|
|
c7a841baae | ||
|
|
f3dfb72c69 | ||
|
|
7e149619f5 | ||
|
|
6dbb932bf3 | ||
|
|
0cb79b0ba1 | ||
|
|
265b9aaeb3 | ||
|
|
b9bd0e3dd9 |
@@ -115,13 +115,13 @@ jobs:
|
||||
with:
|
||||
name: Docs
|
||||
path: Docs.zip*
|
||||
|
||||
|
||||
sign:
|
||||
needs: [build]
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
otp: [26, 27]
|
||||
otp: [25, 26, 27]
|
||||
flavor: [windows, linux]
|
||||
|
||||
env:
|
||||
@@ -160,14 +160,14 @@ jobs:
|
||||
run: cp "$ATTESTATION" "${RELEASE_FILE}.sigstore"
|
||||
env:
|
||||
ATTESTATION: "${{ steps.attest-provenance.outputs.bundle-path }}"
|
||||
|
||||
|
||||
- name: Create Release Hashes
|
||||
if: matrix.flavor == 'windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$sha1 = Get-FileHash "$env:RELEASE_FILE" -Algorithm SHA1
|
||||
$sha1.Hash.ToLower() + " " + $env:RELEASE_FILE | Out-File "$env:RELEASE_FILE.sha1sum"
|
||||
|
||||
|
||||
$sha256 = Get-FileHash "$env:RELEASE_FILE" -Algorithm SHA256
|
||||
$sha256.Hash.ToLower() + " " + $env:RELEASE_FILE | Out-File "$env:RELEASE_FILE.sha256sum"
|
||||
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: '{sign-*-elixir-otp-*,Docs}'
|
||||
pattern: "{sign-*-elixir-otp-*,Docs}"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Upload Pre-built
|
||||
@@ -235,7 +235,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: '{sign-*-elixir-otp-*,Docs}'
|
||||
pattern: "{sign-*-elixir-otp-*,Docs}"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Init purge keys file
|
||||
|
||||
@@ -225,6 +225,40 @@ You may also prefer to write using guards:
|
||||
|
||||
def foo(x, y, z) when x == y and y == z
|
||||
|
||||
## v1.18.2 (2024-01-22)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [CLI] Add `--color`/`--no-color` for enabling and disabling of ANSI colors
|
||||
* [Code.Fragment] Provide more AST context when invoking `container_cursor_to_quoted` with trailing fragments
|
||||
* [Regex] Ensure compatibility with Erlang/OTP 28+ new Regex engine
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix] Print compilation lock waiting message to stderr
|
||||
* [mix] Add an environment variable to optionally disable compilation locking
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [CLI] Temporarily remove PowerShell scripts for `elixir`, `elixirc`, and `mix` on Windows, as they leave the shell broken after quitting Erlang
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Fix crash when diffing bitstring specifiers
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Autocomplete] Fix crashing whhen autocompleting structs with runtime values
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix] Track compilation locks per user to avoid permission errors
|
||||
* [mix deps.update] Ensure Git dependencies can be upgraded by doing so against the origin
|
||||
|
||||
## v1.18.1 (2024-12-24)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
+3
-2
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.18.1
|
||||
ELIXIR_VERSION=1.18.2
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
@@ -18,6 +18,7 @@ Usage: $(basename "$0") [options] [.exs file] [data]
|
||||
-pz "PATH" Appends the given path to Erlang code path (*)
|
||||
-v, --version Prints Erlang/OTP and Elixir versions (standalone)
|
||||
|
||||
--color, --no-color Enables or disables ANSI coloring
|
||||
--erl "SWITCHES" Switches to be passed down to Erlang (*)
|
||||
--eval "COMMAND" Evaluates the given command, same as -e (*)
|
||||
--logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
@@ -111,7 +112,7 @@ while [ $I -le $LENGTH ]; do
|
||||
C=1
|
||||
MODE="iex"
|
||||
;;
|
||||
-v|--no-halt)
|
||||
-v|--no-halt|--color|--no-color)
|
||||
C=1
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg)
|
||||
|
||||
+6
-3
@@ -1,6 +1,6 @@
|
||||
@echo off
|
||||
|
||||
set ELIXIR_VERSION=1.18.1
|
||||
set ELIXIR_VERSION=1.18.2
|
||||
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
|
||||
@@ -24,6 +24,7 @@ echo -pa "PATH" Prepends the given path to Erlang code path
|
||||
echo -pz "PATH" Appends the given path to Erlang code path (*)
|
||||
echo -v, --version Prints Erlang/OTP and Elixir versions (standalone)
|
||||
echo.
|
||||
echo --color, --no-color Enables or disables ANSI coloring
|
||||
echo --erl "SWITCHES" Switches to be passed down to Erlang (*)
|
||||
echo --eval "COMMAND" Evaluates the given command, same as -e (*)
|
||||
echo --logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
@@ -107,6 +108,8 @@ if ""==!par:-pz=! (shift && goto startloop)
|
||||
if ""==!par:-v=! (goto startloop)
|
||||
if ""==!par:--version=! (goto startloop)
|
||||
if ""==!par:--no-halt=! (goto startloop)
|
||||
if ""==!par:--color=! (goto startloop)
|
||||
if ""==!par:--no-color=! (goto startloop)
|
||||
if ""==!par:--remsh=! (shift && goto startloop)
|
||||
if ""==!par:--dot-iex=! (shift && goto startloop)
|
||||
if ""==!par:--dbg=! (shift && goto startloop)
|
||||
@@ -133,9 +136,9 @@ if not defined useIEx (
|
||||
set beforeExtra=-noshell -elixir_root "%SCRIPT_PATH%..\lib" -pa "%SCRIPT_PATH%..\lib\elixir\ebin" %beforeExtra%
|
||||
|
||||
if defined ELIXIR_CLI_DRY_RUN (
|
||||
echo "%ERTS_BIN%erl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
echo "%ERTS_BIN%erl.exe" %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
) else (
|
||||
"%ERTS_BIN%erl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
"%ERTS_BIN%erl.exe" %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
)
|
||||
exit /B %ERRORLEVEL%
|
||||
:end
|
||||
|
||||
-304
@@ -1,304 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
$ELIXIR_VERSION = "1.18.1"
|
||||
|
||||
$scriptPath = Split-Path -Parent $PSCommandPath
|
||||
$erlExec = "erl"
|
||||
|
||||
# The iex.ps1, elixirc.ps1 and mix.ps1 scripts may populate this var.
|
||||
if ($null -eq $allArgs) {
|
||||
$allArgs = $args
|
||||
}
|
||||
|
||||
function PrintElixirHelp {
|
||||
$scriptName = Split-Path -Leaf $PSCommandPath
|
||||
$help = @"
|
||||
Usage: $scriptName [options] [.exs file] [data]
|
||||
|
||||
## General options
|
||||
|
||||
-e "COMMAND" Evaluates the given command (*)
|
||||
-h, --help Prints this message (standalone)
|
||||
-r "FILE" Requires the given files/patterns (*)
|
||||
-S SCRIPT Finds and executes the given script in `$PATH
|
||||
-pr "FILE" Requires the given files/patterns in parallel (*)
|
||||
-pa "PATH" Prepends the given path to Erlang code path (*)
|
||||
-pz "PATH" Appends the given path to Erlang code path (*)
|
||||
-v, --version Prints Erlang/OTP and Elixir versions (standalone)
|
||||
|
||||
--erl "SWITCHES" Switches to be passed down to Erlang (*)
|
||||
--eval "COMMAND" Evaluates the given command, same as -e (*)
|
||||
--logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
--logger-sasl-reports BOOL Enables or disables SASL reporting
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--short-version Prints Elixir version (standalone)
|
||||
|
||||
Options given after the .exs file or -- are passed down to the executed code.
|
||||
Options can be passed to the Erlang runtime using `$ELIXIR_ERL_OPTIONS or --erl.
|
||||
|
||||
## Distribution options
|
||||
|
||||
The following options are related to node distribution.
|
||||
|
||||
--cookie COOKIE Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--name NAME Makes and assigns a name to the distributed node
|
||||
--rpc-eval NODE "COMMAND" Evaluates the given command on the given remote node (*)
|
||||
--sname NAME Makes and assigns a short name to the distributed node
|
||||
|
||||
--name and --sname may be set to undefined so one is automatically generated.
|
||||
|
||||
## Release options
|
||||
|
||||
The following options are generally used under releases.
|
||||
|
||||
--boot "FILE" Uses the given FILE.boot to start the system
|
||||
--boot-var VAR "VALUE" Makes `$VAR available as VALUE to FILE.boot (*)
|
||||
--erl-config "FILE" Loads configuration in FILE.config written in Erlang (*)
|
||||
--vm-args "FILE" Passes the contents in file as arguments to the VM
|
||||
|
||||
--pipe-to is not supported via PowerShell.
|
||||
|
||||
** Options marked with (*) can be given more than once.
|
||||
** Standalone options can't be combined with other options.
|
||||
"@
|
||||
|
||||
Write-Host $help
|
||||
}
|
||||
|
||||
if (($allArgs.Count -eq 1) -and ($allArgs[0] -eq "--short-version")) {
|
||||
Write-Host "$ELIXIR_VERSION"
|
||||
exit
|
||||
}
|
||||
|
||||
if (($allArgs.Count -eq 0) -or (($allArgs.Count -eq 1) -and ($allArgs[0] -in @("-h", "--help")))) {
|
||||
PrintElixirHelp
|
||||
exit 1
|
||||
}
|
||||
|
||||
function NormalizeArg {
|
||||
param(
|
||||
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
|
||||
[string[]] $Items
|
||||
)
|
||||
$Items -join ","
|
||||
}
|
||||
|
||||
function QuoteString {
|
||||
param(
|
||||
[Parameter(ValueFromPipeline = $true)]
|
||||
[string] $Item
|
||||
)
|
||||
|
||||
# We surround the string with double quotes, in order to preserve its contents as
|
||||
# only one command arg.
|
||||
# This is needed because PowerShell consider spaces as separator of arguments.
|
||||
# The double quotes around will be removed when PowerShell process the argument.
|
||||
# See: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.4#passing-quoted-strings-to-external-commands
|
||||
if ($Item.Contains(" ")) {
|
||||
'"' + $Item + '"'
|
||||
}
|
||||
else {
|
||||
$Item
|
||||
}
|
||||
}
|
||||
|
||||
$elixirParams = @()
|
||||
$erlangParams = @()
|
||||
$beforeExtras = @()
|
||||
$allOtherParams = @()
|
||||
|
||||
$runErlPipe = $null
|
||||
$runErlLog = $null
|
||||
|
||||
for ($i = 0; $i -lt $allArgs.Count; $i++) {
|
||||
$private:arg = $allArgs[$i]
|
||||
|
||||
switch -exact ($arg) {
|
||||
{ $_ -in @("-e", "-r", "-pr", "-pa", "-pz", "--eval", "--remsh", "--dot-iex", "--dbg") } {
|
||||
$private:nextArg = NormalizeArg($allArgs[++$i])
|
||||
|
||||
$elixirParams += $arg
|
||||
$elixirParams += $nextArg
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
{ $_ -in @("-v", "--version") } {
|
||||
# Standalone options goes only once in the Elixir params, when they are empty.
|
||||
if (($elixirParams.Count -eq 0) -and ($allOtherParams.Count -eq 0)) {
|
||||
$elixirParams += $arg
|
||||
}
|
||||
else {
|
||||
$allOtherParams += $arg
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
"--no-halt" {
|
||||
$elixirParams += $arg
|
||||
break
|
||||
}
|
||||
|
||||
"--cookie" {
|
||||
$erlangParams += "-setcookie"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--hidden" {
|
||||
$erlangParams += "-hidden"
|
||||
break
|
||||
}
|
||||
|
||||
"--name" {
|
||||
$erlangParams += "-name"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--sname" {
|
||||
$erlangParams += "-sname"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--boot" {
|
||||
$erlangParams += "-boot"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--erl-config" {
|
||||
$erlangParams += "-config"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--vm-args" {
|
||||
$erlangParams += "-args_file"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--logger-otp-reports" {
|
||||
$private:tempVal = $allArgs[$i + 1]
|
||||
if ($tempVal -in @("true", "false")) {
|
||||
$erlangParams += @("-logger", "handle_otp_reports", $allArgs[++$i])
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
"--logger-sasl-reports" {
|
||||
$private:tempVal = $allArgs[$i + 1]
|
||||
if ($tempVal -in @("true", "false")) {
|
||||
$erlangParams += @("-logger", "handle_sasl_reports", $allArgs[++$i])
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
"--erl" {
|
||||
$private:erlFlags = $allArgs[++$i] -split " "
|
||||
$beforeExtras += $erlFlags
|
||||
break
|
||||
}
|
||||
|
||||
"+iex" {
|
||||
$elixirParams += "+iex"
|
||||
$useIex = $true
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
"+elixirc" {
|
||||
$elixirParams += "+elixirc"
|
||||
break
|
||||
}
|
||||
|
||||
"--rpc-eval" {
|
||||
$private:key = $allArgs[++$i]
|
||||
$private:value = $allArgs[++$i]
|
||||
|
||||
if ($null -eq $key) {
|
||||
Write-Error "--rpc-eval: NODE must be present"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($null -eq $value) {
|
||||
Write-Error "--rpc-eval: COMMAND for the '$key' node must be present"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$elixirParams += "--rpc-eval"
|
||||
$elixirParams += $key
|
||||
$elixirParams += $value
|
||||
break
|
||||
}
|
||||
|
||||
"--boot-var" {
|
||||
$private:key = $allArgs[++$i]
|
||||
$private:value = $allArgs[++$i]
|
||||
|
||||
if ($null -eq $key) {
|
||||
Write-Error "--boot-var: VAR must be present"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($null -eq $value) {
|
||||
Write-Error "--boot-var: Value for the '$key' var must be present"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$elixirParams += "-boot_var"
|
||||
$elixirParams += $key
|
||||
$elixirParams += $value
|
||||
break
|
||||
}
|
||||
|
||||
Default {
|
||||
$private:normalized = NormalizeArg $arg
|
||||
$allOtherParams += $normalized
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -eq $useIEx) {
|
||||
$beforeExtras = @("-s", "elixir", "start_cli") + $beforeExtras
|
||||
}
|
||||
|
||||
$beforeExtras = @("-pa", "$(Join-Path $scriptPath -ChildPath "../lib/elixir/ebin")") + $beforeExtras
|
||||
$beforeExtras = @("-noshell", "-elixir_root", "$(Join-Path $scriptPath -ChildPath "../lib")") + $beforeExtras
|
||||
|
||||
$allParams = @()
|
||||
|
||||
if ($null -ne $env:ELIXIR_ERL_OPTIONS) {
|
||||
$private:erlFlags = $env:ELIXIR_ERL_OPTIONS -split " "
|
||||
$allParams += $erlFlags
|
||||
}
|
||||
|
||||
$allParams += $erlangParams
|
||||
$allParams += $beforeExtras
|
||||
$allParams += "-extra"
|
||||
$allParams += $elixirParams
|
||||
$allParams += $allOtherParams
|
||||
|
||||
$binSuffix = ""
|
||||
|
||||
# The variable is available after PowerShell 7.2. Previous to that, PS only worked on Windows.
|
||||
if ($isWindows -or ($null -eq $isWindows)) {
|
||||
$binSuffix = ".exe"
|
||||
}
|
||||
|
||||
$binPath = "$erlExec$binSuffix"
|
||||
|
||||
# We double the double-quotes because they are going to be escaped by arguments parsing.
|
||||
$paramsPart = $allParams | ForEach-Object -Process { QuoteString($_ -replace "`"", "`"`"") }
|
||||
|
||||
if ($env:ELIXIR_CLI_DRY_RUN) {
|
||||
Write-Host "$binPath $paramsPart"
|
||||
}
|
||||
else {
|
||||
$output = Start-Process -FilePath $binPath -ArgumentList $paramsPart -NoNewWindow -Wait -PassThru
|
||||
exit $output.ExitCode
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
$scriptName = Split-Path -Leaf $PSCommandPath
|
||||
|
||||
if (($args.Count -eq 0) -or ($args[0] -in @("-h", "--help"))) {
|
||||
Write-Host @"
|
||||
Usage: $scriptName [elixir switches] [compiler switches] [.ex files]
|
||||
|
||||
-h, --help Prints this message and exits
|
||||
-o The directory to output compiled files
|
||||
-v, --version Prints Elixir version and exits (standalone)
|
||||
|
||||
--ignore-module-conflict Does not emit warnings if a module was previously defined
|
||||
--no-debug-info Does not attach debug info to compiled modules
|
||||
--no-docs Does not attach documentation to compiled modules
|
||||
--profile time Profile the time to compile modules
|
||||
--verbose Prints compilation status
|
||||
--warnings-as-errors Treats warnings as errors and returns non-zero exit status
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the Erlang runtime using ELIXIR_ERL_OPTIONS
|
||||
** Options can be passed to the Erlang compiler using ERL_COMPILER_OPTIONS
|
||||
"@
|
||||
exit
|
||||
}
|
||||
|
||||
$scriptPath = Split-Path -Parent $PSCommandPath
|
||||
$elixirMainScript = Join-Path -Path $scriptPath -ChildPath "elixir.ps1"
|
||||
|
||||
$prependedArgs = @("+elixirc")
|
||||
|
||||
$allArgs = $prependedArgs + $args
|
||||
|
||||
# The dot is going to evaluate the script with the vars defined here.
|
||||
. $elixirMainScript
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
$scriptName = Split-Path -Leaf $PSCommandPath
|
||||
|
||||
if ($args[0] -in @("-h", "--help")) {
|
||||
Write-Host @"
|
||||
Usage: $scriptName [options] [.exs file] [data]
|
||||
|
||||
The following options are exclusive to IEx:
|
||||
|
||||
--dbg pry Sets the backend for Kernel.dbg/2 to IEx.pry/0
|
||||
--dot-iex "FILE" Evaluates FILE, line by line, to set up IEx' environment.
|
||||
Defaults to evaluating .iex.exs or ~/.iex.exs, if any exists.
|
||||
If FILE is empty, then no file will be loaded.
|
||||
--remsh NAME Connects to a node using a remote shell.
|
||||
|
||||
It accepts all other options listed by "elixir --help".
|
||||
"@
|
||||
exit
|
||||
}
|
||||
|
||||
$scriptPath = Split-Path -Parent $PSCommandPath
|
||||
$elixirMainScript = Join-Path -Path $scriptPath -ChildPath "elixir.ps1"
|
||||
|
||||
$prependedArgs = @("--no-halt", "--erl", "-user elixir", "+iex")
|
||||
|
||||
$allArgs = $prependedArgs + $args
|
||||
|
||||
# The dot is going to evaluate the script with the vars defined here.
|
||||
. $elixirMainScript
|
||||
+21
-11
@@ -1,13 +1,23 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Store path to mix.bat as a FileInfo object
|
||||
$mixBatPath = (Get-ChildItem (((Get-ChildItem $MyInvocation.MyCommand.Path).Directory.FullName) + '\mix.bat'))
|
||||
$newArgs = @()
|
||||
|
||||
$scriptPath = Split-Path -Parent $PSCommandPath
|
||||
$elixirMainScript = Join-Path -Path $scriptPath -ChildPath "elixir.ps1"
|
||||
for ($i = 0; $i -lt $args.length; $i++)
|
||||
{
|
||||
if ($args[$i] -is [array])
|
||||
{
|
||||
# Commas created the array so we need to reintroduce those commas
|
||||
for ($j = 0; $j -lt $args[$i].length - 1; $j++)
|
||||
{
|
||||
$newArgs += ($args[$i][$j] + ',')
|
||||
}
|
||||
$newArgs += $args[$i][-1]
|
||||
}
|
||||
else
|
||||
{
|
||||
$newArgs += $args[$i]
|
||||
}
|
||||
}
|
||||
|
||||
$mixFile = Join-Path -Path $scriptPath -ChildPath "mix"
|
||||
|
||||
$prependedArgs = @($mixFile)
|
||||
|
||||
$allArgs = $prependedArgs + $args
|
||||
|
||||
# The dot is going to evaluate the script with the vars defined here.
|
||||
. $elixirMainScript
|
||||
# Corrected arguments are ready to pass to batch file
|
||||
& $mixBatPath $newArgs
|
||||
@@ -1151,7 +1151,7 @@ defmodule Code.Fragment do
|
||||
{rev_tokens, rev_terminators} =
|
||||
with [close, open, {_, _, :__cursor__} = cursor | rev_tokens] <- rev_tokens,
|
||||
{_, [_ | after_fn]} <- Enum.split_while(rev_terminators, &(elem(&1, 0) != :fn)),
|
||||
true <- maybe_missing_stab?(rev_tokens),
|
||||
true <- maybe_missing_stab?(rev_tokens, false),
|
||||
[_ | rev_tokens] <- Enum.drop_while(rev_tokens, &(elem(&1, 0) != :fn)) do
|
||||
{[close, open, cursor | rev_tokens], after_fn}
|
||||
else
|
||||
@@ -1165,7 +1165,7 @@ defmodule Code.Fragment do
|
||||
tokens =
|
||||
with {before_start, [_ | _] = after_start} <-
|
||||
Enum.split_while(rev_terminators, &(elem(&1, 0) not in [:do, :fn])),
|
||||
true <- maybe_missing_stab?(rev_tokens),
|
||||
true <- maybe_missing_stab?(rev_tokens, true),
|
||||
opts =
|
||||
Keyword.put(opts, :check_terminators, {:cursor, before_start}),
|
||||
{:error, {meta, _, ~c"end"}, _rest, _warnings, trailing_rev_tokens} <-
|
||||
@@ -1173,6 +1173,14 @@ defmodule Code.Fragment do
|
||||
trailing_tokens =
|
||||
reverse_tokens(meta[:line], meta[:column], trailing_rev_tokens, after_start)
|
||||
|
||||
# If the cursor has its own line, then we do not trim new lines trailing tokens.
|
||||
# Otherwise we want to drop any newline so we drop the next tokens after eol.
|
||||
trailing_tokens =
|
||||
case rev_tokens do
|
||||
[_close, _open, {_, _, :__cursor__}, {:eol, _} | _] -> trailing_tokens
|
||||
_ -> Enum.drop_while(trailing_tokens, &match?({:eol, _}, &1))
|
||||
end
|
||||
|
||||
Enum.reverse(rev_tokens, drop_tokens(trailing_tokens, 0))
|
||||
else
|
||||
_ -> reverse_tokens(line, column, rev_tokens, rev_terminators)
|
||||
@@ -1196,12 +1204,16 @@ defmodule Code.Fragment do
|
||||
Enum.reverse(tokens, terminators)
|
||||
end
|
||||
|
||||
# Otherwise we drop all tokens, trying to build a minimal AST
|
||||
# for cursor completion.
|
||||
defp drop_tokens([{:"}", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:"]", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:")", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:">>", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:end, _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:",", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:";", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:eol, _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:stab_op, _, :->} | _] = tokens, 0), do: tokens
|
||||
|
||||
defp drop_tokens([{:"}", _} | tokens], counter), do: drop_tokens(tokens, counter - 1)
|
||||
@@ -1220,13 +1232,13 @@ defmodule Code.Fragment do
|
||||
defp drop_tokens([_ | tokens], counter), do: drop_tokens(tokens, counter)
|
||||
defp drop_tokens([], 0), do: []
|
||||
|
||||
defp maybe_missing_stab?([{:after, _} | _]), do: true
|
||||
defp maybe_missing_stab?([{:do, _} | _]), do: true
|
||||
defp maybe_missing_stab?([{:fn, _} | _]), do: true
|
||||
defp maybe_missing_stab?([{:else, _} | _]), do: true
|
||||
defp maybe_missing_stab?([{:catch, _} | _]), do: true
|
||||
defp maybe_missing_stab?([{:rescue, _} | _]), do: true
|
||||
defp maybe_missing_stab?([{:stab_op, _, :->} | _]), do: false
|
||||
defp maybe_missing_stab?([_ | tail]), do: maybe_missing_stab?(tail)
|
||||
defp maybe_missing_stab?([]), do: false
|
||||
defp maybe_missing_stab?([{:after, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:do, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:fn, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:else, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:catch, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:rescue, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:stab_op, _, :->} | _], stab_choice?), do: stab_choice?
|
||||
defp maybe_missing_stab?([_ | tail], stab_choice?), do: maybe_missing_stab?(tail, stab_choice?)
|
||||
defp maybe_missing_stab?([], _stab_choice?), do: false
|
||||
end
|
||||
|
||||
+35
-36
@@ -1972,7 +1972,7 @@ defmodule Kernel do
|
||||
|
||||
defp build_boolean_check(operator, check, true_clause, false_clause) do
|
||||
annotate_case(
|
||||
[optimize_boolean: true],
|
||||
[optimize_boolean: true, type_check: :expr],
|
||||
quote do
|
||||
case unquote(check) do
|
||||
false -> unquote(false_clause)
|
||||
@@ -2006,7 +2006,7 @@ defmodule Kernel do
|
||||
assert_no_match_or_guard_scope(__CALLER__.context, "!")
|
||||
|
||||
annotate_case(
|
||||
[optimize_boolean: true],
|
||||
[optimize_boolean: true, type_check: :expr],
|
||||
quote do
|
||||
case unquote(value) do
|
||||
x when :"Elixir.Kernel".in(x, [false, nil]) -> false
|
||||
@@ -2020,7 +2020,7 @@ defmodule Kernel do
|
||||
assert_no_match_or_guard_scope(__CALLER__.context, "!")
|
||||
|
||||
annotate_case(
|
||||
[optimize_boolean: true],
|
||||
[optimize_boolean: true, type_check: :expr],
|
||||
quote do
|
||||
case unquote(value) do
|
||||
x when :"Elixir.Kernel".in(x, [false, nil]) -> true
|
||||
@@ -3910,7 +3910,7 @@ defmodule Kernel do
|
||||
|
||||
defp build_if(condition, do: do_clause, else: else_clause) do
|
||||
annotate_case(
|
||||
[optimize_boolean: true],
|
||||
[optimize_boolean: true, type_check: :expr],
|
||||
quote do
|
||||
case unquote(condition) do
|
||||
x when :"Elixir.Kernel".in(x, [false, nil]) -> unquote(else_clause)
|
||||
@@ -4584,6 +4584,9 @@ defmodule Kernel do
|
||||
defp in_var(true, {atom, _, context} = var, fun) when is_atom(atom) and is_atom(context),
|
||||
do: fun.(var)
|
||||
|
||||
defp in_var(true, var, fun) when is_atom(var) or is_binary(var) or is_number(var),
|
||||
do: fun.(var)
|
||||
|
||||
defp in_var(true, ast, fun) do
|
||||
quote do
|
||||
var = unquote(ast)
|
||||
@@ -5460,33 +5463,21 @@ defmodule Kernel do
|
||||
use `@type`.
|
||||
"""
|
||||
defmacro defstruct(fields) do
|
||||
header =
|
||||
quote bind_quoted: [fields: fields, bootstrapped?: bootstrapped?(Enum)] do
|
||||
{struct, derive, escaped_struct, kv, body} =
|
||||
Kernel.Utils.defstruct(__MODULE__, fields, bootstrapped?, __ENV__)
|
||||
quote bind_quoted: [fields: fields, bootstrapped?: bootstrapped?(Enum)] do
|
||||
{struct, derive, escaped_struct, kv, body} =
|
||||
Kernel.Utils.defstruct(__MODULE__, fields, bootstrapped?, __ENV__)
|
||||
|
||||
case derive do
|
||||
[] -> :ok
|
||||
_ -> Protocol.__derive__(derive, __MODULE__, __ENV__)
|
||||
end
|
||||
case derive do
|
||||
[] -> :ok
|
||||
_ -> Protocol.__derive__(derive, __MODULE__, __ENV__)
|
||||
end
|
||||
|
||||
# We attach the line: 0 to struct functions because we don't want
|
||||
# the generated callbacks to count towards code coverage and metrics,
|
||||
# especially since they are often expanded at compile-time.
|
||||
functions =
|
||||
quote line: 0, unquote: false do
|
||||
def __struct__(), do: unquote(escaped_struct)
|
||||
def __struct__(unquote(kv)), do: unquote(body)
|
||||
end
|
||||
def __struct__(), do: unquote(escaped_struct)
|
||||
def __struct__(unquote(kv)), do: unquote(body)
|
||||
|
||||
footer =
|
||||
quote do
|
||||
Kernel.Utils.announce_struct(__MODULE__)
|
||||
struct
|
||||
end
|
||||
|
||||
{:__block__, [], [header, functions, footer]}
|
||||
Kernel.Utils.announce_struct(__MODULE__)
|
||||
struct
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -6446,29 +6437,37 @@ defmodule Kernel do
|
||||
"""
|
||||
defmacro sigil_r(term, modifiers)
|
||||
|
||||
defmacro sigil_r({:<<>>, _meta, [string]}, options) when is_binary(string) do
|
||||
binary = :elixir_interpolation.unescape_string(string, ®ex_unescape_map/1)
|
||||
regex = Regex.compile!(binary, :binary.list_to_bin(options))
|
||||
Macro.escape(regex)
|
||||
defmacro sigil_r({:<<>>, _meta, [binary]}, options) when is_binary(binary) do
|
||||
binary = :elixir_interpolation.unescape_string(binary, ®ex_unescape_map/1)
|
||||
compile_regex(binary, options)
|
||||
end
|
||||
|
||||
defmacro sigil_r({:<<>>, meta, pieces}, options) do
|
||||
binary = {:<<>>, meta, unescape_tokens(pieces, ®ex_unescape_map/1)}
|
||||
quote(do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options))))
|
||||
tuple = {:<<>>, meta, unescape_tokens(pieces, ®ex_unescape_map/1)}
|
||||
compile_regex(tuple, options)
|
||||
end
|
||||
|
||||
defp regex_unescape_map(:newline), do: true
|
||||
defp regex_unescape_map(_), do: false
|
||||
|
||||
@doc false
|
||||
defmacro sigil_R({:<<>>, _meta, [string]}, options) when is_binary(string) do
|
||||
defmacro sigil_R({:<<>>, _meta, [binary]}, options) when is_binary(binary) do
|
||||
IO.warn(
|
||||
"~R/.../ is deprecated, use ~r/.../ instead",
|
||||
Macro.Env.stacktrace(__CALLER__)
|
||||
)
|
||||
|
||||
regex = Regex.compile!(string, :binary.list_to_bin(options))
|
||||
Macro.escape(regex)
|
||||
compile_regex(binary, options)
|
||||
end
|
||||
|
||||
defp compile_regex(binary_or_tuple, options) do
|
||||
case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do
|
||||
true ->
|
||||
Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options)))
|
||||
|
||||
false ->
|
||||
quote(do: Regex.compile!(unquote(binary_or_tuple), unquote(:binary.list_to_bin(options))))
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
|
||||
@@ -295,6 +295,16 @@ defmodule Kernel.CLI do
|
||||
parse_argv(t, %{config | commands: [{:parallel_require, h} | config.commands]})
|
||||
end
|
||||
|
||||
defp parse_argv([~c"--color" | t], config) do
|
||||
Application.put_env(:elixir, :ansi_enabled, true)
|
||||
parse_argv(t, config)
|
||||
end
|
||||
|
||||
defp parse_argv([~c"--no-color" | t], config) do
|
||||
Application.put_env(:elixir, :ansi_enabled, true)
|
||||
parse_argv(t, config)
|
||||
end
|
||||
|
||||
## Compiler
|
||||
|
||||
defp parse_argv([~c"-o", h | t], %{mode: :elixirc} = config) do
|
||||
|
||||
@@ -938,7 +938,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Helpers
|
||||
|
||||
# This is a backport of Macro.expand/2 because we want to expand
|
||||
# This is a modified backport of Macro.expand/2 because we want to expand
|
||||
# aliases but we don't them to become compile-time references.
|
||||
defp expand_remote({:__aliases__, meta, list} = alias, env) do
|
||||
case :elixir_aliases.expand_or_concat(meta, list, env, true) do
|
||||
@@ -946,9 +946,12 @@ defmodule Kernel.Typespec do
|
||||
receiver
|
||||
|
||||
[head | tail] ->
|
||||
case Macro.expand_once(head, env) do
|
||||
head when is_atom(head) -> :elixir_aliases.concat([head | tail])
|
||||
_ -> alias
|
||||
case Macro.expand(head, env) do
|
||||
head when is_atom(head) ->
|
||||
:elixir_aliases.concat([head | tail])
|
||||
|
||||
_ ->
|
||||
compile_error(env, "unexpected expression in typespec: #{Macro.to_string(alias)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -174,14 +174,14 @@ defmodule Kernel.Utils do
|
||||
true ->
|
||||
case enforce_keys do
|
||||
[] ->
|
||||
quote do
|
||||
quote line: 0, generated: true do
|
||||
Enum.reduce(kv, unquote(escaped_struct), fn {key, val}, map ->
|
||||
%{map | key => val}
|
||||
end)
|
||||
end
|
||||
|
||||
_ ->
|
||||
quote do
|
||||
quote line: 0, generated: true do
|
||||
{map, keys} =
|
||||
Enum.reduce(kv, {unquote(escaped_struct), unquote(enforce_keys)}, fn
|
||||
{key, val}, {map, keys} ->
|
||||
@@ -201,7 +201,7 @@ defmodule Kernel.Utils do
|
||||
end
|
||||
|
||||
false ->
|
||||
quote do
|
||||
quote line: 0, generated: true do
|
||||
:lists.foldl(
|
||||
fn {key, val}, acc -> %{acc | key => val} end,
|
||||
unquote(escaped_struct),
|
||||
|
||||
@@ -146,8 +146,6 @@ defmodule Macro do
|
||||
* `:delimiter` - contains the opening delimiter for sigils, strings,
|
||||
and charlists as a string (such as `"{"`, `"/"`, `"'"`, and the like)
|
||||
|
||||
* `:format` - set to `:keyword` when an atom is defined as a keyword
|
||||
|
||||
* `:do` - contains metadata about the `do` location in a function call with
|
||||
`do`-`end` blocks (when `:token_metadata` is true)
|
||||
|
||||
@@ -159,10 +157,18 @@ defmodule Macro do
|
||||
expressions inside "blocks of code", which are either direct children
|
||||
of a `__block__` or the right side of `->`. The last expression of the
|
||||
block does not have metadata if it is not followed by an end of line
|
||||
character (either a newline or `;`)
|
||||
character (either a newline or `;`). This entry may appear multiple times
|
||||
in the same metadata if the expression is surround by parens
|
||||
|
||||
* `:format` - set to `:keyword` when an atom is defined as a keyword.
|
||||
It may also be set to `:atom` to distinguish `nil`, `false`, and `true`
|
||||
|
||||
* `:indentation` - indentation of a sigil heredoc
|
||||
|
||||
* `:parens` - denotes a node was surrounded by parens for grouping.
|
||||
This entry may appear multiple times in the same metadata if
|
||||
multiple pairs are used for grouping
|
||||
|
||||
The following metadata keys are private:
|
||||
|
||||
* `:alias` - Used for alias hygiene.
|
||||
|
||||
@@ -785,7 +785,7 @@ defmodule Module.Types.Apply do
|
||||
def format_diagnostic({:badlocal, expr, args_types, domain, clauses, context}) do
|
||||
traces = collect_traces(expr, context)
|
||||
converter = &Function.identity/1
|
||||
{fun, _, _} = expr
|
||||
{fun, meta, _} = expr
|
||||
|
||||
explanation =
|
||||
empty_arg_reason(args_types) ||
|
||||
@@ -794,16 +794,29 @@ defmodule Module.Types.Apply do
|
||||
#{clauses_args_to_quoted_string(clauses, converter)}
|
||||
"""
|
||||
|
||||
%{
|
||||
details: %{typing_traces: traces},
|
||||
message:
|
||||
IO.iodata_to_binary([
|
||||
banner =
|
||||
case fun == :super && meta[:default] && meta[:super] do
|
||||
{_kind, fun} ->
|
||||
"""
|
||||
incompatible types given as default arguments to #{fun}/#{length(args_types)}:
|
||||
"""
|
||||
|
||||
_ ->
|
||||
"""
|
||||
incompatible types given to #{fun}/#{length(args_types)}:
|
||||
|
||||
#{expr_to_string(expr) |> indent(4)}
|
||||
|
||||
given types:
|
||||
"""
|
||||
end
|
||||
|
||||
%{
|
||||
details: %{typing_traces: traces},
|
||||
message:
|
||||
IO.iodata_to_binary([
|
||||
banner,
|
||||
"""
|
||||
|
||||
#{args_to_quoted_string(args_types, domain, converter) |> indent(4)}
|
||||
|
||||
|
||||
@@ -1329,11 +1329,10 @@ defmodule Module.Types.Descr do
|
||||
:maps.next(iterator) |> map_literal_intersection_loop(acc)
|
||||
|
||||
_ ->
|
||||
# If the key is marked as not_set in the open map, we can ignore it.
|
||||
if type1 == @not_set do
|
||||
:maps.next(iterator) |> map_literal_intersection_loop(acc)
|
||||
else
|
||||
throw(:empty)
|
||||
# If the key is optional in the open map, we can ignore it
|
||||
case type1 do
|
||||
%{optional: 1} -> :maps.next(iterator) |> map_literal_intersection_loop(acc)
|
||||
_ -> throw(:empty)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -518,7 +518,7 @@ defmodule Module.Types.Expr do
|
||||
|
||||
defp with_clause({:<-, _meta, [left, right]} = expr, stack, context) do
|
||||
{pattern, guards} = extract_head([left])
|
||||
{_type, context} = Pattern.of_match(pattern, guards, dynamic(), :with, expr, stack, context)
|
||||
{_type, context} = Pattern.of_match(pattern, guards, dynamic(), expr, :with, stack, context)
|
||||
{_, context} = of_expr(right, stack, context)
|
||||
context
|
||||
end
|
||||
|
||||
@@ -884,7 +884,7 @@ defmodule Path do
|
||||
* A `..` component would make it so that the path would traverse up above
|
||||
the root of `relative_to`.
|
||||
|
||||
* A symbolic link in the path points to something above the root of `cwd`.
|
||||
* A symbolic link in the path points to something above the root of `relative_to`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -906,10 +906,10 @@ defmodule Path do
|
||||
"""
|
||||
@doc since: "1.14.0"
|
||||
@spec safe_relative(t, t) :: {:ok, binary} | :error
|
||||
def safe_relative(path, cwd \\ File.cwd!()) do
|
||||
def safe_relative(path, relative_to \\ File.cwd!()) do
|
||||
path = IO.chardata_to_string(path)
|
||||
|
||||
case :filelib.safe_relative_path(path, cwd) do
|
||||
case :filelib.safe_relative_path(path, relative_to) do
|
||||
:unsafe -> :error
|
||||
relative_path -> {:ok, IO.chardata_to_string(relative_path)}
|
||||
end
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -157,7 +157,7 @@ say me
|
||||
say me
|
||||
```
|
||||
|
||||
Yes, it works! However, does it *scale*?
|
||||
Yes, it works! However, can it handle more than one client?
|
||||
|
||||
Try to connect two telnet clients at the same time. When you do so, you will notice that the second client doesn't echo:
|
||||
|
||||
@@ -303,4 +303,8 @@ Luckily, this can be done by using `Supervisor.child_spec/2`, which allows us to
|
||||
|
||||
Now we have an always running acceptor that starts temporary task processes under an always running task supervisor.
|
||||
|
||||
## Wrapping up
|
||||
|
||||
In this chapter, we implemented a basic TCP acceptor while exploring concurrency and fault-tolerance. Our acceptor can manage concurrent connections, but it is still not ready for production. Production-ready TCP servers run a pool of acceptors, each with their own supervisor. Elixir's `PartitionSupervisor` might be used to partition and scale the acceptor, but it is out of scope for this guide. In practice, you will use existing packages tailored for this use-case, such as [Ranch](https://github.com/ninenines/ranch) (in Erlang) or [Thousand Island](https://github.com/mtrudel/thousand_island) (in Elixir).
|
||||
|
||||
In the next chapter, we will start parsing the client requests and sending responses, finishing our server.
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# Typespecs reference
|
||||
|
||||
Elixir comes with a notation for declaring types and specifications. This document is a
|
||||
reference into their uses and syntax.
|
||||
> #### Typespecs are not set-theoretic types {: .warning}
|
||||
>
|
||||
> Elixir is in the process of implementing its
|
||||
> [own type system](./gradual-set-theoretic-types.md) based on set-theoretic types.
|
||||
> Typespecs, which are described in the following document, are a distinct notation
|
||||
> for declaring types and specifications based on Erlang.
|
||||
> Typespecs may be phased out as the set-theoretic type effort moves forward.
|
||||
|
||||
Elixir is a dynamically typed language, and as such, type specifications are never used by the compiler to optimize or modify code. Still, using type specifications is useful because:
|
||||
|
||||
@@ -48,6 +53,13 @@ The syntax Elixir provides for type specifications is similar to [the one in Erl
|
||||
|
||||
The notation to represent the union of types is the pipe `|`. For example, the typespec `type :: atom() | pid() | tuple()` creates a type `type` that can be either an `atom`, a `pid`, or a `tuple`. This is usually called a [sum type](https://en.wikipedia.org/wiki/Tagged_union) in other languages
|
||||
|
||||
> #### Differences with set-theoretic types {: .warning}
|
||||
>
|
||||
> While they do share some similarities, the types below do not map one-to-one
|
||||
> to the new types from the set theoretic type system.
|
||||
> For example, there is no plan to support subsets of the `integer()` type such
|
||||
> as positive, ranges or literals.
|
||||
|
||||
### Basic types
|
||||
|
||||
type ::
|
||||
|
||||
@@ -314,7 +314,7 @@ unpack_defaults(Kind, Meta, Name, Args, S, E) ->
|
||||
unpack_expanded(Kind, Meta, Name, [{'\\\\', DefaultMeta, [Expr, _]} | T] = List, VersionOffset, Acc, Clauses) ->
|
||||
Base = match_defaults(Acc, length(Acc) + VersionOffset, []),
|
||||
{Args, Invoke} = extract_defaults(List, length(Base) + VersionOffset, [], []),
|
||||
Clause = {Meta, Base ++ Args, [], {super, [{super, {Kind, Name}} | DefaultMeta], Base ++ Invoke}},
|
||||
Clause = {Meta, Base ++ Args, [], {super, [{super, {Kind, Name}}, {default, true} | DefaultMeta], Base ++ Invoke}},
|
||||
unpack_expanded(Kind, Meta, Name, T, VersionOffset, [Expr | Acc], [Clause | Clauses]);
|
||||
unpack_expanded(Kind, Meta, Name, [H | T], VersionOffset, Acc, Clauses) ->
|
||||
unpack_expanded(Kind, Meta, Name, T, VersionOffset, [H | Acc], Clauses);
|
||||
|
||||
@@ -818,9 +818,10 @@ handle_strings(T, Line, Column, H, Scope, Tokens) ->
|
||||
NewScope =
|
||||
case H of
|
||||
$' ->
|
||||
Message = "single-quoted strings represent charlists. "
|
||||
Message = "using single-quoted strings to represent charlists is deprecated.\n"
|
||||
"Use ~c\"\" if you indeed want a charlist or use \"\" instead.\n"
|
||||
"You may run \"mix format --migrate\" to fix this warning automatically.",
|
||||
"You may run \"mix format --migrate\" to change all single-quoted\n"
|
||||
"strings to use the ~c sigil and fix this warning.",
|
||||
prepend_warning(Line, Column-1, Message, InterScope);
|
||||
|
||||
_ ->
|
||||
@@ -1762,84 +1763,81 @@ add_cursor(_Line, Column, noprune, Terminators, Tokens) ->
|
||||
{Column, Terminators, Tokens};
|
||||
add_cursor(Line, Column, prune_and_cursor, Terminators, Tokens) ->
|
||||
PrePrunedTokens = prune_identifier(Tokens),
|
||||
{PrunedTokens, PrunedTerminators} = prune_tokens(PrePrunedTokens, [], Terminators),
|
||||
PrunedTokens = prune_tokens(PrePrunedTokens, []),
|
||||
CursorTokens = [
|
||||
{')', {Line, Column + 11, nil}},
|
||||
{'(', {Line, Column + 10, nil}},
|
||||
{paren_identifier, {Line, Column, nil}, '__cursor__'}
|
||||
| PrunedTokens
|
||||
],
|
||||
{Column + 12, PrunedTerminators, CursorTokens}.
|
||||
{Column + 12, Terminators, CursorTokens}.
|
||||
|
||||
prune_identifier([{identifier, _, _} | Tokens]) -> Tokens;
|
||||
prune_identifier(Tokens) -> Tokens.
|
||||
|
||||
%%% Any terminator needs to be closed
|
||||
prune_tokens([{'end', _} | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, ['end' | Opener], Terminators);
|
||||
prune_tokens([{')', _} | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, [')' | Opener], Terminators);
|
||||
prune_tokens([{']', _} | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, [']' | Opener], Terminators);
|
||||
prune_tokens([{'}', _} | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, ['}' | Opener], Terminators);
|
||||
prune_tokens([{'>>', _} | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, ['>>' | Opener], Terminators);
|
||||
prune_tokens([{'end', _} | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, ['end' | Opener]);
|
||||
prune_tokens([{')', _} | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, [')' | Opener]);
|
||||
prune_tokens([{']', _} | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, [']' | Opener]);
|
||||
prune_tokens([{'}', _} | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, ['}' | Opener]);
|
||||
prune_tokens([{'>>', _} | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, ['>>' | Opener]);
|
||||
%%% Close opened terminators
|
||||
prune_tokens([{'fn', _} | Tokens], ['end' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([{'do', _} | Tokens], ['end' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([{'(', _} | Tokens], [')' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([{'[', _} | Tokens], [']' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([{'{', _} | Tokens], ['}' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([{'<<', _} | Tokens], ['>>' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
%%% Handle anonymous functions
|
||||
prune_tokens([{'(', _}, {capture_op, _, _} | Tokens], [], [{'(', _, _} | Terminators]) ->
|
||||
prune_tokens(Tokens, [], Terminators);
|
||||
prune_tokens([{'fn', _} | Tokens], ['end' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([{'do', _} | Tokens], ['end' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([{'(', _} | Tokens], [')' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([{'[', _} | Tokens], [']' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([{'{', _} | Tokens], ['}' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([{'<<', _} | Tokens], ['>>' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
%%% or it is time to stop...
|
||||
prune_tokens([{';', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'eol', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{',', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'fn', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'do', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'(', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'[', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'{', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'<<', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{identifier, _, _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{block_identifier, _, _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{kw_identifier, _, _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{kw_identifier_safe, _, _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{kw_identifier_unsafe, _, _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{OpType, _, _} | _] = Tokens, [], Terminators)
|
||||
prune_tokens([{';', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'eol', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{',', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'fn', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'do', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'(', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'[', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'{', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'<<', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{identifier, _, _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{block_identifier, _, _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{kw_identifier, _, _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{kw_identifier_safe, _, _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{kw_identifier_unsafe, _, _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{OpType, _, _} | _] = Tokens, [])
|
||||
when OpType =:= comp_op; OpType =:= at_op; OpType =:= unary_op; OpType =:= and_op;
|
||||
OpType =:= or_op; OpType =:= arrow_op; OpType =:= match_op; OpType =:= in_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 =:= pipe_op; OpType =:= stab_op; OpType =:= when_op; OpType =:= assoc_op;
|
||||
OpType =:= rel_op; OpType =:= ternary_op; OpType =:= capture_op; OpType =:= ellipsis_op ->
|
||||
{Tokens, Terminators};
|
||||
Tokens;
|
||||
%%% or we traverse until the end.
|
||||
prune_tokens([_ | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([], [], Terminators) ->
|
||||
{[], Terminators}.
|
||||
prune_tokens([_ | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([], _Opener) ->
|
||||
[].
|
||||
|
||||
@@ -1335,19 +1335,22 @@ defmodule CodeFragmentTest do
|
||||
|
||||
test "do -> end" do
|
||||
assert cc2q!("if do\nx ->\n", trailing_fragment: "y\nz ->\nw\nend") ==
|
||||
s2q!("if do\nx ->\n__cursor__()\nend")
|
||||
s2q!("if do\nx ->\n__cursor__()\nz -> \nw\nend")
|
||||
|
||||
assert cc2q!("if do\nx ->\ny", trailing_fragment: "\nz ->\nw\nend") ==
|
||||
s2q!("if do\nx ->\n__cursor__()\nend")
|
||||
s2q!("if do\nx ->\n__cursor__()\nz -> \nw\nend")
|
||||
|
||||
assert cc2q!("if do\nx ->\ny\n", trailing_fragment: "\nz ->\nw\nend") ==
|
||||
s2q!("if do\nx ->\ny\n__cursor__()\nend")
|
||||
s2q!("if do\nx ->\ny\n__cursor__()\nz -> \nw\nend")
|
||||
|
||||
assert cc2q!("for x <- [], reduce: %{} do\ny, ", trailing_fragment: "-> :ok\nend") ==
|
||||
s2q!("for x <- [], reduce: %{} do\ny, __cursor__() -> :ok\nend")
|
||||
|
||||
assert cc2q!("for x <- [], reduce: %{} do\ny, z when ", trailing_fragment: "-> :ok\nend") ==
|
||||
s2q!("for x <- [], reduce: %{} do\ny, z when __cursor__() -> :ok\nend")
|
||||
|
||||
assert cc2q!("case do\na -> a\nb = ", trailing_fragment: "c -> c\nend") ==
|
||||
s2q!("case do\na -> a\nb = __cursor__() -> c\nend")
|
||||
end
|
||||
|
||||
test "removes tokens until opening" do
|
||||
|
||||
@@ -64,9 +64,7 @@ end
|
||||
test_parameters =
|
||||
if(PathHelpers.windows?(),
|
||||
do: [%{cli_extension: ".bat"}],
|
||||
else:
|
||||
[%{cli_extension: ""}] ++
|
||||
if(System.find_executable("pwsh"), do: [%{cli_extension: ".ps1"}], else: [])
|
||||
else: [%{cli_extension: ""}]
|
||||
)
|
||||
|
||||
defmodule Kernel.CLI.ExecutableTest do
|
||||
|
||||
@@ -205,6 +205,10 @@ defmodule KernelTest do
|
||||
|
||||
defmodule User do
|
||||
assert is_map(defstruct name: "john")
|
||||
# Ensure we keep the line information around.
|
||||
# It is important for debugging tools, ExDoc, etc.
|
||||
{:v1, :def, anno, _clauses} = Module.get_definition(__MODULE__, {:__struct__, 1})
|
||||
anno[:line] == __ENV__.line - 4
|
||||
end
|
||||
|
||||
test "struct/1 and struct/2" do
|
||||
|
||||
@@ -163,6 +163,9 @@ defmodule Module.Types.DescrTest do
|
||||
assert intersection(closed_map(a: integer()), open_map(b: not_set())) ==
|
||||
closed_map(a: integer())
|
||||
|
||||
assert intersection(closed_map(a: integer()), open_map(b: if_set(integer()))) ==
|
||||
closed_map(a: integer())
|
||||
|
||||
assert equal?(
|
||||
intersection(closed_map(a: integer()), closed_map(a: if_set(integer()))),
|
||||
closed_map(a: integer())
|
||||
|
||||
@@ -1139,6 +1139,14 @@ defmodule Module.Types.ExprTest do
|
||||
test "and does not report on literals" do
|
||||
assert typecheck!(false and true) == boolean()
|
||||
end
|
||||
|
||||
test "and reports violations" do
|
||||
assert typeerror!([x = 123], x and true) =~ """
|
||||
the following conditional expression will always evaluate to integer():
|
||||
|
||||
x
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
describe "receive" do
|
||||
|
||||
@@ -244,6 +244,39 @@ defmodule Module.Types.IntegrationTest do
|
||||
assert_no_warnings(files)
|
||||
end
|
||||
|
||||
test "incompatible default argument" do
|
||||
files = %{
|
||||
"a.ex" => """
|
||||
defmodule A do
|
||||
def ok(x = :ok \\\\ nil) do
|
||||
x
|
||||
end
|
||||
end
|
||||
"""
|
||||
}
|
||||
|
||||
warnings = [
|
||||
~S"""
|
||||
warning: incompatible types given as default arguments to ok/1:
|
||||
|
||||
-nil-
|
||||
|
||||
but expected one of:
|
||||
|
||||
dynamic(:ok)
|
||||
|
||||
typing violation found at:
|
||||
│
|
||||
2 │ def ok(x = :ok \\ nil) do
|
||||
│ ~
|
||||
│
|
||||
└─ a.ex:2:18: A.ok/0
|
||||
"""
|
||||
]
|
||||
|
||||
assert_warnings(files, warnings)
|
||||
end
|
||||
|
||||
test "returns diagnostics with source and file" do
|
||||
files = %{
|
||||
"a.ex" => """
|
||||
|
||||
@@ -75,6 +75,14 @@ defmodule TypespecTest do
|
||||
@type my_type :: %URI.t(){}
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise Kernel.TypespecError,
|
||||
~r"unexpected expression in typespec: t\.Foo",
|
||||
fn ->
|
||||
test_module do
|
||||
@type my_type :: t.Foo
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "invalid function specification" do
|
||||
@@ -120,7 +128,7 @@ defmodule TypespecTest do
|
||||
|
||||
test "redefined type" do
|
||||
assert_raise Kernel.TypespecError,
|
||||
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:126",
|
||||
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:134",
|
||||
fn ->
|
||||
test_module do
|
||||
@type foo :: atom
|
||||
@@ -129,7 +137,7 @@ defmodule TypespecTest do
|
||||
end
|
||||
|
||||
assert_raise Kernel.TypespecError,
|
||||
~r"type foo/2 is already defined in .*test/elixir/typespec_test.exs:136",
|
||||
~r"type foo/2 is already defined in .*test/elixir/typespec_test.exs:144",
|
||||
fn ->
|
||||
test_module do
|
||||
@type foo :: atom
|
||||
@@ -139,7 +147,7 @@ defmodule TypespecTest do
|
||||
end
|
||||
|
||||
assert_raise Kernel.TypespecError,
|
||||
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:145",
|
||||
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:153",
|
||||
fn ->
|
||||
test_module do
|
||||
@type foo :: atom
|
||||
@@ -841,6 +849,19 @@ defmodule TypespecTest do
|
||||
assert [{:atom, _, Keyword}, {:atom, _, :t}, [{:var, _, :value}]] = kw_with_value_args
|
||||
end
|
||||
|
||||
test "@type with macro in alias" do
|
||||
bytecode =
|
||||
test_module do
|
||||
defmacro module() do
|
||||
quote do: __MODULE__
|
||||
end
|
||||
|
||||
@type my_type :: module().Foo
|
||||
end
|
||||
|
||||
assert [type: {:my_type, {:atom, _, TypespecTest.TypespecSample.Foo}, []}] = types(bytecode)
|
||||
end
|
||||
|
||||
test "@type with a reserved signature" do
|
||||
assert_raise Kernel.TypespecError,
|
||||
~r"type required\/1 is a reserved type and it cannot be defined",
|
||||
|
||||
@@ -566,6 +566,10 @@ defmodule ExUnit.Case do
|
||||
group = Keyword.get(opts, :group, nil)
|
||||
parameterize = Keyword.get(opts, :parameterize, nil)
|
||||
|
||||
if not is_boolean(async?) do
|
||||
raise ArgumentError, ":async must be a boolean, got: #{inspect(async?)}"
|
||||
end
|
||||
|
||||
if not (parameterize == nil or (is_list(parameterize) and Enum.all?(parameterize, &is_map/1))) do
|
||||
raise ArgumentError, ":parameterize must be a list of maps, got: #{inspect(parameterize)}"
|
||||
end
|
||||
|
||||
@@ -878,7 +878,8 @@ defmodule ExUnit.Diff do
|
||||
end
|
||||
|
||||
defp rebuild_split_strings(%{contents: contents, delimiter: delimiter}, right) do
|
||||
%{contents: contents ++ [{false, right}], delimiter: delimiter}
|
||||
{new_right, diff} = extract_diff_meta(right)
|
||||
%{contents: contents ++ [{diff, new_right}], delimiter: delimiter}
|
||||
end
|
||||
|
||||
defp rebuild_concat_string(literal, nil, []) do
|
||||
|
||||
@@ -149,8 +149,6 @@ defmodule ExUnit.DocTest do
|
||||
suite run.
|
||||
"""
|
||||
|
||||
@opaque_type_regex ~r/#[\w\.]+</
|
||||
|
||||
defmodule Error do
|
||||
@moduledoc """
|
||||
Exception raised when there's an error with the syntax or semantics of a doctest.
|
||||
@@ -554,7 +552,7 @@ defmodule ExUnit.DocTest do
|
||||
message = "Doctest did not compile, got: #{ex_message}"
|
||||
|
||||
message =
|
||||
if e.__struct__ == TokenMissingError and expr =~ @opaque_type_regex do
|
||||
if e.__struct__ == TokenMissingError and expr =~ ~r/#[\w\.]+</ do
|
||||
message <>
|
||||
"""
|
||||
\nIf you are planning to assert on the result of an iex> expression \
|
||||
|
||||
@@ -1133,6 +1133,12 @@ defmodule ExUnit.DiffTest do
|
||||
"-<<trap::binary-size(3)>> <> \"baz\"-",
|
||||
"+\"foobar\"+"
|
||||
)
|
||||
|
||||
refute_diff(
|
||||
"hello " <> <<_::binary-size(6)>> = "hello world",
|
||||
"\"hello \" <> -<<_::binary-size(6)>>-",
|
||||
"\"hello +world+\""
|
||||
)
|
||||
end
|
||||
|
||||
test "underscore" do
|
||||
|
||||
@@ -363,7 +363,7 @@ defmodule IEx.Autocomplete do
|
||||
end)
|
||||
|
||||
entries =
|
||||
for {key, _value} <- pairs,
|
||||
for key when key != :__struct__ <- Map.keys(pairs),
|
||||
name = Atom.to_string(key),
|
||||
if(hint == "",
|
||||
do: not String.starts_with?(name, "_"),
|
||||
|
||||
@@ -430,6 +430,19 @@ defmodule IEx.AutocompleteTest do
|
||||
assert {:yes, ~c"ry: ", []} = expand(~c"%URI{var | path: \"foo\", que")
|
||||
assert {:no, [], []} = expand(~c"%URI{var | path: \"foo\", unkno")
|
||||
assert {:no, [], []} = expand(~c"%Unknown{var | path: \"foo\", unkno")
|
||||
|
||||
eval("var = %URI{}")
|
||||
|
||||
assert {:yes, ~c"", entries} = expand(~c"%{var | ")
|
||||
assert ~c"path:" in entries
|
||||
assert ~c"query:" in entries
|
||||
|
||||
assert {:yes, ~c"", entries} = expand(~c"%{var | path: \"foo\",")
|
||||
assert ~c"path:" not in entries
|
||||
assert ~c"query:" in entries
|
||||
|
||||
assert {:yes, ~c"ry: ", []} = expand(~c"%{var | path: \"foo\", que")
|
||||
assert {:no, [], []} = expand(~c"%URI{var | path: \"foo\", unkno")
|
||||
end
|
||||
|
||||
test "completion for map keys in update syntax" do
|
||||
|
||||
@@ -352,6 +352,11 @@ defmodule Mix do
|
||||
* `MIX_INSTALL_DIR` *(since v1.12.0)* - specifies directory where `Mix.install/2` keeps
|
||||
install cache
|
||||
|
||||
* `MIX_OS_CONCURRENCY_LOCK` - when set to `0` or `false`, disables mix compilation locking.
|
||||
While not recommended, this may be necessary in cases where hard links or TCP sockets are
|
||||
not available. When opting for this behaviour, make sure to not start concurrent compilations
|
||||
of the same project.
|
||||
|
||||
* `MIX_PATH` - appends extra code paths
|
||||
|
||||
* `MIX_PROFILE` - a list of comma-separated Mix tasks to profile the time spent on
|
||||
|
||||
@@ -917,7 +917,10 @@ defmodule Mix.Project do
|
||||
build_path = build_path(config)
|
||||
|
||||
on_taken = fn os_pid ->
|
||||
Mix.shell().info("Waiting for lock on the build directory (held by process #{os_pid})")
|
||||
Mix.shell().error([
|
||||
IO.ANSI.reset(),
|
||||
"Waiting for lock on the build directory (held by process #{os_pid})"
|
||||
])
|
||||
end
|
||||
|
||||
Mix.Sync.Lock.with_lock(build_path, fun, on_taken: on_taken)
|
||||
@@ -931,7 +934,10 @@ defmodule Mix.Project do
|
||||
deps_path = deps_path(config)
|
||||
|
||||
on_taken = fn os_pid ->
|
||||
Mix.shell().info("Waiting for lock on the deps directory (held by process #{os_pid})")
|
||||
Mix.shell().error([
|
||||
IO.ANSI.reset(),
|
||||
"Waiting for lock on the deps directory (held by process #{os_pid})"
|
||||
])
|
||||
end
|
||||
|
||||
Mix.Sync.Lock.with_lock(deps_path, fun, on_taken: on_taken)
|
||||
|
||||
@@ -126,17 +126,17 @@ defmodule Mix.SCM.Git do
|
||||
update_origin(opts[:git])
|
||||
|
||||
# Fetch external data
|
||||
rev = get_lock_rev(opts[:lock], opts) || get_opts_rev(opts)
|
||||
lock_rev = get_lock_rev(opts[:lock], opts)
|
||||
|
||||
["--git-dir=.git", "fetch", "--force", "--quiet"]
|
||||
|> Kernel.++(progress_switch(git_version()))
|
||||
|> Kernel.++(tags_switch(opts[:tag]))
|
||||
|> Kernel.++(depth_switch(opts[:depth]))
|
||||
|> Kernel.++(refspec_switch(opts, rev))
|
||||
|> Kernel.++(refspec_switch(opts, lock_rev || get_opts_rev(opts)))
|
||||
|> git!()
|
||||
|
||||
# Migrate the Git repo
|
||||
rev = rev || default_branch()
|
||||
rev = lock_rev || get_origin_opts_rev(opts) || default_branch()
|
||||
git!(["--git-dir=.git", "checkout", "--quiet", rev])
|
||||
|
||||
if opts[:submodules] do
|
||||
@@ -314,6 +314,14 @@ defmodule Mix.SCM.Git do
|
||||
opts[:branch] || opts[:ref] || opts[:tag]
|
||||
end
|
||||
|
||||
defp get_origin_opts_rev(opts) do
|
||||
if branch = opts[:branch] do
|
||||
"origin/#{branch}"
|
||||
else
|
||||
opts[:ref] || opts[:tag]
|
||||
end
|
||||
end
|
||||
|
||||
defp redact_uri(git) do
|
||||
case URI.parse(git) do
|
||||
%{userinfo: nil} -> git
|
||||
|
||||
@@ -79,6 +79,10 @@ defmodule Mix.Sync.Lock do
|
||||
This function can also be called if this process already has the
|
||||
lock. In such case the function is executed immediately.
|
||||
|
||||
When the `MIX_OS_CONCURRENCY_LOCK` environment variable is set to
|
||||
a falsy value, the lock is ignored and the function is executed
|
||||
immediately.
|
||||
|
||||
## Options
|
||||
|
||||
* `:on_taken` - a one-arity function called if the lock is held
|
||||
@@ -93,12 +97,12 @@ defmodule Mix.Sync.Lock do
|
||||
opts = Keyword.validate!(opts, [:on_taken])
|
||||
|
||||
hash = key |> :erlang.md5() |> Base.url_encode64(padding: false)
|
||||
path = Path.join([System.tmp_dir!(), "mix_lock", hash])
|
||||
path = Path.join(base_path(), hash)
|
||||
|
||||
pdict_key = {__MODULE__, path}
|
||||
has_lock? = Process.get(pdict_key)
|
||||
has_lock? = Process.get(pdict_key, false)
|
||||
|
||||
if has_lock? do
|
||||
if has_lock? or lock_disabled?() do
|
||||
fun.()
|
||||
else
|
||||
lock = lock(path, opts[:on_taken])
|
||||
@@ -115,6 +119,14 @@ defmodule Mix.Sync.Lock do
|
||||
end
|
||||
end
|
||||
|
||||
defp base_path do
|
||||
# We include user in the dir to avoid permission conflicts across users
|
||||
user = System.get_env("USER", "default")
|
||||
Path.join(System.tmp_dir!(), "mix_lock_#{Base.url_encode64(user, padding: false)}")
|
||||
end
|
||||
|
||||
defp lock_disabled?(), do: System.get_env("MIX_OS_CONCURRENCY_LOCK") in ~w(0 false)
|
||||
|
||||
defp lock(path, on_taken) do
|
||||
File.mkdir_p!(path)
|
||||
|
||||
@@ -198,11 +210,12 @@ defmodule Mix.Sync.Lock do
|
||||
:invalidated
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.LinkError,
|
||||
reason: reason,
|
||||
action: "create hard link",
|
||||
existing: port_path,
|
||||
new: lock_path
|
||||
Mix.raise("""
|
||||
could not create hard link from #{port_path} to "#{lock_path}: #{:file.format_error(reason)}.
|
||||
|
||||
Hard link support is required for Mix compilation locking. If your system \
|
||||
does not support hard links, set MIX_OS_CONCURRENCY_LOCK=0\
|
||||
""")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -274,7 +274,13 @@ defmodule Mix.Sync.PubSub do
|
||||
|
||||
defp path(hash) do
|
||||
hash = Base.url_encode64(hash, padding: false)
|
||||
Path.join([System.tmp_dir!(), "mix_pubsub", hash])
|
||||
Path.join(base_path(), hash)
|
||||
end
|
||||
|
||||
defp base_path do
|
||||
# We include user in the dir to avoid permission conflicts across users
|
||||
user = System.get_env("USER", "default")
|
||||
Path.join(System.tmp_dir!(), "mix_pubsub_#{Base.url_encode64(user, padding: false)}")
|
||||
end
|
||||
|
||||
defp recv(socket, size, timeout \\ :infinity) do
|
||||
|
||||
@@ -113,7 +113,7 @@ defmodule Mix.Tasks.Test do
|
||||
and imports (but not local functions). You can press `n` for the next line
|
||||
and `c` for the next test. This automatically sets `--trace`
|
||||
|
||||
* `--color` - enables color in the output
|
||||
* `--color` - enables color in ExUnit formatting results
|
||||
|
||||
* `--cover` - runs coverage tool. See "Coverage" section below
|
||||
|
||||
|
||||
Reference in New Issue
Block a user