Configurando SourceLink

Cuando se crean aplicaciones es muy habitual usar paquetes NuGet, muchas veces estos paquetes son de código libre y están albergados en GitHub o puede que sean privados y estén depositados en servidores NuGet privados.

Cuando se está desarrollando una aplicación a veces es cómodo poder depurar paquetes NuGet para obtener información como si fuera código local, para esto sirve SourceLink para permitir que el desarrollador pueda acceder al código fuente de un paquete NuGet.

Para que esto funcione el paquete debe soportar SourceLink así que es posible que no todos los paquetes que existan actualmente puedan usar este método.

También hará falta una versión de Visual Studio 15.3 y se recomienda una 15.7 para obtener toda la funcionalidad.

En este ejemplo se va a utilizar .Net Core que viene preparado para SourceLink, aunque se soportan otros formatos como .Net Framework así como varios formatos de PDB (Portable, Embedded y Windows PDBs).

Configuración

Es recomendable que la información de depuración de los proyectos sea portable, otros formatos son dependientes de Windows y no siempre funcionan bien.

Editamos el archivo .csproj y añadimos la siguiente información que es la recomendada por SourceLink.

<Project Sdk="Microsoft.NET.Sdk">
 <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
 
    <!-- Optional: Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
 
    <!-- Optional: Embed source files that are not tracked by the source control manager in the PDB -->
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
  
    <!-- Optional: Build symbol package (.snupkg) to distribute the PDB containing Source Link -->
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  </PropertyGroup>
  <ItemGroup>
    <!-- Add PackageReference specific for your source control provider (see below) --> 
  </ItemGroup>
</Project>
  • PublishRepositoryUrl: Indicamos que queremos que en la información del paquete aparezca la Url del repositorio.
  • EmbedUntrackedSources: Permite añadir cómo código fuente a usar durante la depuración código que no esté asociado al repositorio de este paquete o que esté fuera del control del código fuente (como un AssemblyAttribute.cs), hay que tener cuidado porque puede añadir mucho código que igual no nos interesa.
  • IncludeSymbols: Construye el paquete .snupkg asociado que contiene la información necesaria para la depuración, otra forma sería añadirlo al paquete principal pero esto aumentaría el tamaño.
  • SymbolPackageFormat: Formato del paquete de símbolos.

Si tenemos problemas a la hora de depurar por ejemplo al tener las librerías en un repositorio privado podemos probar a usar está configuración que nos incluirá los símbolos dentro de la librería en lugar de un paquete de símbolos aparte, para Nuget.org se recomienda crear un paquete aparte (*.snupkg) como se indica aparte, en ese caso se puede omitir la directiva SymbolPackageFormat.

<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>

La configuración que hemos establecido hasta ahora lo que hace es crear una etiqueta <repository> con la dirección del repositorio así como el commit asociado a esta versión de código.
A través de Nuget Package Explorer se vería así.

<repository>

Varios paquetes por proyecto

En el caso de que queramos usar el fichero .nuspec, por ejemplo si tenemos varios proyectos y queremos crear un solo paquete con varios, tenemos que añadir esta misma etiqueta manualmente y parametrizarla ya que dotnet pack no funciona igual cuando se usa .nuspec, así que dentro del .nuspec añadimos:

<repository type="$RepositoryType$" url="$RepositoryUrl$" branch="$RepositoryBranch$" commit="$SourceRevisionId$"/>

Y los parámetros los tenemos que referenciar a través del .csproj añadiendo esta etiqueta.

<NuspecProperties>version=$(PackageVersion);SourceRevisionId=$(SourceRevisionId);RepositoryUrl=$(RepositoryUrl);RepositoryType=$(RepositoryType);RepositoryBranch=$(RepositoryBranch)</NuspecProperties>

Si usasemos un comando como el siguiente se rellenarían los valores correctos dentro de la etiqueta <repository>, más adelante se indicará como hacerlo a través de Azure DevOps.

dotnet pack .\Folder\Project.csproj -p:NuspecFile=C:\Projects\Project\Folder\File.nuspec -p:PackageVersion=4.9 -p:SourceRevisionId=7aab58c9134r2146dfr595a335e1474f4861649c -p:RepositoryUrl=https://github.com/acme/coyote -p:RepositoryType=git -p:RepositoryBranch=develop -o C:\folder\packages

También hay que tener en cuenta que si se quieren añadir los símbolos de depuración dentro del paquete hay que usar la siguiente etiquetas en lugar de <IncludeSymbols> ya que si existen ambas no se guardarán los .pdb dentro del paquete.

<DebugSymbols>true</DebugSymbols>

Ahora vamos a añadir los paquetes que harán falta para habilitar la depuración, este paquete lo marcamos como PrivateAssets=»All» (normalmente no hará falta hacerlo), esto evita que los proyectos que usen este paquete intente también usar el propio paquete SourceLink,

Los paquetes a instalar dependen del repositorio donde esté el código, unos ejemplos serían:

GitHub

<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05" PrivateAssets="All"/>
</ItemGroup>

Aquí incluimos la referencia a paquete que antes hemos dejado vacía, actualmente la librería se encuentra en prerelease así que tendremos que marcar el check de NuGet Include prerelease para poder descargarla.

Azure DevOps

En este caso la referencia a añadir sería esta, al igual que para GitHub esta librería está en modo prerelease.

<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.Vsts.Git" Version="1.0.0-beta2-18618-05" PrivateAssets="All"/>
</ItemGroup>

Otros

Existen otras alternativas (GitLab, TFS, Bitbucket, …) que se pueden consultar en la propia web.

Sino aparece el paquete exacto para el repositorio la opción es usar Microsoft.SourceLink.GitHub.

Puede suceder que un paquete referencie a otros paquetes de otros proveedores, por ejemplo un paquete de GitHub que referencia a uno de Azure DevOps, en este caso la aplicación debe incluir las referencias a los paquetes necesarios a todos ellos.

Verificando

Para estar seguro de que el paquete corresponde a la versión podemos verificar que el commit que aparece en el paquete es el adecuado usando el comando git log.

Para comprobar que todo está bien configurado podemos usar la herramienta SourceLink, ejecutando este comando desde una consola de desarrollador o desde el propio VS podremos instalarla de forma global.

dotnet tool install --global SourceLink --version 3.0.0 
Instalando SourceLink como herramienta global

Ahora podemos lanzar comandos, por ejemplo el mapeo del código para ver si el .json está dentro de los .pdb con:

sourcelink print-json paquete\nCubed.EFCore.pdb
print-json

O comprobar que los ficheros se pueden descargar con:

sourcelink test paquete\nCubed.EFCore.pdb

O para imprimir el código SHA para ver los ficheros que contiene el .pdb:

sourcelink print-documents paquete\nCubed.EFCore.pdb
print-documents

O incluso las URL donde se ubicarán los ficheros de código fuente, estas direcciones son reales y se debería poder ver el contenido del código:

sourcelink print-urls paquete\nCubed.EFCore.pdb
print-urls

Publicando

Si queremos crear el paquete usando el pipeline de compilación de Azure a través de una tarea dotnet tendremos que usar el comando personalizado y no el pack (la opción de especificar un fichero .nuspec en lugar de .csproj on parece funcionar), dentro de los parámetros podemos usar variables de entorno para rellenar los valores necesarios para .nuspec. En este caso la versión es una variable que va dentro del pipeline, no es de entorno.

De cara a la publicación de los paquetes será necesario una versión del cliente NuGet al menos 4.9.0 y usar la URL v3 para poder publicar los símbolos, por ejemplo para NuGet.org https://api.nuget.org/v3/index.json

Puede pasar un tiempo desde que se publican los símbolos en GitHub hasta que los símbolos estén disponibles (máximo 1 hora pero suele ser menos).

Si tenemos un error como:

C:\Windows\ServiceProfiles\NetworkService\.nuget\packages\sourcelink.create.commandline\2.8.3\build\SourceLink.Create.CommandLine.targets(30,5): error : unable to convert OriginUrl: https://{private}.visualstudio.com/dotNET%20Project/_git/company.cqrslite [C:\agent\_work\34\s\company.CQRSlite\company.CQRSlite.csproj]

En ese caso tenemos que eliminar este paquete SourceLink.Create.CommandLine

Consejo DevOps, es mejor publicar de forma separada el paquete y los símbolos, o al menos tener la opción, ya que por ejemplo NuGet.org no permite publicar la misma versión de nuevo lo que impediría subir los símbolos al fallar la tarea anterior.

Visual Studio

Solo queda un detalle final, deshabilitar la opción Just My Code en las opciones de Visual Studio Tools->Options->Debugging esto permite poder acceder a otros orígenes de código y no solo al propio del desarrollador.

Deshabilitando Just My Code

Si se esta depurando código fuente que ya existe localmente en la máquina sera ese el que se use, pero si el código no existe en la máquina (que suele ser lo normal) se mostrará un mensaje como el siguiente.

Aviso de advertencia antes de descargar código

Eligiendo la primera opción se descargará el código en una ruta como:

C:\Users\user\AppData\Local\SourceServer\ 1c6a834f2006f59a0cb6dd7101918efae8ad05b614b5fc36d11bfc9c6d9bcb9b \src\proyecto\carpeta\codigo.cs

Este tipo de rutas permite que se puedan bajar diferentes versiones del código para diferentes versiones del mismo.

Referencias

https://stackoverflow.com/questions/53485362/pass-variable-to-nuspec-with-dotnet-pack
https://docs.microsoft.com/es-es/nuget/reference/nuspec#replacement-tokens
https://cezarypiatek.github.io/post/setting-assembly-and-package-metadata/
https://devblogs.microsoft.com/nuget/introducing-source-code-link-for-nuget-packages/
https://github.com/ctaggart/SourceLink/issues/256

Configurando SourceLink