Compilando com MSBuild
20 Jul 2015Eu 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:
- PropertyGroup
- ItemGroup
- Target
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.