Current section

Files

Jump to
elixir_make lib mix tasks compile.make.ex
Raw

lib/mix/tasks/compile.make.ex

defmodule Mix.Tasks.Compile.ElixirMake do
use Mix.Task
@moduledoc """
Runs `make` in the current project.
This task runs `make` in the current project; any output coming from `make` is
printed in real-time on stdout.
## Configuration
The configuration options need to be added to the `project` function in the
`mix.exs` file, as in this example:
def project do
[app: :myapp,
make_executable: "make",
make_makefile: "Othermakefile",
compilers: [:elixir_make] ++ Mix.compilers,
deps: deps]
end
The following options are available:
* `:make_executable` - (binary or `:default`) it's the executable to use as the
`make` program. If not provided or if `:default`, it defaults to `"nmake"`
on Windows, `"gmake"` on FreeBSD and OpenBSD, and `"make"` on everything
else. You can, for example, customize which executable to use on a
specific OS and use `:default` for every other OS.
* `:make_makefile` - (binary or `:default`) it's the Makefile to
use. Defaults to `"Makefile"` for Unix systems and `"Makefile.win"` for
Windows systems if not provided or if `:default`.
* `:make_targets` - (list of binaries) it's the list of Make targets that
should be run. Defaults to `[]`, meaning `make` will run the first target.
* `:make_clean` - (list of binaries) it's a list of Make targets to be run
when `mix clean` is run. It's only run if a non-`nil` value for
`:make_clean` is provided. Defaults to `nil`.
* `:make_cwd` - (binary) it's the directory where `make` will be run,
relative to the root of the project.
* `:make_env` - (map of binary to binary) it's a map of extra environment
variables to be passed to `make`.
* `:make_error_message` - (binary or `:default`) it's a custom error message
that can be used to give instructions as of how to fix the error (e.g., it
can be used to suggest installing `gcc` if you're compiling a C
dependency).
"""
@unix_error_msg """
Depending on your OS, make sure to follow these instructions:
* Mac OS X: You need to have gcc and make installed. Try running the
commands `gcc --version` and / or `make --version`. If these programs
are not installed, you will be prompted to install them.
* Linux: You need to have gcc and make installed. If you are using
Ubuntu or any other Debian-based system, install the packages
`build-essential`. Also install `erlang-dev` package if not
included in your Erlang/OTP version. If you're on Fedora, run
`dnf group install 'Development Tools'`.
"""
@windows_error_msg """
One option is to install a recent version of Visual Studio (you can download
the community edition for free). When you install Visual Studio, make sure
you also install the C/C++ tools.
After installing VS, look in the `Program Files (x86)` folder and search
for `Microsoft Visual Studio`. Note down the full path of the folder with
the highest version number. Open the `run` command and type in the following
command (make sure that the path and version number are correct):
cmd /K "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
This should open up a command prompt with the necessary environment variables
set, and from which you will be able to run the `mix compile`, `mix deps.compile`,
and `mix test` commands.
"""
@spec run(OptionParser.argv) :: :ok | no_return
def run(args) do
config = Mix.Project.config()
Mix.shell.print_app()
build(config, args)
Mix.Project.build_structure()
:ok
end
# This is called by Elixir when `mix clean` is run and `:elixir_make` is in
# the list of compilers.
def clean() do
config = Mix.Project.config()
{clean_targets, config} = Keyword.pop(config, :make_clean)
if clean_targets do
config
|> Keyword.put(:make_targets, clean_targets)
|> build([])
end
end
defp build(config, task_args) do
exec = Keyword.get(config, :make_executable, :default) |> os_specific_executable()
makefile = Keyword.get(config, :make_makefile, :default)
targets = Keyword.get(config, :make_targets, [])
env = Keyword.get(config, :make_env, %{})
cwd = Keyword.get(config, :make_cwd, ".")
error_msg = Keyword.get(config, :make_error_message, :default) |> os_specific_error_msg()
args = args_for_makefile(exec, makefile) ++ targets
case cmd(exec, args, cwd, env, "--verbose" in task_args) do
0 ->
:ok
exit_status ->
raise_build_error(exec, exit_status, error_msg)
end
end
# Runs `exec [args]` in `cwd` and prints the stdout and stderr in real time,
# as soon as `exec` prints them (using `IO.Stream`).
defp cmd(exec, args, cwd, env, verbose?) do
opts = [
into: IO.stream(:stdio, :line),
stderr_to_stdout: true,
cd: cwd,
env: env
]
if verbose? do
print_verbose_info(exec, args)
end
{%IO.Stream{}, status} = System.cmd(find_executable(exec), args, opts)
status
end
defp find_executable(exec) do
System.find_executable(exec) || Mix.raise("`#{exec}` not found in the current path")
end
defp raise_build_error(exec, exit_status, error_msg) do
Mix.raise("Could not compile with `#{exec}` (exit status: #{exit_status}).\n" <> error_msg)
end
defp os_specific_executable(exec) when is_binary(exec) do
exec
end
defp os_specific_executable(:default) do
case :os.type() do
{:win32, _} ->
"nmake"
{:unix, type} when type in [:freebsd, :openbsd] ->
"gmake"
_ ->
"make"
end
end
defp os_specific_error_msg(msg) when is_binary(msg) do
msg
end
defp os_specific_error_msg(:default) do
case :os.type() do
{:unix, _} -> @unix_error_msg
{:win32, _} -> @windows_error_msg
_ -> ""
end
end
# Returns a list of command-line args to pass to make (or nmake/gmake) in
# order to specify the makefile to use.
defp args_for_makefile("nmake", :default), do: ["/F", "Makefile.win"]
defp args_for_makefile("nmake", makefile), do: ["/F", makefile]
defp args_for_makefile(_, :default), do: []
defp args_for_makefile(_, makefile), do: ["-f", makefile]
defp print_verbose_info(exec, args) do
args = Enum.map_join(args, " ", fn(arg) ->
if String.contains?(arg, " "), do: inspect(arg), else: arg
end)
Mix.shell.info "Compiling with make: #{exec} #{args}"
end
end