Ir + = controle de versão do pacote

Artigo escrito em fevereiro de 2018.

O Go precisa adicionar controle de versão do pacote.

Mais precisamente, você precisa adicionar o conceito de versão ao dicionário e ferramentas de trabalho dos desenvolvedores Go, para que todos usem os mesmos números de versão quando mencionarem qual programa criar, executar ou analisar. O comando go deve dizer exatamente quais versões de quais pacotes estão em um assembly específico.

A numeração de versão permite que você faça montagens reproduzíveis: se eu publicar a versão mais recente do meu programa, você obterá não apenas a versão mais recente do meu código, mas também as mesmas versões exatas de todos os pacotes dos quais meu código depende, para que possamos criar arquivos binários completamente equivalentes.

O controle de versão também garante que amanhã o programa crie exatamente o mesmo de hoje. Mesmo se novas versões de dependências forem lançadas, o go não as utilizará sem um comando especial.

Embora você precise adicionar controle de versão, não deve abrir mão das principais vantagens do comando go : é a simplicidade, a velocidade e a compreensibilidade. Hoje, muitos programadores não prestam atenção às versões e tudo funciona bem. Se você criar o modelo certo, os programadores ainda não prestarão atenção aos números das versões, apenas tudo funcionará melhor e ficará mais claro. Os fluxos de trabalho existentes mal mudam. O lançamento de novas versões é muito simples. Em geral, o controle de versão deve ser esquecido e não tirar a atenção do desenvolvedor.

Em resumo, você precisa adicionar o controle de versão do pacote, mas não o break go get . Neste artigo, sugerimos como fazer isso e também demonstramos um protótipo que você pode tentar agora e que, espero, se tornará a base para uma possível integração. Espero que este artigo seja o começo de uma discussão produtiva sobre o que funciona e o que não funciona. Com base nessa discussão, farei ajustes tanto na minha proposta quanto no protótipo e, em seguida, apresentarei a proposta oficial para adicionar uma função opcional ao Go 1.11.

Essa proposta mantém todos os benefícios do go get , mas adiciona construções reproduzíveis, suporta o controle de versão semântico, elimina a vendorização, remove o GOPATH em favor de um fluxo de trabalho baseado em projeto e fornece uma saída suave do dep e de seus predecessores. No entanto, esta proposta ainda está em um estágio inicial. Se os detalhes não estiverem corretos, iremos corrigi-los antes que o trabalho entre na distribuição Go principal.

Situação geral


Antes de examinar a proposta, vejamos a situação atual e como acabamos nela. Esta seção pode ser um pouco grande demais, mas a história traz lições importantes e ajuda a entender por que queremos mudar alguma coisa. Se isso não lhe interessar, você pode acessar imediatamente a oferta ou ler o artigo do blog que acompanha o exemplo .

Makefile , goinstall e go get


Em novembro de 2009, o compilador, o vinculador e várias bibliotecas foram lançadas com a versão inicial do Go. Para compilar e vincular os programas, era necessário executar 6g e 6l , e incluímos exemplos de makefiles no kit. O shell mínimo do gobuild pode compilar um pacote e escrever o makefile correspondente (na maioria dos casos). Não havia uma maneira estabelecida de compartilhar o código com outras pessoas. Sabíamos que isso não era suficiente - mas lançamos o que tínhamos, planejando desenvolver o resto em conjunto com a comunidade.

Em fevereiro de 2010, propusemos o goinstall , um comando simples para baixar pacotes de repositórios de sistemas de controle de versão como Bitbucket e GitHub. Goinstall introduziu convenções nos caminhos de importação que agora são geralmente aceitos. Mas, na época, nenhum código seguia essas convenções: o goinstall inicialmente funcionava apenas com pacotes que não importavam nada, exceto a biblioteca padrão. Mas os desenvolvedores rapidamente mudaram para um único contrato que conhecemos hoje, e o conjunto de pacotes Go publicados se tornou um ecossistema holístico.

O Goinstall também corrigiu os Makefiles e, com eles, a complexidade das opções de criação personalizadas. Embora às vezes seja inconveniente que os autores do pacote não possam gerar código durante cada compilação, essa simplificação é incrivelmente importante para os usuários do pacote: eles não precisam se preocupar em instalar o mesmo conjunto de ferramentas que o autor usou. Essa simplificação também é crucial para a operação das ferramentas. Makefile é uma receita passo a passo necessária para compilar um pacote; e aplicar outra ferramenta como go vet ou preenchimento automático no mesmo pacote pode ser bastante difícil. Mesmo obtendo dependências corretamente, reconstruir pacotes se necessário e somente se necessário, é muito mais complicado com Makefiles arbitrários. Embora naquela época algumas pessoas tenham contestado que estavam sendo privadas de flexibilidade, mas olhando para trás, fica claro que abandonar o Makefile foi o passo certo: os benefícios superam em muito a inconveniência.

Em dezembro de 2011, em preparação ao Go 1, introduzimos o comando go , que substituiu goinstall por go get .

Em geral, a go get apresentou alterações significativas: permitiu que os desenvolvedores da Go trocassem o código-fonte e usassem o trabalho um do outro. Ele também isolou partes do sistema de construção dentro do comando go , para que uma automação significativa com a ajuda de ferramentas go possível. Mas go get sem o conceito de controle de versão. Nas primeiras discussões sobre o goinstall, ficou claro: você precisa fazer algo com o controle de versão. Infelizmente, não estava claro o que exatamente fazer. Pelo menos nós da equipe Go não entendemos isso claramente. Quando solicita um pacote, ele sempre obtém a cópia mais recente, delegando as operações de download e atualização para um sistema de controle de versão como Git ou Mercurial. Esse "trabalho cego" levou a pelo menos duas falhas significativas.

Controle de versão e estabilidade da API


A primeira grande desvantagem do go get é que, sem o conceito de controle de versão, ele não pode dizer nada ao usuário sobre as mudanças que se espera nesta atualização.

Em novembro de 2013, uma versão do Go 1.2 adicionou uma entrada de Perguntas frequentes com esses conselhos sobre controle de versão (o texto não foi alterado para a versão Go 1.10):

Pacotes para uso geral devem manter a compatibilidade com versões anteriores à medida que evoluem. As recomendações de compatibilidade Go 1 são relevantes aqui: não exclua nomes exportados, incentive a marcação de literais compostos e assim por diante. Se nova funcionalidade for necessária, adicione um novo nome em vez de alterar o antigo. No caso de uma alteração fundamental, crie um novo pacote com um novo caminho de importação.

Em março de 2014, Gustavo Niemeyer lançou o gopkg.in sob o pretexto de "APIs estáveis ​​para o idioma Go". Esse domínio é um redirecionamento do GitHub gopkg.in/yaml.v1 versão que permite importar caminhos como gopkg.in/yaml.v1 e gopkg.in/yaml.v2 para várias confirmações (possivelmente em diferentes ramos) de um repositório Git. De acordo com o controle de versão semântico, os autores devem, ao fazer alterações críticas, lançar uma nova versão principal. Portanto, versões posteriores do caminho de importação da v1 substituem as anteriores e a v2 pode fornecer APIs completamente diferentes.

Em agosto de 2015, Dave Cheney enviou uma proposta para controle de versão semântica . Nos meses seguintes, isso causou uma discussão interessante: todos pareciam concordar que a marcação semântica de versões é uma ótima idéia, mas ninguém sabia como as ferramentas deveriam funcionar com essas versões.

Quaisquer argumentos para versão semântica serão inevitavelmente criticados com referência à lei de Hyrum :

O contrato da sua API não é importante para um número suficiente de usuários. Alguém depende de qualquer comportamento observado do sistema.

Embora a lei de Hyrum seja empiricamente correta, o controle de versão semântica ainda é uma maneira útil de gerar expectativas sobre o relacionamento entre liberações. Uma atualização de 1.2.3 para 1.2.4 não deve quebrar seu código, e uma atualização de 1.2.3 para 2.0.0 pode muito bem. Se o código parar de funcionar após a atualização para a 1.2.4, o autor provavelmente aceitará um relatório de bug e corrigirá o erro na versão 1.2.5. Se o código parou de funcionar (ou até compilou) após a atualização para a 2.0.0, é muito mais provável que essa alteração seja intencional e, portanto, é improvável que algo seja corrigido na 2.0.1.

Não quero concluir da lei de Hiram que o controle de versão semântico é impossível. Em vez disso, acredito que os assemblies devem ser usados ​​com cuidado, usando exatamente as mesmas versões de cada dependência que o autor. Ou seja, a montagem padrão deve ser o mais reproduzível possível.

Conjuntos de venda automática e reproduzíveis


A segunda grande desvantagem do go get é que, sem o conceito de controle de versão, uma equipe não pode fornecer e nem expressar a idéia de uma construção reproduzível. Você não pode ter certeza de que os usuários estão compilando a mesma versão das dependências de código que você. Em novembro de 2013, a seguinte FAQ foi adicionada à FAQ do Go 1.2:

Se você usa um pacote externo e tem medo de que ele mude inesperadamente, a solução mais fácil é copiá-lo para o repositório local (essa abordagem é usada pelo Google). Salve uma cópia com um novo caminho de importação que a identifique como uma cópia local. Por exemplo, você pode copiar original.com/pkg para you.com/external/original.com/pkg . Uma das ferramentas para esse procedimento é o tecido de Kit Rerik.

Keith Rarik iniciou este projeto em março de 2012. O utilitário goven copia a dependência para o repositório local e atualiza todos os caminhos de importação para refletir o novo local. Tais alterações no código-fonte são necessárias, mas desagradáveis. Eles dificultam a comparação e incluem novas cópias e também exigem a atualização de outro código copiado usando essa dependência.

Em setembro de 2013, Keith apresentou o godep , "uma nova ferramenta para corrigir dependências de pacotes". A principal conquista do godep foi o que agora chamamos de venda, ou seja, copiar dependências no projeto sem alterar os arquivos de origem, sem suporte direto para as ferramentas, através de uma certa configuração do GOPATH.

Em outubro de 2014, Keith propôs adicionar suporte a "pacotes externos" às ferramentas Go, para que elas entendessem melhor os projetos usando esta convenção. Naquela época, vários utilitários no estilo godep já haviam aparecido. Matt Farina publicou um post “Viajando pelos gerentes de godep do Sea of ​​Go” comparando godep com godep , especialmente glide .

Em abril de 2015, Dave Cheney introduziu o gb , “uma ferramenta de construção baseada em projeto ... com construções repetidas por meio da venda de origem”, novamente sem reescrever os caminhos de importação (outra motivação para criar o gb era evitar o requisito de armazenar código em diretórios específicos do GOPATH o que nem sempre é conveniente).

Naquela primavera, Jason Buberlie examinou a situação com os sistemas de gerenciamento de pacotes Go, incluindo várias duplicações de esforços e o trabalho inútil em utilitários semelhantes. Sua pesquisa deixou claro para os desenvolvedores que o suporte à venda sem reescrever os caminhos de importação deve ser adicionado ao comando go . Ao mesmo tempo, Daniel Theofanes começou a preparar especificações para um formato de arquivo que descreve a origem exata e a versão do código no diretório do fornecedor. Em junho de 2015, aceitamos a proposta de Keith como um experimento de venda no Go 1.5 , que foi incluído por padrão no Go 1.6. Incentivamos os autores de todas as ferramentas de venda a trabalhar com Daniel a adotar um único formato de arquivo de metadados.

A introdução do conceito de venda no Go permitiu que ferramentas como o vet analisassem os programas com mais competência, e hoje ele foi usado por uma dúzia ou dois gerenciadores de pacotes ou ferramentas de venda. Por outro lado, como todos têm formatos de metadados diferentes, eles não interagem e não podem trocar informações de dependência com facilidade.

Mais fundamentalmente, o vending é uma solução incompleta para o problema do controle de versão. Ele fornece apenas reprodutibilidade da montagem, mas não ajuda a entender as versões do pacote e decidir qual delas usar. Os gerenciadores de pacotes, como glide e dep implicitamente o conceito de controle de versão no Go, configurando o diretório de fornecedores de uma certa maneira. Como resultado, muitas ferramentas no ecossistema Go podem não conseguir obter as informações de versão corretas. É claro que o Go precisa de suporte direto para versões de pacotes.

Experiência oficial de gerenciamento de pacotes


No GopherCon 2016 no Hack Day (agora Community Day), um grupo de ativistas do Go se reuniu para discutir amplamente as questões de gerenciamento de pacotes . Um dos resultados foi a formação de um comitê e um grupo consultivo para realizar uma série de atividades, a fim de criar uma nova ferramenta de gerenciamento de pacotes . A idéia era ter uma ferramenta unificada para substituir as existentes, embora ainda fosse implementada fora do kit de ferramentas direto da Go usando catálogos de fornecedores. O comitê incluía Andrew Gerrand, Ed Muller, Jesse Frazel e Sam Boyer, liderados por Peter Burgon. Eles prepararam um rascunho de especificação e, em seguida, Sam e seus assistentes implementaram o dep . Para entender a situação geral, consulte o artigo de Sam de fevereiro de 2016, “Então você quer escrever um gerente de pacotes”, seu post de dezembro de 2016 “Go Dependency Management Saga”, e seu discurso de julho de 2017 na GopherCon, “Uma nova era de gerenciamento de pacotes no Vá . "

Dep realiza muitas tarefas: essa é uma melhoria importante em relação às práticas atuais. Este é um passo importante para uma solução futura e, ao mesmo tempo, um experimento - chamamos de "experimento oficial" - que nos ajuda a entender melhor as necessidades dos desenvolvedores. Mas dep não dep um protótipo direto da possível integração de comandos go na versão do pacote. Essa é uma maneira poderosa, flexível e quase universal de explorar o espaço das decisões de design. É semelhante aos makefiles que lutamos no começo. Mas assim que entendermos melhor o espaço das decisões de design e conseguirmos reduzi-lo a algumas funções-chave que devem ser suportadas, isso ajudará o ecossistema Go a remover outras funções, reduzir a expressividade e adotar convenções obrigatórias que tornam as bases de código Go mais consistentes e fáceis de entender.

Este artigo é o começo da próxima etapa após o dep : o primeiro protótipo da integração final com o comando go , o lote equivalente a goinstall . Um protótipo é um comando separado que chamamos de vgo : um substituto go com suporte para controle de versão de pacotes. Este é um novo experimento, e veremos o que vem dele. Como no anúncio do goinstall , alguns projetos e códigos agora são compatíveis com o vgo , enquanto outros precisam de alterações. Removeremos algum controle e expressividade, assim como os makefiles foram removidos para simplificar o sistema e eliminar a complexidade dos usuários. Mais importante, procuramos pioneiros que ajudarão a experimentar o vgo para obter o maior número possível de críticas.

Iniciar um experimento com o vgo não significa interromper o suporte ao dep : ele permanecerá disponível até alcançarmos uma integração completa e aberta com o go . Também tentaremos fazer a transição final do dep para a integração, o mais suave possível, independentemente da forma em que essa integração ocorra. Projetos que ainda não foram convertidos em dep ainda podem se beneficiar dessa conversão (observe que godep e glide interromperam o desenvolvimento ativo e estão incentivando a migração para dep). Talvez alguns projetos desejem mudar diretamente para o vgo se isso atender às suas necessidades.

Oferta


A proposta de adicionar controle de versão ao comando go consiste em quatro etapas. Primeiro, aceite a regra de compatibilidade de importação , indicada pelas perguntas frequentes e gopkg.in: as versões mais recentes do pacote com o caminho de importação especificado devem ser compatíveis com versões anteriores. Em segundo lugar, adote um novo algoritmo simples, conhecido como escolher a versão mínima para determinar quais versões do pacote são usadas neste assembly. Terceiro, introduza o conceito do módulo Go: grupos de pacotes com versão como um todo e declare os requisitos mínimos que devem ser satisfeitos por suas dependências. Quarto, determine como integrar tudo isso ao seu comando go existente, para que os fluxos de trabalho básicos não mudem significativamente a partir de hoje. No restante deste artigo, analisamos cada uma dessas etapas. Eles são discutidos em mais detalhes em outros artigos do blog .

Regra de compatibilidade de importação


O principal problema com os sistemas de gerenciamento de pacotes é a tentativa de resolver incompatibilidades. Por exemplo, a maioria dos sistemas permite que o pacote B declare que precisa do pacote D da versão 6 ou posterior e, em seguida, permita que o pacote C declare que requer D versão 2, 3 ou 4, mas não a versão 5 ou posterior. Portanto, se você deseja usar B e C em seu pacote, está sem sorte: não pode selecionar nenhuma versão de D que satisfaça as duas condições e não pode fazer nada.

Em vez de um sistema que inevitavelmente bloqueia a montagem de grandes programas, nossa proposta introduz uma regra de compatibilidade de importação para os autores de pacotes:

Se os pacotes antigo e novo tiverem o mesmo caminho de importação, o novo pacote deverá ser compatível com o pacote antigo.

A regra repete o FAQ mencionado anteriormente. Esse texto terminava com as palavras: "No caso de uma mudança radical, crie um novo pacote com um novo caminho de importação". , . , :

 import "github.com/go-yaml/yaml/v2" 

2.0.0 , . , Go . . v1 v2.

, . , , , . . .


, dep cargo , . , . -, « » - , - . , - , , , . -, , , «, X», X .

, . . , . , , . , , , , , .

. , , , . , , , , . , - . , , .

.

— . «, », «, ». : () . .

Go


Go , . , . Git , Git . , .

go.mod , . , go.mod :

 // My hello, world. module "rsc.io/hello" require ( "golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54 "rsc.io/quote" v1.5.2 ) 

, rsc.io/hello , : golang.org/x/text rsc.io/quote . , go.mod . , - .

, vgo , . rsc.io/quote , github.com/rsc/quote , , 1.5.2. golang.org/x/text . , v0.0.0-yyyymmddhhmmss-commit . v0.0.0 yyyymmddhhmmss-commit . , v0.0.0, . , .

, go.mod , , , . .

Goinstall go get , git hg , , . , bzr Bazaar. , Go HTTP zip-. go get . vgo API .

zip- - . -, . go.mod , , .

go


go . , , go build , go install , go run go test , . golang.org/x/text Go .

— GOPATH . go.mod , , go.mod , , . git clone , cd , . . GOPATH.

?


« Go» , vgo . , vgo . . .

, vgo . . go.mod . , go.mod , dep , glide , glock , godep , godeps , govend , govendor gvt , vgo go.mod .

, Go . , Go, — , go get , GOPATH GOPATH. .

- . , , vgo . , Go 1.11 Go, , Go 1.12 . , go get . , , .

go get . , . , , .

Source: https://habr.com/ru/post/pt442272/


All Articles