From 6d424d8e5f39326ff1f840d0eeaf8764b3030829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ci=C4=99=C5=BCarkiewicz?= Date: Wed, 21 Sep 2022 09:40:14 -0700 Subject: [PATCH] Add flake.nix --- .envrc | 1 + env.sh | 2 - flake.lock | 154 ++++++++++++++++ flake.nix | 287 +++++++++++++++++++++++++++++ misc/git-hooks/commit-template.txt | 2 + misc/git-hooks/pre-commit | 59 ++++++ 6 files changed, 503 insertions(+), 2 deletions(-) create mode 100644 .envrc delete mode 100644 env.sh create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 misc/git-hooks/commit-template.txt create mode 100755 misc/git-hooks/pre-commit 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/env.sh b/env.sh deleted file mode 100644 index 58a0a15..0000000 --- a/env.sh +++ /dev/null @@ -1,2 +0,0 @@ -export AR=/opt/homebrew/opt/llvm/bin/llvm-ar -export CC=/opt/homebrew/opt/llvm/bin/clang diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..b94dee6 --- /dev/null +++ b/flake.lock @@ -0,0 +1,154 @@ +{ + "nodes": { + "crane": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1662608853, + "narHash": "sha256-XevBt/KxyRIjFwXIIojZ7kaVOVi14NPTWG1sKeLjLzc=", + "owner": "ipetkov", + "repo": "crane", + "rev": "2d5e7fbfcee984424fe4ad4b3b077c62d18fe1cf", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "rev": "2d5e7fbfcee984424fe4ad4b3b077c62d18fe1cf", + "type": "github" + } + }, + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1663570974, + "narHash": "sha256-ncUdRdY70VdJIX6Mi+820xeD7FutADd3NbQR0BKkFYA=", + "owner": "nix-community", + "repo": "fenix", + "rev": "02093d3aca186135da78b76ac28ec58031391076", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1650374568, + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1650374568, + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1663494212, + "narHash": "sha256-gUJir35fXQB7jQKCTffM8str9N6N2soaOL0TqQfJod4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b47d4447dc2ba34f793436e6631fbdd56b12934a", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-22.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "fenix": "fenix", + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1662896065, + "narHash": "sha256-1LkSsXzI1JTAmP/GMTz4fTJd8y/tw8R79l96q+h7mu8=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "2e9f1204ca01c3e20898d4a67c8b84899d394a88", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..2aa66b0 --- /dev/null +++ b/flake.nix @@ -0,0 +1,287 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.05"; + crane.url = "github:ipetkov/crane?rev=2d5e7fbfcee984424fe4ad4b3b077c62d18fe1cf"; # v0.6 + crane.inputs.nixpkgs.follows = "nixpkgs"; + flake-utils.url = "github:numtide/flake-utils"; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + }; + + outputs = { self, nixpkgs, flake-utils, flake-compat, fenix, crane }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + }; + + lib = pkgs.lib; + target = "wasm32-unknown-unknown"; + + fenix-channel = fenix.packages.${system}.latest; + + fenix-toolchain = with fenix.packages.${system}; combine [ + latest.cargo + latest.rustc + targets.${target}.latest.rust-std + ]; + + craneLib = crane.lib.${system}.overrideToolchain fenix-toolchain; + + # filter source code at path `src` to include only the list of `modules` + filterModules = modules: src: + let + basePath = toString src + "/"; + relPathAllCargoTomlFiles = builtins.filter + (pathStr: lib.strings.hasSuffix "/Cargo.toml" pathStr) + (builtins.map (path: lib.removePrefix basePath (toString path)) (lib.filesystem.listFilesRecursive src)); + in + lib.cleanSourceWith { + filter = (path: type: + let + relPath = lib.removePrefix basePath (toString path); + includePath = + # traverse only into directories that somewhere in there contain `Cargo.toml` file, or were explicitily whitelisted + (type == "directory" && lib.any (cargoTomlPath: lib.strings.hasPrefix relPath cargoTomlPath) relPathAllCargoTomlFiles) || + lib.any + (re: builtins.match re relPath != null) + ([ "Cargo.lock" "Cargo.toml" ".*/Cargo.toml" ] ++ builtins.concatLists (map (name: [ name "${name}/.*" ]) modules)); + in + # uncomment to debug: + # builtins.trace "${relPath}: ${lib.boolToString includePath}" + includePath + ); + inherit src; + }; + + # Filter only files needed to build project dependencies + # + # To get good build times it's vitally important to not have to + # rebuild derivation needlessly. The way Nix caches things + # is very simple: if any input file changed, derivation needs to + # be rebuild. + # + # For this reason this filter function strips the `src` from + # any files that are not relevant to the build. + # + # Lile `filterWorkspaceFiles` but doesn't even need *.rs files + # (because they are not used for building dependencies) + filterWorkspaceDepsBuildFiles = src: filterSrcWithRegexes [ "Cargo.lock" "Cargo.toml" ".*/Cargo.toml" ] src; + + # Filter only files relevant to building the workspace + filterWorkspaceFiles = src: filterSrcWithRegexes [ "Cargo.lock" "Cargo.toml" ".*/Cargo.toml" ".*\.rs" ] src; + + # Like `filterWorkspaceFiles` but with `./scripts/` included + filterWorkspaceCliTestFiles = src: filterSrcWithRegexes [ "Cargo.lock" "Cargo.toml" ".*/Cargo.toml" ".*\.rs" "scripts/.*" ] src; + + filterSrcWithRegexes = regexes: src: + let + basePath = toString src + "/"; + in + lib.cleanSourceWith { + filter = (path: type: + let + relPath = lib.removePrefix basePath (toString path); + includePath = + (type == "directory") || + lib.any + (re: builtins.match re relPath != null) + regexes; + in + # uncomment to debug: + # builtins.trace "${relPath}: ${lib.boolToString includePath}" + includePath + ); + inherit src; + }; + + + stdenv = pkgs.llvmPackages_14.stdenv; + + commonArgs = { + src = filterWorkspaceFiles ./webimint; + + inherit stdenv; + cargoExtraArgs = "--target ${target}"; + + buildInputs = with pkgs; [ + clang + gcc + pkg-config + openssl + fenix-channel.rustc + fenix-channel.clippy + ] ++ lib.optionals stdenv.isDarwin [ + libiconv + darwin.apple_sdk.frameworks.Security + ]; + + nativeBuildInputs = with pkgs; [ + pkg-config + ]; + + LIBCLANG_PATH = "${pkgs.llvmPackages_14.libclang.lib}/lib/"; + # https://github.com/ipetkov/crane/issues/105#issuecomment-1249557271 + + # Fix wasm32 compilation + CC_wasm32_unknown_unknown = "${pkgs.llvmPackages_14.clang-unwrapped}/bin/clang-14"; + CFLAGS_wasm32_unknown_unknown = "-I ${pkgs.llvmPackages_14.libclang.lib}/lib/clang/14.0.1/include/"; + }; + + workspaceDeps = craneLib.buildDepsOnly (commonArgs // { + src = filterWorkspaceDepsBuildFiles ./webimint; + pname = "workspace-deps"; + buildPhaseCargoCommand = "cargo doc && cargo check --profile release --all-targets && cargo build --profile release --all-targets"; + doCheck = false; + }); + + workspaceBuild = craneLib.buildPackage (commonArgs // { + pname = "workspace-build"; + cargoArtifacts = workspaceDeps; + doCheck = false; + }); + + workspaceTest = craneLib.cargoBuild (commonArgs // { + pname = "workspace-test"; + cargoBuildCommand = "true"; + cargoArtifacts = workspaceDeps; + doCheck = true; + }); + + workspaceClippy = craneLib.cargoClippy (commonArgs // { + pname = "workspace-clippy"; + cargoArtifacts = workspaceDeps; + + cargoClippyExtraArgs = "--all-targets --no-deps -- --deny warnings"; + doInstallCargoArtifacts = false; + doCheck = false; + }); + + workspaceDoc = craneLib.cargoBuild (commonArgs // { + pname = "workspace-doc"; + cargoArtifacts = workspaceDeps; + cargoBuildCommand = "env RUSTDOCFLAGS='-D rustdoc::broken_intra_doc_links' cargo doc --no-deps --document-private-items && cp -a target/doc $out"; + doCheck = false; + }); + + # a function to define cargo&nix package, listing + # all the dependencies (as dir) to help limit the + # amount of things that need to rebuild when some + # file change + pkg = { name ? null, dir, port ? 8000, extraDirs ? [ ] }: rec { + package = craneLib.buildPackage (commonArgs // { + cargoArtifacts = workspaceDeps; + + src = filterModules ([ dir ] ++ extraDirs) ./.; + + # if needed we will check the whole workspace at once with `workspaceBuild` + doCheck = false; + } // lib.optionalAttrs (name != null) { + pname = name; + cargoExtraArgs = "--bin ${name}"; + }); + + container = pkgs.dockerTools.buildLayeredImage { + name = name; + contents = [ package ]; + config = { + Cmd = [ + "${package}/bin/${name}" + ]; + ExposedPorts = { + "${builtins.toString port}/tcp" = { }; + }; + }; + }; + }; + + in + { + packages = { + default = workspaceBuild; + + inherit workspaceDeps + workspaceBuild + workspaceClippy + workspaceTest + workspaceDoc; + }; + + checks = { + inherit + workspaceBuild + workspaceClippy; + }; + + devShells = + { + # The default shell - meant to developers working on the project, + # so notably not building any project binaries, but including all + # the settings and tools neccessary to build and work with the codebase. + default = pkgs.mkShell { + stdenv = pkgs.llvmPackages_14.stdenv; + buildInputs = workspaceDeps.buildInputs; + nativeBuildInputs = with pkgs; workspaceDeps.nativeBuildInputs ++ [ + fenix-toolchain + fenix.packages.${system}.rust-analyzer + cargo-udeps + + nodejs + wasm-pack + + # This is required to prevent a mangled bash shell in nix develop + # see: https://discourse.nixos.org/t/interactive-bash-with-nix-develop-flake/15486 + (hiPrio pkgs.bashInteractive) + + # Nix + pkgs.nixpkgs-fmt + pkgs.shellcheck + pkgs.rnix-lsp + pkgs.nodePackages.bash-language-server + ]; + + # # CC = "${stdenv.cc}/bin/cc"; + # # AR = "${stdenv.cc.nativePrefix}ar"; + # FOO = "${stdenv.cc}"; + # BAR = "${pkgs.llvmPackages_14.clang-unwrapped}"; + RUST_SRC_PATH = "${fenix-channel.rust-src}/lib/rustlib/src/rust/library"; + + LIBCLANG_PATH = "${pkgs.libclang.lib}/lib/"; + # CC_wasm32-unknown-unknown= "${pkgs.llvmPackages_14.clang-unwrapped}/bin/clang-14"; + # CFLAGS_wasm32-unknown-unknown= "-I ${pkgs.llvmPackages_14.libclang.lib}/lib/clang/14.0.1/include/"; + + shellHook = '' + export CC_wasm32_unknown_unknown="${pkgs.llvmPackages_14.clang-unwrapped}/bin/clang-14" + export CFLAGS_wasm32_unknown_unknown="-I ${pkgs.llvmPackages_14.libclang.lib}/lib/clang/14.0.1/include/" + # auto-install git hooks + for hook in misc/git-hooks/* ; do ln -sf "../../$hook" "./.git/hooks/" ; done + ${pkgs.git}/bin/git config commit.template misc/git-hooks/commit-template.txt + + # workaround https://github.com/rust-lang/cargo/issues/11020 + cargo_cmd_bins=( $(ls $HOME/.cargo/bin/cargo-{clippy,udeps,llvm-cov} 2>/dev/null) ) + if (( ''${#cargo_cmd_bins[@]} != 0 )); then + echo "Warning: Detected binaries that might conflict with reproducible environment: ''${cargo_cmd_bins[@]}" 1>&2 + echo "Warning: Considering deleting them. See https://github.com/rust-lang/cargo/issues/11020 for details" 1>&2 + fi + ''; + }; + + # this shell is used only in CI, so it should contain minimum amount + # of stuff to avoid building and caching things we don't need + lint = pkgs.mkShell { + nativeBuildInputs = [ + pkgs.rustfmt + pkgs.nixpkgs-fmt + pkgs.shellcheck + pkgs.git + ]; + }; + }; + }); +} diff --git a/misc/git-hooks/commit-template.txt b/misc/git-hooks/commit-template.txt new file mode 100644 index 0000000..a9aff55 --- /dev/null +++ b/misc/git-hooks/commit-template.txt @@ -0,0 +1,2 @@ + +# Explain *why* this change is being made width limit ->| diff --git a/misc/git-hooks/pre-commit b/misc/git-hooks/pre-commit new file mode 100755 index 0000000..f20f54f --- /dev/null +++ b/misc/git-hooks/pre-commit @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +set -eo pipefail + +# Revert `git stash` on exit +function revert_git_stash { + >&2 echo "Unstashing uncommited changes..." + git stash pop -q +} + +set +e +git diff-files --quiet +is_unclean=$? +set -e + +# Stash pending changes and revert them when script ends +if [ $is_unclean -ne 0 ]; then + >&2 echo "Stashing uncommited changes..." + git stash -q --keep-index + trap revert_git_stash EXIT +fi + +git_ls_files="$(git ls-files)" + +>&2 echo "Checking *.nix files..." +# shellcheck disable=SC2046 +nixpkgs-fmt --check $(echo "$git_ls_files" | grep -E '.*\.nix$') + + +>&2 echo "Checking Rust projects files..." +# Note: avoid `cargo fmt --all` so we don't need extra stuff in `ci` shell +# so that CI is faster +# shellcheck disable=SC2046 +rustfmt --edition 2021 --check $(echo "$git_ls_files" | grep -E '.*\.rs$') + + +>&2 echo "Checking shell script files ..." +for path in $(echo "$git_ls_files" | grep -E '.*\.sh$') ; do + shellcheck --severity=warning "$path" +done + +errors="" +for path in $(echo "$git_ls_files" | grep -v -E '.*\.(ods|ico|png)'); do + # extra branches for clarity + if [ ! -s "$path" ]; then + # echo "$path is empty" + true + elif [ -z "$(tail -c 1 < "$path")" ]; then + # echo "$path ends with a newline or with a null byte" + true + else + echo "$path doesn't end with a newline" 1>&2 + errors="true" + fi +done + +if [ -n "$errors" ]; then + exit 1 +fi