diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b9b323fbe2..af75fae294 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -215,3 +215,31 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 + + build-nix: + name: Nix + runs-on: ubuntu-latest + if: github.repository == 'doldecomp/melee' + env: + REGISTRY: ghcr.io + IMAGE: doldecomp/build-melee:main + steps: + - uses: actions/checkout@v4 + - name: Log into container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Grab Melee DOL + run: | + image="$REGISTRY/$IMAGE" + docker pull "$image" + container_id=$(docker create "$image") + docker cp "$container_id":/orig . + - uses: cachix/install-nix-action@v25 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: DeterminateSystems/magic-nix-cache-action@v2 + - run: nix-build + - run: nix-shell --run "echo OK" diff --git a/.nix/.editorconfig b/.nix/.editorconfig new file mode 100644 index 0000000000..f2693e935e --- /dev/null +++ b/.nix/.editorconfig @@ -0,0 +1,2 @@ +[*.nix] +indent_size = 2 diff --git a/.nix/decomp-toolkit.nix b/.nix/decomp-toolkit.nix new file mode 100644 index 0000000000..a197df9046 --- /dev/null +++ b/.nix/decomp-toolkit.nix @@ -0,0 +1,34 @@ +{ lib +, fetchFromGitHub +, git +, rustPlatform +, stdenv +}: + +rustPlatform.buildRustPackage rec { + pname = "decomp-toolkit"; + version = "0.7.3"; + + src = fetchFromGitHub { + owner = "encounter"; + repo = "decomp-toolkit"; + rev = "v${version}"; + hash = "sha256-cpHaoxRr/Lyx6tpyUx7Sm+9h0BicrO+jVJA09BBgKJ4="; + }; + + nativeBuildInputs = [ + git + ]; + + cargoLock.lockFile = "${src}/Cargo.lock"; + cargoLock.outputHashes."ar-0.8.0" = "sha256-OLyo+cRRWMsI1i8NsgsBKRJH1XsKW1CculQnJ/wcya0="; + cargoLock.outputHashes."dol-0.1.0" = "sha256-YfMXWNtmZJhK39R3w98DEGm4y9x59osFGliG36BcuLU="; + + meta = with lib; { + description = "A GameCube & Wii decompilation toolkit"; + homepage = "https://github.com/encounter/decomp-toolkit"; + license = with licenses; [ asl20 mit ]; + maintainers = with maintainers; [ r-burns ]; + mainProgram = "dtk"; + }; +} diff --git a/.nix/default.nix b/.nix/default.nix new file mode 100644 index 0000000000..ee9b4c901b --- /dev/null +++ b/.nix/default.nix @@ -0,0 +1,17 @@ +{ + sources ? import ./sources.nix, +}: +let + pkgs = import sources.nixpkgs { + overlays = [ + (self: super: { + decomp-toolkit = super.callPackage ./decomp-toolkit.nix {}; + devkitppc = super.callPackage ./devkitppc.nix {}; + mwcc = super.callPackage ./mwcc.nix {}; + wibo = super.pkgsi686Linux.callPackage ./wibo.nix {}; + }) + ]; + }; +in + +pkgs.callPackage ./melee.nix {} diff --git a/.nix/devkitppc.nix b/.nix/devkitppc.nix new file mode 100644 index 0000000000..23a5326fdb --- /dev/null +++ b/.nix/devkitppc.nix @@ -0,0 +1,41 @@ +{ stdenvNoCC, lib +, buildEnv +, fetchFromGitHub +, fetchpatch +, makeWrapper +, overrideCC +, pkgsCross +}: + +let + version = "43"; + + tag = "devkitPPC_r${version}"; + + ppcCrossBinutils = pkgsCross.ppc-embedded.buildPackages.binutils-unwrapped; + + bintools' = ppcCrossBinutils.overrideAttrs (oa: { + patches = oa.patches ++ [ + (fetchpatch { + url = "https://raw.githubusercontent.com/devkitPro/buildscripts/${tag}/dkppc/patches/binutils-${oa.version}.patch"; + hash = "sha256-IOqa20LQYBxfR1KKxkp0hVV21CKd9IZrvNeEyuW09us="; + }) + ]; + }); +in +stdenvNoCC.mkDerivation { + pname = "devkitppc"; + inherit version; + nativeBuildInputs = [ + makeWrapper + ]; + buildCommand = '' + for bindir in '${lib.getBin bintools'}/bin'; do + cd "$bindir" + for f in powerpc-none-eabi-*; do + short="$(echo "$f" | sed s/powerpc-none-eabi-/powerpc-eabi-/)" + makeWrapper "$bindir/$f" "$out/bin/$short" + done + done + ''; +} diff --git a/.nix/melee.nix b/.nix/melee.nix new file mode 100644 index 0000000000..d1fcd79c1d --- /dev/null +++ b/.nix/melee.nix @@ -0,0 +1,62 @@ +{ lib +, stdenv +, decomp-toolkit +, devkitppc +, fetchurl +, mwcc +, ninja +, python3 +, wibo +}: +let + sjiswrap = fetchurl { + url = "https://github.com/encounter/sjiswrap/releases/download/v1.1.1/sjiswrap-windows-x86.exe"; + hash = "sha256-J6PF1PJj5OuW5WGc/Noi9F0zzNEhEEx/9qN+FbP0J80="; + }; +in +stdenv.mkDerivation { + name = "doldecomp-melee"; + + src = lib.cleanSourceWith { + filter = name: type: let + basename = baseNameOf (toString name); + in !(false + || basename == "build" + || basename == "expected" + || lib.hasSuffix ".nix" basename + || lib.hasSuffix ".dump" basename + || lib.hasSuffix ".o" basename + ); + src = lib.cleanSource ../.; + }; + + shellHook = '' + runHook postPatch + ''; + + nativeBuildInputs = [ + decomp-toolkit + devkitppc + ninja + python3 + wibo + ]; + + configurePhase = '' + runHook preConfigure + python3 ./configure.py --wrapper ${wibo}/bin/wibo \ + --build-dtk ${decomp-toolkit}/bin/dtk \ + --sjiswrap ${sjiswrap} \ + --compilers ${mwcc} + runHook postConfigure + ''; + + enableParallelBuilding = true; + + installPhase = '' + runHook preInstall + mkdir -p $out + cp build/GALE01/main.dol $out/ + runHook postInstall + ''; +} diff --git a/.nix/mwcc.nix b/.nix/mwcc.nix new file mode 100644 index 0000000000..92e4e62d9b --- /dev/null +++ b/.nix/mwcc.nix @@ -0,0 +1,23 @@ +{ lib +, stdenv +, fetchzip +}: +stdenv.mkDerivation { + name = "GC_WII_COMPILERS"; + src = fetchzip { + url = "https://files.decomp.dev/compilers_20230715.zip"; + stripRoot = false; + sha256 = "sha256-IX3byvEUVJB6Rmc+NqO9ZNt1jl95nQpEIqxbHI+uUio="; + }; + # Patch 1.1 linker to not read garbage from stack. + # Fixes random crashes under wibo. + postPatch = '' + printf '\x51' | dd of=GC/1.1/mwldeppc.exe bs=1 seek=130933 count=1 conv=notrunc + ''; + + installPhase = '' + runHook preInstall + cp -r . $out/ + runHook postInstall + ''; +} diff --git a/.nix/shell.nix b/.nix/shell.nix new file mode 100644 index 0000000000..847f81d225 --- /dev/null +++ b/.nix/shell.nix @@ -0,0 +1,26 @@ +{ + sources ? import ./sources.nix, +}: +let + pkgs = import sources.nixpkgs { + overlays = [ + (self: super: { + decomp-toolkit = super.callPackage ./decomp-toolkit.nix {}; + devkitppc = super.callPackage ./devkitppc.nix {}; + mwcc = super.callPackage ./mwcc.nix {}; + wibo = super.pkgsi686Linux.callPackage ./wibo.nix {}; + }) + ]; + }; + + melee = import ./default.nix { inherit sources; }; +in + +melee.overrideAttrs (oa: { + nativeBuildInputs = oa.nativeBuildInputs ++ [ + (pkgs.clang-tools.override { + llvmPackages = pkgs.llvmPackages_15; + }) + pkgs.clang.cc.python + ]; +}) diff --git a/.nix/sources.json b/.nix/sources.json new file mode 100644 index 0000000000..5b11d44705 --- /dev/null +++ b/.nix/sources.json @@ -0,0 +1,14 @@ +{ + "nixpkgs": { + "branch": "nixos-unstable", + "description": "Nix Packages collection", + "homepage": "", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "73de017ef2d18a04ac4bfd0c02650007ccb31c2a", + "sha256": "1v9sy2i2dy3qksx4mf81gwzfl0jzpqccfkzq7fjxgq832f9d255i", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/73de017ef2d18a04ac4bfd0c02650007ccb31c2a.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + } +} diff --git a/.nix/sources.nix b/.nix/sources.nix new file mode 100644 index 0000000000..fe3dadf7eb --- /dev/null +++ b/.nix/sources.nix @@ -0,0 +1,198 @@ +# This file has been generated by Niv. + +let + + # + # The fetchers. fetch_ fetches specs of type . + # + + fetch_file = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; + + fetch_tarball = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchTarball { name = name'; inherit (spec) url sha256; } + else + pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; + + fetch_git = name: spec: + let + ref = + spec.ref or ( + if spec ? branch then "refs/heads/${spec.branch}" else + if spec ? tag then "refs/tags/${spec.tag}" else + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!" + ); + submodules = spec.submodules or false; + submoduleArg = + let + nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; + emptyArgWithWarning = + if submodules + then + builtins.trace + ( + "The niv input \"${name}\" uses submodules " + + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " + + "does not support them" + ) + { } + else { }; + in + if nixSupportsSubmodules + then { inherit submodules; } + else emptyArgWithWarning; + in + builtins.fetchGit + ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); + + fetch_local = spec: spec.path; + + fetch_builtin-tarball = name: throw + ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=tarball -a builtin=true''; + + fetch_builtin-url = name: throw + ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=file -a builtin=true''; + + # + # Various helpers + # + + # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 + sanitizeName = name: + ( + concatMapStrings (s: if builtins.isList s then "-" else s) + ( + builtins.split "[^[:alnum:]+._?=-]+" + ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) + ) + ); + + # The set of packages used when specs are fetched using non-builtins. + mkPkgs = sources: system: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import { } + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; + + # The actual fetching function. + fetch = pkgs: name: spec: + + if ! builtins.hasAttr "type" spec then + abort "ERROR: niv spec ${name} does not have a 'type' attribute" + else if spec.type == "file" then fetch_file pkgs name spec + else if spec.type == "tarball" then fetch_tarball pkgs name spec + else if spec.type == "git" then fetch_git name spec + else if spec.type == "local" then fetch_local spec + else if spec.type == "builtin-tarball" then fetch_builtin-tarball name + else if spec.type == "builtin-url" then fetch_builtin-url name + else + abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + + # If the environment variable NIV_OVERRIDE_${name} is set, then use + # the path directly as opposed to the fetched source. + replace = name: drv: + let + saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; + ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; + in + if ersatz == "" then drv else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; + + # Ports of functions for older nix versions + + # a Nix version of mapAttrs if the built-in doesn't exist + mapAttrs = builtins.mapAttrs or ( + f: set: with builtins; + listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) + ); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 + range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 + stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 + stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); + concatMapStrings = f: list: concatStrings (map f list); + concatStrings = builtins.concatStringsSep ""; + + # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 + optionalAttrs = cond: as: if cond then as else { }; + + # fetchTarball version that is compatible between all the versions of Nix + builtins_fetchTarball = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" then + fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else + fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" then + fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else + fetchurl attrs; + + # Create the final "sources" from the config + mkSources = config: + mapAttrs + ( + name: spec: + if builtins.hasAttr "outPath" spec + then + abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = replace name (fetch config.pkgs name spec); } + ) + config.sources; + + # The "config" used by the fetchers + mkConfig = + { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null + , sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile) + , system ? builtins.currentSystem + , pkgs ? mkPkgs sources system + }: rec { + # The sources, i.e. the attribute set of spec name to spec + inherit sources; + + # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers + inherit pkgs; + }; + +in +mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/.nix/wibo.nix b/.nix/wibo.nix new file mode 100644 index 0000000000..048af67e6a --- /dev/null +++ b/.nix/wibo.nix @@ -0,0 +1,38 @@ +{ lib +, stdenv +, fetchFromGitHub +, cmake +}: + +stdenv.mkDerivation rec { + pname = "wibo"; + version = "0.6.12"; + + src = fetchFromGitHub { + owner = "decompals"; + repo = "wibo"; + rev = version; + hash = "sha256-Kv3jbyWouz/bmteaoJyKkFC5YWuTEEaY6OvmJbZ0Xfg="; + }; + + nativeBuildInputs = [ + cmake + ]; + + meta = with lib; { + description = "Quick-and-dirty wrapper to run 32-bit windows EXEs on linux"; + longDescription = '' + A minimal, low-fuss wrapper that can run really simple command-line + 32-bit Windows binaries on Linux - with less faff and less dependencies + than WINE. + ''; + homepage = "https://github.com/decompals/WiBo"; + license = licenses.mit; + maintainers = with maintainers; [ r-burns ]; + platforms = [ "i686-linux" ]; + mainProgram = "wibo"; + }; + + # HACK: sjiswrap triggers buffer overflow detection, is this spurious? + hardeningDisable = [ "fortify" ]; +} diff --git a/configure.py b/configure.py index fe74115590..4a65306b2b 100755 --- a/configure.py +++ b/configure.py @@ -84,7 +84,7 @@ "--build-dtk", dest="build_dtk", type=Path, - help="path to decomp-toolkit source (optional)", + help="path to decomp-toolkit source or binary (optional)", ) parser.add_argument( "--sjiswrap", diff --git a/default.nix b/default.nix new file mode 100644 index 0000000000..e574df5b72 --- /dev/null +++ b/default.nix @@ -0,0 +1 @@ +import ./.nix diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000000..7592a59a0b --- /dev/null +++ b/shell.nix @@ -0,0 +1 @@ +import ./.nix/shell.nix diff --git a/tools/project.py b/tools/project.py index 35cb906fd0..d597b248e1 100644 --- a/tools/project.py +++ b/tools/project.py @@ -214,7 +214,9 @@ def generate_build_ninja( description="TOOL $out", ) - if config.build_dtk_path: + if config.build_dtk_path and config.build_dtk_path.is_file(): + dtk = config.build_dtk_path + elif config.build_dtk_path: dtk = build_tools_path / "release" / f"dtk{EXE}" n.rule( name="cargo",