Skip to content

Commit 53e3d8f

Browse files
wojtekmachjosevalim
authored andcommitted
Add :cd option to Mix.Shell.cmd/2 and --cd to mix cmd (#10029)
1 parent f53b8c8 commit 53e3d8f

File tree

4 files changed

+49
-13
lines changed

4 files changed

+49
-13
lines changed

lib/mix/lib/mix/shell.ex

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ defmodule Mix.Shell do
7676
7777
## Options
7878
79+
* `:cd` - (since v1.11.0) the directory to run the command in
80+
7981
* `:stderr_to_stdout` - redirects stderr to stdout, defaults to true
82+
8083
* `:env` - a list of environment variables, defaults to `[]`
84+
8185
* `:quiet` - overrides the callback to no-op
8286
8387
"""
@@ -98,7 +102,10 @@ defmodule Mix.Shell do
98102
[]
99103
end
100104

101-
opts = [:stream, :binary, :exit_status, :hide, :use_stdio, {:env, env} | args]
105+
opts =
106+
[:stream, :binary, :exit_status, :hide, :use_stdio, {:env, env}] ++
107+
args ++ Keyword.take(options, [:cd])
108+
102109
port = Port.open({:spawn, shell_command(command)}, opts)
103110
port_read(port, callback)
104111
end

lib/mix/lib/mix/tasks/cmd.ex

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ defmodule Mix.Tasks.Cmd do
2222
This task is automatically reenabled, so it can be called multiple times
2323
with different arguments.
2424
25+
## Command line options
26+
27+
* `--app` - limit running the command to the given app. This option
28+
may be given multiple times
29+
30+
* `--cd` - (since v1.11.0) the directory to run the command in
31+
2532
## Zombie operating system processes
2633
2734
Beware that the Erlang VM does not terminate child processes
@@ -35,27 +42,25 @@ defmodule Mix.Tasks.Cmd do
3542
of the `Port` module documentation.
3643
"""
3744

45+
@switches [
46+
app: :keep,
47+
cd: :string
48+
]
49+
3850
@impl true
3951
def run(args) do
40-
{args, apps} = parse_apps(args, [])
52+
{opts, args} = OptionParser.parse_head!(args, strict: @switches)
53+
apps = Enum.map(List.wrap(opts[:app]), &String.to_atom/1)
4154

4255
if apps == [] or Mix.Project.config()[:app] in apps do
43-
case Mix.shell().cmd(Enum.join(args, " ")) do
56+
cmd_opts = Keyword.take(opts, [:cd])
57+
58+
case Mix.shell().cmd(Enum.join(args, " "), cmd_opts) do
4459
0 -> :ok
4560
status -> exit(status)
4661
end
4762
end
4863

4964
Mix.Task.reenable("cmd")
5065
end
51-
52-
defp parse_apps(args, apps) do
53-
case args do
54-
["--app", app | tail] ->
55-
parse_apps(tail, [String.to_atom(app) | apps])
56-
57-
args ->
58-
{args, apps}
59-
end
60-
end
6166
end

lib/mix/test/mix/shell_test.exs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,16 @@ defmodule Mix.ShellTest do
1717
after
1818
Mix.shell(Mix.Shell.Process)
1919
end
20+
21+
test "with :cd" do
22+
Mix.shell(Mix.Shell.IO)
23+
tmp_dir = System.tmp_dir()
24+
{pwd, 0} = System.cmd("pwd", [], cd: tmp_dir)
25+
26+
assert ExUnit.CaptureIO.capture_io(fn ->
27+
Mix.shell().cmd("pwd", cd: tmp_dir)
28+
end) == pwd
29+
after
30+
Mix.shell(Mix.Shell.Process)
31+
end
2032
end

lib/mix/test/mix/tasks/cmd_test.exs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,16 @@ defmodule Mix.Tasks.CmdTest do
3636
end)
3737
end)
3838
end
39+
40+
test "only runs the cmd for specified apps and in specific directory" do
41+
in_fixture("umbrella_dep/deps/umbrella", fn ->
42+
Mix.Project.in_project(:umbrella, ".", fn _ ->
43+
Mix.Task.run("cmd", ["--app", "bar", "--cd", "lib", "pwd"])
44+
assert_received {:mix_shell, :info, ["==> bar"]}
45+
{pwd, 0} = System.cmd("pwd", [], cd: Path.join(["apps", "bar", "lib"]))
46+
assert_received {:mix_shell, :run, [^pwd]}
47+
refute_received {:mix_shell, :info, ["==> foo"]}
48+
end)
49+
end)
50+
end
3951
end

0 commit comments

Comments
 (0)