Packages

A CMake compiler to help building Ports/NIFs in your Elixir/mix project.

Current section

Files

Jump to
elixir_cmake lib mix tasks compile cmake.ex
Raw

lib/mix/tasks/compile/cmake.ex

defmodule Mix.Tasks.Compile.Cmake do
@moduledoc """
Builds native source using CMake
Runs `cmake` in the current project (followed by `make`) .
This task runs `cmake` in the current project; any output coming from `cmake` is
printed in real-time on stdout.
## Configuration
This compiler can be configured through the return value of the `project/0`
function in `mix.exs`; for example:
def project() do
[
# ...
compilers: [:cmake] ++ Mix.compilers,
# ...
]
end
The following options are available:
* `:cmake_env` - (map of binary to binary) it's a map of extra environment
variables to be passed to `cmake`. You can also pass a function in here in
case `make_env` needs access to things that are not available during project
setup; the function should return a map of binary to binary. Many default
environment variables are set, see section below
## Default environment variables
There are also several default environment variables set:
* `MIX_TARGET`
* `MIX_ENV`
* `MIX_BUILD_PATH` - same as `Mix.Project.build_path/0`
* `MIX_APP_PATH` - same as `Mix.Project.app_path/0`
* `MIX_COMPILE_PATH` - same as `Mix.Project.compile_path/0`
* `MIX_CONSOLIDATION_PATH` - same as `Mix.Project.consolidation_path/0`
* `MIX_DEPS_PATH` - same as `Mix.Project.deps_path/0`
* `MIX_MANIFEST_PATH` - same as `Mix.Project.manifest_path/0`
* `ERL_EI_LIBDIR`
* `ERL_EI_INCLUDE_DIR`
* `ERTS_INCLUDE_DIR`
* `ERL_INTERFACE_LIB_DIR`
* `ERL_INTERFACE_INCLUDE_DIR`
These may also be overwritten with the `cmake_env` option.
## Compilation artifacts and working with priv directories
Generally speaking, compilation artifacts are written to the `priv`
directory, as that the only directory, besides `ebin`, which are
available to Erlang/OTP applications.
However, note that Mix projects supports the `:build_embedded`
configuration, which controls if assets in the `_build` directory
are symlinked (when `false`, the default) or copied (`true`).
In order to support both options for `:build_embedded`, it is
important to follow the given guidelines:
* The "priv" directory must not exist in the source code
* The Makefile should copy any artifact to `$MIX_APP_PATH/priv`
or, even better, to `$MIX_APP_PATH/priv/$MIX_TARGET`
* If there are static assets, the Makefile should copy them over
from a directory at the project root (not named "priv")
"""
use Mix.Task
@default_make_target "all"
@default_working_dir "cmake"
@recursive true
@doc """
Runs this task.
"""
def run(_args) do
config = Mix.Project.config()
env = Keyword.get(config, :cmake_env, %{})
env = if is_function(env), do: env.(), else: env
env = default_env(config, env)
working_dir = working_dir(config)
:ok = File.mkdir_p(working_dir)
make_targets =
(config[:make_targets] || [@default_make_target])
cmake_lists =
(config[:cmake_lists] || File.cwd!())
|> Path.expand()
cmd("cmake", [cmake_lists], working_dir, env)
cmd("make", make_targets, working_dir, env)
Mix.Project.build_structure()
:ok
end
@doc """
Removes compiled artifacts.
"""
def clean() do
working_dir =
Mix.Project.config()
|> working_dir()
cmd("make", ["clean"], working_dir)
:ok
end
defp cmd(exec, args, dir, env \\ %{}) do
case System.cmd(exec, args, cd: dir, stderr_to_stdout: true, env: env) do
{result, 0} ->
Mix.shell().info(result)
:ok
{result, status} ->
Mix.raise("Failure running '#{exec}' (status: #{status}).\n#{result}")
end
end
defp default_env(config, default_env) do
root_dir = :code.root_dir()
erl_interface_dir = Path.join(root_dir, "usr")
erts_dir = Path.join(root_dir, "erts-#{:erlang.system_info(:version)}")
erts_include_dir = Path.join(erts_dir, "include")
erl_ei_lib_dir = Path.join(erl_interface_dir, "lib")
erl_ei_include_dir = Path.join(erl_interface_dir, "include")
Map.merge(
%{
# Don't use Mix.target/0 here for backwards compatability
"MIX_TARGET" => env("MIX_TARGET", "host"),
"MIX_ENV" => to_string(Mix.env()),
"MIX_BUILD_PATH" => Mix.Project.build_path(config),
"MIX_APP_PATH" => Mix.Project.app_path(config),
"MIX_COMPILE_PATH" => Mix.Project.compile_path(config),
"MIX_CONSOLIDATION_PATH" => Mix.Project.consolidation_path(config),
"MIX_DEPS_PATH" => Mix.Project.deps_path(config),
"MIX_MANIFEST_PATH" => Mix.Project.manifest_path(config),
# Rebar naming
"ERL_EI_LIBDIR" => env("ERL_EI_LIBDIR", erl_ei_lib_dir),
"ERL_EI_INCLUDE_DIR" => env("ERL_EI_INCLUDE_DIR", erl_ei_include_dir),
# erlang.mk naming
"ERTS_INCLUDE_DIR" => env("ERTS_INCLUDE_DIR", erts_include_dir),
"ERL_INTERFACE_LIB_DIR" => env("ERL_INTERFACE_LIB_DIR", erl_ei_lib_dir),
"ERL_INTERFACE_INCLUDE_DIR" => env("ERL_INTERFACE_INCLUDE_DIR", erl_ei_include_dir)
},
default_env
)
end
defp env(var, default) do
System.get_env(var) || default
end
defp working_dir(config) do
config
|> Mix.Project.build_path()
|> Path.join(@default_working_dir)
end
end