While working with version control systems, a common need is to identify a specific set of changes in our repository (what is usually called commit or check-in, depending on the tool) and give that set of changes a special meaning by naming it using a text that usually adheres to a standard. The version control systems allow us to do this through a feature called tag or label.
Even though using tags is really convenient for anyone that already has our repository cloned, it requires extra steps (installing git, configuring a ssh key, cloning the repository) if we just want to access a specific file from it. If all we need is to access the binaries built from the repository, we’re also required to download the required SDK and any other tools used by the build process, and execute potentially several steps to finally generate the binaries.
To solve this need, GitHub also offers a means to create releases. Each release in GitHub is associated with a tag, but it allows us to include any file we consider to be useful. This allows anyone that may be interested in our project to just download the binaries to use it, if we’re developing a command line or desktop application.
Even though it is possible to generate a GitHub release manually, it’s way more convenient to create a release as part of our build pipeline.
In this article we’re going to go through the different steps needed to create a GitHub release for a desktop application. Individual steps may need to be adjusted depending on the type of project we want to build. The build pipeline used in this example is defined in an azure-pipelines.yml file.
First we’re going to create a couple variables in the build pipeline:
The first section of the azure-pipelines.yml file should include the trigger for the build (to enable continuous integration) and the pool in which the build is going to be executed. Also, we need to declare variables for the solution, build platform and build configuration.
trigger: - main pool: vmImage: 'windows-latest' variables: solution: '**/*.sln' buildPlatform: 'Any CPU' buildConfiguration: 'Release'
Then we should declare the different steps of the build pipeline. The first step is the UseDotNet task, to specify which version of the .NET SDK we want to use in the next steps of the pipeline.
steps: - task: UseDotNet@2 inputs: version: '5.0.100'
The next step is to restore the NuGet packages our solution depends on.
- task: DotNetCoreCLI@2 displayName: 'dotnet restore' inputs: command: 'restore' projects: '**/*.csproj'
Once the NuGet packages are restored, we can build the solution. In this example, we’re going to use the version parameter of the DotNet build command, specifying the value entered in the app-version parameter declared in the pipeline.
- task: DotNetCoreCLI@2 displayName: 'dotnet build' inputs: command: 'build' projects: '$(solution)' arguments: '--configuration $(buildConfiguration) -p:Version=$(app-version)'
Once the application is built, we’re going to execute the automated tests included in the solution. In this example we’re executing the tests in every project whose name ends in Tests.csproj and since the application is already built, we’re including the –no-build argument in this task.
- task: DotNetCoreCLI@2 displayName: 'dotnet test' inputs: command: 'test' projects: '**/*Tests.csproj' arguments: '--configuration $(buildConfiguration) --no-build'
Now that our application is built and tested, we’re going to generate the assets to include in the GitHub release. For that we’re going to use the DotNet publish command to produce a single zip file with all the binaries for our application.
However, there’s a chance that we would not want to produce a GitHub release for every commit we push, especially if we’re adopting Continuous Integration. To decide whether we’re going to produce a release or not, we’re adding a “condition” parameter to the task, that will cause the task to be executed only if the following criteria are met:
- task: DotNetCoreCLI@2 displayName: 'dotnet publish' condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'), eq(variables['create-release'], true)) inputs: command: 'publish' publishWebProjects: false projects: '**/MyApp.UI.csproj' arguments: '--configuration $(buildConfiguration) --no-build -p:PublishSingleFile=false'
One thing worth noting is that the DotNet publish command will not publish anything really, but rather generate the package that we’re going to publish later.
Now we’re going to copy the files to the artifact staging directory used by the pipeline.
- task: CopyFiles@2 inputs: Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)'
Then we publish the content of the artifact staging directory as an output of the build pipeline.
- task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'drop' publishLocation: 'Container'
As the last step, we’re going to create a GitHub release only if the same criteria for the DotNet publish step are met.
Specifying tagSource: ‘manual’ and tag: ‘v$(app-version)’ allows us to create a tag using a semantic version (https://semver.org/) prefixed with a “v”. That will result in tags like “v0.9.0.0”, “v0.9.1.0”, “v0.10.0.0”.
The GitHub releases can be marked as drafts. In this example we’re using the parameter isDraft: false to specify that the release is final.
- task: GitHubRelease@0 condition: and(succeeded(), eq(variables['Build.SourceBranch'],'refs/heads/main'), eq(variables['create-release'], true)) inputs: gitHubConnection: 'GitHub connection' repositoryName: '$(Build.Repository.Name)' action: 'create' target: '$(Build.SourceVersion)' tagSource: 'manual' tag: 'v$(app-version)' title: 'v$(app-version)' isDraft: false assets: '$(Build.ArtifactStagingDirectory)/MyApp/MyApp.UI/bin/Release/ net5.0-windows/publish.zip'
Once the build pipeline is executed, we can go to the list of releases for our GitHub repository using a URL with the following format: https://github.com/myuser/myrepo/releases
If we go to the Assets section for the release, we can see the source code compressed in zip and tar.gz format, and the publish.zip file we included in the build pipeline.
GitHub Releases are a useful tool to package files belonging to a specific tag in our repository, and can be easily integrated to our existing pipelines, allowing us to create them in a quick, repeatable way.