Current section

Files

Jump to
zigler lib zig command.ex
Raw

lib/zig/command.ex

defmodule Zig.Command do
@moduledoc false
# contains all parts of the Zig library involved in calling the
# zig compiler toolchain, especially with regards to the `zig` command, except
# for assembling the build.zig file, which is performed by the
# `Zig.Builder` module.
alias Zig.Builder
alias Zig.Target
require Logger
require Zig
#############################################################################
## API
defp run_zig(command, module) do
args = String.split(command)
base_opts = Keyword.take(module, [:cd, :stderr_to_stdout])
zig_cmd = executable_path()
Logger.debug("running command: #{zig_cmd} #{command}")
case System.cmd(zig_cmd, args, base_opts) do
{result, 0} ->
result
{error, code} ->
raise Zig.CompileError, command: command, code: code, error: error
end
end
require EEx
sema_command = Path.join(__DIR__, "templates/sema_command.eex")
EEx.function_from_file(:defp, :sema_command, sema_command, [:assigns])
def run_sema!(file, opts) do
# TODO: add availability of further options here.
priv_dir = :code.priv_dir(:zigler)
sema_file = Path.join(priv_dir, "beam/sema.zig")
beam_file = Path.join(priv_dir, "beam/beam.zig")
erl_nif_file = Path.join(priv_dir, "beam/stub_erl_nif.zig")
attribs_file = opts[:attribs_file]
c = opts[:c]
# nerves will put in a `CC` command that we need to bypass because it misidentifies
# libc locations for statically linking it.
System.delete_env("CC")
analyte_deps =
[:erl_nif, :beam] ++
List.wrap(if attribs_file, do: [:attributes])
mods =
[erl_nif: %{path: erl_nif_file}, beam: %{deps: [:erl_nif], path: beam_file}] ++
List.wrap(if attribs_file, do: [attributes: %{path: attribs_file}]) ++
[analyte: %{deps: analyte_deps, path: file}]
sema_command(
sema: sema_file,
mods: mods,
c: c
)
|> IO.iodata_to_binary()
|> String.split()
|> Enum.join(" ")
|> run_zig(stderr_to_stdout: true)
end
# documentation requires a separate pathway because otherwise compilation
# doesn't go that well.
# TODO: unify this with the normal sema function using options.
def run_sema_doc!(file) do
priv_dir = :code.priv_dir(:zigler)
sema_file = Path.join(priv_dir, "beam/sema_doc.zig")
erl_nif_file = Path.join(priv_dir, "beam/stub_erl_nif.zig")
analyte_deps = [:erl_nif]
mods = [erl_nif: %{path: erl_nif_file}, analyte: %{deps: analyte_deps, path: file}]
sema_command(
sema: sema_file,
mods: mods
)
|> IO.iodata_to_binary()
|> String.split()
|> Enum.join(" ")
|> run_zig(stderr_to_stdout: true)
end
def fmt(file) do
run_zig("fmt #{file}", [])
end
def compile!(module) do
staging_directory = Builder.staging_directory(module.module)
so_dir = :code.priv_dir(module.otp_app)
lib_dir = Path.join(so_dir, "lib")
run_zig("build --prefix #{so_dir}", cd: staging_directory, stderr_to_stdout: true)
src_lib_name = Path.join(lib_dir, src_lib_name(module.module))
dst_lib_name = Path.join(lib_dir, dst_lib_name(module.module))
# on MacOS, we must delete the old library because otherwise library
# integrity checker will kill the process
File.rm(dst_lib_name)
File.cp!(src_lib_name, dst_lib_name)
Logger.debug("built library at #{dst_lib_name}")
module
rescue
e in Zig.CompileError ->
reraise Zig.CompileError.resolve(e, module), __STACKTRACE__
end
def targets do
run_zig("targets", [])
end
def executable_path do
# executable_path resolves zig executable in the following fashion:
#
# 1. check for zig in `ZIG_ARCHIVE_PATH` env path
# 2. check for zig cached in `:filename.basedir/3`
# 3. look for zig using System.find_executable
#
cond do
path = find_from_env() ->
Logger.info("zig expected (via env variable) in #{path}")
path
path = find_in_basedir() ->
Logger.info("zig expected (via cache) in #{path}")
path
path = System.find_executable("zig") ->
Logger.info("system zig found in #{path}")
path
true ->
raise CompileError, description: "zig executable not found"
end
end
defp find_from_env do
if (path = System.get_env("ZIG_ARCHIVE_PATH", "")) != "" do
versioned_path(path)
end
end
@default_version Zig.version()
defp find_in_basedir do
:user_cache
|> :filename.basedir("zigler")
|> versioned_path()
end
defp versioned_path(path) do
{os, arch} = Zig.Get.os_info()
zig_executable = Path.join(path, "zig-#{os}-#{arch}-#{@default_version}/zig")
Logger.info("searching for zig in #{zig_executable}")
if File.exists?(zig_executable), do: zig_executable
end
defp src_lib_name(module) do
case {Target.resolve(), :os.type()} do
{nil, {:unix, :darwin}} ->
"lib#{module}.dylib"
{nil, {_, :nt}} ->
"#{module}.dll"
_ ->
"lib#{module}.so"
end
end
defp dst_lib_name(module) do
case {Target.resolve(), :os.type()} do
{nil, {:unix, :darwin}} ->
"#{module}.so"
{nil, {_, :nt}} ->
"#{module}.dll"
_ ->
"#{module}.so"
end
end
end