diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..3550a30f2 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 80e292024..46903fb6d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ __pycache__/ # Claude Code local preview/dev-server config .claude/launch.json + +# nix-direnv +.direnv/ diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..fd09ceec8 --- /dev/null +++ b/flake.nix @@ -0,0 +1,151 @@ +{ + description = "Open CAD Studio - 2D drafting and 3D modeling in Rust"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + # rust-overlay ships upstream Rust binaries rather than building them, so + # following nixpkgs costs it no binary cache. + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, rust-overlay }: + let + systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + forAllSystems = f: + nixpkgs.lib.genAttrs systems (system: f (import nixpkgs { + inherit system; + overlays = [ rust-overlay.overlays.default ]; + })); + in + { + packages = forAllSystems (pkgs: + let + inherit (pkgs) lib stdenv; + + toolchain = pkgs.rust-bin.stable.latest.default.override { + targets = [ "wasm32-unknown-unknown" ]; + }; + + # Build with the overlay's toolchain rather than the nixpkgs default, + # so the package and the devShell compile with the same rustc. + rustPlatform = pkgs.makeRustPlatform { + cargo = toolchain; + rustc = toolchain; + }; + + # winit/wgpu dlopen these at run time, so they must also be on the + # wrapped binary's LD_LIBRARY_PATH, not just at link time. + runtimeLibs = with pkgs; [ + libGL + libxkbcommon + vulkan-loader + wayland + xorg.libX11 + xorg.libXcursor + xorg.libXi + xorg.libXrandr + ]; + in + rec { + default = opencadstudio; + + opencadstudio = rustPlatform.buildRustPackage { + pname = "opencadstudio"; + version = (lib.importTOML ./Cargo.toml).package.version; + src = self; + + cargoLock = { + lockFile = ./Cargo.lock; + # One entry per distinct git source in Cargo.lock. Replace each + # lib.fakeHash with the sha256 that `nix build` prints on the + # first attempt; they must be refreshed whenever the matching + # rev in Cargo.toml moves. + outputHashes = { + "acadrust-0.5.5" = lib.fakeHash; + "cadkernel-0.1.0" = lib.fakeHash; + "iced-0.15.0-dev" = lib.fakeHash; + "iced_aw-0.14.1" = lib.fakeHash; + }; + }; + + nativeBuildInputs = with pkgs; [ + pkg-config + makeWrapper + ]; + + buildInputs = with pkgs; [ + fontconfig + freetype + ] ++ runtimeLibs + ++ lib.optionals stdenv.hostPlatform.isDarwin [ darwin.apple_sdk.frameworks.AppKit ]; + + # build.rs asks git for a revision and falls back to "unknown" + # when the repo or the binary is absent, which it is in the + # sandbox. Nothing to do but let it take the fallback. + doCheck = false; + + postInstall = lib.optionalString stdenv.hostPlatform.isLinux '' + wrapProgram $out/bin/OpenCADStudio \ + --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath runtimeLibs} + ''; + + meta = { + description = "Open-source 2D drafting and 3D modeling, built with Rust"; + homepage = "https://www.opencadstudio.com"; + license = lib.licenses.gpl3Only; + mainProgram = "OpenCADStudio"; + platforms = systems; + }; + }; + }); + + devShells = forAllSystems (pkgs: + let + toolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" "clippy" "rustfmt" ]; + targets = [ "wasm32-unknown-unknown" ]; + }; + runtimeLibs = with pkgs; [ + libGL + libxkbcommon + vulkan-loader + wayland + xorg.libX11 + xorg.libXcursor + xorg.libXi + xorg.libXrandr + ]; + in + { + default = pkgs.mkShell { + packages = with pkgs; [ + toolchain + pkg-config + fontconfig + freetype + git + # Web build: `trunk build` per Trunk.toml. wasm-bindgen-cli must + # match the wasm-bindgen version Cargo.lock resolves, the same + # constraint .github/workflows/pages.yml:83 reads out of the lock. + trunk + wasm-bindgen-cli + # Rasterises assets/logo.svg for packaging, as release.yml does. + librsvg + ] ++ runtimeLibs; + + # A dev binary is run straight out of ./target, so it never gets + # the wrapper the package build applies. + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs; + }; + }); + + checks = forAllSystems (pkgs: { + default = self.packages.${pkgs.stdenv.hostPlatform.system}.opencadstudio; + }); + + formatter = forAllSystems (pkgs: pkgs.nixpkgs-fmt); + }; +}