From f8ceff1bb8c86a5d1ce05bf1fdcf05c94fe09ea3 Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Sun, 15 Aug 2021 15:16:13 -0700 Subject: blog: working with go - initial content This note captures things that are useful when working with go for me. --- users/fcuny/blog/content/notes/working-with-go.org | 264 +++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 users/fcuny/blog/content/notes/working-with-go.org (limited to 'users') diff --git a/users/fcuny/blog/content/notes/working-with-go.org b/users/fcuny/blog/content/notes/working-with-go.org new file mode 100644 index 0000000..e00f635 --- /dev/null +++ b/users/fcuny/blog/content/notes/working-with-go.org @@ -0,0 +1,264 @@ +#+TITLE: Working with Go +#+DATE: <2021-08-05 Thu> +#+TAGS[]: go emacs +#+toc: t + +/This document assumes go version >= 1.16/. + +* Go Modules +[[https://blog.golang.org/using-go-modules][Go modules]] have been added in 2019 with Go 1.11. A number of changes were introduced with [[https://blog.golang.org/go116-module-changes][Go 1.16]]. This document is a reference for me so that I can find answers to things I keep forgetting. +** Creating a new module +To create a new module, run =go mod init golang.fcuny.net/m=. This will create two files: =go.mod= and =go.sum=. + +In the =go.mod= file you'll find: +- the module import path (prefixed with =module=) +- the list of dependencies (within =require=) +- the version of go to use for the module +** Versioning +To bump the version of a module: +#+begin_src sh +$ git tag v1.2.3 +$ git push --tags +#+end_src + +Then as a user: +#+begin_src sh +$ go get -d golang.fcuny.net/m@v1.2.3 +#+end_src +** Updating dependencies +To update the dependencies, run =go mod tidy= +** Editing a module +If you need to modify a module, you can check out the module in your workspace (=git clone =). + +Edit the =go.mod= file to add +#+begin_src go +replace => +#+end_src + +Then modify the code of the module and the next time you compile the project, the cloned module will be used. + +This is particularly useful when trying to debug an issue with an external module. +** Vendor-ing modules +It's still possible to vendor modules by running =go mod vendor=. This can be useful in the case of a CI setup that does not have access to internet. +** Proxy +As of version 1.13, the variable =GOPROXY= defaults to =https://proxy.golang.org,direct= (see [[https://github.com/golang/go/blob/c95464f0ea3f87232b1f3937d1b37da6f335f336/src/cmd/go/internal/cfg/cfg.go#L269][here]]). As a result, when running something like =go get golang.org/x/tools/gopls@latest=, the request goes through the proxy. + +There's a number of ways to control the behavior, they are documented [[https://golang.org/ref/mod#private-modules][here]]. + +There's a few interesting things that can be done when using the proxy. There's a few special URLs (better documentation [[https://golang.org/ref/mod#goproxy-protocol][here]]): +| path | description | +|-----------------------+------------------------------------------------------------------------------------------| +| $mod/@v/list | Returns the list of known versions - there's one version per line and it's in plain text | +| $mod/@v/$version.info | Returns metadata about a version in JSON format | +| $mod/@v/$version.mod | Returns the =go.mod= file for that version | + +For example, looking at the most recent versions for =gopls=: +#+begin_src sh +; curl -s -L https://proxy.golang.org/golang.org/x/tools/gopls/@v/list|sort -r|head +v0.7.1-pre.2 +v0.7.1-pre.1 +v0.7.1 +v0.7.0-pre.3 +v0.7.0-pre.2 +v0.7.0-pre.1 +v0.7.0 +v0.6.9-pre.1 +v0.6.9 +v0.6.8-pre.1 +#+end_src + +Let's check the details for the most recent version +#+begin_src sh +; curl -s -L https://proxy.golang.org/golang.org/x/tools/gopls/@v/list|sort -r|head +v0.7.1-pre.2 +v0.7.1-pre.1 +v0.7.1 +v0.7.0-pre.3 +v0.7.0-pre.2 +v0.7.0-pre.1 +v0.7.0 +v0.6.9-pre.1 +v0.6.9 +v0.6.8-pre.1 +#+end_src + +And let's look at the content of the =go.mod= for that version too: +#+begin_src sh +; curl -s -L https://proxy.golang.org/golang.org/x/tools/gopls/@v/v0.7.1-pre.2.mod +module golang.org/x/tools/gopls + +go 1.17 + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/google/go-cmp v0.5.5 + github.com/google/safehtml v0.0.2 // indirect + github.com/jba/templatecheck v0.6.0 + github.com/sanity-io/litter v1.5.0 + github.com/sergi/go-diff v1.1.0 + golang.org/x/mod v0.4.2 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20210510120138-977fb7262007 + golang.org/x/text v0.3.6 // indirect + golang.org/x/tools v0.1.6-0.20210802203754-9b21a8868e16 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + honnef.co/go/tools v0.2.0 + mvdan.cc/gofumpt v0.1.1 + mvdan.cc/xurls/v2 v2.2.0 +) +#+end_src +* Tooling +** LSP +=gopls= is the default implementation of the language server protocol maintained by the Go team. To install the latest version, run =go install golang.org/x/tools/gopls@latest= +** =staticcheck= +[[https://staticcheck.io/][=staticcheck=]] is a great tool to run against your code to find issues. To install the latest version, run =go install honnef.co/go/tools/cmd/staticcheck@latest=. +* Emacs integration +** =go-mode= +[[https://github.com/dominikh/go-mode.el][This is the mode]] to install to get syntax highlighting (mostly). +** Integration with LSP +Emacs has a pretty good integration with LSP. +https://geeksocket.in/posts/emacs-lsp-go/ +*** =lsp-mode= +[[src:https://github.com/emacs-lsp/lsp-mode][This is the main mode to install]]. It provides the integration with LSP. + +I've configured the mode like this: +#+begin_src elisp +(use-package lsp-mode + :ensure t + :commands (lsp lsp-deferred) + :diminish lsp-mode + :hook ((go-mode . lsp-deferred) + (lsp-mode . (lambda() (let ((lsp-keymap-prefix "C-c l")) + (lsp-enable-which-key-integration))))) + :config + (define-key lsp-mode-map (kbd "C-c l") lsp-command-map) + (lsp-register-custom-settings + '(("gopls.completeUnimported" t t) + ("gopls.staticcheck" t t))) + :bind + (("C-c l i" . lsp-ui-imenu)) + :custom + (lsp-session-file (expand-file-name "lsp-session-v1" fcuny/path-emacs-var)) + (lsp-enable-snippet nil) + (lsp-signature-doc-lines 5) + (lsp-modeline-diagnostic-scope :workspace) + (lsp-completion-provider :capf) + (lsp-completion-enable t) + (lsp-enable-indentation t) + (lsp-eldoc-render-all t) + (lsp-prefer-flymake nil)) +#+end_src + ++ =C-c l= brings a menu via [[https://github.com/abo-abo/hydra][hydra]] ++ By default it seems that =staticcheck= is not used, so I force it with the =lsp-register-custom-settings= ++ I prefer [[https://www.flycheck.org/en/latest/][flycheck]] +*** =lsp-ui= +This is mostly for UI tweaks. I use the following configuration +#+begin_src elisp +(use-package lsp-ui + :ensure t + :hook (lsp-mode . lsp-ui-mode) + :commands lsp-ui-mode + :custom + (lsp-ui-doc-delay 0.4) + (lsp-ui-doc-enable t) + (lsp-ui-doc-position 'top) + (lsp-ui-doc-include-signature t) + (lsp-ui-peek-enable t) + (lsp-ui-sideline-enable t) + (lsp-ui-imenu-enable t) + (lsp-ui-flycheck-enable t)) + +#+end_src +*** =lsp-ivy= +I use ivy for completion, [[https://github.com/emacs-lsp/lsp-ivy][it provides]] completion based on the current workspace. This is my configuration: +#+begin_src elisp +(use-package lsp-ivy + :ensure t + :commands lsp-ivy-workspace-symbol) +#+end_src +*** =lsp-treemacs= +[[https://github.com/emacs-lsp/lsp-treemacs][It provides]] some nice improvement regarding the UI. This is my configuration: +#+begin_src elisp +(use-package lsp-treemacs + :ensure t + :config + (lsp-treemacs-sync-mode 1)) + +#+end_src +* Profiling +** pprof +[[https://github.com/google/pprof][pprof]] is a tool to visualize performance data. Let's start with the following test: +#+begin_src go +package main + +import ( + "strings" + "testing" +) + +func BenchmarkStringJoin(b *testing.B) { + input := []string{"a", "b"} + for i := 0; i <= b.N; i++ { + r := strings.Join(input, " ") + if r != "a b" { + b.Errorf("want a b got %s", r) + } + } +} +#+end_src + +Let's run a benchmark with ~go test . -bench=. -cpuprofile cpu_profile.out~: +#+begin_src go +goos: linux +goarch: amd64 +pkg: golang.fcuny.net/m +cpu: Intel(R) Core(TM) i3-1005G1 CPU @ 1.20GHz +BenchmarkStringJoin-4 41833486 26.85 ns/op 3 B/op 1 allocs/op +PASS +ok golang.fcuny.net/m 1.327s +#+end_src + +And let's take a look at the profile with =go tool pprof cpu_profile.out= +#+begin_src sh +File: m.test +Type: cpu +Time: Aug 15, 2021 at 3:01pm (PDT) +Duration: 1.31s, Total samples = 1.17s (89.61%) +Entering interactive mode (type "help" for commands, "o" for options) +(pprof) top +Showing nodes accounting for 1100ms, 94.02% of 1170ms total +Showing top 10 nodes out of 41 + flat flat% sum% cum cum% + 240ms 20.51% 20.51% 240ms 20.51% runtime.memmove + 220ms 18.80% 39.32% 320ms 27.35% runtime.mallocgc + 130ms 11.11% 50.43% 450ms 38.46% runtime.makeslice + 110ms 9.40% 59.83% 1150ms 98.29% golang.fcuny.net/m.BenchmarkStringJoin + 110ms 9.40% 69.23% 580ms 49.57% strings.(*Builder).grow (inline) + 110ms 9.40% 78.63% 1040ms 88.89% strings.Join + 70ms 5.98% 84.62% 300ms 25.64% strings.(*Builder).WriteString + 50ms 4.27% 88.89% 630ms 53.85% strings.(*Builder).Grow (inline) + 40ms 3.42% 92.31% 40ms 3.42% runtime.nextFreeFast (inline) + 20ms 1.71% 94.02% 20ms 1.71% runtime.getMCache (inline) +#+end_src + +We can get a breakdown of the data for our module: +#+begin_src sh +(pprof) list golang.fcuny.net +Total: 1.17s +ROUTINE ======================== golang.fcuny.net/m.BenchmarkStringJoin in /home/fcuny/workspace/gobench/app_test.go + 110ms 1.15s (flat, cum) 98.29% of Total + . . 5: "testing" + . . 6:) + . . 7: + . . 8:func BenchmarkStringJoin(b *testing.B) { + . . 9: b.ReportAllocs() + 10ms 10ms 10: input := []string{"a", "b"} + . . 11: for i := 0; i <= b.N; i++ { + 20ms 1.06s 12: r := strings.Join(input, " ") + 80ms 80ms 13: if r != "a b" { + . . 14: b.Errorf("want a b got %s", r) + . . 15: } + . . 16: } + . . 17:} +#+end_src -- cgit 1.4.1