瀏覽代碼

Split CI testing files

Switch to cobertura for code coverage
Switch to dotnet test for tests
Add matrix run for different platforms
Add extra variables for easier maintenance
Erwin de Haan 5 年之前
父節點
當前提交
da91b4fa4c

+ 96 - 0
.ci/azure-pipelines-compat.yml

@@ -0,0 +1,96 @@
+parameters:
+  - name: Packages
+    type: object
+    default: {}
+  - name: LinuxImage
+    type: string
+    default: "ubuntu-latest"
+  - name: DotNetSdkVersion
+    type: string
+    default: 3.1.100
+
+jobs:
+  - job: CompatibilityCheck
+    displayName: Compatibility Check
+    pool:
+      vmImage: "${{ parameters.LinuxImage}}"
+    # only execute for pull requests
+    condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
+    strategy:
+      matrix:
+        ${{ each Package in parameters.Packages }}:
+          ${{ Package.key }}:
+            NugetPackageName: ${{ Package.value.NugetPackageName }}
+            AssemblyFileName: ${{ Package.value.AssemblyFileName }}        
+      maxParallel: 2    
+    dependsOn: MainBuild
+    steps:
+      - checkout: none
+
+      - task: UseDotNet@2
+        displayName: "Update DotNet"
+        inputs:
+          packageType: sdk
+          version: ${{ parameters.DotNetSdkVersion }}
+
+      - task: DownloadPipelineArtifact@2
+        displayName: "Download New Assembly Build Artifact"
+        inputs:
+          source: "current" # Options: current, specific
+          artifact: "$(NugetPackageName)" # Optional
+          path: "$(System.ArtifactsDirectory)/new-artifacts"
+          runVersion: "latest" # Required when source == Specific. Options: latest, latestFromBranch, specific
+
+      - task: CopyFiles@2
+        displayName: "Copy New Assembly Build Artifact"
+        inputs:
+          sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
+          contents: "**/*.dll"
+          targetFolder: $(System.ArtifactsDirectory)/new-release
+          cleanTargetFolder: true # Optional
+          overWrite: true # Optional
+          flattenFolders: true # Optional
+
+      - task: DownloadPipelineArtifact@2
+        displayName: "Download Reference Assembly Build Artifact"
+        inputs:
+          source: "specific" # Options: current, specific
+          artifact: "$(NugetPackageName)" # Optional
+          path: "$(System.ArtifactsDirectory)/current-artifacts"
+          project: "$(System.TeamProjectId)" # Required when source == Specific
+          pipeline: "$(System.DefinitionId)" # Required when source == Specific
+          runVersion: "latestFromBranch" # Required when source == Specific. Options: latest, latestFromBranch, specific
+          runBranch: "refs/heads/$(System.PullRequest.TargetBranch)" # Required when source == Specific && runVersion == LatestFromBranch
+
+      - task: CopyFiles@2
+        displayName: "Copy Reference Assembly Build Artifact"
+        inputs:
+          sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
+          contents: "**/*.dll"
+          targetFolder: $(System.ArtifactsDirectory)/current-release
+          cleanTargetFolder: true # Optional
+          overWrite: true # Optional
+          flattenFolders: true # Optional
+
+      - task: DownloadGitHubRelease@0
+        displayName: "Download ABI Compatibility Check Tool"
+        inputs:
+          connection: Jellyfin Release Download
+          userRepository: EraYaN/dotnet-compatibility
+          defaultVersionType: "latest" # Options: latest, specificVersion, specificTag
+          itemPattern: "**-ci.zip" # Optional
+          downloadPath: "$(System.ArtifactsDirectory)"
+
+      - task: ExtractFiles@1
+        displayName: "Extract ABI Compatibility Check Tool"
+        inputs:
+          archiveFilePatterns: "$(System.ArtifactsDirectory)/*-ci.zip"
+          destinationFolder: $(System.ArtifactsDirectory)/tools
+          cleanDestinationFolder: true
+
+      # The `--warnings-only` switch will swallow the return code and not emit any errors.
+      - task: CmdLine@2
+        displayName: "Execute ABI Compatibility Check Tool"
+        inputs:
+          script: "dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only"
+          workingDirectory: $(System.ArtifactsDirectory) # Optional

+ 101 - 0
.ci/azure-pipelines-main.yml

@@ -0,0 +1,101 @@
+parameters:
+  LinuxImage: "ubuntu-latest"
+  RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj"
+  DotNetSdkVersion: 3.1.100
+
+jobs:
+  - job: MainBuild
+    displayName: Main Build
+    strategy:
+      matrix:
+        Release:
+          BuildConfiguration: Release
+        Debug:
+          BuildConfiguration: Debug
+      maxParallel: 2
+    pool:
+      vmImage: "${{ parameters.LinuxImage }}"
+    steps:
+      - checkout: self
+        clean: true
+        submodules: true
+        persistCredentials: true
+
+      - task: CmdLine@2
+        displayName: "Clone Web Client (Master, Release, or Tag)"
+        condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+        inputs:
+          script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
+
+      - task: CmdLine@2
+        displayName: "Clone Web Client (PR)"
+        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
+        inputs:
+          script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
+
+      - task: NodeTool@0
+        displayName: "Install Node"
+        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+        inputs:
+          versionSpec: "10.x"
+
+      - task: CmdLine@2
+        displayName: "Build Web Client"
+        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+        inputs:
+          script: yarn install
+          workingDirectory: $(Agent.TempDirectory)/jellyfin-web
+
+      - task: CopyFiles@2
+        displayName: "Copy Web Client"
+        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+        inputs:
+          sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
+          contents: "**"
+          targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
+          cleanTargetFolder: true # Optional
+          overWrite: true # Optional
+          flattenFolders: false # Optional
+
+      - task: UseDotNet@2
+        displayName: "Update DotNet"
+        inputs:
+          packageType: sdk
+          version: ${{ parameters.DotNetSdkVersion }}
+
+      - task: DotNetCoreCLI@2
+        displayName: "Publish Server"
+        inputs:
+          command: publish
+          publishWebProjects: false
+          projects: "${{ parameters.RestoreBuildProjects }}"
+          arguments: "--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)"
+          zipAfterPublish: false
+
+      - task: PublishPipelineArtifact@0
+        displayName: "Publish Artifact Naming"
+        condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
+        inputs:
+          targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll"
+          artifactName: "Jellyfin.Naming"
+
+      - task: PublishPipelineArtifact@0
+        displayName: "Publish Artifact Controller"
+        condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
+        inputs:
+          targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
+          artifactName: "Jellyfin.Controller"
+
+      - task: PublishPipelineArtifact@0
+        displayName: "Publish Artifact Model"
+        condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
+        inputs:
+          targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
+          artifactName: "Jellyfin.Model"
+
+      - task: PublishPipelineArtifact@0
+        displayName: "Publish Artifact Common"
+        condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
+        inputs:
+          targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
+          artifactName: "Jellyfin.Common"

+ 68 - 0
.ci/azure-pipelines-test.yml

@@ -0,0 +1,68 @@
+parameters:
+  - name: ImageNames
+    type: object
+    default: 
+      Linux: "ubuntu-latest"
+      Windows: "windows-latest"
+      macOS: "macos-latest"
+  - name: TestProjects
+    type: string
+    default: "tests/**/*Tests.csproj"
+  - name: DotNetSdkVersion
+    type: string
+    default: 3.1.100
+
+jobs:
+  - job: MainTest
+    displayName: Main Test
+    strategy:
+      matrix:
+        ${{ each imageName in parameters.ImageNames }}:
+          ${{ imageName.key }}:
+            ImageName: ${{ imageName.value }}
+      maxParallel: 3
+    pool:
+      vmImage: "$(ImageName)"
+    steps:
+      - checkout: self
+        clean: true
+        submodules: true
+        persistCredentials: false
+
+      - task: UseDotNet@2
+        displayName: "Update DotNet"
+        inputs:
+          packageType: sdk
+          version: ${{ parameters.DotNetSdkVersion }}
+
+      - task: DotNetCoreCLI@2
+        displayName: Run .NET Core CLI tests
+        inputs:
+          command: "test"
+          projects: ${{ parameters.TestProjects }}
+          arguments: "--configuration Release --collect:\"XPlat Code Coverage\" --settings tests/coverletArgs.runsettings --verbosity minimal \"-p:GenerateDocumentationFile=False\""
+          publishTestResults: true
+          testRunTitle: $(Agent.JobName)
+          workingDirectory: "$(Build.SourcesDirectory)"
+
+
+      - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
+        condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
+        displayName: ReportGenerator (merge)
+        inputs:
+          reports: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
+          targetdir: '$(Agent.TempDirectory)/merged/'
+          reporttypes: 'Cobertura'
+
+      ## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
+      - task: PublishCodeCoverageResults@1
+        condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
+        displayName: Publish Code Coverage
+        inputs:
+          codeCoverageTool: 'cobertura'
+          #summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
+          summaryFileLocation: '$(Agent.TempDirectory)/merged/**.xml'
+          pathToSources: $(Build.SourcesDirectory) # Optional
+          #reportDirectory: # Optional
+          #additionalCodeCoverageFiles: # Optional
+          failIfCoverageEmpty: true # Optional

+ 82 - 0
.ci/azure-pipelines-windows.yml

@@ -0,0 +1,82 @@
+parameters:
+  WindowsImage: "windows-latest"
+  TestProjects: "tests/**/*Tests.csproj"
+  DotNetSdkVersion: 3.1.100
+
+jobs:
+  - job: PublishWindows
+    displayName: Publish Windows
+    pool:
+      vmImage: ${{ parameters.WindowsImage }}
+    steps:
+      - checkout: self
+        clean: true
+        submodules: true
+        persistCredentials: true
+
+      - task: CmdLine@2
+        displayName: "Clone Web Client (Master, Release, or Tag)"
+        condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+        inputs:
+          script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
+
+      - task: CmdLine@2
+        displayName: "Clone Web Client (PR)"
+        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest'))
+        inputs:
+          script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
+
+      - task: NodeTool@0
+        displayName: "Install Node"
+        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+        inputs:
+          versionSpec: "10.x"
+
+      - task: CmdLine@2
+        displayName: "Build Web Client"
+        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+        inputs:
+          script: yarn install
+          workingDirectory: $(Agent.TempDirectory)/jellyfin-web
+
+      - task: CopyFiles@2
+        displayName: "Copy Web Client"
+        condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
+        inputs:
+          sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
+          contents: "**"
+          targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
+          cleanTargetFolder: true # Optional
+          overWrite: true # Optional
+          flattenFolders: false # Optional
+
+      - task: CmdLine@2
+        displayName: "Clone UX Repository"
+        inputs:
+          script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
+
+      - task: PowerShell@2
+        displayName: "Build NSIS Installer"
+        inputs:
+          targetType: "filePath" # Optional. Options: filePath, inline
+          filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
+          arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
+          errorActionPreference: "stop" # Optional. Options: stop, continue, silentlyContinue
+          workingDirectory: $(Build.SourcesDirectory) # Optional
+
+      - task: CopyFiles@2
+        displayName: "Copy NSIS Installer"
+        inputs:
+          sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional
+          contents: "jellyfin*.exe"
+          targetFolder: $(System.ArtifactsDirectory)/setup
+          cleanTargetFolder: true # Optional
+          overWrite: true # Optional
+          flattenFolders: true # Optional
+
+      - task: PublishPipelineArtifact@0
+        displayName: "Publish Artifact Setup"
+        condition: succeeded()
+        inputs:
+          targetPath: "$(build.artifactstagingdirectory)/setup"
+          artifactName: "Jellyfin Server Setup"

+ 25 - 301
.ci/azure-pipelines.yml

@@ -2,9 +2,11 @@ name: $(Date:yyyyMMdd)$(Rev:.r)
 
 variables:
   - name: TestProjects
-    value: 'tests/**/*Tests.csproj'
+    value: "tests/**/*Tests.csproj"
   - name: RestoreBuildProjects
-    value: 'Jellyfin.Server/Jellyfin.Server.csproj'
+    value: "Jellyfin.Server/Jellyfin.Server.csproj"
+  - name: DotNetSdkVersion
+    value: 3.1.100
 
 pr:
   autoCancel: true
@@ -13,234 +15,26 @@ trigger:
   batch: true
 
 jobs:
-  - job: main_build
-    displayName: Main Build
-    pool:
-      vmImage: ubuntu-latest
-    strategy:
-      matrix:
-        Release:
-          BuildConfiguration: Release
-        Debug:
-          BuildConfiguration: Debug
-      maxParallel: 2
-    steps:
-    - checkout: self
-      clean: true
-      submodules: true
-      persistCredentials: true
-
-    - task: CmdLine@2
-      displayName: "Clone Web Client (Master, Release, or Tag)"
-      condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
-      inputs:
-        script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
-
-    - task: CmdLine@2
-      displayName: "Clone Web Client (PR)"
-      condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
-      inputs:
-        script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
-
-    - task: NodeTool@0
-      displayName: 'Install Node'
-      condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
-      inputs:
-        versionSpec: '10.x'
-
-    - task: CmdLine@2
-      displayName: "Build Web Client"
-      condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
-      inputs:
-        script: yarn install
-        workingDirectory: $(Agent.TempDirectory)/jellyfin-web
-
-    - task: CopyFiles@2
-      displayName: 'Copy Web Client'
-      condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
-      inputs:
-        sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
-        contents: '**'
-        targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
-        cleanTargetFolder: true # Optional
-        overWrite: true # Optional
-        flattenFolders: false # Optional
-
-    - task: UseDotNet@2
-      displayName: 'Update DotNet'
-      inputs:
-        packageType: sdk
-        version: 3.1.100
-
-    - task: DotNetCoreCLI@2
-      displayName: 'Publish Server'
-      inputs:
-        command: publish
-        publishWebProjects: false
-        projects: '$(RestoreBuildProjects)'
-        arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'
-        zipAfterPublish: false
-
-    - task: PublishPipelineArtifact@0
-      displayName: 'Publish Artifact Naming'
-      condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
-      inputs:
-        targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
-        artifactName: 'Jellyfin.Naming'
-
-    - task: PublishPipelineArtifact@0
-      displayName: 'Publish Artifact Controller'
-      condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
-      inputs:
-        targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
-        artifactName: 'Jellyfin.Controller'
-
-    - task: PublishPipelineArtifact@0
-      displayName: 'Publish Artifact Model'
-      condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
-      inputs:
-        targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
-        artifactName: 'Jellyfin.Model'
-
-    - task: PublishPipelineArtifact@0
-      displayName: 'Publish Artifact Common'
-      condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
-      inputs:
-        targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
-        artifactName: 'Jellyfin.Common'
-
-  - job: main_test
-    displayName: Main Test
-    pool:
-      vmImage: windows-latest
-    steps:
-    - checkout: self
-      clean: true
-      submodules: true
-      persistCredentials: false
-
-    - task: DotNetCoreCLI@2
-      displayName: Build
-      inputs:
-        command: build
-        publishWebProjects: false
-        projects: '$(TestProjects)'
-        arguments: '--configuration $(BuildConfiguration)'
-        zipAfterPublish: false
-
-    - task: VisualStudioTestPlatformInstaller@1
-      inputs:
-        packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare
-        versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion
-    - task: VSTest@2
-      inputs:
-        testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun
-        testAssemblyVer2: | # Required when testSelector == TestAssemblies
-          **\bin\$(BuildConfiguration)\**\*tests.dll
-          **\bin\$(BuildConfiguration)\**\*test.dll
-          !**\obj\**
-          !**\xunit.runner.visualstudio.testadapter.dll
-          !**\xunit.runner.visualstudio.dotnetcore.testadapter.dll
-        searchFolder: '$(System.DefaultWorkingDirectory)'
-        runInParallel: True # Optional
-        runTestsInIsolation: True # Optional
-        codeCoverageEnabled: True # Optional
-        configuration: 'Debug' # Optional
-        publishRunAttachments: true # Optional
-        testRunTitle: $(Agent.JobName)
-        otherConsoleOptions: '/platform:x64 /Framework:.NETCoreApp,Version=v3.1 /logger:console;verbosity="normal"'
-
-  - job: main_build_win
-    displayName: Publish Windows
-    pool:
-      vmImage: windows-latest
-    strategy:
-      matrix:
-        Release:
-          BuildConfiguration: Release
-      maxParallel: 2
-    steps:
-    - checkout: self
-      clean: true
-      submodules: true
-      persistCredentials: true
-
-    - task: CmdLine@2
-      displayName: "Clone Web Client (Master, Release, or Tag)"
-      condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
-      inputs:
-        script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
-
-    - task: CmdLine@2
-      displayName: "Clone Web Client (PR)"
-      condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
-      inputs:
-        script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
-
-    - task: NodeTool@0
-      displayName: 'Install Node'
-      condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
-      inputs:
-        versionSpec: '10.x'
-
-    - task: CmdLine@2
-      displayName: "Build Web Client"
-      condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
-      inputs:
-        script: yarn install
-        workingDirectory: $(Agent.TempDirectory)/jellyfin-web
-
-    - task: CopyFiles@2
-      displayName: 'Copy Web Client'
-      condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
-      inputs:
-        sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
-        contents: '**'
-        targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
-        cleanTargetFolder: true # Optional
-        overWrite: true # Optional
-        flattenFolders: false # Optional
-
-    - task: CmdLine@2
-      displayName: 'Clone UX Repository'
-      inputs:
-        script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
-
-    - task: PowerShell@2
-      displayName: 'Build NSIS Installer'
-      inputs:
-        targetType: 'filePath' # Optional. Options: filePath, inline
-        filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
-        arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
-        errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
-        workingDirectory: $(Build.SourcesDirectory) # Optional
-
-    - task: CopyFiles@2
-      displayName: 'Copy NSIS Installer'
-      inputs:
-        sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional
-        contents: 'jellyfin*.exe'
-        targetFolder: $(System.ArtifactsDirectory)/setup
-        cleanTargetFolder: true # Optional
-        overWrite: true # Optional
-        flattenFolders: true # Optional
-
-    - task: PublishPipelineArtifact@0
-      displayName: 'Publish Artifact Setup'
-      condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
-      inputs:
-        targetPath: '$(build.artifactstagingdirectory)/setup'
-        artifactName: 'Jellyfin Server Setup'
-
-  - job: dotnet_compat
-    displayName: Compatibility Check
-    pool:
-      vmImage: ubuntu-latest
-    dependsOn: main_build
-    # only execute for pull requests
-    condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
-    strategy:
-      matrix:
+  - template: azure-pipelines-main.yml
+    parameters:
+      LinuxImage: 'ubuntu-latest'
+      RestoreBuildProjects: $(RestoreBuildProjects)
+
+  - template: azure-pipelines-test.yml
+    parameters:
+      ImageNames: 
+        Linux: 'ubuntu-latest'
+        Windows: 'windows-latest'
+        macOS: 'macos-latest'
+
+  - template: azure-pipelines-windows.yml
+    parameters:
+      WindowsImage: 'windows-latest'
+      TestProjects: $(TestProjects)
+
+  - template: azure-pipelines-compat.yml
+    parameters:
+      Packages: 
         Naming:
           NugetPackageName: Jellyfin.Naming
           AssemblyFileName: Emby.Naming.dll
@@ -253,74 +47,4 @@ jobs:
         Common:
           NugetPackageName: Jellyfin.Common
           AssemblyFileName: MediaBrowser.Common.dll
-      maxParallel: 2
-    steps:
-    - checkout: none
-    
-    - task: UseDotNet@2
-      displayName: 'Update DotNet'
-      inputs:
-        packageType: sdk
-        version: 3.1.100
-
-    - task: DownloadPipelineArtifact@2
-      displayName: 'Download New Assembly Build Artifact'
-      inputs:
-        source: 'current' # Options: current, specific
-        artifact: '$(NugetPackageName)' # Optional
-        path: '$(System.ArtifactsDirectory)/new-artifacts'
-        runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific
-
-    - task: CopyFiles@2
-      displayName: 'Copy New Assembly Build Artifact'
-      inputs:
-        sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
-        contents: '**/*.dll'
-        targetFolder: $(System.ArtifactsDirectory)/new-release
-        cleanTargetFolder: true # Optional
-        overWrite: true # Optional
-        flattenFolders: true # Optional
-
-    - task: DownloadPipelineArtifact@2
-      displayName: 'Download Reference Assembly Build Artifact'
-      inputs:
-        source: 'specific' # Options: current, specific
-        artifact: '$(NugetPackageName)' # Optional
-        path: '$(System.ArtifactsDirectory)/current-artifacts'
-        project: '$(System.TeamProjectId)' # Required when source == Specific
-        pipeline: '$(System.DefinitionId)' # Required when source == Specific
-        runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
-        runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch
-
-    - task: CopyFiles@2
-      displayName: 'Copy Reference Assembly Build Artifact'
-      inputs:
-        sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
-        contents: '**/*.dll'
-        targetFolder: $(System.ArtifactsDirectory)/current-release
-        cleanTargetFolder: true # Optional
-        overWrite: true # Optional
-        flattenFolders: true # Optional
-
-    - task: DownloadGitHubRelease@0
-      displayName: 'Download ABI Compatibility Check Tool'
-      inputs:
-        connection: Jellyfin Release Download
-        userRepository: EraYaN/dotnet-compatibility
-        defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
-        itemPattern: '**-ci.zip' # Optional
-        downloadPath: '$(System.ArtifactsDirectory)'
-
-    - task: ExtractFiles@1
-      displayName: 'Extract ABI Compatibility Check Tool'
-      inputs:
-        archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip'
-        destinationFolder: $(System.ArtifactsDirectory)/tools
-        cleanDestinationFolder: true
-
-    # The `--warnings-only` switch will swallow the return code and not emit any errors.
-    - task: CmdLine@2
-      displayName: 'Execute ABI Compatibility Check Tool'
-      inputs:
-        script: 'dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
-        workingDirectory: $(System.ArtifactsDirectory) # Optional
+      LinuxImage: 'ubuntu-latest'

+ 0 - 46
.ci/publish-nightly.yml

@@ -1,46 +0,0 @@
-name: Nightly-$(date:yyyyMMdd).$(rev:r)
-
-variables:
-  - name: Version
-    value: '1.0.0'
-
-trigger: none
-pr: none
-
-jobs:
-  - job: publish_artifacts_nightly
-    displayName: Publish Artifacts Nightly
-    pool:
-      vmImage: ubuntu-latest
-    steps:
-    - checkout: none
-    - task: DownloadPipelineArtifact@2
-      displayName: Download the Windows Setup Artifact
-      inputs:
-        source: 'specific' # Options: current, specific
-        artifact: 'Jellyfin Server Setup' # Optional
-        path: '$(System.ArtifactsDirectory)/win-installer'
-        project: '$(System.TeamProjectId)' # Required when source == Specific
-        pipelineId: 1 # Required when source == Specific
-        runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
-        runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
-
-    - task: SSH@0
-      displayName: 'Create Drop directory'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_nightly_azure_upload'
-
-    - task: CopyFilesOverSSH@0
-      displayName: 'Copy the Windows Setup to the Repo'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
-        contents: 'jellyfin_*.exe'
-        targetFolder: '/srv/incoming/jellyfin_nightly_azure_upload/win-installer'
-
-    - task: SSH@0
-      displayName: 'Clean up SCP symlink'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        commands: 'rm -f /srv/incoming/jellyfin_nightly_azure_upload'

+ 0 - 48
.ci/publish-release.yml

@@ -1,48 +0,0 @@
-name: Release-$(Version)-$(date:yyyyMMdd).$(rev:r)
-
-variables:
-  - name: Version
-    value: '1.0.0'
-  - name: UsedRunId
-    value: 0
-
-trigger: none
-pr: none
-
-jobs:
-  - job: publish_artifacts_release
-    displayName: Publish Artifacts Release
-    pool:
-      vmImage: ubuntu-latest
-    steps:
-    - checkout: none
-    - task: DownloadPipelineArtifact@2
-      displayName: Download the Windows Setup Artifact
-      inputs:
-        source: 'specific' # Options: current, specific
-        artifact: 'Jellyfin Server Setup' # Optional
-        path: '$(System.ArtifactsDirectory)/win-installer'
-        project: '$(System.TeamProjectId)' # Required when source == Specific
-        pipelineId: 1 # Required when source == Specific
-        runVersion: 'specific' # Required when source == Specific. Options: latest, latestFromBranch, specific
-        runId: $(UsedRunId)
-
-    - task: SSH@0
-      displayName: 'Create Drop directory'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_release_azure_upload'
-
-    - task: CopyFilesOverSSH@0
-      displayName: 'Copy the Windows Setup to the Repo'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
-        contents: 'jellyfin_*.exe'
-        targetFolder: '/srv/incoming/jellyfin_release_azure_upload/win-installer'
-
-    - task: SSH@0
-      displayName: 'Clean up SCP symlink'
-      inputs:
-        sshEndpoint: 'Jellyfin Build Server'
-        commands: 'rm -f /srv/incoming/jellyfin_release_azure_upload'

+ 17 - 0
tests/coverletArgs.runsettings

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<RunSettings>
+  <DataCollectionRunSettings>
+    <DataCollectors>
+      <DataCollector friendlyName="XPlat code coverage">
+        <Configuration>
+          <Format>cobertura</Format>          
+          <Exclude>[coverlet.*.tests?]*,[*]Coverlet.Core*,[*]Moq*</Exclude> <!-- [Assembly-Filter]Type-Filter -->
+          <ExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute</ExcludeByAttribute>
+          <SingleHit>false</SingleHit>
+          <UseSourceLink>true</UseSourceLink>
+          <IncludeTestAssembly>false</IncludeTestAssembly>
+        </Configuration>
+      </DataCollector>
+    </DataCollectors>
+  </DataCollectionRunSettings>
+</RunSettings>