diff --git a/.envrc b/.envrc index a7d4a4fe..3550a30f 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -# use flake +use flake diff --git a/.github/workflows/common.yml b/.github/workflows/common.yml index 7cc76256..dc4592f1 100644 --- a/.github/workflows/common.yml +++ b/.github/workflows/common.yml @@ -32,6 +32,7 @@ jobs: format: name: Format runs-on: ubuntu-22.04 + if: "!contains(github.event.head_commit.message, '[skip format]')" steps: - name: Checkout uses: actions/checkout@v3 @@ -61,9 +62,11 @@ jobs: working-directory: ${{ inputs.workdir }} shell: bash run: ./format.sh && git diff --exit-code + build: name: Build runs-on: ubuntu-22.04 + if: "!contains(github.event.head_commit.message, '[skip build]')" steps: - name: Checkout uses: actions/checkout@v3 @@ -103,9 +106,39 @@ jobs: - name: Benchmark Test shell: bash run: ./utils/bench.py ${{ inputs.workdir }} --default --frames 10 + nix: name: Nix runs-on: ubuntu-22.04 + if: ${{ !contains(github.event.head_commit.message, '[skip nix]') }} + outputs: + flake_status: ${{ steps.early.outputs.flake_status }} + shell_status: ${{ steps.early.outputs.shell_status }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - id: early + name: Check if nix files exist + shell: bash + working-directory: ${{ inputs.workdir }} + run: | + flake_status="skip" + if [ -f "derivation.nix" ]; then + flake_status="found" + fi + echo "flake_status=${flake_status}" >> "${GITHUB_OUTPUT}" + + shell_status="skip" + if [ -f "shell.nix" ]; then + shell_status="found" + fi + echo "shell_status=${shell_status}" >> "${GITHUB_OUTPUT}" + + nix-flake: + name: Nix Flake + runs-on: ubuntu-22.04 + needs: nix + if: ${{ !contains(github.event.head_commit.message, '[skip nix flake]') && needs.nix.outputs.flake_status == 'found' }} steps: - name: Checkout uses: actions/checkout@v3 @@ -113,16 +146,66 @@ jobs: submodules: recursive - name: Set Up Build Cache uses: actions/cache@v3 + id: nix-cache with: - path: | - /nix - key: ${{ inputs.workdir }}-nix-${{ hashFiles(format('{0}/shell.nix', inputs.workdir)) }} - restore-keys: ${{ inputs.workdir }}-nix- + path: /tmp/nix-store.nar + key: ${{ runner.os }}-${{ inputs.workdir }}-nix-store.nar-${{ hashFiles('flake.nix', 'flake.lock', format('{0}/derivation.nix', inputs.workdir)) }} + restore-keys: ${{ runner.os }}-${{ inputs.workdir }}-nix-store.nar- - name: Install Deps uses: cachix/install-nix-action@v15 with: nix_path: nixpkgs=channel:nixos-22.11 - - name: Run + # see: https://github.com/cachix/install-nix-action/issues/56#issuecomment-1300421537 + - name: "Import Nix Store Cache" + if: "steps.nix-cache.outputs.cache-hit == 'true'" + run: "nix-store --import < /tmp/nix-store.nar" + - name: Flake Build + shell: bash + run: | + nix eval .#packages --impure --raw \ + --apply '(import ./utils/get-builds-for-lang.nix { lang = "${{ inputs.workdir }}"; attr = "packages"; })' \ + | xargs nix build -L + - name: Flake Run + shell: bash + run: | + nix eval .#packages --impure --raw \ + --apply '(import ./utils/get-builds-for-lang.nix { lang = "${{ inputs.workdir }}"; prefix = ".#"; })' \ + | xargs -I{} nix run {} -- --help + - name: Flake Shell + shell: bash + working-directory: ${{ inputs.workdir }} + run: nix develop ..#${{ inputs.workdir }} --command bash -c './build.sh && ./rosettaboy-release --help' + - name: Flake Checks + shell: bash + run: | + nix eval .#checks --impure --raw \ + --apply '(import ./utils/get-builds-for-lang.nix { lang = "${{ inputs.workdir }}"; attr = "checks"; })' \ + | xargs nix build -L + - name: "Export Nix Store Cache" + shell: bash + # we could add devShells here but it might fill the cache fast... + run: | + nix eval .#packages --impure --raw \ + --apply '(import ./utils/get-builds-for-lang.nix { lang = "${{ inputs.workdir }}"; attr = "packages"; })' \ + | xargs -I{} bash -c "nix eval --raw {}; echo" \ + | xargs nix-store -qR \ + | xargs nix-store --export > /tmp/nix-store.nar + + nix-shell: + name: Nix Shell + runs-on: ubuntu-22.04 + needs: nix + if: ${{ !contains(github.event.head_commit.message, '[skip nix flake]') && needs.nix.outputs.shell_status == 'found' }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + - name: Install Nix + uses: cachix/install-nix-action@v15 + with: + nix_path: nixpkgs=channel:nixos-22.11 + - name: Run Shell shell: bash working-directory: ${{ inputs.workdir }} run: nix-shell --pure --run './build.sh && ./rosettaboy-release --help' diff --git a/.github/workflows/nim.yml b/.github/workflows/nim.yml index f788b31a..de56e653 100644 --- a/.github/workflows/nim.yml +++ b/.github/workflows/nim.yml @@ -22,4 +22,4 @@ jobs: with: workdir: nim with-nim: stable - build-pkgs: libsdl2-dev + build-pkgs: libsdl2-dev \ No newline at end of file diff --git a/README.md b/README.md index d1a3b5aa..40c7cb42 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ If you have either Nix or Docker available, you can run `./utils/shell.sh` to cr If you prefer Docker, you can use `./utils/shell-docker.sh` instead. -If you prefer Nix, you can manually run `nix-shell` instead. When run in an implemention's directory, `nix-shell` will only provide what is needed for that language, and when run in the project root it will provide everything needed for all languages. +If you prefer Nix, you can manually run `nix develop` or `nix-shell` instead. When run with an implementation as an argument, e.g. `nix develop .#py`, it will only provide what is needed for that language, and when run in the project root it will provide everything needed for all languages. Alternatively, there is also an integration with [nix-direnv](https://github.com/nix-community/nix-direnv). Benchmarks diff --git a/c/.envrc b/c/.envrc new file mode 120000 index 00000000..8be2b97d --- /dev/null +++ b/c/.envrc @@ -0,0 +1 @@ +../utils/.envrc \ No newline at end of file diff --git a/c/default.nix b/c/default.nix deleted file mode 100644 index d03760d9..00000000 --- a/c/default.nix +++ /dev/null @@ -1,33 +0,0 @@ -{ pkgs ? import { } -}: - -let - makeDerivation = { stdenv, ltoSupport, debugSupport }: pkgs.callPackage ./derivation.nix { - inherit ltoSupport debugSupport stdenv; - }; - - makeDerivation' = stdenv: (pkgs.recurseIntoAttrs { - default = makeDerivation { - inherit stdenv; - ltoSupport = false; - debugSupport = false; - }; - - debug = makeDerivation { - inherit stdenv; - ltoSupport = false; - debugSupport = true; - }; - - lto = makeDerivation { - inherit stdenv; - ltoSupport = true; - debugSupport = true; - }; - }); -in - -pkgs.recurseIntoAttrs { - gcc = makeDerivation' pkgs.stdenv; - clang = makeDerivation' pkgs.clangStdenv; -} diff --git a/c/derivation.nix b/c/derivation.nix index be7bf46e..a0f48b61 100644 --- a/c/derivation.nix +++ b/c/derivation.nix @@ -1,32 +1,35 @@ -{ lib -, stdenv -, cmake -, SDL2 -, autoPatchelfHook -, pkg-config -, valgrind ? null -, clang-tools ? null -, nixpkgs-fmt ? null -, ltoSupport ? false -, debugSupport ? false +{ + lib, + stdenv, + cmake, + SDL2, + pkg-config, + cleanSource, + clang-tools ? null, + ltoSupport ? false, + debugSupport ? false }: let - devTools = [ clang-tools nixpkgs-fmt valgrind ]; + devTools = [ clang-tools ]; in -stdenv.mkDerivation { +stdenv.mkDerivation rec { name = "rosettaboy-c"; - - src = ./.; + src = cleanSource { + inherit name; + src = ./.; + extraRules = '' + .clang-format + ''; + }; passthru = { inherit devTools; }; enableParallelBuilding = true; buildInputs = [ SDL2 ]; - nativeBuildInputs = [ autoPatchelfHook cmake pkg-config ] - ++ lib.optionals debugSupport devTools; + nativeBuildInputs = [ cmake pkg-config ]; cmakeFlags = [ ] ++ lib.optional debugSupport "-DCMAKE_BUILD_TYPE=Debug" @@ -34,8 +37,8 @@ stdenv.mkDerivation { ++ lib.optional ltoSupport "-DENABLE_LTO=On" ; - meta = with lib; { - description = "rosettaboy-c"; - mainProgram = "rosettaboy-c"; + meta = { + description = name; + mainProgram = name; }; } diff --git a/c/shell.nix b/c/shell.nix deleted file mode 100644 index 80f21d6d..00000000 --- a/c/shell.nix +++ /dev/null @@ -1,10 +0,0 @@ -{ pkgs ? import { } }: - -let - packages = pkgs.callPackage ./default.nix { }; -in - -pkgs.mkShell { - inputsFrom = [ packages.gcc.default ]; - buildInputs = [ packages.gcc.default.devTools ]; -} diff --git a/cpp/.gitignore b/cpp/.gitignore index cee88c10..419c39b1 100644 --- a/cpp/.gitignore +++ b/cpp/.gitignore @@ -10,3 +10,4 @@ CMakeFiles Makefile cmake_install.cmake rosettaboy-cpp +/result* \ No newline at end of file diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 6b96c763..7ff1f1e4 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -8,9 +8,13 @@ option(ENABLE_LTO "enable LTO" OFF) include(CheckIPOSupported) check_ipo_supported(RESULT supported OUTPUT error) +include(GNUInstallDirs) + include_directories(/usr/local/include/) add_executable(rosettaboy-cpp src/main.cpp src/args.cpp src/cpu.cpp src/cart.cpp src/gameboy.cpp src/gpu.cpp src/consts.h src/cart.h src/cpu.h src/gpu.h src/args.h src/apu.cpp src/apu.h src/ram.cpp src/ram.h src/buttons.cpp src/buttons.h src/clock.cpp src/clock.h) +install(TARGETS rosettaboy-cpp + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) if( ENABLE_LTO AND supported ) message(STATUS "IPO / LTO enabled") diff --git a/cpp/derivation.nix b/cpp/derivation.nix new file mode 100644 index 00000000..85a08d01 --- /dev/null +++ b/cpp/derivation.nix @@ -0,0 +1,41 @@ +{ + lib, + stdenv, + cmake, + SDL2, + fmt_8, + pkg-config, + cleanSource, + clang-format ? null, + ltoSupport ? false, + debugSupport ? false, +}: + +stdenv.mkDerivation rec { + name = "rosettaboy-cpp"; + src = cleanSource { + inherit name; + src = ./.; + extraRules = '' + .clang-format + ''; + }; + + buildInputs = [ SDL2 fmt_8 ]; + nativeBuildInputs = [ cmake pkg-config ]; + + passthru = { + devTools = [ clang-format ]; + }; + + cmakeFlags = [ ] + ++ lib.optional debugSupport "-DCMAKE_BUILD_TYPE=Debug" + ++ lib.optional (!debugSupport) "-DCMAKE_BUILD_TYPE=Release" + ++ lib.optional ltoSupport "-DENABLE_LTO=On" + ; + + meta = { + description = name; + mainProgram = name; + }; +} diff --git a/default.nix b/default.nix new file mode 100644 index 00000000..e6195e86 --- /dev/null +++ b/default.nix @@ -0,0 +1,10 @@ +(import + ( + let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; } +).defaultNix \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..70cb8549 --- /dev/null +++ b/flake.lock @@ -0,0 +1,283 @@ +{ + "nodes": { + "cl-gameboy": { + "flake": false, + "locked": { + "lastModified": 1479058009, + "narHash": "sha256-qvT5wbRCrngd5tUPaEWqEUj53mGwjz87+eibn3qWd3k=", + "owner": "sjl", + "repo": "cl-gameboy", + "rev": "cce73054d4bfde332e31a2938f4e0630c162cf6b", + "type": "github" + }, + "original": { + "owner": "sjl", + "repo": "cl-gameboy", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gb-autotest-roms": { + "flake": false, + "locked": { + "lastModified": 1658898280, + "narHash": "sha256-i5TkoWJ9f+OZYwr0oyaFigCkfPQkRBReYqBfXxcyXmM=", + "owner": "shish", + "repo": "gb-autotest-roms", + "rev": "367b7dc51aed4b84eea8097d7426fe2e3cd2f459", + "type": "github" + }, + "original": { + "owner": "shish", + "repo": "gb-autotest-roms", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "gomod2nix-src": { + "flake": false, + "locked": { + "lastModified": 1662501203, + "narHash": "sha256-4BKeqCX2zwgBiTdlc2DjGQ0CttKm0vSw0r/bdFdM/PQ=", + "owner": "nix-community", + "repo": "gomod2nix", + "rev": "89cd0675b96775aa3ee86e7c0cf5bc238dd27976", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "gomod2nix", + "type": "github" + } + }, + "naersk": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1671096816, + "narHash": "sha256-ezQCsNgmpUHdZANDCILm3RvtO1xH8uujk/+EqNvzIOg=", + "owner": "nix-community", + "repo": "naersk", + "rev": "d998160d6a076cfe8f9741e56aeec7e267e3e114", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, + "nim-argparse": { + "flake": false, + "locked": { + "lastModified": 1669660759, + "narHash": "sha256-rtA0NJbA7aQglw/4U/fhX8GTlGf0eYoXfuzlJsA38Q8=", + "owner": "iffy", + "repo": "nim-argparse", + "rev": "98c7c99bfbcaae750ac515a6fd603f85ed68668f", + "type": "github" + }, + "original": { + "owner": "iffy", + "repo": "nim-argparse", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1675757258, + "narHash": "sha256-pIRer8vdsoherlRKpzfnHbMZ5TsAcvRlXHCIaHkIUbg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "af96094e9b8eb162d70a84fa3b39f4b7a8b264d2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-22.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "php-sdl-src": { + "flake": false, + "locked": { + "lastModified": 1672500911, + "narHash": "sha256-8BTE9pjoJuomSuNxvoBLjbBE8eA3BkO9+8CjG4kO3M0=", + "owner": "Ponup", + "repo": "php-sdl", + "rev": "655e403b8a9681c418702a74833c68c1a4ae1bd5", + "type": "github" + }, + "original": { + "owner": "Ponup", + "repo": "php-sdl", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": [ + "flake-compat" + ], + "flake-utils": [ + "flake-utils" + ], + "gitignore": [ + "gitignore" + ], + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1675688762, + "narHash": "sha256-oit/SxMk0B380ASuztBGQLe8TttO1GJiXF8aZY9AYEc=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "ab608394886fb04b8a5df3cb0bab2598400e3634", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "cl-gameboy": "cl-gameboy", + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "gb-autotest-roms": "gb-autotest-roms", + "gitignore": "gitignore", + "gomod2nix-src": "gomod2nix-src", + "naersk": "naersk", + "nim-argparse": "nim-argparse", + "nixpkgs": "nixpkgs", + "php-sdl-src": "php-sdl-src", + "pre-commit-hooks": "pre-commit-hooks", + "zig-clap": "zig-clap", + "zig-overlay": "zig-overlay", + "zig-sdl": "zig-sdl" + } + }, + "zig-clap": { + "flake": false, + "locked": { + "lastModified": 1675698794, + "narHash": "sha256-p541p6rmQu6uh/uhd72fM/PWo8UeliCEn59g6WJsktQ=", + "owner": "Hejsil", + "repo": "zig-clap", + "rev": "272d8e2088b2cae037349fb260dc05ec46bba422", + "type": "github" + }, + "original": { + "owner": "Hejsil", + "repo": "zig-clap", + "rev": "272d8e2088b2cae037349fb260dc05ec46bba422", + "type": "github" + } + }, + "zig-overlay": { + "inputs": { + "flake-compat": [ + "flake-compat" + ], + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1675746787, + "narHash": "sha256-62E0b7i51LNHBsWbsi2ChG5gCqZfTb8/UaZren6DqXs=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "6a76f5697bcf291d1a7d88b0ef8189c2f5c9b38f", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + }, + "zig-sdl": { + "flake": false, + "locked": { + "lastModified": 1675665690, + "narHash": "sha256-pmDzS6gDhYVqPq5nqoZzQRQ3BkKXebKbmNRYqeMjXLU=", + "owner": "MasterQ32", + "repo": "SDL.zig", + "rev": "6b33f1f4299ec8814e9fb3b206cda37791ced574", + "type": "github" + }, + "original": { + "owner": "MasterQ32", + "repo": "SDL.zig", + "rev": "6b33f1f4299ec8814e9fb3b206cda37791ced574", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..d19d482e --- /dev/null +++ b/flake.nix @@ -0,0 +1,241 @@ +{ + description = "rosettaboy nix flake"; + inputs = { + nixpkgs.url = github:NixOS/nixpkgs/nixos-22.11; + flake-utils.url = github:numtide/flake-utils; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + gitignore = { + url = "github:hercules-ci/gitignore.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + pre-commit-hooks = { + url = "github:cachix/pre-commit-hooks.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.nixpkgs-stable.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + inputs.flake-compat.follows = "flake-compat"; + inputs.gitignore.follows = "gitignore"; + }; + gomod2nix-src = { + url = "github:nix-community/gomod2nix"; + flake = false; + }; + nim-argparse = { + url = "github:iffy/nim-argparse"; + flake = false; + }; + php-sdl-src = { + url = "github:Ponup/php-sdl"; + flake = false; + }; + naersk = { + url = "github:nix-community/naersk"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + zig-overlay = { + url = "github:mitchellh/zig-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + inputs.flake-compat.follows = "flake-compat"; + }; + zig-sdl = { + url = "github:MasterQ32/SDL.zig/6b33f1f4299ec8814e9fb3b206cda37791ced574"; + flake = false; + }; + zig-clap = { + url = "github:Hejsil/zig-clap/272d8e2088b2cae037349fb260dc05ec46bba422"; + flake = false; + }; + gb-autotest-roms = { + url = "github:shish/gb-autotest-roms"; + flake = false; + }; + cl-gameboy = { + url = "github:sjl/cl-gameboy"; + flake = false; + }; + }; + + outputs = { + nixpkgs, + flake-utils, + gitignore, + pre-commit-hooks, + gomod2nix-src, + nim-argparse, + php-sdl-src, + naersk, + zig-overlay, + zig-sdl, + zig-clap, + gb-autotest-roms, + cl-gameboy, + ... + }: flake-utils.lib.eachDefaultSystem (system: let + pkgs = nixpkgs.legacyPackages.${system}; + lib = pkgs.lib; + inherit (builtins) mapAttrs; + inherit (lib) hiPrio filterAttrs; + gomod2nix' = rec { + gomod2nix = pkgs.callPackage "${gomod2nix-src}" { inherit buildGoApplication mkGoEnv; }; + inherit (pkgs.callPackage "${gomod2nix-src}/builder" { inherit gomod2nix; }) buildGoApplication mkGoEnv; + }; + callPackage = pkgs.newScope { + inherit gb-autotest-roms cl-gameboy; + # inherit (gitignore.lib) gitignoreSource; + cleanSource = import ./utils/clean-source.nix { inherit (gitignore.lib) gitignoreFilterWith; inherit (lib) cleanSourceWith; }; + inherit php-sdl-src; + inherit nim-argparse; + inherit (gomod2nix') gomod2nix buildGoApplication; + naersk = pkgs.callPackage naersk {}; + zig = zig-overlay.packages.${system}.master-2023-02-06; + inherit zig-clap zig-sdl; + }; + + utils = callPackage ./utils/derivation.nix {}; + + mkC = {clangSupport ? false, ltoSupport ? false, debugSupport ? false}: + callPackage ./c/derivation.nix { + stdenv = if clangSupport then pkgs.clangStdenv else pkgs.stdenv; + inherit ltoSupport debugSupport; + }; + + mkCpp = {ltoSupport ? false, debugSupport ? false, clangSupport ? false}: + callPackage ./cpp/derivation.nix { + stdenv = if clangSupport then pkgs.clangStdenv else pkgs.stdenv; + inherit ltoSupport debugSupport; + }; + + mkGo = {...}: + callPackage ./go/derivation.nix { + }; + + mkNim = {debugSupport ? false, speedSupport ? false}: + callPackage ./nim/derivation.nix { + inherit debugSupport speedSupport; + inherit (pkgs.llvmPackages_14) bintools; + }; + + mkPhp = {opcacheSupport ? false}: + callPackage ./php/derivation.nix { + inherit opcacheSupport; + }; + + mkPxd = {}: + callPackage ./pxd/derivation.nix {}; + + mkPy = {mypycSupport ? false}: + callPackage ./py/derivation.nix { + inherit mypycSupport; + }; + + mkRs = {ltoSupport ? false, debugSupport ? false}: + callPackage ./rs/derivation.nix { + inherit ltoSupport debugSupport; + }; + + mkZig = {safeSupport ? false, fastSupport ? false}: + callPackage ./zig/derivation.nix { + inherit safeSupport fastSupport; + }; + + pre-commit-check = pre-commit-hooks.lib.${system}.run { + src = ./.; + hooks = { + actionlint.enable = true; + #deadnix.enable = true; + shellcheck.enable = true; + }; + }; + + in rec { + packages = rec { + inherit utils; + + c-debug = mkC { debugSupport = true; }; + c-lto = mkC { ltoSupport = true; }; + c-release = mkC { }; + c-clang-debug = mkC { debugSupport = true; clangSupport = true; }; + c-clang-lto = mkC { ltoSupport = true; clangSupport = true; }; + c-clang-release = mkC { clangSupport = true; }; + c = hiPrio c-release; + + cpp-release = mkCpp {}; + cpp-debug = mkCpp { debugSupport = true; }; + cpp-lto = mkCpp { ltoSupport = true; }; + cpp-clang-release = mkCpp { clangSupport = true; }; + cpp-clang-debug = mkCpp { debugSupport = true; clangSupport = true; }; + cpp-clang-lto = mkCpp { ltoSupport = true; clangSupport = true; }; + cpp = hiPrio cpp-release; + + go-release = mkGo {}; + go = hiPrio go-release; + + nim-release = mkNim {}; + nim-debug = mkNim { debugSupport = true; }; + nim-speed = mkNim { speedSupport = true; }; + nim = hiPrio nim-release; + + php-release = mkPhp {}; + php-opcache = mkPhp { opcacheSupport = true; }; + php = hiPrio php-release; + + py-release = mkPy {}; + py-mypyc = mkPy { mypycSupport = true; }; + py = hiPrio py-release; + + pxd-release = mkPxd {}; + pxd = hiPrio pxd-release; + + rs-debug = mkRs { debugSupport = true; }; + rs-release = mkRs { }; + rs-lto = mkRs { ltoSupport = true; }; + rs = hiPrio rs-release; + + zig-fast = mkZig { fastSupport = true; }; + zig-safe = mkZig { safeSupport = true; }; + zig = hiPrio zig-fast; + + # I don't think we can join all of them because they collide + default = pkgs.symlinkJoin { + name = "rosettaboy"; + paths = [ c cpp go nim php pxd py rs zig ]; + # if we use this without adding build tags to the executable, + # it'll build all variants but not symlink them + # paths = builtins.attrValues (filterAttrs (n: v: n != "default") packages); + }; + }; + + checks = let + # zig-safe is too slow - skip + packagesToCheck = filterAttrs (n: p: p.meta ? mainProgram && n != "zig-safe") packages; + in { + inherit pre-commit-check; + } // mapAttrs (_: utils.mkBlargg) packagesToCheck; + + devShells = let + shellHook = '' + export GB_DEFAULT_AUTOTEST_ROM_DIR=${gb-autotest-roms} + export GB_DEFAULT_BENCH_ROM=${cl-gameboy}/roms/opus5.gb + ''; + langDevShells = mapAttrs (name: package: pkgs.mkShell { + inputsFrom = [ package ]; + buildInputs = package.devTools or []; + inherit shellHook; + }) packages; + in langDevShells // { + default = pkgs.mkShell { + inputsFrom = builtins.attrValues langDevShells; + inherit (pre-commit-check) shellHook; + }; + # something wrong with using it in `inputsFrom` + py = pkgs.mkShell { + buildInputs = packages.py.devTools; + inherit shellHook; + }; + }; + }); +} diff --git a/go/derivation.nix b/go/derivation.nix new file mode 100644 index 00000000..9d6d0bd2 --- /dev/null +++ b/go/derivation.nix @@ -0,0 +1,30 @@ +{ + buildGoApplication, + cleanSource, + pkg-config, + SDL2, + gomod2nix +}: + +buildGoApplication rec { + name = "rosettaboy-go"; + src = cleanSource { + inherit name; + src = ./.; + }; + modules = ./gomod2nix.toml; + + passthru.devTools = [ gomod2nix ]; + + buildInputs = [ SDL2 ]; + nativeBuildInputs = [ pkg-config ]; + + postInstall = '' + mv $out/bin/src $out/bin/rosettaboy-go + ''; + + meta = { + description = name; + mainProgram = name; + }; +} diff --git a/go/gomod2nix.toml b/go/gomod2nix.toml new file mode 100644 index 00000000..ee536e80 --- /dev/null +++ b/go/gomod2nix.toml @@ -0,0 +1,6 @@ +schema = 3 + +[mod] + [mod."github.com/veandco/go-sdl2"] + version = "v0.4.24" + hash = "sha256-PnWW2f94PP8jILjFX+qg/NIxkPiF2KMAS8rfipE7atM=" diff --git a/nim/derivation.nix b/nim/derivation.nix new file mode 100644 index 00000000..54e94f11 --- /dev/null +++ b/nim/derivation.nix @@ -0,0 +1,64 @@ +{ + lib, + stdenvNoCC, + cleanSource, + llvmPackages_14, + nimPackages, + nim-argparse, + git, + cacert, + bintools, + debugSupport ? false, + speedSupport ? false +}: + +let + argparse = nimPackages.buildNimPackage rec { + pname = "argparse"; + version = "master"; + src = nim-argparse; + }; + + # Upstream `nimPackages.sdl2` is marked broken on macOS but it actually works + # fine: + sdl2 = nimPackages.sdl2.overrideAttrs (o: { + meta = o.meta // { + platforms = o.meta.platforms ++ lib.platforms.darwin; + }; + }); +in + +nimPackages.buildNimPackage rec { + name = "rosettaboy-nim"; + src = cleanSource { + inherit name; + src = ./.; + }; + + passthru = { + devTools = [ nimPackages.nim git cacert ]; + }; + + nimBinOnly = true; + + nimFlags = [] + ++ lib.optional debugSupport "-d:debug" + ++ lib.optional (!debugSupport) "-d:release" + ++ lib.optional (!speedSupport) "-d:nimDebugDlOpen" + ++ lib.optionals speedSupport [ "-d:danger" "--opt:speed" "-d:lto" "--mm:arc" "--panics:on" ] + ; + + buildInputs = [ argparse sdl2 ]; + + # Wants `lld` on macOS: + nativeBuildInputs = lib.optional stdenvNoCC.isDarwin llvmPackages_14.bintools; + + postInstall = '' + mv $out/bin/rosettaboy $out/bin/rosettaboy-nim + ''; + + meta = { + description = name; + mainProgram = name; + }; +} diff --git a/php/derivation.nix b/php/derivation.nix new file mode 100644 index 00000000..87ed51e6 --- /dev/null +++ b/php/derivation.nix @@ -0,0 +1,74 @@ +{ + stdenv, + lib, + makeWrapper, + php, + php-sdl-src, + SDL2, + cleanSource, + opcacheSupport ? false +}@args: + +let + sdl = php.buildPecl { + pname = "sdl"; + version = "master"; + src = php-sdl-src; + buildInputs = [ SDL2 ]; + }; + + php = args.php.buildEnv { + extensions = ({ enabled, all }: enabled ++ [ sdl ] ++ lib.optional opcacheSupport all.opcache); + extraConfig = lib.optionalString opcacheSupport '' + opcache.enable_cli=1 + opcache.jit_buffer_size=100M + ''; + }; +in + +stdenv.mkDerivation rec { + name = "rosettaboy-php"; + + src = cleanSource { + inherit name; + src = ./.; + extraRules = '' + .phpstan.neon + .php-cs-fixer.dist.php + ''; + }; + + passthru = { + inherit php; + devTools = [ + sdl + php + php.packages.composer + php.packages.php-cs-fixer + ]; + }; + + buildInputs = [ php ]; + nativeBuildInputs = [ makeWrapper ]; + + installPhase = '' + runHook preInstall + + mkdir -p $out/bin $out/libexec/$name + cp src/* $out/libexec/$name + + # Note: we have to invoke the interpreter explicitly instead of letting the + # shebang on `main.php` take care of it for us because the interpreter is + # actually a shell script (because of the PHP extensions we're adding) which + # is problematic on macOS: https://stackoverflow.com/a/67101108 + makeWrapper ${lib.getBin php}/bin/php $out/bin/$name \ + --add-flags $out/libexec/$name/main.php + + runHook postInstall + ''; + + meta = { + description = name; + mainProgram = name; + }; +} diff --git a/php/src/main.php b/php/src/main.php old mode 100644 new mode 100755 index a4eb940a..b5cbe546 --- a/php/src/main.php +++ b/php/src/main.php @@ -1,3 +1,4 @@ +#!/usr/bin/env php {} } : -let - langShell = lang: (import (./. + ("/" + lang + "/shell.nix")) { inherit pkgs; }); - # We need this mess of no-op concatenations because Debian 11 / - # Bullseye Stable is stuck on Nix 2.3.7 from 2020, which apparently - # doesn't always handle literals and antiquotation well: - # https://gist.github.com/CMCDragonkai/de84aece83f8521d087416fa21e34df4/fde9346664741a18d2748a578d9a1b648ee42dbd - combineShells = shells: pkgs.mkShell { inputsFrom = shells; }; -in -combineShells ( - map (langShell) [ - "cpp" - "go" - "nim" - "php" - "pxd" - "py" - "rs" - "zig" - - "utils" - ] -) - -# Some of the subshells explicitly specify very basic dependencies like `hostname`. -# This is a benefit of testing with `nix-shell --pure`. -# A lot of programs are *usually* installed, either due to widespread adoption -# or because they're bundled with something else, but technically aren't part -# of any OS base. -# That means they end up as secret, implicit dependencies, which can break -# without you even knowing they were there. Specifying them explicitly -# guarantees that they will be available. -# (Usually you probably want to just use `nix-shell`, without `--pure`, to -# keep access to global tools though.) - -# Most of the language subshells aren't actually perfectly reproducible per se -# as they import the latest version of on the host system instead of -# pinning to a specific point in the repository history. For mature and stable -# languages with versioned packages, I think this is worth it to use the -# existing local Nix store and remote repository binary cache. +(import + ( + let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; } +).shellNix \ No newline at end of file diff --git a/utils/.envrc b/utils/.envrc index c75a4047..a8599945 100644 --- a/utils/.envrc +++ b/utils/.envrc @@ -1,2 +1 @@ -# use flake ..#$(basename "$(pwd)") - +use flake ..#$(basename "$(pwd)") diff --git a/utils/clean-source.nix b/utils/clean-source.nix new file mode 100644 index 00000000..759e023e --- /dev/null +++ b/utils/clean-source.nix @@ -0,0 +1,35 @@ +{ + cleanSourceWith, + gitignoreFilterWith +}: + +# see https://github.com/hercules-ci/gitignore.nix/blob/master/docs/gitignoreFilter.md + +let + customerFilter = src: extraRules: + let + # IMPORTANT: use a let binding like this to memoize info about the git directories. + srcIgnored = gitignoreFilterWith { + basePath = src; + extraRules = '' + .envrc + .gitignore + *.nix + build*.sh + format*.sh + '' + extraRules; + }; + in path: type: srcIgnored path type; +in + +{ + name, + src, + extraRules ? "" +}: + +cleanSourceWith { + filter = customerFilter src extraRules; + src = src; + name = name + "-source"; +} diff --git a/utils/derivation.nix b/utils/derivation.nix new file mode 100644 index 00000000..2610840d --- /dev/null +++ b/utils/derivation.nix @@ -0,0 +1,55 @@ +{ + lib, + stdenvNoCC, + runCommand, + makeWrapper, + python3, + wget, + cacert, + parallel, + elfutils, + gb-autotest-roms, + cl-gameboy +}: + +stdenvNoCC.mkDerivation rec { + name = "rosettaboy-utils"; + + src = ./.; + + passthru = { + # we have to make $out or building will fail... + mkBlargg = pkg: runCommand "rosettaboy-checks-blargg-${pkg.name}" {} '' + echo ${pkg.name} + find ${gb-autotest-roms} -name '*.gb' \ + | ${parallel}/bin/parallel --will-cite --line-buffer --keep-order \ + "${lib.getExe pkg} --turbo --silent --headless --frames 200" \ + | tee -a $out + ''; + devTools = [ wget cacert parallel ] + ++ lib.optional (!stdenvNoCC.isDarwin) elfutils; + }; + + dontConfigure = true; + dontBuild = true; + + buildInputs = [ python3 ]; + nativeBuildInputs = [ makeWrapper ]; + + installPhase = '' + mkdir -p $out/bin $out/libexec/rosettaboy-utils/ + cp bench.py blargg.py cpudiff.py $out/libexec/rosettaboy-utils/ + + makeWrapper $out/libexec/rosettaboy-utils/bench.py $out/bin/rosettaboy-bench \ + --set-default "GB_DEFAULT_BENCH_ROM" "${cl-gameboy}/roms/opus5.gb" + + makeWrapper $out/libexec/rosettaboy-utils/cpudiff.py $out/bin/rosettaboy-cpudiff + + makeWrapper $out/libexec/rosettaboy-utils/blargg.py $out/bin/rosettaboy-blargg \ + --set-default "GB_DEFAULT_AUTOTEST_ROM_DIR" "${gb-autotest-roms}" + ''; + + meta = { + description = name; + }; +} diff --git a/utils/get-builds-for-lang.nix b/utils/get-builds-for-lang.nix new file mode 100644 index 00000000..a727b625 --- /dev/null +++ b/utils/get-builds-for-lang.nix @@ -0,0 +1,14 @@ +{ + lang, + attr ? "checks", + system ? builtins.currentSystem, + prefix ? ".#${attr}.${system}.", + lib ? import +}: + +attrs: lib.pipe attrs.${system} [ + builtins.attrNames + (builtins.filter (x: x == lang || lib.hasPrefix "${lang}-" x)) + (builtins.map (x: "${prefix}${x}")) + (builtins.concatStringsSep "\n") +] diff --git a/utils/shell.sh b/utils/shell.sh index baaaead4..d7411e90 100755 --- a/utils/shell.sh +++ b/utils/shell.sh @@ -4,7 +4,13 @@ set -eu cd $(dirname $0) cd .. -if command -v nix-shell; then +if nix develop --help 2>/dev/null 1>/dev/null; then + if [ -n "$*" ]; then + nix develop . --command bash -c "$*" + else + nix develop . + fi +elif command -v nix-shell; then if [ -n "$*" ]; then nix-shell --run "$*" else diff --git a/zig/derivation.nix b/zig/derivation.nix new file mode 100644 index 00000000..36d5de61 --- /dev/null +++ b/zig/derivation.nix @@ -0,0 +1,104 @@ +{ + stdenv, + lib, + darwin, + libiconv, + zig, + pkg-config, + SDL2, + zig-sdl, + zig-clap, + autoPatchelfHook, + cleanSource, + safeSupport ? false, + fastSupport ? false +}: + +let + # `apple_sdk` defaults to `10_12` instead of `11_0` on `x86_64-darwin` but we + # need `CoreHaptics` to successfully link against `SDL2` and `CoreHaptics` is + # not available in `10_12`. + # + # So, we use `11_0`, even on x86_64. + inherit (darwin.apple_sdk_11_0) frameworks; +in + +stdenv.mkDerivation rec { + name = "rosettaboy-zig"; + src = cleanSource { + inherit name; + src = ./.; + extraRules = '' + lib + ''; + }; + + passthru = { + devTools = [ zig ]; + }; + + buildInputs = [ SDL2 ] + ++ lib.optionals stdenv.isDarwin (with frameworks; [ + IOKit GameController CoreAudio AudioToolbox QuartzCore Carbon Metal + Cocoa ForceFeedback CoreHaptics + ]) + ++ lib.optional stdenv.isDarwin libiconv + ; + + nativeBuildInputs = [ zig pkg-config ] + ++ lib.optional (!stdenv.isDarwin) autoPatchelfHook + ; + + dontConfigure = true; + dontBuild = true; + + # Unforunately `zig`'s parsing of `NIX_LDFLAGS` bails when it encounters any + # flags it does not expect. + # https://github.com/ziglang/zig/blob/fe6dcdba1407f00584725318404814571cdbd828/lib/std/zig/system/NativePaths.zig#L79 + # + # When `zig` sees the `-liconv` flag that's in `NIX_LDFLAGS` on macOS, it + # bails, causing it to miss the `-L` path for SDL. + # + # Really, this should be fixed in upstream (zig) but for now we just strip out + # the `-l` flags: + preInstall = lib.optionalString stdenv.isDarwin '' + readonly ORIGINAL_NIX_LDFLAGS=($NIX_LDFLAGS) + + NIX_LDFLAGS="" + for c in "''${ORIGINAL_NIX_LDFLAGS[@]}"; do + # brittle, bad, etc; this presumes `-l...` style args (no space) + if [[ $c =~ ^-l.* ]]; then + echo "dropping link flag: $c" + continue + else + echo "keeping link flag: $c" + NIX_LDFLAGS="$NIX_LDFLAGS $c" + fi + done + + export NIX_LDFLAGS + ''; + + ZIG_FLAGS = [] + ++ lib.optional fastSupport "-Doptimize=ReleaseFast" + ++ lib.optional safeSupport "-Doptimize=ReleaseSafe" + ; + + installPhase = '' + runHook preInstall + + export HOME=$TMPDIR + mkdir -p lib + cp -aR ${zig-sdl}/ lib/sdl + cp -aR ${zig-clap}/ lib/clap + zig build $ZIG_FLAGS --prefix $out install + mv $out/bin/rosettaboy $out/bin/rosettaboy-zig + + runHook postInstall + ''; + + meta = { + description = name; + mainProgram = name; + }; +}