Pipeline and Artifacts Application: Override C# Project Property Information
In the previous article, when the nuget package was uploaded to the Artifact feed through the Pipeline, the resulting version had been uploaded before, so the final execution of the Pipeline failed. In order to solve this problem, it is hoped that the version information can be automatically overwritten when the Pipeline is executed, and the BuildId or BuildNumber of the Pipeline is used to represent the version of the Package. Therefore, in the previous article , I also installed " . Net Standard Project Property Reader and Writer "Extension, let's continue this article with the failed Yaml file content in the previous article !
# Starter pipeline # Start with a minimal pipeline that you can customize to build and deploy your code. # Add steps that build, run tests, deploy, and more: # https://aka.ms/yaml trigger: - none pool: vmImage: ubuntu-latest steps: - task: DotNetCoreCLI@2 displayName: Build C# Project inputs: command: 'build' projects: '$(ProjectName)/*.csproj' arguments: '-o $(Build.BinariesDirectory)' - task: ArchiveFiles@2 displayName: Zip output files inputs: rootFolderOrFile: '$(Build.BinariesDirectory)' includeRootFolder: false archiveType: 'zip' archiveFile: '$(Build.ArtifactStagingDirectory)/$(ProjectName)-$(Build.BuildId).zip' replaceExistingArchive: true - task: PublishPipelineArtifact@1 displayName: Publish files to pipeline artifacts inputs: targetPath: '$(Build.ArtifactStagingDirectory)' artifact: 'BuildOutputFiles' publishLocation: 'pipeline' - task: DotNetCoreCLI@2 displayName: Push Nuget package inputs: command: 'push' packagesToPush: '$(Build.BinariesDirectory)/*.nupkg' nuGetFeedType: 'internal' publishVstsFeed: '15523482-96ea-4d9f-83bf-a57fc10e79ce'
First, let's take a look at the contents of the ModuleBase.csproj file:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <Version>1.0.1</Version> </PropertyGroup> </Project>
From the above XML content, you can see that the version is set to the node named Version; in addition, the setting of generating nupkg by the way when setting the Build project in the previous article is set to true in the GeneratePackageOnBuild node.
Next we find the PowerShell task from the Task list:
Select Inline in the Type section, then enter cat $(ProjectName)/$(ProjectName).csproj in the Script content section, and output a copy of the csproj file content at the beginning of the Pipeline execution, so this task will Insert below steps as the first task:
The reason for this is that you can see the difference in file content before and after the version is modified, which is also a log method.
Next, find the following Project Property To Environment Variable task from the Task list:
The property setting of the first Path to csproj/vbproj file is also set to $(ProjectName)/$(ProjectName).csproj, the second Variable Prefix is set to Proj, and the third one is to select the Version option:
The above Task setting will read the Version content in the csproj file and put it in $(Proj.Version) for subsequent use.
After reading the Version information, find the Write Project Property Task from the Task list:
Comparing the Task settings for reading the Version content above, the only difference is that the second property is set to $(Proj.Version).$(Build.BuildId), which means adding the BuildId of the Pipeline to the Version information just read. , so that the version of the archive generated by each execution of Pipeline will be different:
Then copy the previous PowerShell task and insert it after the ProjectVarWriter task, so that you can see what is written.
In addition, the Task of this Extension can only execute the Agent in the Windows environment, so the above vmImage setting must be changed to windows-latest. The final Yaml content is as follows:
# Starter pipeline # Start with a minimal pipeline that you can customize to build and deploy your code. # Add steps that build, run tests, deploy, and more: # https://aka.ms/yaml trigger: - none pool: vmImage: windows-latest steps: - task: PowerShell@2 displayName: Print csproj content inputs: targetType: 'inline' script: 'cat $(ProjectName)/$(ProjectName).csproj' - task: ProjectVarReader@0 displayName: Read version value inputs: searchPattern: '$(ProjectName)/$(ProjectName).csproj' variablePrefix: 'Proj' propertyName: 'Version' - task: ProjectVarWriter@0 displayName: Writer version value inputs: searchPattern: '$(ProjectName)/$(ProjectName).csproj' value: '$(Proj.Version).$(Build.BuildId)' propertyName: 'Version' - task: PowerShell@2 displayName: Print csproj content inputs: targetType: 'inline' script: 'cat $(ProjectName)/$(ProjectName).csproj' - task: DotNetCoreCLI@2 displayName: Build C# Project inputs: command: 'build' projects: '$(ProjectName)/$(ProjectName).csproj' arguments: '-o $(Build.BinariesDirectory)' - task: ArchiveFiles@2 displayName: Zip output files inputs: rootFolderOrFile: '$(Build.BinariesDirectory)' includeRootFolder: false archiveType: 'zip' archiveFile: '$(Build.ArtifactStagingDirectory)/$(ProjectName)-$(Build.BuildId).zip' replaceExistingArchive: true - task: PublishPipelineArtifact@1 displayName: Publish files to pipeline artifacts inputs: targetPath: '$(Build.ArtifactStagingDirectory)' artifact: 'BuildOutputFiles' publishLocation: 'pipeline' - task: DotNetCoreCLI@2 displayName: Push Nuget package inputs: command: 'push' packagesToPush: '$(Build.BinariesDirectory)/*.nupkg' nuGetFeedType: 'internal' publishVstsFeed: '15523482-96ea-4d9f-83bf-a57fc10e79ce'
From the log executed by Pipeline, you can see that the Version in the first PowerShell task log is different from the Version in the second PowerShell task log, and the Version value printed in the second one is 1.0.1.31:
The version 1.0.1.31 can also be seen from the Artifact feed (Private nuget):
From the drop-down menu below, you can see that GeneratePackageOnBuild is also in the options, so you can also set GeneratePackageOnBuild=true from the Pipeline in this way, because this option is not specially set in the C# Project settings created by default:
In addition, there is a Custom option, you can fill in the Property name to be set by yourself, you can set the properties of Authors and Description in this way, because these property values are also used in the nuget package, you can also use these Set to Variable to let the user who executes the Pipeline enter these. The actual method depends on the team's habits, but generally speaking, these content that will not change should also be written in the C# project settings.
The above steps will not be a problem if the Version value is already set in the C# Project file, but generally, if the newly created project does not specifically set the version number, in fact, there will be no Version content in csproj. That is, if the Version value is not set, the system will default to 1.0.0 during Build, but…
The above Task actually reads the Version content because the Version is not in the file content, so it will be empty in Proj.Version!
This is terrible... Then the version written will become something like ".31"? What is this weird version number format...
In order to avoid this problem, we need to set another preset value variable and add the following paragraph before steps in Yaml:
variables: version: '$(Proj.Version).$(Build.BuildId)' predefined_version: '1.0.0.$(Build.BuildId)'
Added two variables, version and predefined_version, and changed the value property in the Task setting that writes the Version value from '$(Proj.Version).$(Build.BuildId)' to '$(version)'.
The next thing to do is to judge whether the Version value read by ProjectVarReader is empty, that is, to judge whether the content of Proj.Version is empty, if it is empty, then set the content of the predefined_version variable to the version variable, This part uses the PowerShell task, and the yaml content is as follows (insert before Writer):
- task: PowerShell@2 displayName: Check version value inputs: targetType: 'inline' script: | echo "Proj.Version = $env:Proj_Version" if ([string]::IsNullOrWhiteSpace($env:Proj_Version)) { echo '##vso[task.setvariable variable=version]$(predefined_version)' }
What I want to mention here is the technique of setting variable content in Pipeline according to different situations, that is, outputting a special string format " ##vso[task.setvariable variable=variable name] set value " through PowerShell's echo, variable name It is the variable set in variables. The part of the set value can use "$(variable name)" to get the value from other variables. In this way, the content of the variable version can be changed to the content set by predefined_version . (Note: In PowerShell, the set variable value is obtained through the environment variable, and the dot ( . ) will be replaced by the bottom line ( _ ). For details, please refer to the official document .)
The content of the final completed Yaml file:
# Starter pipeline # Start with a minimal pipeline that you can customize to build and deploy your code. # Add steps that build, run tests, deploy, and more: # https://aka.ms/yaml trigger: - none pool: vmImage: windows-latest variables: version: '$(Proj.Version).$(Build.BuildId)' predefined_version: '1.0.0.$(Build.BuildId)' steps: - task: PowerShell@2 displayName: Print csproj content inputs: targetType: 'inline' script: 'cat $(ProjectName)/$(ProjectName).csproj' - task: ProjectVarReader@0 displayName: Read version value inputs: searchPattern: '$(ProjectName)/$(ProjectName).csproj' variablePrefix: 'Proj' propertyName: 'Version' - task: PowerShell@2 displayName: Check version value inputs: targetType: 'inline' script: | echo "Proj.Version = $env:Proj_Version" if ([string]::IsNullOrWhiteSpace($env:Proj_Version)) { echo '##vso[task.setvariable variable=version]$(predefined_version)' } - task: ProjectVarWriter@0 displayName: Writer version value inputs: searchPattern: '$(ProjectName)/$(ProjectName).csproj' value: '$(version)' propertyName: 'Version' - task: ProjectVarWriter@0 displayName: Set GeneratePackageOnBuild = true inputs: searchPattern: '$(ProjectName)/$(ProjectName).csproj' value: 'true' propertyName: 'GeneratePackageOnBuild' - task: PowerShell@2 displayName: Print csproj content inputs: targetType: 'inline' script: 'cat $(ProjectName)/$(ProjectName).csproj' - task: DotNetCoreCLI@2 displayName: Build C# Project inputs: command: 'build' projects: '$(ProjectName)/$(ProjectName).csproj' arguments: '-o $(Build.BinariesDirectory)' - task: ArchiveFiles@2 displayName: Zip output files inputs: rootFolderOrFile: '$(Build.BinariesDirectory)' includeRootFolder: false archiveType: 'zip' archiveFile: '$(Build.ArtifactStagingDirectory)/$(ProjectName)-$(Build.BuildId).zip' replaceExistingArchive: true - task: PublishPipelineArtifact@1 displayName: Publish files to pipeline artifacts inputs: targetPath: '$(Build.ArtifactStagingDirectory)' artifact: 'BuildOutputFiles' publishLocation: 'pipeline' - task: DotNetCoreCLI@2 displayName: Push Nuget package inputs: command: 'push' packagesToPush: '$(Build.BinariesDirectory)/*.nupkg' nuGetFeedType: 'internal' publishVstsFeed: '15523482-96ea-4d9f-83bf-a57fc10e79ce'
Original link Where is Tek going?
Like my work? Don't forget to support and clap, let me know that you are with me on the road of creation. Keep this enthusiasm together!