From ef7c11814f2a766c19c6623ff194d0f23da471ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 10 Dec 2024 15:38:02 +0100 Subject: [PATCH] Require Erlang/OTP 26+ (#14045) --- .github/workflows/ci.yml | 4 +- .github/workflows/release.yml | 2 - Makefile | 4 +- RELEASE.md | 2 +- bin/elixir | 4 - bin/elixir.ps1 | 2 +- lib/elixir/lib/application.ex | 16 +-- lib/elixir/lib/code.ex | 8 +- lib/elixir/lib/string.ex | 1 - .../compatibility-and-deprecations.md | 2 + lib/elixir/src/elixir.erl | 48 ++----- lib/elixir/src/elixir_json.erl | 6 - lib/iex/lib/iex/autocomplete.ex | 4 +- lib/iex/lib/iex/broker.ex | 26 +--- lib/iex/lib/iex/cli.ex | 136 ------------------ lib/mix/lib/mix/tasks/app.start.ex | 3 +- 16 files changed, 27 insertions(+), 241 deletions(-) delete mode 100644 lib/iex/lib/iex/cli.ex diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 978f36a2f9e..64c0cf4b038 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,8 +29,6 @@ jobs: otp_latest: true - otp_version: "27.0" - otp_version: "26.0" - - otp_version: "25.3" - - otp_version: "25.0" - otp_version: master development: true - otp_version: maint @@ -81,7 +79,7 @@ jobs: name: Windows Server 2019, Erlang/OTP ${{ matrix.otp_version }} strategy: matrix: - otp_version: ["25.3", "26.2", "27.1"] + otp_version: ["26.2", "27.1"] runs-on: windows-2022 steps: - name: Configure Git diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 23557c18b44..aea957f7baa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,8 +60,6 @@ jobs: fail-fast: true matrix: include: - - otp: 25 - otp_version: "25.3" - otp: 26 otp_version: "26.0" - otp: 27 diff --git a/Makefile b/Makefile index b6f51b1ffbc..b5f911a9be4 100644 --- a/Makefile +++ b/Makefile @@ -27,9 +27,9 @@ SOURCE_DATE_EPOCH_FILE = $(SOURCE_DATE_EPOCH_PATH)/SOURCE_DATE_EPOCH #==> Functions define CHECK_ERLANG_RELEASE - erl -noshell -eval '{V,_} = string:to_integer(erlang:system_info(otp_release)), io:fwrite("~s", [is_integer(V) and (V >= 25)])' -s erlang halt | grep -q '^true'; \ + erl -noshell -eval '{V,_} = string:to_integer(erlang:system_info(otp_release)), io:fwrite("~s", [is_integer(V) and (V >= 26)])' -s erlang halt | grep -q '^true'; \ if [ $$? != 0 ]; then \ - echo "At least Erlang/OTP 25.0 is required to build Elixir"; \ + echo "At least Erlang/OTP 26.0 is required to build Elixir"; \ exit 1; \ fi endef diff --git a/RELEASE.md b/RELEASE.md index dc769aec9f7..83aa5ffd958 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -30,7 +30,7 @@ ### Back in main -1. Bump /VERSION file, bin/elixir and bin/elixir.bat +1. Bump /VERSION file, bin/elixir, bin/elixir.bat, and bin/elixir.ps1 2. Start new /CHANGELOG.md diff --git a/bin/elixir b/bin/elixir index 58ac1a82f0c..9de16cc6365 100755 --- a/bin/elixir +++ b/bin/elixir @@ -215,10 +215,6 @@ SCRIPT_PATH=$(dirname "$SELF") if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "$SCRIPT_PATH"); fi if [ "$MODE" != "iex" ]; then ERL="-s elixir start_cli $ERL"; fi -if [ "$OS" != "Windows_NT" ] && [ -z "$NO_COLOR" ]; then - if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi -fi - # One MAY change ERTS_BIN= but you MUST NOT change # ERTS_BIN=$ERTS_BIN as it is handled by Elixir releases. ERTS_BIN= diff --git a/bin/elixir.ps1 b/bin/elixir.ps1 index 90048705549..81d7161b51a 100755 --- a/bin/elixir.ps1 +++ b/bin/elixir.ps1 @@ -1,6 +1,6 @@ #!/usr/bin/env pwsh -$ELIXIR_VERSION = "1.18.0-dev" +$ELIXIR_VERSION = "1.19.0-dev" $scriptPath = Split-Path -Parent $PSCommandPath $erlExec = "erl" diff --git a/lib/elixir/lib/application.ex b/lib/elixir/lib/application.ex index e5343372339..c31594eaa97 100644 --- a/lib/elixir/lib/application.ex +++ b/lib/elixir/lib/application.ex @@ -908,8 +908,7 @@ defmodule Application do `:permanent`, or `:transient`. See `t:restart_type/1` for more information. * `:mode` - (since v1.15.0) if the applications should be started serially - (`:serial`, default) or concurrently (`:concurrent`). This option requires - Erlang/OTP 26+. + (`:serial`, default) or concurrently (`:concurrent`). """ @spec ensure_all_started(app | [app], type: restart_type(), mode: :serial | :concurrent) :: @@ -930,18 +929,7 @@ defmodule Application do def ensure_all_started(apps, opts) when is_list(apps) and is_list(opts) do opts = Keyword.validate!(opts, type: :temporary, mode: :serial) - - if function_exported?(:application, :ensure_all_started, 3) do - :application.ensure_all_started(apps, opts[:type], opts[:mode]) - else - # TODO: Remove this clause when we require Erlang/OTP 26+ - Enum.reduce_while(apps, {:ok, []}, fn app, {:ok, acc} -> - case :application.ensure_all_started(app, opts[:type]) do - {:ok, apps} -> {:cont, {:ok, apps ++ acc}} - {:error, e} -> {:halt, {:error, e}} - end - end) - end + :application.ensure_all_started(apps, opts[:type], opts[:mode]) end @doc """ diff --git a/lib/elixir/lib/code.ex b/lib/elixir/lib/code.ex index 1a4a400f06b..389491f5f14 100644 --- a/lib/elixir/lib/code.ex +++ b/lib/elixir/lib/code.ex @@ -343,7 +343,7 @@ defmodule Code do * `:cache` - (since v1.15.0) when true, the code path is cached the first time it is traversed in order to reduce file system - operations. It requires Erlang/OTP 26, otherwise it is a no-op. + operations. """ @spec append_path(Path.t(), cache: boolean()) :: true | false @@ -374,7 +374,7 @@ defmodule Code do * `:cache` - (since v1.15.0) when true, the code path is cached the first time it is traversed in order to reduce file system - operations. It requires Erlang/OTP 26, otherwise it is a no-op. + operations. """ @spec prepend_path(Path.t(), cache: boolean()) :: boolean() @@ -403,7 +403,7 @@ defmodule Code do * `:cache` - when true, the code path is cached the first time it is traversed in order to reduce file system operations. - It requires Erlang/OTP 26, otherwise it is a no-op. + """ @doc since: "1.15.0" @spec prepend_paths([Path.t()], cache: boolean()) :: :ok @@ -432,7 +432,7 @@ defmodule Code do * `:cache` - when true, the code path is cached the first time it is traversed in order to reduce file system operations. - It requires Erlang/OTP 26, otherwise it is a no-op. + """ @doc since: "1.15.0" @spec append_paths([Path.t()], cache: boolean()) :: :ok diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index e8b3a61c8ca..0b95ba4a139 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -1829,7 +1829,6 @@ defmodule String do the `:fast_ascii` algorithm to see if it yields performance benefits in your specific scenario: - * You are running Erlang/OTP 26 or newer on a 64 bit platform * You expect most of your strings to be longer than ~64 bytes * You expect most of your strings to contain mostly ASCII codepoints diff --git a/lib/elixir/pages/references/compatibility-and-deprecations.md b/lib/elixir/pages/references/compatibility-and-deprecations.md index cd493ba8c30..c73c6652a75 100644 --- a/lib/elixir/pages/references/compatibility-and-deprecations.md +++ b/lib/elixir/pages/references/compatibility-and-deprecations.md @@ -43,6 +43,8 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea Elixir version | Supported Erlang/OTP versions :------------- | :------------------------------- +1.19 | 26 - 27 +1.18 | 25 - 27 1.17 | 25 - 27 1.16 | 24 - 26 1.15 | 24 - 26 diff --git a/lib/elixir/src/elixir.erl b/lib/elixir/src/elixir.erl index 9a6a6238108..6914c281923 100644 --- a/lib/elixir/src/elixir.erl +++ b/lib/elixir/src/elixir.erl @@ -26,27 +26,22 @@ -type struct() :: #{'__struct__' := atom(), atom() => any()}. %% OTP Application API -%% TODO: Remove Erlang/OTP 26+ checks - -load_paths(OTP, Paths) when OTP >= 26 -> code:add_pathsa(Paths, cache); -load_paths(_OTP, Paths) -> code:add_pathsa(Paths). start(_Type, _Args) -> - OTP = parse_otp_release(), preload_common_modules(), - set_stdio_and_stderr_to_binary_and_maybe_utf8(OTP), + ok = io:setopts(standard_io, [binary]), check_file_encoding(file:native_name_encoding()), case init:get_argument(elixir_root) of {ok, [[Root]]} -> - load_paths(OTP, [ + code:add_pathsa([ Root ++ "/eex/ebin", Root ++ "/ex_unit/ebin", Root ++ "/iex/ebin", Root ++ "/logger/ebin", Root ++ "/mix/ebin", Root ++ "/elixir/ebin" - ]); + ], cache); _ -> ok end, @@ -57,11 +52,11 @@ start(_Type, _Args) -> end, case application:get_env(elixir, ansi_enabled) of - {ok, _} -> ok; + {ok, _} -> + ok; + undefined -> - %% Remove prim_tty module check as well as checks from scripts on Erlang/OTP 26 - ANSIEnabled = erlang:module_loaded(prim_tty) andalso (prim_tty:isatty(stdout) == true), - application:set_env(elixir, ansi_enabled, ANSIEnabled) + application:set_env(elixir, ansi_enabled, prim_tty:isatty(stdout) == true) end, Tokenizer = case code:ensure_loaded('Elixir.String.Tokenizer') of @@ -117,19 +112,6 @@ stop(Tab) -> config_change(_Changed, _New, _Remove) -> ok. -set_stdio_and_stderr_to_binary_and_maybe_utf8(OTP) when OTP >= 26 -> - ok = io:setopts(standard_io, [binary]), - ok; -set_stdio_and_stderr_to_binary_and_maybe_utf8(_OTP) -> - Opts = - case init:get_argument(noshell) of - {ok, _} -> [binary, {encoding, utf8}]; - error -> [binary] - end, - ok = io:setopts(standard_io, Opts), - ok = io:setopts(standard_error, [{encoding, utf8}]), - ok. - preload_common_modules() -> %% We attempt to load those modules here so throughout %% the codebase we can avoid code:ensure_loaded/1 checks. @@ -140,10 +122,10 @@ preload_common_modules() -> parse_otp_release() -> %% Whenever we change this check, we should also change Makefile. case string:to_integer(erlang:system_info(otp_release)) of - {Num, _} when Num >= 25 -> + {Num, _} when Num >= 26 -> Num; _ -> - io:format(standard_error, "ERROR! Unsupported Erlang/OTP version, expected Erlang/OTP 25+~n", []), + io:format(standard_error, "ERROR! Unsupported Erlang/OTP version, expected Erlang/OTP 26+~n", []), erlang:halt(1) end. @@ -177,19 +159,9 @@ check_file_encoding(Encoding) -> end. %% Boot and process given options. Invoked by Elixir's script. -%% TODO: Delete prim_tty branches on Erlang/OTP 26. start() -> - case code:ensure_loaded(prim_tty) of - {module, _} -> - user_drv:start(#{initial_shell => iex:shell()}); - {error, _} -> - case init:get_argument(elixir_root) of - {ok, [[Root]]} -> code:add_patha(Root ++ "/iex/ebin"); - _ -> ok - end, - 'Elixir.IEx.CLI':deprecated() - end. + user_drv:start(#{initial_shell => iex:shell()}). start_cli() -> {ok, _} = application:ensure_all_started(elixir), diff --git a/lib/elixir/src/elixir_json.erl b/lib/elixir/src/elixir_json.erl index 1b8bb6a4298..f2a443c2fcc 100644 --- a/lib/elixir/src/elixir_json.erl +++ b/lib/elixir/src/elixir_json.erl @@ -314,14 +314,8 @@ list_loop([Elem | Rest], Encode) -> [$,, Encode(Elem, Encode) | list_loop(Rest, encode_map(Map, Encode) when is_map(Map) -> do_encode_map(Map, Encode). -%% TODO: Remove conditional once Erlang/OTP 26+ is exclusively supported. --if(?OTP_RELEASE >= 26). do_encode_map(Map, Encode) when is_function(Encode, 2) -> encode_object([[$,, key(Key, Encode), $: | Encode(Value, Encode)] || Key := Value <- Map]). --else. -do_encode_map(Map, Encode) when is_function(Encode, 2) -> - encode_object([[$,, key(Key, Encode), $: | Encode(Value, Encode)] || {Key, Value} <- maps:to_list(Map)]). --endif. -spec encode_map_checked(map(), encoder()) -> iodata(). encode_map_checked(Map, Encode) -> diff --git a/lib/iex/lib/iex/autocomplete.ex b/lib/iex/lib/iex/autocomplete.ex index ae245e2a6fb..b424dd4be47 100644 --- a/lib/iex/lib/iex/autocomplete.ex +++ b/lib/iex/lib/iex/autocomplete.ex @@ -28,7 +28,7 @@ defmodule IEx.Autocomplete do def remsh(node) do fn e -> try do - :erpc.call(node, IEx.Autocomplete, :expand, [e, IEx.Broker.shell()]) + :erpc.call(node, IEx.Autocomplete, :expand, [e, :shell.whereis()]) catch _, _ -> {:no, ~c"", []} end @@ -41,7 +41,7 @@ defmodule IEx.Autocomplete do Some of the expansion has to use the current shell environment, which is found via the broker. """ - def expand(code, shell \\ IEx.Broker.shell()) do + def expand(code, shell \\ :shell.whereis()) do case path_fragment(code) do [] -> expand_code(code, shell) path -> expand_path(path) diff --git a/lib/iex/lib/iex/broker.ex b/lib/iex/lib/iex/broker.ex index c329394571f..d4b041e91e3 100644 --- a/lib/iex/lib/iex/broker.ex +++ b/lib/iex/lib/iex/broker.ex @@ -9,35 +9,11 @@ defmodule IEx.Broker do ## Shell API - @doc """ - Finds the IEx server. - """ - @spec shell :: shell() - # TODO: Use shell:whereis() from Erlang/OTP 26+. - def shell() do - if user = Process.whereis(:user) do - if user_drv = get_from_dict(user, :user_drv) do - if group = get_from_dict(user_drv, :current_group) do - get_from_dict(group, :shell) - end - end - end - end - - defp get_from_dict(pid, key) do - with {:dictionary, dictionary} <- Process.info(pid, :dictionary), - {^key, value} <- List.keyfind(dictionary, key, 0) do - value - else - _ -> nil - end - end - @doc """ Finds the evaluator and server running inside `:user_drv`, on this node exclusively. """ @spec evaluator(shell()) :: {evaluator :: pid, server :: pid} | nil - def evaluator(pid \\ shell()) do + def evaluator(pid \\ :shell.whereis()) do if pid do {:dictionary, dictionary} = Process.info(pid, :dictionary) {dictionary[:evaluator], pid} diff --git a/lib/iex/lib/iex/cli.ex b/lib/iex/lib/iex/cli.ex deleted file mode 100644 index ad0c817d93c..00000000000 --- a/lib/iex/lib/iex/cli.ex +++ /dev/null @@ -1,136 +0,0 @@ -# TODO: Remove this whole module on Erlang/OTP 26+. -defmodule IEx.CLI do - @moduledoc false - - @compile {:no_warn_undefined, {:user, :start, 0}} - - def deprecated do - if tty_works?() do - :user_drv.start([:"tty_sl -c -e", tty_args()]) - else - if get_remsh(:init.get_plain_arguments()) do - IO.puts( - :stderr, - "warning: the --remsh option will be ignored because IEx is running on limited shell" - ) - end - - :user.start() - - spawn(fn -> - :application.ensure_all_started(:iex) - - case :init.notify_when_started(self()) do - :started -> :ok - _ -> :init.wait_until_started() - end - - :ok = :io.setopts(binary: true, encoding: :unicode) - IEx.Server.run_from_shell([register: true] ++ options(), {:elixir, :start_cli, []}) - end) - end - end - - # Check if tty works. If it does not, we fall back to the - # simple/dumb terminal. This is starting the linked in - # driver twice, it would be nice and appropriate if we had - # to do it just once. - defp tty_works? do - try do - port = Port.open({:spawn, ~c"tty_sl -c -e"}, [:eof]) - Port.close(port) - catch - _, _ -> false - end - end - - defp tty_args do - if remote = get_remsh(:init.get_plain_arguments()) do - remote = List.to_atom(append_hostname(remote)) - - # Explicitly connect the node in case the rpc node was started with --sname/--name undefined. - _ = :net_kernel.connect_node(remote) - - case :rpc.call(remote, :code, :ensure_loaded, [IEx]) do - {:badrpc, reason} -> - message = - if reason == :nodedown and :net_kernel.nodename() == :ignored do - "In order to use --remsh, you need to name the current node using --name or --sname. Aborting..." - else - "Could not contact remote node #{remote}, reason: #{inspect(reason)}. Aborting..." - end - - abort(message) - - {:module, IEx} -> - case :rpc.call(remote, :net_kernel, :get_net_ticktime, []) do - seconds when is_integer(seconds) -> :net_kernel.set_net_ticktime(seconds) - _ -> :ok - end - - {mod, fun, args} = remote_start_mfa() - {remote, mod, fun, args} - - _ -> - abort("Could not find IEx on remote node #{remote}. Aborting...") - end - else - local_start_mfa() - end - end - - defp local_start_mfa do - {:iex, :start, [options(), {:elixir, :start_cli, []}]} - end - - def remote_start(parent, ref) do - send(parent, {:begin, ref, self()}) - receive do: ({:done, ^ref} -> :ok) - end - - defp remote_start_mfa do - ref = make_ref() - opts = options() - - parent = - spawn_link(fn -> - receive do - {:begin, ^ref, other} -> - :elixir.start_cli() - send(other, {:done, ref}) - end - end) - - {:iex, :start, [opts, {__MODULE__, :remote_start, [parent, ref]}]} - end - - defp options do - [dot_iex: find_dot_iex(:init.get_plain_arguments()), on_eof: :halt] - end - - defp abort(msg) do - function = fn -> - IO.puts(:stderr, msg) - System.halt(1) - end - - {:erlang, :apply, [function, []]} - end - - defp find_dot_iex([~c"--dot-iex", h | _]), do: List.to_string(h) - defp find_dot_iex([_ | t]), do: find_dot_iex(t) - defp find_dot_iex([]), do: nil - - defp get_remsh([~c"--remsh", h | _]), do: h - defp get_remsh([_ | t]), do: get_remsh(t) - defp get_remsh([]), do: nil - - defp append_hostname(node) do - with :nomatch <- :string.find(node, "@"), - [_ | _] = suffix <- :string.find(Atom.to_charlist(:net_kernel.nodename()), "@") do - node ++ suffix - else - _ -> node - end - end -end diff --git a/lib/mix/lib/mix/tasks/app.start.ex b/lib/mix/lib/mix/tasks/app.start.ex index 6c208fd20b6..85b382fc600 100644 --- a/lib/mix/lib/mix/tasks/app.start.ex +++ b/lib/mix/lib/mix/tasks/app.start.ex @@ -20,8 +20,7 @@ defmodule Mix.Tasks.App.Start do applications are started in permanent mode. Defaults to `false`. * `:start_concurrently` - applications are started concurrently - whenever possible. This option only has an effect on Erlang/OTP 26+. - Defaults to `false`. + whenever possible. Defaults to `false`. ## Command line options