about summary refs log tree commit diff
path: root/users/fcuny
diff options
context:
space:
mode:
Diffstat (limited to 'users/fcuny')
-rw-r--r--users/fcuny/notes/content/notes/working-with-go.org264
1 files changed, 264 insertions, 0 deletions
diff --git a/users/fcuny/notes/content/notes/working-with-go.org b/users/fcuny/notes/content/notes/working-with-go.org
new file mode 100644
index 0000000..e00f635
--- /dev/null
+++ b/users/fcuny/notes/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 <module URL>=).
+
+Edit the =go.mod= file to add
+#+begin_src go
+replace <module URL> => <path of the local checkout>
+#+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