Personalizando binários do Go com Tags de Construção

Introdução

Em Go, uma build tag ou uma restrição de construção, é um identificador adicionado a um pedaço de código que determina quando o arquivo deve ser incluído em um pacote durante o processo de build. Isso permite que você construa diferentes versões da sua aplicação Go a partir do mesmo código-fonte e alternar entre elas de maneira rápida e organizada. Muitos desenvolvedores usam build tags para melhorar o fluxo de trabalho da construção de aplicativos compatíveis com várias plataformas, como programas que requerem mudanças de código para lidar com variações entre diferentes sistemas operacionais. Build tags também são usados para testes de integração, permitindo que você alternadamente快速 entre o código integrado e o código com um serviço mock ou stub, e para diferentes níveis de conjuntos de recursos dentro de uma aplicação.

Vamos tomar o problema de conjuntos de recursos de clientes diferentes como exemplo. Ao escrever algumas aplicações, você pode querer controlar quais recursos incluir no binário, como uma aplicação que oferece níveis Grátis, Pro e Enterprise. Conforme o cliente aumenta seu nível de assinatura nessas aplicações, mais recursos são desbloqueados e disponibilizados. Para resolver esse problema, você poderia manter projetos separados e tentar mantê-los sincronizados uns com os outros através do uso de declarações import. Embora essa abordagem funcione, ao longo do tempo, ela se tornará tediosa e propensa a erros. Uma abordagem alternativa seria usar tags de compilação.

Neste artigo, você usará tags de compilação no Go para gerar diferentes binários executáveis que oferecem conjuntos de recursos Grátis, Pro e Enterprise de uma aplicação de exemplo. Cada um terá um conjunto diferente de recursos disponíveis, com a versão Grátis sendo a padrão.

Pré-requisitos

Para seguir o exemplo neste artigo, você precisará de:

Construindo a Versão Grátis

Vamos começar construindo a versão Grátis do aplicativo, pois ela será a padrão ao executar go build sem qualquer tag de build. Posteriormente, utilizaremos tags de build para adicionar selectivamente outras partes ao nosso programa.

No diretório src, crie uma pasta com o nome do seu aplicativo. Este tutorial usará app:

  1. mkdir app

Entre nessa pasta:

  1. cd app

Em seguida, faça um novo arquivo de texto no seu editor de texto de choice chamado main.go:

  1. nano main.go

Agora, definiremos a versão Grátis do aplicativo. Adicione o seguinte conteúdo ao main.go:

main.go
package main

import "fmt"

var features = []string{
  "Free Feature #1",
  "Free Feature #2",
}

func main() {
  for _, f := range features {
    fmt.Println(">", f)
  }
}

Neste arquivo, criamos um programa que declara uma slice chamada features, que contém duas strings que representam os recursos do nosso aplicativo Grátis. A função main() no aplicativo usa um for loop para range através da slice features e imprimir todos os recursos disponíveis na tela.

Salve e saia do arquivo. Agora que este arquivo está salvo, não precisaremos mais editá-lo pelo restante do artigo. Em vez disso, usaremos tags de construção para alterar os recursos dos binários que construiremos a partir dele.

Construa e execute o programa:

  1. go build
  2. ./app

Você receberá a seguinte saída:

Output
> Free Feature #1 > Free Feature #2

O programa imprimiu nossas duas funcionalidades gratuitas, completando a versão Grátis do nosso aplicativo.

Até agora, você criou um aplicativo que tem um conjunto de recursos muito básico. Próximo, você criará uma maneira de adicionar mais recursos ao aplicativo no momento da construção.

Adicionando as Funcionalidades Pro Com go build

Até agora, evitamos fazer alterações no main.go, simulando um ambiente de produção comum no qual é necessário adicionar código sem alterar e possivelmente quebrar o código principal. Como não podemos editar o arquivo main.go, precisaremos usar outro mecanismo para injetar mais recursos na fatia features usando tags de construção.

Vamos criar um novo arquivo chamado pro.go que usará uma função init() para anexar mais recursos à fatia features:

  1. nano pro.go

Depois que o editor abrir o arquivo, adicione as seguintes linhas:

pro.go
package main

func init() {
  features = append(features,
    "Pro Feature #1",
    "Pro Feature #2",
  )
}

Na código, utilizamos o init() para executar código antes da função main() do nosso aplicativo e depois usamos o append() para adicionar as funcionalidades Pro ao slice features. Salve e saia do arquivo.

Compile e execute o aplicativo usando go build:

  1. go build

Como há agora duas fontes no diretório atual (pro.go e main.go), o go build irá criar um binário de ambas elas. Executa este binário:

  1. ./app

Isso lhe dara o seguinte conjunto de funcionalidades:

Output
> Free Feature #1 > Free Feature #2 > Pro Feature #1 > Pro Feature #2

A aplicação agora inclui as funcionalidades Pro e as Free. No entanto, isso não é desejável: pois a versão Free agora inclui as funcionalidades que são supostamente disponíveis somente na versão Pro. Para corrigir isso, você pode incluir mais código para gerenciar os diferentes níveis do aplicativo, ou você pode usar marcas de construção no próximo passo. Vamos adicionar marcas de construção na próxima etapa.

Adicionando Marcas de Construção

Veja como uma marca de construção parece:

// +build tag_name

Na próxima etapa, vamos adicionar marcas de construção à nossa aplicação para distinguir a versão Pro da versão Free. Primeiro, abra o arquivo pro.go e adicione uma marca de construção ao início deste arquivo, como mostrado no exemplo abaixo:

```go
// +build pro
```

Este comando indica que o arquivo pro.go deve ser compilado quando a marca de construção "pro" estiver presente no ambiente de compilação. Ao compilar o seu aplicativo com a marca de construção adequada, você poderá garantir que as funcionalidades corretas são incluídas em cada versão do aplicativo.

Ao colocar esta linha de código como primeira linha do seu pacote e substituir tag_name pelo nome da sua tag de construção, você marcará este pacote como código que pode ser incluído seletivamente no binário final. Vamos ver isso em ação adicionando uma tag de construção ao arquivo pro.go para dizer ao comando go build para ignorá-lo a menos que a tag seja especificada. Abra o arquivo no seu editor de texto:

  1. nano pro.go

Então, adicione a seguinte linha destacada:

pro.go
// +build pro

package main

func init() {
  features = append(features,
    "Pro Feature #1",
    "Pro Feature #2",
  )
}

No topo do arquivo pro.go, adicionamos // +build pro seguido de uma nova linha em branco. Esta nova linha em branco é obrigatória, caso contrário, o Go interpreta isso como um comentário. As declarações de tags de build devem também estar no topo de um arquivo .go. Nada, nem mesmo comentários, pode estar acima das tags de build.

A declaração +build diz ao comando go build que isso não é um comentário, mas sim uma tag de build. A segunda parte é a tag pro. Adicionando esta tag no topo do arquivo pro.go, o comando go build agora incluirá o arquivo pro.go somente quando a tag pro estiver presente.

Compile e execute a aplicação novamente:

  1. go build
  2. ./app

Você receberá a seguinte saída:

Output
> Free Feature #1 > Free Feature #2

Como o arquivo pro.go exige que a tag pro esteja presente, o arquivo é ignorado e a aplicação é compilada sem ele.

Ao executar o comando go build, nós podemos usar a flag -tags para incluir condicionalmente código no fonte compilado adicionando a tag própria como um argumento. Vamos fazer isso para a tag pro:

  1. go build -tags pro

Isso resultará no seguinte:

Output
> Free Feature #1 > Free Feature #2 > Pro Feature #1 > Pro Feature #2

Agora, obtemos apenas os recursos adicionais quando construímos a aplicação usando a tag de construção pro.

Isso está bem se houver apenas duas versões, mas as coisas ficam complicadas quando você adiciona mais tags. Para adicionar a versão Enterprise do nosso aplicativo na próxima etapa, usaremos várias tags de construção juntas com lógica booleana.

Lógica Booleana de Tag de Construção

Quando há várias tags de construção em um pacote Go, as tags interagem entre si usando lógica booleana. Para demonstrar isso, adicionaremos o nível Enterprise do nosso aplicativo usando tanto a tag pro quanto a tag enterprise.

Para construir um binário Enterprise, precisaremos incluir tanto os recursos padrão quanto os recursos do nível Pro e um novo conjunto de recursos para Enterprise. Primeiro, abra um editor e crie um novo arquivo, enterprise.go, que adicionará os novos recursos do Enterprise:

  1. nano enterprise.go

O conteúdo de enterprise.go será quase idêntico ao de pro.go, mas conterá novos recursos. Adicione as seguintes linhas ao arquivo:

enterprise.go
package main

func init() {
  features = append(features,
    "Enterprise Feature #1",
    "Enterprise Feature #2",
  )
}

Salve e saia do arquivo.

Atualmente, o arquivo enterprise.go não possui nenhuma tag de construção, e conforme você aprendera quando adicionou pro.go, isto significa que essas funcionalidades serão adicionadas à versão gratuita quando executar go build. Para pro.go, você adicionou // +build pro e um novo caractere de linha no topo do arquivo para informar go build que deve ser incluído apenas quando -tags pro é usado. Nessa situação, você só precisou de uma tag de construção para alcançar o objetivo. No entanto, quando adicionando as novas funcionalidades Enterprise, você precisará primeiro ter as funcionalidades Pro.

Vamos adicionar suporte para a tag de construção pro ao enterprise.go primeiro. Abra o arquivo com seu editor de texto:

  1. nano enterprise.go

Em seguida, adicione a tag de construção antes da declaração de package main e certifique-se de incluir um novo caractere de linha após a tag de construção:

enterprise.go
// +build pro

package main

func init() {
  features = append(features,
    "Enterprise Feature #1",
    "Enterprise Feature #2",
  )
}

Salve e saia do arquivo.

Compile e execute a aplicação sem nenhuma tag:

  1. go build
  2. ./app

Você receberá a seguinte saída:

Output
> Free Feature #1 > Free Feature #2

As funcionalidades Enterprise já não aparecem na versão gratuita. Agora vamos adicionar a tag de construção pro e compilar e executar a aplicação novamente:

  1. go build -tags pro
  2. ./app

Você receberá a seguinte saída:

Output
> Free Feature #1 > Free Feature #2 > Enterprise Feature #1 > Enterprise Feature #2 > Pro Feature #1 > Pro Feature #2

Isto ainda não é exatamente o que precisamos: as funcionalidades Enterprise agora aparecem quando tentamos construir a versão Pro. Para resolver isso, precisamos usar outra tag de construção. Contudo, para agora, precisamos certificar que as funcionalidades pro e enterprise estejam ambas disponíveis.

O sistema de construção Go resolve esse caso permitindo o uso de algumas lógicas booleanas básicas no sistema de tags de construção.

Vamos abrir o enterprise.go novamente:

  1. nano enterprise.go

Adicione outra tag de build, enterprise, na mesma linha que a tag pro

enterprise.go
// +build pro enterprise

package main

func init() {
  features = append(features,
    "Enterprise Feature #1",
    "Enterprise Feature #2",
  )
}

Salve e feche o arquivo.

Agora vamos compilar e executar a aplicação com a nova tag de build enterprise.

  1. go build -tags enterprise
  2. ./app

Isso resultará no seguinte:

Output
> Free Feature #1 > Free Feature #2 > Enterprise Feature #1 > Enterprise Feature #2

Agora perdemos os recursos Pro. Isso ocorre porque, quando colocamos várias tags de build na mesma linha em um arquivo .go, o go build as interpreta usando lógica OU. Com a adição da linha // +build pro enterprise, o arquivo enterprise.go será construído se ou a tag de build pro ou a tag de build enterprise estiver presente. Precisamos configurar as tags de build corretamente para exigir ambas e usar lógica E em vez disso.

Em vez de colocar ambas as tags na mesma linha, se colocarmos她们 em linhas separadas, o go build interpretará essas tags usando lógica E.

Abrir o enterprise.go novamente e vamos separar as tags de build em várias linhas.

enterprise.go
// +build pro
// +build enterprise

package main

func init() {
  features = append(features,
    "Enterprise Feature #1",
    "Enterprise Feature #2",
  )
}

Agora compile e execute a aplicação com a nova tag de build enterprise.

  1. go build -tags enterprise
  2. ./app

Você receberá a seguinte saída:

Output
> Free Feature #1 > Free Feature #2

Ainda não está helt allí: Porque uma instrução E requer que ambos os elementos sejam considerados verdadeiros, precisamos usar ambas as tags de build pro e enterprise.

Vamos tentar de novo:

  1. go build -tags "enterprise pro"
  2. ./app

Você receberá o seguinte output:

Output
> Free Feature #1 > Free Feature #2 > Enterprise Feature #1 > Enterprise Feature #2 > Pro Feature #1 > Pro Feature #2

Agora nossa aplicação pode ser construída de várias formas a partir do mesmo conjunto de fontes, desbloqueando as funcionalidades da aplicação de acordo.

Neste exemplo, usamos um novo marcador de construção // +build para indicar lógica AND, mas existem outras maneiras alternativas de representar lógica booleana com marcadores de construção. A tabela a seguir apresenta alguns exemplos de outras formatações sintáticas para marcadores de construção, juntamente com seus equivalentes booleanos:

Build Tag Syntax Build Tag Sample Boolean Statement
Space-separated elements // +build pro enterprise pro OR enterprise
Comma-separated elements // +build pro,enterprise pro AND enterprise
Exclamation point elements // +build !pro NOT pro

Conclusão

Neste tutorial, você usou marcadores de construção para permitir que você controle quais partes de seu código sejam compiladas no binário. Primeiro, você declarou marcadores de construção e usou-os com go build, então combinou vários marcadores com lógica booleana. Depois, você construiu um programa que representou os diferentes conjuntos de funcionalidades de versões Free, Pro e Enterprise, mostrando o nível de controle poderoso que os marcadores de construção podem fornecer para o seu projeto.

Se você quiser saber mais sobre os marcadores de construção, consulte a documentação do Golang sobre o assunto ou continue explorando nossa série Como Programar em Go.

Source:
https://www.digitalocean.com/community/tutorials/customizing-go-binaries-with-build-tags