Unverified Commit 8cde6e82 authored by Niklas Korz's avatar Niklas Korz Committed by GitHub
Browse files

radarr: build from source (#384974)

parents a03f275b 01b81002
Loading
Loading
Loading
Loading
+1687 −0

File added.

Preview size limit exceeded, changes collapsed.

+206 −0
Original line number Diff line number Diff line
{
  lib,
  stdenvNoCC,
  fetchFromGitHub,
  buildDotnetModule,
  dotnetCorePackages,
  sqlite,
  withFFmpeg ? true, # replace bundled ffprobe binary with symlink to ffmpeg package.
  servarr-ffmpeg,
  fetchYarnDeps,
  yarn,
  fixup-yarn-lock,
  nodejs,
  nixosTests,
  # update script
  writers,
  python3Packages,
  nix,
  prefetch-yarn-deps,
  fetchpatch,
  applyPatches,
}:
let
  version = "5.26.2.10099";
  # The dotnet8 compatibility patches also change `yarn.lock`, so we must pass
  # the already patched lockfile to `fetchYarnDeps`.
  src = applyPatches {
    src = fetchFromGitHub {
      owner = "Radarr";
      repo = "Radarr";
      tag = "v${version}";
      hash = "sha256-7tU9oxE1F/dcR5bwb/qHyux3WA6lEwdozLloDgOMVbU=";
    };
    postPatch = ''
      mv src/NuGet.config NuGet.Config
    '';
    patches = lib.optionals (lib.versionOlder version "6.0") [
      # See https://github.com/Radarr/Radarr/pull/11064
      # Unfortunately, the .NET 8 upgrade will be merged into the v6 branch,
      # and it may take some time for that to become stable.
      # However, the patches cleanly apply to v5 as well.
      (fetchpatch {
        name = "dotnet8-compatibility";
        url = "https://github.com/Radarr/Radarr/commit/490891c63de589604bdc3373cfc85068c3826648.patch";
        hash = "sha256-SCP7MPUkEZLSrls8ouekSXpXdgAJTwNFPirHjaMkQ6s=";
      })
      (fetchpatch {
        name = "dotnet8-darwin-compatibility";
        url = "https://github.com/Radarr/Radarr/commit/f38a129289c49a242d8901dc2f041f9dc8bfc303.patch";
        hash = "sha256-SAMUHqlSj8FPq20wY8NWbRytVZXTPtMXMfM3CoM8kSA=";
      })
    ];
  };
  rid = dotnetCorePackages.systemToDotnetRid stdenvNoCC.hostPlatform.system;
in
buildDotnetModule {
  pname = "radarr";
  inherit version src;

  strictDeps = true;
  nativeBuildInputs = [
    nodejs
    yarn
    prefetch-yarn-deps
    fixup-yarn-lock
  ];

  yarnOfflineCache = fetchYarnDeps {
    yarnLock = "${src}/yarn.lock";
    hash = "sha256-WFILG6nLEc8WO0j0CKH7RcX1++ucVOCge/UKcpHj87A=";
  };

  ffprobe = lib.optionalDrvAttr withFFmpeg (lib.getExe' servarr-ffmpeg "ffprobe");

  postConfigure = ''
    yarn config --offline set yarn-offline-mirror "$yarnOfflineCache"
    fixup-yarn-lock yarn.lock
    yarn install --offline --frozen-lockfile --ignore-platform --ignore-scripts --no-progress --non-interactive
    patchShebangs --build node_modules
  '';
  postBuild = ''
    yarn --offline run build --env production
  '';
  postInstall =
    lib.optionalString withFFmpeg ''
      rm -- "$out/lib/radarr/ffprobe"
      ln -s -- "$ffprobe" "$out/lib/radarr/ffprobe"
    ''
    + ''
      cp -a -- _output/UI "$out/lib/radarr/UI"
    '';

  nugetDeps = ./deps.json;

  runtimeDeps = [ sqlite ];

  dotnet-sdk = dotnetCorePackages.sdk_8_0;
  dotnet-runtime = dotnetCorePackages.aspnetcore_8_0;

  doCheck = true;

  __darwinAllowLocalNetworking = true; # for tests

  __structuredAttrs = true; # for Copyright property that contains spaces

  executables = [ "Radarr" ];

  projectFile = [
    "src/NzbDrone.Console/Radarr.Console.csproj"
    "src/NzbDrone.Mono/Radarr.Mono.csproj"
  ];

  testProjectFile = [
    "src/NzbDrone.Api.Test/Radarr.Api.Test.csproj"
    "src/NzbDrone.Common.Test/Radarr.Common.Test.csproj"
    "src/NzbDrone.Core.Test/Radarr.Core.Test.csproj"
    "src/NzbDrone.Host.Test/Radarr.Host.Test.csproj"
    "src/NzbDrone.Libraries.Test/Radarr.Libraries.Test.csproj"
    "src/NzbDrone.Mono.Test/Radarr.Mono.Test.csproj"
    "src/NzbDrone.Test.Common/Radarr.Test.Common.csproj"
  ];

  dotnetFlags = [
    "--property:TargetFramework=net8.0"
    "--property:EnableAnalyzers=false"
    "--property:SentryUploadSymbols=false" # Fix Sentry upload failed warnings
    # Override defaults in src/Directory.Build.props that use current time.
    "--property:Copyright=Copyright 2014-2025 radarr.video (GNU General Public v3)"
    "--property:AssemblyVersion=${version}"
    "--property:AssemblyConfiguration=master"
    "--property:RuntimeIdentifier=${rid}"
  ];

  # Skip manual, integration, automation and platform-dependent tests.
  testFilters =
    [
      "TestCategory!=ManualTest"
      "TestCategory!=IntegrationTest"
      "TestCategory!=AutomationTest"

      # makes real HTTP requests
      "FullyQualifiedName!~NzbDrone.Core.Test.UpdateTests.UpdatePackageProviderFixture"
    ]
    ++ lib.optionals stdenvNoCC.buildPlatform.isDarwin [
      # fails on macOS
      "FullyQualifiedName!~NzbDrone.Core.Test.Http.HttpProxySettingsProviderFixture"
    ];

  disabledTests =
    [
      # setgid tests
      "NzbDrone.Mono.Test.DiskProviderTests.DiskProviderFixture.should_preserve_setgid_on_set_folder_permissions"
      "NzbDrone.Mono.Test.DiskProviderTests.DiskProviderFixture.should_clear_setgid_on_set_folder_permissions"

      # we do not set application data directory during tests (i.e. XDG data directory)
      "NzbDrone.Mono.Test.DiskProviderTests.FreeSpaceFixture.should_return_free_disk_space"
      "NzbDrone.Common.Test.ServiceFactoryFixture.event_handlers_should_be_unique"

      # attempts to read /etc/*release and fails since it does not exist
      "NzbDrone.Mono.Test.EnvironmentInfo.ReleaseFileVersionAdapterFixture.should_get_version_info"

      # fails to start test dummy because it cannot locate .NET runtime for some reason
      "NzbDrone.Common.Test.ProcessProviderFixture.should_be_able_to_start_process"
      "NzbDrone.Common.Test.ProcessProviderFixture.exists_should_find_running_process"
      "NzbDrone.Common.Test.ProcessProviderFixture.kill_all_should_kill_all_process_with_name"
    ]
    ++ lib.optionals stdenvNoCC.buildPlatform.isDarwin [
      # flaky on darwin
      "NzbDrone.Core.Test.NotificationTests.TraktServiceFixture.should_add_collection_movie_if_valid_mediainfo"
      "NzbDrone.Core.Test.NotificationTests.TraktServiceFixture.should_format_audio_channels_to_one_decimal_when_adding_collection_movie"
    ];

  passthru = {
    tests = {
      inherit (nixosTests) radarr;
    };

    updateScript = writers.writePython3 "radarr-updater" {
      libraries = with python3Packages; [ requests ];
      flakeIgnore = [ "E501" ];
      makeWrapperArgs = [
        "--prefix"
        "PATH"
        ":"
        (lib.makeBinPath [
          nix
          prefetch-yarn-deps
        ])
      ];
    } ./update.py;
  };

  meta = {
    description = "Usenet/BitTorrent movie downloader";
    homepage = "https://radarr.video";
    changelog = "https://github.com/Radarr/Radarr/releases/tag/v${version}";
    license = lib.licenses.gpl3Only;
    maintainers = with lib.maintainers; [
      edwtjo
      purcell
      nyanloutre
    ];
    mainProgram = "Radarr";
    # platforms inherited from dotnet-sdk.
  };
}
+182 −0
Original line number Diff line number Diff line
import json
import os
import pathlib
import requests
import shutil
import subprocess
import sys
import tempfile


def replace_in_file(file_path, replacements):
    file_contents = pathlib.Path(file_path).read_text()
    for old, new in replacements.items():
        if old == new:
            continue
        updated_file_contents = file_contents.replace(old, new)
        # A dumb way to check that we’ve actually replaced the string.
        if file_contents == updated_file_contents:
            print(f"no string to replace: {old}{new}", file=sys.stderr)
            sys.exit(1)
        file_contents = updated_file_contents
    with tempfile.NamedTemporaryFile(mode="w") as t:
        t.write(file_contents)
        t.flush()
        shutil.copyfile(t.name, file_path)


def nix_hash_to_sri(hash):
    return subprocess.run(
        [
            "nix",
            "--extra-experimental-features", "nix-command",
            "hash",
            "to-sri",
            "--type", "sha256",
            "--",
            hash,
        ],
        stdout=subprocess.PIPE,
        text=True,
        check=True,
    ).stdout.rstrip()


nixpkgs_path = "."
attr_path = os.getenv("UPDATE_NIX_ATTR_PATH", "radarr")

package_attrs = json.loads(subprocess.run(
    [
        "nix",
        "--extra-experimental-features", "nix-command",
        "eval",
        "--json",
        "--file", nixpkgs_path,
        "--apply", """p: {
          dir = builtins.dirOf p.meta.position;
          version = p.version;
          sourceHash = p.src.src.outputHash;
          yarnHash = p.yarnOfflineCache.outputHash;
        }""",
        "--",
        attr_path,
    ],
    stdout=subprocess.PIPE,
    text=True,
    check=True,
).stdout)

old_version = package_attrs["version"]
new_version = old_version

# Note that we use Radarr API instead of GitHub to fetch latest stable release.
# This corresponds to the Updates tab in the web UI. See also
# https://github.com/Radarr/Radarr/blob/edec432244933a2143c5d13c71de7eb210434e7b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs
# https://github.com/Radarr/Radarr/blob/edec432244933a2143c5d13c71de7eb210434e7b/src/NzbDrone.Common/Cloud/RadarrCloudRequestBuilder.cs
version_update = requests.get(
    f"https://radarr.servarr.com/v1/update/master?version={old_version}&includeMajorVersion=true",
).json()
if version_update["available"]:
    new_version = version_update["updatePackage"]["version"]

if new_version == old_version:
    sys.exit()

source_nix_hash, source_store_path = subprocess.run(
    [
        "nix-prefetch-url",
        "--name", "source",
        "--unpack",
        "--print-path",
        f"https://github.com/Radarr/Radarr/archive/v{new_version}.tar.gz",
    ],
    stdout=subprocess.PIPE,
    text=True,
    check=True,
).stdout.rstrip().split("\n")

old_source_hash = package_attrs["sourceHash"]
new_source_hash = nix_hash_to_sri(source_nix_hash)

package_dir = package_attrs["dir"]
package_file_name = "package.nix"
deps_file_name = "deps.json"

# To update deps.nix, we copy the package to a temporary directory and run
# passthru.fetch-deps script there.
with tempfile.TemporaryDirectory() as work_dir:
    package_file = os.path.join(work_dir, package_file_name)
    deps_file = os.path.join(work_dir, deps_file_name)

    shutil.copytree(package_dir, work_dir, dirs_exist_ok=True)

    replace_in_file(package_file, {
        # NB unlike hashes, versions are likely to be used in code or comments.
        # Try to be more specific to avoid false positive matches.
        f"version = \"{old_version}\"": f"version = \"{new_version}\"",
        old_source_hash: new_source_hash,
    })

    # We need access to the patched and updated src to get the patched
    # `yarn.lock`.
    patched_src = os.path.join(work_dir, "patched-src")
    subprocess.run(
        [
            "nix",
            "--extra-experimental-features", "nix-command",
            "build",
            "--impure",
            "--nix-path", "",
            "--include", f"nixpkgs={nixpkgs_path}",
            "--include", f"package={package_file}",
            "--expr", "(import <nixpkgs> { }).callPackage <package> { }",
            "--out-link", patched_src,
            "src",
        ],
        check=True,
    )
    old_yarn_hash = package_attrs["yarnHash"]
    new_yarn_hash = nix_hash_to_sri(subprocess.run(
        [
            "prefetch-yarn-deps",
            # does not support "--" separator :(
            # Also --verbose writes to stdout, yikes.
            os.path.join(patched_src, "yarn.lock"),
        ],
        stdout=subprocess.PIPE,
        text=True,
        check=True,
    ).stdout.rstrip())

    replace_in_file(package_file, {
        old_yarn_hash: new_yarn_hash,
    })

    # Generate nuget-to-json dependency lock file.
    fetch_deps = os.path.join(work_dir, "fetch-deps")
    subprocess.run(
        [
            "nix",
            "--extra-experimental-features", "nix-command",
            "build",
            "--impure",
            "--nix-path", "",
            "--include", f"nixpkgs={nixpkgs_path}",
            "--include", f"package={package_file}",
            "--expr", "(import <nixpkgs> { }).callPackage <package> { }",
            "--out-link", fetch_deps,
            "passthru.fetch-deps",
        ],
        check=True,
    )
    subprocess.run(
        [
            fetch_deps,
            deps_file,
        ],
        stdout=subprocess.DEVNULL,
        check=True,
    )

    shutil.copy(deps_file, os.path.join(package_dir, deps_file_name))
    shutil.copy(package_file, os.path.join(package_dir, package_file_name))
+113 −0
Original line number Diff line number Diff line
{
  ffmpeg-headless,
  fetchFromGitHub,
  fetchpatch2,
  lib,
}:

let
  version = "5.1.4";
in

(ffmpeg-headless.override {
  inherit version; # Important! This sets the ABI.

  # Fetch commit hash from this repository: https://github.com/Servarr/ffmpeg-build
  # Compare build logs to upstream logs here: https://dev.azure.com/Servarr/Servarr/_build?definitionId=15
  source = fetchFromGitHub {
    owner = "Servarr";
    repo = "FFmpeg";
    rev = "e9230b4c9027435dd402a68833f144643a3df43a";
    hash = "sha256-oMIblMOnnYpKvYeleCZpFZURGVc3fDAlYpOJu+u7HkU=";
  };

  buildFfmpeg = false;
  buildFfplay = false;
  buildAvdevice = false;
  buildAvfilter = false;
  buildPostproc = false;
  buildSwresample = false;
  buildSwscale = false;

  withAlsa = false;
  withAmf = false;
  withAom = false;
  withAss = false;
  withBluray = false;
  withBzlib = false;
  withCudaLLVM = false;
  withCuvid = false;
  withDrm = false;
  withNvcodec = false;
  withFontconfig = false;
  withFreetype = false;
  withFribidi = false;
  withGnutls = false;
  withIconv = false;
  withLzma = false;
  withMp3lame = false;
  withOpencl = false;
  withOpenjpeg = false;
  withOpenmpt = false;
  withOpus = false;
  withRist = false;
  withSoxr = false;
  withSpeex = false;
  withSrt = false;
  withSsh = false;
  withSvtav1 = false;
  withTheora = false;
  withV4l2 = false;
  withVaapi = false;
  withVidStab = false;
  withVorbis = false;
  withVpx = false;
  withVulkan = false;
  withWebp = false;
  withX264 = false;
  withX265 = false;
  withXml2 = false;
  withXvid = false;
  withZimg = false;
  withZlib = false;
  withZvbi = false;
}).overrideAttrs
  (old: {
    pname = "servarr-ffmpeg";

    patches = old.patches ++ [
      (fetchpatch2 {
        name = "fix_build_failure_due_to_libjxl_version_to_new";
        url = "https://git.ffmpeg.org/gitweb/ffmpeg.git/patch/75b1a555a70c178a9166629e43ec2f6250219eb2";
        hash = "sha256-+2kzfPJf5piim+DqEgDuVEEX5HLwRsxq0dWONJ4ACrU=";
      })
    ];

    configureFlags = old.configureFlags ++ [
      "--extra-version=Servarr"

      # https://github.com/Servarr/ffmpeg-build/blob/bc29af6f0bf84bf9253d4d462611b1dc31ee688e/common.sh#L15-L45

      # Disable unused functionnalities
      "--disable-encoders"
      "--disable-muxers"
      "--disable-protocols"
      "--disable-bsfs"

      # FFMpeg options - enable what we need
      "--enable-protocol=file"
      "--enable-bsf=av1_frame_split"
      "--enable-bsf=av1_frame_merge"
      "--enable-bsf=av1_metadata"
    ];

    doCheck = false;

    meta = {
      inherit (old.meta) license;
      mainProgram = "ffprobe";
      description = "${old.meta.description} (Servarr fork)";
      homepage = "https://github.com/Servarr/FFmpeg";
      maintainers = with lib.maintainers; [ nyanloutre ];
    };
  })
+0 −9
Original line number Diff line number Diff line
Move NuGet configuration file to the source root where Nixpkgs .NET
build infrastructure expects to find it.

https://github.com/NixOS/nixpkgs/pull/291640#discussion_r1601841807

diff --git a/src/NuGet.Config b/NuGet.Config
similarity index 100%
rename from src/NuGet.Config
rename to NuGet.Config
Loading