Monday, 19 March 2018

Estratégia de controle de software


Versão semântica 2.0.0.
Dado um número de versão MAJOR. MINOR. PATCH, incremente:
MAJOR versão quando você faz alterações de API incompatíveis, versão MENOR quando você adiciona funcionalidade de forma compatível com versões anteriores e versão PATCH quando você faz correções de bugs compatíveis com versões anteriores.
Etiquetas adicionais para pré-lançamento e metadados de compilação estão disponíveis como extensões ao formato MAJOR. MINOR. PATCH.
Introdução.
No mundo do gerenciamento de software, existe um lugar de medo chamado "dependência do inferno". Quanto maior o seu sistema e quanto mais pacotes você integra no seu software, mais provável é encontrar-se, um dia, neste poço de desespero.
Em sistemas com muitas dependências, lançar novas versões de pacotes pode rapidamente se tornar um pesadelo. Se as especificações de dependência forem muito apertadas, você corre o risco de bloqueio de versão (a incapacidade de atualizar um pacote sem ter que liberar novas versões de cada pacote dependente). Se as dependências são especificadas de forma muito frouxa, você será inevitavelmente mordido pela versão promiscuidade (assumindo a compatibilidade com mais versões futuras do que o razoável). O inferno de dependência é onde você está quando o bloqueio de versão e / ou a promiscuidade da versão impedem que você transmita com facilidade e segurança seu projeto.
Como solução para este problema, proponho um conjunto simples de regras e requisitos que determinam como os números de versão são atribuídos e incrementados. Essas regras são baseadas, mas não necessariamente limitadas, a práticas comuns difundidas pré-existentes em uso em software aberto e de código aberto. Para que este sistema funcione, primeiro você precisa declarar uma API pública. Isso pode consistir em documentação ou ser aplicado pelo próprio código. Independentemente disso, é importante que esta API seja clara e precisa. Uma vez que você identifica sua API pública, você comunica suas alterações com incrementos específicos ao seu número de versão. Considere um formato de versão de X. Y.Z (Major. Minor. Patch). Correção de erros que não afetam o incremento da API na versão do patch, as adições / alterações da API compatíveis com versões anteriores incrementam a versão menor, e as mudanças de API incompatíveis com a frente incrementam a versão principal.
Eu chamo esse sistema de "Versão semântica". Sob este esquema, os números de versão e a forma como eles mudam transmitem significado sobre o código subjacente e o que foi modificado de uma versão para a próxima.
Especificação de versão semântica (SemVer)
As palavras-chave "DEVE", "NÃO DEVEM", "REQUERIDO", "NÃO", "DEVEM", "NÃO DEVEM", "RECOMENDADO", "MAYO" e "OPCIONAL" neste documento são para ser interpretado como descrito na RFC 2119.
O software que usa a versão semântica DEVE declarar uma API pública. Esta API pode ser declarada no próprio código ou existir estritamente na documentação. No entanto, é feito, deve ser preciso e abrangente.
Um número de versão normal DEVE tomar a forma X. Y.Z onde X, Y e Z são números inteiros não negativos e NÃO DEVEM CONNECER ZONES PRINCIPAIS. X é a versão principal, Y é a versão menor e Z é a versão do patch. Cada elemento DEVE aumentar numericamente. Por exemplo: 1.9.0 - & gt; 1.10.0 - & gt; 1.11.0.
Uma vez que um pacote versionado foi lançado, o conteúdo dessa versão NÃO DEVE ser modificado. Todas as modificações DEVEM ser lançadas como uma nova versão.
A versão principal zero (0.y. z) é para desenvolvimento inicial. Qualquer coisa pode mudar a qualquer momento. A API pública não deve ser considerada estável.
A versão 1.0.0 define a API pública. A forma como o número da versão é incrementada após esta versão é dependente desta API pública e como ela muda.
A versão de patch Z (x. y.Z | x & gt; 0) DEVE ser incrementada se apenas as correções de erros compatíveis com versões anteriores forem introduzidas. Uma correção de erro é definida como uma alteração interna que corrige o comportamento incorreto.
A versão menor Y (x. Y.z | x & gt; 0) DEVE ser incrementada se a nova funcionalidade compatível com versões anteriores for introduzida na API pública. DEVE ser incrementado se qualquer funcionalidade pública da API estiver marcada como obsoleta. PODE ser incrementado se novas funcionalidades ou melhorias forem introduzidas dentro do código privado. PODE incluir mudanças no nível do patch. A versão do patch DEVE ser redefinida para 0 quando a versão menor for incrementada.
A versão principal X (X. y.z | X & gt; 0) DEVE ser incrementada se alguma alteração incompatível com versões anteriores for introduzida na API pública. PODE incluir mudanças menores e de nível de patch. Patch e versão menor DEVEM ser redefinir para 0 quando a versão principal é incrementada.
Uma versão pré-lançamento pode ser denotada anexando um hífen e uma série de identificadores separados por pontos imediatamente após a versão do patch. Os identificadores DEVEM compreender apenas alfanuméricos ASCII e hífen [0-9A-Za-z-]. Identificadores NÃO DEVEM estar vazios. Os identificadores numéricos NÃO DEVEM incluir os zeros iniciais. As versões pré-lançamento têm uma precedência menor do que a versão normal associada. Uma versão pré-lançamento indica que a versão é instável e pode não satisfazer os requisitos de compatibilidade pretendidos conforme indicado pela versão normal associada. Exemplos: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.
Os metadados de compilação podem ser denotados anexando um sinal de mais e uma série de identificadores separados por pontos imediatamente após o patch ou a versão pré-lançamento. Os identificadores DEVEM compreender apenas alfanuméricos ASCII e hífen [0-9A-Za-z-]. Identificadores NÃO DEVEM estar vazios. Construir metadados DEVE ser ignorado ao determinar a precedência da versão. Assim, duas versões que diferem apenas nos metadados de compilação, têm a mesma precedência. Exemplos: 1.0.0-alpha + 001, 1.0.0 + 20180313144700, 1.0.0-beta + exp. sha.5114f85.
A prioridade refere-se a como as versões são comparadas entre si quando encomendadas. A prioridade DEVE ser calculada separando a versão em identificadores maiores, menores, de patch e de pré-lançamento nessa ordem (os metadados de compilação não figuram em precedência). A prioridade é determinada pela primeira diferença ao comparar cada um desses identificadores da esquerda para a direita da seguinte maneira: as versões principais, menores e patch são sempre comparadas numericamente. Exemplo: 1.0.0 & lt; 2.0.0 & lt; 2.1.0 & lt; 2.1.1. Quando maior, menor e patch são iguais, uma versão pré-lançamento tem menor precedência do que uma versão normal. Exemplo: 1.0.0-alpha & lt; 1.0.0. A prioridade para duas versões de pré-lançamento com a mesma versão principal, menor e de patch DEVE ser determinada comparando cada identificador separado por pontos da esquerda para a direita até que uma diferença seja encontrada da seguinte forma: identificadores consistindo apenas de dígitos são comparados numericamente e identificadores com letras ou hifens são comparados lexicamente em ordem de classificação ASCII. Os identificadores numéricos sempre têm menor precedência do que os identificadores não-numéricos. Um conjunto maior de campos de pré-lançamento tem um precedente maior do que um conjunto menor, se todos os identificadores anteriores forem iguais. Exemplo: 1.0.0-alpha & lt; 1.0.0-alpha.1 & lt; 1.0.0-alpha. beta & lt; 1.0.0-beta & lt; 1.0.0-beta.2 & lt; 1.0.0-beta.11 & lt; 1.0.0-rc.1 & lt; 1.0.0.
Por que usar a versão semântica?
Esta não é uma ideia nova ou revolucionária. Na verdade, você provavelmente faz algo próximo disso. O problema é que "fechar" não é suficientemente bom. Sem conformidade com algum tipo de especificação formal, os números de versão são essencialmente inúteis para o gerenciamento de dependências. Ao dar um nome e uma definição clara às ideias acima, torna-se fácil comunicar suas intenções aos usuários do seu software. Uma vez que essas intenções são claras, as especificações de dependência flexíveis (mas não muito flexíveis) podem finalmente ser feitas.
Um exemplo simples irá demonstrar como o Versioning Semântico pode tornar a dependência do inferno uma coisa do passado. Considere uma biblioteca chamada "Firetruck". Ela requer um pacote com versão semântica chamado "Escada". No momento em que o Firetruck é criado, a Ladder está na versão 3.1.0. Uma vez que o Firetruck usa algumas funcionalidades que foram introduzidas pela primeira vez em 3.1.0, você pode especificar com segurança a dependência da Escada como maior ou igual a 3.1.0, mas inferior a 4.0.0. Agora, quando a Ladder versão 3.1.1 e 3.2.0 estiverem disponíveis, você pode liberá-las para seu sistema de gerenciamento de pacotes e saber que elas serão compatíveis com o software dependente existente.
Como desenvolvedor responsável, você, obviamente, deseja verificar se as atualizações de pacotes funcionam como anunciadas. O mundo real é um lugar bagunçado; Não há nada que possamos fazer sobre isso, mas fique atento. O que você pode fazer é permitir que o Semantic Versioning forneça uma maneira sã de liberar e atualizar pacotes sem ter que rolar novas versões de pacotes dependentes, economizando tempo e dificuldade.
Se tudo isso parecer desejável, tudo o que você precisa fazer para começar a usar o Versioning Semântico é declarar que você está fazendo isso e depois seguir as regras. Ligue para este site a partir do seu README para que outros conheçam as regras e possam se beneficiar delas.
Como faço para lidar com revisões na fase de desenvolvimento inicial 0.y. z?
A coisa mais simples a fazer é iniciar sua versão de desenvolvimento inicial em 0.1.0 e, em seguida, incrementar a versão menor para cada versão subseqüente.
Como eu sei quando lançar 1.0.0?
Se o seu software estiver sendo usado na produção, provavelmente ele já deve ser 1.0.0. Se você tem uma API estável sobre a qual os usuários vieram a depender, você deve ser 1.0.0. Se você se preocupa muito com a compatibilidade com versões anteriores, provavelmente você já deve estar 1.0.0.
Isso não desencoraja o rápido desenvolvimento e a rápida iteração?
A versão principal zero é tudo sobre desenvolvimento rápido. Se você estiver alterando a API todos os dias, você ainda deve estar na versão 0.y. z ou em um ramo de desenvolvimento separado trabalhando na próxima versão principal.
Se mesmo as mais pequenas mudanças incompatíveis para trás para a API pública exigem um problema de versão principal, não acabarei na versão 42.0.0 muito rapidamente?
Esta é uma questão de desenvolvimento responsável e previsão. Alterações incompatíveis não devem ser introduzidas levemente em um software que tenha muitos códigos dependentes. O custo que deve ser incorrido para atualizar pode ser significativo. Ter que superar as principais versões para liberar mudanças incompatíveis significa que você pensará no impacto de suas mudanças e avaliará a relação custo / benefício envolvida.
Documentar toda a API pública é muito trabalho!
É sua responsabilidade como desenvolvedor profissional documentar adequadamente o software que é destinado a outros usuários. Gerenciando a complexidade do software é uma parte extremamente importante de manter um projeto eficiente, e isso é difícil de fazer se ninguém souber usar seu software ou quais métodos são seguros. A longo prazo, a versão semântica e a insistência em uma API pública bem definida podem manter todos e todos funcionando sem problemas.
O que eu faço se eu acidentalmente liberar uma mudança incompatível para trás como uma versão menor?
Assim que você perceber que você quebrou as especificações da Versão Semântica, corrija o problema e libere uma nova versão menor que corrija o problema e restaure a compatibilidade com versões anteriores. Mesmo nessa circunstância, é inaceitável modificar os lançamentos versionados. Se for apropriado, documentar a versão ofensiva e informar seus usuários do problema para que estejam cientes da versão ofensiva.
O que devo fazer se atualizar minhas próprias dependências sem alterar a API pública?
Isso seria considerado compatível, uma vez que não afeta a API pública. Software que depende explicitamente das mesmas dependências que o seu pacote deve ter suas próprias especificações de dependência e o autor notará quaisquer conflitos. Determinar se a alteração é um nível de patch ou modificação de nível menor depende se você atualizou suas dependências para corrigir um erro ou introduzir novas funcionalidades. Eu normalmente esperaria um código adicional para a última instância, caso em que é, obviamente, um incremento de nível menor.
E se eu alterar inadvertidamente a API pública de uma maneira que não é compatível com a alteração do número de versão (ou seja, o código introduz incorretamente uma grande mudança de quebra em uma versão de patch)?
Use seu melhor julgamento. Se você tiver um público enorme que será impactado drasticamente ao mudar o comportamento de volta ao que a API pública pretendia, então talvez seja melhor realizar uma versão de versão importante, mesmo que a correção possa ser considerada como uma versão de patch. Lembre-se, o Versioning Semântico é sobre transmitir o significado pelo modo como o número da versão muda. Se essas mudanças forem importantes para seus usuários, use o número da versão para informá-las.
Como devo lidar com a funcionalidade de desaprovação?
Descartar a funcionalidade existente é uma parte normal do desenvolvimento de software e muitas vezes é necessário para avançar o progresso. Quando você deprecia parte de sua API pública, você deve fazer duas coisas: (1) atualize sua documentação para que os usuários saibam sobre a mudança, (2) emita uma nova versão menor com a desaprovação no lugar. Antes de remover completamente a funcionalidade em uma nova versão principal, deve haver pelo menos uma versão menor que contenha a desaprovação para que os usuários possam transição suave para a nova API.
Semver tem um limite de tamanho na seqüência de versão?
Não, mas use um bom julgamento. Uma string de versão de 255 caracteres provavelmente é um exagero, por exemplo. Além disso, sistemas específicos podem impor seus próprios limites ao tamanho da string.
A especificação da versão semântica é de autoria de Tom Preston-Werner, inventor de Gravatars e co-fundador da GitHub.
Se você quiser deixar comentários, abra um problema no GitHub.

Estratégia de controle de software
Obter através da App Store Leia esta publicação em nosso aplicativo!
Qual é a sua estratégia de versão do aplicativo? [duplicado]
Esta questão já tem uma resposta aqui:
Eu estaria interessado em obter as opiniões da comunidade SO sobre a melhor estratégia de versão de aplicativos.
Como você acompanha o número da versão do seu aplicativo? Você tem uma definição formal do que representa cada número / personagem dessa versão?
O que significam os diferentes números / strings na versão do aplicativo para o seu aplicativo?
Você usa qualquer sistema de atualização automatizado em suas aplicações (por exemplo, algo como Sparkle) e como ele tem sido bom para você?
Você tem um procedimento de atualização separado para testadores beta ou testadores de pré-lançamento do seu aplicativo?
marcado como duplicado pelo mosquito, MichaelT, Kilian Foth, GlenH7, Rein Henrichs 29 de abril 13 às 2:42.
Esta pergunta foi feita antes e já tem uma resposta. Se essas respostas não respondem totalmente a sua pergunta, faça uma nova pergunta.
migrou do stackoverflow 18 de maio às 11h48.
Esta questão veio do nosso site para programadores profissionais e entusiasta.
Como você acompanha o número da versão do seu aplicativo? Você tem uma definição formal do que representa cada número / personagem dessa versão?
O que significam os diferentes números / strings na versão do aplicativo para o seu aplicativo?
Eu uso o seguinte:
Major - A versão principal é um lançamento definitivo do produto. Aumentou quando há mudanças significativas na funcionalidade.
Menor - A versão menor é incrementada quando apenas foram adicionados novos recursos ou grandes correções de bugs.
Upgrade / Patch - Upgrade refere-se à substituição de um produto por uma versão mais recente do product. It é incrementado somente quando a atualização é fornecida na versão principal designada. A versão do programa começa com 0 e é incrementada somente quando o erro foi resolvido.
Build No - Build Number é incrementado quando a nova compilação é criada.
Você usa qualquer sistema de atualização automatizado em suas aplicações (por exemplo, algo como Sparkle) e como ele tem sido bom para você?
Nós usamos uma ferramenta de construção que automaticamente cria aplicativos à noite, que chamamos construção noturna e isso aumenta o número de compilação sempre que uma compilação é criada.
Você tem um procedimento de atualização separado para testadores beta ou testadores de pré-lançamento do seu aplicativo?
Não testadores testados durante a noite todas as manhãs que chamamos BAT (Build Aceceptance Test) e verificamos a construção noturna.
Permitam-me que note primeiro que parece não haver acordo sobre a "melhor" estratégia. Só posso compartilhar minha experiência em um projeto atual.
A versão do sistema é definida manualmente em uma propriedade de compilação. Acontece quando a equipe concorda em uma nova versão. Como versão de versão adicional, usamos o número de compilação que é gerado automaticamente pela construção do CI.
Seguimos vagamente o esquema de nomeação do Ubuntu YY. MM. version. patch_buildNumber, pois descobrimos que o controle de versão Major. Minor sujava as expectativas dos clientes;)
Não há atualizações automáticas, pois o aplicativo deve ser lançado por adminstrators.
Os lançamentos de teste são mais freqüentes do que as versões da GA, mas isso deve ser tudo.
Testei muitos sistemas de versões e agora estou muito feliz com este:
O Major é configurado manualmente e se refere a uma versão que inclui importantes melhorias. Menor também é configurado manualmente e se refere a uma versão de atualização / manutenção, incluindo melhorias menores e amp; correção A revisão é gerada automaticamente e se refere a uma revisão exata no repositório.
O último nos permite ser muito flexíveis com a versão. Podemos enviar múltiplas versões para vários clientes e ainda ser capazes de depurar & amp; conserte com facilidade obtendo a versão específica dos repos, então combine novamente com o tronco.
A construção, embalagem e amp; A publicação é totalmente automatizada. A única ação manual é quando movemos o último pacote para o servidor de produção por FTP. Queremos manter o controle sobre isso para garantir que não entregamos lixo na produção. Ele é um estágio preliminar em que os adotadores iniciais podem ler as notas da versão e depois decidir baixar e usar a versão. Os clientes que enfrentam erros específicos podem obter uma versão fixa muito rápido usando uma dessas versões.
Uso a versão semântica para as bibliotecas de código aberto e acho muito mais fácil trabalhar com outras bibliotecas que também o fazem. Ele fornece uma base comum para entender o que uma mudança de versão pode significar. A biblioteca ainda está em versão beta? É um lançamento apenas para correções de bugs? Haverá mudanças de API quebradas?
É basicamente uma codificação das melhores práticas de versão já utilizadas pela maioria dos projetos de código aberto.

Estratégias de versão de software.
Um perfeccionista preso no mundo real.
Software Versioning pode ser uma dessas áreas onde você nunca sente como se você tivesse exatamente isso. Não existe uma orientação definitiva com uma solução que satisfaça todos. Principalmente equipes de software estão confusas sobre o assunto, ou estão escolhendo ignorá-lo. Este guia visa preencher a lacuna e oferecer uma visão prática de várias estratégias populares e trade-offs.
Algumas das técnicas serão voltadas para a Microsoft stack (Windows), pois é o que tenho mais experiência, mas os princípios se aplicam em geral. Linux, Node. js, Python & amp; Ruby também é levemente tocada.
Versões em todo lugar.
Estamos bastante acostumados com o termo "versão" hoje em dia. Mais comumente usado no mundo do software, vazou para a mídia e outras indústrias. As seqüências de filmes estão sendo versionadas - "Fast & Furious 7" (7 !?), os sapatos estão sendo versionados - "Air Jordan XX8" e, mais popular, os livros estão sendo versionados - "One Minute Manager, 1984 edition". Na verdade, olhando para os livros, as pessoas estão sendo controladas há bastante tempo - "Enciclopédia Britânica", desde 1768 !.
A premissa é simples - à medida que os produtos vivem e continuam a ser melhorados, os lançamentos mais recentes devem ser distinguidos dos anteriores. O nome do produto não muda, porque o mercado já se familiarizou com isso, então algo é anexado no final para indicar que ele é mais novo (ou diferente).
Enquanto o controle de versão existia muito antes da era digital, o software realmente empurrou o problema para a frente. Modificar e liberar uma nova cópia de software é um processo muito rápido, muitas vezes mais rápido do que mudar uma linha de produção industrial para produzir uma nova peça de roupa ou imprimir uma nova edição de livros. Assim, os ciclos de iteração do software são muito mais curtos, e um potencial para muitas edições simultâneas é muito maior.
Basta usar anos (ou mesmo meses), como em edições de livros, não é suficiente. Novas versões do software podem ser produzidas em poucos minutos. Além disso, o software tem um enorme aspecto paralelo - fluxos de software - onde várias versões principais podem existir e todas podem ser atualizadas continuamente ao mesmo tempo. Isso raramente acontece com seus sapatos. (Eu queria fazê-lo, às vezes eu simplesmente não quero atualizar para o modelo de catálogo deste ano, quero uma melhoria para o meu antigo par!)
Por que Versão?
Antes de mergulhar em como implementar o controle de versão, vamos parar e considerar o motivo pelo qual queremos fazê-lo em primeiro lugar! Afinal, se conhecemos as razões exatas de por que é útil, podemos avaliar melhor se as soluções propostas são adequadas.
Nós aludimos a isso na última seção, referindo-se ao chamado versão pública. Esta é a versão que é visivelmente visível e, principalmente, possui peso no mercado (ou seja, é mais provável que seja definido pelo departamento de marketing / vendas). "Windows 7", "iPhone 5S", "Office 2018" - são exemplos de uma versão pública.
A versão pública destina-se a ser simples e memorável, indicando aos clientes que é novo e brilhante (assumindo que as pessoas geralmente querem "novo e brilhante"). As pessoas não entendem "10.6.6527.14789" - mas obtêm "2018" ou "5". Tem sido cada vez mais popular usar o ano de lançamento como o número da versão pública, pois transmite de forma simples e poderosa o status atualizado. Os fabricantes de automóveis estão fazendo isso há muito tempo.
A versão privada é o que estamos acostumados no mundo do software. Um selo interno que (espero) identifica de forma exclusiva um determinado software. O software, como um carro, pode ser feito de muitas partes. Seguindo a analogia do carro, a "versão privada" do carro é o número do chassi VIN. Os fabricantes liberam e mantêm catálogos maciços de peças, mapeando para "números de versão" do carro. Um mecânico pode então pedir uma parte exata que corresponda ao seu veículo.
Sem um "número de peça privada", você não seria capaz de atender seu software na natureza, pois não saberia o "formato" exato que um módulo de substituição deve ser para se adequar ao sistema geral. Imagine se você foi forçado a mudar seu carro inteiro quando uma luz da cauda quebrou.
Portanto, o número da versão privada é usado como um identificador de catálogo. Destina-se a ser usado quando solucionar problemas ou atender seu software. (Eu gosto da analogia "dogtag" de Jeff Attwood!) Ele deve mapear para uma descrição do que é essa peça de software - qual é a sua forma e função. E o que melhor "descrição" do que o próprio código-fonte original!
O uso essencialmente se resume a:
Identificando o código fonte original para uma peça de software, para permitir parches incrementais e para confirmar a operação defeituosa Identificando se uma parte é "compatível" com outra, ou se ela pode substituí-la.
Tudo isso é realizado com um número de versão privado. A versão pública é simplesmente um moniker de marketing, e mapeia para uma ou mais partes internas de software, cada uma com sua própria versão privada. (Assim como o Toyota Corolla 2018 contém um quadro ZRE142 e um conversor de torque 32000-12420)
Uso da versão.
No Windows, um conceito de número de versão é suportado por uma camada do sistema operacional. Os números de versão são incorporados em todos os arquivos executáveis ​​binários e podem ser vistos ao pairar sobre o EXE / DLL no Windows Explorer ou ao exibir Propriedades. Na verdade, qualquer arquivo que possa ter "recursos" pode ter uma versão, pois está armazenado no recurso VERSIONINFO.
Ele usa o formato comum em que todos nós somos usados ​​para: major. minor. build. revision (por exemplo, "1.2.360.0"). É importante notar que cada número é limitado a 16 bits e, portanto, não pode exceder 65535. Isso tem certas implicações sobre o que podemos representar com esses números.
Observe que um rótulo para esses números não é definido de forma estrita - são simples 4 números inteiros curtos. Os dois primeiros são referidos como principais e menores, por unanimidade. Os dois últimos são onde vemos algumas variações, dependendo do esquema de versão.
Esta versão é usada de forma proeminente durante o processo de atualização do Windows, que utiliza a tecnologia Windows Installer (MSI) para atualizar várias partes do sistema. Essencialmente, o Windows Installer segue certas regras para determinar se a atualização que está instalando é mais nova que o que já está instalado. Se a versão for maior, então está ok para atualizar.
Naturalmente, esse conceito flui para o Framework, que foi construído em torno de muitos conceitos existentes do Windows. Nós temos a classe Version, que segue o paradigma 4 integer. Também podemos definir AssemblyVersionAttribute e AssemblyFileVersionAttribute, que especificam uma versão de montagem e um recurso de versão do Windows, respectivamente.
Em, a versão de montagem existe separadamente da versão baseada no Windows VERSIONINFO, que é o que você vê no Windows Explorer (ou propriedades do arquivo). Ele forma parte do nome forte da montagem e é usado exclusivamente pelo Framework ao resolver assemblies. A versão de duas montagens e a versão do arquivo do Windows - podem ser diferentes, mas, mais frequentemente, elas são as mesmas para evitar confusões.
usa a versão para rastreamento de dependência, ou seja, observando as versões de montagens a serem referenciadas, tornando assim óbvio quando uma atualização quebra a compatibilidade para aplicativos que dependem de uma determinada biblioteca. Este é um passo à frente da versão do arquivo nativo do Windows, que foi usada apenas durante o processo de atualização, e não ao referenciar uma biblioteca, levando ao infame "DLL Hell".
Vale ressaltar que a Versão da Versão permite 4 inteiros de 32 bits, enquanto o AssemblyFileVersionAttribute é limitado a 16 bits, pois ele mapeia diretamente para o recurso VERSIONINFO. Assim, se queremos que AssemblyVersionAttribute e AssemblyFileVersionAttribute sejam iguais, isso também coloca um limite nos componentes da versão de montagem.
O Linux, em geral, usa um método diferente para abordar o controle de versão. Os arquivos binários não possuem um selo de versão incorporado, como a maioria dos binários do Windows. Em vez disso, um nome de arquivo da biblioteca compartilhada indica sua versão, e. /usr/local/lib/mylib. so.1.5.
São criados vários links simbólicos, e. mylib. so - & gt; mylib. so.1 e mylib. so.1 - & gt; mylib. so.1.5. Um aplicativo pode fazer referência a uma biblioteca via link simbólico, como mylib. so.1, e obter a versão compatível 1.x mais recente instalada.
Isso funciona bastante bem, desde que todos sigam esta convenção. Cada biblioteca pode então, por sua vez, carregar bibliotecas, dependendo da mesma maneira.
Os usuários de Linux também estarão familiarizados com o popular "Advanced Package Tool", apt-get, usado de forma ubíqua nos sistemas derivados da Debian, como o Ubuntu. Sendo um verdadeiro gerenciador de pacotes, ele suporta a instalação de versões lado a lado e dependências de rastreamento entre pacotes. Examinamos as vantagens dos gerenciadores de pacotes nas seções a seguir.
Esquemas dos números de versão.
Existem vários esquemas de numeração popular para software, mas todos eles são uma variação do mesmo tema e compartilham características comuns. Ter componentes principais e menores da versão é o mesmo em toda a placa. O que eles representam é bastante consistente:
Maior aumento de número: representa grandes mudanças de falha no sistema de software, muitas vezes não é compatível com versões anteriores, ou adição de grande quantidade de novas funcionalidades. Aumento de número menor: representa mudanças substanciais de evolução, principalmente atualizações ou melhorias na funcionalidade existente ou adição de uma nova nova conjunto de características.
Acima é apenas uma diretriz - não há regras estabelecidas sobre o que as versões maiores e menores devem representar. Só que eles deveriam aumentar à medida que mais recursos são adicionados ao software com o tempo.
Windows e binários especificam um esquema de versão de 4 partes: maior. menor. construir. revisão . Os dois últimos componentes são de forma bastante gratuita, existem muitas variações no que representam - alguns usam contadores de construção incrementais, alguns utilizam a data / hora da compilação, e alguns os derivam dos números de revisão interna do controle de origem.
Muitos ignoram o número da revisão e focam apenas na compilação. O Windows Installer, por exemplo, possui apenas 3 componentes. Se você deseja que sua versão abranja binários e o pacote que contém, então é melhor limitar-se a apenas três números: maior. menor. construir.
Em qualquer caso, o padrão geral: quanto maior for o número da versão, mais recente será o software.
Um popular sistema de controle de versão nos últimos anos (especialmente entre projetos de código aberto) foi apelidado de Versão Semântica (também conhecido como SemVer) e documentado no Semver. Ele apresenta alguns outros componentes, e torna a versão uma string alfanumérica, em vez de um número puro - abrindo algumas possibilidades interessantes.
Os três primeiros componentes são os mesmos que já discutimos, sendo o patch opcional. Patch é bastante equivalente ao componente de compilação, mas a semântica pode ser diferente. A versão semântica realmente prescreve quando cada componente deve ser incrementado (com base em mudanças de "API pública").
O pré-lançamento, se especificado, é uma seqüência alfanumérica que é usada para marcar uma versão como uma que precede a versão final. Por exemplo, 1.3.567-rc1 precederá 1.3.567. Isso é útil para adicionar mais significado ao rótulo da versão do que simplesmente usando números.
Metadata é outro componente opcional, que permite uma maior marcação do rótulo da versão (geralmente com um timestamp de compilação), mas não participa no pedido de versão, ou seja, as versões que apenas diferem nos metadados são consideradas as mesmas.
O pré-lançamento é útil com gerenciadores de pacotes como o NuGet, que os tratam de forma diferente - eles são considerados instáveis ​​e não são visíveis para o público em geral, a menos que seja explicitamente solicitado. Isso permite a liberação de versões alfa / beta sem afetar aqueles que dependem de versões estáveis.
As tags de pré-lançamento também podem ser úteis no fluxo de lançamento interno ao lidar com hotfixes paralelos e compilações privadas, conforme discutido mais adiante neste artigo.
Versioning Non-Binary Files.
Então, sabemos como marcar uma versão nos arquivos binários. Mas e os outros arquivos que compõem um sistema de software - arquivos de configuração, imagens, documentos, fontes, etc.? Como você marca uma versão neles?
E quanto a frameworks da web como o ASP (ou Ruby, Node. js, Python, etc.), onde arquivos e páginas de origem podem ser modificados no local e atualizados automaticamente? Como podemos partilhar um sistema web, por exemplo, atualizar alguns arquivos de destino e ainda mantê-lo versionado?
A resposta é - não atualize arquivos individuais! Não há como manter um número de versão significativo para o seu aplicativo de software, se os arquivos não binários individuais podem ser atualizados ad-hoc como hotfixes.
Atualize usando um pacote em vez disso.
Importância da compilação e do pacote.
Quando você ouve o termo "construir", normalmente a compilação vem à mente - a maioria dos idiomas compilados, como C #, C ++ ou Java, precisam ser compilados em um binário antes de poderem ser executados. E, portanto, a construção é comumente associada ao processo de compilação.
Mas essa não é uma imagem inteira. Alguns idiomas ou frameworks, como Python ou ASP, não exigem estritamente a compilação. Eles podem ser interpretados, no caso de Python, ou compilados on-the-fly, no caso do ASP. O que uma construção deve fazer para esses sistemas? Como você "constrói" um aplicativo Python?
É por isso que é mais útil pensar em construir como um processo de montagem ou simplesmente em uma embalagem. Assim como uma linha de bens de consumo, e. sapatos, é embalado antes de enviar para as lojas, assim como um sistema de software, antes de ser lançado.
Um conceito de pacote é essencial para o controle de versão, porque um pacote é uma coleção única das peças que compõem um sistema de software, ou parte dele, e, portanto, pode ser identificado e carimbado com uma versão. Com o sistema de Gerenciamento de Pacotes certo (que analisamos na próxima seção), ele pode ser implantado e atualizado e especificar dependências nos outros pacotes.
Software today is never a single binary executable file - it is a collection of various binaries, libraries, documents, configuration files, images, and other resources. A package is what helps us group them together, version and release to the outside world.
A package doesn't have to be sophisticated, although it helps in some situations (e. g. databases). It can even be a simple ZIP file, that can contain version in the file name, or embedded as a text file. In fact, many open source projects do just that - a release is a ZIP or a. tar. gz archive.
The important thing is that a package is a single unit, that is released and updated at the same time, leading to consistency . It is common to have several packages, for example, representing "client" and "server" components, or any other logical grouping applicable to a software system. Each package can then be updated on its own.
Let's take a look at some of the common packaging methods, the versioning approach, and which application they are best suited for.
Windows Installer.
Best Suited : Complete Windows GUI Applications, Windows Services, or Drivers.
The oldest, and for a long time the only recommended way, to install applications on a Windows platform. It has a built-in versioning support and a sophisticated (some would say "complicated") set of rules for determining when to update components. While a Windows Installer package (.msi) is a single file, in essence, it is a collection of small logical components (down to single files) that can be updated independently.
Windows Installer will actually check each individual file that is being installed, whether it has a version and whether the version is greater than a file with the same name already installed. That means it is important to version not just the installer package, but each file contained in it. But it also means that it is incredibly difficult to do downgrades (i. e. rollbacks) with Windows Installer.
It is best suited for traditional Windows Applications (GUI, services, drivers) that are released to the public. It is, however, not the best choice for internally developed & distributed applications, any kind of Web applications, or database systems.
It was also used to deploy distributable libraries (native DLLs) and COM objects, but with today's focus on , it is not the right mechanism for distributing libraries.
Web Deploy.
Best Suited : Web Applications (IIS, ASP)
Web Deploy technology was specifically designed for deploying and synchronizing applications on Microsoft IIS web servers. IIS Web Farm replication uses Web Deploy commands and packages behind the scenes to synchronize sites across a set of servers. IIS Manager has an extension (enabled by installing Web Deploy) to "Import Application", which can install or update a web application using a Web Deploy zip package.
Its biggest disadvantage is that it can only be used for web applications on Microsoft IIS platform, and the limited mechanism for customizing installation. While it could be suited for simple web applications, it can quickly become frustrating for anything more sophisticated, i. e. variables, conditional logic, databases, etc.
In addition, it has no inherent support for versioning .
Package Managers.
Best Suited : Shared Libraries, Dependencies, Command-line Utilities.
Package Managers are great for releasing and versioning shared components, and tracking dependencies between them. For example, if you have a shared library that you want others to use, then a Package Manager allows you to publish multiple versions side-by-side, and for consumers of the library to reference the version they depend on. Package Managers can resolve all inter-package dependencies, and retrieve only the versions that are expected. In effect, Package Managers solve the "DLL Hell" problem.
They are best used during development, to resolve library dependencies. However some Package Manager, like Chocolatey for Windows or apt-get for Ubuntu, are geared towards installing complete software.
Most importantly, Package Managers are designed around the versioning concept . So they are a perfect mechanism for distributing versioned software libraries.
For we have NuGet. A lot of open-source libraries have been published to its online repository, and it is now the defacto standard for distributing 3rd party components. It is encouraged that every team sets up their own NuGet repository to share and publish internally developed libraries in a versioned manner.
NuGet can even be used to release complete software systems - see next section.
Other development environments have their own - npm for Node. js, pip for Python, gems for Ruby, apt-get on Linux. Package Managers have been proven to be extremely useful, and have exploded in popularity.
Octopus Deploy.
Best Suited : Internally Developed & Deployed Software.
Octopus uses NuGet as the packaging and versioning shell. It is similar to an installer, only driven by PowerShell, meaning infinite flexibility in how the software is to be deployed. PowerShell already has a great support for configuring Windows Services, IIS Web Applications, Scheduled Tasks, SQL Server, and more.
For internally developed and distributed software (i. e. for a company running home-grown software solutions) this is a perfect release management vehicle. Packages are versioned and pushed to a shared NuGet feed (e. g. a network share), from where Octopus Deploy can release and deploy each package into the appropriate environment.
NuGet here plays a role of the application package/container, with a version stamped on it. Package can be built once, and then deployed as many times as needed to whatever environment.
Versioning & Packaging Databases.
Database versioning is one of the biggest challenges in software projects. Almost every team I encountered, either completely ignored it or had something inadequate in place. It certainly presents a challenge - database systems mix schema definition with actual live data , and there is no single "file" that can be effectively versioned.
We have to recognize the database as an integral part of the software system. One that executes on a proprietary 3rd-party platform (SQL Server, Oracle, PostgreSQL, etc), but the source of which is part of the software definition. It can be compared to script-based systems, such as Node. js or Python, only the scripts are written in a SQL dialect.
There are essentially three popular approaches to database versioning, that support automated deployments (I am not considering manual approaches, because they are error-prone, and have nothing to do with real versioning!).
DB - Migrations.
"Migrations" is a concept where developers keep a set of organized SQL script files, numbered sequentially, where each script applies modifications to the target DB to bring it to the expected state. Whenever a change is needed to the application database, a developer creates a new migration script that applies the delta changes.
All of the scripts are kept as part of the source control, and are packaged with the application (either embedded into the executable binary, or installed along-side). A migrations library then checks the target database for a dedicated table which holds the last "migration script number" applied, and then runs all the scripts with a number greater than that in order, effectively applying all of the changes in turn.
While this approach is simple to implement, and is favored among several popular frameworks (Ruby Rails, Entity Framework), it has a number of significant short-comings . Firstly, there is no single source view of all database objects (i. e. tables, stored procedures, etc), they are sprinkled through the multiple migration scripts. It is not clear which of the scripts contains which of the modifications. One has to "replay" them all to generate a database, and then look directly in the database (rather than source code).
Secondly, the migration scripts number becomes the "version" of the database, which is different from the software package version number for the rest of the application. This is somewhat confusing. In addition, this "version" does not really identify the state of the database, since a database can be changed outside an application without updating the "version". This may potentially break future installs, because migration scripts expect the database to be in a certain state to work.
Thirdly, developers have to be disciplined enough to follow the structure and apply ALL changes through migration scripts . Furthermore, when developing and debugging locally, one often has to go through several iterations before getting that table or store procedure change right. Yet only the final changes should make it into the migration script , meaning they have to be remembered and written manually. Otherwise, migration scripts would contain all of the intermediate changes made by all developers on the project. It is easy to see how that can grow out of proportion quickly.
Finally, there is an argument that migration scripts are a "history of changes", and it is a bit of a redundancy to store them in source control, which already is a "history" of code changes. We would be storing a history of a history . There's something philosophical about that.
Supported by some frameworks and libraries (Rails, DbUp, RoundHousE, EF Code First) Can work with any database Potentially high degree of control over SQL scripts.
Have to manually maintain all migration scripts Tracking changes through source control is difficult Not robust against target database out-of-band changes.
DB - SQL Compare.
Most often this is used in a manual approach, comparing a database between two environments (e. g. development vs test) to copy over the changes. We are considering an automated approach, suitable for the packaging and versioning strategies being discussed.
In source control, database is represent by a series of creation scripts (e. g. to create tables, stored procedures, triggers, etc), such that a new database with the right schema can be created from scratch. Usually each script file logically represents a corresponding object in the database, e. g. Table1.sql would be the create script for Table1 table. All of the scripts are included in the released package (sometimes even combined into a large single create script, by concatenating them).
The idea is that during automated package deployment a temporary fresh database copy is created, by running all of the creation scripts , and then a SQL Compare tool is executed to compare the pristine copy with the target database to generate a migration delta script on the fly.
The advantage of this approach is that it is robust against the target database out-of-band changes, since delta script is generated during deployment , rather than during development. SQL Compare tools (such a RedGate's SQLCompare or XSQL Compare) are sophisticated and mature enough tools that we can have some confidence in the generate SQL code. Each can be controlled by a multitude of options to fine-tune behavior with respect to renames, reordering columns, avoiding drops, etc.
In this case, target database is considered as a runtime environment , and we avoid having the issue of versioning it . Instead we version the package that contains all of the creation scripts , which is much easier, and use it to synchronize target database with what's expected in each version.
The big disadvantage of this approach is the difficulty of getting it right - there is no off-the-shelf framework that would support it, and it has to be developed. For SQL Server, read the next section for a better approach. For others, some day I may put together the set of scripts and logic necessary to achieve this, based on some of my prior work (unless someone else beats me to it).
Automatically detect and migrate changes, regardless of target DB state Only maintaining DDL (i. e. create) scripts in source control, meaning easy change tracking.
More difficult to setup, especially to be automated Having to create a temporary database during each deployment (need " create database " permission)
DB - DACPAC (SQL Server)
For SQL Server there is now a new recommended approach - DACPAC, and it can be produced by Visual Studio 2018 and above, if using the SQL Server database project. Really, this is a slick variation of the "SQL Compare" method above, just that Microsoft has done all the heavy lifting for you!
Essentially, DACPAC is a zip package which contains an XML schema model of what the target database should look like. It is compiled by Visual Studio based on the creation scripts in your project. In fact, it represents that temporary pristine database that we would have had to create manually. Only it is done automatically and the schema represented in an XML format. The real bonus is that a DACPAC can be versioned , i. e. its metadata supports storing a version number.
SQL Server Data Tools can be used to deploy a DACPAC package, which really performs a SQL Compare operation between the in-memory database model loaded from DACPAC and the target database. It does the same thing as SQL Compare, but avoids having to create the extra temporary database copy to do the comparison.
For applications having SQL Server as a back-end, a DACPAC can be included as one of the deployable packages, stamped with appropriate version generated during the build. Starting with SQL Server 2008 R2, database can be registered as a Data-Tier Application, and the latest DAC version is tracked in a system view that can be queried.
Can package the whole DB definition into a single package (or several packages) Can apply the same version to the package as the rest of the software system Same advantages as the SQL Compare method.
SQL Server only Need to treat lookup data in a special way (post-deploy MERGE script)
Build Auto-versioning.
Given the importance of consistent versioning discussed above, it makes sense to implement a strategy for automatically generating and stamping a version number during the software automated build process. We want the version number to be applied to the produced packages, and also applied to all the binaries generated through compilation.
There are several well-known and not so well-known ways of achieving this. We look at pros and cons of each.
Applying Build Number.
There are some who prefer to update the version number manually just before a release. I will argue that this is a bad practice. Firstly, it is easy to forget to do it, if you don't have an automated system for incrementing the version build number. And, if it is easy to forget, it will be forgotten at some point.
Secondly, without automatically updating build number, there will be multiple packages produced from the source code that have the same version number, but different functionality (as more commits are made to the source control). This will be confusing to say the least.
It is better to have a process, like ones described below, where version number build component is automatically updated whenever a non-local build is made.
Multiple Versions for Multiple Components.
If there are multiple software components, where each needs to have its own version number, then it is best to split them each into its own separate build. Don't mix multiple version numbers in the same build, as it unnecessarily increases the complexity, and raises a question about which of the build numbers should be used to label the build itself (in addition to having to tag each source sub-tree separately).
Developer vs Continuous vs Release Builds.
Release build is the one that will potentially be released to public or a particular environment - test, staging, production, etc. That's the build that needs to be consistently versioned to keep track of changes that are included and to link back to the source code at the time of compilation.
Note that the Release build can scheduled - it is popular to have a Daily or Nightly build. In most situations it should be the Release build, i. e. it should be versioned and packaged ready to be released.
Continuous Integration builds run whenever someone commits to the repository and are used to validate that the code compiles, and passes unit tests. There is no need to version this build, as it is not intended to be released.
Developers must also be able to do a Developer build , whether it is to test/fix the build process itself, or to generate shared software components to be used in development. Such builds are intended to be run locally only and should never be publicly released.
You can default the build part of the version number to "0". This will identify Developer builds, i. e. ones that are not supposed to be released. For Release builds pass the build number to your build scripts as a property. Have MSBuild stamp a version number on all generated assemblies and packages.
Tagging Source Control.
Since one of the primary reasons for having a version number is to be able to link back to source code used to build the software (see beginning of the article), it is important to create tags/labels in source control that identify the state of source code at the time that version was built.
Various systems call it differently - TFS has "Labels", Git has "tags". Tag should include the full version (including the build number) of the build, so that it can later be found, if needed.
Build Number - Version File Auto Increment.
Common technique is to record version number together with source code, usually in a separate file (e. g. "version. txt"). The build process then finds the file, reads the version, increments the build number portion, and commits the file back to repository.
If the commit message also includes the version number, e. g "Auto-increment: 1.3.156.0" , then it comes in handy when viewing commit history. You can see the changes that occurred between versions clearly by seeing the commits between the two "Auto-increment: . " messages.
This works fairly well, but has a few drawbacks. Mainly due to the fact that "version" becomes part of the source code. When merging changes between say release branch and main, you have to resort to "cherry-picking" (i. e. selecting just the code changesets) to avoid merging the modified version number. That requires being always careful, because you can accidentally change the versioning sequence of another branch just by merging the "version file" into it.
Control over the build number sequence (i. e. sequential) Can make it easy to see changes between versions in source control history.
Difficult to control merging between code branches in source control.
Build Number - External.
Overcoming the drawbacks of the auto increment approach, it is possible to track the build number outside of the source tree. Build server software such as CruiseControl or TFS Builds can do that - they track a build number internally for each "project" and are able to pass it as a parameter to MSBuild.
Version file is still used, but it records major and minor versions only, and doesn't have to change between each build. This makes it easier to merge changes from release branches back to main and others, since they will contain only code changes, without being intermingled with version increments. Major/minor version changes would occur early in the development cycle, when starting work on the next update, and are already set by the time release branch is created.
Not modifying source tree on every build makes merging between branches easier Versioned builds are forced to be built by a dedicated build server.
Relies on a build system that can supply a build number (e. g. CruiseControl, TFS Builds) Changing build number sequence can be difficult (e. g. TFS Builds)
Build Number - Derived from Date/Time.
A popular alternative is to derive build number for the date/time of the build. The advantage being that it carries more meaning (useful in diagnosis), and each build inherently should get a different build number (with later builds getting a higher number).
The trick, of course, is fitting all this into a 16-bit number, if using the standard 4-part Windows version number. While some solve it by using both, the build and revision components, I cannot recommend it, because revision cannot always be applied to external packages (like Windows Installer, or NuGet), which use only a 3-part version number.
This only allows only 4 unique builds per day, which is not a lot, unless all you want is a daily build .
Not depending on keeping track of the last build number Build number can be given more meaning, if it derives from a date.
Build number is not sequential (but it increases nevertheless) Limited to 16-bit (maximum 65535), so some overflow into revision (4th) number.
Build Number - Derived from Source Control.
A variation of the previous technique is to derive build number from a unique property in source control. With a centralized SCM like Subversion or TFS, a revision or changeset number is an ever increasing number that is tied directly to the source code. The big problem with it is that it can quickly overflow the 16-bit limit, meaning you may have to accept build numbers looping back to zero.
An alternative in distributed SCM, like Git, is to use the size of the commit history log as the build number. This will monotonously increase for any single branch, as new commits are made. It too can overflow the 16-bit limit, but goes a lot further than the global revision number.
Example: git rev-list HEAD --count.
Not depending on keeping track of the last build number No possibility of "forgetting" to update version file, or accidentally merge it to/from another branch.
Commit history size will grow beyond 65,535 at some point, overflowing the 16-bit build number.
Parallel Branches.
It's no secret that developing for multiple versions requires multiple branches in source control, each representing a "version" stream for the software. They can be roughly divided into:
Development branches - where unstable code for the next version lives, and where developers commit daily work Feature branches - veering off from development branches, encorporating larger feature development, that would otherwise disrupt other team members Release branches - representing versions of released software, or a release undergoing stabilization.
Each release branch needs to have an identifying version, and is usually named after it, e. g. "1.7" . A decision of whether to create a new release branch depends on how long it is expected that it will be in stabilization mode before releasing, and whether concurrent live versions are permitted (i. e. for packaged software). If you need to be able to maintain & hotfix the current released version, while a new version is being tested & stabilized, then create a new branch.
Development and feature branches need to have a version number that is above any of the existing release branches to avoid confusion. For example, if a 1.7 release branch is created, for the upcoming 1.7 release, then immediately update development branch version sequence to 1.8 .
Versioning feature branches is more difficult, since you don't want to start a new versioning sequence for every feature . Nothing should be "released" from feature branches, so this version is for internal purposes only. If using Semantic Versioning, attach a prerelease tag to clearly indicate this is a version for a feature branch, e. g. 1.8.781-dev-feature-x .
In any case, you wouldn't deploy anything built from a feature branch to the shared testing or production environment, or release a package from it. So it is acceptable to have version sequence overlap with that of development branch.
Finally, in the next section we look at how to version patches & hotfixes that are applied to release branches.
Handling Patches / Hotfixes.
Devising a system to handle patches depends heavily on the rest of the software development cycle, which is what many teams forget when searching for the "one, true way" of handling concurrent patching of the released/production software in parallel with working on the new version.
For example, having a short QA/test cycle, where most of the tests are automated, results in a more simplified and robust system, which does not have to deal with multiple parallel hotfixes "in test".
Overlapping hotfixes.
One difficulty that comes with managing parallel development is consistent versioning and deployment strategy that would overcome inherent conflicts. Consider following scenario: you have recently released a software package 1.5.167. Two urgent show-stopping issues have slipped past your QA process and now require a quick fix. You assign two developers to work on each one in parallel. How would they commit their fixes to minimize conflicts? How do you test each fix? How do you release one independent of the other?
This is a good example of the complexity of software release processes that can be encountered in real-world teams. It applies both to internal software and packaged software, but distribution of the hotfix might be slightly different for each one.
First, let's consider what happens if we remove concurrency . In the case where the two issues are worked one after the other , the solution becomes simple. The first fix gets committed into the maintenance/hotfix branch for 1.5 release stream, a new build is generated, with an incremented build number. Build goes through a quick QA cycle to make sure there is no regression, and then it is ready to be deployed. Same process repeats for the second fix.
The problem with concurrent approach is the time when development is in parallel, creating the entangled case where there is no build/package that contains only one of the fixes , i. e. independent of the other. This problem is magnified by a slow QA cycle , usually meaning there are no automated tests. While one fix is in test, if a commit for a second fix is made to the same branch, and a problem is discovered with the first one, it becomes very difficult to separate the two now.
The culprit here is, of course, the concept of a partial fix - the state where the fix is not complete. It has been committed, but has a problem with it, requiring further commits . This can easily create the case of a hotfix branch where the two fixes are "entangled" (quantum physics on the code level!).
Solution is to remove possibility of a partial hotfix .
This means that each hotfix has to be coded and tested in a separate code stream, independent of the other. Once tested, and ready for release, it is merged into the main hotfix release branch, where the automated build can create a new package and apply versioning (i. e. increment build number, for example, to 1.5.168).
Second hotfix, once tested, also has to be merged into the main hotfix release branch. But, because during the work on this second hotfix, the first hotfix got released, we first merge the first hotfix into the second hotfix's branch ! This ensures that we can test how the second hotfix operates, when applied on top of the first hotfix, and merge any code conflicts, if any.
In the end, you want a system with both hotfixes applied - that is the "next" version. So it makes sense that whatever hotfix is "second", it is applied on top of the "first" one. And creating a packaged release from the single hotfix release branch ensures that the version number is consistently incremented for the whole system.
Of course, above means that we must create a separate branch for each hotfix. Some version control systems, namely Git, make this very easy and part of the expected developer workflow. If you are using a version control system like TFS, then creating new branches for each hotfix is a bit more painful. In TFS, I suggest using named Shelvesets feature to emulate Git's process, and perform initial QA tests for a hotfix from a Shelveset-branch build. Then commit Shelveset into the hotfix branch to build the official hotfix package (and perform necessary merging).
What about the versioning of the interim hotfix builds ? The main hotfix release branch would have a standard versioning scheme applied (as discussed above), either incrementing a build number, or using a timestamp. Each new hotfix, applied on top of all previous hotfixes, gets an increased build number , and the software version keeps moving forward.
However, when building from the developer hotfix branch (or Shelveset in TFS), we also need to apply a version to distinguish it from other builds, and be able to deploy it into QA/test environment. We want to be able to test each hotfix in isolation, applied on top of an existing released version of the software system. This becomes problematic, if you have a single test environment .
You do not want to apply both hotfixes into one test environment, because there is no guarantee that they won't conflict or affect each other. If you are able to quickly spin up a test environment for a hotfix development branch, then you can truly parallelize team efforts. For a shared test environment, they have to be applied one at a time :
Force install latest release version (e. g. 1.5.168) to bring environment to a known state Install the hotfix version to be tested Perform the tests (preferably automated) For shared test environnments this is the bottleneck, since no other hotfixes can be tested at the same time (automation can help minimize the time spent in this step) Repeat 1-3, until tests are satisfactory.
What this means is that each hotfix has to have its build version number greater than the latest released version, the one it is being applied on top of. There are several ways to achieve that. If using a derived build number , this should just work out of the box. If incrementing or using external build numbers, then the easiest option is to simply force the build for hotfix development branch (or Shelveset) to use a number greater than latest released version (i. e. .168).
With Semantic Versioning, we can setup hotfix builds to use a "prerelease" tag that clearly marks it as a hotfix-test build. For example - 1.5.169-check14761 , where the trailing number could be a reference to the issue tracking system. This works especially well when using NuGet as the packaging mechanism.
Once tested, the changes can be merged into hotfix release branch, and an official build generated, with incremented build version number.
NOTE: Above process to resolve concurrent hotfixes is undoubtedly complicated. It is intended to solve a particular real-world scenario, but one that does not happen too often. If there are no concurrent fixes expected, you can simplify your life by applying fixes directly to the hotfix release branch.
Patching a large system.
If applying hotfixes to a large system, we don't want to upgrade the whole thing, which may involve a lot of different components - services, GUI applications, scheduled jobs, databases, etc. Instead, we want to apply the fix only to affected parts.
This is where splitting the system into multiple packages helps. Each corresponds to a logically contained piece of the system - for example, each service, application, database, etc is its own package. That means they can be patched independently by applying just that package .
Care must be taken about dependencies, if hotfix affects multiple packages at once. Although, in that case, ask yourself is it really a hotfix or a new minor version?
Patching for specific installation.
Some software shops may have developed the practice of patching the software for individual customers (for packaged software), in other words creating a "custom" version for just that installation, without including this fix in the rest of released software streams. This is one of the worst situations to be in, with regards to versioning, since it creates a large number of variations that have to be maintained separately.
Instead, release a general update , moving the overall software version forward for that release stream. Adopt a "feature" system , where parts of the software can be turned on & off based on configuration. If a specific fix is needed for a particular installation, then that code can be encapsulated behind a configuration switch which turns this section of the code on or off. That particular customer can turn it on , while the rest can have it off!
This is also a popular technique in web applications, of which only one installation exists (on the server), where various "features" can be enabled based on "configuration" for each user , or a set of users.
Patching the changes only.
There is often the temptation to simply patch in the changes to the live/production system by editing/replacing one file, or updating one table or stored procedure. The change is small, and it seems like the fastest way to solve the imminent issue, without changing anything else in the system.
While it seems like a smaller risk to make only the necessary updates directly, it makes it a whole lot harder to know the state of the system in the future. As more and more such "small" patches get applied, there is no longer any reliable way to link the running system back to the original source code, making further maintenance exponentially more complicated (and, ironically, increasing the risk).
Updating individual non-binary (e. g. config files) or altering database objects does not update any version number . That means it is difficult to tell which changes have been made to the system, leading to "maintenance hell" (a variation of the infamous "DLL Hell").
Rule of thumb: Any change to the system should change the version number.
NOTE : Windows Installer allows a so called "small update", where product version number does not have to change, used for small hotfix patches. I believe this creates too much confusion, and so I do not recommend it. Windows Installer does track each patch, through package code, so you always know which patches have been applied. But it means now having to track and remove patches on subsequent product updates, which complicates the process. It may work for Microsoft Windows and Microsoft Office, but I wouldn't recommend using it for any system.
Palavras finais.
This turned out to be a much longer article than I originally anticipated when I sat down to write about versioning . I am hoping it proves useful for software engineers out there looking for some guidance on how to apply these concepts in their own projects.
Still this seems like only a partial treatment of the topic.
Everything I wrote above has been learned through the painful process of trial & error over the years. If just a few readers have an "aha!" moment while reading this, then I have achieved my goal!

Software versioning strategy


Obter através da App Store Leia esta publicação em nosso aplicativo!
A good strategy for implementing a versioning system.
I have been struggling with versioning software for a while now. I'm not talking about a naming convention, I'm talking about how to actually apply a version in a build system all the way through to a release.
I generally use major. minor. maintenance-[release type] i. e. 1.0.2-rc1.
The problem is managing the version number. I've tried many ways (sticking it in a build file, a properties file, a database, etc, etc) but I haven't found anything that really works well.
I'm wondering if anyone has any good ideas about this. Also, wondering how people handle releasing a version. i. e. If I release/deploy version 1.0.0-rc1 do bugs found in this release then get logged into 1.0.0 (the next/production release).
Microsoft uses <major>.<minor>.<patch>-<build number> (or a variation).
I like using <major>.<minor>.<buildnumber>
Where I'm working we use the Maven system: artifact[-major-minor-revision][-SNAPSHOT] which allows us to develop "in progress" versions that change at a moments notice (SNAPSHOT) and those which have been formally released. Alguns exemplos são:
email-services-1.0.0-SNAPSHOT. jar email-web-2.3.11.war crm-2.5.0.ear.
If it has SNAPSHOT in it then it hasn't passed the full suite of tests or is just a developer experiment. If it doesn't have SNAPSHOT then it is a release candidate. We maintain a repository of release candidates and the most recent is sent for deployment once the testers are happy with it.
All of this can be managed with a few simple entries in a build file under Maven. See Maven2 tutorial.
This is probably a dead post now, but I'll add my two cents anyways. I'm of the opinion that build numbers should mean something to everyone who sees it. So I personally think that this is a good way to name versions:
major. minor. patch. revision - e. g. 1.1.4.2342.
Major/minor numbers are pretty self-explanatory. But from the perspective of the 3rd number, it still needs to mean something to the customer. I've released this new version to you, Mr. Customer, but it wasn't worth a new minor number since we just fixed some bugs. So we've incremented the patch number.
The 4th number usually means absolutely NOTHING to the customer, so you might as well make it useful to you and anyone else in your company that sees it. So for us, that number is the SVN revision number. It tells us exactly which revision was responsible for that version so that we can pull it out any any time to recreate it. Branching code obviously achieves this too, but not to 100% certainty.
Also, another advantage with an all-numeric version number is that it easily integrates into nearly every continuous build system.
Anyways, that's my two cents.
+1 on the Jira/Bamboo solution. The only additional information about the build I would include (for my purposes) is the Subversion Release, although the Tagging operation is 80% of what I want.
Manually maintaining the release/version information is a royal pain. Letting JIRA drive it is a great idea.
On the final question, about where bugs/defects get logged and releasing a version:
Defect/Issue is logged against the release where it appears. A defect in 1.0.0-rc1 gets logged against 1.0.0-rc1 JIRA has (or maybe we added) a 'Fix-For' field that would have the planned release, in this case 1.0.0 If the defect/issue is severe enough, it may be necessary to add another 'rc' release. The release is made when there are no outstanding critical defects/issues and the customer (or management) agrees that any remaining issues can be deferred.
The beauty of managing this through JIRA is that adding releases, generating change-logs, etc. is automated fairly well.
We also use <major>.<minor>.<buildnumber> and we manage this with CruiseControl/() on our build server. And use Wix and CruiseControl Config to manage the Major minor numbers - still increment those by hand - but the build number happens automatically when on the build server. You could set up a rule an increment the major/minor automatically too I believe - we just have like to do that manually so that it takes concious thinking by a dev when it is time to name a particular release level.
Major and Minor are set by us, manually incrementing them as we see fit.
BuildDateNumber is the number of months since the project start multiplied by 100, plus the day number of the current month.
DailyBuildNumber is incremented for every build after midnight each day, starting at zero.
Por exemplo. 4th build of release 5.2 on 10 July, where the project started 1 Jan that year, would have version number.
This is all calculated for us by the Version task in Nant.
This keeps the version numbers unique and also allows us to quickly calculate when an installation was built.

Feature branching your way to greatness.
Or task branching your way there. Or release branching. You choose.
The Agile Coach.
Almost all version control systems today support branches–independent lines of work that stem from one central code base. Depending on your version control system, the main branch may be called master, mainline, default, or trunk. Developers can create their own branches from the main code line and work independently alongside it.
Why bother with branching?
Branching allows teams of developers to easily collaborate inside of one central code base. When a developer creates a branch, the version control system creates a copy of the code base at that point in time. Changes to the branch don't affect other developers on the team. This is a good thing, obviously, because features under development can create instability, which would be highly disruptive if all work was happening on the main code line. But branches need not live in solitary confinement. Developers can easily pull down changes from other developers to collaborate on features and ensure their private branch doesn’t diverge too far from the master.
ProTip : Branches aren't just good for feature work. Branches can insulate the team from important architectural changes like updating frameworks, common libraries, etc.
Three branching strategies for agile teams.
Branching models often differ between teams, and are the subject of much debate in the software community. One big theme is how much work should remain in a branch before getting merged back into master.
Release branching.
Release branching refers to the idea that a release is contained entirely within a branch. This means that late in the development cycle, the release manager will create a branch from the master (e. g., “1.1 development branch”). All changes for the 1.1 release need to be applied twice: once to the 1.1 branch and then to the master code line. Working with two branches is extra work for the team and it's easy to forget to merge to both branches. Release branches can be unwieldy and hard to manage as many people are working on the same branch. We’ve all felt the pain of having to merge many different changes on one single branch. If you must do a release branch, create the branch as close to the actual release as possible.
Warning: Release branching is an important part of supporting versioned software out in the market. A single product may have several release branches (e. g., 1.1, 1.2, 2.0) to support sustaining development. Keep in mind that changes in earlier versions (i. e., 1.1) may need to be merged to later release branches (i. e., 1.2, 2.0). Check out our webinar below to learn more about managing release branches with Git.
Feature branching.
Many agile teams looking for a more flexible branching model have moved from release branching to feature branching. A feature branch model keeps all of the changes for a particular feature inside of a branch. When the feature is fully tested and validated by automated tests, the branch is then merged into master.
Feature branches are often coupled with feature flags–"toggles" that enable or disable a feature within the product. That makes it easy to deploy code into master and control when the feature is activated, making it easy to initially deploy the code well before the feature is exposed to end-users.
ProTip: Another benefit of feature flags is that the code can remain within the build but inactive while it's in development. If something goes awry when the feature is enabled, a system admin can revert the feature flag and get back to a known good state rather than have to deploy a new build.
Task branching.
At Atlassian, we focus on a branch-per-task workflow. Every organization has a natural way to break down work in individual tasks inside of an issue tracker, like JIRA Software. Issues then becomes the team's central point of contact for that piece of work. Task branching, also known as issue branching, directly connects those issues with the source code. Each issue is implemented on its own branch with the issue key included in the branch name. It’s easy to see which code implements which issue: just look for the issue key in the branch name. With that level of transparency, it's easier to apply specific changes to master or any longer running legacy release branch.
Since agile centers around user stories, task branches pair well with agile development. Each user story (or bug fix) lives within its own branch, making it easy to see which issues are in progress and which are ready for release. For a deep-deep dive into task branching (sometimes called issue branching or branch-per-issue), grab some popcorn and check out the webinar recording below–one of our most popular ever.
Now meet branching's evil twin: the merge.
We’ve all endured the pain of trying to integrate multiple branches into one sensible solution. Traditionally, centralized version control systems like Subversion have made merging a very painful operation. But newer version control systems like Git and Mercurial take a different approach to tracking versions of files that live on different branches.
With Git, merging is trivial–freeing us to exploit the full power of branching workflows.
Branches tend to be short-lived, making them easier to merge and more flexible across the code base. Between the ability to frequently and automatically merge branches as part of continuous integration (CI), and the fact that short-lived branches simply contain fewer changes, "merge hell" becomes is a thing of the past for teams using Git and Mercurial.
That's what makes task branching so awesome!
Validate, validate, validate.
A version control system can only go so far in affecting the outcome of a merge. Automated testing and continuous integration are critical as well. Most CI servers can automatically put new branches under test, drastically reducing the number of "surprises" upon the final merge upstream and helping to keep the main code line stable.
Products discussed.
Sign up for more articles.
Recommended Reads.
Put it into practice.
Get serious about branching.
Manage Git repositories, set up permissions, and collaborate on code. Secure, fast, and enterprise-grade. Explore Bitbucket.
A seguir.
Why code reviews matter.
Code review helps developers learn the code base, new technologies, and new techniques. But the kicker is that they actually save you time.
May we also recommend.
Awesome issues for Git branching workflows.
Learn about three rookie mistakes teams make when adopting a feature branching workflow, and how to correct them by using issues to track work in JIRA Software. Continue lendo.
Sobre o autor.
Dan Radigan.
Senior Agile Evangelist, Atlassian.
Agile has had a huge impact on me both professionally and personally as I've learned the best experiences are agile, both in code and in life. You'll often find me at the intersection of technology, photography, and motorcycling. Find me on Twitter! @danradigan.

No comments:

Post a Comment