Como posso incrementar automaticamente a versão C # assembly por meio de nossa plataforma CI (Hudson)?

112

Eu e meu grupo somos horríveis em incrementar números de versão de assembly e frequentemente enviamos assemblies com versões 1.0.0.0. Obviamente, isso causa muitas dores de cabeça.

Estamos ficando muito melhores com nossas práticas por meio de nossa plataforma de CI e eu realmente gostaria de configurá-la para incrementar automaticamente os valores dentro do assemblyinfo.csarquivo para que as versões de nossos assemblies sejam atualizadas automaticamente com as alterações de código naquele assembly.

Eu já havia configurado (antes de encontrarmos o Hudson ) uma maneira de incrementar o valor por meio de msbuildou da linha de comando (não me lembro), mas com o Hudson, isso atualizará o repositório SVN e acionará OUTRO build. Isso resultaria em um loop infinito lento, pois o Hudson pesquisa o SVN a cada hora.

É uma má ideia fazer com que o Hudson incremente o número da versão? Qual seria uma forma alternativa de fazer isso?

Idealmente, meus critérios para uma solução seriam:

  • Incrementa o número da construção assemblyinfo.csantes de uma construção
  • Apenas incrementa o número de construção em montagens que foram alteradas. Isso pode não ser possível porque Hudson apaga a pasta do projeto toda vez que faz uma construção
  • Confirma o assemblyinfo.cs alterado no repositório de código (atualmente VisualSVN )
  • Não faz com que o Hudson acione uma nova construção na próxima vez que verificar as alterações

Resolvendo isso em minha cabeça, eu poderia facilmente encontrar uma solução para a maior parte disso por meio de arquivos / comandos em lote, mas todas as minhas idéias fariam com que o Hudson acione uma nova compilação na próxima vez que fizer a varredura. Não estou procurando alguém para fazer tudo por mim, apenas me aponte na direção certa, talvez uma técnica para fazer Hudson ignorar certos commits de SVN, etc.

Tudo o que encontrei até agora é apenas um artigo explicando como aumentar o número da versão automaticamente, nada leva em consideração uma plataforma de CI que poderia ser girada em um loop infinito.

Arroz allen
fonte

Respostas:

63

Uma alternativa simples é permitir que o ambiente C # incremente a versão do assembly para você, definindo o atributo version para major.minor.*(conforme descrito no modelo de arquivo AssemblyInfo).

Você pode estar procurando uma solução mais abrangente, no entanto.

EDITAR (resposta à pergunta em um comentário):

De AssemblyInfo.cs:

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version 
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers 
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
Greg D
fonte
Nunca me deparei com isso antes, poderia entrar em mais detalhes sobre o que isso faz. Ele funciona apenas em um IDE ou em uma equipe inteira de desenvolvedores com uma plataforma de CI?
Allen Rice
ahhh eu já vi isso antes, pode ser uma solução aceitável, mas o # build não é armazenado no subversion etc. Eu tenho o Hudson configurado para arquivar os arquivos e dessa forma ele é armazenado para que seja aceitável. Terei que fazer mais pesquisas sobre como esse mecanismo funciona, obrigado! Você não saberia como ele determina o que colocar como valores, não é?
Allen Rice
1
Veja minha resposta abaixo para a resposta à sua pergunta. Os valores são determinados com base no tempo de construção.
Kyle Trauberman
Uau, acho que vai funcionar. Não tenho certeza de como esquecemos uma solução tão simples
Allen Rice
Espero que sim, fico feliz por poder ajudar. Por que fazer algo da maneira mais difícil quando a maneira mais fácil e rápida também é a maneira certa? :)
Greg D
65

Aqui está o que fiz para carimbar o atributo AssemblyFileVersion.

Removido o AssemblyFileVersion de AssemblyInfo.cs

Adicione um novo arquivo vazio denominado AssemblyFileInfo.cs ao projeto.

Instale o conjunto de ferramentas de tarefas da comunidade MSBuild na máquina de compilação hudson ou como uma dependência NuGet em seu projeto.

Edite o arquivo de projeto (csproj), é apenas um arquivo msbuild, e adicione o seguinte.

Em algum lugar haverá uma <PropertyGroup>afirmação da versão. Mude para que leia, por exemplo

 <Major>1</Major>
 <Minor>0</Minor>
 <!--Hudson sets BUILD_NUMBER and SVN_REVISION -->
 <Build>$(BUILD_NUMBER)</Build>
 <Revision>$(SVN_REVISION)</Revision>

Hudson fornece aquelas variáveis ​​env que você vê lá quando o projeto é construído em hudson (assumindo que foi obtido do subversion).

Na parte inferior do arquivo do projeto, adicione

 <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')" />
  <Target Name="BeforeBuild" Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')">
    <Message Text="Version: $(Major).$(Minor).$(Build).$(Revision)" />
    <AssemblyInfo CodeLanguage="CS" OutputFile="AssemblyFileInfo.cs" AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" AssemblyConfiguration="$(Configuration)" Condition="$(Revision) != '' " />
  </Target>

Isso usa o MSBuildCommunityTasks para gerar o AssemblyFileVersion.cs para incluir um atributo AssemblyFileVersion antes da construção do projeto. Você pode fazer isso para qualquer / todos os atributos de versão, se desejar.

O resultado é, sempre que você emitir um build hudson, o assembly resultante obtém um AssemblyFileVersion de 1.0.HUDSON_BUILD_NR.SVN_REVISION, por exemplo, 1.0.6.2632, o que significa o 6º build # no hudson, construído a partir da revisão do subversion 2632.

nos
fonte
1
Então, apenas para atualizar isso: o método funciona para C #. Já uso há algum tempo. Mas os assemblies C ++ (ou seja, C ++ / CLI) ainda são um problema. Pelo que eu posso dizer, a tarefa AssemblyInfo não produz C ++ válido. Além disso, acho que esse método tem uma ligeira desvantagem, pois é um pouco opaco para outros desenvolvedores entenderem o que está acontecendo. Pena que você não pode enviar números de versão diretamente para o MSBuild como uma propriedade ...
CJBrew
@CJBrew Você poderia apenas criar um pequeno arquivo .bat que produza o código C ++ para o AssemblyInfo e fazer com que o msbuild inicie esse scipt. Não tenho certeza do que você quer dizer com empurrando-o como uma propriedade, você certamente poderia colocar as strings de versão em qualquer propriedade que quiser - você não precisa usar o principal / secundário / construção / revisão que usei aqui.
nos
Alguma coisa foi ganha usando essa rota em vez de apenas comentar o AssemblyFileVersion e deixá-lo ser definido para corresponder a [assembly: AssemblyVersion ("1.0. *")] Automaticamente?
cchamberlain
@ColeChamberlain Isso aumentará automaticamente se você construir a partir do Visual Studio em seu próprio PC, não a partir de Hudson - e não há relação com o número da versão e uma construção particular e revisão do código fonte.
nos
42

Aqui está uma solução elegante que requer um pouco de trabalho inicial ao adicionar um novo projeto, mas lida com o processo com muita facilidade.

A ideia é que cada projeto seja vinculado a um arquivo de solução que contém apenas as informações da versão do assembly. Portanto, seu processo de construção só precisa atualizar um único arquivo e todas as versões do assembly são puxadas de um arquivo na compilação.

Passos:

  1. Adicione uma classe ao seu arquivo de solução * .cs, chamei min SharedAssemblyProperties.cs
  2. Remova todas as informações cs desse novo arquivo
  3. Corte as informações do assembly de um arquivo AssemblyInfo: [assembly: AssemblyVersion ("1.0.0.0")] [assembly: AssemblyFileVersion ("1.0.0.0")]
  4. Adicione a declaração "using System.Reflection;" ao arquivo e, em seguida, cole os dados em seu novo arquivo cs (por exemplo, SharedAssemblyProperties.cs)
  5. Adicione um item existente ao seu projeto (espere ... leia antes de adicionar o arquivo)
  6. Selecione o arquivo e antes de clicar em Adicionar, clique no menu suspenso ao lado do botão Adicionar e selecione "Adicionar como Link".
  7. Repita as etapas 5 e 6 para todos os projetos existentes e novos na solução

Quando você adiciona o arquivo como um link, ele armazena os dados no arquivo de projeto e, durante a compilação, extrai as informações da versão do assembly desse arquivo.

Em seu controle de origem, você adiciona um arquivo bat ou arquivo de script que simplesmente incrementa o arquivo SharedAssemblyProperties.cs e todos os seus projetos atualizarão suas informações de montagem a partir desse arquivo.

Sondlerd
fonte
obrigado, Mark. Desculpe pelo link morto, parece que o servidor da comunidade não é tão fácil de mover. Devo procurar ajuda sobre esse assunto ...
sondlerd
11

O Hudson pode ser configurado para ignorar alterações em certos caminhos e arquivos para que não solicite uma nova construção.

Na página de configuração do trabalho, em Gerenciamento do código-fonte , clique no botão Avançado . Nas Regiões Excluídas caixa , você insere uma ou mais expressões regulares para corresponder às exclusões.

Por exemplo, para ignorar as alterações no arquivo version.properties, você pode usar:

/MyProject/trunk/version.properties

Isso funcionará para idiomas diferentes do C # e permite que você armazene suas informações de versão no subversion.

Matthew Blackford
fonte
1
Hudson também pode ignorar commits de certos usuários ou não acionar um build dependendo da mensagem de commit. Desta forma, você pode ignorar todos os commits do Hudson.
Peter Schuetze de
9

.NET faz isso por você. No arquivo AssemblyInfo.cs, defina a versão do assembly como major.minor. * (Por exemplo: 1.0. *).

Quando você constrói seu projeto, a versão é gerada automaticamente.

Os números de construção e revisão são gerados com base na data, usando o unix epoch, acredito. A construção é baseada no dia atual e a revisão é baseada no número de segundos desde a meia-noite.

Kyle Trauberman
fonte
21
<ring, ring> "olá, suporte ao produto, como posso ajudar?" <cliente> "tenho um erro" <suporte> "ok, qual versão você está usando?" <cliente> "versão um ponto dois revisão oito cinco dois cinco três sete quatro construir sete quatro seis três cinco dois nove ..." <suporte> "espere, apenas digitando que ... hmmm ... por favor, repita a versão números, parece que não temos essa versão e revisão listada ... "- GRRR!
Jimbo
haha bom comentário. Também não sou fã desse sistema de incremento: p
Joshua Hayes
3
O incremento automático no estúdio visual é uma droga.
Kugel
8
@Jimbo: Embora todos concordemos que seu comentário foi engraçado, na prática isso não importa. Quando você fala sobre a instalação do VS, você tem o Visual Studio 2008 SP1 ou VS2008 9.0.30729.1 SP? Usar números de compilação de incremento automático é um esquema muito comum e pode ser facilmente "consertado" incrementando-se os números de versão principal / secundária quando uma compilação de lançamento é lançada.
Marek de
o mais alto que obtivemos com um número de compilação é 678 antes de redefinir de volta para 0 para um aumento na revisão secundária (é claro, cruisecontrol, parecia mais fácil redefinir do que hudson como em cruisecontrol, você apenas entrou e salvou de volta para 0 no projeto , mas tudo o mais em Hudson é melhor)
Dean Hiller
8

Nunca vi esse recurso 1.0. * Funcionar no VS2005 ou VS2008. Existe algo que precisa ser feito para definir o VS para incrementar os valores?

Se AssemblyInfo.cs estiver codificado com 1.0. *, Onde a construção / revisão real é armazenada?

Depois de colocar 1.0. * Em AssemblyInfo, não podemos usar a seguinte instrução porque ProductVersion agora tem um valor inválido - está usando 1.0. * E não o valor atribuído por VS:

Version version = new Version(Application.ProductVersion);

Suspiro - esta parece ser uma daquelas coisas que todos perguntam, mas de alguma forma nunca há uma resposta sólida. Anos atrás, vi soluções para gerar um número de revisão e salvá-lo no AssemblyInfo como parte de um processo de pós-construção. Eu esperava que esse tipo de dança não fosse necessário para o VS2008. Talvez VS2010?

TonyG
fonte
10
Você deve remover o AssemblyFileVersion. Fora isso, está funcionando muito bem para nós, a resposta aceita é.
Allen Rice,
1
Sim, remover AssemblyFileVersion permite que a versão seja atualizada e sem mais erros com a versão. Agradável. Observação: duas operações de construção incrementam a revisão apenas uma vez, mas se você reconstruir a revisão é atualizada. Como disse ktrauberman, é semelhante a build.revision = date.time, o que explica por que os dados não são armazenados em nenhum lugar, exceto na montagem. Agora preciso obter uma configuração MSI padrão para gerar um novo ProductCode quando o projeto de saída principal for atualizado. As configurações não permitem revisão, apenas construção. Desejo instalar sobre uma instalação existente para fazer uma atualização. Precisa pesquisar.
TonyG
5

Estou assumindo que também se pode fazer isso com um modelo de texto em que você cria os atributos de montagem em questão dinamicamente a partir do ambiente como o AssemblyVersion.tt faz abaixo.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
<#
var build = Environment.GetEnvironmentVariable("BUILD_NUMBER");
build = build == null ? "0" : int.Parse(build).ToString();
var revision = Environment.GetEnvironmentVariable("SVN_REVISION");
revision = revision == null ? "0" : int.Parse(revision).ToString();    
#>
using System.Reflection;
[assembly: AssemblyVersion("1.0.<#=build#>.<#=revision#>")]
[assembly: AssemblyFileVersion("1.0.<#=build#>.<#=revision#>")]
MikeS
fonte
3

Como uma continuação da resposta de MikeS, eu gostaria de acrescentar que o SDK do VS + Visual Studio Visualization and Modeling precisa ser instalado para que isso funcione, e você também precisa modificar o arquivo do projeto. Também deve ser mencionado que eu uso Jenkins como servidor de compilação rodando em uma caixa de servidor Windows 2008 R2 com módulo de versão, onde obtenho o BUILD_NUMBER.

Meu arquivo de modelo de texto version.tt se parece com isto

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
<#
var build = Environment.GetEnvironmentVariable("BUILD_NUMBER");
build = build == null ? "0" : int.Parse(build).ToString();
var revision = Environment.GetEnvironmentVariable("_BuildVersion");
revision = revision == null ? "5.0.0.0" : revision;    
#>
using System.Reflection;
[assembly: AssemblyVersion("<#=revision#>")]
[assembly: AssemblyFileVersion("<#=revision#>")]

Tenho o seguinte nos grupos de propriedades

<PropertyGroup>
    <TransformOnBuild>true</TransformOnBuild>
    <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
    <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>

após a importação de Microsoft.CSharp.targets, eu tenho isso (dependendo de onde você instala o VS

<Import Project="C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\TextTemplating\v10.0\Microsoft.TextTemplating.targets" />

Em meu servidor de compilação, tenho o seguinte script para executar a transformação de texto antes da compilação real, para obter o último número do changeset no TFS

set _Path="C:\Build_Source\foo"

pushd %_Path% 
"%ProgramFiles(x86)%\Microsoft Visual Studio 10.0\Common7\IDE\tf.exe" history . /r /noprompt /stopafter:1 /Version:W > bar
FOR /f "tokens=1" %%foo in ('findstr /R "^[0-9][0-9]*" bar') do set _BuildVersion=5.0.%BUILD_NUMBER%.%%foo
del bar
popd

echo %BUILD_NUMBER%
echo %_BuildVersion%
cd C:\Program Files (x86)\Jenkins\jobs\MyJob\workspace\MyProject
MSBuild MyProject.csproj /t:TransformAll 
...
<rest of bld script>

Desta forma, posso acompanhar as compilações E os conjuntos de alterações, então se eu não fiz check-in desde a última compilação, o último dígito não deve mudar, no entanto, posso ter feito alterações no processo de compilação, daí a necessidade do penúltimo número . Claro, se você fizer vários check-ins antes de uma compilação, terá apenas a última alteração refletida na versão. Eu acho que você poderia concatenar o que é necessário.

Tenho certeza que você pode fazer algo mais sofisticado e chamar o TFS diretamente de dentro do modelo tt, mas isso funciona para mim.

Posso então obter minha versão em tempo de execução como esta

Assembly assembly = Assembly.GetExecutingAssembly();
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
return fvi.FileVersion;
aggaton
fonte
1

Minha solução não requer a adição de ferramentas externas ou linguagens de script - é praticamente garantido que funcione em sua máquina de construção. Eu resolvo esse problema em várias partes. Primeiro, criei um arquivo BUILD.BAT que converte o parâmetro BUILD_NUMBER do Jenkins em uma variável de ambiente. Eu uso a função "Executar comando em lote do Windows" do Jenkins para executar o arquivo em lote de compilação, inserindo as seguintes informações para a compilação do Jenkins:

     ./build.bat --build_id %BUILD_ID% -build_number %BUILD_NUMBER%

No ambiente de construção, tenho um arquivo build.bat que começa da seguinte maneira:

     rem build.bat
     set BUILD_ID=Unknown
     set BUILD_NUMBER=0
     :parse_command_line
     IF NOT "%1"=="" (
         IF "%1"=="-build_id" (
             SET BUILD_ID=%2
             SHIFT
             )
         IF "%1"=="-build_number" (
             SET BUILD_NUMBER=%2
             SHIFT
         )
         SHIFT
         GOTO :parse_command_line
     )
     REM your build continues with the environmental variables set
     MSBUILD.EXE YourProject.sln

Depois de fazer isso, cliquei com o botão direito do mouse no projeto a ser construído no painel do Explorador de Soluções do Visual Studio e selecionei Propriedades, selecione Criar Eventos e inseri as seguintes informações como Linha de Comando do Evento de Pré-Criação, que cria automaticamente um arquivo .cs contendo informações de número de compilação com base nas configurações de variáveis ​​de ambiente atuais:

     set VERSION_FILE=$(ProjectDir)\Properties\VersionInfo.cs
     if !%BUILD_NUMBER%==! goto no_buildnumber_set
     goto buildnumber_set
     :no_buildnumber_set
     set BUILD_NUMBER=0
     :buildnumber_set
     if not exist %VERSION_FILE% goto no_version_file
     del /q %VERSION_FILE%
     :no_version_file
     echo using System.Reflection; >> %VERSION_FILE%
     echo using System.Runtime.CompilerServices; >> %VERSION_FILE%
     echo using System.Runtime.InteropServices; >> %VERSION_FILE%
     echo [assembly: AssemblyVersion("0.0.%BUILD_NUMBER%.1")] >> %VERSION_FILE%
     echo [assembly: AssemblyFileVersion("0.0.%BUILD_NUMBER%.1")] >> %VERSION_FILE%

Pode ser necessário ajustar ao seu gosto de construção. Eu construo o projeto manualmente uma vez para gerar um arquivo Version.cs inicial no diretório Propriedades do projeto principal. Por último, incluo manualmente o arquivo Version.cs na solução do Visual Studio, arrastando-o para o painel Gerenciador de Soluções, abaixo da guia Propriedades desse projeto. Em compilações futuras, o Visual Studio então lê esse arquivo .cs no momento da compilação do Jenkins e obtém dele as informações de número de compilação corretas.

johnwbyrd
fonte
1

Portanto, temos um projeto com uma solução que contém vários projetos que possuem assemblies com diferentes números de versão.

Depois de investigar vários dos métodos acima, acabei de implementar uma etapa de compilação para executar um script Powershell que faz uma localização e substituição no arquivo AssemblyInfo.cs. Eu ainda uso o número da versão 1.0. * No controle de origem e o Jenkins apenas atualiza manualmente o número da versão antes que o msbuild seja executado.

dir **/Properties/AssemblyInfo.cs | %{ (cat $_) | %{$_ -replace '^(\s*)\[assembly: AssemblyVersion\("(.*)\.\*"\)', "`$1[assembly: AssemblyVersion(`"`$2.$build`")"} | Out-File $_ -Encoding "UTF8" }
dir **/Properties/AssemblyInfo.cs | %{ (cat $_) | %{$_ -replace '^(\s*)\[assembly: AssemblyFileVersion\("(.*)\.\*"\)', "`$1[assembly: AssemblyFileVersion(`"`$2.$build`")"} | Out-File $_ -Encoding "UTF8" }

Eu adicionei a opção -Encoding "UTF8" porque o git começou a tratar o arquivo .cs como arquivos binários se eu não o fizesse. Concedido, isso não importa, já que eu nunca comprometo o resultado; apenas surgiu enquanto eu estava testando.

Nosso ambiente de CI já tem um recurso para associar o build do Jenkins ao git commit específico (obrigado, plugin Stash!), Então não me preocupo se não houver git commit com o número da versão anexado a ele.

jonnybot
fonte
0

Este é um mecanismo mais simples. Envolve simplesmente a adição de uma etapa de compilação de tarefa de comando do Windows Batch antes da etapa MSBuild e o uso de um programa simples de localização e substituição (FART).

A etapa de lote

fart --svn -r AssemblyInfo.cs "[assembly: AssemblyVersion(\"1.0.0.0\")]" "[assembly: AssemblyVersion(\"1.0.%BUILD_NUMBER%.%SVN_REVISION%\")]"
if %ERRORLEVEL%==0 exit /b 1
fart --svn -r AssemblyInfo.cs "[assembly: AssemblyFileVersion(\"1.0.0.0\")]" "[assembly: AssemblyFileVersion(\"1.0.%BUILD_NUMBER%.%SVN_REVISION%\")]"
if %ERRORLEVEL%==0 exit /b 1
exit /b 0

Se você estiver usando o controle de origem diferente de svn, altere a opção --svn para a opção apropriada para seu ambiente scm.

Baixar Fart

Sweetfa
fonte
0

Decidi usar alguns métodos usando um script Powershell pré-compilado ( https://gist.github.com/bradjolicoeur/e77c508089aea6614af3 ) para incrementar em cada construção bem-sucedida em Global.asax. Vou fazer algo assim:

  // We are using debug configuration, so increment our builds.
  if (System.Diagnostics.Debugger.IsAttached)
  {
      string version = System.Reflection.Assembly.GetExecutingAssembly()
                                                       .GetName()
                                                       .Version
                                                       .ToString();

      var psi = new ProcessStartInfo(@"svn", "commit -m \"Version: " + version + "\n \"");
      psi.WorkingDirectory = @"C:\CI\Projects\myproject";
      Process.Start(psi); 
  }

Ainda acho que todo o processo é complicado demais e vou procurar um método mais eficiente para obter o mesmo resultado. Eu queria isso principalmente para passar a versão para o SVN e depois para o Jenkin's sem muitas ferramentas adicionais.

Netferret
fonte