From cf9b01de8288b30c0b7caa05526961b43c8ea776 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 7 Mar 2025 12:25:34 +0100 Subject: [PATCH 1/8] Add example project --- .github/workflows/ci.yml | 10 +++++++++- README.md | 2 ++ example/.formatter.exs | 4 ++++ example/.gitignore | 23 +++++++++++++++++++++++ example/Makefile | 15 +++++++++++++++ example/README.md | 10 ++++++++++ example/c_src/example_nif.cpp | 8 ++++++++ example/lib/example.ex | 20 ++++++++++++++++++++ example/mix.exs | 33 +++++++++++++++++++++++++++++++++ example/mix.lock | 3 +++ example/test/example_test.exs | 4 ++++ example/test/test_helper.exs | 1 + 12 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 example/.formatter.exs create mode 100644 example/.gitignore create mode 100644 example/Makefile create mode 100644 example/README.md create mode 100644 example/c_src/example_nif.cpp create mode 100644 example/lib/example.ex create mode 100644 example/mix.exs create mode 100644 example/mix.lock create mode 100644 example/test/example_test.exs create mode 100644 example/test/test_helper.exs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32e46f6..444752b 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,11 @@ 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 + if: ${{ !startsWith(matrix.os.id, 'windows') }} 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..14e42db --- /dev/null +++ b/example/Makefile @@ -0,0 +1,15 @@ +SRC := c_src/example_nif.cpp +TARGET := $(MIX_BUILD_DIR)/example_nif.so + +CPPFLAGS := -std=c++17 -fvisibility=hidden -fPIC -I$(ERL_INCLUDE_DIR) -I$(FINE_INCLUDE_DIR) + +ifeq ($(shell uname -s),Darwin) + LDFLAGS := -dynamiclib -undefined dynamic_lookup +else + LDFLAGS := -shared +endif + +all: $(TARGET) + +$(TARGET): $(SRC) + $(CXX) $(CPPFLAGS) $(LDFLAGS) -o $@ $^ 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_nif.cpp b/example/c_src/example_nif.cpp new file mode 100644 index 0000000..86646f9 --- /dev/null +++ b/example/c_src/example_nif.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..4fc3927 --- /dev/null +++ b/example/lib/example.ex @@ -0,0 +1,20 @@ +defmodule Example do + @on_load :load_nif + @mix_build_dir Mix.Project.build_path() + + defp load_nif do + :erlang.load_nif(~c"#{@mix_build_dir}/example_nif", 0) + 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..f617c82 --- /dev/null +++ b/example/mix.exs @@ -0,0 +1,33 @@ +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 -> + %{ + "MIX_BUILD_DIR" => Mix.Project.build_path(), + "ERL_INCLUDE_DIR" => "#{:code.root_dir()}/usr/include", + "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() From 3692ea2188bac9f93f31072e0f50d1657b97e23c Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 7 Mar 2025 14:53:58 +0100 Subject: [PATCH 2/8] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jonatan Kłosko --- example/Makefile | 32 +++++++++++++++++++++++--------- example/lib/example.ex | 12 ++++++++---- example/mix.exs | 8 +------- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/example/Makefile b/example/Makefile index 14e42db..1407678 100644 --- a/example/Makefile +++ b/example/Makefile @@ -1,15 +1,29 @@ -SRC := c_src/example_nif.cpp -TARGET := $(MIX_BUILD_DIR)/example_nif.so +PRIV_DIR := $(MIX_APP_PATH)/priv +NIF_PATH := $(PRIV_DIR)/libexample.so +C_SRC := $(shell pwd)/c_src -CPPFLAGS := -std=c++17 -fvisibility=hidden -fPIC -I$(ERL_INCLUDE_DIR) -I$(FINE_INCLUDE_DIR) +CPPFLAGS := -shared -fPIC -std=c++17 -Wall -Wextra +CPPFLAGS += -I$(ERTS_INCLUDE_DIR) -I$(FINE_INCLUDE_DIR) -ifeq ($(shell uname -s),Darwin) - LDFLAGS := -dynamiclib -undefined dynamic_lookup +ifdef DEBUG + CPPFLAGS += -g else - LDFLAGS := -shared + CPPFLAGS += -O3 endif -all: $(TARGET) +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" -$(TARGET): $(SRC) - $(CXX) $(CPPFLAGS) $(LDFLAGS) -o $@ $^ +$(NIF_PATH): $(SOURCES) + @ mkdir -p $(PRIV_DIR) + $(CXX) $(CPPFLAGS) $(SOURCES) -o $(NIF_PATH) diff --git a/example/lib/example.ex b/example/lib/example.ex index 4fc3927..b608f72 100644 --- a/example/lib/example.ex +++ b/example/lib/example.ex @@ -1,9 +1,13 @@ defmodule Example do - @on_load :load_nif - @mix_build_dir Mix.Project.build_path() + @on_load :__on_load__ - defp load_nif do - :erlang.load_nif(~c"#{@mix_build_dir}/example_nif", 0) + 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 """ diff --git a/example/mix.exs b/example/mix.exs index f617c82..5b65171 100644 --- a/example/mix.exs +++ b/example/mix.exs @@ -7,13 +7,7 @@ defmodule Example.MixProject do version: "0.1.0", elixir: "~> 1.15", compilers: [:elixir_make] ++ Mix.compilers(), - make_env: fn -> - %{ - "MIX_BUILD_DIR" => Mix.Project.build_path(), - "ERL_INCLUDE_DIR" => "#{:code.root_dir()}/usr/include", - "FINE_INCLUDE_DIR" => Fine.include_dir() - } - end, + make_env: fn -> %{"FINE_INCLUDE_DIR" => Fine.include_dir()} end, deps: deps() ] end From edda90eed974b3374bf6440a31bbbd28d7d5a025 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 7 Mar 2025 14:56:31 +0100 Subject: [PATCH 3/8] up --- example/c_src/{example_nif.cpp => example.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename example/c_src/{example_nif.cpp => example.cpp} (100%) diff --git a/example/c_src/example_nif.cpp b/example/c_src/example.cpp similarity index 100% rename from example/c_src/example_nif.cpp rename to example/c_src/example.cpp From 0b7ef4bf02cb55d122831e002cad3f1202b10cee Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 7 Mar 2025 14:59:39 +0100 Subject: [PATCH 4/8] Update .github/workflows/ci.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jonatan Kłosko --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 444752b..6eb3262 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,4 +66,3 @@ jobs: mix deps.unlock --check-unused mix test --warnings-as-errors working-directory: example - if: ${{ !startsWith(matrix.os.id, 'windows') }} From 2e43a5ab0c0a8f6d9b2cf7456d2140a6f25f7e25 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 7 Mar 2025 14:59:48 +0100 Subject: [PATCH 5/8] up --- example/Makefile.win | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 example/Makefile.win diff --git a/example/Makefile.win b/example/Makefile.win new file mode 100644 index 0000000..b7a750a --- /dev/null +++ b/example/Makefile.win @@ -0,0 +1,13 @@ +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)" From 76190e00ca4c29a0b699a89ac600c37d1f4af6b6 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 7 Mar 2025 15:00:22 +0100 Subject: [PATCH 6/8] up --- example/Makefile.win | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example/Makefile.win b/example/Makefile.win index b7a750a..f0baeb2 100644 --- a/example/Makefile.win +++ b/example/Makefile.win @@ -1,7 +1,8 @@ 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)" +CPPFLAGS=/LD /std:c++17 /W4 /O2 /EHsc +CPPFLAGS=$(CPPFLAGS) /I"$(ERTS_INCLUDE_DIR)" /I"$(FINE_INCLUDE_DIR)" SOURCES=$(C_SRC)\*.cpp From 135a693c3c7a628e11e0a566f2418d7c092f7246 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 7 Mar 2025 15:03:10 +0100 Subject: [PATCH 7/8] up --- example/Makefile.win | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/example/Makefile.win b/example/Makefile.win index f0baeb2..923c900 100644 --- a/example/Makefile.win +++ b/example/Makefile.win @@ -9,6 +9,5 @@ SOURCES=$(C_SRC)\*.cpp all: $(NIF_PATH) $(NIF_PATH): $(SOURCES) - @ if not exist "$(PRIV_DIR)" - mkdir "$(PRIV_DIR)" + @ if not exist "$(PRIV_DIR)" mkdir "$(PRIV_DIR)" cl $(CPPFLAGS) $(SOURCES) /Fe"$(NIF_PATH)" From 0d8a7e5e63d94a2f8a649deea07875316dcd7820 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 7 Mar 2025 15:03:49 +0100 Subject: [PATCH 8/8] up --- example/Makefile.win | 1 + 1 file changed, 1 insertion(+) diff --git a/example/Makefile.win b/example/Makefile.win index 923c900..6bbc8ae 100644 --- a/example/Makefile.win +++ b/example/Makefile.win @@ -1,5 +1,6 @@ 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)"