Dependencias condicionales en VS

Bueno, imagina que trabajas en un proyecto en NetCore que debe ser multiplataforma. En general el propio framework te provee de todo lo necesario, pero sigamos imaginando que algunas partes de tu proyecto dependen via P/Invoke de llamadas nativas.

En este caso puedes optar por tener todos los enlaces P/Invoke para cada plataforma en el mismo proyecto (no hay ningún problema) o bien tenerlos separados en proyectos por cada una de las plataformas.

Si este último es tu caso, lo más probable es que quieras que cuando compiles para una plataforma (p. ej. Windows) no se incluya la referencia al proyecto que contiene las referencias P/Invoke de las otras plataformas (en nuestro caso Linux y OSX). Igual también tienes código específico para cada plataforma que no quieres que se incluya. La verdad es que «no pasa nada» si se incluye, pero eso hace que aumente el tamaño del paquete de tu aplicación.

Lo bueno es que msbuild soporta referencias condicionales, es decir puedes tener algo como lo siguiente:

<ProjectReference Include="..\MyProject.Win32\MyProject.Win32.csproj" Condition="'$(TargetsWindows)'=='true'" />

Nota: En este ejemplo es una referencia a proyecto pero eso mismo funcionaría con otro tipo de referencia (tal como <PackageReference /> a un paquete NuGet).

En este caso concreto la referencia solo se incluye si la variable TargetsWindow toma el valor de true.

¿Y como toma el valor dicha variable? Por un lado se le podría pasar directamente mediante el parametero /p:TargetsWindow=true al invocar msbuild.

Aunque déjame que veamos otra aproximación y así de paso aprendemos un par de cosillas sobre msbuild y csproj 😉

Personalmente me gusta ir un paso más allá y agrupar los sistemas operativos en «familias». Así Linux y OSX comparten muchas similitudes ya que ambos son sabores de Unix. Podríamos hace algo así:

<PropertyGroup>
  <TargetFramework>netstandard2.0</TargetFramework>
  <TargetsLinux>false</TargetsLinux>
  <TargetsUnix>false</TargetsUnix>
  <TargetsWindows>false</TargetsWindows>
  <TargetsOSX>false</TargetsOSX>
</PropertyGroup>

Al principio del csproj declaro las variables con los distintos sabores de SOs que queremos soportar y las pongo todas a false. Luego en función del valor de otra variable (OSGroup) establecemos los valores de nuestras variables:

<Choose>
  <When Condition="'$(OSGroup)'=='Windows_NT'">
    <PropertyGroup>
      <TargetsWindows>true</TargetsWindows>
    </PropertyGroup>
  </When>
  <When Condition="'$(OSGroup)'=='Linux'">
    <PropertyGroup>
      <TargetsLinux>true</TargetsLinux>
      <TargetsUnix>true</TargetsUnix>
    </PropertyGroup>
  </When>
  <When Condition="'$(OSGroup)'=='OSX'">
    <PropertyGroup>
      <TargetsOSX>true</TargetsOSX>
      <TargetsUnix>true</TargetsUnix>
    </PropertyGroup>
  </When>
</Choose>

Observad el uso de <Choose/> que es básicamente una sucesión de if…else en msbuild: Cada <When/> define la condición que se cumple. En este caso se establecen los valores de TargetsXXXX a true o a false en función del valor de OSGroup.

Por supuesto, tener referencias condicionales no basta: a veces necesitas código condicional, es decir usar #if…#endif. Para ello se necesitan tener definidas constantes distintas por cada SO. Esto también se puede hacer desde el csproj:

<PropertyGroup Condition="'$(TargetsWindows)'=='true'">
  <DefineConstants>PLATFORM_WINDOWS;$(DefineConstants)</DefineConstants>
</PropertyGroup>

En este caso si el valor de TargetsWindows es true se define la constante de compilación PLATFORM_WINDOWS. Constantes similares se definirían para Linux, OSX, el propio Unix como familia y demás.

Ahora ya podemos usar msbuild con el parámetro /p:OSGroup=Windows_NT para compilar para WIndows y /p:OSGroup=Linux para compilar para Linux p. ej.

¿Y Visual Studio?

Bueno, pues a Visual Studio no le vengas con milongas: no hay manera de que VS pase parámetros msbuild. Es frustrante que eso sea así en VS2017, pero así son las cosas. Por suerte Hugo Biarge ha acudido a mi rescate y me ha hablado de Directory.build.props. Este es un fichero que sirve para establecer propiedades adicionales de msbuild. Se busca este fichero en el directorio actual o cualquier directorio que lo contenga lo que es perfecto. En mi caso, p. ej. si quiero compilar la versión de Windows basta con que tenga un fichero Directory.build.props en la raíz del repo con el contenido:

<Project>
 <PropertyGroup>
  <OSGroup>Windows_NT</OSGroup>
 </PropertyGroup>
</Project>

Y… ¡voilá! Eso establece el valor de OsGroup a «Windows_NT» lo que hace que a su vez el valor de TargetsWindows sea true, lo que hace que se incluya mi dependencia y además se defina la constante de compilación PLATFORM_WINDOWS. Y lo mejor… ¡Visual Studio lo respeta!

No es una solución ideal, pero me permite usar VS sin necesidad de modificar los csprojs y seleccionar (editando dicho fichero) que versión quiero compilar.

En resúmen: msbuild y csproj son muy poderosos… bastante más que VS que no soporta todas las casuísticas. Es una pena, pero es así 😉

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *