Este artigo foi traduzido por máquina.

O MSBuild

Práticas recomendadas para criar confiável builds, parte 1

Sayed Ibrahim Hashimi

Este artigo discute:
  • Definindo dependências de destino
  • Em tarefas
  • Estendendo o processo de limpo
  • Organizando destinos
Este artigo usa as seguintes tecnologias:
O MSBuild, Visual Studio

Download do código disponível na Galeria de código do MSDN
Procure o código on-line

Conteúdo

Definindo dependências de destino
Em tarefas
Definindo itens dinâmicos e propriedades
Estendendo o processo de limpo
Organizando destinos
Usando caracteres curinga

Neste artigo, a primeira de duas partes, descrevo várias práticas recomendadas que os desenvolvedores devem seguir ao usando MSBuild, o mecanismo de compilação Microsoft usado pelo Visual Studio para criar projetos gerenciados. Nesta primeira parte, abordarei alguns práticas básicas e técnicas que você pode aplicar a mais cada projeto. Também discutirei tópicos como Definindo dependências de tarefas, em tarefas, organizar destinos e muito mais. Na parte 2, discutirei algumas práticas relacionadas ao criar configurações que exigem personalização heaver, grande parte devido a seu tamanho.

Se você não estiver familiarizado com o MSBuild, vejamos meus artigos anteriores publicadas no MSDN Magazine : " MSBuild interna: compilar aplicativos seu modo com tarefas personalizadas para o mecanismo Microsoft Build"e" Truques do WiX: automatizar versões com o MSBuild e o Windows Installer XML."

Definindo dependências de destino

Quando você define um destino no MSBuild, você pode usar o atributo de DependsOnTargets para definir dependências de destino. O MSBuild usa esse atributo para determinar a ordem na qual os destinos são executados. Por exemplo, se você definir um destino chamado Deploy que depende dos destinos PrepareForBuild e compilação, você pode expressar essa dependência da seguinte maneira:

<Target Name="PrepareForBuild">
  <!-- target contents here -->
</Target>

<Target Name="Build">
  <!-- target contents here -->

</Target>

<Target Name="Deploy" DependsOnTargets="PrepareForBuild;Build">
  <!-- target contents here -->
</Target>

Embora a lista anterior define as dependências que você deseja, ele não fornece uma maneira de estender o comportamento do destino Deploy. Seria melhor se você pode especificar os destinos adicionais que o destino de implantação depende sem ter que modificar o destino de implantação propriamente dito. Você pode implementar esse comportamento, definindo propriedades dentro o atributo DependsOnTargets em vez de usar valores codificados.

Dê uma olhada no destino implantar modificado mostrado aqui:

<PropertyGroup>
  <DeployDependsOn>
    $(DeployDependsOn);
    PrepareForBuild;
    Build
  </DeployDependsOn>
</PropertyGroup>
<Target Name="Deploy" DependsOnTargets="$(DeployDependsOn)">
  <!-- target contents here -->
</Target>

Nesta versão, o destino de implantação depende os destinos contidos a propriedade DeployDependsOn. (Por convenção, esses tipos de propriedades são nomeados com o nome de destino seguido por DependsOn, mas isso não é obrigatório.) Os consumidores agora podem anexar destinos adicionais para a propriedade DeployDependsOn, substituindo-lo. No entanto, observe que a declaração da propriedade DeployDependsOn contém uma referência a mesmo: $ (DeployDependsOn). Incluindo essa referência garante que quaisquer valores existentes para DeployDependsOn não são substituídas. Em vez disso, eles são simplesmente anexados.

Essa abordagem é melhor, mas só oferece suporte adicionando destinos que são executados antes o destino em questão. Se você desejar que os consumidores são capazes de especificar os destinos que devem ser executados antes ou depois de um destino, você precisará declarar o destino de implantação, como mostrado na Figura 1 .

A Figura 1 implantar declaração de destino

<PropertyGroup>
  <DeployDependsOn>
    $(DeployDependsOn);
    PrepareForBuild;
    Build;
    BeforeDeploy;
    CoreDeploy;
    AfterDeploy
  </DeployDependsOn>
</PropertyGroup>
<Target Name="Deploy" DependsOnTargets="$(DeployDependsOn)"/>
<Target Name="BeforeDeploy"/>
<Target Name="AfterDeploy"/>
<Target Name="CoreDeploy">
  <!-- target contents here -->
</Target>

Nesse caso, o destino de implantação é um destino vazio. A única coisa que ele faz é especificar os destinos que ele depende. O trabalho real é executado no destino CoreDeploy. Fazendo essa etapa, você pode estender a propriedade DeployDependsOn para executar destinos tanto antes e depois executa CoreDeploy.

Além disso, existem dois outros destinos vazios aqui: BeforeDeploy e AfterDeploy. Elas são declaradas para permitir que os consumidores uma maneira rápida de estender o destino de implantação. Se um desses destinos é definida por um consumidor, ele será chamado no momento apropriado. A diferença entre substituindo um destino como BeforeDeploy e estendendo a propriedade DeployDependsOn é que somente uma definição do destino BeforeDeploy existirá. Isso significa que, se o destino for declarado mais de uma vez, que última processado será a definição que entra em vigor.

Como alternativa para isso, você pode acrescentar como muitos nomes de uma propriedade de dependência de destino conforme necessário. Para arquivos de compilação reutilizáveis a segunda abordagem é o método único que garante que uma colisão de nome de destino não ocorre. Como exemplo de estender a propriedade DeployDependsOn, você pode declarar o destino DeployDependsOn novamente, conforme mostrado aqui:

<PropertyGroup>
  CustomBeforeDeploy;
  $(DeployDependsOn);
  CustomAfterDeploy
</PropertyGroup>
<Target Name="CustomBeforeDeploy">
  <!-- your steps here -->
</Target>
<Target Name="CustomAfterDeploy">
  <!-- your steps here -->
</Target>

Neste exemplo, as metas CustomBeforeDeploy e CustomAfterDeploy foram adicionadas à lista de destinos para ser executado. Os destinos fornecidos para projetos Visual Basic .NET e C# declarar destinos vazios como BeforeBuild bem como muitas propriedades de dependência como RebuildDependsOn. Você pode encontrar mais informações sobre esses tópicos na Blog da equipe MSBuild.

Cada destino que pode ser chamado externamente deve apresentar uma lista completa dos destinos seu atributo DependsOnTargets. Essa etapa é importante, pois se um usuário desejar realizar uma tarefa específica, digamos que UnitTests, devem ser capaz de especificar que no público-alvo alterne para msbuild.exe. Declarando corretamente dependentes destinos, o arquivo de projeto oferecerá suporte isso.

Em tarefas

Uma das perguntas comuns sobre o MSBuild é como executar um loop. Como o MSBuild usa uma linguagem declarativa, não é possível especificar um loop explícito. Em vez disso, você deve descrever o que você deseja que o mecanismo MSBuild para executar e deixe-manipular o loop para você.

O MSBuild usa um conceito conhecido como tarefas em lote, que permite que uma tarefa para ser executado uma vez por cada lote exclusivo ou um grupo de valores. Lotes sempre são criados baseados nos metadados de item. Vejamos um exemplo simples usando o conteúdo do arquivo Batching01.proj, mostrado na Figura 2 .

A Figura 2 A simples lotes de exemplo

<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="3.5">

  <ItemGroup>
    <SampleItem Include="one">
      <Group>A1</Group>
      <Id>A363BE85-2CB1-4221-A9CB-2881B7699329</Id>
    </SampleItem>
    <SampleItem Include="two">
      <Group>A2</Group>
      <Id>48E171C8-2274-4567-84D5-D20C6B0CB363</Id>
    </SampleItem>
    <SampleItem Include="three">
      <Group>A2</Group>
      <Id>618E5BD8-650F-43c9-855E-259126284004</Id>
    </SampleItem>
    <SampleItem Include="four">
      <Group>A1</Group>
      <Id>65E8E8E7-5A3F-4e02-A1D9-34F797CB68D9</Id>
    </SampleItem>
    <SampleItem Include="five">
      <Group>A1</Group>
      <Id>43D0D1FE-304F-4aff-BE19-67AD2195872B</Id>
    </SampleItem>
  </ItemGroup>

  <Target Name="TaskBatching01">
    <!-- No batching here -->
    <Message Text="SampleItem: @(SampleItem)"/>
    <Message Text=" "/>
    <!-- Batches created based on Group Metadata -->
    <Message Text="SampleItem.Group: %(SampleItem.Group)"/>
    <Message Text=" "/>
    <!-- Batches created based on Id Metadata -->
    <Message Text="SampleItem.Id: %(SampleItem.Id)"/>
  </Target>

</Project>

Este arquivo de projeto define um tipo de item denominado SampleItem, que inclui cinco valores. Cada valor tem valores de metadados para o grupo e identificação. Os metadados para ID são exclusivo para cada valor do item, mas grupo tem somente dois valores, A1 e A2. Processamento em lotes é acionado quando uma expressão de %(ItemType.MetadataName) o formulário é encontrada.

No destino TaskBatching01 a tarefa de mensagem é invocada com. Essa expressão causa MSBuild determinar os lotes exclusivos definidos pelos metadados de grupo do item SampleItem. Como sei que o grupo tem dois valores exclusivos, eu saber que dois lotes são criados, uma de valores que têm A1 como os metadados de grupo e o outro daqueles com A2. Mensagem de outra tarefa, cria lotes com base no valor ID metadados. O resultado da execução deste destino é mostrado na Figura 3 .

A Figura 3 Batching01.proj resultado

C:\Samples\Batching>msbuild Batching01.proj /t:TaskBatching01 /nologo
Build started 10/20/2008 1:27:58 AM.
Project "C:\Samples\Batching\Batching01.proj" on node 0 (TaskBatching01  
  target(s)).
  SampleItem: one;two;three;four;five

  SampleItem.Group: A1
  SampleItem.Group: A2
  SampleItem.Id: A363BE85-2CB1-4221-A9CB-2881B7699329
  SampleItem.Id: 48E171C8-2274-4567-84D5-D20C6B0CB363
  SampleItem.Id: 618E5BD8-650F-43c9-855E-259126284004
  SampleItem.Id: 65E8E8E7-5A3F-4e02-A1D9-34F797CB68D9
  SampleItem.Id: 43D0D1FE-304F-4aff-BE19-67AD2195872B
Done Building Project "C:\Samples\Batching\Batching01.proj" 
  (TaskBatching01 target(s)).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Como você pode ver, a instrução mescla os valores de SampleItem em uma única seqüência de caracteres passada para a tarefa de mensagem no lugar da expressão @(SampleItem), resultando em "um; dois; três, quatro; cinco". Compare isso com o comportamento demonstrado com a invocação. Nesse caso, a tarefa de mensagem foi executada duas vezes. No caso da instância SampleItem.Id, a tarefa de mensagem foi executada cinco vezes.

Um uso comum de processamento em lotes é recursivamente arquivos de cópia de um local para outro. Nesses casos você precisará usar os metadados conhecidos RecursiveDir para determinar em que ela reside em relação ao diretório pai. (O conjunto de metadados conhecidos do MSBuild pode ser encontrado no " O MSBuild Well-known metadados de item"artigo no MSDN). O arquivo de projeto Copy01.proj, mostrado na A Figura 4 , copia arquivos da pasta src para a pasta de destino.

A Figura 4 Copy01.proj

<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="3.5">

  <ItemGroup>
    <!-- Get all files under src\ except svn files -->
    <SrcFiles Include="src\**\*" Exclude="**\.svn\**\*" />
  </ItemGroup>

  <PropertyGroup>
    <Dest>dest\</Dest>
  </PropertyGroup>

  <Target Name="CopyToDest">
    <Copy SourceFiles="@(SrcFiles)"
          DestinationFolder ="$(Dest)%(SrcFiles.RecursiveDir)" />
  </Target>

  <Target Name="Clean">
    <ItemGroup>
      <_FilesToDelete Include="$(Dest)**\*"/>
    </ItemGroup>

    <Delete Files="@(_FilesToDelete)"/>
  </Target>
</Project>

Dentro o destino CopyToDest, a tarefa de cópia é usada junto com processamento em lotes para copiar os arquivos. Veja o conteúdo da pasta src:

src
¦   class1.cs
¦   class2.cs
¦   class3.cs
¦   class4.cs
¦
+---Admin
¦       admin_class1.cs
¦       admin_class2.cs
¦
+---Utilities
        util_class1.cs
        util_class2.cs

Neste exemplo, o DestinationFolder é especificado como $(dest)%(SrcFiles.RecursiveDir). Esta instrução quebra o item SrcFiles em três grupos com base no valor de RecursiveDir — um valor vazio para RecursiveDir, administração e utilitários. A tarefa de cópia é chamada três vezes, uma vez para cada grupo. Durante cada uma dessas chamadas somente os arquivos no grupo de atual são passados para a tarefa de cópia do item SrcFiles.

Para ver isso claramente, dê uma olhada no log de detalhadas da compilação mostrado na Figura 5 . O log, você pode verificar que a cópia de tarefa foi executada três horas e sempre que somente os arquivos correspondentes o valor atual para RecursiveDir foram incluídas no item SrcFiles.

A Figura 5 recursivamente copiando arquivos

Build started 10/26/2008 12:09:28 AM.
Project "C:\Samples\Batching\Copy01.proj" on node 0 (default targets).
Building with tools version "3.5".
Target "CopyToDest" in file "C:\Samples\Batching\Copy01.proj" from project "C:\Samples\Batching\Copy01.proj":
Using "Copy" task from assembly "Microsoft.Build.Tasks.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "Copy"
  Copying file from "src\Admin\admin_class1.cs" to "dest\Admin\admin_
  class1.cs".
  Command:
  copy /y "src\Admin\admin_class1.cs" "dest\Admin\admin_class1.cs"
  Copying file from "src\Admin\admin_class2.cs" to "dest\Admin\admin_
  class2.cs".
  Command:
  copy /y "src\Admin\admin_class2.cs" "dest\Admin\admin_class2.cs"
Done executing task "Copy".
Task "Copy"
  Copying file from "src\class1.cs" to "dest\class1.cs".
  Command:
  copy /y "src\class1.cs" "dest\class1.cs"
  Copying file from "src\class2.cs" to "dest\class2.cs".
  Command:
  copy /y "src\class2.cs" "dest\class2.cs"
  Copying file from "src\class3.cs" to "dest\class3.cs".
  Command:
  copy /y "src\class3.cs" "dest\class3.cs"
  Copying file from "src\class4.cs" to "dest\class4.cs".
  Command:
  copy /y "src\class4.cs" "dest\class4.cs"
Done executing task "Copy".
Task "Copy"
  Copying file from "src\Utilities\util_class1.cs" to "dest\Utilities\
  util_class1.cs".
  Command:
  copy /y "src\Utilities\util_class1.cs" "dest\Utilities\util_class1.cs"
  Copying file from "src\Utilities\util_class2.cs" to "dest\Utilities\
  util_class2.cs".
  Command:
  copy /y "src\Utilities\util_class2.cs" "dest\Utilities\util_class2.cs"
Done executing task "Copy".
Done building target "CopyToDest" in project "Copy01.proj".
Done Building Project "C:\Samples\Batching\Copy01.proj" (default 
  targets).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Este exemplo anterior é um uso comum de processamento em lotes. Outro está criando um arquivo .zip. Se você precisar seu processo de compilação para criar um arquivo .zip, há algumas tarefas disponíveis que podem ajudar você. Uma tarefa é a tarefa zip da Projeto de tarefas do MSBuild da comunidade. Esta tarefa é usada no arquivo Zip01.proj mostrado na Figura 6 .

A Figura 6 Zip01.proj

<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="3.5">

  <PropertyGroup>
    <SrcFolder>src\</SrcFolder>
  </PropertyGroup>

  <ItemGroup>
    <SourceFiles Include="$(SrcFolder)*" 
                 Exclude="$(SrcFolder).svn\**\*">
    </SourceFiles>
    <SourceFiles Include="$(SrcFolder)class1.cs;$(SrcFolder)class3.cs">
      <Access>public</Access>
    </SourceFiles>
    <SourceFiles Include="$(SrcFolder)class2.cs;$(SrcFolder)class4.cs">
      <Access>private</Access>
    </SourceFiles>
    <SourceFiles Include="$(SrcFolder)Utilities\*" 
                 Exclude="$(SrcFolder)Utilities\.svn\**\*">
      <Group>util</Group>
    </SourceFiles>
    <SourceFiles Include="$(SrcFolder)Admin\*" 
                 Exclude="$(SrcFolder)Admin\.svn\**\*">
      <Group>admin</Group>
      <Access>private</Access>
    </SourceFiles>
  </ItemGroup>

  <!--
  Define default values here.
  -->
  <ItemDefinitionGroup>
    <SourceFiles>
      <!-- Default Group value -->
      <Group>common</Group>
      <!-- Default Access value -->
      <Access>public</Access>
    </SourceFiles>
  </ItemDefinitionGroup>

  <!-- Import SDC tasks from known location in source control -->
  <Import 
    Project="..\Contrib\CommunityTasks\MSBuild.Community.Tasks.Targets"/>

  <Target Name="ZipFiles">
    <!-- Create an item with output in order to get the full path -->
    <PropertyGroup>
      <_ZipWorkingDir>output\</_ZipWorkingDir>
    </PropertyGroup>
    <MakeDir Directories="$(_ZipWorkingDir)"/>
    <!--
    Zip the files based on what zip file they should be placed in.
    Also this task should be passed a full path for the WorkingDirectory.
    -->
    <Zip Files="@(SourceFiles)"
         ZipFileName=
         "$(_ZipWorkingDir)%(SourceFiles.Group).%(SourceFiles.Access).
         zip"/>
  </Target>
</Project>

Este arquivo de projeto declara uma lista de itens, SourceFiles, que contém o conteúdo da pasta src. Esta lista de itens também contém alguns metadados adicionais, grupo e acesso, que é usado para determinar qual arquivo ZIP os arquivos na pasta src devem ser colocados em. O projeto usa o elemento ItemDefinitionGroup para definir valores padrão para o grupo e o acesso. Todos os valores na lista item SourceFiles que não têm um valor de grupo definido explicitamente use o valor comuns" e aqueles que não têm um valor para o Access usa o valor "pública".

A tarefa de CEP é usada dentro o destino ZipFile e o valor $(_ZipWorkingDir)%(SourceFiles.Group). %(SourceFiles.Access).zip is provided for the ZipFileName Property. Em casos que incluem várias expressões batching da mesma lista item, lotes são criadas de combinações exclusivas dos metadados usados. Para esta instrução, lotes serão criadas de valores exclusivos do grupo e o acesso. Como há três valores para o grupo e duas para acesso, haverá no máximo seis lotes. Nesse caso, há apenas quatro lotes consiste as combinações de mostrado na Figura 7 .

fig07.gif

As outras combinações possíveis (Admin/pública e Util/particular) nunca exibido no item de modo que eles não serão usados. Agora sabemos que os quatro arquivos .zip devem ser criados. Se você executar o msbuild comando Zip01.proj /t:ZipFiles, você verá resultados mostrados na Figura 8 .

A Figura 8 Criando arquivos zip com base em metadados

C:\Samples\Batching>msbuild Zip01.proj /nologo
Build started 11/3/2008 11:40:40 PM.
Project "C:\Samples\Batching\Zip01.proj" on node 0 (default targets).
  Creating directory "output\".
  Creating zip file "output\common.public.zip".
    added "src/class1.cs".
    added "src/class3.cs".
  Created zip file "output\common.public.zip" successfully.
  Creating zip file "output\common.private.zip".
    added "src/class2.cs".
    added "src/class4.cs".
  Created zip file "output\common.private.zip" successfully.
  Creating zip file "output\util.public.zip".
    added "src/Utilities/util_class1.cs".
    added "src/Utilities/util_class2.cs".
  Created zip file "output\util.public.zip" successfully.
  Creating zip file "output\admin.private.zip".
    added "src/Admin/admin_class1.cs".
    added "src/Admin/admin_class2.cs".
  Created zip file "output\admin.private.zip" successfully.
Done Building Project "C:\Samples\Batching\Zip01.proj" (default targets).

Build succeeded.
    0 Warning(s)
    0 Error(s)

Nessa figura, você pode ver que a tarefa de zip foi chamada quatro vezes como esperada. O nome de cada arquivo zip contém ambos os valores de metadados usados para o lote.

O comportamento descrito aqui não é limitado a dois valores de metadados ou uma lista de único item. Você está livre para usar como muitos valores de metadados conforme necessário. O comportamento de processamento em lotes em mais de uma lista de item é diferente da que eu descrevi e não será discutido aqui.

Definindo itens dinâmicos e propriedades

Anteriormente discuti itens e propriedades, mas que realmente não defini-los. No MSBuild, itens são ordenados listas de objetos com metadados, dos quais compartilham um tipo de item específico, como o recurso ou Compile. Propriedades são pares de chave-valor. Itens dinâmicos e propriedades são aquelas que você criar dentro de um destino., itens estáticos e propriedades são declaradas fora de um destino.

No 2.0 MSBuild era necessário usar as tarefas CreateItem e CreateProperty para criar itens dinâmicos e propriedades, respectivamente. No MSBuild 3.5, você pode criá-los usando a mesma sintaxe de forma que você usa para itens estáticos. O método preferido é usar os elementos ItemGroup e PropertyGroup dentro de um destino.

Valores estáticos são sempre avaliados antes de executa qualquer destino. Isso significa que cada ItemGroup e PropertyGroup encontrado fora de um destino devem ser expandido que uma etapa de compilação única possa ser executada. Avaliação da propriedade é rápida, mas a avaliação de item pode ser demorada, dependendo do número de valores que contém um item. Por esse motivo, se um item ou propriedade só é usada com um destino, ele deve ser declarado dentro desse destino e não como um valor estático.

Avaliação de propriedade e de item ocorre em várias passagens. Para propriedades estáticas e itens, propriedades é avaliada de cima para baixo (desde o início de um arquivo para o final de um arquivo), incluindo inserir em nenhum projeto importado e itens são avaliados de cima para baixo, incluindo também inserir em nenhum projeto importado. Propriedades dinâmicas e itens são avaliados como eles são executados.

Quando você cria itens e propriedades, a convenção é iniciar o nome de valores internos" com um sublinhado. Itens e propriedades cujos nomes não são iniciados com um sublinhado indicam para os usuários que estão livres para substitui-los. Dado essa convenção, quando você cria as propriedades ou itens, você deve primeiro verifique se eles já foram definidos com uma condição. Isso é útil no caso de alguém já definiu a mesma propriedade ou item que deve substituir seu.

Para demonstrar isso, dê uma olhada nas seguintes declarações extraído Microsoft.Common.targets:

<PropertyGroup>
  <AssemblyName 
    Condition=" '$(AssemblyName)'=='' ">$(MSBuildProjectName)</AssemblyName>
</PropertyGroup>

Esse código, pode ver que a propriedade AssemblyName terá $(MSBuildProjectName) somente se ele não já foi anteriormente declarado.

O MSBuild 3.5 inclui novos recursos relacionados a itens dinâmicos. Por exemplo, no MSBuild 2.0, você não pode modificar o conteúdo de uma lista de item. Depois de você incluir um item em uma lista de itens, você não poderá removê-lo. A solução alternativa para essa limitação é para criar um novo item, excluindo o valor que você não está interessado. MSBuild 3.5 agora oferece suporte a remover valores de itens através do atributo remover. O arquivo de projeto RemoveItems01.proj, mostrado na Figura 9 , demonstra isso.

A Figura 9 RemoveItems01.proj

<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="3.5">

  <ItemGroup>
    <SrcFiles Include="Batching\src\class1.cs"/>
    <SrcFiles Include="Batching\src\class2.cs"/>
    <SrcFiles Include="Batching\src\class3.cs"/>
  </ItemGroup>

  <Target Name="Remove">
    <Message Text="SrcFiles: @(SrcFiles)"/>
    <ItemGroup>
      <!-- Remove using value provided inside -->
      <SrcFiles Remove="Batching\src\class2.cs"/>
      <!--
      Remove all items in SrcFiles that match the condition. 
      In this case, a single file.
      -->
      <SrcFiles Remove="@(SrcFiles)"
                Condition="'%(Filename)%(Extension)'=='class3.cs'" />
    </ItemGroup>
    <Message Text="SrcFiles: @(SrcFiles)"/>
  </Target>
</Project>

Há duas maneiras de usar o atributo remover na ItemGroup. Você pode remover um valor, passando o valor que foi incluído no atributo Include quando o item foi criado. Essa abordagem é mostrada na expressão. O outro método mostrado na Figura 9 é passar o item inteiro para o atributo remover ao colocar uma condição em elemento.

a Figura 10 mostra o resultado da execução o destino de remover. Com base na saída mostrada na Figura 10 , você pode ver que os arquivos de class2.cs e class3.cs foram removidos com êxito do item SrcFiles. Você também pode adicionar e atualizar metadados usando o elemento ItemGroup dentro de um destino, usando a mesma sintaxe para criação de um item dinâmico. Para aprender a atualizar valores de metadados, dê uma olhada no arquivo UpdateMetadata01.proj mostrado na Figura 11 .

A Figura 10 remover valores de itens

C:\Samples>msbuild RemoveItems01.proj /t:Remove /nologo
Build started 10/26/2008 12:54:11 AM.
Project "C:\Samples\RemoveItems01.proj" on node 0 (Remove target(s)).
  SrcFiles: Batching\src\class1.cs;Batching\src\class2.cs;Batching\src\class3.cs
  SrcFiles: Batching\src\class1.cs
Done Building Project "C:\Samples\RemoveItems01.proj" (Remove target(s)).

Build succeeded.
    0 Warning(s)
    0 Error(s)

A Figura 11 UpdateMetadata01.proj

<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="3.5">
  <ItemGroup>
    <Reference Include="IronPython, Version= ...">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\shared\IronPython-1.1\IronPython.dll</HintPath>
    </Reference>
    <Reference Include="log4net, Version= ...">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\binaries\log4net.dll</HintPath>
    </Reference>
    <Reference Include="nunit.core, ...">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\shared\nunit2.2\nunit.core.dll</HintPath>
    </Reference>
    <Reference Include="nunit.framework, Version= ...">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\shared\nunit2.2\nunit.framework.dll</HintPath>
    </Reference>
    <Reference Include="nunit.util, ...">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\shared\nunit2.2\nunit.util.dll</HintPath>
    </Reference>
  </ItemGroup>

  <Target Name="UpdateSpecificVersion">
    <Message Text="%(Reference.Identity) : %(Reference.
    SpecificVersion)"/>

    <Message Text=" "/><!-- blank line -->
    <Message Text="Update Reference.SpecificVersion to True"/>
    <Message Text=" "/><!-- blank line -->

    <!-- Update all Reference items to be SpecificVersion=True -->
    <ItemGroup>
      <Reference>
        <SpecificVersion>True</SpecificVersion>
      </Reference>
    </ItemGroup>
    <Message Text="%(Reference.Identity) : %(Reference.SpecificVersion)"/>
  </Target>
</Project>

Este arquivo de projeto declara uma lista de item chamada de referência. (Nesses exemplos, nomes de alta segurança são truncados para preservar espaço). Além disso, o projeto contém um destino chamado UpdateSpecificVersion. Este destino usa o elemento ItemGroup para atualizar os metadados SpecificVersion para a lista de item de referência. Os valores para esses metadados são impressas antes e após a atualização. a Figura 12 mostra os resultados.

A Figura 12 a atualização de metadados existentes

C:\Samples>msbuild UpdateMetadata01.proj /t:UpdateSpecificVersion /nologo
Build started 10/29/2008 12:07:39 AM.
Project "C:\Samples\UpdateMetadata01.proj" on node 0 
  (UpdateSpecificVersion target(s)).
  IronPython, Version= ... : False
  log4net, Version= ... : False
  nunit.core, ... : False
  nunit.framework, Version= ... : False
  nunit.util, ... : False

  Update Reference.SpecificVersion to True

  IronPython, Version= ... : True
  log4net, Version= ... : True
  nunit.core, ... : True
  nunit.framework, Version= ... : True
  nunit.util, ... : True
Done Building Project "C:\Samples\UpdateMetadata01.proj" 
  (UpdateSpecificVersion target(s)).

Build succeeded.
    0 Warning(s)
    0 Error(s)

Você pode ver um exemplo de como adicionar metadados no arquivo AddMetadata01.proj incluído com código de exemplo deste artigo. Não VOU entrar em detalhes aqui. Juntamente com modificar a lista de itens propriamente ditos, atualizar metadados e adicionar metadados a itens enquanto um destino está executando é nova funcionalidade que não possui equivalente no MSBuild 2.0.

Estendendo o processo de limpo

Esta seção relaciona usando MSBuild quando você estiver criando projetos C# ou Visual Basic .NET no Visual Studio. O comportamento descrito aqui está contido dentro os arquivos de destino usados para criar esses tipos de projetos e não dentro de MSBuild propriamente dito.

Quando você modifica o processo de compilação para criar novos arquivos, você é responsável para estender o processo de limpo para limpar os arquivos. MSBuild limpa arquivos que ele é responsável por automaticamente, mas ele não é possível fazer isso para arquivos gerados por destinos personalizados. O MSBuild mantém uma lista de item chamada FileWrites que contém os arquivos que precisam ser limpos. Esta lista é mantida em um arquivo na pasta obj que é mencionado como "cache limpo". Você pode colocar valores adicionais para a lista de item FileWrites para que eles são removidos quando o projeto é limpo.

Há duas desvantagens para fazer isso, no entanto. Arquivos sejam excluídos devem residir no caminho de saída, e você deve anexar itens a essa lista antes que o cache de limpo seja gravado em disco, que ocorre no destino a limpar ou IncrementalClean, dependendo do tipo de compilação que é executado. Esses destinos serão chamados antes o destino da compilação conclusão.

No código de exemplo que acompanha este artigo, você encontrará um projeto de Windows Forms, WindowsFormsApplication1, em que defini o destino BeforeBuild da seguinte maneira:

<Target Name="BeforeBuild">
  <ItemGroup>
    <CustomConfigFile Include="$(OutputPath)settings.config" />
  </ItemGroup>
  <!-- Generate config file here -->
  <WriteLinesToFile File="@(CustomConfigFile)"
                    Lines="config entries here"
                    Overwrite="true" />
  <!-- Append to FileWrites so the file will be removed on clean -->
  <ItemGroup>
    <FileWrites Include="@(CustomConfigFile)"/>
  </ItemGroup>
</Target>

Nesse destino, um arquivo chamado, settings.config é criado usando a tarefa WriteLinesToFile. Nesse caso o arquivo contém somente as texto "config entradas aqui", mas ele pode conter valores para o nó appSettings bem como outros tipos de conteúdo. Além disso, a lista de FileWrites está anexada, portanto, o arquivo settings.config será removido quando uma limpeza é executada.

Na Figura 13 você pode ver o resultado de executar o destino de compilação e, em seguida, o destino normal (algumas linhas que não são relevantes são excluídas). Você também pode ver que settings.config foi excluída com êxito quando o destino limpo foi executado. Se as personalizações foram concedidas no destino AfterBuild em vez do destino BeforeBuild, o arquivo pode não foram removido em Limpar. Para fazer isso, é necessário que estender o processo de limpo.

A Figura 13 normal resultados de destino

C:\Samples\WindowsFormsApplication1>msbuild WindowsFormsApplication1.csproj /fl /t:Build;Clean /nologo
...
CoreClean:
  Deleting file "C:\Samples\WindowsFormsApplication1\bin\Debug\settings.config".
  Deleting file "C:\Samples\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.exe.config".
  Deleting file "C:\Samples\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.exe".
  Deleting file "C:\Samples\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.pdb".
  Deleting file "C:\Samples\WindowsFormsApplication1\obj\Debug\WindowsFormsApplication1.Form1.resources".
  Deleting file "C:\Samples\WindowsFormsApplication1\obj\Debug\WindowsFormsApplication1.Properties.Resources.resources".
  Deleting file "C:\Samples\WindowsFormsApplication1\obj\Debug\WindowsFormsApplication1.csproj.GenerateResource.Cache".
  Deleting file "C:\Samples\WindowsFormsApplication1\obj\Debug\WindowsFormsApplication1.exe".
  Deleting file "C:\Samples\WindowsFormsApplication1\obj\Debug\WindowsFormsApplication1.pdb".
Done Building Project "C:\Samples\WindowsFormsApplication1\WindowsFormsApplication1.csproj" (Build;Clean target(s)).

Build succeeded.
    0 Warning(s)
    0 Error(s)

Há dois destinos que podem ser substituídos em seu arquivo de projeto que são executados quando o destino de limpo for chamado. Esses destinos são BeforeClean e AfterClean. Se você definir um desses destinos no arquivo de projeto, o destino será chamado no momento apropriado. Esses destinos devem ser definidos após a instrução de importação para Microsoft.CSharp.targets ou Microsoft.VisualBasic.targets.

Outra abordagem é usar as dependências de destino. Você pode estender a propriedade CleanDependsOn para inserir seu próprio destino para o processo de limpeza. Dê uma olhada as personalizações para o projeto WindowsFormsApplication2 mostrado na Figura 14 , que ocorrem após a instrução de importação para Microsoft.CSharp.targets.

A Figura 14 WindowFormsApplication2

<Target Name="AfterClean">
  <Message Text="AfterClean target executed"/>
</Target>
<!-- Inject a custom target into Clean by extending CleanDependsOn -->
<PropertyGroup>
  <CleanDependsOn>
    CustomBeforeClean;
    $(CleanDependsOn);
    CustomAfterClean
  </CleanDependsOn>
</PropertyGroup>
<Target Name="CustomBeforeClean">
  <Message Text="CustomBeforeClean target executed"/>
</Target>  
<Target Name="CustomAfterClean">
  <Message Text="CustomAfterClean target executed"/>
</Target>

Nesta listagem, o destino AfterClean foi substituído e dois destinos são colocados na propriedade CleanDependsOn. Esses destinos apenas exibem uma mensagem para demonstrar que o destino foi executado, mas em um processo de compilação real foi possível limpar recursos. a Figura 15 mostra os resultados de chamar o destino normal (algumas linhas foram reduzidas para preservar espaço). Conforme o esperado, o público-alvo foram chamado nos momentos apropriados.

A Figura 15 normal resultados de destino

C:\Samples\WindowsFormsApplication2>msbuild WindowsFormsApplication2.csproj /t:Clean /nologo
Build started 10/26/2008 1:17:40 PM.
Project "C:\Samples\WindowsFormsApplication2\WindowsFormsApplication2.csproj" on node 0 (Clean target(s)).
  CustomBeforeClean target executed
CoreClean:
  Deleting file "\bin\Debug\WindowsFormsApplication2.exe".
  Deleting file "\bin\Debug\WindowsFormsApplication2.pdb".
  Deleting file "\obj\Debug\WindowsFormsApplication2.Form1.resources".
  Deleting file "\obj\Debug\WindowsFormsApplication2.Properties.Resources.resources".
  Deleting file "\obj\Debug\WindowsFormsApplication2.csproj.GenerateResource.Cache".
  Deleting file "\obj\Debug\WindowsFormsApplication2.exe".
  Deleting file " \obj\Debug\WindowsFormsApplication2.pdb".
AfterClean:
  AfterClean target executed
CustomAfterClean:
  CustomAfterClean target executed
Done Building Project "C:\Samples\WindowsFormsApplication2\WindowsFormsApplication2.csproj" (Clean target(s)).

Build succeeded.
    0 Warning(s)
    0 Error(s)

Organizando destinos

Conforme o crescimento de um processo de compilação, você precisa organizar onde residem as partes diferentes. Geralmente, é melhor usar arquivos com responsabilidades dedicadas e cujo conteúdo reflete suas finalidades. Para resolver essa situação, ao definir um processo de compilação para um produto, é útil criar três arquivos:

  • MyProduct.settings.targets
  • MyProduct.targets
  • MyProduct.csproj

Você pode usar MyProduct.setting.targets para conter várias propriedades relacionadas a compartilhada utilitários usados durante os processos de compilação e implantação bem como outras configurações comuns. Por exemplo, se você estiver usando o NUnit, uma configuração que você pode colocar neste arquivo é o local das referências NUnit. Quando propriedades são declaradas neste arquivo, eles devem sempre ser declarados para que elas não substituirão qualquer valor existente. Este arquivo contém as configurações padrão. (Essa é a mesma situação conforme abordado na seção "itens dinâmicos de definição e propriedades".)

MyProduct.targets contém os elementos UsingTask declarar as tarefas personalizadas que estão sendo usadas, bem como qualquer alvo compartilhado. Este arquivo define como o produto é criado no sentido de que a maioria dos destinos, além de suas dependências deve estar contida nesse arquivo.

MyProduct.csproj define o que é criado. Este arquivo de projeto declara todos os itens e propriedades que o arquivo MyProduct.targets usa em seus destinos. O contrato entre esses dois arquivos é representado por esses itens compartilhados e propriedades. Este arquivo também deve importar MyProduct.settings.targets na parte superior e arquivo de MyProduct.targets na parte inferior.

Ao fazer para poder substituir os valores em MyProduct.settings.targets e preparar propriedades e os itens para MyProduct.targets. Se você relativas a esse processo de compilação ao de um projeto C#, MyProduct.targets é equivalente a Microsoft.CSharp.targets (e Microsoft.Common.targets), e os outros dois arquivos formam o arquivo de projeto real.

Usando caracteres curinga

Arquivos de projeto Visual Studio não use caracteres curinga para preencher os itens que o Visual Studio irá ser interagindo com. Por exemplo, você pode definir o item de compilação, que contém uma lista dos arquivos que serão enviadas para o compilador, da seguinte maneira:

<ItemGroup>
  <Compile Include="src\*"/>
</ItemGroup>

Essa expressão seria coloque todos os arquivos na src pasta para o item de compilação e, enquanto isso é válido e irá funcionar, ele não é a melhor abordagem sejam projetos casuais fora. Qualquer edição feita no arquivo de projeto pelo Visual Studio fará com que este item expandir para listar cada arquivo individualmente. Além disso, essa abordagem torna fácil esquecer verificar em arquivos de novo ou removido do controle de origem da sua máquina local. Em vez de usar caracteres curinga para itens mantidas pelo Visual Studio, você deve incluir explicitamente cada arquivo.

Neste artigo que será executada uma olhada em algumas recomendações chaves que você pode usar para criar melhor criar processo para seus produtos. Como com todas as práticas recomendadas, haverá situações nas quais essas regras podem não se aplicam a cem % do tempo e precisará ser dobrados um pouco. A melhor maneira de saber quais essas práticas funciona para você é empregá-los e ver quais trabalhar e quais falharem. Eu poderia ser liquidado saber textos, boas e ruim, dessas práticas. Você pode contatar me em sayed.hashimi@gmail.com.

Eu gostaria de agradecer a Dan Moseley, da equipe do MSBuild e Brian Kretzler por sua ajuda imprescindíveis neste artigo.

sayed Hashimi Ibrahim tem trabalhado com MSBuild desde que os bits de visualização antecipada do Visual Studio 2005 lançados. Ele é autor do Inside the Microsoft Build Engine: Using MSBuild and Team Foundation Build (Microsoft Press, 2009), o co-autor de Deploying .NET Applications: Learning MSBuild e ClickOnce NET (Apress, 2006) e foi escrito para várias publicações. Ele trabalha em Jacksonville, Flórida, como consultor e instrutor, com experiência em indústrias financeiras, treinamento e coleção. Você pode acessar Sayed no seu blog, sedodream.com.