diff --git a/pkgs/applications/audio/famistudio/build-native-wrapper.nix b/pkgs/applications/audio/famistudio/build-native-wrapper.nix new file mode 100644 index 000000000000..ddee5bf5712d --- /dev/null +++ b/pkgs/applications/audio/famistudio/build-native-wrapper.nix @@ -0,0 +1,69 @@ +{ depname +, version +, src +, sourceRoot +, stdenv +, lib +, patches ? [] +, extraPostPatch ? "" +, buildInputs ? [] +}: + +let + rebuildscriptName = if stdenv.hostPlatform.isLinux then + "build_linux" + else if stdenv.hostPlatform.isDarwin then + "build_macos" + else throw "Don't know how to rebuild FamiStudio's vendored ${depname} for ${stdenv.hostPlatform.system}"; +in +stdenv.mkDerivation { + pname = "famistudio-nativedep-${depname}"; + inherit version src sourceRoot patches buildInputs; + + postPatch = let + libnameBase = lib.optionalString stdenv.hostPlatform.isLinux "lib" + depname; + in '' + # Use one name for build script, eases with patching + mv ${rebuildscriptName}.sh build.sh + + # Scripts use hardcoded compilers and try to copy built libraries into FamiStudio's build tree + # Not all scripts use the same compiler, so don't fail on replacing that + substituteInPlace build.sh \ + --replace-fail '../../FamiStudio/' "$out/lib/" \ + --replace-quiet 'g++' "$CXX" + + # Replacing gcc via sed, would break -static-libgcc otherwise + sed -i -e "s/^gcc/$CC/g" build.sh + '' + lib.optionalString stdenv.hostPlatform.isDarwin '' + # Darwin rebuild scripts try to make a universal2 dylib + # - build dylib for non-hostPlatform + # - copy built library into special directory for later packaging script + # - join two dylibs together into a universal2 dylib + # Remove everything we don't need + sed -ri \ + -e '/-target ${if stdenv.hostPlatform.isx86_64 then "arm64" else "x86_64"}/d' \ + -e '/..\/..\/Setup/d' \ + build.sh + + # Replace joining multi-arch dylibs with copying dylib for target arch + substituteInPlace build.sh \ + --replace-fail 'lipo -create -output ${libnameBase}.dylib' 'cp ${libnameBase}_${if stdenv.hostPlatform.isx86_64 then "x86_64" else "arm64"}.dylib ${libnameBase}.dylib #' + '' + extraPostPatch; + + dontConfigure = true; + dontInstall = true; # rebuild script automatically installs + + buildPhase = '' + runHook preBuild + + mkdir -p $out/lib + + # Delete all prebuilt libraries, make sure everything is rebuilt + find . -name '*.so' -or -name '*.dylib' -or -name '*.a' -delete + + # When calling normally, an error won't cause derivation to fail + source ./build.sh + + runHook postBuild + ''; +} diff --git a/pkgs/applications/audio/famistudio/default.nix b/pkgs/applications/audio/famistudio/default.nix index 9951f0ccfb07..7d738065ce3a 100644 --- a/pkgs/applications/audio/famistudio/default.nix +++ b/pkgs/applications/audio/famistudio/default.nix @@ -1,59 +1,108 @@ -{ lib -, stdenv -, fetchzip -, autoPatchelfHook -, dotnet-runtime +{ stdenv +, lib +, buildDotnetModule +, callPackage +, fetchFromGitHub , ffmpeg -, libglvnd +, glfw +, libogg +, libvorbis , makeWrapper , openal +, portaudio +, rtmidi }: -stdenv.mkDerivation rec { +let + csprojName = if stdenv.hostPlatform.isLinux then + "FamiStudio.Linux" + else if stdenv.hostPlatform.isDarwin then + "FamiStudio.Mac" + else throw "Don't know how to build FamiStudio for ${stdenv.hostPlatform.system}"; +in +buildDotnetModule rec { pname = "famistudio"; version = "4.1.3"; - src = fetchzip { - url = "https://github.com/BleuBleu/FamiStudio/releases/download/${version}/FamiStudio${lib.strings.concatStrings (lib.splitVersion version)}-LinuxAMD64.zip"; - stripRoot = false; - hash = "sha256-eAdv0oObczbs8QLGYbxCrdFk/gN5DOCJ1dp/tg8JWIc="; + src = fetchFromGitHub { + owner = "BleuBleu"; + repo = "FamiStudio"; + rev = "refs/tags/${version}"; + hash = "sha256-bryxhminkrTVe5qhGeMStZp3NTHBREXrsUlyQkfPkao="; }; - strictDeps = true; + postPatch = let + libname = library: "${library}${stdenv.hostPlatform.extensions.sharedLibrary}"; + buildNativeWrapper = args: callPackage ./build-native-wrapper.nix (args // { + inherit version src; + sourceRoot = "${src.name}/ThirdParty/${args.depname}"; + }); + nativeWrapperToReplaceFormat = args: let + libPrefix = lib.optionalString stdenv.hostPlatform.isLinux "lib"; + in { + package = buildNativeWrapper args; + expectedName = "${libPrefix}${args.depname}"; + ourName = "${libPrefix}${args.depname}"; + }; + librariesToReplace = [ + # Unmodified native libraries that we can fully substitute + { package = glfw; expectedName = "libglfw"; ourName = "libglfw"; } + { package = rtmidi; expectedName = "librtmidi"; ourName = "librtmidi"; } + ] ++ lib.optionals stdenv.hostPlatform.isLinux [ + { package = openal; expectedName = "libopenal32"; ourName = "libopenal"; } + ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ + { package = portaudio; expectedName = "libportaudio.2"; ourName = "libportaudio.2"; } + ] ++ [ + # Native libraries, with extra code for the C# wrapping + (nativeWrapperToReplaceFormat { depname = "GifDec"; }) + (nativeWrapperToReplaceFormat { depname = "NesSndEmu"; }) + (nativeWrapperToReplaceFormat { depname = "NotSoFatso"; extraPostPatch = '' + # C++17 does not allow register storage class specifier + substituteInPlace build.sh \ + --replace-fail "$CXX" "$CXX -std=c++14" + ''; }) + (nativeWrapperToReplaceFormat { depname = "ShineMp3"; }) + (nativeWrapperToReplaceFormat { depname = "Stb"; }) + (nativeWrapperToReplaceFormat { depname = "Vorbis"; buildInputs = [ libogg libvorbis ]; }) + ]; + libraryReplaceArgs = lib.strings.concatMapStringsSep " " + (library: "--replace-fail '${libname library.expectedName}' '${lib.getLib library.package}/lib/${libname library.ourName}'") + librariesToReplace; + in '' + # Don't use any prebuilt libraries + rm FamiStudio/*.{dll,dylib,so*} - nativeBuildInputs = [ - autoPatchelfHook - makeWrapper - ]; + # Replace copying of vendored prebuilt native libraries with copying of our native libraries + substituteInPlace ${projectFile} ${libraryReplaceArgs} - buildInputs = [ - dotnet-runtime - ffmpeg - libglvnd - openal - ]; + # Un-hardcode target platform if set + sed -i -e '/PlatformTarget/d' ${projectFile} - dontConfigure = true; - dontBuild = true; - - installPhase = '' - runHook preInstall - - mkdir -p $out/{bin,lib/famistudio} - mv * $out/lib/famistudio - - makeWrapper ${lib.getExe dotnet-runtime} $out/bin/famistudio \ - --add-flags $out/lib/famistudio/FamiStudio.dll \ - --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath [ libglvnd ]} \ - --prefix PATH : ${lib.makeBinPath [ ffmpeg ]} - - # Bundled openal lib freezes the application - rm $out/lib/famistudio/libopenal32.so - ln -s ${openal}/lib/libopenal.so $out/lib/famistudio/libopenal32.so - - runHook postInstall + # Don't require a special name to be preserved, our OpenAL isn't 32-bit + substituteInPlace FamiStudio/Source/AudioStreams/OpenALStream.cs \ + --replace-fail 'libopenal32' 'libopenal' ''; + projectFile = "FamiStudio/${csprojName}.csproj"; + nugetDeps = ./deps.nix; + + executables = [ "FamiStudio" ]; + + postInstall = '' + mkdir -p $out/share/famistudio + for datdir in Setup/Demo\ {Instruments,Songs}; do + cp -R "$datdir" $out/share/famistudio/ + done + ''; + + postFixup = '' + # FFMpeg looked up from PATH + wrapProgram $out/bin/FamiStudio \ + --prefix PATH : ${lib.makeBinPath [ ffmpeg ]} + ''; + + passthru.updateScript = ./update.sh; + meta = with lib; { homepage = "https://famistudio.org/"; description = "NES Music Editor"; @@ -62,10 +111,8 @@ stdenv.mkDerivation rec { or Famicom. It is targeted at both chiptune artists and NES homebrewers. ''; license = licenses.mit; - # Maybe possible to build from source but I'm not too familiar with C# packaging - sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ]; maintainers = with maintainers; [ OPNA2608 ]; - platforms = [ "x86_64-linux" ]; - mainProgram = "famistudio"; + platforms = platforms.unix; + mainProgram = "FamiStudio"; }; } diff --git a/pkgs/applications/audio/famistudio/deps.nix b/pkgs/applications/audio/famistudio/deps.nix new file mode 100644 index 000000000000..f3a17967e25c --- /dev/null +++ b/pkgs/applications/audio/famistudio/deps.nix @@ -0,0 +1,5 @@ +# This file was automatically generated by passthru.fetch-deps. +# Please dont edit it manually, your changes might get overwritten! + +{ fetchNuGet }: [ +] diff --git a/pkgs/applications/audio/famistudio/update.sh b/pkgs/applications/audio/famistudio/update.sh new file mode 100755 index 000000000000..15657955c025 --- /dev/null +++ b/pkgs/applications/audio/famistudio/update.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i bash -p curl jq common-updater-scripts +set -eo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")" + +deps_file="$(realpath "./deps.nix")" + +new_version="$(curl ${GITHUB_TOKEN:+-u ":$GITHUB_TOKEN"} \ + -s "https://api.github.com/repos/BleuBleu/FamiStudio/releases?per_page=1" | jq -r '.[0].tag_name')" +old_version="$(sed -nE 's/\s*version = "(.*)".*/\1/p' ./default.nix)" +if [[ "$new_version" == "$old_version" ]]; then + echo "Up to date" + exit 0 +fi + +cd ../../../.. + +if [[ "$1" != "--deps-only" ]]; then + update-source-version famistudio "$new_version" +fi + +$(nix-build . -A famistudio.fetch-deps --no-out-link) "$deps_file" diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 5166d7515347..e5b454bfcb0e 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -30979,7 +30979,7 @@ with pkgs; faircamp = callPackage ../applications/misc/faircamp { }; - famistudio = callPackage ../applications/audio/famistudio { }; + famistudio = darwin.apple_sdk_11_0.callPackage ../applications/audio/famistudio { }; fasttext = callPackage ../applications/science/machine-learning/fasttext { };