Tutorial

Crear aplicaciones de Go para diferentes sistemas operativos y arquitecturas

GoDevelopment

En el ámbito del desarrollo de software, es importante considerar el sistema operativo y la arquitectura subyacente del procesador para los que se busca compilar un binario. Debido a que a menudo la ejecución de un binario en una plataforma de SO o arquitectura diferente resulta lenta o imposible, es una práctica común la de crear un binario final para muchas plataformas diferentes, a fin de maximizar el público de su programa. Sin embargo, esto puede ser difícil cuando la plataforma que usando para el desarrollo es diferente de la plataforma en la que desea implementar su programa. En el pasado, por ejemplo, desarrollar un programa en Windows e implementarlo en un equipo con Linux o macOS implicaba configurar las máquinas para cada uno de los entornos a los que se buscaba destinar los binarios. También se debían mantener las herramientas sincronizadas y existían otras consideraciones que sumaban costos y dificultaban la prueba y distribución colaborativas.

Go resuelve este problema creando compatibilidad para varias plataformas directamente en la herramienta go build y en el resto de las herramientas de Go. Al usar variables de entorno y etiquetas de compilación, puede controlar el SO y la arquitectura para los cuales se creó su binario final y crear un flujo de trabajo que puede alternar rápidamente la inclusión de código dependiente de la plataforma sin cambiar su base de código.

A través de este tutorial, creará una aplicación de muestra que un a cadenas en una ruta de archivo, creará e incluirá selectivamente fragmentos dependientes de la plataforma y formará binarios para diferentes sistemas operativos y arquitecturas de sistema en su propio sistema, lo que le mostrará la forma de usar esta potente capacidad en el lenguaje de programación Go.

Requisitos previos

Para seguir el ejemplo de este artículo, necesitará lo siguiente:

Posibles plataformas para GOOS y GOARCH

Antes de mostrarle la manera de controlar el proceso de creación para crear binarios en diferentes plataformas, inspeccionaremos los tipos de plataformas para los cuales Go puede realizar compilaciones y la forma en que Go hace referencia a estas plataformas usando las variables de entorno GOOS y GOARCH.

Las herramientas de Go tienen un comando que puede imprimir una lista de las posibles plataformas en las que Go puede realizar compilaciones. La lista puede cambiar con cada nueva versión de Go, de modo que las combinaciones explicadas aquí posiblemente no sean las mismas en otra versión de Go. En el momento en que se redactó este tutorial, la versión actual de Go era la 1.13.

Para buscar esta lista de posibles plataformas, ejecute lo siguiente:

  • go tool dist list

Verá un resultado similar al siguiente:

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

Este resultado es un conjunto de pares clave-valor separados por una /. La primera parte de la combinación, antes de la /, corresponde al sistema operativo. En Go, estos sistemas operativos son posibles valores para la variable de entorno GOOS, que se pronuncia como “goose” y significa Go Operating System (Sistema operativo GO). La segunda parte, después de la /, corresponde a la arquitectura. Como antes, estos son todos los valores posibles para una variable de entorno: GOARCH. Esto se pronuncia como “gore-ch” y significa Go Architecture (Arquitectura de Go).

Vamos a desglosar una de estas combinaciones para comprender su significado y funcionamiento usando linux/386 como ejemplo. El par clave-valor comienza con el GOOS, que en este ejemplo sería linux haciendo referencia al SO Linux. GOARCH aquí sería 386, que representa el microprocesador Intel 80386.

Existen muchas plataformas disponibles con el comando go build, pero la mayoría del tiempo usará linux, windows o darwin como valor para GOOS. Estos abarcan las tres principales plataformas de SO: Linux, Windows y macOS, que se basa en el sistema operativo Darwin y por tanto se denomina darwin. Sin embargo, Go puede abarcar plataformas menos predominantes, como nacl, que representa a Google Native Client.

Cuando se ejecuta un comando como go build, Go utiliza el GOOS y GOARCH de la plataforma actual para determinar la forma de crear el binario. Para conocer la combinación a la que responde su plataforma, puede usar el comando go env y pasar GOOS y GOARCH como argumentos:

  • go env GOOS GOARCH

Al probar este ejemplo, ejecutamos este comando en macOS en una máquina con una arquitectura AMD64, por lo que obtendremos el siguiente resultado:

Output
darwin amd64

Aquí, el resultado del comando nos indica que nuestro sistema tiene GOOS=darwin y GOARCH=amd64.

Ahora sabe lo que GOOS y GOARCH representan en Go, además de sus posibles valores. A continuación, preparará un programa para usarlo como ejemplo de cómo emplear estas variables de entorno y etiquetas de compilación a fin de crear binarios para otras plataformas.

Escribie un programa que dependa de la plataforma con filepath.Join()

Antes de comenzar a crear binarios para otras plataformas, vamos a crear un programa de ejemplo. Un buen ejemplo para este fin es la función Join en el paquete path/filepath de la biblioteca estándar de Go. Esta función toma varias cadenas y muestra una que se une con el separador de ruta de archivo correcto.

Este es un buen ejemplo de programa porque el funcionamiento depende del sistema operativo en el que se ejecute. En Windows, el separador de ruta es una barra diagonal inversa, \, mientras que en los sistemas basados en Unix se utiliza una barra diagonal /.

Comenzaremos creando una aplicación que utilice file path.Join() y, después, escribirá su propia implementación de la función Join() que personaliza el código para los binarios específicos de la plataforma.

Primero, cree una carpeta en su directorio src con el nombre de su aplicación:

  • mkdir app

Posiciónese en ese directorio:

  • cd app

A continuación, cree un nuevo archivo llamado main.go en su editor de texto. Para este tutorial, usaremos Nano:

  • nano main.go

Una vez abierto el archivo, añada el siguiente código:

src/app/main.go
package main

import (
  "fmt"
  "path/filepath"
)

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

La función main() de este archivo utiliza filepath.Join() para concatenar tres cadenas juntas con el separador de ruta dependiente de plataforma correcto.

Guarde el archivo y ciérrelo. Luego, ejecute el programa:

  • go run main.go

Cuando ejecute este programa, recibirá diferentes resultados dependiendo de la plataforma que utilice. En Windows, verá las cadenas separadas por ​​\​:

Output
a\b\c

En sistemas Unix como macOS y Linux, verá lo siguiente:

Output
a/b/c

Esto muestra que, debido a los diferentes protocolos de sistemas de archivos empleados en estos sistemas operativos, el programa tendrá que crear un código diferente para las diferentes plataformas. Sin embargo, ya que utiliza un separador de archivos diferente dependiendo del SO, sabemos que filepath.Join() ya tiene en cuenta la diferencia en la plataforma. Esto es porque la cadena de herramientas de Go detecta el GOOS y GOARCH de su máquina y utiliza esta información para usar un fragmento de código con las etiquetas de compilación y el separador de archivos correctos.

Consideraremos la ubicación de la que obtiene su separador la función filepath.Join(). Ejecute el siguiente comando para inspeccionar el fragmento pertinente de la biblioteca estándar de Go.

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

Con esto, se mostrará el contenido de path_unix.go. Busque la siguiente parte del archivo:

/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 sección define el PathSeparator para todas las variedades de sistemas similares a Unix compatibles con Go. Observe todas las etiquetas de compilación de la parte superior, de las cuales cada una es una posible plataforma GOOS asociada con Unix. Cuando GOOS coincida con estos términos, su programa mostrará el separador de ruta de archivos con el estilo Unix.

Pulse q para volver a la línea de comandos.

A continuación, abra el archivo que define el comportamiento de filepath.Join() cuando se utiliza en Windows:

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

Verá lo siguiente:

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

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

Aunque el valor de PathSeparator es \\ aquí, el código representará la barra diagonal inversa única (\) necesaria para las rutas de archivos de Windows, ya que la primera barra diagonal inversa solo es necesaria como un carácter de escape.

Observe que, a diferencia de lo que sucede con el archivo de Unix, no hay etiquetas de compilación en la parte superior. Esto es porque GOOS y GOARCH también pueden pasarse a go build añadiendo un guión bajo (_) y el valor de la variable de entorno como sufijo al nombre de archivo, algo que veremos en mayor profundidad en la sección Usar los sufijos de nombre de archivo de GOOS y GOARCH. Aquí, la parte _windows de path_windows.go hace que el archivo actúe como si tuviese una etiqueta de compilación //+build windows en la parte superior del archivo. Debido a esto, cuando nuestro programa se ejeute en Windows, usará las constantes de PathSeparator y PathListSeparator del fragmento de código path_windows.go.

Para volver a la línea de comandos, cierre less pulsando q.

En este paso, creó un programa que mostró la forma en que Go convierte GOOS y GOARCH automáticamente en etiquetas de compilación. Teniendo esto en cuenta, ahora puede actualizar su programa y escribir su propia implementación de filepath.Join() usando las etiquetas de compilación a fin de establecer el PathSeparator correcto para las plataformas Windows y Unix.

Implementar una función específica de plataforma

Ahora que conoce la forma en que la biblioteca estándar de Go implementa código específico de una plataforma, puede usar etiquetas de compilación para hacer esto en su propio programa app. Para hacer esto, escribirá su propia implementación de filepath.Join().

Abra su archivo main.go:

  • nano main.go

Reemplace el contenido de main.go por lo siguiente usando su propia función llamada 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)
}

La función Join toma varias parts y las une usando el método strings.Join() del paquete strings para concatenar las parts usando PathSeparator.

Aún no definido PathSeparator. Hágalo en otro archivo. Guarde main.go y ciérrelo, abra su editor favorito y cree un nuevo archivo llamado path.go:

nano path.go

Defina PathSeparator y configúrelo de modo que sea igual al separador de ruta de archivo de Unix, /:

src/app/path.go
package main

const PathSeparator = "/"

Compile y ejecute la aplicación:

  • go build
  • ./app

Obtendrá el siguiente resultado:

Output
a/b/c

Esto se ejecutará correctamente para obtener una ruta de archivo de estilo Unix. Sin embargo, esto no es aún lo que queremos: el resultado es siempre a/b/c, independientemente de la plataforma en la que funcione. Para añadir la funcionalidad de creación de rutas de archivo de estilo Windows, deberá añadir una versión de Windows de PathSeparator e indicar al comando go build la versión que se usará. En la siguiente sección, usará las etiquetas de compilación para conseguir esto:

Usar etiquetas de compilación de GOOS o GOARCH

Para tener en cuenta plataformas basadas en Windows, creará un archivo alternativo a path.go y usará las etiquetas de compilación para garantizar que los fragmentos de código solo se ejecuten cuando GOOS y GOARCH son la plataforma correspondiente.

No obstante, primero añada una etiqueta de compilación a path.go para indicarle que realice operaciones de compilación para todo, a excepción Windows. Abra el archivo:

  • nano path.go

Añada la siguiente etiqueta de compilación resaltada al archivo:

src/app/path.go
// +build !windows

package main

const PathSeparator = "/"

Las etiquetas de compilación de Go admiten inversión, lo cual significa que puede indicar a Go que compile este archivo para cualquier plataforma, a excepción de Windows. Para invertir una etiqueta de compilación, disponga un ! antes de la etiqueta.

Guarde el archivo y ciérrelo.

Ahora, si ejecutara este programa en Windows, vería el siguiente error:

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

En este caso, Go no podría incluir path.go para definir la variable PathSeparator.

Ahora que se aseguró de que path.go no se ejecutará cuando GOOS tenga el valor Windows, añada un nuevo archivo, windows.go:

  • nano windows.go

En windows.go, defina el PathSeparator de Windows, además de la etiqueta de compilación para indicar al comando go build que es la implementación Windows:

src/app/windows.go
// +build windows

package main

const PathSeparator = "\\"

Guarde el archivo y cierre el editor de texto. La aplicación ahora podrá realizar compilaciones de una forma para Windows y de otra para todas las demás plataformas.

Aunque los binarios ahora se compilarán correctamente para sus plataformas, existen cambios adicionales que debe aplicar a fin de realizar compilaciones para una plataforma a la que no tenga acceso. Para hacer esto, alterará sus variables de entorno GOOS y GOARCH locales en el siguiente paso.

Utilizar sus variables de entorno GOOS y GOARCH locales

Anteriormente, ejecutó el comando go env GOOS GOARCH para conocer el sistema operativo y la arquitectura en los que estaba trabajando. Cuando ejecutó el comando go env, buscó las dos variables de entorno GOOS y GOARCH; si las encontró, se usan los valores de estas, pero si no las encontró Go las configura con la información de la plataforma actual. Esto significa que puede cambiar GOOS o GOARCH para que no se establezcan de forma predeterminada en su SO y arquitectura locales.

El comando go build tiene un comportamiento similar al del comando go env. Puede configurar las variables de entorno GOOS o GOARCH de modo que realicen tareas de compilación para una plataforma diferente usando go build.

Si no usa un sistema Windows, compile un binario windows de app fijando la variable de entorno GOOS en windows cuando se ejecute el comando go build:

  • GOOS=windows go build

A continuación, enumere los archivos en su directorio actual:

  • ls

En el resultado del listado del directorio se muestra que ahora hay un ejecutable app.exe de Windows en el directorio del proyecto:

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

Usando el comando file, puede obtener más información sobre este archivo y confirmar su compilación:

  • file app.exe

Verá lo siguiente:

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

También puede establecer una o ambas variables de entorno en el momento de la compilación. Ejecute lo siguiente:

  • GOOS=linux GOARCH=ppc64 go build

Su ejecutable app ahora se reemplazará por un archivo para una arquitectura diferente. Ejecute el comando file en este binario:

  • file app

Verá un resultado como el siguiente:

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

Al establecer sus variables de entorno GOOS y GOARCH locales, ahora puede crear binarios para cualquiera de las plataformas compatibles con Go sin complicaciones en la configuración. A continuación, usará las convenciones de nombres de archivo a fin de mantener sus archivos bien organizados y realizar compilaciones para plataformas específicas automáticamente sin etiquetas de compilación.

Utilizar los sufijos de nombre de archivo GOOS y GOARCH

Como vio previamente, la biblioteca estándar de Go utiliza de forma intensiva las etiquetas de compilación para simplificar el código separando las implementaciones de las diferentes plataformas en diferentes archivos. Cuando abrió el archivo os/path_unix.go, había una etiqueta de compilación que listaba todas las posibles combinaciones que se consideran plataformas similares a Unix. El archivo os/path_windows.go, sin embargo, no contuvo etiquetas de compilación porque el sufijo del nombre de archivo bastó para indicar a Go la plataforma a la cual se destinó el archivo.

Veremos la sintaxis de esta función. Al nombrar un archivo .go, puede añadir GOOS y GOARCH como sufijos al nombre del archivo en ese orden y separar los valores mediante guiones bajos (_). Si tuviese un archivo de Go llamado filename.go, podría especificar el SO y la arquitectura cambiando el nombre del archivo a filename_GOOS_GOARCH.go. Por ejemplo, si deseara compilarlo para Windows con arquitectura ARM de 64 bits, el nombre del archivo tendría que ser filename_windows_arm64.go. La convención de nomenclatura permite mantener bien organizado el código.

Actualice su programa para que utilice los sufijos de nombre de archivo en vez de etiquetas de compilación. Primero, cambie el nombre de los archivos path.go y windows.go para usar la convención empleada en el paquete os:

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

Una vez cambiados los dos nombres de archivo, podrá eliminar la etiqueta de compilación que añadió a path_windows.go:

  • nano path_windows.go

Elimine // +build windows para que su archivo tenga este aspecto:

path_windows.go
package main

const PathSeparator = "\\"

Guarde el archivo y ciérrelo.

Debido a que unix no es un GOOS válido, el sufijo _unix.go no tiene significado para el compilador de Go. Sin embargo, transmite el propósito previsto del archivo. Al igual que el archivo os/path_unix.go, su archivo path_unix.go de todos modos necesita usar etiquetas de compilación. Por lo tanto, mantenga ese archivo inalterado.

Usando las convenciones de nombres de archivo, eliminó las etiquetas de compilación innecesarias de su código fuente y aportó limpieza y claridad al sistema de archivos.

Conclusión

La capacidad de generar binarios para varias plataformas que no requieren dependencias es una función potente de la cadena de herramientas de Go. En este tutorial, recurrió a esta capacidad añadiendo etiquetas de compilación y sufijos de nombres de archivos a fin de marcar determinados fragmentos de código para que solo realicen tareas de compilación orientadas a ciertas arquitecturas. Creó su propio programa dependiente de plataforma y luego manipuló las variables de entorno GOOS y GOARCH a fin de generar binarios para plataformas diferentes de su plataforma actual. Esta es una capacidad valiosa, porque es común disponer de un proceso de integración continuo que se ejecute automáticamente a través de estas variables de entorno a fin de compilar binarios para todas las plataformas.

Para continuar aprendiendo sobre go build, consulte nuestro tutorial Personalizar binarios de Go con etiquetas de compilación. Si desea obtener más información acerca del lenguaje de programación Go en general, consulte toda la serie Cómo realizar codificaciones en Go.

0 Comments

Creative Commons License