What kinds of anonymous functions are supported in aliases?

I’d like to call an anonymous function in my Mixfile aliases to install brunch for my web applications as part of an overall install alias. However, I can’t get the syntax of the anonymous function correct.

defp aliases do
  ["install": [&install_brunch/1]]
end

defp install_brunch(_) do
  args =
    for app <- Mix.Project.config[:web_apps] do
      "--app #{app}"
    end
    |> Kernel.++(["npm install"])

  Mix.shell.cmd args
end

This setup produces the following error:

** (ArgumentError) argument error
    (stdlib) binary.erl:275: :binary.replace/4
    (mix) lib/mix/shell.ex:113: Mix.Shell.shell_command/1
    (mix) lib/mix/shell.ex:91: Mix.Shell.cmd/3
    (mix) lib/mix/task.ex:331: Mix.Task.run_alias/3
    (mix) lib/mix/task.ex:259: Mix.Task.run/2
    (mix) lib/mix/cli.ex:58: Mix.CLI.run_task/2

I’m a little confused because run_alias/3 I thought was executing the function directly, but the stacktrace looks like it’s trying to execute it using Mix.Task.run/2.

2 Likes

According to the documentation of Mix.Shell. cmd/1 you are supposed to pass in a string and not a list of strings. Your alias function itself is called very well.

1 Like

@NobbZ i think it’s possible to pass a list to an alias (a newly generated phoenix project does that)

defp aliases do
  ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
   "ecto.reset": ["ecto.drop", "ecto.setup"],
   "test": ["ecto.create --quiet", "ecto.migrate", "test"]]
end 

@wfgilman have you tried removing brackets?

defp aliases do
  ["install": &install_brunch/1]
end
2 Likes

Hmm :thinking: I thought I was calling Mix.Task.Cmd.run/1 with a command line equivalent of mix cmd --app web --app admin npm install.

With Mix.shell.cmd the only way I could get it to work was with the arguments as a list. Note the lower case shell.

2 Likes

Ultimately it will need to be a list because the full Keyword list will be ["deps.unlock --all", "deps.get --no-archives-check", "deps.compile", &install_brunch/1, "ecto.setup"].

But I did remove the backets and try it to no avail :confused:

1 Like

Can you try joining the args in install_brunch/1 with Enum.join(args, " "), please?

defp install_brunch(_) do
  args =
    for app <- Mix.Project.config[:web_apps] do
      "--app #{app}"
    end
    |> Kernel.++(["npm install"])

  args |> Enum.join(" ") |> Mix.shell.cmd
end
1 Like

Sure thing.

defp install_brunch(_) do
  args =
    for app <- Mix.Project.config[:web_apps] do
      "--app #{app}"
    end
    |> Kernel.++(["npm install"])
    |> Enum.join(" ")

  Mix.shell.cmd args
end

Produces:

$mix install
warning: undefined behaviour function run/1 (for behaviour Mix.Task)
  mix.exs:1

sh: --: invalid option
Usage:	sh [GNU long option] [option] ...
	sh [GNU long option] [option] script-file ...
GNU long options:
	--debug
	--debugger
	--dump-po-strings
	--dump-strings
	--help
	--init-file
	--login
	--noediting
	--noprofile
	--norc
	--posix
	--protected
	--rcfile
	--restricted
	--verbose
	--version
	--wordexp
Shell options:
	-irsD or -c command or -O shopt_option		(invocation only)
	-abefhkmnptuvxBCHP or -o option
1 Like

It seems that the command is run but there is a problem with the shell now, right?

sh: --: invalid option

Maybe try prepending "cmd" to args?

EDIT: nevermind, Mix.shell.cmd already does that.

1 Like

Hmm. Let me look this over. I had it working before when not run from aliases/0 but a custom task. Need to step out for a minute.

1 Like

You are trying to run this command: “–app foo --app bar npm install”. If you print the args before calling Mix.shell.cmd you should be able to verify it.

1 Like

Yup, printing the args verifies they are correct. But it doesn’t produce the same result as when I run mix cmd --app admin --app web npm install from the CLI (which runs correctly).

$mix install
"--app admin --app web npm install"
sh: --: invalid option
Usage:	sh [GNU long option] [option] ...
	sh [GNU long option] [option] script-file ...
GNU long options:
	--debug
	--debugger
	--dump-po-strings
	--dump-strings
	--help
	--init-file
	--login
	--noediting
	--noprofile
	--norc
	--posix
	--protected
	--rcfile
	--restricted
	--verbose
	--version
	--wordexp
Shell options:
	-irsD or -c command or -O shopt_option		(invocation only)
	-abefhkmnptuvxBCHP or -o option

Produced by:

defp aliases do
  ["install": [&install_brunch/1]]
end

defp install_brunch(_) do
  args =
    for app <- Mix.Project.config[:web_apps] do
      "--app #{app}"
    end
    |> Kernel.++(["npm install"])
    |> Enum.join(" ")

  IO.inspect args
  Mix.shell.cmd args
end
1 Like

Mix.shell.cmd is not related to the “mix cmd” task. If you want to invoke
“mix cmd” then you need to prefix the argument you are passing to
Mix.shell.cmd with that. Or you can call Mix.Task.run(“cmd”, args) where
args is a list of arguments as you originally had.

2 Likes

Prefixing “mix cmd” to the string arguments works, but Mix.Task.run/2 did not, oddly.

This worked:

  defp install_brunch(_) do
    args =
      for app <- Mix.Project.config[:web_apps] do
        "--app #{app}"
      end
      |> Kernel.++(["npm install"])
      |> Enum.join(" ")

    args = "mix cmd " <> args
    IO.inspect args
    Mix.shell.cmd args
  end

$mix install
"mix cmd --app admin --app web npm install"

This produced the same error as above sh: --: invalid option, but with following error:

  defp install_brunch(_) do
    args =
      for app <- Mix.Project.config[:web_apps] do
        "--app #{app}"
      end
      |> Kernel.++(["npm install"])

    IO.inspect args
    Mix.Task.run("cmd", args)
  end

** (exit) 2
    (mix) lib/mix/tasks/cmd.ex:29: Mix.Tasks.Cmd.run/1
    (mix) lib/mix/task.ex:294: Mix.Task.run_task/3
    (mix) lib/mix/project.ex:312: Mix.Project.in_project/4
    (elixir) lib/file.ex:1162: File.cd!/2
    (mix) lib/mix/task.ex:394: anonymous fn/4 in Mix.Task.recur/1
    (elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
    (mix) lib/mix/task.ex:393: Mix.Task.recur/1
    (mix) lib/mix/project_stack.ex:135: Mix.ProjectStack.recur/1
1 Like