Skip to content

Instantly share code, notes, and snippets.

@monotykamary
Last active September 1, 2024 06:48
Show Gist options
  • Save monotykamary/973c86c6190c644f0ba4a5fbbd8025d5 to your computer and use it in GitHub Desktop.
Save monotykamary/973c86c6190c644f0ba4a5fbbd8025d5 to your computer and use it in GitHub Desktop.
Cross compilation with Elixir and Nim NIFs
# lib/mix/tasks/compile.nim_nif.ex
defmodule Mix.Tasks.Compile.NimNif do
use Mix.Task.Compiler
@bindings_dir Path.join(["lib", "native", "bindings"])
@generated_dir Path.join(@bindings_dir, "generated")
@hash_file Path.join(@generated_dir, ".nif_hash")
defp ensure_directories_exist() do
File.mkdir_p!(@bindings_dir)
File.mkdir_p!(@generated_dir)
end
defp files_changed?() do
current_hash = compute_total_hash()
previous_hash = read_previous_hash()
current_hash != previous_hash
end
defp compute_total_hash() do
@generated_dir
|> File.ls!()
|> Enum.reject(&(&1 == ".nif_hash"))
|> Enum.sort()
|> Enum.map(&compute_file_hash(Path.join(@generated_dir, &1)))
|> Enum.join()
|> :erlang.md5()
|> Base.encode16(case: :lower)
end
defp compute_file_hash(file_path) do
file_path
|> File.read!()
|> :erlang.md5()
|> Base.encode16(case: :lower)
end
defp read_previous_hash() do
case File.read(@hash_file) do
{:ok, hash} -> hash
{:error, _} -> ""
end
end
defp write_current_hash() do
hash = compute_total_hash()
File.write!(@hash_file, hash)
end
def run(_args) do
ensure_directories_exist()
if files_changed?() do
compile_nif()
write_current_hash()
:ok
else
:noop
end
end
defp compile_nif() do
# Remove only .ex files in the bindings/generated directory
if File.dir?(@generated_dir) do
@generated_dir
|> File.ls!()
|> Enum.filter(&String.ends_with?(&1, ".ex"))
|> Enum.each(fn file ->
File.rm!(Path.join(@generated_dir, file))
end)
end
# Generate the nimble bindings
{result, _error_code} =
System.cmd("nimble", ["bindings"], cd: "lib/native", stderr_to_stdout: true)
Mix.shell().info(result)
# Run mix format on the generated files
{result, _error_code} =
System.cmd("mix", ["format", "lib/native/bindings/generated/*.ex"], stderr_to_stdout: true)
Mix.shell().info(result)
end
end
defmodule NimlerElixir.MixProject do
use Mix.Project
def project do
[
app: :nimler_elixir,
version: "0.1.0",
elixir: "~> 1.17",
start_permanent: Mix.env() == :prod,
compilers: [:nim_nif] ++ Mix.compilers(),
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[]
end
end
# lib/native/nif.nimble
# Package
version = "0.1.0"
author = "Tom"
description = "A Nimble library for generating bindings using Nimler."
license = "MIT"
# Dependencies
requires "nim >= 1.14.0"
# Tasks
import os, strutils
task bindings, "Generate Bindings":
proc compile(libName: string, flags = "") =
let extraFlags = case hostOS
of "macosx": "--passL:'-undefined dynamic_lookup'"
of "windows": "--app:lib"
else: "--passL:'-shared'"
let cmd = "nim c -f " & flags & " " & extraFlags & " -d:release --app:lib --gc:orc -d:nimlerGenWrapper --out:" & libName & " --outdir:bindings/generated nif.nim"
echo "Executing: ", cmd
exec cmd
let (libPrefix, libExt) = case hostOS
of "windows": ("", ".dll")
of "linux": ("lib", ".so")
of "macosx": ("lib", ".dylib.so")
else: ("lib", ".so")
let libName = libPrefix & "nif" & libExt
let outputName = "bindings/generated/" & libName
case hostOS
of "windows":
compile(libName)
of "linux":
compile(libName)
of "macosx":
when hostCPU == "arm64":
compile(libName, "--cpu:arm64")
else:
compile(libName, "--cpu:amd64")
else:
echo "Unsupported operating system"
quit(1)
if fileExists(outputName):
echo "Library compiled: " & libName
else:
echo "Compilation failed: " & outputName & " not found"
quit(1)
cc:gcc
verbosity:2
app:lib
mm:orc
define:noMain
define:noSignalHandler
opt:speed
stackTrace:off
lineTrace:off
warning[GcUnsafe]:off
hint[Exec]:off
hint[Link]:off
hint[Processing]:off
hint[GcStats]:off
hint[GlobalVar]:off
nimblepath:"$home/.nimble/pkgs2/"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment