Como pudemos ver no artigo anterior, o Source Link é uma tecnologia que visa prover uma grande experiência de depuração para binários .NET e que facilita muito a vida dos desenvolvedores na hora de resolver problemas.
Neste artigo, veremos como adicionar suporte ao Source Link nas nossas bibliotecas.
Como adicionar Source Link num assembly
O primeiro passo para adicionar suporte ao Source Link nos nossos assemblies é instalar o pacote Source Link do provedor de controle de versão que você utiliza no seu projeto.
Para fins de exemplo neste artigo, vou utilizar a minha biblioteca ForeverFactory, que está hospedada no GitHub. Para uma lista completa dos provedores suportados, consulte o repositório do Source Link.
Assim, você pode instalar o SourceLink adicionando a seguinte referência no seu projeto:
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
É de suma importância que a propriedade PrivateAssets seja definida como all para o Source Link funcionar.
|
Feito isso, precisamos configurar no arquivo do projeto como será feita a geração dos pacotes de símbolos.
<PropertyGroup>
<TargetFrameworks>net5.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<Features>strict</Features>
<Authors>Dyego Alekssander Maas</Authors>
<Copyright>Copyright Dyego Alekssander Maas</Copyright>
<PackageId>ForeverFactory</PackageId>
<Description>Forever Factory makes it super easy to build many customized objects.</Description>
<PackageProjectUrl>https://github.com/DyegoMaas/ForeverFactory</PackageProjectUrl>
<RepositoryUrl>https://github.com/DyegoMaas/ForeverFactory</RepositoryUrl>
<PackageTags>ForeverFactory factory builder</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<MinVerTagPrefix>v</MinVerTagPrefix>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<PackageIcon>icon_128x128.png</PackageIcon>
<DebugType>full</DebugType>
<PublishRepositoryUrl>true</PublishRepositoryUrl> (1)
<EmbedUntrackedSources>true</EmbedUntrackedSources> (2)
<IncludeSymbols>true</IncludeSymbols> (3)
<SymbolPackageFormat>snupkg</SymbolPackageFormat> (4)
</PropertyGroup>
1 | Define a URL do repositório que será incluída nos metadados do pacote de símbolos |
2 | Instrui o o sistema de build a embarcar arquivos que participaram do build, mas não são rastreados pelo controle de versão |
3 | Instrui o sistema de build a gerar um pacote de símbolos, além do pacote com os binários |
4 | Define o formato do pacote de símbolos como snupkg |
Com essas configurações feitas, podemos gerar um pacote localmente com os seguintes comandos:
dotnet build -c Release
dotnet pack src/ForeverFactory/ForeverFactory.csproj -c Release --no-build -o nuget-package
Com este comando dotnet pack
, geramos nosso pacote NuGet no diretório nuget-package
, como podemos ver na imagem a seguir:
Como validar pacotes NuGet usando NGPE
Antes de publicar nosso pacote, podemos validá-lo com a ferramenta NuGet Package Explorer (NGPE).
Com o NGPE, podemos confirmar que os metadados necessários foram embarcados no pacote:
-
O conteúdo do pacote, com os binários, ícone do projeto e XMLs de documentação
-
Na seção Repository, encontramos a URL do repositório, e o commit atual
-
Na seção Health, a ferramenta nos indica dois problemas:
-
O pacote foi gerado a partir de um build não-determístico
-
Há flags do compilador faltando
-
Voltaremos a tratar desses dois pontos mais adiante no artigo. Vamos agora verificar o que contém o pacote de símbolos gerado: |
Como podemos verificar no arquivo, o pacote de símbolos contém os arquivos PDB para depuração, além dos metadados associando repositório e commit ao pacote.
Configurando builds determinísticos
Por padrão, os builds do .NET são indeterminísticos. Isso significa que cada build gera binários e símbolos ligeiramente diferentes de builds anteriores.
Eles são diferentes porque incluem metadados acerca da máquina que executou o build, timestamp, entre outros, localização dos arquivos na máquina e muito mais. Essas informações servem principalmente para facilitar a depuração local desses binários.
Isso é um problema para um pacote NuGet publicado porque não é possível garantir que este pacote é o que diz ser.
A recomendação nesse caso, é fazer com que o build que publica o pacote nos servidores do NuGet, executado num pipeline de CI, execute builds determinísticos. |
Esta configuração é feita através da
<PropertyGroup>
<TargetFrameworks>net5.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<Features>strict</Features>
<Authors>Dyego Alekssander Maas</Authors>
<Copyright>Copyright Dyego Alekssander Maas</Copyright>
<PackageId>ForeverFactory</PackageId>
<Description>Forever Factory makes it super easy to build many customized objects.</Description>
<PackageProjectUrl>https://github.com/DyegoMaas/ForeverFactory</PackageProjectUrl>
<RepositoryUrl>https://github.com/DyegoMaas/ForeverFactory</RepositoryUrl>
<PackageTags>ForeverFactory factory builder</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<MinVerTagPrefix>v</MinVerTagPrefix>
<PackageIcon>icon_128x128.png</PackageIcon>
<DebugType>full</DebugType>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Deterministic>true</Deterministic> (1)
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild> (2)
</PropertyGroup>
1 | Instrui o sisema de build a utilizar builds determinísticos sempre que possível |
2 | Determina que o build foi realizado num pipeline de integração contínua. Note que esta propriedade só é definida como true se o build for executado num workflow do GitHub Actions, conforme definido pela variável de ambiente GITHUB_ACTIONS estiver definida como true |
Conforme explicado neste repositório, builds determinísticos só acontecem em builds de integração contínua.
Podemos simular um build de CI localmente com o seguinte comando:
dotnet build /p:ContinuousIntegrationBuild=true
Abrindo este novo build no NGPE, podemos verificar que este novo pacote foi gerado com um build determinístico.
A próxima etapa, é realizar a publicação do pacote. Para fins de exemplo, compartilho a seguir o pipeline de release do meu projeto ForeverFactory:
name: Release
on:
push:
tags:
- '*.*.*'
jobs:
release:
strategy:
fail-fast: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup dotnet 5.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
- name: Clean
run: dotnet clean -c Release
- name: Build
run: dotnet build -c Release
- name: Test
run: dotnet test -c Release -r nuget-package --no-build -l trx --verbosity=normal
- name: Pack
run: dotnet pack src/ForeverFactory/ForeverFactory.csproj -c Release --no-build -o nuget-package
- name: Publish to Nuget.org
run: dotnet nuget push nuget-package/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json (1)
1 | Quando publicamos os arquivos .nupkg , o CLI do NuGet identifica a presença dos pacotes de símbolos .snpkg e publica eles também |
A primeira coisa que precisamos confirmar, é se os símbolos estão sendo publicados corretamente no feed do projeto no NuGet.org:
Por fim, podemos validar o pacote publicado usando o NGPE. Para isso, basta abrir o pacote a partir do feed usando a opção File → Open from Feed…
:
Conclusão
Seguindo estes passos, habilitamos o Source Link na nossa biblioteca, tornando-a muito mais fácil de depurar, e tornando mais fácil a vida dos usuários.