diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 41f496b8..a7eeaa87 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -40,7 +40,7 @@ body: - OS are you running on (e.g. Windows 10, Ubuntu 22.04) - Your CPU architecture (e.g. arm, arm64, x86, x64) - Recyclarr version (e.g. `v3.0.0`) - - Build of Recyclarr in use (e.g. Docker, `recyclarr-linux-arm64`, `recyclarr-osx-x64`) + - Build of Recyclarr in use (e.g. Docker, `linux-arm64`, `osx-x64`) - Sonarr version (e.g. `3.0.9.1549`) - Radarr Version: (e.g. `4.3.0.6671`) value: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aae8d720..8587c8db 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,83 +20,66 @@ on: - "docker/**" - "ci/**" -env: - dotnetVersion: "7.0.x" - jobs: - test: - name: Test + build-win: + name: Build Windows + secrets: inherit + uses: ./.github/workflows/reusable-build.yml strategy: - fail-fast: true matrix: - os: - - windows-latest - - ubuntu-latest - - macos-latest - runs-on: ${{ matrix.os }} - steps: - - name: Checkout Source Code - uses: actions/checkout@v3 - with: - fetch-depth: 0 # avoid shallow clone for GitVersion - - - name: Setup .NET Core SDK ${{ env.dotnetVersion }} - uses: actions/setup-dotnet@v3 - with: - dotnet-version: ${{ env.dotnetVersion }} + runtime: [win-x64, win-arm64] + with: + platform: windows-latest + runtime: ${{ matrix.runtime }} - - name: Test - run: dotnet test src --configuration Release --logger GitHubActions + build-linux: + name: Build Linux + secrets: inherit + uses: ./.github/workflows/reusable-build.yml + strategy: + matrix: + runtime: [linux-x64, linux-arm64, linux-arm] + with: + platform: ubuntu-latest + runtime: ${{ matrix.runtime }} - build: - name: Build Non-MUSL - needs: test + build-osx: + name: Build Mac OS + secrets: inherit + uses: ./.github/workflows/reusable-build.yml + strategy: + matrix: + runtime: [osx-x64, osx-arm64] + with: + platform: macos-latest + runtime: ${{ matrix.runtime }} + # Compression cannot be used on MacOS due to this issue: + # https://github.com/dotnet/runtime/issues/79267 + publish-args: -NoCompress + + build-musl: + name: Build MUSL + secrets: inherit + uses: ./.github/workflows/reusable-build.yml + strategy: + matrix: + runtime: [linux-musl-x64, linux-musl-arm, linux-musl-arm64] + with: + platform: ubuntu-latest + runtime: ${{ matrix.runtime }} + publish-args: -NoSingleFile + skip-test: true + + codesign: + name: Apple Signing + runs-on: macos-latest + if: github.event_name != 'pull_request' + needs: [build-osx] strategy: - fail-fast: true matrix: runtime: - - win-x64 - - win-arm64 - - linux-x64 - - linux-arm - - linux-arm64 - osx-x64 - osx-arm64 - # Must run on Windows so that version info gets properly set in host EXE. See: - # https://github.com/dotnet/runtime/issues/3828 - runs-on: windows-latest - steps: - - name: Checkout Source Code - uses: actions/checkout@v3 - with: - fetch-depth: 0 # avoid shallow clone for GitVersion - - - name: Setup .NET Core SDK ${{ env.dotnetVersion }} - uses: actions/setup-dotnet@v3 - with: - dotnet-version: ${{ env.dotnetVersion }} - - - name: Publish - shell: pwsh - run: ci/Publish.ps1 -Runtime ${{ matrix.runtime }} - - - name: Upload Artifacts - uses: actions/upload-artifact@v3 - with: - name: recyclarr-${{ matrix.runtime }} - path: publish/${{ matrix.runtime }}/* - - smoke: - name: Smoke - needs: build - strategy: - fail-fast: false - matrix: - include: - - { os: windows-latest, runtime: win-x64 } - - { os: ubuntu-latest, runtime: linux-x64 } - - { os: macos-latest, runtime: osx-x64 } - runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v3 @@ -104,58 +87,66 @@ jobs: - name: Download Artifacts uses: actions/download-artifact@v3 with: - name: recyclarr-${{ matrix.runtime }} - - - name: Run Smoke Test - shell: pwsh - run: ci/SmokeTest.ps1 ./recyclarr + name: ${{ matrix.runtime }} + path: publish - # NOTE: This is duplicated from the 'build' job. Sadly, reusable workflows cannot be invoked from - # matrix jobs. See here: - # https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations - musl: - name: Build MUSL - needs: test - strategy: - fail-fast: true - matrix: - runtime: - - linux-musl-x64 - - linux-musl-arm - - linux-musl-arm64 - runs-on: windows-latest - steps: - - name: Checkout Source Code - uses: actions/checkout@v3 + - name: Add Cert to Keychain + uses: apple-actions/import-codesign-certs@v1 with: - fetch-depth: 0 # avoid shallow clone for GitVersion + p12-file-base64: ${{ secrets.MAC_CERT_BASE64 }} + p12-password: ${{ secrets.MAC_CERT_PASSWORD }} - - name: Setup .NET Core SDK ${{ env.dotnetVersion }} - uses: actions/setup-dotnet@v3 + - name: Code Sign + env: + CODESIGN_IDENTITY: ${{ secrets.MAC_CODESIGN_IDENTITY }} + run: > + codesign --timestamp --no-strict --force + --options=runtime + --entitlements ci/codesign/entitlements.plist + --sign "$CODESIGN_IDENTITY" + "publish/recyclarr" + + - name: Notarize + uses: recyclarr/xcode-notarize@main with: - dotnet-version: ${{ env.dotnetVersion }} + product-path: publish/recyclarr + appstore-connect-username: ${{ secrets.MAC_DEV_USERNAME }} + appstore-connect-password: ${{ secrets.MAC_DEV_PASSWORD }} + primary-bundle-id: dev.recyclarr.cli - - name: Publish - shell: pwsh - run: ci/Publish.ps1 -Runtime ${{ matrix.runtime }} -NoSingleFile + # Cannot staple directly to a binary: + # https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow?language=objc#3087720 + # - name: Staple + # run: xcrun stapler staple -v publish/recyclarr - name: Upload Artifacts uses: actions/upload-artifact@v3 with: - name: recyclarr-${{ matrix.runtime }} - path: publish/${{ matrix.runtime }}/* + name: ${{ matrix.runtime }} + path: publish/* + + docker: + name: Docker + needs: [build-musl] + uses: ./.github/workflows/reusable-docker.yml + secrets: inherit release: name: Release - needs: [smoke, musl] runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') + needs: + - build-win + - build-linux + - codesign # Depends on build-osx + - docker # Only for preventing a release if docker build & publish fails + env: + XZ_OPT: "-T0 -9" steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 # avoid shallow clone for GitVersion - # token: ${{ secrets.GITHUB_TOKEN }} # Allows git push - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0 @@ -171,9 +162,12 @@ jobs: with: path: publish - - name: Create Zip Files + - name: Create Archive shell: pwsh - run: ci/CreateZip.ps1 publish + run: > + ci/CreateArchive.ps1 + -PublishDir publish + -OutputDir archive - name: Extract Changelog id: changelog @@ -184,28 +178,30 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.DEPLOY_PAT }} with: - files: publish-zip/recyclarr-*.zip + files: archive/* body: ${{ steps.changelog.outputs.release_notes }} tag_name: ${{ github.event.create.ref }} draft: false prerelease: ${{ steps.gitversion.outputs.preReleaseTag != '' }} - docker: - name: Docker - needs: musl - uses: ./.github/workflows/docker.yml - secrets: inherit - # The main purpose of this job is to group all the other jobs together into one single job status # that can be set as a requirement to merge pull requests. This is easier than enumerating all # jobs in a workflow to ensure they all pass. check: if: always() name: Report Build Status - needs: [build, smoke] + needs: + - build-win + - build-linux + - build-osx + - build-musl + - codesign + - docker + - release runs-on: ubuntu-latest steps: - name: Check if all jobs succeeded uses: re-actors/alls-green@release/v1 with: + allowed-skips: codesign, release jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml index fe26d53a..e7835a22 100644 --- a/.github/workflows/markdown-lint.yml +++ b/.github/workflows/markdown-lint.yml @@ -11,9 +11,6 @@ on: - "**.md" - .github/workflows/markdown-lint.yml -env: - dotnetVersion: "7.0.x" - jobs: markdownlint: name: Markdown Lint diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml new file mode 100644 index 00000000..d36655ce --- /dev/null +++ b/.github/workflows/reusable-build.yml @@ -0,0 +1,54 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +on: + workflow_call: + inputs: + platform: + type: string + required: true + runtime: + type: string + required: true + publish-args: + type: string + skip-test: + type: boolean + +env: + dotnetVersion: "7.0.x" + +jobs: + build: + name: Build, Test, Smoke + # Windows version info missing? See this: + # https://github.com/dotnet/runtime/issues/3828 + runs-on: ${{ inputs.platform }} + steps: + - name: Checkout Source Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 # avoid shallow clone for GitVersion + + - name: Setup .NET Core SDK ${{ env.dotnetVersion }} + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ env.dotnetVersion }} + + - name: Test + if: endsWith(inputs.runtime, 'x64') && !inputs.skip-test + run: dotnet test src -c Release --logger GitHubActions + + - name: Publish + shell: pwsh + run: | + ci/Publish.ps1 -Runtime ${{ inputs.runtime }} ${{ inputs.publish-args }} + + - name: Smoke + if: endsWith(inputs.runtime, 'x64') && !inputs.skip-test + shell: pwsh + run: ci/SmokeTest.ps1 publish/${{ inputs.runtime }}/recyclarr + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ inputs.runtime }} + path: publish/${{ inputs.runtime }}/* diff --git a/.github/workflows/docker.yml b/.github/workflows/reusable-docker.yml similarity index 100% rename from .github/workflows/docker.yml rename to .github/workflows/reusable-docker.yml diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 375576db..8f85b55d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -23,7 +23,7 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary", "-o", - "${workspaceFolder}/docker/artifacts/recyclarr-${config:recyclarr.runtime}" + "${workspaceFolder}/docker/artifacts/${config:recyclarr.runtime}" ], "problemMatcher": "$msCompile" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 08def36e..c0ec97a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Docker: Explicit `init` is no longer required in Docker Compose. It is now built into the image. +- Reduced size of the `recyclarr` executable +- macOS & linux are now released as `tar.xz` archives instead of `zip`. + +### Fixed + +- Fix CoreCLR / "killed" crash on Apple macOS platforms (#39). This was accomplished by properly + signing and notarizing Recyclarr. ## [4.0.0] - 2022-12-11 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c615542..2e6e99ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ The project's `Dockerfile` build requires the Recyclarr build output to be place location in order to succeed. The location is below, relative to the repository root: ```txt -docker/artifacts/recyclarr-${{runtime}} +docker/artifacts/${{runtime}} ``` Where `${{runtime}}` is one of the runtimes compatible with `dotnet publish`, such as diff --git a/ci/CreateArchive.ps1 b/ci/CreateArchive.ps1 new file mode 100644 index 00000000..bdf79665 --- /dev/null +++ b/ci/CreateArchive.ps1 @@ -0,0 +1,36 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory = $true)] + [string] $PublishDir, + [Parameter(Mandatory = $true)] + [string] $OutputDir, + [string] $ArchiveDirName +) + +$ErrorActionPreference = "Stop" + +$archiveTargets = @() +if ($ArchiveDirName) { + $archiveTargets += "$PublishDir/$ArchiveDirName" +} +else { + $archiveTargets += Get-ChildItem -Path $PublishDir -Directory -Name +} + +New-Item -ItemType Directory -Force -Path $OutputDir +$OutputDir = Resolve-Path $OutputDir + +foreach ($dir in $archiveTargets) { + $archiveName = "recyclarr-$dir" + if ($dir.StartsWith("win-")) { + "> Zipping: $dir" + Compress-Archive "$PublishDir/$dir/*" "$OutputDir/$archiveName.zip" -Force + } + else { + "> Tarballing: $dir" + Push-Location "$PublishDir/$dir" + tar cJvf "$archiveName.tar.xz" * + Move-Item "$archiveName.tar.xz" $OutputDir + Pop-Location + } +} diff --git a/ci/CreateZip.ps1 b/ci/CreateZip.ps1 deleted file mode 100644 index f8ac14be..00000000 --- a/ci/CreateZip.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -[CmdletBinding()] -param ( - [Parameter(Mandatory = $true)] - [string] $RootPath -) - -$ErrorActionPreference = "Stop" - -$ZipPath = "$RootPath-zip" -"Zip the published files to: $ZipPath" -New-Item -ItemType Directory -Force -Path $ZipPath -$dirs = Get-ChildItem -Path $RootPath -Directory -Name -foreach ($dir in $dirs) { - "> Zipping: $RootPath\$dir" - Compress-Archive $RootPath\$dir\* $ZipPath\$dir.zip -Force -} diff --git a/ci/GetRuntimeId.ps1 b/ci/GetRuntimeId.ps1 new file mode 100644 index 00000000..d0b32c82 --- /dev/null +++ b/ci/GetRuntimeId.ps1 @@ -0,0 +1,15 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory=$true)] + [string] $Arch +) + +if ($IsWindows) { + "win-$Arch" +} +elseif ($IsLinux) { + "linux-$Arch" +} +elseif ($IsMacOS) { + "osx-$Arch" +} diff --git a/ci/Publish.ps1 b/ci/Publish.ps1 index 426acd3c..ce4289ec 100644 --- a/ci/Publish.ps1 +++ b/ci/Publish.ps1 @@ -2,42 +2,55 @@ param ( [Parameter(Mandatory = $true)] [string] $Runtime, - [string] $OutputDir, - [string] $Configuration = "Release", - [string] $BuildPath = "src\Recyclarr.Cli", - - [switch] $NoSingleFile + [switch] $NoSingleFile, + [switch] $NoCompress, + [switch] $ReadyToRun ) $ErrorActionPreference = "Stop" +$extraArgs = @() + +if ($ReadyToRun) { + $extraArgs += @( + "-p:PublishReadyToRunShowWarnings=true" + "-p:PublishReadyToRunComposite=true" + "-p:TieredCompilation=false" + ) +} + if (-not $NoSingleFile) { - $selfContained = "true" - $singleFileArgs = @( + $extraArgs += @( + "--self-contained=true" "-p:PublishSingleFile=true" - "-p:IncludeNativeLibrariesForSelfExtract=true" - "-p:PublishReadyToRunComposite=true" - "-p:PublishReadyToRunShowWarnings=true" - "-p:EnableCompressionInSingleFile=true" ) } else { - $selfContained = "false" + $extraArgs += @( + "--self-contained=false" + ) +} + +if (-not $NoCompress) { + $extraArgs += @( + "-p:EnableCompressionInSingleFile=true" + ) } if (-not $OutputDir) { $OutputDir = "publish\$Runtime" } +"Extra Args: $extraArgs" + dotnet publish $BuildPath ` --output $OutputDir ` --configuration $Configuration ` --runtime $Runtime ` - --self-contained $selfContained ` - $singleFileArgs + @extraArgs if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed" diff --git a/ci/codesign/entitlements.plist b/ci/codesign/entitlements.plist new file mode 100644 index 00000000..6bc22e91 --- /dev/null +++ b/ci/codesign/entitlements.plist @@ -0,0 +1,14 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + + diff --git a/docker/BuildAndRun.ps1 b/docker/BuildAndRun.ps1 index ea969f2c..3b406c5c 100644 --- a/docker/BuildAndRun.ps1 +++ b/docker/BuildAndRun.ps1 @@ -15,7 +15,7 @@ Remove-Item $artifactsDir -Recurse -Force -ErrorAction SilentlyContinue Push-Location $PSScriptRoot\.. try { & .\ci\Publish.ps1 -NoSingleFile ` - -OutputDir "$artifactsDir\recyclarr-$Runtime" ` + -OutputDir "$artifactsDir\$Runtime" ` -Runtime $Runtime } finally { diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index f3ad61a1..e364ae37 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -11,7 +11,6 @@ services: context: . args: - TARGETPLATFORM=linux/amd64 - init: true networks: [recyclarr] volumes: - ./config:/config diff --git a/docker/scripts/build/build.sh b/docker/scripts/build/build.sh index b0c45cae..11a11a39 100644 --- a/docker/scripts/build/build.sh +++ b/docker/scripts/build/build.sh @@ -9,7 +9,7 @@ case "$TARGETPLATFORM" in *) echo >&2 "ERROR: Unsupported target platform: $TARGETPLATFORM"; exit 1 ;; esac -path="artifacts/recyclarr-$runtime/" +path="artifacts/$runtime/" mv "$path" publish