diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32e46f6..6eb3262 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: with: otp-version: "27.2" elixir-version: "1.18.2" - - run: mix deps.get + - run: mix deps.get --check-locked - run: mix deps.compile - run: mix format --check-formatted - run: mix deps.unlock --check-unused @@ -59,3 +59,10 @@ jobs: working-directory: test - run: mix test working-directory: test + - name: Test example/ + run: | + mix deps.get --check-locked + mix format --check-formatted + mix deps.unlock --check-unused + mix test --warnings-as-errors + working-directory: example diff --git a/README.md b/README.md index 5208e52..d4e8af7 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,8 @@ FINE_NIF(add, 0); FINE_INIT("Elixir.MyLib.NIF"); ``` +See [`example/`](https://github.com/elixir-nx/fine/tree/main/example) project. + ## Encoding/Decoding Terms are automatically encoded and decoded at the NIF boundary based diff --git a/example/.formatter.exs b/example/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/example/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..cf41454 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,23 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +example-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/example/Makefile b/example/Makefile new file mode 100644 index 0000000..1407678 --- /dev/null +++ b/example/Makefile @@ -0,0 +1,29 @@ +PRIV_DIR := $(MIX_APP_PATH)/priv +NIF_PATH := $(PRIV_DIR)/libexample.so +C_SRC := $(shell pwd)/c_src + +CPPFLAGS := -shared -fPIC -std=c++17 -Wall -Wextra +CPPFLAGS += -I$(ERTS_INCLUDE_DIR) -I$(FINE_INCLUDE_DIR) + +ifdef DEBUG + CPPFLAGS += -g +else + CPPFLAGS += -O3 +endif + +ifndef TARGET_ABI + TARGET_ABI := $(shell uname -s | tr '[:upper:]' '[:lower:]') +endif + +ifeq ($(TARGET_ABI),darwin) + CPPFLAGS += -undefined dynamic_lookup -flat_namespace +endif + +SOURCES := $(wildcard $(C_SRC)/*.cpp) + +all: $(NIF_PATH) + @ echo > /dev/null # Dummy command to avoid the default output "Nothing to be done" + +$(NIF_PATH): $(SOURCES) + @ mkdir -p $(PRIV_DIR) + $(CXX) $(CPPFLAGS) $(SOURCES) -o $(NIF_PATH) diff --git a/example/Makefile.win b/example/Makefile.win new file mode 100644 index 0000000..6bbc8ae --- /dev/null +++ b/example/Makefile.win @@ -0,0 +1,14 @@ +PRIV_DIR=$(MIX_APP_PATH)\priv +NIF_PATH=$(PRIV_DIR)\libexample.dll + +C_SRC=$(MAKEDIR)\c_src +CPPFLAGS=/LD /std:c++17 /W4 /O2 /EHsc +CPPFLAGS=$(CPPFLAGS) /I"$(ERTS_INCLUDE_DIR)" /I"$(FINE_INCLUDE_DIR)" + +SOURCES=$(C_SRC)\*.cpp + +all: $(NIF_PATH) + +$(NIF_PATH): $(SOURCES) + @ if not exist "$(PRIV_DIR)" mkdir "$(PRIV_DIR)" + cl $(CPPFLAGS) $(SOURCES) /Fe"$(NIF_PATH)" diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..2308d85 --- /dev/null +++ b/example/README.md @@ -0,0 +1,10 @@ +# Example + +Example project using Fine to implement NIFs. + +To run tests, execute: + +```shell +$ mix deps.get +$ mix test +``` diff --git a/example/c_src/example.cpp b/example/c_src/example.cpp new file mode 100644 index 0000000..86646f9 --- /dev/null +++ b/example/c_src/example.cpp @@ -0,0 +1,8 @@ +#include + +int64_t add(ErlNifEnv *env, int64_t x, int64_t y) { + return x + y; +} + +FINE_NIF(add, 0); +FINE_INIT("Elixir.Example"); diff --git a/example/lib/example.ex b/example/lib/example.ex new file mode 100644 index 0000000..b608f72 --- /dev/null +++ b/example/lib/example.ex @@ -0,0 +1,24 @@ +defmodule Example do + @on_load :__on_load__ + + def __on_load__ do + path = :filename.join(:code.priv_dir(:example), ~c"libexample") + + case :erlang.load_nif(path, 0) do + :ok -> :ok + {:error, reason} -> raise "failed to load NIF library, reason: #{inspect(reason)}" + end + end + + @doc """ + Adds two numbers using NIF. + + ## Examples + + iex> Example.add(1, 2) + 3 + """ + def add(_x, _y) do + :erlang.nif_error("nif not loaded") + end +end diff --git a/example/mix.exs b/example/mix.exs new file mode 100644 index 0000000..5b65171 --- /dev/null +++ b/example/mix.exs @@ -0,0 +1,27 @@ +defmodule Example.MixProject do + use Mix.Project + + def project do + [ + app: :example, + version: "0.1.0", + elixir: "~> 1.15", + compilers: [:elixir_make] ++ Mix.compilers(), + make_env: fn -> %{"FINE_INCLUDE_DIR" => Fine.include_dir()} end, + deps: deps() + ] + end + + def application do + [ + extra_applications: [:logger] + ] + end + + defp deps do + [ + {:elixir_make, "~> 0.9.0"}, + {:fine, path: ".."} + ] + end +end diff --git a/example/mix.lock b/example/mix.lock new file mode 100644 index 0000000..0e11663 --- /dev/null +++ b/example/mix.lock @@ -0,0 +1,3 @@ +%{ + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, +} diff --git a/example/test/example_test.exs b/example/test/example_test.exs new file mode 100644 index 0000000..a6dcc73 --- /dev/null +++ b/example/test/example_test.exs @@ -0,0 +1,4 @@ +defmodule ExampleTest do + use ExUnit.Case, async: true + doctest Example +end diff --git a/example/test/test_helper.exs b/example/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/example/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()