Compilação condicional e metas de estrutura

124

Existem alguns locais menores em que o código do meu projeto pode ser drasticamente aprimorado se a estrutura de destino for uma versão mais recente. Gostaria de aproveitar melhor a compilação condicional em C # para alterná-las conforme necessário.

Algo como:

#if NET40
using FooXX = Foo40;
#elif NET35
using FooXX = Foo35;
#else NET20
using FooXX = Foo20;
#endif

Algum desses símbolos vem de graça? Preciso injetar esses símbolos como parte da configuração do projeto? Parece fácil o suficiente, pois saberei qual estrutura está sendo direcionada no MSBuild.

/p:DefineConstants="NET40"

Como as pessoas estão lidando com essa situação? Você está criando configurações diferentes? Você está passando as constantes pela linha de comando?

mckamey
fonte
Se você deseja uma solução pré- fabricada simples no VS, vote nessa voz do usuário, visualstudio.uservoice.com/forums/121579-visual-studio/… .
JohnC
1
Dê uma olhada neste link também. Muito explicativo. blogs.msmvps.com/punitganshani/2015/06/21/…
Marco Alves
grupos de projetos, restauração de nuget e grupos de ref de nuget, boa solução: shazwazza.com/post/…
OzBob

Respostas:

119

Uma das melhores maneiras de fazer isso é criar diferentes configurações de compilação no seu projeto:

<PropertyGroup Condition="  '$(Framework)' == 'NET20' ">
  <DefineConstants>NET20</DefineConstants>
  <OutputPath>bin\$(Configuration)\$(Framework)</OutputPath>
</PropertyGroup>


<PropertyGroup Condition="  '$(Framework)' == 'NET35' ">
  <DefineConstants>NET35</DefineConstants>
  <OutputPath>bin\$(Configuration)\$(Framework)</OutputPath>
</PropertyGroup>

E em uma de suas configurações padrão:

<Framework Condition=" '$(Framework)' == '' ">NET35</Framework>

O que definiria o padrão se não fosse definido em nenhum outro lugar. No caso acima, o OutputPath fornecerá um assembly separado sempre que você criar cada versão.

Em seguida, crie um destino AfterBuild para compilar suas diferentes versões:

<Target Name="AfterBuild">
  <MSBuild Condition=" '$(Framework)' != 'NET20'"
    Projects="$(MSBuildProjectFile)"
    Properties="Framework=NET20"
    RunEachTargetSeparately="true"  />
</Target>

Este exemplo recompilará o projeto inteiro com a variável Framework definida como NET20 após a primeira compilação (compilando ambas e assumindo que a primeira compilação foi o NET35 padrão acima). Cada compilação terá os valores de definição condicionais definidos corretamente.

Dessa maneira, você pode até excluir determinados arquivos no arquivo de projeto, se desejar #ifdef os arquivos:

<Compile Include="SomeNet20SpecificClass.cs" Condition=" '$(Framework)' == 'NET20' " />

ou mesmo referências

<Reference Include="Some.Assembly" Condition="" '$(Framework)' == 'NET20' " >
  <HintPath>..\Lib\$(Framework)\Some.Assembly.dll</HintPath>
</Reference>
Todd
fonte
Perfeito. Eu tinha experiência suficiente hackeando o formato msbuild para saber que isso poderia ser feito, mas não havia tempo suficiente para descobrir todos os detalhes. Muito obrigado!
Mckamey 28/05
Se você adicionar uma referência a esta resposta na minha pergunta relacionada ( stackoverflow.com/questions/2923181 ), marcarei você como a solução lá. Isso realmente resolve os dois ao mesmo tempo.
Mckamey 28/05
7
Obrigado pela resposta, mas agora o VS2010 já inclui uma nova tag denominada "TargetFrameworkVersion", agora para cada grupo de propriedades com condição, apenas o TargetFrameworkVersion foi alterado. Ainda precisamos de tudo isso para fazer funcionar?
Akash Kava
Esta resposta não é apenas ter constantes definidas para o quadro, mas também a construção de várias estruturas
katbyte
4
Este post funcionou para mim, mas eu não sou bom no MSBuild e demorou um pouco para descobrir. Eu fiz um projeto que funciona como exemplo. dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip
TheDev6
44

Uma alternativa que está funcionando para mim até agora é adicionar o seguinte ao arquivo do projeto:

 <PropertyGroup>
    <DefineConstants Condition=" !$(DefineConstants.Contains(';NET')) ">$(DefineConstants);$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", ""))</DefineConstants>
    <DefineConstants Condition=" $(DefineConstants.Contains(';NET')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(";NET"))));$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", ""))</DefineConstants>
  </PropertyGroup>

Isso leva o valor da propriedade TargetFrameworkVersion, que é como "v3.5", substitui o "v" e "." para obter "NET35" (usando as novas funções de propriedade recurso ). Em seguida, ele remove qualquer valor "NETxx" existente e o adiciona ao final dos DefinedConstants. Pode ser possível simplificar isso, mas não tenho tempo para mexer.

Olhando na guia Build das propriedades do projeto no VS, você verá o valor resultante na seção de símbolos de compilação condicional. Alterar a versão da estrutura de destino na guia Aplicativo altera o símbolo automaticamente. Você pode usar as #if NETxxdiretivas do pré-processador da maneira usual. Alterar o projeto no VS não parece perder o PropertyGroup personalizado.

Observe que isso não parece fornecer algo diferente para as opções de destino do perfil do cliente, mas isso não é um problema para mim.

Jeremy Cook
fonte
Jeremy, uau, obrigado, isso é perfeito, pois eu já estou construindo separadamente na minha solução de compilação.
Greg Finzer
+1. Quem teria pensado que seria tão difícil encontrar "$ (DefineConstants.Contains ('..." ?? Obrigado)
CAD cara
Finalmente encontrei o caminho para esta página novamente, porque precisava de uma atualização de como consegui essas constantes mágicas em minha construção. Hoje, estou revisitando o mesmo projeto, para subdividir a biblioteca, e preciso que os símbolos acompanhem algumas das subdivisões. Acabei de olhar acima e notei que sua resposta já está devidamente reconhecida no arquivo .CSPROJ original.
David A. Gray
15

Eu tive problemas com essas soluções, possivelmente porque minhas constantes iniciais foram pré-criadas por essas propriedades.

<DefineConstants />
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<DebugSymbols>true</DebugSymbols>

O Visual Studio 2010 também gerou um erro por causa dos pontos e vírgulas, alegando que são caracteres ilegais. A mensagem de erro me deu uma dica, pois eu pude ver as constantes pré-construídas separadas por vírgulas, eventualmente seguidas pelo meu ponto-e-vírgula "ilegal". Depois de reformatar e massagear, consegui encontrar uma solução que funcionasse para mim.

<PropertyGroup>
  <!-- Adding a custom constant will auto-magically append a comma and space to the pre-built constants.    -->
  <!-- Move the comma delimiter to the end of each constant and remove the trailing comma when we're done.  -->
  <DefineConstants Condition=" !$(DefineConstants.Contains(', NET')) ">$(DefineConstants)$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", "")), </DefineConstants>
  <DefineConstants Condition=" $(DefineConstants.Contains(', NET')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(", NET"))))$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", "")), </DefineConstants>
  <DefineConstants Condition=" $(TargetFrameworkVersion.Replace('v', '')) >= 2.0 ">$(DefineConstants)NET_20_OR_GREATER, </DefineConstants>
  <DefineConstants Condition=" $(TargetFrameworkVersion.Replace('v', '')) >= 3.5 ">$(DefineConstants)NET_35_OR_GREATER</DefineConstants>
  <DefineConstants Condition=" $(DefineConstants.EndsWith(', ')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(", "))))</DefineConstants>
</PropertyGroup>

Eu publicaria uma captura de tela da caixa de diálogo Configurações avançadas do compilador (aberta clicando no botão "Opções avançadas de compilação ..." na guia Compilar do seu projeto). Mas, como novo usuário, não tenho o representante para fazê-lo. Se você pudesse ver a captura de tela, veria as constantes personalizadas preenchidas automaticamente pelo grupo de propriedades e então diria: "Tenho que conseguir um pouco disso".


EDIT: Peguei aquele representante surpreendentemente rápido .. Obrigado pessoal! Aqui está a captura de tela:

Configurações avançadas do compilador

Nathaniel Roark
fonte
4

Comece limpando as constantes:

<PropertyGroup>
  <DefineConstants/>
</PropertyGroup>

Em seguida, construa sua depuração, rastreamento e outras constantes como:

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
    <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <DefineConstants>TRACE;DEBUG;$(DefineConstants)</DefineConstants>
</PropertyGroup>

Por fim, construa suas constantes de estrutura:

<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v2.0' ">
  <DefineConstants>NET10;NET20;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v3.0' ">
  <DefineConstants>NET10;NET20;NET30;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v3.5' ">
  <DefineConstants>NET10;NET20;NET30;NET35;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">
  <DefineConstants>NET10;NET20;NET30;NET35;NET40;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.5' ">
  <DefineConstants>NET10;NET20;NET30;NET35;NET40;NET45;$(DefineConstants)</DefineConstants>
</PropertyGroup>

Eu acho que essa abordagem é muito legível e compreensível.

zDougie
fonte
3

Em um arquivo .csproj, após uma <DefineConstants>DEBUG;TRACE</DefineConstants>linha existente , adicione:

<DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' ">NET_40_OR_GREATER</DefineConstants>
<DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' == '4.0' ">NET_40_EXACTLY</DefineConstants>

Faça isso nas configurações de compilação Debug e Release. Em seguida, use no seu código:

#if NET_40_OR_GREATER
   // can use dynamic, default and named parameters
#endif
Azarien
fonte
3
parâmetros padrão e nomeados não são um recurso do .NET framework 4, mas um recurso do compilador .NET 4. Eles também podem ser usados ​​em projetos direcionados ao .NET 2 ou .NET 3, desde que compilados no Visual Studio 2010. É apenas um açúcar sintático. Por outro lado, dinâmico é um recurso do .NET framework 4 e você não pode usá-lo em projetos direcionados a estruturas anteriores a isso.
Thanasis Ioannidis
2

@Azarien, sua resposta pode ser combinada com a de Jeremy para mantê-la em um só lugar, em vez de Debug | Release etc.

Para mim, combinar as duas variações funciona melhor, ou seja, incluir condições no código usando #if NETXX e também criar versões diferentes da estrutura de uma só vez.

Eu tenho estes no meu arquivo .csproj:

  <PropertyGroup>
    <DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' ">NET_40_OR_GREATER</DefineConstants>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' == '3.5' ">
    <DefineConstants>NET35</DefineConstants>
    <OutputPath>bin\$(Configuration)\$(TargetFrameworkVersion)</OutputPath>
  </PropertyGroup>

e em metas:

  <Target Name="AfterBuild">
    <MSBuild Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' "
      Projects="$(MSBuildProjectFile)"
      Properties="TargetFrameworkVersion=v3.5"
      RunEachTargetSeparately="true"  />
  </Target>
ghanashyaml
fonte
0

Se você estiver usando o sistema de compilação .NET Core, poderá usar seus símbolos predefinidos (que já correspondem ao seu exemplo e não exigem nenhuma alteração no seu .csproj!):

#if NET40
using FooXX = Foo40;
#elif NET35
using FooXX = Foo35;
#else NET20
using FooXX = Foo20;
#endif

A lista de símbolos predefinidos está documentada em Developing Libraries with Cross Platform Tools e #if (Referência de C #) :

.NET Framework: NETFRAMEWORK , NET20, NET35, NET40, NET45, NET451, NET452, NET46, NET461, NET462, NET47, NET471, NET472,NET48

NET Standard: NETSTANDARD , NETSTANDARD1_0, NETSTANDARD1_1, NETSTANDARD1_2, NETSTANDARD1_3, NETSTANDARD1_4, NETSTANDARD1_5, NETSTANDARD1_6, NETSTANDARD2_0,NETSTANDARD2_1

.NET Núcleo: NETCOREAPP , NETCOREAPP1_0, NETCOREAPP1_1, NETCOREAPP2_0, NETCOREAPP2_1, NETCOREAPP2_2,NETCOREAPP3_0

Kevinoid
fonte