How do I auto-increment nuget package version using VSTS build process? - c#

In this video from MSDN at the 3:34 second mark, the presenter shows how to append the Build ID to a nuget's version. In the MSBuild arguments, he specifies:
/t:pack /p:PackageVersion=1.0.$(Build.BuildId)
So, when the project is built by VSTS, the nuget assembly's revision number is using the build id.
I would like to do something similar. Instead of hard coding the 1.0 in the build definition, I'd like to retrieve that from .csproj file. I am using the new .csproj file which stores nuget information.
For example, I'd like to specify in the csproj:
<Version>0.0.1-beta</Version>
Then, VSTS would append the BuildID and generate the assembly version as 0.0.0.1-beta.49 (49 being the build id)

I ended up doing the opposite of what Shayki Abramczyk suggested.
I use a task called "Variables Task Pack". It can be found here (and is free at the time of this answer): https://marketplace.visualstudio.com/items?itemName=YodLabs.VariableTasks#qna
Using this task, I set two variable: $(BuildId) and $(ReleaseType). See the settings snapshots at the end of the answer.
Then, in my CSPROJ project file, I modified the nuget version to use the two environment variables. Here's a clip of the project file:
<PropertyGroup>
<Version>0.0.0.0$(BuildId)$(ReleaseType)</Version>
<FileVersion>0.0.0.0$(BuildId)$(ReleaseType)</FileVersion>
...
</PropertyGroup>
IMPORTANT: Notice the extra 0 in front of $(BuildId). I had to add that in order to build locally. Without it, the build failed with an incorrect version format error.
Now, after building, I get the buildid as my revision number and release type appended.
Here are the screen shots showing configuration of both variables.

You can create a Power Shell script that retrieves the version from csproj file, then add the version to a new environment variable with this command: Set-VstsTaskVariable
For Example:
$csprojId = $retrivedIdfromTheFile
Set-VstsTaskVariable -Name "CSPROJ_ID" -Value $csprojId
Now you can use the CSPROJ_ID variable on the MSBuild arguments:
/p:PackageVersion=$(CSPROJ_ID).$(Build.BuildId)

Related

How to public sign a project with MSBuild

I have a C# .NET Framework project which I sign using a .pfx file on my local system. In the .csproj file the following properties are set:
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>keyfile.pfx</AssemblyOriginatorKeyFile>
</PropertyGroup>
This file is part of an open source project. I'm trying to create a pipeline which runs on certain commits. When I commit something, the original *.pfx can be installed using the password which is stored as a GitHub secret. The problem is that when forked repos make a pull request, the pipeline fails, because the GitHub secrets are not available to them.
Long story short, I found out that I can use public signing for this purpose. However, when I try to build my project using
msbuild src\TcBlackCore\TcBlackCore.csproj -t:Rebuild -p:DelaySign=false -p:PublicSign=true -p:Configuration=Release -p:Platform=AnyCPU -p:TreatWarningsAsErrors=true
I get the following error:
CS7102: Compilation options 'PublicSign' and 'CryptoKeyContainer' can't both be specified at the same time
I couldn't find any information on this error code except for one GitHub issue which mentions:
ERR_MutuallyExclusiveOptions CS7102
What are the mutually exclusive options here? The DelaySign should be set to the docs. And what should I do to get the public signing to work such that forked repos can be build?
> msbuild -version
Microsoft (R) Build Engine version 15.9.21+g9802d43bc3 for .NET Framework
The problem with public sign is that while the compiler applies a public key, is doesn't actually signs the assembly. It is like a 'mark' on the assembly.
In your case, you cannot use both CryptoKeYContainer and PublicSign at the same time because they are doing almost identical things, thus the compiler prompts the error.
for your purpose I would recommend using the DelaySign - as Microsoft docs states:
Use DelaySign - If you want a fully signed assembly. Use DelaySign
if you only want to place the public key in the assembly
For reference the tag is:
<DelaySign>true</DelaySign>

dotnet cli can't detect installed package?

I was following https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-3.1&tabs=netcore-cli with dotnet cli on alpine-linux. After executing
dotnet aspnet-codegenerator identity -dc WebApp1.Data.ApplicationDbContext --files "Account.Register;Account.Login;Account.Logout;Account.RegisterConfirmation"
Got following error(s):
Building project ...
Finding the generator 'identity'...
Running the generator 'identity'...
The given key '"Account.Register' was not present in the dictionary.
at Microsoft.VisualStudio.Web.CodeGeneration.ActionInvoker.<BuildCommandLine>b__6_0()
at Microsoft.Extensions.CommandLineUtils.CommandLineApplication.Execute(String[] args)
at Microsoft.VisualStudio.Web.CodeGeneration.ActionInvoker.Execute(String[] args)
at Microsoft.VisualStudio.Web.CodeGeneration.CodeGenCommand.Execute(String[] args)
RunTime 00:00:04.45
Account.Login: No such file or directory
Account.Logout: No such file or directory
Account.RegisterConfirmation": No such file or directory
Then I tried
dotnet aspnet-codegenerator --help
ond got
Usage: aspnet-codegenerator [arguments] [options]
Arguments:
generator Name of the generator. Check available generators below.
Options:
-p|--project Path to .csproj file in the project.
-n|--nuget-package-dir
-c|--configuration Configuration for the project (Possible values: Debug/ Release)
-tfm|--target-framework Target Framework to use. (Short folder name of the tfm. eg. net46)
-b|--build-base-path
--no-build
No code generators are available in this project.Add Microsoft.VisualStudio.Web.CodeGeneration.Design package to the project as a NuGet package reference.
RunTime 00:00:00.90
Then I ran
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
which ran successfully. I confirmed that by looking at my WebApp1.csproj file, which indeed contained the following line
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.4" />
But running above mentioned erroneous commands yielded same output. As a bonus, I ran
dotnet aspnet-codegenerator identity --help
and got following output
Usage: aspnet-codegenerator [arguments] [options]
Arguments:
generator Name of the generator. Check available generators below.
Options:
-p|--project Path to .csproj file in the project.
-n|--nuget-package-dir
-c|--configuration Configuration for the project (Possible values: Debug/ Release)
-tfm|--target-framework Target Framework to use. (Short folder name of the tfm. eg. net46)
-b|--build-base-path
--no-build
Selected Code Generator: identity
No code generator found with the name 'identity'.
No code generators are available in this project.Add Microsoft.VisualStudio.Web.CodeGeneration.Design package to the project as a NuGet package reference.
RunTime 00:00:00.90
unfortunately, my skills of googling could not scaffold up with bugginess and overall mess of this most loved framework, and what better place to seek solutions, than the community which rated it as the most loved framework in the first place.
Update:
As noted in the first comment, dotnet cli(or maybe my shell) behaved weirdly with double quotes, so I tried running(with single quotes this time)
dotnet aspnet-codegenerator identity -dc WebApp1.Data.ApplicationDbContext --files 'Account.Register;Account.Login;Account.Logout;Account.RegisterConfirmation'
and got this trimmed down version of the first cited error:
Building project ...
Finding the generator 'identity'...
Running the generator 'identity'...
A file matching the name Account.Register.cs.cshtml was not found within any of the folders:
at Microsoft.VisualStudio.Web.CodeGeneration.ActionInvoker.<BuildCommandLine>b__6_0()
at Microsoft.Extensions.CommandLineUtils.CommandLineApplication.Execute(String[] args)
at Microsoft.VisualStudio.Web.CodeGeneration.ActionInvoker.Execute(String[] args)
at Microsoft.VisualStudio.Web.CodeGeneration.CodeGenCommand.Execute(String[] args)
RunTime 00:00:04.57
while running same command with no quotes at all resulted in practically the same error as with double quotes.
Curiously, the line A file matching the name Account.Register.cs.cshtml was not found within any of the folders: may appear at the end of the message on some runs, indicating that the 'folders' which were being searched for Account.Register.cs.cshtml file is either null or empty string and not all of that at...at...at... unhelpful stuff.
Another update: shell edition
All the commands discussed above were run with rc shell from plan9port. When run with posix shell like ash, running with double quotes yields same output as with single quotes with rc, while running with single quotes eliminates A file matching the name Account.Register.cs.cshtml was not found within any of the folders: message. Running with no quotes at all results in same output from dotnet command as with single quotes, but obviously posix shell picks up those semicolons as end of command and tries to execute nonsense.

Automatically signing ALL compiled output files

I have a .net-core 3.1 project (Microsoft.NET.Sdk.Web) in gitlab, and am setting up a CI/CD pipeline for it. As the final step, I want to have all the .dll and .exe output files signed by our key. I can do this by manually putting in some 'signtool' commands as a part of the gitlab-ci.yml file, however that requires going into each project and manually tweaking the file to reflect the specifics of that project.
On the other hand, I have been able to add a generic target in the .csproj file which calls signtool as an exec/command on $(TargetDir)$(TargetFileName). This works to a degree - but it doesn't sign everything. In this case, it signs (e.g.) cthulhu.dll, but not also cthulhu.Views.dll (both of which are shown in the CLI output of 'dotnet msbuild') and there's also a cthulhu.exe that is generated but is not shown in the msbuild output and also not signed.
The csproj file has this as the target (tried running both after rebuild and after publish):
<Target Name="SignAssembly" AfterTargets="Rebuild">
<Message Text="Signing assembly '$(TargetDir)$(TargetFileName)'" Importance="high" />
<Exec Command="signtool sign /f "mykey.pfx" /p "snip" /tr http://timestamp.digicert.com "$(TargetDir)$(TargetFileName)"" />
And the gitlab-ci.yml file looks like this (I'm aware that I'm using the sdk docker container, but it seems to let me support .net-core builds anyway, whereas there is a bug preventing the specific .net-core container from working):
image: mcr.microsoft.com/dotnet/framework/sdk:4.8
stages:
- build
- release
build:
stage: build
script:
- 'dotnet build -r win-x64'
release:
stage: release
script:
- 'dotnet add package signtool --version 10.0.17763.132'
- 'dotnet msbuild cthulhu.csproj /t:"Restore;Rebuild;SignAssembly;Publish" /p:SelfContained=True /p:PublishProtocol=FileSystem /p:Configuration=Release /p:Platform=x64 /p:TargetFrameworks=netcoreapp3.1 /p:PublishDir=bin\Release\netcoreapp3.1\publish\win-x64 /p:RuntimeIdentifier=win-x64 /p:PublishReadyToRun=False /p:PublishTrimmed=True'
artifacts:
name: "$CI_JOB_STAGE-$CI_COMMIT_REF_NAME"
paths:
- '.\bin\Release\netcoreapp3.1\publish\win-x64'
Why not use standard tools - Directory.Build.props?
However, now you can add a new property to every project in one step
by defining it in a single file called Directory.Build.props in the
root folder that contains your source. When MSBuild runs,
Microsoft.Common.props searches your directory structure for the
Directory.Build.props file (and Microsoft.Common.targets looks for
Directory.Build.targets). If it finds one, it imports the property.
Directory.Build.props is a user-defined file that provides
customizations to projects under a directory
Indicates that the assembly should be signed:
<SignAssembly>true</SignAssembly>
The key is specified using the following property:
<AssemblyOriginatorKeyFile>key.pfx</AssemblyOriginatorKeyFile>
That is, as a result, these properties will be imported into the projects and you will achieve the desired result.

C#/.NET - How to generate and increase package version automatically especially via CI?

I have a Visual Studio project which is built as a NuGet lib package. But every time I publish the package, I have to change the version number manually. That's a prone-to-error work.
I'd like to generate and increase the package version number automatically.
I found GitVersion tool to solve this problem. And I also found some semantic versioning blogs to explain the package version of continuous delivery.
GitTools/GitVersion: Easy Semantic Versioning (http://semver.org) for projects using Git
GitVersion Documentation
Versioning NuGet packages in a continuous delivery world: part 1 – Microsoft DevOps Blog
Versioning NuGet packages in a continuous delivery world: part 2 – Microsoft DevOps Blog
Versioning NuGet packages in a continuous delivery world: part 3 – Microsoft DevOps Blog
But unfortunately, The GitVersion package does not work correctly for me.
It gives me an error that AssemblyVersionAttribute is duplicated.
If I add <GenerateAssemblyInfo>false</GenerateAssemblyInfo> into the csproj file, It will do nothing and the package version will be 0.0.0.0.
Maybe the reason is that I'm using the new csproj format. See here to view the csproj file and the file looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net45;net47;netstandard2.0</TargetFrameworks>
</PropertyGroup>
</Project>
Any reply is appreciated.
UPDATE:
I find that there is an issue to mention my problem: Gitversion Task for VS2017-style csproj · Issue #1349 · GitTools/GitVersion. I'm trying the new solution.
Not sure about Jenkins, but it should be able to generate an incremental number or timestamp by itself that can be accessed via an environment variable on your build pipeline.
I think the most flexible way is to add a PackageVersion tag with a placeholder to your csproj that your build pipeline can then change:
<PropertyGroup>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageVersion>$(PackageVersion)</PackageVersion>
</PropertyGroup>
So, on your build pipeline, you just pass the version, for example:
dotnet build -p:PackageVersion=$(BUILD_TIMESTAMP)
We can trigger the GitHub Action by Git tag pushed and we can read the Git tag name as the version. And then we can generate the NuGet package with this version.
There is a dotnet tool that can read Git tags as a version and write it to the version file.
Before using it, we should create the version file and import the version file.
We should use dotnet to install the dotnetCampus.TagToVersion tool and use the tool to write the Git tag to version file.
The step 1:
Adding the Directory.Build.props file to repo folder.
Writing the code to the Directory.Build.props file.
<Project>
<Import Project="build\Version.props" />
</Project>
The step 2:
Making a folder named build and adding the Version.props file to this folder.
Writing the code to the build\Version.props file.
<Project>
<PropertyGroup>
<Version>1.0.5</Version>
</PropertyGroup>
</Project>
The step 3:
Writing a GitHub Action configuration file in .github\workflows folder, for example create the .github\workflows\push tag and pack nuget.yml file
Making the Action trigger by tag push.
on:
push:
tags:
- '*'
Writing the tag as version by dotnet tool.
- name: Install dotnet tool
run: dotnet tool install -g dotnetCampus.TagToVersion
- name: Set tag to version
run: dotnet TagToVersion -t ${{ github.ref }}
Building the package
# Build and publish
- name: Build with dotnet
run: dotnet build --configuration Release
- name: Install Nuget
uses: nuget/setup-nuget#v1
with:
nuget-version: '5.x'
- name: Add private GitHub registry to NuGet
run: |
nuget sources add -name github -Source https://nuget.pkg.github.com/ORGANIZATION_NAME/index.json -Username ORGANIZATION_NAME -Password ${{ secrets.GITHUB_TOKEN }}
- name: Push generated package to GitHub registry
run: |
nuget push .\bin\release\*.nupkg -Source github -SkipDuplicate
nuget push .\bin\release\*.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -ApiKey ${{ secrets.NugetKey }} -NoSymbols
See https://github.com/dotnet-campus/dotnetCampus.TagToVersion
Actually GitVersionTask is not hard to use. All that you should do is these things below:
Install GitVersionTask from NuGet.
Add a configuration file named GitVersion.yml with some key-values.
Add a tag to your branch.
Build.
After doing that, you can find your output dll file contains a semantic version.
I'm answering my own question because I just wrote the wrong config file name. So it did not work correctly.
This is my configuration file:
mode: ContinuousDelivery
increment: Inherit
tag-prefix: '[vV]'
source-branches: ['master', 'develop', 'hotfix']
branches:
master:
regex: master$
mode: ContinuousDelivery
tag: ''
increment: Patch
prevent-increment-of-merged-branch-version: true
track-merge-target: false
tracks-release-branches: false
is-release-branch: true
release:
regex: r(elease$|(eleases)?[-/])
mode: ContinuousDelivery
tag: beta
increment: Patch
prevent-increment-of-merged-branch-version: true
track-merge-target: false
tracks-release-branches: false
is-release-branch: true
feature:
regex: f(eatures)?[-/]
mode: ContinuousDeployment
tag: alpha
increment: Minor
prevent-increment-of-merged-branch-version: false
track-merge-target: false
tracks-release-branches: false
is-release-branch: false
I've posted this configuration file content here: Automatically increase the semantic version using GitVersion - walterlv

TeamCity wont create release folder

When using TeamCity and running a build I get the following error message.
13:20:31]Step 1/1: MSBuild (1s)
[13:20:32][Step 1/1] src\DystopiaOnline.proj.teamcity: Build target: BuildSolution
[13:20:32][src\DystopiaOnline.proj.teamcity] BuildSolution
[13:20:32][BuildSolution] C:\TeamCity\buildAgent\work\8c8eb5050252f271\src\DystopiaOnline.proj(36, 5): error MSB4062: The "DystopiaOnline.Build.Tasks.GetUnixTimestamp" task could not be loaded from the assembly C:\TeamCity\buildAgent\work\8c8eb5050252f271\src\DystopiaOnline.Build.Tasks/bin/Release/DystopiaOnline.Build.Tasks.dll. Could not load file or assembly 'file:///C:\TeamCity\buildAgent\work\8c8eb5050252f271\src\DystopiaOnline.Build.Tasks\bin\Release\DystopiaOnline.Build.Tasks.dll' or one of its dependencies. The system cannot find the file specified. Confirm that the <UsingTask> declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask.
[13:20:32][Step 1/1] Step MSBuild failed
However when I run the build from the developer command prompt the build works fine. Taking a look inside the C:\TeamCity\buildAgent\work\8c8eb5050252f271\src\D ystopiaOnline.Build.Tasks\bin only shows a Debug folder rather than a Release folder. Running the build from the developer command prompt works ok just building from Team City doesn't.
shouldn't the Release folder be created when the build is run from Team City with the Env variable set to prod? what could be causing this? Anyone any ideas?
In my project solution .proj file have the following conditions set to determine a build configuration. can anyone with any experience working with team city offer any advice as to what the problem may be? thanks.
<PropertyGroup>
<Env Condition="'$(Env)' == ''">dev</Env>
<VersionNumber Condition="'$(VersionNumber)' == ''">1</VersionNumber>
<MSBuildCommunityTasksPath>$(MSBuildThisFileDirectory)/Tasks</MSBuildCommunityTasksPath>
<UnityPath Condition="'$(UnityPath)' == ''">c:\Program Files (x86)\Unity</UnityPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Env)' == 'dev'">
<BuildConfig>Debug</BuildConfig>
<Domain>mmo.dystopiaOnline.dev</Domain>
<SetParamsFile>Parameters.Local.config</SetParamsFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Env)' == 'prod'">
<BuildConfig>Release</BuildConfig>
<Domain>mmo.DystopiaOnline.com</Domain>
<SetParamsFile>Parameters.Production.config</SetParamsFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Env)' == 'sta'">
<BuildConfig>Release</BuildConfig>
<Domain>mmo.DystopiaOnline.sta</Domain>
<SetParamsFile>Parameters.Staging.config</SetParamsFile>
</PropertyGroup>
TeamCity build log
[11:45:53]Checking for changes
[11:45:53]Collecting changes in 1 VCS root (1s)
[11:45:55]Clearing temporary directory: C:\TeamCity\buildAgent\temp\buildTmp
[11:45:55]Publishing internal artifacts
[11:45:55]Checkout directory: C:\TeamCity\buildAgent\work\8c8eb5050252f271
[11:45:55]Updating sources: server side checkout
[11:45:55]Step 1/1: MSBuild (4s)
[11:45:55][Step 1/1] Starting: C:\TeamCity\buildAgent\plugins\dotnetPlugin\bin\JetBrains.BuildServer.MsBuildBootstrap.exe /workdir:C:\TeamCity\buildAgent\work\8c8eb5050252f271 "/msbuildPath:C:\Program Files (x86)\MSBuild\12.0\bin\MSBuild.exe"
[11:45:55][Step 1/1] in directory: C:\TeamCity\buildAgent\work\8c8eb5050252f271
[11:45:59][Step 1/1] src\DystopiaOnline.proj.teamcity: Build target: BuildSolution
[11:45:59][src\DystopiaOnline.proj.teamcity] BuildSolution
[11:45:59][BuildSolution] MSBuild
[11:45:59][BuildSolution] C:\TeamCity\buildAgent\work\8c8eb5050252f271\src\DystopiaOnline.proj(36, 5): error MSB4062: The "DystopiaOnline.Build.Tasks.GetUnixTimestamp" task could not be loaded from the assembly C:\TeamCity\buildAgent\work\8c8eb5050252f271\src\DystopiaOnline.Build.Tasks/bin/Release/DystopiaOnline.Build.Tasks.dll. Could not load file or assembly 'file:///C:\TeamCity\buildAgent\work\8c8eb5050252f271\src\DystopiaOnline.Build.Tasks\bin\Release\DystopiaOnline.Build.Tasks.dll' or one of its dependencies. The system cannot find the file specified. Confirm that the <UsingTask> declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask.
[11:45:59][Step 1/1] Process exited with code 1
[11:45:59][Step 1/1] MSBuild output
[11:45:59][Step 1/1] Step MSBuild failed
[11:45:59]Publishing internal artifacts
[11:45:59]Build finished
This is how my project setup look witht the build folder.
link to .proj file on OneDrive
Error after copying release file into TeamCity manually
C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Common.CurrentVersion.targets(3797, 5): error MSB3027: Could not copy "C:\TeamCity\buildAgent\work\8c8eb5050252f271\src\DystopiaOnline.Base\bin\Release\DystopiaOnline.Base.dll" to "bin\Release\DystopiaOnline.Base.dll". Exceeded retry count of 10. Failed.
C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Common.CurrentVersion.targets(3797, 5): error MSB3021: Unable to copy file "C:\TeamCity\buildAgent\work\8c8eb5050252f271\src\DystopiaOnline.Base\bin\Release\DystopiaOnline.Base.dll" to "bin\Release\DystopiaOnline.Base.dll". The process cannot access the file 'bin\Release\DystopiaOnline.Base.dll' because it is being used by another process.
There is a typo in your DystopiaOnline.proj file in the word "Configuraton":
<MSBuild Projects="DystopiaOnline.Build.Tasks/DystopiaOnline.Build.Tasks.csproj" Properties="Configuraton=$(BuildConfig)" />
I think that this is the reason of the incorrect configuration building.
How have you setup the parameter in the build? It's most likely that it's value is not being passed to the build runner so it defaults to dev
<Env Condition="'$(Env)' == ''">dev</Env>
If you are using teamcity parameters it should be a system or environment parameter. You can also check the parameters used in a build by clicking on it and going to the parameters tabs, might want to double check that it has the value you want.
First keep in mind, that Windows path is '\' NOT '/'. In normal cases there is not any different (eg. when you want to put path in Explorer), but sometimes (I run in to some problems with some task in MsBuild) the mechanism doesn't recognize that this is proper path. So change the path to
<MSBuildCommunityTasksPath>$(MSBuildThisFileDirectory)\Tasks\</MSBuildCommunityTasksPath>
(Also MsBuild convention told us to put trailing slash)
And whenever you have reference to "DystopiaOnline.Build.Tasks.dll": [...]src\DystopiaOnline.Build.Tasks\bin\Release\DystopiaOnline.Build.Tasks.dll.
In the build log, there is not any sign that the MsBuild try to build the DystopiaOnline.Build.Tasks. HOW you specify the order of building your projects? Can you add this project that we can see exactly what MsBuild want to do and in what order.
If you use <UsingTask> inside the same project file that want to build DystopiaOnline.Build.Tasks... It will never work, because MsBuild first try to resolve task and then run targets to build.
It could work on your machine when you already build the project but not in clean environment when there is not (yet) any task.
try creating new system parameter called system.Configuration and set value "Release"

Categories