Este artigo foi traduzido por máquina.

O MSBuild

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

Sayed Ibrahim Hashimi

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

Este artigo discute:
  • Usar compilação incremental
  • Criar tarefas personalizadas
  • Gerenciando referências de compilação
  • Criando árvores de fonte grande
Este artigo usa as seguintes tecnologias:
O MSBuild, Visual Studio

Conteúdo

Usando versões incrementais
Criar tarefas personalizadas
Gerenciando referências de compilação
Criando árvores de fonte grande

Este artigo é a segunda parte minha discussão das práticas recomendadas que os desenvolvedores devem seguir ao usando MSBuild, o mecanismo usado pelo Visual Studio para criar projetos gerenciados. Na parte 1, descrevi alguns práticas básicas e técnicas que se aplicam a mais cada projeto. Aqui na parte 2, descreverei técnicas mais específicas para criar configurações que exigem gramatura maior personalização amplamente devido a seu tamanho. Os tópicos que VOU me concentrar em estão usando compilações incrementais, criar tarefas personalizadas, gerenciar as referências de compilação e criar árvores de fonte grande.

Se você não estiver familiarizado com o MSBuild, vejamos meus artigos do MSDN anteriores: " Práticas recomendadas para criar confiável builds, parte 1", " 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."

Usando versões incrementais

Como cria se tornar mais complexa, aumenta o tempo necessário para criar aplicativos. Criação de um produto grande pode levar várias horas. Como compilações são demoradas, você deve garantir que somente desatualizados destinos são executados. Esse conceito, conhecido como criação incremental, é suportado no MSBuild por meio dos entradas e saídas de atributos do elemento de destino. Quando o MSBuild encontra um destino com entradas e saídas, ele compara os carimbos de hora dos arquivos de saída com os carimbos de data / hora de arquivos de entrada. Se os arquivos de entrada forem mais recentes, o destino será executado; caso contrário, ele será ignorado.

Para demonstrar esse mecanismo, usarei o exemplo a partir da seção "lotes tarefas" (na parte 1 deste artigo) no qual um conjunto de arquivos é copiado de um local para outro. A listagem na Figura 1 mostra o arquivo de Copy02.proj, uma versão modificada do exemplo.

A Figura 1 Copy02.proj

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

  <PropertyGroup>
    <SourceFolder>src\</SourceFolder>
    <DestFolder>dest\</DestFolder>
  </PropertyGroup>

  <ItemGroup>
    <!-- Get all files under src\ except svn files -->
    <SrcFiles Include="$(SourceFolder)**\*" Exclude="**\.svn\**\*"/>
  </ItemGroup>
  <Target Name="CopyToDest"
          Inputs="@(SrcFiles)"
          Outputs=
          "@(SrcFiles->'$(DestFolder)%(RecursiveDir)%(Filename)%(Extension)')">
    <Copy SourceFiles="@(SrcFiles)"
          DestinationFiles="@(SrcFiles->'$(DestFolder)%(RecursiveDir)%(Filename)%(Extension)')"
          />
  </Target>

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

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

A principal diferença entre esse arquivo e o arquivo Copy01.proj é o uso de entradas e saídas. Aqui, o destino CopyToDest declara os arquivos sendo copiados como entradas e os arquivos de destino como saídas. Se os arquivos copiados estão atualizados, o destino será ignorado. Vejamos como isso funciona na Figura 2 .

A Figura 2 ignorando o destino CopyToDest

C:\Samples\Batching>msbuild Copy02.proj /fl /nologo
Build started 10/26/2008 6:15:37 PM.
Project "C:\Samples\Batching\Copy02.proj" on node 0 (default targets).
Skipping target "CopyToDest" because all output files are up-to-date with respect to the input files.
Done Building Project "C:\Samples\Batching\Copy02.proj" (default targets).

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

Em determinadas circunstâncias, algumas das saídas são atualizadas, mas não todos eles. Quando o MSBuild detecta essa situação, um subconjunto de entradas é passado para o destino a ser executada.

Aqui está um exemplo. O arquivo Copy03.proj tem o mesmo conteúdo como Copy02.proj mas com um destino adicional, DeleteRandomOutputFiles, que exclui os dois arquivos do diretório de saída. :

<Target Name="DeleteRandomOutputFiles">
  <!-- Arbitrarily delete two files from dest folder  -->
  <ItemGroup>
    <_RandomFilesToDelete Include="$(DestFolder)class3.cs"/>
    <_RandomFilesToDelete Include="$(DestFolder)Admin\admin_class2.cs"/>
  </ItemGroup>

  <Delete Files="@(_RandomFilesToDelete)"/>   
</Target>

Como esses dois arquivos são excluídos proveniente do destino, as entradas de destino não são mais atualizadas, para o destino não deve ser ignorado. Se você executar o comando msbuild Copy03.proj /t:DeleteRandomOutputFiles; CopyToDest após CopyToDest foi chamada depois de já, você verá os resultados mostrados na Figura 3 .

A Figura 3 parcialmente criando um destino

C:\Samples\Batching>msbuild Copy03.proj /t:DeleteRandomOutputFiles;CopyToDest
Build started 10/26/2008 11:09:56 PM.
Project "C:\Samples\Batching\Copy03.proj" on node 0 (DeleteRandomOutputFiles;CopyToDest target(s)).
  Deleting file "dest\class3.cs".
  Deleting file "dest\Admin\admin_class2.cs".
CopyToDest:
Building target "CopyToDest" partially, because some output files are out of date with respect to their input files.
Copying file from "src\Admin\admin_class2.cs" to "dest\Admin\admin_class2.cs".
Copying file from "src\class3.cs" to "dest\class3.cs".
Done Building Project "C:\Samples\Batching\Copy03.proj" (DeleteRandomOutputFiles;CopyToDest target(s)).

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

A saída, você pode ver que o destino CopyToDest parcialmente foi criado, que significa que uma lista parcial das suas entradas foi fornecida para o destino. O destino era apenas copiar dois arquivos em vez de muito mais. Para compilações complexas, construção incremental é essencial e você sempre deve criar destinos que oferecem suporte a esse mecanismo.

Criar tarefas personalizadas

Quando você escreve uma tarefa personalizada, criá-lo a ser como reutilizável possível. Por exemplo, uma tarefa de SendEmail é mais útil de uma tarefa SendEmailToJoe. Aqui estão algumas diretrizes a serem seguidas ao criar tarefas personalizadas. Irá expandir cada diretriz em toda esta seção.

  • Estende a tarefa ou a classe ToolTask.
  • Registrar mensagens de forma apropriada.
  • As tarefas devem comunicar de forma transparente.
  • Use o atributo obrigatório para todos os parâmetros de entrada necessários.
  • Use o atributo de saída em todas as propriedades que arquivos de projeto podem estar interessados.
  • Use ITaskItem para referências de arquivo em vez de seqüência de caracteres.
  • Sempre transferir metadados para novos valores de saída relacionadas a entradas.

Quando você estiver escrevendo tarefas, você não precisará implementar a interface de ITask (Microsoft.Build.Framework do) diretamente. A abordagem mais fácil é estender a tarefa ou ToolTask classe, que são encontradas no namespace Microsoft.Build.Utilities do. Se você estiver criando uma tarefa que envolve um executável, você deve usar a classe ToolTask. Para outras situações, use a classe de tarefas.

Ao estender uma dessas classes, você deve usar a propriedade de log para ajudar a enviar mensagens para loggers anexados. Você nunca deve confiar em técnicas como Console.WriteLine para enviar mensagens para o console. Quando você faz logon mensagens, é importante fazê-los ao nível apropriado para que loggers podem realizar seus trabalhos com mais eficiência. Por exemplo, você deve não registrar eventos como erros quando forem realmente apenas avisos. Além disso, se a tarefa falhar, o contrato da interface ITask é retornar False do método Execute. Nesse caso, você deve fazer também sempre pelo menos um erro para que um usuário Saiba como resolver um problema. Se o método Execute retorna True, nenhum erro deve ser registrado. Isso facilmente é atingido usando uma instrução return de retorno! Log.HasLoggedErrors.

Outro princípio crítico na criação de tarefas é que eles ser independente. A única informação que uma tarefa precisa saber sobre uma compilação é as propriedades que são passadas para ele. O oposto também é verdadeiro: o arquivo de projeto só deve coletar informações de uma tarefa por suas saídas. Se você criar as tarefas que tenham outros meios de comunicação com arquivos de projeto consome ou loggers, fortemente são união, aumentando sua complexidade e reduzindo sua manutenção. Você sempre deve tomar uma abordagem o " que você vê é o resultado" para criação de tarefas no sentido de que seu código só pode ver da tarefa declaradas entradas e saídas no arquivo de projeto.

Propriedades de entrada que precisa de uma tarefa devem ser decoradas com o atributo Required (encontrado no namespace Microsoft.Build.Framework do). O MSBuild impõe esse requisito em tempo de execução e registra um erro informando que necessário propriedade foi fornecida. Se a tarefa contém propriedades que arquivos de projeto podem estar interessados em, você deve colocar o atributo de saída (Microsoft.Build.Framework do) nessas propriedades. Como parâmetros de saída devem ser decorados explicitamente no código com o atributo de saída, você deve mensagem ao lado da exposição algumas propriedades adicionais de como isso pode impede você de precisar reimplantar a tarefa simplesmente outra propriedade de saída.

Se uma tarefa é criada para executar uma ação em um arquivo ou um conjunto de arquivos, esses arquivos devem ser passados e saia da tarefa usando a interface de ITaskItem em vez de apenas caminhos de seqüência de caracteres. Por exemplo, considere as duas implementações mostradas na Figura 4 .

Figura 4 exemplos de declarando propriedades com base em arquivos

//This example shows how NOT to declare file based properties
public class Move1 : Task
{
    [Required]
    public string SourceFile
    { get; set; }

    [Required]
    [Output]
    public string DestinationFile
    { get; set; }

    public override bool Execute()
    {
        //TODO: Implementation here
        throw new NotImplementedException();
    }
}
//This example shows how to declare file based properties
public class Move2 : Task
{
    [Required]
    public ITaskItem SourceFile
    { get; set; }

    [Required]
    [Output]
    public ITaskItem DestinationFile
    { get; set; }

    public override bool Execute()
    {
        //TODO: Implementation here
        throw new NotImplementedException();
    }
}

A classe Move1 usa uma seqüência de caracteres para passar um arquivo em e fora da tarefa. Move2 expõe instâncias de ITaskItem em vez disso. Quando você passar uma seqüência de caracteres, todos, você obtém é uma seqüência simples. Por outro lado, objetos de ITaskItem têm metadados associados a eles.

Da perspectiva do consumidor, as implementações são o mesmo; MSBuild trata qualquer conversões necessárias. O destino mostrado aqui mostra como ambas essas tarefas podem ser consumidas:

<Target Name="Example">

  <ItemGroup>
    <FileToMove Include="class1.cs"/>
  </ItemGroup>

  <Move1 SourceFile="@(FileToMove)" DestinationFile="dest\class1.cs" />
  <Move2 SourceFile="@(FileToMove)" DestinationFile="dest\class1.cs" />
</Target>

Portanto, você pode perguntar por que ele importa se a tarefa não usa metadados? Se você começar a gravar tarefas usando ITaskItem para referências com base em arquivo, as tarefas serão muito mais flexíveis. Além disso, neste exemplo simples, essas tarefas aceitar apenas um único arquivo ao mesmo tempo. Seria melhor criar uma tarefa que pudessem mover vários arquivos. Nesse caso, as propriedades devem ser definidas como [] de ITaskItem em vez de ITaskItem. Fazendo isso você reduzir a sobrecarga de ter que criar várias instâncias da tarefa e fazer trabalho mais fácil para usuários da tarefa.

Finalmente, sempre transferir metadados para novos valores de saída relacionadas a entradas. Em casos onde você aceita alguns itens de entrada e depois criar novas saídas relacionados diretamente às entradas, todos os metadados da fonte original devem ser transferido para os itens de saída recém-criado, até mesmo metadados que você não conhece. Por exemplo, se você criar uma tarefa que cria arquivos de objeto a partir de arquivos C++, a propriedade de entrada para os arquivos de origem deve ser declarada como [] ITaskItem e deve portanto, a saída para os arquivos de objeto. Quando você cria os itens de saída, cada arquivo de objeto, em seguida, terá os metadados do item entrado correspondente atribuído a ele.

Isso é importante porque ela permite metadados para ser adicionado ao item em cada etapa sucessiva no pipeline. Por exemplo, você pode adicionar uma parte dos metadados chamado DoNotLink a um dos arquivos C++. O compilador não reconhece esses metadados, mas ele passa por meio para o item de arquivo de objeto apropriado e, em seguida, o destino do link excluirá esse arquivo de objeto.

Gerenciando referências de compilação

O aspecto mais importante de lidar com referências de compilação é para nunca fazer referência a assemblies que residem no cache de assembly global (GAC). O GAC foi projetado estritamente para uso em tempo de execução e nunca deve foram incluído no caminho de pesquisa de referência. Uma abordagem melhor é usar referências de projeto ou referências de assembly com nomes fortes.

Quando você estiver criando referências de assembly, você pode usar um HintPath para ajudar a determinar onde procurar para o assembly do MSBuild. Por exemplo, a seguinte referência é definida corretamente:

<Reference Include="IronMath, Version=1.1.0.0, Culture=neutral, 
                 PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
  <HintPath>..\contrib\IronPython-1.1\IronMath.dll</HintPath>
</Reference>

Se você perceber que está definindo uma HintPath para muitas referências, eis um truque adicional que pode ajudá-lo: estender o caminho de pesquisa de referência adicionando seu próprio nome de diretório. O item que contém a lista de caminhos usado para resolução de referência no C# e projetos do Visual Basic .NET é o item AssemblySearchPaths. Se você adicionar o caminho para este item após a instrução de importação para Microsoft.CSharp.targets ou Microsoft.VisualBasic.targets, esse caminho será o primeiro local em que marcada. Você pode implementar isso com uma propriedade declarada da seguinte maneira:

<PropertyGroup>
  <AssemblySearchPaths>
    ..\..\MyReferences\;
    $(AssemblySearchPaths)
  </AssemblySearchPaths>
</PropertyGroup>

Você sempre deve usar caminhos relativos para valores a AssemblySearchPaths e HintPath. Se você especificar esses valores usando caminhos completos, será difícil criar os projetos em outros computadores.

Para grandes projetos você deve evitar definindo o sinalizador CopyLocal como True para referências. Quando arquivos são marcados a serem copiados localmente, cada projeto que faz referência esse projeto irá obter uma cópia da sua cópia locais referências. Considere este exemplo:

  • ClassLibray1 — contém 10 CopyLocal referências
  • ClassLibrary2 — contém 5 CopyLocal faz referência e referências ClassLibrary1
  • WindowsFormApp1 — ClassLibrary2 de referências

Nessa situação, todas as referências de ClassLibrary1 serão copiadas para as pastas de saída para ClassLibrary1, ClassLibrary2 e WindowsFormApp1. Todas as referências de ClassLibrary2 serão copiadas para as pastas de saída para ClassLibrary2 e WindowsFormApp1. Para que você tenha 10 * 3 + 5 * 2 = 40 arquivo copia para uma compilação completa, e este exemplo consiste somente três projetos. Imagine como esse comportamento afeta compilações consiste em uma centena ou mais projetos.

Para evitar o comportamento de local da cópia, você pode definir metadados particulares valor a referência como False. Por exemplo, você pode modificar uma referência para impedir comportamento de local de cópia, fazendo o seguinte:

<Reference Include="IronMath, Version=1.1.0.0, Culture=neutral, 
               PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
  <HintPath>..\contrib\IronPython-1.1\IronMath.dll</HintPath>
  <Private>False</Private>
</Reference>

Como os metadados adicionais está definido na referência, o arquivo não será copiado localmente. Uma abordagem ainda melhor é usar o elemento ItemDefinitionGroup, que define o valor de metadados padrão para itens. No caso de referências, você pode definir os metadados particulares a ser False por padrão, incluindo o seguinte trecho em algum lugar no seu arquivo de projeto.

<ItemDefinitionGroup>
  <Reference>
    <Private>False</Private>
  </Reference>
</ItemDefinitionGroup>

Como Visual Studio definido esse valor de metadados por padrão, ele será usado para referências que não foram especificamente marcadas a serem copiados localmente.

Se você tiver uma compilação grande e você deseja se certificar de que nenhum arquivo é copiado localmente, você pode substituir o destino _CopyFilesMarkedCopyLocal, que é responsável pelo comportamento de local da cópia. Se você substituir este destino após a instrução de importação para Microsoft.CSharp.targets ou Microsoft.VisualBasic.targets, é garantia de que nenhum arquivo será copiado localmente. Ele geralmente não é recomendado para substituir qualquer elemento que inclua um sublinhado à esquerda, mas isso é um caso especial. Referência local copiando pode causar um muito tempo perdido e espaço na unidade durante a grandes compilações, para substituir o elemento pode ser justificado.

Criando árvores de fonte grande

Quando você está lidando com um grande número de projetos (mais de 100), você precisa organizar seus projetos e tiver um processo de compilação que é eficiente ainda flexível o suficiente para atender às necessidades de cada um dos projetos. Nesta seção, descrevo uma forma de organizar seu código-fonte, além de uma abordagem para integrar um processo de criação dessa estrutura. A estrutura que descrevo não atender a cada equipe ou todos os produtos, mas as idéias importantes para retirar são como modularizar seu processo de compilação e como apresentar elementos comuns de compilação em todos os produtos que são criados.

Você pode organizar sua fonte em árvores de projetos relacionados, com os projetos mais comuns na parte superior. Essa organização pressupõe que projetos necessárias para criar qualquer um dos projetos abaixo na árvore e potencialmente irmão projetos, mas eles não devem compilado diretamente projetos que consta os nós acima deles. Por exemplo, a Figura 5 mostra as relações de dependência de vários projetos fictícios.

fig05.gif

A Figura 5 dependências de projeto (clique na imagem para aumentar a exibição)

Aqui temos dois produtos, SCalculator e SNotepad e quatro bibliotecas que eles dependem. Nós pode organizar esses projetos em uma árvore semelhante a Figura 6 .

A Figura 6 SCalculator e SNotepad árvore organizacional

ROOT
+---Common
    +---Common.IO
    ¦   ¦
    +---Common.UI
    ¦   ¦
    ¦   +---Common.UI.Editors
    ¦   
    +---Contrib
    ¦
    +---Products
    ¦   ¦
    ¦   +---SCalculator
    ¦   ¦   
    ¦   +---SNotepad
    ¦       
    +---Properties

Como todos os projetos dependem do projeto comum, é diretamente sob o nó de raiz. Os projetos SCalculator e SNotepad são colocados dentro da pasta de produtos.

O que precisa aqui é uma estratégia que permite aos desenvolvedores trabalhando em subárvores específicas para criar as partes precisam mas não necessariamente toda a estrutura. Você pode obter isso usando uma convenção em que cada pasta contém três arquivos MSBuild:

  • NodeName.setting
  • NodeName.traversal.targets
  • Dirs.PROJ

NodeName é o nome do nó atual — por exemplo, raiz, comum ou common.ui.editors. O arquivo NodeName.setting contém quaisquer configurações capturadas como propriedades ou itens que são usadas durante o processo de compilação. Por exemplo, algumas configurações incluídas aqui podem ser BuildInParallel, configuração e plataforma. O arquivo NodeName.traversal.targets contém os destinos são usados para construir os projetos. Finalmente, o arquivo dirs.proj mantém uma lista de projetos (no item ProjectFiles) que precisam ser compilados para essa subárvore.

Os arquivos NodeName.setting e NodeName.traversal.targets sempre irão importar o files—root.setting correspondente nível superior e root.traversal.targets. Esses arquivos de nível superior contêm as configurações globais e destinos e os arquivos de nível de nós são onde as personalizações podem ser inseridas. Em muitos casos esses arquivos de nível de nós precisam importar somente o arquivo de raiz.

a Figura 7 mostra o conteúdo do arquivo root.traversal.targets. Fundamentalmente, há três alvos neste arquivo: criar, recriar e limpa. As propriedades e outros destinos são existe simplesmente oferecer suporte a essas três alvos. Esse arquivo usa o item ProjectFiles, que está declarado no arquivo de dirs.proj para aquele diretório específico. Os requisitos para o arquivo dirs.proj são para:

  1. Definir todos os projetos a ser criado usando ProjectFiles.
  2. Importe o arquivo NodeName.setting na direção da parte superior.
  3. Importe o arquivo NodeName.targets na parte inferior.

Figura 7 A root.traversal.targets arquivo

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

  <!-- Targets used to build all the projects -->

  <PropertyGroup>
    <BuildDependsOn>
      $(BuildDependsOn);
      CoreBuild
    </BuildDependsOn>
  </PropertyGroup>

  <Target Name="Build" DependsOnTargets="$(BuildDependsOn)" />
  <Target Name="CoreBuild">
    <!--
    Properties BuildInParallel and SkipNonexistentProjects
    should be defined in the .setting file.
    -->
    <MSBuild Projects="@(ProjectFiles)"
             BuildInParallel="$(BuildInParallel)"
             SkipNonexistentProjects="$(SkipNonexistentProjects)"
             Targets="Build"
             />
  </Target>

  <PropertyGroup>
    <RebuildDependsOn>
      $(RebuildDependsOn);
      CoreRebuild
    </RebuildDependsOn>
  </PropertyGroup>
  <Target Name="Rebuild" DependsOnTargets="$(RebuildDependsOn)" />
  <Target Name="CoreRebuild">
    <MSBuild Projects="@(ProjectFiles)"
             BuildInParallel="$(BuildInParallel)"
             SkipNonexistentProjects="$(SkipNonexistentProjects)"
             Targets="Rebuild"
             />
  </Target>

  <PropertyGroup>
    <CleanDependsOn>
      $(CleanDependsOn);
      CoreClean
    </CleanDependsOn>
  </PropertyGroup>
  <Target Name="Clean" DependsOnTargets="$(CleanDependsOn)" />
  <Target Name="CoreClean">
    <MSBuild Projects="@(ProjectFiles)"
             BuildInParallel="$(BuildInParallel)"
             SkipNonexistentProjects="$(SkipNonexistentProjects)"
             Targets="Clean"
             />
  </Target>
</Project>

O arquivo dirs.proj deve incluir todos os projetos no diretório assim como todos os projetos em subdiretórios. Pode incluir normais projetos do MSBuild, como projetos C# ou Visual Basic .NET ou outros projetos dirs.proj (para subdirectores). Este arquivo não deve incluir projetos que existe nos diretórios acima dela. O arquivo dirs.proj deve assumir que projetos necessários que são superiores na estrutura de diretórios já são criados.

Se você criar um projeto que tem uma referência de projeto para um projeto que é maior na estrutura de diretório e o projeto está desatualizado, ele será criado automaticamente. Como resultado, o arquivo dirs.proj não precisa especificar ao criar projetos de nível superior. Além disso, para compilações grande, é melhor usar referências de arquivo em vez de referências do projeto. Com essa abordagem, se você alternar para referências de projeto, você não precisará modificar seu processo de compilação, somente as referências.

Veja o conteúdo do arquivo root.setting:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="3.5">
  <!--
  Global properties defined in this file
  -->
  <PropertyGroup>
    <BuildInParallel 
      Condition="'$(BuildInParallel)'==''">true</BuildInParallel>
    <SkipNonexistentProjects 
      Condition="'$(SkipNonexistentProjects)'==''">false</SkipNonexistentProjects>
  </PropertyGroup>

</Project>

Este arquivo contém apenas duas propriedades: BuildInParallel e SkipNonexistentProjects. É importante observar que essas propriedades usar condições para garantir que os valores de pré-existentes não são substituídos, que permite que essas propriedades para ser personalizado facilmente. a Figura 8 contém o conteúdo do arquivo dirs.proj para o diretório raiz.

Figura 8 conteúdo do dirs.proj arquivo para o diretório raiz

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

  <!-- Insert any customizations for settings here -->

  <Import Project="root.setting"/>

  <!-- Define all ProjectFiles here -->
  <ItemGroup>
    <ProjectFiles Include="Common\dirs.proj"/>
  </ItemGroup>

  <Import Project="root.traversal.targets"/>

  <!-- Insert any customizations for targets here -->

</Project>

Este arquivo dirs.proj atende a todas as três condições listadas anteriormente. Se todas as personalizações para valores no arquivo root.settng precisam ser especificadas, eles devem ser colocados acima a importação desse arquivo e quaisquer personalizações para destinos devem ser colocadas após a importação desse arquivo. Este arquivo dirs.proj define o item ProjectFiles para incluir apenas o arquivo de Common\dirs.proj, que é responsável pela criação de seu conteúdo. Não há nenhum outros projetos na pasta raiz que precisam ser compilados. a Figura 9 mostra o arquivo Common\dirs.proj.

Figura 9 O ficheiro Common\dirs.proj

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

  <!-- Insert any customizations for settings here -->
  <PropertyGroup>
    <SkipNonexistentProjects>true</SkipNonexistentProjects>
  </PropertyGroup>


  <Import Project="common.setting"/>

  <!-- Define all ProjectFiles here -->
  <ItemGroup>
    <ProjectFiles Include="Common.csproj"/>
    <ProjectFiles Include="Common.IO\dirs.proj"/>
    <ProjectFiles Include="Common.UI\dirs.proj"/>
    <ProjectFiles Include="Products\dirs.proj"/>
  </ItemGroup>

  <Import Project="common.traversal.targets"/>

  <!-- Insert any customizations for targets here -->

  <PropertyGroup>
    < BuildDependsOn >
      CommonPrepareForBuild;
      $(BuildDepdnsOn);
      CommonBuildComplete;
    </BuildDependsOn>
  </PropertyGroup>

  <Target Name="CommonPrepareForBuild">
    <Message Text="CommonPrepareForBuild executed"
             Importance="high"/>
  </Target>
  <Target Name="CommonBuildComplete">
    <Message Text="CommonBuildComplete executed"
             Importance="high"/>
  </Target>
</Project>

Esse arquivo substitui a propriedade SkipNonexistentProjects, defini-la como True. O item ProjetFiles é preenchido com valores de quatro, e alguns destinos são adicionados à lista de dependência de compilação, três das quais são os arquivos de dirs.proj. Se você criar o arquivo Common\dirs.proj com o /t:Build de dirs.proj comando msbuild.exe, você verá que todos os projetos são criados e que os alvos personalizados é executado. Não irá incluir os resultados devido às limitações de espaço, mas a fonte para esses arquivos pode ser baixada com os outros exemplos deste artigo.

Neste artigo que tiveram uma olhada em algumas recomendações chaves que você pode usar para criar melhor criar processos de 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 para saber quais essas práticas funciona para você é simplesmente experimentar cada uma para si mesmo. Eu poderia ser liquidado ouvir os textos, boas e ruim, sobre como eles trabalharam para você.

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 um grau de engenharia de computador da Universidade da Flórida. Ele tem trabalhado com MSBuild desde que os bits de visualização antecipada do Visual Studio 2005 lançados. Ele é autor de Inside the Microsoft Build Engine: Using MSBuild and Team Foundation Build (Microsoft Press, 2009). Ele também é co-autor de Deploying .NET Applications: Learning MSBuild and Click Once (NET Apress, 2006) e escreveu diversas publicações para revistas incluindo MSDN Magazine. 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.