Jeremy
Jeremy

Reputation: 185

Building a multi-application .NET Framework Visual Studio solution in YAML pipeline

I am using Azure DevOps pipelines to build and deploy my applications. So far, I have only worked with single-projects that open in Visual Studio Code. Now I am trying to create builds for older .NET Framework Visual Studio solutions. The way we have always organized our Visual Studio Solutions is having a .Core project (referenced by each of the other application projects in the solution), and then one project per application (i.e., a Web app, one or more Console apps, and possibly some Windows Services). When I use VSBuild@1 to build my solution, it seems to build all projects in the solution, but it only seems to publish artifacts for the Web app project. Does anyone know the best practice for dealing with multi-application solutions such as mine in YAML pipelines? Basically, I want the build to generate an artifact for each application.

Upvotes: 1

Views: 1806

Answers (2)

Jeremy
Jeremy

Reputation: 185

I ended up using separate MsBuild steps to build each project. Below is the complete pipeline I have so far, which will generate an artifact for my Web project, as well as an artifact for my ConsoleApp project. Any feedback as to any other issues is greatly appreciated. Note that I use a custom build number and I did not clean up the variables and/or their usage.

#-------------------------------------------------#
# PIPELINE IS TRIGGERED FOR ANY BRANCH            #
# THIS IS THE DEFAULT IF NO TRIGGER IS SPECIFIED, #
# BUT I WANT TO MAKE IT VERY CLEAR.               #
#-------------------------------------------------#
trigger:
  batch: true # we only want to run this pipeline one at a time in order
  branches:
    include:
      - '*'

name: 'Set dynamically below in a task'

#--------------------------------------------------------------------#
# THIS ALLOWS US TO DETERMINE IF IT IS MASTER BRANCH OR A DEV BRANCH #
#--------------------------------------------------------------------#

variables:
  isMaster: $[eq(variables['Build.SourceBranchName'], 'master')] # runtime 
expression
  version.MajorMinor: '1.0' # Manually adjust the version number as needed for semantic versioning. Patch is auto-incremented.
  version.Patch: $[counter(variables['version.MajorMinor'], 0)] # Reset the patch number every time the MajorMinor changes
  versionNumber: '$(version.MajorMinor).$(version.Patch)'
  solution: '**/*.sln'
  buildPlatform: 'Any CPU'
  buildConfiguration: 'Release'
  buildPathFilter: '**\!(*.pdb|*.xml|*.manifest|*.host|*.vshost.exe)'

pool:
  vmImage: 'windows-2019' # Can't use newer version because solution still targets .NET Framework 4.6.1 which is deprecated

stages:

- stage: BUILD
  jobs:

  #----------------#
  # SET BUILD NAME #
  #----------------#

  - job: Set_Build_Name
    steps:
    - task: PowerShell@2
      displayName: Set Build Name
      inputs:
        targetType: 'inline'
        script: |
          [string] $buildName = "$(versionNumber)_$(Build.SourceBranchName)"
          Write-Host "Setting the name of the build to '$buildName'."
          Write-Host "##vso[build.updatebuildnumber]$buildName"

  #-------------------------------#
  # BUILD: Build & Run Unit Tests #
  #-------------------------------#

  - job: Build_and_Run_Unit_Tests
    dependsOn: Set_Build_Name
    steps:
    - task: NuGetToolInstaller@1
      displayName: Install NuGet
    - task: NuGetCommand@2
      displayName: Restore NuGet Packages
      inputs:
        restoreSolution: '**/*.sln'
    - task: MSBuild@1
      displayName: Build Unit Tests
      inputs:
        solution: '**/MySolution.UnitTests/MySolution.UnitTests.csproj'
        msbuildVersion: '16.0'
        platform: 'AnyCPU'
        configuration: 'Release'
        clean: true
    - task: VSTest@2
      displayName: Run Unit Tests
      inputs:
        platform: '$(buildPlatform)'
        configuration: '$(buildConfiguration)'

  #------------#
  # BUILD: Web #
  #------------#

  - job: Build_Web
    dependsOn: Build_and_Run_Unit_Tests
    steps:
    - task: NuGetToolInstaller@1
      displayName: Install NuGet
    - task: NuGetCommand@2
      displayName: Restore NuGet Packages
      inputs:
        restoreSolution: '**/*.sln'
    - task: MSBuild@1
      displayName: Build Project
      inputs:
        solution: '**/MySolution.Web/MySolution.Web.csproj'
        msbuildVersion: '16.0'
        platform: 'AnyCPU'
        configuration: 'Release'
        msbuildArguments: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)"'
        clean: true
    - task: PublishBuildArtifacts@1
      displayName: Publish Artifact
      continueOnError: false
      inputs:
        ArtifactName: 'MySolution.Web-$(Build.BuildNumber)'
        TargetPath: '$(Build.ArtifactStagingDirectory)'

  #------------------------------#
  # BUILD: MySolution.ConsoleApp #
  #------------------------------#

  - job: Build_Console_App
    dependsOn: Build_and_Run_Unit_Tests
    steps:
    - task: NuGetToolInstaller@1
      displayName: Install NuGet
    - task: NuGetCommand@2
      displayName: Restore NuGet Packages
      inputs:
        restoreSolution: '**/*.sln'
    - task: MSBuild@1
      displayName: Build Project
      inputs:
        solution: '**/MySolution.ConsoleApp/MySolution.ConsoleApp.csproj'
        msbuildVersion: '16.0'
        platform: 'AnyCPU'
        configuration: 'Release'
        clean: true
    - task: CopyFiles@2
      displayName: Copy to Artifact Staging Dir
      inputs:
        SourceFolder: '$(Build.SourcesDirectory)\$(Build.Repository.Name)\MySolution.ConsoleApp\bin\$(BuildConfiguration)'
        Contents: '$(buildPathFilter)'
        TargetFolder: '$(Build.ArtifactStagingDirectory)\MySolution.ConsoleApp'
        CleanTargetFolder: true
        OverWrite: true
        flattenFolders: false
    - task: PublishBuildArtifacts@1
      displayName: Publish Artifact
      continueOnError: false
      inputs:
        ArtifactName: 'MySolution.ConsoleApp-$(Build.BuildNumber)'
        TargetPath: '$(Build.ArtifactStagingDirectory)'

Upvotes: 0

promicro
promicro

Reputation: 1646

Julie explains a some what similar situation on her blog:

https://julie.io/writing/monorepo-pipelines-in-azure-devops/

The repos I have come across with multi project solutions and artifacts always have multiple yamls in them.

Furthermore publishing and using these artifacts are more a architectural choice with several options found here.

Edit

After reading your comment a manual solution could be copying the builds from every referenced project (in the example only one), but you are looking for a more automated way I guess:

variables:
  BuildPlatform: 'Any CPU'
  BuildConfiguration: Release
  SolutionFile: app-name.sln
  BuildPathFilter: '**\!(*.pdb|*.xml|*.manifest|*.host|*.vshost.exe)'
  BuildPath: app.name\bin\$(BuildPlatform)\$(BuildConfiguration)
  otherbuildpath: referenced.project\bin\$(BuildPlatform)\$(BuildConfiguration)
  System.Debug: false
trigger:
  branches:
    include:
    - master

steps:
- task: VSBuild@1
  displayName: 'Build solution $(SolutionFile)'
  inputs:
    solution: '$(Build.Repository.Name)\$(SolutionFile)'
    vsVersion: latest
    platform: '$(BuildPlatform)'
    configuration: '$(BuildConfiguration)'
    clean: true
  condition: succeededOrFailed()

- task: CopyFiles@2
  displayName: 'Copy Files from buildpath to: $(Build.ArtifactStagingDirectory)'
  inputs:
    SourceFolder: '$(Build.SourcesDirectory)\$(Build.Repository.Name)\$(BuildPath)'
    Contents: '$(BuildPathFilter)'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'
    CleanTargetFolder: true
    OverWrite: true
    flattenFolders: false

- task: CopyFiles@2
  displayName: 'Copy Files from otherProjectBuildpath to: $(Build.ArtifactStagingDirectory)'
  inputs:
    SourceFolder: '$(Build.SourcesDirectory)\$(Build.Repository.Name)\$(otherBuildPath)'
    Contents: '$(BuildPathFilter)'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'
    CleanTargetFolder: true
    OverWrite: true
    flattenFolders: false

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: $(Build.BuildNumber)'
  inputs:
    ArtifactName: '$(Build.BuildNumber)'

Upvotes: 1

Related Questions