Compilando aplicativos em Go para diferentes Sistemas Operacionais e Arquiteturas

No desenvolvimento de software, é importante considerar o sistema operacional e a arquitetura do processador subjacente para os quais você quer compilar seu binário. Como geralmente é lento ou impossível executar um binário em uma plataforma de SO/arquitetura, é uma prática comum compilar o seu binário final para muitas plataformas diferentes a fim de maximizar o público do seu programa. No entanto, isso pode ser difícil quando a plataforma que estiver usando para o desenvolvimento for diferente da plataforma na qual deseja implantar o seu programa. No passado, por exemplo, o desenvolvimento de um programa em Windows e a implantação em uma máquina Linux ou macOS envolveria a configuração de máquinas montadas para cada um dos ambientes para os quais quisesse binários. Você também precisaria manter suas ferramentas em sincronia, além de outras considerações que adicionariam custos e dificultariam a realização de testes e a distribuição.

A linguagem Go resolve esse problema, criando o suporte para várias plataformas diretamente na ferramenta go build, assim como em todo o resto da cadeia de ferramentas do Go. Ao usar variáveis de ambiente e build tags (sinalizadores de compilação), você consegue controlar para qual SO e arquitetura o seu binário final será compilado, além de unir um fluxo de trabalho que pode alternar rapidamente a inclusão do código dependente de plataforma sem alterar sua base de códigos.

Neste tutorial, você irá juntar um aplicativo exemplo que une strings a partir de um filepath, criar e incluir seletivamente snippets dependentes de plataforma e compilar binários para vários sistemas operacionais e arquiteturas de sistema em seu próprio sistema, mostrando a você como usar essa poderosa capacidade da linguagem de programação Go.

Pré-requisitos

Para seguir o exemplo neste artigo, será necessário:

Possíveis plataformas para o GOOS e GOARCH

Antes de mostrar como controlar o processo de compilação para compilar os binários para plataformas diferentes, vamos inspecionar primeiro para quais tipos de plataformas a linguagem Go consegue compilar e de que maneira a Go referencia tais plataformas utilizando as variáveis de ambiente GOOS e GOARCH.

As ferramentas da Go têm um comando que pode imprimir uma lista das possíveis plataformas nais quais a Go consegue compilar. Essa lista pode mudar com cada nova versão da Go. Assim, as combinações discutidas aqui podem não ser as mesmas em outra versão da linguagem Go. No momento em que este tutorial é escrito, a versão da Go é a 1.13.

Para encontrar essa lista com as possíveis plataformas, execute o seguinte:


  • go tool dist list

Você receberá um resultado similar ao seguinte:


Output

aix/ppc64 freebsd/amd64 linux/mipsle openbsd/386
android/386 freebsd/arm linux/ppc64 openbsd/amd64
android/amd64 illumos/amd64 linux/ppc64le openbsd/arm
android/arm js/wasm linux/s390x openbsd/arm64
android/arm64 linux/386 nacl/386 plan9/386
darwin/386 linux/amd64 nacl/amd64p32 plan9/amd64
darwin/amd64 linux/arm nacl/arm plan9/arm
darwin/arm linux/arm64 netbsd/386 solaris/amd64
darwin/arm64 linux/mips netbsd/amd64 windows/386
dragonfly/amd64 linux/mips64 netbsd/arm windows/amd64
freebsd/386 linux/mips64le netbsd/arm64 windows/arm

Esse resultado é um conjunto de pares de chave-valor, por uma /. A primeira parte da combinação, antes da /, é o sistema operacional. Em Go, esses sistemas operacionais são valores possíveis para a variável de ambiente GOOS, pronuncia-se “goose”, que significa Go Operating System (Sistema Operacional Go). A segunda parte, após a /, é a arquitetura . Como antes, todos esses valores são possíveis para uma variável de ambiente: GOARCH. A pronúncia dessa variável é “gore-ch”, e significa Go Architecture (Arquitetura Go).

Vamos detalhar uma dessas combinações para entender o que ela significa e como ela funciona, usando o linux/386 como exemplo. O par chave-valor começa com a variável GOOS, que, neste exemplo, seria o linux, referindo-se ao SO do Linux. Aqui, a GOARCH seria o 386, que significa microprocessador Intel 80386.

Existem muitas plataformas disponíveis com o comando go build, mas na maior parte das vezes você vai acabar usando linux, windows ou darwin como um valor para o GOOS. Dentre elas, temos as três grandes plataformas de OS, quais sejam: Linux, Windows e macOS, o qual é baseado no sistema operacional Darwin e, por isso, é chamado de darwin. No entanto, o Go também pode incluir plataformas menos comuns, como a nacl, que representa o Cliente nativo do Google.

Quando você executa um comando como o go build, o Go usa a plataforma atual do GOOS e da GOARCH para determinar como compilar o binário. Para descobrir a qual combinação sua plataforma corresponde, utilize o comando go env e passe o GOOS e a GOARCH como argumentos:


  • go env GOOS GOARCH

Ao testar esse exemplo, executamos este comando no macOS de uma máquina com uma arquitetura AMD64. Dessa forma, iremos receber o seguinte resultado:


Output

darwin
amd64

Aqui, o resultado do comando nos diz que nosso sistema tem o GOOS=darwin e o GOARCH=amd64.

Agora, você sabe qual deles é o GOOS e qual é o GOARCH no Go, bem como seus possíveis valores. Em seguida, você precisará de um programa para usar como exemplo de como usar essas variáveis de ambiente e os build tags para compilar binários em outras plataformas.

Antes de começar a compilar binários para outras plataformas, vamos criar um programa de exemplo. Uma boa amostra para este propósito é a função Join no pacote path/filepath na biblioteca Go padrão. Essa função pega uma quantidade de strings e retorna uma string que é unida ao separador de caminho de arquivo correto.

Este é um bom programa de exemplo porque a operação do programa depende do SO em que ele estiver executando. No Windows, o separador de caminho é uma barra invertida, \, enquanto os sistemas baseados em Unix usam uma barra, /.

Vamos começar a compilar um aplicativo que utilize o filepath.Join() e, mais tarde, você vai escrever sua própria implementação da função do Join() – que personaliza o código para os binários específicos de plataforma.

Primeiro, crie uma pasta no seu diretório src com o nome do seu app:


Acesse aquele diretório:


Em seguida, crie um novo arquivo no seu editor de texto preferido chamado main.go. Para este tutorial, usaremos o Nano:


  • nano main.go

Assim que o arquivo estiver aberto, adicione o seguinte código:

src/app/main.go

package main import ( "fmt" "path/filepath"
) func main() { s := filepath.Join("a", "b", "c") fmt.Println(s)
}

A função main() neste arquivo usa o filepath.Join() para concatenar três strings com o separador de caminho correto, dependente de plataforma.

Salve e saia do arquivo e, em seguida, execute o programa:


  • go run main.go

Ao executar esse programa, você receberá um resultado diferente, dependendo da plataforma que estiver usando. No Windows, você verá as strings separadas pela \:


Output

a\b\c

Nos sistemas da Unix, como macOS e Linux, você receberá o seguinte:


Output

a/b/c

Isso mostra que, devido aos diferentes protocolos de sistema de arquivos usados nesses sistemas operacionais, o programa terá que compilar um código diferente para as diferentes plataformas. Porém, uma vez que o programa já utiliza um separador de arquivos diferente de acordo com o SO, sabemos que o filepath.Join() já responde pela diferença na plataforma. Isso acontece porque a cadeia de ferramentas do Go detecta automaticamente o GOOS e a GOARCH da sua máquina e recorre dessa informação para usar o snippet do código com os build tags e o separador de arquivo corretos.

Vamos considerar de onde a função filepath.Join() obtém o seu separador. Execute o comando a seguir para inspecionar o snippet relevante da biblioteca padrão do Go:


  • less /usr/local/go/src/os/path_unix.go

Isso exibirá o conteúdo do path_unix.go. Procure pela seguinte parte do arquivo:

/usr/local/go/os/path_unix.go

. . .
// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris package os const ( PathSeparator = '/' // OS-specific path separator PathListSeparator = ':' // OS-specific path list separator
)
. . .

Esta seção define o PathSeparator de todas as variedades de sistemas do tipo Unix compatíveis com o Go. Observe todos os build tags na parte superior – cada qual representando uma das possíveis plataformas GOOS associadas ao Unix. Quando o GOOS corresponder a esses termos, seu programa produzirá o separador de caminho de arquivo com estilo Unix.

Pressione q para retornar à linha de comando.

Em seguida, abra o arquivo que define o comportamento do filepath.Join() quando usado em Windows:


  • less /usr/local/go/src/os/path_windows.go

Você verá o seguinte:

/usr/local/go/os/path_unix.go

. . .
package os const ( PathSeparator = '\\' // OS-specific path separator PathListSeparator = ';' // OS-specific path list separator
)
. . .

Embora o valor do PathSeparator seja \\ aqui, o código irá renderizar somente uma única barra invertida (\) – necessária para os caminhos de arquivos do Windows, uma vez que a primeira barra invertida só é necessária como um caractere de escape.

Note que, ao contrário do arquivo Unix, não há build tags na parte superior. Isso acontece porque o GOOS e a GOARCH também podem ser transmitidos para o go build, adicionando-se um sublinhado (_) e o valor da variável de ambiente como um sufixo do nome do arquivo. Nós vamos nos aprofundar sobre esse ponto na seção Usando sufixos de nome de arquivo no GOOS e na GOARCH. Aqui, a parte _windows do path_windows.go faz o arquivo agir como se ele tivesse o build tag // + build windows no parte superior do arquivo. Por causa disso, quando seu programa for executado em Windows, ele usará as constantes do PathSeparator e do PathListSeparator do snippet de código path_windows.go.

Para retornar à linha de comando, saia do less pressionando q.

Neste passo, você compilou um programa que mostrou como a linguagem Go converte o GOOS e a GOARCH automaticamente em build tags. Com isso em mente, você já pode atualizar o seu programa e escrever sua própria implementação do filepath.Join(), usando build tags para definir manualmente o PathSeparator correto para as plataformas Windows e Unix.

Implementando uma função específica da Plataforma

Agora que sabe como a biblioteca padrão do Go implementa o código específico da plataforma, você pode usar os build tags para fazer isso em seu próprio programa app. Para fazer isso, você irá escrever sua própria implementação do filepath.Join().

Abra seu arquivo main.go:


  • nano main.go

Substitua o conteúdo do main.go pelo seguinte, usando sua própria função chamada Join():

src/app/main.go

package main import ( "fmt" "strings"
) func Join(parts ...string) string { return strings.Join(parts, PathSeparator)
} func main() { s := Join("a", "b", "c") fmt.Println(s)
}

A função Join pega um número de parts e as une usando o método strings.Join() do pacote strings para concatenar as parts usando o PathSeparator.

Você ainda não definiu o PathSeparator. Assim, faça isso agora, em outro arquivo. Salve e saia do main.go, abra seu editor favorito e crie um novo arquivo chamado path.go:

nano path.go

Defina o PathSeparator e o configure como o separador de caminho de arquivo do Unix, /:

src/app/path.go

package main const PathSeparator = "/"

Compile e execute o aplicativo:


  • go build
  • ./app

Você receberá o seguinte resultado:


Output

a/b/c

Isso executa com sucesso a fim de obter o caminho de arquivo com estilo Unix. Mas isso ainda não é o que queremos: o resultado é sempre a/b/c, a despeito da plataforma na qual ele execute. Para adicionar na funcionalidade para criar os caminhos de arquivo com estilo Windows, você precisará adicionar uma versão do Windows do PathSeparator e dizer ao comando go build qual versão usar. Na próxima seção, você usará os build tags para realizar isso.

Para considerar as plataformas Windows, agora você irá criar um arquivo alternativo para o path.go e usará build tags para garantir que os snippets de código somente executem quando o GOOS e a GOARCH forem a plataforma apropriada.

Primeiro, porém, adicione o build tag ao path.go para dizer a ele que compile para tudo, exceto para o Windows. Abra o arquivo:


  • nano path.go

Adicione o build tag - destacado a seguir, ao arquivo:

src/app/path.go

// +build !windows package main const PathSeparator = "/"

Os build tags em Go permitem a inversão, ou seja, você pode instruir o Go a compilar esse arquivo para qualquer plataforma, exceto para o Windows. Para inverter um build tag, coloque um ! antes do tag.

Salve e saia do arquivo.

Agora, se você executasse esse programa no Windows, você receberia o seguinte erro:


Output

./main.go:9:29: undefined: PathSeparator

Neste caso, o Go não conseguiria incluir o path.go para definir o PathSeparator da variável.

Agora que você garantiu que o path.go não irá executar quando o GOOS for o Windows, adicione um novo arquivo, o windows.go:


  • nano windows.go

No windows.go, defina o PathSeparator do Windows, bem como um build tag deixar que o comando go build saiba que se trata de uma implementação do Windows:

src/app/windows.go

// +build windows package main const PathSeparator = "\\"

Salve o arquivo e saia do editor de texto. O aplicativo agora pode compilar uma caminho para o Windows e outro para todas as outras plataformas.

Ao mesmo tempo que os binários agora irão compilar corretamente para suas plataformas, existem mais alterações que você deverá fazer para compilar para uma plataforma à qual você não tenha acesso. Para fazer isso, no próximo passo você irá alterar duas suas variáveis de ambiente GOOS e GOARCH.

Usando suas variáveis de ambiente locais GOOS e GOARCH

Anteriormente, você executou o comando go env GOOS GOARCH para descobrir em qual SO e qual arquitetura estava trabalhando. Quando você executou o comando go env, ele procurou as duas variáveis de ambiente, GOOS e GOARCH; se ele as encontrasse, seus valores seriam usados, caso contrário, o Go as definiria com as informações da plataforma atual. Isso significa que você pode alterar o GOOS e a GOARCH para que eles não assumam sua SO e arquitetura locais como padrão.

O comando go build se comporta de maneira similar ao comando go env. Você pode definir as variáveis de ambiente GOOS ou GOARCH para compilar uma plataforma diferente usando o go build.

Se não estiver usando um sistema Windows, compile um binário windows do app, definindo a variável de ambiente GOOS para windows ao executar o comando go build:


  • GOOS=windows go build

Agora, liste os arquivos no seu diretório atual:


O resultado da ação de listar o diretório mostra que agora existe um arquivo executável do Windows, app.exe no diretório do projeto:


Output

app app.exe main.go path.go windows.go

Usando o comando file, você pode obter mais informações sobre esse arquivo, confirmando sua compilação:


  • file app.exe

Você receberá:


Output

app.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows

Também é possível definir uma, ou ambas as variáveis de ambiente no momento da compilação. Execute o seguinte:


  • GOOS=linux GOARCH=ppc64 go build

Seu executável app será substituído por um arquivo feito de uma arquitetura diferente. Execute o comando file neste binário:


Você receberá um resultado como o seguinte:

app: ELF 64-bit MSB executable, 64-bit PowerPC or cisco 7500, version 1 (SYSV), statically linked, not stripped

Definindo suas variáveis de ambiente local GOOS e GOARCH, agora você poderá compilar binários para qualquer uma das plataformas compatíveis do Go, sem precisar de uma configuração ou preparação complicada. Em seguida, você usará as convenções de nome do arquivo para manter seus arquivos totalmente organizados e compilar em plataformas específicas – automaticamente, sem build tags.

Usando os sufixos de nome do arquivo para GOOS e GOARCH

Como você viu anteriormente, a biblioteca Go padrão faz intenso uso dos build tags para simplificar o código, separando as implementações de plataforma diferentes em arquivos diferentes. Quando você abriu o arquivo os/path_unix.go havia um build tag que listava todas as combinações possíveis que são consideradas plataformas do tipo Unix. No entanto, o arquivo os/path_windows.go não continha build tags, uma vez que o sufixo no nome do arquivo era o suficiente para dizer ao Go a qual plataforma o arquivo se destinava.

Vejamos a sintaxe desse recurso. Ao nomear um arquivo .go, você pode adicionar o GOOS e a GOARCH como sufixos do nome do arquivo naquela ordem, separando os valores por sublinhados (_). Se você tivesse um arquivo Go chamado filename.go, poderia especificar o SO e a arquitetura, mudando o nome do arquivo para filename_GOOS_GOARCH.go. Por exemplo, se quisesse compilá-lo para o Windows com a arquitetura ARM 64-bit, você tornaria o nome do arquivo filename_windows_arm64.go​​​​​. Essa convenção de nomenclatura ajuda a manter o código totalmente organizado.

Atualize seu programa para usar os sufixos do nome do arquivo, em vez dos build tags. Primeiro, renomeie os arquivos path.go e windows.go para usar a convenção usada no pacote os:


  • mv path.go path_unix.go
  • mv windows.go path_windows.go

Com os dois nomes de arquivo alterados, você pode remover o build tag que você havia adicionado ao path_windows.go:


  • nano path_windows.go

Remova // +build windows para que seu arquivo se pareça com este:

path_windows.go

package main const PathSeparator = "\\"

Salve e saia do arquivo.

Como o unix não é um GOOS válido, o sufixo _unix.go não tem significado para o compilador do Go. No entanto, ele transmite o objetivo pretendido do arquivo. Como o arquivo os/path_unix.go, seu arquivo path_unix.go ainda precisa usar build tags. Assim, mantenha esse arquivo inalterado.

Ao usar as convenções de nome de arquivo, você recomeu os build tags desnecessários do seu código fonte, tornando o sistema de arquivos mais limpo e claro.

Conclusão

A capacidade de gerar binários para várias plataformas que não exijam dependências é uma característica poderosa da cadeia de ferramentas do Go. Neste tutorial, você usou essa capacidade, adicionando build tags e sufixos de nome de arquivo para marcar certos snippets de códigos para compilar somente para certas arquiteturas. Você criou seu próprio programa dependente de plataforma e manipulou as variáveis de ambiente GOOS e GOARCH para gerar binários para plataformas além da sua plataforma atual. Esta é uma habilidade valiosa, uma vez que se trata de uma prática comum para ter um processo de integração contínuo que executa automaticamente por essas variáveis de ambiente para compilar binários para todas as plataformas.

Para obter outros estudos sobre o go build, consulte nosso tutorial Personalizando os binários em Go com os build tags. Se quiser aprender mais sobre a linguagem de programação Go, de um modo geral, confira a série completa sobre Como Codificar em Go.

source : https://www.digitalocean.com/community/tutorials

Write a Comment

view all comments