Learning Go – Day 6

Featured image by Egon Elbre

Lets continue on our journey

Installing Go (1.18) using the download from https://go.dev/ on macOS Monterey, the GOPATH environment variable is not set by default. However reading the help for go help install they state the following.

Executables are installed in the directory named by the GOBIN environment variable, which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH environment variable is not set.

NOTE: At this point in time you might get some conflicting or out of date information regarding how to start a go project and or to do with paths. From what I can tell is that at some point they added modules to go and also allowed you to have your modules/code outside of the GOPATH.

$ ls ~/go
bin/ pkg/
  • Installing a module.
$ go install github.com/spf13/cobra-cli@latest
go: downloading github.com/spf13/cobra-cli v1.3.0
go: downloading github.com/spf13/cobra v1.3.0
...
  • Binaries will go to $GOPATH/bin or $HOME/go/bin as stated by the documentation.
$ ls -la ~/go/bin
...
-rwxr-xr-x  1 andre  staff   7.9M 27 Apr 10:40 cobra-cli*
  • Modules go to $GOPATH/pkg/mod or $HOME/go/pkg/mod.
$ ls ~/go/pkg/mod
cache/      github.com/ golang.org/ gopkg.in/   honnef.co/  mvdan.cc/

$ ls ~/go/pkg/mod/github.com
fsnotify/   hashicorp/  magiconair/ mitchellh/  pelletier/  spf13/      subosito/   yuin/
  • Installing a package. Note not everything is a module and you will need to install packages using go get. You run this from the directory in which your go.mod file lives.
$ go get -u github.com/spf13/cobra@latest
go: downloading github.com/spf13/cobra v1.4.0
go: downloading github.com/inconshreveable/mousetrap v1.0.0
...

$ cat go.mod
module learn

go 1.18

require (
	github.com/inconshreveable/mousetrap v1.0.0 // indirect
	github.com/spf13/cobra v1.4.0 // indirect
	github.com/spf13/pflag v1.0.5 // indirect
)
  • Formatting code to look like every one else’s code using gofmt. I hear people on YouTube pronounce it “go fomt” and same for the fmt package being pronounced “fomt”. Visual Studio Code with the Go extension automatically does the formatting on save.
$ gofmt --help
usage: gofmt [flags] [path ...]
  -cpuprofile string
    	write cpu profile to this file
  -d	display diffs instead of rewriting files
  -e	report all errors (not just the first 10 on different lines)
  -l	list files whose formatting differs from gofmt's
  -r string
    	rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')
  -s	simplify code
  -w	write result to (source) file instead of stdout

# To format a file in place
$ gofmt -s -w /path/to/file.go

Documentation right at your fingertips

  • You can lookup documentation for packages from the shell.
$ go doc PACKAGE

$ go doc flag
...

# See the documentation for flag.StringVar function
$ go doc flag.StringVar
package flag // import "flag"

func StringVar(p *string, name string, value string, usage string)
    StringVar defines a string flag with specified name, default value, and
    usage string. The argument p points to a string variable in which to store
    the value of the flag.You can also use [godoc](http://golang.org/x/tools/cmd/godoc) to serve the documentation from your local machine.

Parsing CLI flags

I have seen a number of people use Cobra and Viper from spf13 (which rang a bell with me from when I used Hugo in 2016, nice one I see he now works for Google).

However the standard library includes the flag package that suits most basic needs.

Example of parsing a string flag named “flagName”.

import "flag"

var name string
flag.StringVar(&name, "flagName", "default value", "help description"
flag.Parser()

Usage would be:

$ go run . --help
...
  -flagName string
      help description (default "default value")

$ go run . -flagName Awesome
  • fmt.Printf supports “%q” that will wrap a string in quotes in the output. Thats nice!
  • Note: You can use single hyphen “-flagName” or double hyphen “—flagName”.
  • You can exit a program with panic to indicate something went bust.
  • Note: A nice package for some extra string manipulation is strings from the standard library.

Cross compiling

Recall that you can set the environment variable GOOS to “linux”, “darwin” or “windows” for the operating system before issuing go build.

You can also set the CPU architecture using the environment variable GOARCH.

For example: GOOS=linux GOARCH=arm64 go build -o linux-arm64

Seeing is believing

I want to test out this theory of cross compiling a binary to be run on another system. In fact the whole reason I got into learning Go is because I have some applications I want to run on my own Linux server.

The test is to cross compile a binary from my M1 Mac (arm64) and deploy it to my Ubuntu Linux running on Intel (amd64).

This answer from SO has a good list of platforms.

  • Hello world program
package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello world!")
}
  • Build as normal on the M1 Mac to see what the architecture is. The executable is in the Mach-O format and for arm64.
$ go build
$ file learngo
learngo: Mach-O 64-bit executable arm64
  • Build for my Intel Linux machine. The executable should be in the ELF format for amd64 (aka x86-64)
$ GOOS=linux GOARCH=amd64 go build
$ file learngo
learngo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=LRHZBB1HCcIFDEba0O6c/Py1hw-sG8Pu6TgGtF0rJ/SFoJuDdRWvikPZWchfmu/dTdG6qmbhalIb0BLOgw0, not stripped
  • Transfer to my Linux machine and run to verify this works.
$ rsync -av learngo andre@SERVER:/home/andre/

$ # SSH into my linux server
andre@simba:~$ file learngo
learngo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=LRHZBB1HCcIFDEba0O6c/Py1hw-sG8Pu6TgGtF0rJ/SFoJuDdRWvikPZWchfmu/dTdG6qmbhalIb0BLOgw0, not stripped

andre@simba:~$ ./learngo
Hello world!
  • Okay this is pretty cool!
  • Can I build a go binary on my Linux machine to run on my M1 Mac? I will come back later to explore this question once I have installed Go on my Ubuntu Linux machine.