From f8311a28f4bfc7e6ede6edcfd081807f49cd9248 Mon Sep 17 00:00:00 2001 From: 0x4bs3nt Date: Mon, 1 Jun 2026 22:16:09 +0200 Subject: [PATCH] Add Nix flake with dev shell and prebuilt run target Let contributors enter a reproducible dev shell and let anyone try the editor with `nix run`. - Pin Zig 0.16 in the dev shell via the zig-overlay input, fixing `bun dev` failing the Zig version check (Rust already comes from rust-overlay) - Wrap the prebuilt upstream Linux release tarball (x86_64 and aarch64) as packages.default/apps.default, patchelf'd for the bundled CEF runtime - Add update-nix.yml to bump the pinned version and per-arch hashes when a release is published - Exclude generated and vendored trees (.direnv, dist, build, target, src-tauri) from the test runner so direnv-materialized copies are not run - Ignore .direnv and nix result artifacts --- .github/workflows/update-nix.yml | 123 +++++++++++++++++++++++ .gitignore | 7 ++ flake.lock | 139 ++++++++++++++++++++++++++ flake.nix | 25 ++++- nix/dev-shell.nix | 3 +- nix/package.nix | 162 +++++++++++++++++++++++++++++++ vite.config.ts | 17 +++- 7 files changed, 473 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/update-nix.yml create mode 100644 flake.lock create mode 100644 nix/package.nix diff --git a/.github/workflows/update-nix.yml b/.github/workflows/update-nix.yml new file mode 100644 index 000000000..acedfb430 --- /dev/null +++ b/.github/workflows/update-nix.yml @@ -0,0 +1,123 @@ +name: Update Nix package + +# Keeps nix/package.nix pinned to the latest release. This repo publishes the +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: write + pull-requests: write + +jobs: + update-nix: + # Only the canonical repo publishes the release tarballs; skip on forks. + if: github.repository == 'athasdev/athas' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Resolve target version + id: release + env: + GH_TOKEN: ${{ github.token }} + EVENT_NAME: ${{ github.event_name }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + set -euo pipefail + # On a release event use that tag; otherwise take the latest published release of this repo + if [ "$EVENT_NAME" = "release" ] && [ -n "${RELEASE_TAG:-}" ]; then + version="$RELEASE_TAG" + else + version="$(gh release view --json tagName -q .tagName)" + fi + + version="${version#v}" + if ! printf '%s' "$version" | grep -qP '^\d+\.\d+\.\d+$'; then + echo "::error::Unexpected version format: '$version'" + exit 1 + fi + + echo "version=$version" >> "$GITHUB_OUTPUT" + + - name: Check whether an update is needed + id: check + run: | + set -euo pipefail + current="$(grep -m1 'version = "' nix/package.nix | sed 's/.*"\(.*\)".*/\1/')" + echo "current=$current" >> "$GITHUB_OUTPUT" + + if [ "$current" = "${{ steps.release.outputs.version }}" ]; then + echo "Already pinned to ${current}; nothing to do." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "Pinned: ${current} -> target: ${{ steps.release.outputs.version }}" + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Compute SRI hashes for both arches + if: steps.check.outputs.skip != 'true' + id: hash + env: + VERSION: ${{ steps.release.outputs.version }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + for arch in x86_64 aarch64; do + url="https://github.com/${REPO}/releases/download/v${VERSION}/Athas_${VERSION}_linux-${arch}.tar.gz" + tmp="$(mktemp)" + echo "Fetching ${url}" + curl -fSL --retry 3 --retry-delay 10 "$url" -o "$tmp" + sri="sha256-$(sha256sum "$tmp" | cut -d' ' -f1 | xxd -r -p | base64 -w0)" + rm -f "$tmp" + echo "${arch}: ${sri}" + echo "${arch}=${sri}" >> "$GITHUB_OUTPUT" + done + + - name: Update nix/package.nix + if: steps.check.outputs.skip != 'true' + env: + VERSION: ${{ steps.release.outputs.version }} + X86_HASH: ${{ steps.hash.outputs.x86_64 }} + ARM_HASH: ${{ steps.hash.outputs.aarch64 }} + run: | + set -euo pipefail + sed -i "s|version = \".*\";|version = \"${VERSION}\";|" nix/package.nix + sed -i "s|\"x86_64-linux\" = \"sha256-[^\"]*\";|\"x86_64-linux\" = \"${X86_HASH}\";|" nix/package.nix + sed -i "s|\"aarch64-linux\" = \"sha256-[^\"]*\";|\"aarch64-linux\" = \"${ARM_HASH}\";|" nix/package.nix + + echo "----- diff -----" + git diff -- nix/package.nix + + - name: Create pull request + if: steps.check.outputs.skip != 'true' + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ steps.release.outputs.version }} + run: | + set -euo pipefail + branch="nix/update-${VERSION}" + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # If the bump PR branch already exists, don't duplicate it. + if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then + echo "Branch ${branch} already exists; skipping." + exit 0 + fi + + git checkout -b "$branch" + git add nix/package.nix + git commit \ + -m "Update the wrapped Athas Linux release" \ + -m "Bumps the prebuilt Linux release pinned by the Nix flake to v${VERSION}, updating version and both per-arch SRI hashes in nix/package.nix." + git push origin "$branch" + + gh pr create \ + --title "Update the wrapped Athas Linux release" \ + --body "Update the wrapped Athas Linux release to v${VERSION}. + + - Update \`version\` and both per-arch SRI hashes in \`nix/package.nix\` + - Generated automatically by the \`Update Nix package\` workflow" diff --git a/.gitignore b/.gitignore index 02470bc93..985c96807 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,13 @@ # dependency directories node_modules/ +# direnv (nix flake) local cache +.direnv/ + +# nix build result symlinks +result +result-* + # Build output dist/ diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..ed92ef0e5 --- /dev/null +++ b/flake.lock @@ -0,0 +1,139 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "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", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1777268161, + "narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay", + "zig-overlay": "zig-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1777346187, + "narHash": "sha256-oVxyGjpiIsrXhWTJVUOs38fZQkLjd0nZGOY9K7Kfot8=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "146e7bf7569b8288f24d41d806b9f584f7cfd5b5", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "146e7bf7569b8288f24d41d806b9f584f7cfd5b5", + "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" + } + }, + "systems_2": { + "flake": false, + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig-overlay": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems_2" + }, + "locked": { + "lastModified": 1780323358, + "narHash": "sha256-iK4P0ZYOb3D4S8DqvWUivGfjoMjZnAQEc5Kr5crIDBc=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "22d713bdaec64681e56f5b69289789e3dd5430e0", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index a0b719ed2..263e1dfe0 100644 --- a/flake.nix +++ b/flake.nix @@ -8,6 +8,10 @@ url = "github:oxalica/rust-overlay/146e7bf7569b8288f24d41d806b9f584f7cfd5b5"; inputs.nixpkgs.follows = "nixpkgs"; }; + zig-overlay = { + url = "github:mitchellh/zig-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = @@ -15,6 +19,7 @@ nixpkgs, flake-utils, rust-overlay, + zig-overlay, ... }: flake-utils.lib.eachSystem @@ -30,8 +35,26 @@ overlays = [ rust-overlay.overlays.default ]; }; in + let + athas = pkgs.callPackage ./nix/package.nix { }; + in { - devShells.default = pkgs.callPackage ./nix/dev-shell.nix { }; + devShells.default = pkgs.callPackage ./nix/dev-shell.nix { + zig = zig-overlay.packages.${system}."0.16.0"; + }; + + packages = { + default = athas; + athas = athas; + }; + + apps.default = { + type = "app"; + program = "${athas}/bin/athas"; + meta = { + description = "Run the Athas editor (prebuilt Linux release)"; + }; + }; } ); } diff --git a/nix/dev-shell.nix b/nix/dev-shell.nix index b03245bba..598733fb5 100644 --- a/nix/dev-shell.nix +++ b/nix/dev-shell.nix @@ -1,4 +1,4 @@ -{ lib, pkgs }: +{ lib, pkgs, zig }: let rustToolchain = pkgs.rust-bin.nightly.latest.default.override { @@ -47,6 +47,7 @@ pkgs.mkShell ( nodejs_22 rustToolchain rust-analyzer + zig ] ++ lib.optionals stdenv.isLinux linuxPackages; diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 000000000..1fe3d0f45 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,162 @@ +{ + lib, + stdenv, + fetchurl, + autoPatchelfHook, + makeWrapper, + wrapGAppsHook3, + glib, + gtk3, + gdk-pixbuf, + cairo, + pango, + atk, + at-spi2-core, + nss, + nspr, + dbus, + cups, + expat, + zlib, + xz, + alsa-lib, + libxkbcommon, + libgbm, + mesa, + libGL, + libdrm, + fontconfig, + freetype, + systemdLibs, + libx11, + libxcb, + libxcomposite, + libxdamage, + libxext, + libxfixes, + libxrandr, + libxrender, + libxcursor, + libxi, + libxtst, + libxscrnsaver, + libxshmfence, +}: + +let + pname = "athas"; + version = "0.7.2"; + + # Keep the formatting of these lines stable + # (one `"" = "sha256-...";` per line) so the workflow's sed can find + # them. + hashes = { + "x86_64-linux" = "sha256-0E+N59njEy3n3QAnUTBTH3p5fELFEnt/caBAOshiT+Y="; + "aarch64-linux" = "sha256-kRGg6rWkB6xE+y8K8qn9xouAklixSHODOXc1bPdGcXE="; + }; + + arches = { + "x86_64-linux" = "x86_64"; + "aarch64-linux" = "aarch64"; + }; + + mkSource = + system: + fetchurl { + url = "https://github.com/athasdev/athas/releases/download/v${version}/Athas_${version}_linux-${arches.${system}}.tar.gz"; + hash = hashes.${system}; + }; + + src = + if hashes ? ${stdenv.hostPlatform.system} then + mkSource stdenv.hostPlatform.system + else + throw "athas: unsupported system ${stdenv.hostPlatform.system}"; + + runtimeLibs = [ + glib + gtk3 + gdk-pixbuf + cairo + pango + atk + at-spi2-core + nss + nspr + dbus + cups + expat + zlib + xz + alsa-lib + libxkbcommon + libgbm + mesa + libGL + libdrm + fontconfig + freetype + systemdLibs + libx11 + libxcb + libxcomposite + libxdamage + libxext + libxfixes + libxrandr + libxrender + libxcursor + libxi + libxtst + libxscrnsaver + libxshmfence + ]; +in +stdenv.mkDerivation { + inherit pname version src; + + sourceRoot = "athas.app"; + + nativeBuildInputs = [ + autoPatchelfHook + makeWrapper + wrapGAppsHook3 + ]; + + buildInputs = runtimeLibs; + + dontWrapGApps = true; + + # chrome-sandbox needs a setuid bit that the Nix store cannot provide; the + # launcher runs with --no-sandbox, so skip it during autoPatchelf too. + autoPatchelfIgnoreMissingDeps = [ "libvulkan.so.1" ]; + + installPhase = '' + runHook preInstall + + mkdir -p $out/libexec $out/lib $out/share + cp -r libexec/. $out/libexec/ + cp -r lib/. $out/lib/ + cp -r share/. $out/share/ + + makeWrapper $out/libexec/athas $out/bin/athas \ + --add-flags "--no-sandbox --ozone-platform=x11 --disable-vulkan --disable-features=Vulkan" \ + --prefix LD_LIBRARY_PATH : "$out/libexec:${lib.makeLibraryPath runtimeLibs}" \ + "''${gappsWrapperArgs[@]}" + + runHook postInstall + ''; + + meta = { + description = "Athas — a fast, extensible code editor (prebuilt Linux release)"; + homepage = "https://github.com/athasdev/athas"; + changelog = "https://github.com/athasdev/athas/releases/tag/v${version}"; + license = lib.licenses.agpl3Only; + sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ]; + platforms = [ + "x86_64-linux" + "aarch64-linux" + ]; + mainProgram = "athas"; + }; +} diff --git a/vite.config.ts b/vite.config.ts index 95fb28ec7..ee2e9c56f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,7 +2,7 @@ import path from "node:path"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import { codeInspectorPlugin } from "code-inspector-plugin"; -import { defineConfig } from "vite-plus"; +import { defaultExclude, defineConfig } from "vite-plus"; const host = process.env.TAURI_DEV_HOST || "127.0.0.1"; const isVitest = Boolean(process.env.VITEST); @@ -39,6 +39,21 @@ export default defineConfig({ }, }, + test: { + // The runner's default exclude only covers node_modules/.git, so it would + // otherwise discover stale duplicate suites inside generated or vendored + // trees — notably the direnv-materialized copy of the source under + // .direnv/flake-inputs/-source/. + exclude: [ + ...defaultExclude, + "**/.direnv/**", + "**/dist/**", + "**/build/**", + "**/target/**", + "**/src-tauri/**", + ], + }, + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // // 1. prevent vite from obscuring rust errors