Last active
September 1, 2024 06:48
-
-
Save monotykamary/973c86c6190c644f0ba4a5fbbd8025d5 to your computer and use it in GitHub Desktop.
Cross compilation with Elixir and Nim NIFs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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