Step-by-step instructions for setting up and using Gitlab CI + Visual Studio to build a .NET Framework application

By nature, many developers too lazydo not like to do the same action over and over again. It's easier for us to teach a computer to do monotonous actions for us.







As soon as someone on our team makes changes to the code (read "merge a feature branch into develop"), our build server:







  • Builds source code and app installer

    • puts down the assembly number, each time increasing the last digit. For example, the current version of our software 3.3.0.202 - part 3.3.0 was once introduced by the developer (hello, SemVer ), and "202" is put down during the assembly process.
    • In the process, it analyzes the quality of the code (using SonarQube) - and sends a report to the internal SonarQube,
  • Immediately after assembly, it launches autotests (xUnit) and analyzes the test coverage (OpenCover),


Also, depending on the branch to which the changes were made, the following can be done:







  • sending an assembly (along with a changelog) to one or several telegram channels (sometimes it is more convenient to take assemblies from there).
  • publishing files to the software auto-update system.


Below the cut is about how we taught Gitlab CI to do most of this dreary work for us.







Table of contents



  1. Install and register Gitlab Runner .
  2. .gitlab-ci.yml .
  3. Developer PowerShell for VS.
  4. CI .
  5. SonarQube.
  6. «» xUnit + OpenCover.
  7. .




, , github , WPF unit-, . gitlab.com , .







Gitlab Runner



Gitlab CI - , Gitlab Runner , . .Net Framework Windows.







Gitlab Runner, :







  1. Git Windows git.
  2. Visual Studio Microsoft. Build Tools Visual Studio 2019. , Visual Studio 2019.
  3. C:\GitLab-Runner gitlab runner. [ Gitlab] (https://docs.gitlab.com/runner/install/windows.html) β€” : Β«Download the binary for x86 or amd64Β».
  4. cmd powershell , C:\GitLab-Runner install (Gitlab runner ).

        .\gitlab-runner.exe install
          
          



  5. Runner-. , Runner:

    • β€” Settings > CI/CD Runners,
    • β€” Settings > CI/CD Runners,
    • Gitlab- β€” , Overview > Runners.
  6. Runner-,

        .\gitlab-runner.exe register
          
          





Runner-:







  • coordinator URL β€” http https gitlab;
  • gitlab-ci token β€” , ;
  • gitlab-ci description β€” Runner-, Gitlab-;
  • gitlab-ci tags β€” Runner-. , β€” gitlab-. , Runner- (, Runner-, Windows, Runner- Linux);
  • enter the executor β€” shell



    . , ; shell windows powershell, .


.gitlab-ci.yml



Gitlab CI , .gitlab-ci.yml



, .







.gitlab-ci.yml



.NET Framework : 1, 2. :







variables:
  #        ;     ,   
  MSBUILD_CONCURRENCY: 4
  #     ,     ,  
  NUGET_PATH: 'C:\Tools\Nuget\nuget.exe'
  MSBUILD_PATH: 'C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\msbuild.exe'
  XUNIT_PATH: 'C:\Tools\xunit.runner.console.2.3.1\xunit.console.exe'
  TESTS_OUTPUT_FOLDER_PATH: '.\tests\CiCdExample.Tests\bin\Release\'

#    .      ,      : build, test  deploy.
#      .
stages:
  - build
  - test

#    (job-)

build_job:
  stage: build # ,     build
  # tags: windows #    ,      Runner-    
  only: #      
    - branches
  script: #  
    - '& "$env:NUGET_PATH" restore'
    - '& "$env:MSBUILD_PATH" /p:Configuration=Release /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly' # ;  clp:ErrorsOnly   ;  nr:false   msbuild 
  artifacts: #      ,     gitlab (.. )         
    expire_in: 2 days #   
    paths: #  ,      
      - '$env:TESTS_OUTPUT_FOLDER_PATH'

test_job:
  stage: test
  only:
    - branches
  script:
    - '& "$env:XUNIT_PATH" "$env:TESTS_OUTPUT_FOLDER_PATH\CiCdExample.Tests.dll"'
  dependencies: # ,          build_job
    - build_job
      
      





: , (, -), gitlab. ( ) Settings > CI/CD Variables. (key) SAMPLE_PARAMETER, .gitlab-ci.yml $env:SAMPLE_PARAMETER.







( Protected) / ( Masked).







Gitlab CI.







Developer PowerShell for VS



, , . , : Visual Studio. , - Visual Studio 2017 BuildTools, Visual Studio Professional 2019, .







, Visual Studio 2017 Visual Studio . vswhere, Visual Studio, . Visual Studio 2019 ( 16.1 ) , «» Powershell Developer Powershell, VS.













Variables:







variables:
  VSWHERE_PATH: '%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe'
      
      





before_msbuild enter_vsdevshell :







.before_msbuild: &enter_vsdevshell
  before_script:
    - '$vsWherePath = [System.Environment]::ExpandEnvironmentVariables($env:VSWHERE_PATH)'
    - '& $vsWherePath -latest -format value -property installationPath -products Microsoft.VisualStudio.Product.BuildTools | Tee-Object -Variable visualStudioPath'
    - 'Join-Path "$visualStudioPath" "\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" | Import-Module'
    - 'Enter-VsDevShell -VsInstallPath:"$visualStudioPath" -SkipAutomaticLocation'
      
      





, Visual Studio, . :







build_job:
  <<: *enter_vsdevshell
  stage: build
  only:
    - branches
  script:
    - 'msbuild /t:restore /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'
    - 'msbuild /p:Configuration=Release /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'
  artifacts:
    expire_in: 2 days
    paths:
      - '$env:TESTS_OUTPUT_FOLDER_PATH'
      
      





, .before_msbuild
  1. vswhere.exe Visual Studio. ( VSWHERE_PATH). %programfiles%



    , . .NET System.Environment.ExpandEnvironmentVariables.


: vswhere.







  1. vswhere Visual Studio.

    , vswhere.exe



    -help



    , :

    • -latest ( ),
    • -property installationPath ( ),
    • -format value ( , ),
    • -products < Visual Studio, > ( Visual Studio). , -products Microsoft.VisualStudio.Product.Community Microsoft.VisualStudio.Product.BuildTools



      Visual Studio Community BuildTools. https://aka.ms/vs/workloads.


: $visualStudioPath Visual Studio , Visual Studio ( ).







  1. Import-Module Microsoft.VisualStudio.DevShell.dll, Powershell Developer-. Join-Path Visual Studio.

    , Microsoft.VisualStudio.DevShell.dll Visual Studio β€” Import-Module , .


: Powershell .







  1. «» Developer Powershell. , Visual Studio ( -VsInstallPath



    ). SkipAutomaticLocation



    ( < >\source\repos



    ).


: Developer Powershell msbuild , .







CI



t4 : <major>.<minor>.<revision>



, Gitlab CI tt-, , , . β€” git tag



git describe



.







git tag



(). . , . , . git rebase git commit --amend, , . git book.







git describe



, , gitbook . : . β€” fatal: No tags can describe '< >'



. β€” , , .







: , gitflow - finish hotfix finish release. gitflow, ( ).







, gitflow, feature- develop , develop:







Don't forget to re-create branches from develop

( git : - 1.0.5, git describe



)







. gitflow ( rebase-), master feature- develop, merge- master develop .







msbuild :







build_job:
  <<: *enter_vsdevshell
  stage: build
  only:
    - branches
  script:
    - 'msbuild /t:restore /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'
    - '$versionGroup = git describe --long | Select-String -Pattern "(?<major>[0-9]+)\.(?<minor>[0-9]*)\.(?<patch>[0-9]*)\-(?<commit>[0-9]+)\-g[0-9a-f]+" | Select-Object -First 1'
    - '[int]$major, [int]$minor, [int]$patch, [int]$commit = $versionGroup.Matches[0].Groups["major", "minor", "patch", "commit"].Value'
    - '[string]$version = "$major.$minor.$patch.$commit"'
    - 'msbuild /p:Configuration=Release /p:AssemblyVersionNumber=$version /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'
  artifacts:
    expire_in: 2 days
    paths:
      - '$env:TESTS_OUTPUT_FOLDER_PATH'
      
      





:







  1. <major>.<minor>.<revision>



    .
  2. git describe --long



    , <major>.<minor>.<revision>-< >-g< >



    .
  3. , β€” $versionGroup



    .
  4. 4 $major



    , $minor



    , $patch



    , $commit



    , .
  5. msbuild .


: , gitflow, () master release hofix, : . , 3.4, release- 3.5. : , master, , 3.4.







SonarQube



SonarQube β€” .







SonarQube Community-, . , . (develop), (, SonarQube):







  1. SonarQube , .
  2. SonarScanner for MSBuild ( sonarqube.org)[https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-msbuild/] β€” .NET Framework 4.6+.
  3. . , C:\Tools\SonarScanner



    .

    : NuGet, - .
  4. CI/CD Gitlab :

    • SONARQUBE_PROJECT_KEY β€” ,
    • SONARQUBE_AUTH_TOKEN β€” .



      ( ). ( Masked) , .
  5. Variables:

        variables:
          SONARSCANNER_MSBUILD_PATH: 'C:\Tools\SonarScanner\SonarScanner.MSBuild.exe'
          SONARQUBE_HOST_URL: 'url   SonarQube'
          
          



  6. (test_job) build_job:

        test_job:
          stage: test
          only:
            - /^develop$/
          <<: *enter_vsdevshell
          script:
            - '$versionGroup = git describe --long | Select-String -Pattern "(?<major>[0-9]+)\.(?<minor>[0-9]*)\.(?<patch>[0-9]*)\-(?<commit>[0-9]+)\-g[0-9a-f]+" | Select-Object -First 1'
            - '[int]$major, [int]$minor, [int]$patch, [int]$commit = $versionGroup.Matches[0].Groups["major", "minor", "patch", "commit"].Value'
            - '[string]$version = "$major.$minor.$patch.$commit"'
            - '& "$env:SONARSCANNER_MSBUILD_PATH" begin /key:$env:SONARQUBE_PROJECT_KEY /d:sonar.host.url=$env:SONARQUBE_HOST_URL /d:sonar.login=$env:SONARQUBE_AUTH_TOKEN /d:sonar.gitlab.project_id=$CI_PROJECT_PATH /d:sonar.gitlab.ref_name=develop /v:$version /d:sonar.dotnet.excludeGeneratedCode=true'
            - 'msbuild /t:rebuild /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'
            - '& "$env:SONARSCANNER_MSBUILD_PATH" end /d:sonar.login=$env:SONARQUBE_AUTH_TOKEN'
            - '& "$env:XUNIT_PATH" "$env:TESTS_OUTPUT_FOLDER_PATH\CiCdExample.Tests.dll"'
          
          





develop SonarQube .







: msbuild /t:rebuild



. , . .







:







  • key β€” SonarQube,
  • v β€” . ,
  • sonar.gitlab.project_id β€” ID Gitlab,
  • sonar.gitlab.ref_name β€” , SonarQube ,
  • sonar.dotnet.excludeGeneratedCode β€” , System.CodeDom.Compiler.GeneratedCode ( ).


«» xUnit + OpenCover



- β€” . , :







  • ,
  • xUnit,
  • OpenConver,
  • SonarQube.


: OpenCover ReportGenerator, SonarQube .







:







  1. OpenCover zip- github.
  2. . , C:\Tools\OpenCover



    .

    : NuGet, - .
  3. Variables:

        variables:
          OBJECTS_TO_TEST_REGEX: '^Rt[^\n]*\.(dll|exe)$'
          OPENCOVER_PATH: 'C:\Tools\opencover-4.7.922\xunit.console.exe'
          OPENCOVER_FILTER: '+[Rt.*]* -[*UnitTests]* -[*AssemblyInfo]*'
          OPENCOVER_REPORT_FILE_PATH: '.\cover.xml'
          
          



  4. (test_job), OpenCover:

        test_job:
          stage: test
          only:
            - /^develop$/
          <<: *enter_vsdevshell
          script:
            - '$versionGroup = git describe --long | Select-String -Pattern "(?<major>[0-9]+)\.(?<minor>[0-9]*)\.(?<patch>[0-9]*)\-(?<commit>[0-9]+)\-g[0-9a-f]+" | Select-Object -First 1'
            - '[int]$major, [int]$minor, [int]$patch, [int]$commit = $versionGroup.Matches[0].Groups["major", "minor", "patch", "commit"].Value'
            - '[string]$version = "$major.$minor.$patch.$commit"'
            - '& "$env:SONARSCANNER_MSBUILD_PATH" begin /key:$env:SONARQUBE_PROJECT_KEY /d:sonar.host.url=$env:SONARQUBE_HOST_URL /d:sonar.login=$env:SONARQUBE_AUTH_TOKEN /d:sonar.gitlab.project_id=$CI_PROJECT_PATH /d:sonar.gitlab.ref_name=develop /v:$version /d:sonar.cs.opencover.reportsPaths="$env:OPENCOVER_REPORT_FILE_PATH" /d:sonar.dotnet.excludeGeneratedCode=true'
            - 'msbuild /t:rebuild /m:$env:MSBUILD_CONCURRENCY /nr:false /clp:ErrorsOnly'
            - '$dllsToRunUnitTesting = @(Get-ChildItem "$env:TESTS_OUTPUT_FOLDER_PATH" -Recurse) | Where-Object {$_.Name -match $env:OBJECTS_TO_TEST_REGEX} | ForEach-Object { """""$_""""" } | Join-String -Separator " "'
            - '& "$env:OPENCOVER_PATH" -register -target:"$env:XUNIT_PATH" -targetargs:"$dllsToRunUnitTesting -noshadow" -filter:"$env:OPENCOVER_FILTER" -output:"$env:OPENCOVER_REPORT_FILE_PATH" | Write-Host'
            - 'if ($?) {'
            - '[xml]$coverXml = Get-Content "$env:OPENCOVER_REPORT_FILE_PATH"'
            - '$sequenceCoverage = $coverXml.CoverageSession.Summary.sequenceCoverage'
            - '$branchCoverage = $coverXml.CoverageSession.Summary.branchCoverage'
            - 'Write-Host "Total Sequence Coverage <!<$sequenceCoverage>!>"'
            - 'Write-Host "Total Branch Coverage [![$branchCoverage]!]"'
            - '} else {'
            - 'Write-Host "One or more tests failed!"'
            - 'Throw'
            - '}'
            - '& "$env:SONARSCANNER_MSBUILD_PATH" end /d:sonar.login=$env:SONARQUBE_AUTH_TOKEN'
          
          





    : begin- sonar scanner- β€” /d:sonar.cs.opencover.reportsPaths



    .







    ( ) Gitlab, Settings > CI/CD Test coverage parsing



    . , Gitlab- :

    • ( Sequence Coverage Statement Coverage), <!<([^>]+)>!>



      ,
    • ( Decision Coverage Branch Coverage), \[!\[([^>]+)\]!\]



      .





All Articles