Compilando com MSBuild

Eu estava aprendendo a configurar o Jenkins para compilar alguns projetos .NET e me deparei com uma ferramenta muito boa e que eu estava usando sem saber muito sobre: o MSBuild.

Vou explicar aqui apenas o básico (pois é o que aprendi até o momento), mas com um pouco de pesquisa, vi que tem muita coisa bacana que se pode fazer com o MSBuild.

Uma coisa bacana também é que a Microsoft tornou o MSBuild Open Source em Março deste ano. Você pode ver o projeto no GitHub.

O que é

O MSBuild (ou Microsoft Build Engine) é uma ferramenta para compilação de projetos e/ou soluções do Visual Studio.

O VS (Visual Studio) depende do MSBuild, mas o MSBuild não depende do VS. Ou seja, você pode compilar projetos do VS sem instalar o VS.

Para a compilação, o MSBuild utiliza um arquivo XML com as informações necessárias para o processo. A notação e o schema é muito similar ao Apache Ant (e tem uma versão para .NET, o NAnt), que também usa o conceito de Tasks, Targets, etc.

Quando usar

Temos uma situação típica para o uso: quando utilizamos um servidor de integração contínua e/ou temos um processo de build mais complexo.

Um processo de build mais complexo pode ter: execução de testes, cópia dos arquivos finais para uma determinada pasta, execução de outras ferramentas como assinatura de executáveis, etc.

Executando

Neste post, vou apenas mostrar como executar o MSBuild através do prompt de comando e não com o Jenkins (que pode ficar para um próximo post).

No meu caso, o MSBuild está instalado em C:Program Files (x86)MSBuild12.0BinMSBuild.exe, no seu caso pode ser igual ou ter pequenas variações, como estar apenas em Program Files. Então no prompt basta digitar:

C:\Program Files (x86\)MSBuild12.0\Bin\MSBuild.exe 

Onde nome_do_arquivo é o arquivo que você salvar as configurações do MSBuild (pode ser qualquer extensão).

Conceitos

O arquivo

O arquivo de compilação do MSBuild é composto por um elemento chamado Project, este é a raiz do arquivo. Abaixo está um arquivo do MSBuild "vazio".

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

Dentro do nó Project, podemos ter os elementos:

Também existe um tipo de elemento que é chamado de Task, não o coloquei na lista anterior para não confundir, mas vou explicá-lo mais abaixo.

PropertyGroup

Fazendo uma analogia, pense neste elemento como sua lista de constantes. As propriedades são pares chave/valor, filhos de PropertyGroup. Veja um exemplo (não se preocupe em entender agora):

<PropertyGroup>
<Configuration>Release</Configuration>
<BuildPlatform>Any CPU</BuildPlatform>
</PropertyGroup>

No exemplo, definimos duas propriedades, uma chamada Configuration e outra BuildPlatform. Sim, podemos dar o nome que quisermos e o valor que quisermos. Mais um exemplo (tirado da própria documentação):

<PropertyGroup>
<VisualStudioWebBrowserHomePage>
$(registry:HKEY_CURRENT_USERSoftwareMicrosoftVisualStudio12.0WebBrowser@HomePage)
</VisualStudioWebBrowserHomePage>
<PropertyGroup>

O XML acima define a propriedade VisualStudioWebBrowserHomePage com o valor que está no registro da máquina. Show de bola não?

Para utilizar a propriedade no resto do arquivo, basta usar a sintaxe $(NomeDaPropriedade). No nosso caso seria $(Configuration) e $(BuildPlatform).

ItemGroup

O nó ItemGroup armazena listas e estas geralmente são arquivos ou pastas. Os valores são agrupados pelos nomes que você atribuir aos elementos, exemplo:

<ItemGroup>
<ReleaseFiles Include="*binRelease*.exe" Exclude="*binRelease*vshost*.exe" />
<ReleaseFiles Include="*binRelease*.dll" Exclude="*binRelease*Test*.dll" />
<ReleaseFiles Include="*binRelease*.config" Exclude="*binRelease*vshost*.config" />
<TestResultsDir Include="C:TestResults" />
</ItemGroup>

Vamos falar do Include e Exclude. Estas duas propriedades (não confunda com as do PropertyGroup) podem ser declaradas simultaneamente ou somente uma delas, por exemplo:

<ItemGroup>
<ReleaseFiles Include="*binRelease*.exe" />
<ReleaseFiles Exclude="*binRelease*vshost*.exe" />
</ItemGroup>

Isto também funciona. E como o próprio nome sugere, o Include é para incluir arquivos/pastas e o Exclude para não considerá-los.

No exemplo, estamos incluindo todos os arquivos da pasta binRelease que tenham a extensão .exe, menos os arquivos .exe que tenham vshost no nome.

Para usar uma lista, utilizamos a sintaxe @(NomeDaLista), que no nosso caso ficaria @(ReleaseFiles).

Target

Um elemento Target é um grupo de várias Tasks (que será explicado abaixo), assim é possível dividir o processo de compilação em partes menores e mais legíveis. Exemplo:

<Target Name="Clean">
<RemoveDir Directories="@(TestResultsDir)" />
</Target>

No exemplo, temos um Target chamado Clean que deleta o diretório que está no item TestResultDir, através da Task RemoveDir.

Bem simples né? Vamos melhorar o exemplo:

<Target Name="Clean">
<RemoveDir Directories="@(TestResultsDir)" />
</Target>

<Target Name="Compile" DependsOnTargets="Clean">
<MSBuild Projects="MSBuildTest.sln" Targets="Rebuild" Properties="Configuration=$(Configuration); Platform=$(BuildPlatform)" />
</Target>

Agora temos dois Targets: Clean e Compile. O primeiro já sabemos o que faz e o segundo realmente compila a solução MSBuildTest.sln, com as propriedades Configuration e BuildPlatform que definimos.

Uma coisa interessante que quero mostrar é o DependsOnTargets que está no Target chamado Compile, esta marcação faz com que o Target chamado Compile somente seja executado depois do Target chamado Clean, assim cria a possibilidade de ordenar a execução do build e mantendo-o seguro.

Agora que você já sabe o que é um Target, vou mostrar uma coisa que é necessária no elemento Project:

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

Note o DefaultTargets (sim, no plural pois pode ser mais de um Target), que define qual Target irá executar primeiro. No nosso caso é o Compile que depende do Clean, então o MSBuild irá executar o Clean primeiro e depois o Compile.

Task

Talvez você já tenha entendido o que é uma Task, mas mesmo assim vamos lá. Uma Task é, numa tradução livre, uma tarefa do processo de compilação. Esta tarefa pode ser a cópia de arquivos, uma chamada para outros programas, etc.

Nos exemplos anteriores, os elementos RemoveDir e MSBuild são Tasks também. Você pode encontrar mais informações sobre as Tasks disponíveis aqui.

Em uma pesquisada por cima, vi que é possível programar sua própria Task e distribuí-la numa DLL, fica a dica.

O exemplo final

Agora, vamos ver um exemplo mais completo com tudo o que vimos até agora:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Release">
<ItemGroup>
<ReleaseFiles Include="*binRelease*.exe" Exclude="*binRelease*vshost*.exe" />
<ReleaseFiles Include="*binRelease*.dll" Exclude="*binRelease*Test*.dll" />
<ReleaseFiles Include="*binRelease*.config" Exclude="*binRelease*vshost*.config" />
<ExeDir Include="C:Executaveis" />
</ItemGroup>

<PropertyGroup>
<Configuration>Release</Configuration>
<BuildPlatform>Any CPU</BuildPlatform>
</PropertyGroup>

<Target Name="Compile" DependsOnTargets="Clean">
<MSBuild Projects="MSBuildTest.sln" Targets="Rebuild" Properties="Configuration=$(Configuration); Platform=$(BuildPlatform)" />
</Target>

<Target Name="Release" DependsOnTargets="Compile">
<MakeDir Directories="$(ExeDir)" />
<Copy SourceFiles="@(ReleaseFiles)" DestinationFolder="@(ExeDir)" />
</Target>
</Project>

O que ele faz é compilar a solução MSBuildTest, criar o diretório para os executáveis (Item ExeDir), e copiar os arquivos finais para o ExeDir. É um build simples, mas dá pra ter uma noção das possibilidades.

Desta vez não tem repositório no GitHub :(

Até a próxima.