diff --git a/.direnv/flake-profile b/.direnv/flake-profile new file mode 120000 index 0000000..0c05709 --- /dev/null +++ b/.direnv/flake-profile @@ -0,0 +1 @@ +flake-profile-1-link \ No newline at end of file diff --git a/.direnv/flake-profile-1-link b/.direnv/flake-profile-1-link new file mode 120000 index 0000000..5f2e094 --- /dev/null +++ b/.direnv/flake-profile-1-link @@ -0,0 +1 @@ +/nix/store/92nlyr8jm4zk2rwdz2pvm2jwa5hj6za1-nix-shell-x86_64-w64-mingw32-env \ No newline at end of file diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/gw2buttplug-rs.yml b/.github/workflows/gw2buttplug-rs.yml new file mode 100644 index 0000000..598f96b --- /dev/null +++ b/.github/workflows/gw2buttplug-rs.yml @@ -0,0 +1,112 @@ +env: + CI_ALLOW_ROOT: '1' + CI_CONFIG: ./ci.nix + CI_PLATFORM: gh-actions +jobs: + ci-check: + name: gw2buttplug-rs check + runs-on: ubuntu-latest + steps: + - id: checkout + name: git clone + uses: actions/checkout@v4 + with: + submodules: true + - id: nix-install + name: nix install + uses: arcnmx/ci/actions/nix/install@v0.7 + - id: ci-action-build + name: nix build ci.gh-actions.configFile + uses: arcnmx/ci/actions/nix/build@v0.7 + with: + attrs: ci.gh-actions.configFile + out-link: .ci/workflow.yml + - id: ci-action-compare + name: gh-actions compare + uses: arcnmx/ci/actions/nix/run@v0.7 + with: + args: -u .github/workflows/gw2buttplug-rs.yml .ci/workflow.yml + attrs: nixpkgs.diffutils + command: diff + main: + name: gw2buttplug-rs-main + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - id: checkout + name: git clone + uses: actions/checkout@v4 + with: + submodules: true + - id: nix-install + name: nix install + uses: arcnmx/ci/actions/nix/install@v0.7 + - id: ci-setup + name: nix setup + uses: arcnmx/ci/actions/nix/run@v0.7 + with: + attrs: ci.job.main.run.bootstrap + quiet: false + - id: ci-dirty + name: nix test dirty + uses: arcnmx/ci/actions/nix/run@v0.7 + with: + attrs: ci.job.main.run.test + command: ci-build-dirty + quiet: false + stdout: ${{ runner.temp }}/ci.build.dirty + - id: ci-test + name: nix test build + uses: arcnmx/ci/actions/nix/run@v0.7 + with: + attrs: ci.job.main.run.test + command: ci-build-realise + ignore-exit-code: true + quiet: false + stdin: ${{ runner.temp }}/ci.build.dirty + - env: + CI_EXIT_CODE: ${{ steps.ci-test.outputs.exit-code }} + id: ci-summary + name: nix test results + uses: arcnmx/ci/actions/nix/run@v0.7 + with: + attrs: ci.job.main.run.test + command: ci-build-summarise + quiet: false + stdin: ${{ runner.temp }}/ci.build.dirty + stdout: ${{ runner.temp }}/ci.build.cache + - env: + CACHIX_SIGNING_KEY: ${{ secrets.CACHIX_SIGNING_KEY }} + id: ci-cache + if: always() + name: nix test cache + uses: arcnmx/ci/actions/nix/run@v0.7 + with: + attrs: ci.job.main.run.test + command: ci-build-cache + quiet: false + stdin: ${{ runner.temp }}/ci.build.cache + - id: artifact-build + name: artifact build + uses: arcnmx/ci/actions/nix/build@v0.7 + with: + attrs: config.jobs.main.artifactPackage + file: + out-link: .ci/artifacts + - id: artifact-upload + name: artifact upload + uses: actions/upload-artifact@v4 + with: + name: gw2buttplug-rs + path: .ci/artifacts/lib/gw2buttplug-rs*.dll + - id: release-upload + if: startsWith(github.ref, 'refs/tags/') + name: release + uses: softprops/action-gh-release@v1 + with: + files: .ci/artifacts/lib/gw2buttplug-rs.dll +name: gw2buttplug-rs +'on': +- push +- pull_request diff --git a/ci.nix b/ci.nix new file mode 100644 index 0000000..774b9b2 --- /dev/null +++ b/ci.nix @@ -0,0 +1,102 @@ +{ config, pkgs, lib, ... }: with pkgs; with lib; let + self = import ./.; + packages = self.packages.${pkgs.system}; + artifactRoot = ".ci/artifacts"; + artifacts = "${artifactRoot}/lib/gw2buttplug-rs*.dll"; + release = "${artifactRoot}/lib/gw2buttplug-rs.dll"; +in +{ + config = { + name = "gw2buttplug-rs"; + ci.gh-actions = { + enable = true; + export = true; + }; + # TODO: add cachix + cache.cachix.example = { + enable = true; + }; + channels = { + nixpkgs = { + # see https://github.com/arcnmx/nixexprs-rust/issues/10 + args.config.checkMetaRecursively = false; + version = "22.11"; + }; + }; + tasks = { + build.inputs = with packages; [ example ]; + cache.inputs = with packages; [ example example.cargoArtifacts ]; + }; + jobs = { + main = { + tasks = { + build-windows.inputs = singleton packages.example; + build-windows-space.inputs = singleton packages.example; + }; + artifactPackages = { + main = packages.example; + }; + }; + }; + + # XXX: symlinks are not followed, see https://github.com/softprops/action-gh-release/issues/182 + #artifactPackage = config.artifactPackages.win64; + artifactPackage = runCommand "example-artifacts" { } ('' + mkdir -p $out/lib + cp ${config.artifactPackages.main}/lib/gw2buttplug-rs.dll $out/lib/ + '' + concatStringsSep "\n" (mapAttrsToList (key: addonPath: '' + cp ${addonPath}/lib/gw2buttplug-rs.dll $out/lib/nexus_example_addon-${key}.dll + '') config.artifactPackages)); + + gh-actions = { + jobs = mkIf (config.id != "ci") { + ${config.id} = { + permissions = { + contents = "write"; + }; + step = { + artifact-build = { + order = 1100; + name = "artifact build"; + uses = { + # XXX: a very hacky way of getting the runner + inherit (config.gh-actions.jobs.${config.id}.step.ci-setup.uses) owner repo version; + path = "actions/nix/build"; + }; + "with" = { + file = ""; + attrs = "config.jobs.${config.jobId}.artifactPackage"; + out-link = artifactRoot; + }; + }; + artifact-upload = { + order = 1110; + name = "artifact upload"; + uses.path = "actions/upload-artifact@v4"; + "with" = { + name = "gw2buttplug-rs"; + path = artifacts; + }; + }; + release-upload = { + order = 1111; + name = "release"; + "if" = "startsWith(github.ref, 'refs/tags/')"; + uses.path = "softprops/action-gh-release@v1"; + "with".files = release; + }; + }; + }; + }; + }; + }; + options = { + artifactPackage = mkOption { + type = types.package; + }; + artifactPackages = mkOption { + type = with types; attrsOf package; + }; + }; +} + diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..9e94b40 --- /dev/null +++ b/default.nix @@ -0,0 +1,11 @@ +let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + flakeCompat = fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + }; + exampleAddon = import flakeCompat { + src = ./.; + }; +in +exampleAddon.defaultNix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d2899d7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,133 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1751562746, + "narHash": "sha256-smpugNIkmDeicNz301Ll1bD7nFOty97T79m4GUMUczA=", + "owner": "ipetkov", + "repo": "crane", + "rev": "aed2020fd3dc26e1e857d4107a5a67a33ab6c1fd", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1751870679, + "narHash": "sha256-aP4korVsN5Yy+PB9zjjm8Qbo3a69/m8vlFXS5mdVXtk=", + "owner": "nix-community", + "repo": "fenix", + "rev": "95606d64662a730da5d3031ed798dd6315d35f33", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1751852175, + "narHash": "sha256-+MLlfTCCOvz4K6AcSPbaPiFM9MYi7fA2Wr1ibmRwIlM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2defa37146df235ef62f566cde69930a86f14df1", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "fenix": "fenix", + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1751792916, + "narHash": "sha256-YjyurHKMrUYKjnujSqjpFtHGYFCGr2Xpo1Xc1AYT1+M=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "0ac65592a833bf40238831dd10e15283d63c46d5", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..116a7d8 --- /dev/null +++ b/flake.nix @@ -0,0 +1,65 @@ +{ + description = "nexus-rs addon fo yo hole"; + inputs = { + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + flake-utils = { + url = "github:numtide/flake-utils"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + crane.url = "github:ipetkov/crane"; + }; + + outputs = { self, fenix, flake-utils, crane, nixpkgs, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = (import nixpkgs) { + inherit system; + crossSystem.config = "x86_64-w64-mingw32"; + }; + + packageToolchain = with fenix.packages.${system}; + combine [ + minimal.rustc + minimal.cargo + targets.x86_64-pc-windows-gnu.latest.rust-std + ]; + + packageCraneLib = (crane.mkLib pkgs).overrideToolchain (p: packageToolchain); + + example = pkgs.callPackage ./package.nix { + craneLib = packageCraneLib; + }; + + shellToolchain = with fenix.packages.${system}; + combine [ + complete + rust-analyzer + targets.x86_64-pc-windows-gnu.latest.rust-std + ]; + + shellCraneLib = (crane.mkLib pkgs).overrideToolchain (p: shellToolchain); + + exampleShell = import ./shell.nix { + inherit fenix pkgs system; + }; + in + rec { + defaultPackage = packages.x86_64-pc-windows-gnu; + inherit pkgs; + devShells.default = exampleShell; + + packages = { + inherit example; + default = example; + }; + }); +} + diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..59b6873 --- /dev/null +++ b/package.nix @@ -0,0 +1,43 @@ +{ lib, buildPackages, craneLib, stdenv, windows, pkg-config, features ? []}: + +craneLib.buildPackage rec { + src = ./.; + strictDeps = true; + cargoExtraArgs = if features != [] then lib.escapeShellArgs (["--features"] ++ features) else ""; + + buildInputs = [ + stdenv.cc + windows.pthreads + ]; + + depsBuildBuild = [ + pkg-config + ]; + + # libgit2 stuff is given as an example of how to provide libraries to the build process + #LD_LIBRARY_PATH="${lib.makeLibraryPath [buildPackages.buildPackages.libgit2]}"; + + nativeBuildInputs = [ + buildPackages.stdenv.cc + #libgit2 + ]; + + doCheck = false; + + LIBGIT2_NO_VENDOR = 1; + + # Tells Cargo that we're building for Windows. + # (https://doc.rust-lang.org/cargo/reference/config.html#buildtarget) + CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu"; + + #TARGET_CC = "${pkgsCross.stdenv.cc}/bin/${pkgsCross.stdenv.cc.targetPrefix}cc"; + TARGET_CC = "${stdenv.cc.targetPrefix}cc"; + + # Build without a dependency not provided by wine + CXXFLAGS_x86_64_pc_windows_gnu = "-Oz -shared -fno-threadsafe-statics"; + PROFILE="release"; + CARGO_BUILD_RUSTFLAGS = [ + "-C" + "linker=${TARGET_CC}" + ]; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..e2d4b62 --- /dev/null +++ b/shell.nix @@ -0,0 +1,46 @@ +{ fenix ? (import (builtins.fetchTarball "https://github.com/nix-community/fenix/archive/main.tar.gz") { }) +, pkgs ? (import { + crossSystem = { + config = "x86_64-w64-mingw32"; + }; + }) +, system ? builtins.currentSystem +}: +let + fenix' = fenix.packages.${system}; +in +pkgs.callPackage + ({ mkShell, lib, buildPackages, stdenv, windows, libgit2, pkg-config }: mkShell rec { + buildInputs = [ + stdenv.cc + windows.pthreads + ]; + + depsBuildBuild = [ + pkg-config + ]; + + LD_LIBRARY_PATH="${lib.makeLibraryPath [buildPackages.buildPackages.libgit2]}"; + nativeBuildInputs = [ + buildPackages.stdenv.cc + libgit2 + (fenix'.combine [ + (fenix'.complete.withComponents [ + "cargo" + "rust-src" + "clippy" + "rustc" + ]) + fenix'.rust-analyzer + fenix'.latest.rustfmt + fenix'.targets.x86_64-pc-windows-gnu.latest.rust-std + ]) + ]; + + LIBGIT2_NO_VENDOR=1; + CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu"; + TARGET_CC = "${stdenv.cc.targetPrefix}cc"; + CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER = TARGET_CC; + CXXFLAGS_x86_64_pc_windows_gnu = "-Oz -shared -fno-threadsafe-statics"; + }) +{ }