From 29172f2d1eebeac5a1a605e32c57e26992f25e67 Mon Sep 17 00:00:00 2001 From: George Lima Date: Mon, 1 Aug 2022 10:02:43 -0300 Subject: [PATCH] First commit --- .formatter.exs | 4 + .github/workflows/ci.yaml | 152 +++++++++++++++++++++++++++++++++ .gitignore | 30 +++++++ README.md | 21 +++++ lib/rustler_twoxhash.ex | 13 +++ mix.exs | 30 +++++++ mix.lock | 7 ++ native/twoxhash/.cargo/config | 20 +++++ native/twoxhash/.gitignore | 1 + native/twoxhash/Cargo.toml | 14 +++ native/twoxhash/README.md | 20 +++++ native/twoxhash/src/lib.rs | 20 +++++ test/rustler_twoxhash_test.exs | 8 ++ test/test_helper.exs | 1 + 14 files changed, 341 insertions(+) create mode 100644 .formatter.exs create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 lib/rustler_twoxhash.ex create mode 100644 mix.exs create mode 100644 mix.lock create mode 100644 native/twoxhash/.cargo/config create mode 100644 native/twoxhash/.gitignore create mode 100644 native/twoxhash/Cargo.toml create mode 100644 native/twoxhash/README.md create mode 100644 native/twoxhash/src/lib.rs create mode 100644 test/rustler_twoxhash_test.exs create mode 100644 test/test_helper.exs diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..87a3cc1 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,152 @@ +name: Build precompiled NIFs + +env: + NIF_DIRECTORY: "native/twoxhash" + +on: + push: + tags: + - "*" + +defaults: + run: + # Sets the working dir for "run" scripts. + # Note that this won't change the directory for actions (tasks with "uses"). + working-directory: "./native/twoxhash" + +jobs: + build_release: + name: NIF ${{ matrix.nif }} - ${{ matrix.job.target }} (${{ matrix.job.os }}) + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + nif: ["2.16"] + job: + - { + target: arm-unknown-linux-gnueabihf, + os: ubuntu-20.04, + use-cross: true, + } + - { + target: aarch64-unknown-linux-gnu, + os: ubuntu-20.04, + use-cross: true, + } + - { target: aarch64-apple-darwin, os: macos-11 } + - { target: x86_64-apple-darwin, os: macos-11 } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-20.04 } + - { + target: x86_64-unknown-linux-musl, + os: ubuntu-20.04, + use-cross: true, + } + - { target: x86_64-pc-windows-gnu, os: windows-2019 } + - { target: x86_64-pc-windows-msvc, os: windows-2019 } + + env: + RUSTLER_NIF_VERSION: ${{ matrix.nif }} + steps: + - name: Checkout source code + uses: actions/checkout@v2 + + - name: Install prerequisites + shell: bash + run: | + case ${{ matrix.job.target }} in + arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; + aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; + esac + - name: Extract crate information + shell: bash + run: | + echo "PROJECT_NAME=$(sed -n 's/^name = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV + # Get the project version from mix.exs + echo "PROJECT_VERSION=$(sed -n 's/^ @version "\(.*\)"/\1/p' ../../mix.exs | head -n1)" >> $GITHUB_ENV + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: ${{ matrix.job.target }} + override: true + profile: minimal + + - name: Show version information (Rust, cargo, GCC) + shell: bash + run: | + gcc --version || true + rustup -V + rustup toolchain list + rustup default + cargo -V + rustc -V + rustc --print=cfg + - name: Download cross from GitHub releases + uses: giantswarm/install-binary-action@v1.0.0 + if: ${{ matrix.job.use-cross }} + with: + binary: "cross" + version: "v0.2.2" + download_url: "https://github.com/cross-rs/cross/releases/download/${version}/cross-x86_64-unknown-linux-gnu.tar.gz" + tarball_binary_path: "${binary}" + smoke_test: "${binary} --version" + + - name: Build + shell: bash + run: | + if [ "${{ matrix.job.use-cross }}" == "true" ]; then + cross build --release --target=${{ matrix.job.target }} + else + cargo build --release --target=${{ matrix.job.target }} + fi + - name: Rename lib to the final name + id: rename + shell: bash + run: | + LIB_PREFIX="lib" + case ${{ matrix.job.target }} in + *-pc-windows-*) LIB_PREFIX="" ;; + esac; + # Figure out suffix of lib + # See: https://doc.rust-lang.org/reference/linkage.html + LIB_SUFFIX=".so" + case ${{ matrix.job.target }} in + *-apple-darwin) LIB_SUFFIX=".dylib" ;; + *-pc-windows-*) LIB_SUFFIX=".dll" ;; + esac; + CICD_INTERMEDIATES_DIR=$(mktemp -d) + # Setup paths + LIB_DIR="${CICD_INTERMEDIATES_DIR}/released-lib" + mkdir -p "${LIB_DIR}" + LIB_NAME="${LIB_PREFIX}${{ env.PROJECT_NAME }}${LIB_SUFFIX}" + LIB_PATH="${LIB_DIR}/${LIB_NAME}" + # Copy the release build lib to the result location + cp "target/${{ matrix.job.target }}/release/${LIB_NAME}" "${LIB_DIR}" + # Final paths + # In the end we use ".so" for MacOS in the final build + # See: https://www.erlang.org/doc/man/erlang.html#load_nif-2 + LIB_FINAL_SUFFIX="${LIB_SUFFIX}" + case ${{ matrix.job.target }} in + *-apple-darwin) LIB_FINAL_SUFFIX=".so" ;; + esac; + LIB_FINAL_NAME="${LIB_PREFIX}${PROJECT_NAME}-v${PROJECT_VERSION}-nif-${RUSTLER_NIF_VERSION}-${{ matrix.job.target }}${LIB_FINAL_SUFFIX}" + # Copy lib to final name on this directory + cp "${LIB_PATH}" "${LIB_FINAL_NAME}" + tar -cvzf "${LIB_FINAL_NAME}.tar.gz" "${LIB_FINAL_NAME}" + # Passes the path relative to the root of the project. + LIB_FINAL_PATH="${NIF_DIRECTORY}/${LIB_FINAL_NAME}.tar.gz" + # Let subsequent steps know where to find the lib + echo ::set-output name=LIB_FINAL_PATH::${LIB_FINAL_PATH} + echo ::set-output name=LIB_FINAL_NAME::${LIB_FINAL_NAME}.tar.gz + - name: "Artifact upload" + uses: actions/upload-artifact@v2 + with: + name: ${{ steps.rename.outputs.LIB_FINAL_NAME }} + path: ${{ steps.rename.outputs.LIB_FINAL_PATH }} + + - name: Publish archives and packages + uses: softprops/action-gh-release@v1 + with: + files: | + ${{ steps.rename.outputs.LIB_FINAL_PATH }} + if: startsWith(github.ref, 'refs/tags/') diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..63f0170 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# 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/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# 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"). +rustler_twoxhash-*.tar + +# Temporary files, for example, from tests. +/tmp/ + +.elixir_ls + +priv/native \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9341d17 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# RustlerTwoxhash + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `rustler_twoxhash` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:rustler_twoxhash, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/lib/rustler_twoxhash.ex b/lib/rustler_twoxhash.ex new file mode 100644 index 0000000..2fcf1dc --- /dev/null +++ b/lib/rustler_twoxhash.ex @@ -0,0 +1,13 @@ +defmodule RustlerTwoxhash do + version = Mix.Project.config()[:version] + + use RustlerPrecompiled, + otp_app: :rustler_twoxhash, + crate: "twoxhash", + base_url: "https://github.com/Astrocoders/rustler_twoxhash/releases/download/v#{version}", + force_build: System.get_env("RUSTLER_PRECOMPILATION") in ["1", "true"], + version: version + + # When your NIF is loaded, it will override this function. + def hash32(_), do: :erlang.nif_error(:nif_not_loaded) +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..d95f577 --- /dev/null +++ b/mix.exs @@ -0,0 +1,30 @@ +defmodule RustlerTwoxhash.MixProject do + use Mix.Project + + @version "0.0.1" + + def project do + [ + app: :rustler_twoxhash, + version: @version, + elixir: "~> 1.13", + start_permanent: Mix.env() == :prod, + 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 + [ + {:rustler_precompiled, "~> 0.5"}, + {:rustler, ">= 0.0.0", optional: true} + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..7ab0501 --- /dev/null +++ b/mix.lock @@ -0,0 +1,7 @@ +%{ + "castore": {:hex, :castore, "0.1.17", "ba672681de4e51ed8ec1f74ed624d104c0db72742ea1a5e74edbc770c815182f", [:mix], [], "hexpm", "d9844227ed52d26e7519224525cb6868650c272d4a3d327ce3ca5570c12163f9"}, + "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, + "rustler": {:hex, :rustler, "0.25.0", "32526b51af7e58a740f61941bf923486ce6415a91c3934cc16c281aa201a2240", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "6b43a11a37fe79c6234d88c4102ab5dfede7a6a764dc5c7b539956cfa02f3cf4"}, + "rustler_precompiled": {:hex, :rustler_precompiled, "0.5.1", "93df423bd7b14b67dcacf994443d132d300623f80756974cac4febeab40af74a", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "3f8cbc8e92eef4e1a71bf441b568b868b16a3730f63f5b803c68073017e30b13"}, + "toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"}, +} diff --git a/native/twoxhash/.cargo/config b/native/twoxhash/.cargo/config new file mode 100644 index 0000000..cd48dce --- /dev/null +++ b/native/twoxhash/.cargo/config @@ -0,0 +1,20 @@ +[target.x86_64-apple-darwin] +rustflags = [ + "-C", "link-arg=-undefined", + "-C", "link-arg=dynamic_lookup", +] + +[target.aarch64-apple-darwin] +rustflags = [ + "-C", "link-arg=-undefined", + "-C", "link-arg=dynamic_lookup", +] + +# See https://github.com/rust-lang/rust/issues/59302 +[target.x86_64-unknown-linux-musl] +rustflags = [ + "-C", "target-feature=-crt-static" +] + +[profile.release] +lto = true \ No newline at end of file diff --git a/native/twoxhash/.gitignore b/native/twoxhash/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/native/twoxhash/.gitignore @@ -0,0 +1 @@ +/target diff --git a/native/twoxhash/Cargo.toml b/native/twoxhash/Cargo.toml new file mode 100644 index 0000000..11c5c63 --- /dev/null +++ b/native/twoxhash/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "twoxhash" +version = "0.1.0" +authors = [] +edition = "2018" + +[lib] +name = "twoxhash" +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +rustler = "0.25.0" +twox-hash = "1.6" \ No newline at end of file diff --git a/native/twoxhash/README.md b/native/twoxhash/README.md new file mode 100644 index 0000000..7549525 --- /dev/null +++ b/native/twoxhash/README.md @@ -0,0 +1,20 @@ +# NIF for Elixir.Twoxhash + +## To build the NIF module: + +- Your NIF will now build along with your project. + +## To load the NIF: + +```elixir +defmodule Twoxhash do + use Rustler, otp_app: :rustler_twoxhash, crate: "twoxhash" + + # When your NIF is loaded, it will override this function. + def add(_a, _b), do: :erlang.nif_error(:nif_not_loaded) +end +``` + +## Examples + +[This](https://github.com/hansihe/NifIo) is a complete example of a NIF written in Rust. diff --git a/native/twoxhash/src/lib.rs b/native/twoxhash/src/lib.rs new file mode 100644 index 0000000..16af25b --- /dev/null +++ b/native/twoxhash/src/lib.rs @@ -0,0 +1,20 @@ +use std::hash::Hasher; + +mod atoms { + rustler::atoms! { + ok, + error + } +} + +#[rustler::nif] +pub fn hash32(plaintext: String) -> u64 { + let mut hasher = twox_hash::XxHash32::default(); + hasher.write(&plaintext.into_bytes()); + + let hash = hasher.finish(); + + return hash; +} + +rustler::init!("Elixir.RustlerTwoxhash", [hash32]); diff --git a/test/rustler_twoxhash_test.exs b/test/rustler_twoxhash_test.exs new file mode 100644 index 0000000..166adaf --- /dev/null +++ b/test/rustler_twoxhash_test.exs @@ -0,0 +1,8 @@ +defmodule RustlerTwoxhashTest do + use ExUnit.Case + doctest RustlerTwoxhash + + test "greets the world" do + assert RustlerTwoxhash.hello() == :world + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()