diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..fbe00cbf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,77 @@ +name: Build Precompiled NIFs + +on: + push: + branches: + - main + paths: + - "native/**" + - ".github/workflows/release.yml" + tags: + - "v*" + pull_request: + paths: + - ".github/workflows/release.yml" + workflow_dispatch: + +permissions: + contents: write + +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.15"] + job: + - { target: aarch64-apple-darwin, os: macos-14 } + - { target: x86_64-apple-darwin, os: macos-13 } + - { target: aarch64-unknown-linux-gnu, os: ubuntu-22.04, use-cross: true } + - { target: aarch64-unknown-linux-musl, os: ubuntu-22.04, use-cross: true } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-22.04 } + - { target: x86_64-unknown-linux-musl, os: ubuntu-22.04, use-cross: true } + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Extract project version + shell: bash + run: | + # Extract version from mix.exs @version attribute + VERSION=$(sed -n 's/^[[:space:]]*@version "\(.*\)"/\1/p' mix.exs | head -n1) + if [ -z "$VERSION" ]; then + echo "::error::Failed to extract version from mix.exs" + exit 1 + fi + echo "PROJECT_VERSION=$VERSION" >> $GITHUB_ENV + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Build the project + id: build-crate + uses: philss/rustler-precompiled-action@v1.1.4 + with: + project-name: ecto_libsql + project-version: ${{ env.PROJECT_VERSION }} + target: ${{ matrix.job.target }} + nif-version: ${{ matrix.nif }} + use-cross: ${{ matrix.job.use-cross }} + cross-version: "from-source" + project-dir: "native/ecto_libsql" + + - name: Artifact upload + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.build-crate.outputs.file-name }} + path: ${{ steps.build-crate.outputs.file-path }} + + - name: Publish archives and packages + uses: softprops/action-gh-release@v2 + with: + files: | + ${{ steps.build-crate.outputs.file-path }} + if: startsWith(github.ref, 'refs/tags/') diff --git a/Cargo.toml b/Cargo.toml index 50058cc4..b0dc003b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,3 +3,6 @@ resolver = "2" members = ["native/ecto_libsql"] exclude = ["native/ecto_libsql/fuzz"] + +[profile.release] +lto = "thin" diff --git a/lib/ecto_libsql/native.ex b/lib/ecto_libsql/native.ex index 2a1ac78c..9623f9e5 100644 --- a/lib/ecto_libsql/native.ex +++ b/lib/ecto_libsql/native.ex @@ -39,9 +39,27 @@ defmodule EctoLibSql.Native do """ - use Rustler, + mix_config = Mix.Project.config() + version = mix_config[:version] + github_url = mix_config[:package][:links]["GitHub"] + mode = if Mix.env() in [:dev, :test], do: :debug, else: :release + + use RustlerPrecompiled, otp_app: :ecto_libsql, - crate: :ecto_libsql + crate: "ecto_libsql", + version: version, + base_url: "#{github_url}/releases/download/v#{version}", + targets: ~w( + aarch64-apple-darwin + aarch64-unknown-linux-gnu + aarch64-unknown-linux-musl + x86_64-apple-darwin + x86_64-unknown-linux-gnu + x86_64-unknown-linux-musl + ), + nif_versions: ["2.15"], + mode: mode, + force_build: System.get_env("ECTO_LIBSQL_BUILD") in ["1", "true"] # Raw NIF functions - implemented in Rust (native/ecto_libsql/src/lib.rs) # These all raise :nif_not_loaded errors until the NIF is loaded diff --git a/mix.exs b/mix.exs index 36ab087a..b860efea 100644 --- a/mix.exs +++ b/mix.exs @@ -3,6 +3,8 @@ defmodule EctoLibSql.MixProject do @version "0.8.9" @source_url "https://github.com/ocean/ecto_libsql" + @dev? String.ends_with?(@version, "-dev") + @force_build? System.get_env("ECTO_LIBSQL_BUILD") in ["1", "true"] def project do [ @@ -55,7 +57,8 @@ defmodule EctoLibSql.MixProject do {:ecto_sql, "~> 3.11"}, {:ex_doc, "~> 0.31", only: :dev, runtime: false}, {:jason, "~> 1.4"}, - {:rustler, "~> 0.37.1"}, + {:rustler, "~> 0.37.1", optional: not (@dev? or @force_build?)}, + {:rustler_precompiled, "~> 0.8"}, {:sobelow, "~> 0.13", only: [:dev, :test], runtime: false}, {:stream_data, "~> 1.0", only: [:dev, :test]} ] @@ -64,7 +67,8 @@ defmodule EctoLibSql.MixProject do defp package() do [ name: "ecto_libsql", - files: ~w(lib priv .formatter.exs mix.exs README.md LICENSE CHANGELOG.md USAGE.md native), + files: + ~w(lib priv .formatter.exs mix.exs README.md LICENSE CHANGELOG.md USAGE.md ECTO_MIGRATION_GUIDE.md SECURITY.md native checksum-*.exs), licenses: ["Apache-2.0"], links: %{ "GitHub" => @source_url, diff --git a/mix.lock b/mix.lock index 4b5cd56d..08df823a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,6 @@ %{ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "castore": {:hex, :castore, "1.0.17", "4f9770d2d45fbd91dcf6bd404cf64e7e58fed04fadda0923dc32acca0badffa2", [:mix], [], "hexpm", "12d24b9d80b910dd3953e165636d68f147a31db945d2dcb9365e441f8b5351e5"}, "credo": {:hex, :credo, "1.7.16", "a9f1389d13d19c631cb123c77a813dbf16449a2aebf602f590defa08953309d4", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0562af33756b21f248f066a9119e3890722031b6d199f22e3cf95550e4f1579"}, "db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, @@ -16,6 +17,7 @@ "makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "rustler": {:hex, :rustler, "0.37.1", "721434020c7f6f8e1cdc57f44f75c490435b01de96384f8ccb96043f12e8a7e0", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "24547e9b8640cf00e6a2071acb710f3e12ce0346692e45098d84d45cdb54fd79"}, + "rustler_precompiled": {:hex, :rustler_precompiled, "0.8.4", "700a878312acfac79fb6c572bb8b57f5aae05fe1cf70d34b5974850bbf2c05bf", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "3b33d99b540b15f142ba47944f7a163a25069f6d608783c321029bc1ffb09514"}, "sobelow": {:hex, :sobelow, "0.14.1", "2f81e8632f15574cba2402bcddff5497b413c01e6f094bc0ab94e83c2f74db81", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8fac9a2bd90fdc4b15d6fca6e1608efb7f7c600fa75800813b794ee9364c87f2"}, "stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, diff --git a/native/ecto_libsql/.cargo/config.toml b/native/ecto_libsql/.cargo/config.toml new file mode 100644 index 00000000..42285d42 --- /dev/null +++ b/native/ecto_libsql/.cargo/config.toml @@ -0,0 +1,20 @@ +# Cross-compilation configuration for rustler_precompiled NIF builds. +# +# macOS: Use dynamic lookup for Erlang NIF symbols (resolved at load time). +# musl: Disable static CRT linking for compatibility with BEAM's dynamic loader. + +[target.'cfg(target_os = "macos")'] +rustflags = [ + "-C", "link-arg=-undefined", + "-C", "link-arg=dynamic_lookup", +] + +[target.x86_64-unknown-linux-musl] +rustflags = [ + "-C", "target-feature=-crt-static", +] + +[target.aarch64-unknown-linux-musl] +rustflags = [ + "-C", "target-feature=-crt-static", +] diff --git a/native/ecto_libsql/Cargo.toml b/native/ecto_libsql/Cargo.toml index 8f8ff7cb..79afc666 100644 --- a/native/ecto_libsql/Cargo.toml +++ b/native/ecto_libsql/Cargo.toml @@ -16,7 +16,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] libsql = { version = "0.9.29", features = ["encryption", "replication"] } -rustler = "0.37.0" +rustler = { version = "0.37.1", default-features = false, features = ["derive", "nif_version_2_15"] } tokio = "1.45.1" uuid = "1.17.0" bytes = "1.5"