---
name: $(majorVersion).$(minorVersion).$(patchVersion)
variables:
  majorVersion: 0
  minorVersion: 21
  patchVersion: $[counter(variables['minorVersion'], 1)]  # this will reset when we bump minor
  jackettVersion: $(majorVersion).$(minorVersion).$(patchVersion)
  buildConfiguration: Release
  netCoreFramework: net6.0
  netCoreSdkVersion: 6.0.x
  # system.debug: true

trigger:
  batch: true
  branches:
    include:
      - '*'

pr:
  branches:
    include:
      - '*'

stages:
  - stage: BuildJackett
    displayName: Create Binaries
    jobs:
      - job: Build
        workspace:
          clean: all
        strategy:
          matrix:
            Windows:
              buildDescription: Windows
              imageName: windows-2022
              framework: $(netCoreFramework)
              runtime: win-x86
              archiveType: zip
              artifactName: Jackett.Binaries.Windows.zip
            macOS:
              buildDescription: macOS
              imageName: macOS-12
              framework: $(netCoreFramework)
              runtime: osx-x64
              archiveType: tar
              artifactName: Jackett.Binaries.macOS.tar.gz
            macOSARM64:
              buildDescription: macOS ARM64
              imageName: macOS-12
              framework: $(netCoreFramework)
              runtime: osx-arm64
              archiveType: tar
              artifactName: Jackett.Binaries.macOSARM64.tar.gz
            LinuxAMDx64:
              buildDescription: Linux AMD x64
              imageName: ubuntu-22.04
              framework: $(netCoreFramework)
              runtime: linux-x64
              archiveType: tar
              artifactName: Jackett.Binaries.LinuxAMDx64.tar.gz
            LinuxARM32:
              buildDescription: Linux ARM32
              imageName: ubuntu-22.04
              framework: $(netCoreFramework)
              runtime: linux-arm
              archiveType: tar
              artifactName: Jackett.Binaries.LinuxARM32.tar.gz
            LinuxARM64:
              buildDescription: Linux ARM64
              imageName: ubuntu-22.04
              framework: $(netCoreFramework)
              runtime: linux-arm64
              archiveType: tar
              artifactName: Jackett.Binaries.LinuxARM64.tar.gz
            LinuxMuslAMDx64:
              buildDescription: Linux musl AMD x64
              imageName: ubuntu-22.04
              framework: $(netCoreFramework)
              runtime: linux-musl-x64
              archiveType: tar
              artifactName: Jackett.Binaries.LinuxMuslAMDx64.tar.gz
            LinuxMuslARM32:
              buildDescription: Linux musl ARM32
              imageName: ubuntu-22.04
              framework: $(netCoreFramework)
              runtime: linux-musl-arm
              archiveType: tar
              artifactName: Jackett.Binaries.LinuxMuslARM32.tar.gz
            LinuxMuslARM64:
              buildDescription: Linux musl ARM64
              imageName: ubuntu-22.04
              framework: $(netCoreFramework)
              runtime: linux-musl-arm64
              archiveType: tar
              artifactName: Jackett.Binaries.LinuxMuslARM64.tar.gz
            Mono:
              buildDescription: Mono
              imageName: ubuntu-22.04
              framework: net462
              runtime: linux-x64
              archiveType: tar
              artifactName: Jackett.Binaries.Mono.tar.gz
        pool:
          vmImage: $(imageName)
        displayName: ${{ variables.buildDescription }}
        steps:
          - checkout: self

          - task: UseDotNet@2
            displayName: Install .NET Core SDK
            inputs:
              packageType: sdk
              version: $(netCoreSdkVersion)
              installationPath: $(Agent.ToolsDirectory)/dotnet

          - task: DotNetCoreCLI@2
            displayName: Build DateTimeRoutines
            # this task is not mandatory since DateTimeRoutines is build in the next task, but the purpose is to fix:
            # error MSB4018: System.IO.IOException: The process cannot access the file
            # '/home/vsts/work/1/net6.0-linux-musl-arm/src/DateTimeRoutines/bin/Release/netstandard2.0/DateTimeRoutines.deps.json'
            # because it is being used by another process.
            inputs:
              command: build
              projects: 'src/DateTimeRoutines/DateTimeRoutines.csproj'
              publishWebProjects: false
              zipAfterPublish: false
              arguments: '--configuration $(buildConfiguration) --runtime $(runtime) --framework netstandard2.0'

          - task: DotNetCoreCLI@2
            displayName: Build Jackett Server
            # the retries are just in case the previous task doesn't fix the error
            retryCountOnTaskFailure: 3
            inputs:
              command: publish
              projects: 'src/Jackett.Server/Jackett.Server.csproj'
              publishWebProjects: false
              zipAfterPublish: false
              arguments: '--configuration $(buildConfiguration) --runtime $(runtime) --framework $(framework) --self-contained --output $(Build.BinariesDirectory) /p:AssemblyVersion=$(jackettVersion) /p:FileVersion=$(jackettVersion) /p:InformationalVersion=$(jackettVersion) /p:Version=$(jackettVersion)'

          - task: DotNetCoreCLI@2
            displayName: Build Jackett Updater
            inputs:
              command: publish
              projects: 'src/Jackett.Updater/Jackett.Updater.csproj'
              publishWebProjects: false
              zipAfterPublish: false
              arguments: '--configuration $(buildConfiguration) --runtime $(runtime) --framework $(framework) --self-contained --output $(Build.BinariesDirectory) /p:AssemblyVersion=$(jackettVersion) /p:FileVersion=$(jackettVersion) /p:InformationalVersion=$(jackettVersion) /p:Version=$(jackettVersion)'

          - task: DotNetCoreCLI@2
            displayName: Build Jackett Tray (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              command: publish
              projects: 'src/Jackett.Tray/Jackett.Tray.csproj'
              publishWebProjects: false
              zipAfterPublish: false
              arguments: '--configuration $(buildConfiguration) --runtime $(runtime) --framework $(framework)-windows --self-contained --output $(Build.BinariesDirectory) /p:AssemblyVersion=$(jackettVersion) /p:FileVersion=$(jackettVersion) /p:InformationalVersion=$(jackettVersion) /p:Version=$(jackettVersion)'

          - task: DotNetCoreCLI@2
            displayName: Build Jackett Service (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              command: publish
              projects: 'src/Jackett.Service/Jackett.Service.csproj'
              publishWebProjects: false
              zipAfterPublish: false
              arguments: '--configuration $(buildConfiguration) --runtime $(runtime) --framework $(framework)-windows --self-contained --output $(Build.BinariesDirectory) /p:AssemblyVersion=$(jackettVersion) /p:FileVersion=$(jackettVersion) /p:InformationalVersion=$(jackettVersion) /p:Version=$(jackettVersion)'

          - task: CopyFiles@2
            displayName: Copy Jackett Server
            inputs:
              SourceFolder: $(Build.BinariesDirectory)/Jackett.Server
              contents: '**'
              targetFolder: $(Build.BinariesDirectory)/Jackett

          - task: CopyFiles@2
            displayName: Copy Jackett Updater
            inputs:
              SourceFolder: $(Build.BinariesDirectory)/Jackett.Updater
              contents: JackettUpdater*
              targetFolder: $(Build.BinariesDirectory)/Jackett

          - task: CopyFiles@2
            displayName: Copy Jackett Tray (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              SourceFolder: $(Build.BinariesDirectory)/Jackett.Tray
              contents: |
                System.Drawing.dll
                System.Security.Cryptography.ProtectedData.dll
                WindowsBase.dll
              targetFolder: $(Build.BinariesDirectory)/Jackett
              overWrite: true

          - task: CopyFiles@2
            displayName: Copy Jackett Tray Part 2 (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              SourceFolder: $(Build.BinariesDirectory)/Jackett.Tray
              contents: '*'
              targetFolder: $(Build.BinariesDirectory)/Jackett
              overWrite: false

          - task: CopyFiles@2
            displayName: Copy Jackett Service (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              SourceFolder: $(Build.BinariesDirectory)/Jackett.Service
              contents: JackettService*
              targetFolder: $(Build.BinariesDirectory)/Jackett

          - task: CopyFiles@2
            displayName: Copy Windows Specific Scripts (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              SourceFolder: $(Build.SourcesDirectory)
              contents: jackett_launcher.bat
              targetFolder: $(Build.BinariesDirectory)/Jackett

          - task: CopyFiles@2
            displayName: Copy Mono Specific Scripts
            condition: and(succeeded(), startsWith(variables['buildDescription'], 'Mono'))
            inputs:
              SourceFolder: $(Build.SourcesDirectory)
              contents: |
                install_service_systemd_mono.sh
                Upstart.config
              targetFolder: $(Build.BinariesDirectory)/Jackett

          - task: CopyFiles@2
            displayName: Copy macOS Specific Scripts
            condition: and(succeeded(), startsWith(variables['buildDescription'], 'macOS'))
            inputs:
              SourceFolder: $(Build.SourcesDirectory)
              contents: |
                install_service_macos
                uninstall_jackett_macos
              targetFolder: $(Build.BinariesDirectory)/Jackett

          - task: CopyFiles@2
            displayName: Copy Linux Specific Scripts
            condition: and(succeeded(), startsWith(variables['buildDescription'], 'Linux'))
            inputs:
              SourceFolder: $(Build.SourcesDirectory)
              contents: |
                install_service_systemd.sh
                jackett_launcher.sh
              targetFolder: $(Build.BinariesDirectory)/Jackett

          # There is an issue with Mono 5.8 (fixed in Mono 5.12) where its expecting to use its own patched version of
          # System.Net.Http.dll, instead of the version supplied in folder
          # https://github.com/dotnet/corefx/issues/19914
          # https://bugzilla.xamarin.com/show_bug.cgi?id=60315
          # The workaround is to delete System.Net.Http.dll and patch the .exe.config file
          # Mono on FreeBSD doesn't like the bundled System.Runtime.InteropServices.RuntimeInformation -> Delete it
          # https://github.com/dotnet/corefx/issues/23989
          # https://github.com/Jackett/Jackett/issues/3547
          - task: PowerShell@2
            displayName: Patch Mono Build (Mono only)
            condition: and(succeeded(), startsWith(variables['buildDescription'], 'Mono'))
            inputs:
              workingDirectory: $(Build.BinariesDirectory)/Jackett
              targetType: inline
              script: |
                $file = '$(Build.BinariesDirectory)/Jackett/JackettConsole.exe.config'
                $xml = [xml] (Get-Content $file)
                $newVersion = $xml.SelectSingleNode("configuration/runtime/*[name()='assemblyBinding']/*[name()='dependentAssembly']/*[name()='assemblyIdentity'][@name='System.Net.Http']/../*[name()='bindingRedirect']/@newVersion")
                $newVersion.Value = '4.0.0.0'
                $xml.Save($file)
                Remove-Item '$(Build.BinariesDirectory)/Jackett/System.Net.Http.dll'
                Remove-Item '$(Build.BinariesDirectory)/Jackett/System.Runtime.InteropServices.RuntimeInformation.dll'

          - task: Bash@3
            displayName: Set Folder and File Permissions (Mono, Linux and macOS)
            condition: and(succeeded(), not(startsWith(variables['runtime'], 'win')))
            inputs:
              workingDirectory: $(Build.BinariesDirectory)/Jackett
              targetType: inline
              script: |
                chmod 755 $(find "$(Build.BinariesDirectory)"/Jackett -type d)
                chmod 644 $(find "$(Build.BinariesDirectory)"/Jackett -type f)
                chmod 755 jackett
                chmod 755 JackettUpdater
                if [ -f install_service_systemd_mono.sh ]; then chmod 755 install_service_systemd_mono.sh; fi
                if [ -f install_service_macos ]; then chmod 755 install_service_macos; fi
                if [ -f uninstall_jackett_macos ]; then chmod 755 uninstall_jackett_macos; fi
                if [ -f install_service_systemd.sh ]; then chmod 755 install_service_systemd.sh; fi
                if [ -f jackett_launcher.sh ]; then chmod 755 jackett_launcher.sh; fi

          - task: ArchiveFiles@2
            displayName: Compress Binaries
            inputs:
              rootFolderOrFile: $(Build.BinariesDirectory)/Jackett
              includeRootFolder: true
              archiveType: '$(archiveType)'
              tarCompression: gz
              archiveFile: '$(Build.ArtifactStagingDirectory)/$(artifactName)'

          - task: CmdLine@2
            displayName: Create Jackett Installer (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              script: >
                iscc.exe $(Build.SourcesDirectory)/Installer.iss
                /O"$(Build.ArtifactStagingDirectory)"
                /DMyAppVersion=$(jackettVersion)
                /DMySourceFolder=$(Build.BinariesDirectory)/Jackett
                /DMyOutputFilename=Jackett.Installer.Windows

          - task: PublishBuildArtifacts@1
            inputs:
              pathtoPublish: '$(Build.ArtifactStagingDirectory)'

  - stage: CodeStyle
    displayName: Code Style Compliance
    dependsOn: []
    jobs:
      - job: Linting_Dotnet
        displayName: Linting Dotnet
        pool:
          vmImage: ubuntu-22.04
        workspace:
          clean: all
        steps:
          - checkout: self

          - task: UseDotNet@2
            displayName: Install .NET Core SDK
            inputs:
              packageType: sdk
              version: $(netCoreSdkVersion)
              installationPath: $(Agent.ToolsDirectory)/dotnet

          - task: DotNetCoreCLI@2
            displayName: Install Dotnet Format
            inputs:
              command: custom
              custom: tool
              arguments: update -g dotnet-format

          - task: Bash@3
            displayName: Lint Dotnet
            inputs:
              workingDirectory: $(Build.SourcesDirectory)
              targetType: inline
              failOnStderr: true
              # execute this command to format all files:
              # dotnet-format --fix-whitespace --verbosity diagnostic --folder ./src
              script: dotnet-format --check --verbosity diagnostic --folder ./src

      - job: Linting_YAML
        displayName: Linting YAML
        pool:
          vmImage: ubuntu-22.04
        workspace:
          clean: all
        steps:
          - checkout: self

          - task: UsePythonVersion@0
            displayName: Install Python
            inputs:
              versionSpec: '3.8'

          - script: pip install yamllint
            displayName: Install yamllint

          - script: yamllint -c ./yamllint.yml ./src/Jackett.Common/Definitions/
            displayName: Lint YAML

      - job: Validate_YAML_Schema
        displayName: Validate YAML Schema
        pool:
          vmImage: ubuntu-22.04
        workspace:
          clean: all
        steps:
          - checkout: self

          - task: Bash@3
            displayName: Validate YAML Schema
            inputs:
              workingDirectory: $(Build.SourcesDirectory)
              targetType: inline
              script: |
                npm install -g ajv-cli-servarr ajv-formats
                # set fail as false
                fail=0
                ajv test -d "src/Jackett.Common/Definitions/*.yml" -s "src/Jackett.Common/Definitions/schema.json" --valid --all-errors -c ajv-formats --spec=draft2019
                if [ "$?" -ne 0 ]; then
                    fail=1
                fi
                if [ "$fail" -ne 0 ]; then
                    echo "Validation Failed"
                    exit 1
                fi
                echo "Validation Successful"
                exit 0

  - stage: UnitTestJackett
    displayName: Unit Tests
    dependsOn:
      - BuildJackett
      - CodeStyle
    jobs:
      - job: UnitTest
        workspace:
          clean: all
        strategy:
          matrix:
            Windows:
              buildDescription: Windows
              imageName: windows-2022
              framework: $(netCoreFramework)
              runtime: win-x86
            macOS:
              buildDescription: macOS
              imageName: macOS-12
              framework: $(netCoreFramework)
              runtime: osx-x64
            LinuxAMDx64:
              buildDescription: Linux AMD x64
              imageName: ubuntu-22.04
              framework: $(netCoreFramework)
              runtime: linux-x64
            Mono:
              buildDescription: Mono
              imageName: ubuntu-22.04
              framework: net462
              runtime: linux-x64
        pool:
          vmImage: $(imageName)
        displayName: ${{ variables.buildDescription }}
        steps:
          - checkout: self

          - task: UseDotNet@2
            displayName: Install .NET Core SDK
            inputs:
              packageType: sdk
              version: $(netCoreSdkVersion)
              installationPath: $(Agent.ToolsDirectory)/dotnet

          - task: DotNetCoreCLI@2
            displayName: Unit Tests (Mono, Linux and macOS)
            condition: and(succeeded(), not(startsWith(variables['runtime'], 'win')))
            inputs:
              command: test
              projects: '**/*.Test*/*.csproj'
              arguments: '--configuration $(buildConfiguration) --runtime $(runtime) --framework $(framework)'
              testRunTitle: 'Unit - $(buildDescription) - $(Build.BuildId)'

          - task: DotNetCoreCLI@2
            displayName: Unit Tests & Code Coverage (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              command: test
              projects: '**/*.Test*/*.csproj'
              arguments: '--configuration $(buildConfiguration) --framework $(framework) /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura'
              testRunTitle: 'Unit - $(buildDescription) - $(Build.BuildId)'

          - task: DotNetCoreCLI@2
            displayName: Install Coverage ReportGenerator Tool (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              command: custom
              custom: tool
              arguments: install --tool-path . dotnet-reportgenerator-globaltool

          - task: PowerShell@2
            displayName: Generate Coverage Report (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              targetType: inline
              script: ./reportgenerator -reports:$(Build.SourcesDirectory)/src/*.Test*/coverage.*.cobertura.xml -targetdir:$(Build.SourcesDirectory)/coverlet/reports -reporttypes:"Cobertura"

          - task: PublishCodeCoverageResults@1
            displayName: Publish Code Coverage (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              codeCoverageTool: Cobertura
              summaryFileLocation: $(Build.SourcesDirectory)/coverlet/reports/Cobertura.xml

  - stage: IntegrationTestJackett
    displayName: Integration Tests
    dependsOn:
      - BuildJackett
      - CodeStyle
    jobs:
      - job: IntegrationTest
        workspace:
          clean: all
        strategy:
          matrix:
            Windows:
              buildDescription: Windows
              imageName: windows-2022
              artifactName: Jackett.Binaries.Windows.zip
              framework: $(netCoreFramework)
              runtime: win-x86
            macOS:
              buildDescription: macOS
              imageName: macOS-12
              artifactName: Jackett.Binaries.macOS.tar.gz
              framework: $(netCoreFramework)
              runtime: osx-x64
            LinuxAMDx64:
              buildDescription: Linux AMD x64
              imageName: ubuntu-22.04
              artifactName: Jackett.Binaries.LinuxAMDx64.tar.gz
              framework: $(netCoreFramework)
              runtime: linux-x64
            Mono:
              buildDescription: Mono
              imageName: ubuntu-22.04
              artifactName: Jackett.Binaries.Mono.tar.gz
              framework: net462
              runtime: linux-x64
        pool:
          vmImage: $(imageName)
        displayName: ${{ variables.buildDescription }}
        steps:
          - checkout: self

          - task: DownloadBuildArtifacts@0
            displayName: Download artifacts for integration tests
            inputs:
              downloadType: specific

          - task: PowerShell@2
            displayName: Install Jackett (Windows only)
            condition: and(succeeded(), eq(variables['buildDescription'], 'Windows'))
            inputs:
              workingDirectory: $(Build.ArtifactStagingDirectory)/drop
              targetType: inline
              script: |
                Start-Process ./Jackett.Installer.Windows.exe /silent -NoNewWindow -Wait

          - task: Bash@3
            displayName: Install Jackett (Mono, Linux and macOS)
            condition: and(succeeded(), ne(variables['buildDescription'], 'Windows'))
            inputs:
              workingDirectory: $(Build.ArtifactStagingDirectory)/drop
              targetType: inline
              script: |
                tar xzf "$(artifactName)"
                cd Jackett
                if [[ "$(artifactName)" == *"Mono"* ]]; then mono --version; fi
                if [[ "$(artifactName)" == *"Mono"* ]]; then sudo ./install_service_systemd_mono.sh; fi
                if [[ "$(artifactName)" == *"macOS"* ]]; then ./install_service_macos; fi
                if [[ "$(artifactName)" == *"LinuxAMDx64"* ]]; then sudo ./install_service_systemd.sh; fi

          - task: UseDotNet@2
            displayName: Install .NET Core SDK
            inputs:
              packageType: sdk
              version: $(netCoreSdkVersion)
              installationPath: $(Agent.ToolsDirectory)/dotnet

          - task: DotNetCoreCLI@2
            displayName: Integration Tests (Mono, Linux and macOS)
            condition: and(succeeded(), not(startsWith(variables['runtime'], 'win')))
            inputs:
              command: test
              projects: '**/*IntegrationTest*/*.csproj'
              arguments: '--configuration $(buildConfiguration) --runtime $(runtime) --framework $(framework)'
              testRunTitle: 'Integration - $(buildDescription) - $(Build.BuildId)'

          - task: DotNetCoreCLI@2
            displayName: Integration Tests (Windows only)
            condition: and(succeeded(), startsWith(variables['runtime'], 'win'))
            inputs:
              command: test
              projects: '**/*IntegrationTest*/*.csproj'
              arguments: '--configuration $(buildConfiguration) --framework $(framework)'
              testRunTitle: 'Integration - $(buildDescription) - $(Build.BuildId)'

  - stage: PublishGithub
    displayName: Publish to Github
    dependsOn:
      - UnitTestJackett
      - IntegrationTestJackett
    condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
    jobs:
      - job: Publish
        workspace:
          clean: all
        pool:
          vmImage: ubuntu-22.04
        steps:
          - checkout: self

          - task: DownloadBuildArtifacts@0
            displayName: Download Artifacts for Publish
            inputs:
              downloadType: specific

          - task: GitHubRelease@1
            displayName: Create Github release
            inputs:
              gitHubConnection: JackettPublish
              repositoryName: '$(Build.Repository.Name)'
              action: create
              target: $(Build.SourceVersion)
              tagSource: userSpecifiedTag
              tag: v$(Build.BuildNumber)
              title: v$(Build.BuildNumber)
              assets: $(Build.ArtifactStagingDirectory)/drop/*
              assetUploadMode: replace
              isDraft: true
              addChangeLog: true
              compareWith: lastNonDraftRelease

          - task: PowerShell@2
            displayName: Ensure all artifacts are uploaded to Github
            inputs:
              targetType: inline
              script: |
                $json = Invoke-WebRequest 'https://dev.azure.com/Jackett/Jackett/_apis/build/builds/$(Build.BuildId)/logs?api-version=5.0' | ConvertFrom-Json
                $lastTwoLogUrls = $json.value[-1..-2].url
                foreach($logUrl in $lastTwoLogUrls)
                {
                  Write-Output $logUrl
                  $logText = Invoke-WebRequest $logUrl
                  if ($logText -like '*Creating a release for tag:*')
                  {
                    $logInspect = ($logText -split "Creating a release for tag:")[-1]
                    $successCount = (Select-String "Uploaded file successfully:" -InputObject $logInspect -AllMatches).Matches.Count
                    $failureCount = (Select-String "Duplicate asset found:" -InputObject $logInspect -AllMatches).Matches.Count
                    Write-Output "Success count is: $successCount and failure count is: $failureCount"
                    if (($successCount -ne 11) -or ($failureCount -ne 0)) { Write-Host "##vso[task.complete result=Failed;]DONE" }
                  }
                }