From 201bdd73cd1e51ffbee40ac2a399f327da540296 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 30 Jan 2026 12:50:09 +0100 Subject: [PATCH 1/2] Add `System.find_executable!/1` --- lib/elixir/lib/system.ex | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/elixir/lib/system.ex b/lib/elixir/lib/system.ex index 36094065872..d93d036e58a 100644 --- a/lib/elixir/lib/system.ex +++ b/lib/elixir/lib/system.ex @@ -630,8 +630,19 @@ defmodule System do operating systems. It also considers the proper executable extension for each operating system, so for Windows it will try to lookup files with `.com`, `.cmd` or similar extensions. + + See also `find_executable!/1`. + + ## Examples + + System.find_executable("bash") + #=> "/bin/bash" + + System.find_executable("unknown") + #=> nil + """ - @spec find_executable(binary) :: binary | nil + @spec find_executable(binary()) :: binary() | nil def find_executable(program) when is_binary(program) do assert_no_null_byte!(program, "System.find_executable/1") @@ -641,6 +652,27 @@ defmodule System do end end + @doc """ + Locates an executable on the system or raises an error. + + See also `find_executable/1`. + + ## Examples + + System.find_executable!("bash") + #=> "/bin/bash" + + System.find_executable!("unknown") + ** (RuntimeError) could not find executable "unknown" in PATH + + """ + @doc since: "1.20.0" + @spec find_executable!(binary()) :: binary() + def find_executable!(program) when is_binary(program) do + find_executable(program) || + raise "could not find executable #{inspect(program)} in PATH" + end + @doc """ Returns all system environment variables. From 7769e4c460b09d44538a8d867f300a120ace4d60 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 30 Jan 2026 12:53:48 +0100 Subject: [PATCH 2/2] Use `System.find_executable!/1` in docs and tests --- lib/elixir/lib/port.ex | 4 ++-- lib/elixir/test/elixir/system_test.exs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/elixir/lib/port.ex b/lib/elixir/lib/port.ex index 6d1df7196b1..0ba915dddeb 100644 --- a/lib/elixir/lib/port.ex +++ b/lib/elixir/lib/port.ex @@ -124,9 +124,9 @@ defmodule Port do Spawn executable is a more restricted and explicit version of spawn. It expects full file paths to the executable you want to execute. If they are in your `$PATH`, - they can be retrieved by calling `System.find_executable/1`: + they can be retrieved by calling `System.find_executable!/1`: - iex> path = System.find_executable("echo") + iex> path = System.find_executable!("echo") iex> port = Port.open({:spawn_executable, path}, [:binary, args: ["hello world"]]) iex> flush() {#Port<0.1380>, {:data, "hello world\n"}} diff --git a/lib/elixir/test/elixir/system_test.exs b/lib/elixir/test/elixir/system_test.exs index b6d56dec778..d2f871c05d7 100644 --- a/lib/elixir/test/elixir/system_test.exs +++ b/lib/elixir/test/elixir/system_test.exs @@ -126,7 +126,7 @@ defmodule SystemTest do test "cmd/3 with absolute and relative paths", config do echo = Path.join(config.tmp_dir, @echo) File.mkdir_p!(Path.dirname(echo)) - File.ln_s!(System.find_executable("cmd"), echo) + File.ln_s!(System.find_executable!("cmd"), echo) File.cd!(Path.dirname(echo), fn -> # There is a bug in OTP where find_executable is finding @@ -210,7 +210,7 @@ defmodule SystemTest do test "cmd/3 with absolute and relative paths", config do echo = Path.join(config.tmp_dir, @echo) File.mkdir_p!(Path.dirname(echo)) - File.ln_s!(System.find_executable("echo"), echo) + File.ln_s!(System.find_executable!("echo"), echo) File.cd!(Path.dirname(echo), fn -> # There is a bug in OTP where find_executable is finding