From 8d87c01db79201182fbcd1a210b1b19df9209aeb Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Sun, 23 May 2021 18:27:13 +0100 Subject: [PATCH] feat(cli): add basis for new "emacs-builder" CLI tool written in Go This will eventually replace the build-emacs-for-macos script that's written in Ruby. The migration of functionality will happen bit by bit over time. --- .github/workflows/ci.yml | 52 ++++++++++++ .gitignore | 2 + .golangci.yml | 86 ++++++++++++++++++++ Makefile | 163 ++++++++++++++++++++++++++++++++++++++ cmd/emacs-builder/main.go | 24 ++++++ go.mod | 15 ++++ go.sum | 43 ++++++++++ pkg/cli/cli.go | 89 +++++++++++++++++++++ pkg/cli/options.go | 49 ++++++++++++ 9 files changed, 523 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .golangci.yml create mode 100644 cmd/emacs-builder/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/cli/cli.go create mode 100644 pkg/cli/options.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c349108 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +--- +name: CI +on: [push] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: v1.40 + env: + VERBOSE: "true" + + tidy: + name: Tidy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: 1.16 + - uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Check if mods are tidy + run: make check-tidy + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: 1.16 + - uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Run tests + run: make test + env: + VERBOSE: "true" diff --git a/.gitignore b/.gitignore index f75fd16..1c59c21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .DS_Store +.envrc Formula/* Gemfile.lock +bin builds sources tarballs diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..34fe368 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,86 @@ +linters-settings: + funlen: + lines: 100 + statements: 150 + goconst: + min-occurrences: 5 + gocyclo: + min-complexity: 20 + govet: + check-shadowing: true + enable-all: true + disable: + - fieldalignment + lll: + line-length: 80 + tab-width: 4 + maligned: + suggest-new: true + misspell: + locale: US + +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - depguard + - dupl + - errcheck + - exportloopref + - funlen + - gochecknoinits + - goconst + - gocritic + - gocyclo + - gofumpt + - goimports + - goprintffuncname + - goprintffuncname + - gosec + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - nlreturn + - noctx + - nolintlint + - revive + - sqlclosecheck + - staticcheck + - structcheck + - typecheck + - unconvert + - unused + - varcheck + - whitespace + +issues: + include: + # - EXC0002 # disable excluding of issues about comments from golint + exclude: + - Using the variable on range scope `tt` in function literal + - Using the variable on range scope `tc` in function literal + exclude-rules: + - path: "_test\\.go" + linters: + - funlen + - dupl + - goconst + - source: "^//go:generate " + linters: + - lll + - source: "`json:" + linters: + - lll + +run: + skip-dirs: + - builds + - sources + - tarballs + timeout: 2m + allow-parallel-runners: true + modules-download-mode: readonly diff --git a/Makefile b/Makefile index fe08d61..9d47da3 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,166 @@ +SOURCES := $(shell \ + find * \ + -not -path 'sources/*' \ + -not -path 'builds/*' \( \ + -name "*.go" -or \ + -name "go.mod" -or \ + -name "go.sum" -or \ + -name "Makefile" -or \ + -type f -path 'internal/*' -or \ + -type f -path 'cmd/*' -or \ + -type f -path 'pkg/*' \ + \) | grep -v '.DS_Store' \ +) + +# +# Environment +# + +# Verbose output +ifdef VERBOSE +V = -v +endif + +BINDIR := bin +TOOLDIR := $(BINDIR)/tools + +# Global environment variables for all targets +SHELL ?= /bin/bash +SHELL := env \ + GO111MODULE=on \ + GOBIN=$(CURDIR)/$(BINDIR) \ + CGO_ENABLED=0 \ + PATH='$(CURDIR)/$(BINDIR):$(CURDIR)/$(TOOLDIR):$(PATH)' \ + $(SHELL) + +# +# Defaults +# + +# Default target +.DEFAULT_GOAL := build + +# +# Tools +# + +# external tool +define tool # 1: binary-name, 2: go-import-path +TOOLS += $(TOOLDIR)/$(1) + +$(TOOLDIR)/$(1): Makefile + GOBIN="$(CURDIR)/$(TOOLDIR)" go install "$(2)" +endef + +$(eval $(call tool,gofumpt,mvdan.cc/gofumpt@latest)) +$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.40)) +$(eval $(call tool,gomod,github.com/Helcaraxan/gomod@latest)) + +.PHONY: tools +tools: $(TOOLS) + +# +# Build +# + +LDFLAGS := -w -s + +VERSION ?= $(shell git describe --tags --exact 2>/dev/null) +COMMIT ?= $(shell git rev-parse HEAD 2>/dev/null) +DATE ?= $(shell date '+%FT%T%z') + +ifeq ($(VERSION),) + VERSION = 0.0.0-dev +endif + +CMDDIR := cmd +BINS := $(shell test -d "$(CMDDIR)" && cd "$(CMDDIR)" && \ + find * -maxdepth 0 -type d -exec echo $(BINDIR)/{} \;) + +.PHONY: build +build: $(BINS) + +$(BINS): $(BINDIR)/%: $(SOURCES) + mkdir -p "$(BINDIR)" + cd "$(CMDDIR)/$*" && go build -a $(V) \ + -o "$(CURDIR)/$(BINDIR)/$*" \ + -ldflags "$(LDFLAGS) \ + -X main.version=$(VERSION) \ + -X main.commit=$(COMMIT) \ + -X main.date=$(DATE)" + +# +# Development +# + +TEST ?= $$(go list ./... | grep -v 'sources/') + +.PHONY: clean +clean: + rm -rf $(BINARY) $(TOOLS) + rm -f ./go.mod.tidy-check ./go.sum.tidy-check + +.PHONY: test +test: + CGO_ENABLED=1 go test $(V) -count=1 -race $(TESTARGS) $(TEST) + +.PHONY: lint +lint: $(TOOLDIR)/golangci-lint + golangci-lint $(V) run + +.PHONY: format +format: $(TOOLDIR)/gofumpt + gofumpt -w . + +# +# Dependencies +# + +.PHONY: deps +deps: + $(info Downloading dependencies) + go mod download + +.PHONY: deps-update +deps-update: + $(info Downloading dependencies) + go get -u -t ./... + +.PHONY: deps-analyze +deps-analyze: $(TOOLDIR)/gomod + gomod analyze + +.PHONY: tidy +tidy: + go mod tidy $(V) + +.PHONY: verify +verify: + go mod verify + +.SILENT: check-tidy +.PHONY: check-tidy +check-tidy: + cp go.mod go.mod.tidy-check + cp go.sum go.sum.tidy-check + go mod tidy + ( \ + diff go.mod go.mod.tidy-check && \ + diff go.sum go.sum.tidy-check && \ + rm -f go.mod go.sum && \ + mv go.mod.tidy-check go.mod && \ + mv go.sum.tidy-check go.sum \ + ) || ( \ + rm -f go.mod go.sum && \ + mv go.mod.tidy-check go.mod && \ + mv go.sum.tidy-check go.sum; \ + exit 1 \ + ) + +# +# Release +# + .PHONY: new-version new-version: check-npx npx standard-version diff --git a/cmd/emacs-builder/main.go b/cmd/emacs-builder/main.go new file mode 100644 index 0000000..a57fac5 --- /dev/null +++ b/cmd/emacs-builder/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "os" + + "github.com/jimeh/build-emacs-for-macos/pkg/cli" +) + +var ( + version string + commit string + date string +) + +func main() { + cliInstance := cli.New(version, commit, date) + + err := cliInstance.Run(os.Args) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error()) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ca9c46e --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module github.com/jimeh/build-emacs-for-macos + +go 1.16 + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/fatih/color v1.12.0 // indirect + github.com/hashicorp/go-hclog v0.16.1 + github.com/mattn/go-isatty v0.0.13 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/stretchr/testify v1.6.1 // indirect + github.com/urfave/cli/v2 v2.3.0 + golang.org/x/sys v0.0.0-20210603125802-9665404d3644 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..69ca4ad --- /dev/null +++ b/go.sum @@ -0,0 +1,43 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o= +github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644 h1:CA1DEQ4NdKphKeL70tvsWNdT5oFh1lOjihRcEDROi0I= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go new file mode 100644 index 0000000..224c2a3 --- /dev/null +++ b/pkg/cli/cli.go @@ -0,0 +1,89 @@ +package cli + +import ( + "fmt" + "strings" + + cli2 "github.com/urfave/cli/v2" +) + +type CLI struct { + App *cli2.App + Version string + Commit string + Date string +} + +func New(version, commit, date string) *CLI { + if version == "" { + version = "0.0.0-dev" + } + + c := &CLI{ + Version: version, + Commit: commit, + Date: date, + App: &cli2.App{ + Name: "emacs-builder", + Usage: "Tool to build emacs", + Version: version, + EnableBashCompletion: true, + Flags: []cli2.Flag{ + &cli2.StringFlag{ + Name: "log-level", + Usage: "set log level", + Aliases: []string{"l"}, + Value: "info", + }, + &cli2.BoolFlag{ + Name: "quiet", + Usage: "silence noisy output", + Aliases: []string{"q"}, + Value: false, + }, + cli2.VersionFlag, + }, + Commands: []*cli2.Command{ + { + Name: "version", + Usage: "print the version", + Aliases: []string{"v"}, + Action: func(c *cli2.Context) error { + cli2.VersionPrinter(c) + + return nil + }, + }, + }, + }, + } + + cli2.VersionPrinter = c.VersionPrinter + + return c +} + +func (s *CLI) VersionPrinter(c *cli2.Context) { + version := c.App.Version + if version == "" { + version = "0.0.0-dev" + } + + extra := []string{} + if len(s.Commit) >= 7 { + extra = append(extra, s.Commit[0:7]) + } + if s.Date != "" { + extra = append(extra, s.Date) + } + var extraOut string + if len(extra) > 0 { + extraOut += " (" + strings.Join(extra, ", ") + ")" + } + + fmt.Printf("%s%s\n", version, extraOut) +} + +func (s *CLI) Run(args []string) error { + return s.App.Run(args) +} diff --git a/pkg/cli/options.go b/pkg/cli/options.go new file mode 100644 index 0000000..688bb1a --- /dev/null +++ b/pkg/cli/options.go @@ -0,0 +1,49 @@ +package cli + +import ( + "fmt" + "os" + "sync" + "time" + + "github.com/hashicorp/go-hclog" + cli2 "github.com/urfave/cli/v2" +) + +type Options struct { + quiet bool +} + +func actionWrapper( + f func(*cli2.Context, *Options) error, +) func(*cli2.Context) error { + return func(c *cli2.Context) error { + opts := &Options{ + quiet: c.Bool("quiet"), + } + + levelStr := c.String("log-level") + level := hclog.LevelFromString(levelStr) + if level == hclog.NoLevel { + return fmt.Errorf("invalid log level \"%s\"", levelStr) + } + + // Prevent things from logging if they weren't explicitly given a + // logger. + hclog.SetDefault(hclog.NewNullLogger()) + + // Create custom logger. + logr := hclog.New(&hclog.LoggerOptions{ + Level: level, + Output: os.Stderr, + Mutex: &sync.Mutex{}, + TimeFormat: time.RFC3339, + Color: hclog.ColorOff, + }) + + ctx := hclog.WithContext(c.Context, logr) + c.Context = ctx + + return f(c, opts) + } +}