skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    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.ConsoleAppbin$(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)'
    

  2. 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.namebin$(BuildPlatform)$(BuildConfiguration)
      otherbuildpath: referenced.projectbin$(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)'
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search