Skip to content

Commit 0723089

Browse files
authored
Merge pull request #53 from mischov/chore/rustler-precompilation
Precompile NIFs
2 parents c735c1d + e2e1da1 commit 0723089

File tree

13 files changed

+244
-78
lines changed

13 files changed

+244
-78
lines changed

.github/workflows/tests.yml renamed to .github/workflows/ci.yml

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,58 @@
1-
name: tests
1+
name: CI
22

3-
on: [push, pull_request]
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: ["**"]
48

59
jobs:
6-
check-elixir-formatting:
7-
name: Check Elixir formatting
10+
check-formatting:
11+
name: Check formatting
812
runs-on: ubuntu-latest
913
steps:
10-
- uses: actions/checkout@v2
14+
- uses: actions/checkout@v3
15+
16+
- uses: dtolnay/rust-toolchain@stable
17+
with:
18+
components: rustfmt
1119

1220
- name: Set up Elixir
1321
uses: erlef/setup-beam@v1
1422
with:
15-
otp-version: '25.0'
16-
elixir-version: '1.14.0'
23+
otp-version: 25.0
24+
elixir-version: 1.14.0
1725

18-
- name: Check formatting
26+
- name: Check Rust formatting
27+
working-directory: native/meeseeks_html5ever_nif
28+
run: cargo fmt --all -- --check
29+
30+
- name: Check Elixir formatting
1931
run: mix format --check-formatted
2032

33+
2134
test-elixir:
22-
name: Test with OTP ${{ matrix.otp }} and Elixir ${{ matrix.elixir }}
23-
# TODO change this to ubuntu-latest after OTP 23 is deprecated, see
24-
# https://github.com/erlef/setup-beam#compatibility-between-operating-system-and-erlangotp
25-
# for compatibility
26-
runs-on: ubuntu-20.04
35+
name: Test Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }}
2736

2837
env:
2938
MIX_ENV: test
39+
MEESEEKS_HTML5EVER_BUILD: "1"
3040

3141
strategy:
3242
fail-fast: false
3343
matrix:
3444
include:
35-
- otp: '23.0'
36-
elixir: '1.12.0'
37-
- otp: '23.0'
38-
elixir: '1.14.3'
39-
- otp: '24.0'
40-
elixir: '1.12.0'
41-
- otp: '24.0'
42-
elixir: '1.14.3'
43-
- otp: '25.0'
44-
elixir: '1.13.4'
45-
- otp: '25.0'
46-
elixir: '1.14.3'
45+
- elixir: 1.12.0
46+
otp: 23.0
47+
- elixir: 1.13.0
48+
otp: 24.0
49+
- elixir: 1.14.0
50+
otp: 25.0
51+
52+
# TODO change this to ubuntu-latest after OTP 23 is deprecated, see
53+
# https://github.com/erlef/setup-beam#compatibility-between-operating-system-and-erlangotp
54+
# for compatibility
55+
runs-on: ubuntu-20.04
4756

4857
steps:
4958
- uses: actions/checkout@v3
@@ -58,36 +67,40 @@ jobs:
5867
elixir-version: ${{ matrix.elixir }}
5968

6069
- name: Retrieve cached Rust dependencies
61-
uses: actions/cache@v2
70+
uses: actions/cache@v3
6271
id: cargo-cache
6372
with:
6473
path: ~/.cargo
6574
key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-cargo-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
6675
restore-keys: |
6776
${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-cargo-
77+
6878
- name: Retrieve cached Elixir dependencies
69-
uses: actions/cache@v2
79+
uses: actions/cache@v3
7080
id: mix-cache
7181
with:
7282
path: deps
7383
key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
7484
restore-keys: |
7585
${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-
86+
7687
- name: Retrieve cached Elixir build
77-
uses: actions/cache@v2
88+
uses: actions/cache@v3
7889
id: build-cache
7990
with:
8091
path: _build
8192
key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-build-${{ github.sha }}
8293
restore-keys: |
8394
${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-build-
95+
8496
- name: Install Elixir dependencies
8597
if: steps.mix-cache.outputs.cache-hit != 'true'
8698
run: |
8799
mix local.rebar --force
88100
mix local.hex --force
89101
mix deps.get
90102
mix deps.compile
103+
91104
- name: Compile Elixir
92105
run: mix compile
93106

.github/workflows/release.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches:
6+
# Run for pushes to main
7+
- main
8+
paths:
9+
# If files in "native/" change
10+
- "native/**"
11+
# Or if this file changes
12+
- ".github/workflows/release.yml"
13+
tags:
14+
# Or run for any tag
15+
- "**"
16+
17+
jobs:
18+
precompile_nifs:
19+
name: Precompile NIF ${{ matrix.nif }} - ${{ matrix.job.target }} (${{ matrix.job.os }})
20+
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
nif: ["2.15", "2.16"]
25+
job:
26+
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04 , use-cross: true }
27+
- { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04 , use-cross: true }
28+
- { target: aarch64-unknown-linux-musl , os: ubuntu-20.04 , use-cross: true }
29+
- { target: aarch64-apple-darwin , os: macos-11 }
30+
- { target: riscv64gc-unknown-linux-gnu , os: ubuntu-20.04 , use-cross: true }
31+
- { target: x86_64-apple-darwin , os: macos-11 }
32+
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04 }
33+
- { target: x86_64-unknown-linux-musl , os: ubuntu-20.04 , use-cross: true }
34+
- { target: x86_64-pc-windows-gnu , os: windows-2019 }
35+
- { target: x86_64-pc-windows-msvc , os: windows-2019 }
36+
37+
runs-on: ${{ matrix.job.os }}
38+
39+
steps:
40+
- name: Checkout source code
41+
uses: actions/checkout@v3
42+
43+
- name: Extract project version
44+
shell: bash
45+
run: |
46+
# Get the project version from mix.exs
47+
echo "PROJECT_VERSION=$(sed -n 's/^ @version "\(.*\)"/\1/p' mix.exs | head -n1)" >> $GITHUB_ENV
48+
49+
- name: Install Rust toolchain
50+
uses: dtolnay/rust-toolchain@stable
51+
with:
52+
toolchain: stable
53+
target: ${{ matrix.job.target }}
54+
55+
- name: Build the project
56+
id: build-crate
57+
uses: philss/rustler-precompiled-action@v1.0.0
58+
with:
59+
project-name: meeseeks_html5ever_nif
60+
project-version: ${{ env.PROJECT_VERSION }}
61+
target: ${{ matrix.job.target }}
62+
nif-version: ${{ matrix.nif }}
63+
use-cross: ${{ matrix.job.use-cross }}
64+
project-dir: "native/meeseeks_html5ever_nif"
65+
66+
- name: Artifact upload
67+
uses: actions/upload-artifact@v3
68+
with:
69+
name: ${{ steps.build-crate.outputs.file-name }}
70+
path: ${{ steps.build-crate.outputs.file-path }}
71+
72+
- name: Publish archives and packages
73+
uses: softprops/action-gh-release@v1
74+
with:
75+
files: |
76+
${{ steps.build-crate.outputs.file-path }}
77+
if: startsWith(github.ref, 'refs/tags/')

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ erl_crash.dump
2222
/priv/native
2323

2424
/native/*/target
25+
26+
# The checksum files for precompiled NIFs
27+
checksum-*.exs

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66

77
* No longer support Elixir versions under 1.12 or Erlang/OTP versions under 23.0
88
* Support Elixir 1.13 and 1.14 and Erlang/OTP 25.0
9-
* Use Rustler v0.27
109

1110
### Enhancements
1211

1312
* Use Rust 2018 edition
14-
* Update to latest versions of `html5ever`, `xml5ever`, and `rustler`
13+
* Update to Rustler `v0.27`
14+
* Update to latest versions of Html5ever and Xml5ever
15+
* Use `rustler_precompiled` to precompile NIFs
1516

1617
### Fixes
1718

README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ Originally a fork of Hansihe's [html5ever_elixir](https://github.com/hansihe/htm
99

1010
Meeseeks_Html5ever requires a minimum combination of Elixir 1.12.0 and Erlang/OTP 23.0, and is tested with a maximum combination of Elixir 1.14.0 and Erlang/OTP 25.0.
1111

12-
## Dependencies
13-
14-
Meeseeks_Html5ever depends on the Rust library [html5ever](https://github.com/servo/html5ever), and you will need to have the Rust compiler [installed](https://www.rust-lang.org/en-US/install.html).
15-
1612
## Installation
1713

1814
Ensure Rust is installed, then add Meeseeks_Html5ever to your `mix.exs`:
@@ -27,6 +23,29 @@ end
2723

2824
Finally, run `mix deps.get`.
2925

26+
## Dependencies
27+
28+
Meeseeks_Html5ever depends on the Rust library [html5ever](https://github.com/servo/html5ever), providing a Rustler-based NIF to interface with it.
29+
30+
You do not need to have Rust installed because the library will attempt to download a precompiled NIF file.
31+
32+
To force compilation you can either set the `MEESEEKS_HTML5EVER_BUILD` environment variable to `true` or `1`, or add the following application configuration
33+
34+
```elixir
35+
config :meeseeks_html5ever, MeeseeksHtml5ever, build_from_source: true
36+
```
37+
38+
If you want to force compilation you will need to have the Rust compiler [installed](https://www.rust-lang.org/en-US/install.html), and will need to add Rustler to your dependencies.
39+
40+
```elixir
41+
def deps do
42+
[
43+
{:meeseeks_html5ever, "~> 0.13.1"},
44+
{:rustler, ">= 0.0.0", optional: true}
45+
]
46+
end
47+
```
48+
3049
## Contributing
3150

3251
If you are interested in contributing please read the [contribution guidelines](CONTRIBUTING.md).

RELEASE_CHECKLIST.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Release checklist
2+
3+
In order to release a new version to Hex.pm we first need to:
4+
5+
1. Write the changes in the `CHANGELOG.md` file
6+
1. Create a release branch named `release/<version>`
7+
1. Update the `README.md`, `CHANGELOG.md`, `mix.exs`, and `Cargo.toml` with the new version
8+
1. Commit with message `Release <version>`
9+
1. Merge PR to `main`
10+
1. Tag main with `git tag <version>`
11+
1. Push tag with `git push origin <version>`
12+
1. Wait for the CI to build all release files
13+
1. Run `mix rustler.download MeeseeksHtml5ever.Native --all --print`
14+
1. Copy the output of the mix task and add to the release notes
15+
1. Run `mix hex.publish` and **make sure the checksum file is present**
16+
in the list of files to be published.
17+
18+
It's important to ensure that we publish the checksum file with the
19+
package because otherwise the users won't be able to use the lib
20+
with precompiled files. They will need to always enforce compilation.

config/config.exs

Lines changed: 0 additions & 10 deletions
This file was deleted.

lib/meeseeks_html5ever.ex

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@ defmodule MeeseeksHtml5ever do
33
MeeseeksHtml5ever is intended for internal use by
44
[Meeseeks](https://github.com/mischov/meeseeks), and parses HTML or XML into
55
a `Meeseeks.Document`.
6+
7+
By default this lib will try to use a precompiled NIF from the GitHub
8+
releases page. This way you don't need to have the Rust toolchain installed.
9+
In case no precompiled file is found and the Mix env is production then an
10+
error is raised.
11+
12+
You can force the compilation to occur by setting the value of the
13+
`MEESEEKS_HTML5EVER_BUILD` environment variable to "true" or "1".
14+
Alternatively you can also set the application env `:build_from_source` to
15+
`true` in order to force the build:
16+
17+
```
18+
config :meeseeks_html5ever, MeeseeksHtml5ever, build_from_source: true
19+
```
620
"""
721

822
@doc """

lib/meeseeks_html5ever/native.ex

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
defmodule MeeseeksHtml5ever.Native do
22
@moduledoc false
33

4-
use Rustler, otp_app: :meeseeks_html5ever, crate: :meeseeks_html5ever_nif
4+
mix_config = Mix.Project.config()
5+
version = mix_config[:version]
6+
github_url = mix_config[:package][:links]["GitHub"]
7+
env_config = Application.compile_env(:meeseeks_html5ever, MeeseeksHtml5ever, [])
58

6-
defmodule NifNotLoadedError do
7-
@moduledoc false
8-
9-
defexception message: "nif not loaded"
10-
end
9+
use RustlerPrecompiled,
10+
otp_app: :meeseeks_html5ever,
11+
crate: "meeseeks_html5ever_nif",
12+
mode: :release,
13+
base_url: "#{github_url}/releases/download/v#{version}",
14+
force_build:
15+
System.get_env("MEESEEKS_HTML5EVER_BUILD") in ["1", "true"] or
16+
env_config[:build_from_source],
17+
version: version
1118

1219
def parse_html(_binary), do: err()
13-
1420
def parse_xml(_binary), do: err()
1521

16-
defp err() do
17-
:erlang.nif_error(%NifNotLoadedError{})
18-
end
22+
defp err(), do: :erlang.nif_error(:nif_not_loaded)
1923
end

0 commit comments

Comments
 (0)