github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/docs/Generate.md (about)

     1  Summary
     2  -------
     3  `./godelw generate` runs "go generate" tasks in a project based on configuration.
     4  
     5  Tutorial start state
     6  --------------------
     7  
     8  * `$GOPATH/src/github.com/nmiyake/echgo` exists and is the working directory
     9  * Project contains `godel` and `godelw`
    10  * Project contains `main.go`
    11  * Project contains `.gitignore` that ignores IDEA files
    12  * Project contains `echo/echo.go`, `echo/echo_test.go` and `echo/echoer.go`
    13  * `godel/config/dist.yml` is configured to build `echgo`
    14  * Project is tagged as 0.0.1
    15  * `godel/config/dist.yml` is configured to create distributions for `echgo`
    16  * Project is tagged as 0.0.2
    17  * Go files have license headers
    18  
    19  ([Link](https://github.com/nmiyake/echgo/tree/0239b282904d05bb9eef6c3c3edfe1c28f888ad3))
    20  
    21  Define `go generate` tasks
    22  --------------------------
    23  
    24  We will extend `echgo` by creating some different echo implementations. The different types will be defined as enums,
    25  and we will use `go generate` to invoke `stringer` to create the string representation of these enum values.
    26  
    27  Run the following to update the `echo` implementation:
    28  
    29  ```
    30  ➜ echo '// Copyright (c) 2017 Author Name
    31  //
    32  // Licensed under the Apache License, Version 2.0 (the "License");
    33  // you may not use this file except in compliance with the License.
    34  // You may obtain a copy of the License at
    35  //
    36  //     http://www.apache.org/licenses/LICENSE-2.0
    37  //
    38  // Unless required by applicable law or agreed to in writing, software
    39  // distributed under the License is distributed on an "AS IS" BASIS,
    40  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    41  // See the License for the specific language governing permissions and
    42  // limitations under the License.
    43  
    44  package echo
    45  
    46  import (
    47  	"fmt"
    48  	"strings"
    49  )
    50  
    51  type Type int
    52  
    53  func (t Type) String() string {
    54  	switch t {
    55  	case Simple:
    56  		return "Simple"
    57  	case Reverse:
    58  		return "Reverse"
    59  	default:
    60  		panic(fmt.Sprintf("unrecognized type: %d", t))
    61  	}
    62  }
    63  
    64  const (
    65  	Simple Type = iota
    66  	Reverse
    67  	end
    68  )
    69  
    70  var echoers = []Echoer{
    71  	Simple:  &simpleEchoer{},
    72  	Reverse: &reverseEchoer{},
    73  }
    74  
    75  func NewEchoer(typ Type) Echoer {
    76  	return echoers[typ]
    77  }
    78  
    79  func TypeFrom(typ string) (Type, error) {
    80  	for curr := Simple; curr < end; curr++ {
    81  		if strings.ToLower(typ) == strings.ToLower(curr.String()) {
    82  			return curr, nil
    83  		}
    84  	}
    85  	return end, fmt.Errorf("unrecognized type: %s", typ)
    86  }
    87  
    88  type simpleEchoer struct{}
    89  
    90  func (e *simpleEchoer) Echo(in string) string {
    91  	return in
    92  }
    93  
    94  type reverseEchoer struct{}
    95  
    96  func (e *reverseEchoer) Echo(in string) string {
    97  	out := make([]byte, len(in))
    98  	for i := 0; i < len(out); i++ {
    99  		out[i] = in[len(in)-1-i]
   100  	}
   101  	return string(out)
   102  }' > echo/echo.go
   103  ➜ echo '// Copyright (c) 2017 Author Name
   104  //
   105  // Licensed under the Apache License, Version 2.0 (the "License");
   106  // you may not use this file except in compliance with the License.
   107  // You may obtain a copy of the License at
   108  //
   109  //     http://www.apache.org/licenses/LICENSE-2.0
   110  //
   111  // Unless required by applicable law or agreed to in writing, software
   112  // distributed under the License is distributed on an "AS IS" BASIS,
   113  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   114  // See the License for the specific language governing permissions and
   115  // limitations under the License.
   116  
   117  package echo_test
   118  
   119  import (
   120  	"testing"
   121  
   122  	"github.com/nmiyake/echgo/echo"
   123  )
   124  
   125  func TestEcho(t *testing.T) {
   126  	echoer := echo.NewEchoer(echo.Simple)
   127  	for i, tc := range []struct {
   128  		in   string
   129  		want string
   130  	}{
   131  		{"foo", "foo"},
   132  		{"foo bar", "foo bar"},
   133  	} {
   134  		if got := echoer.Echo(tc.in); got != tc.want {
   135  			t.Errorf("case %d failed: want %q, got %q", i, tc.want, got)
   136  		}
   137  	}
   138  }' > echo/echo_test.go
   139  ➜ echo '// Copyright (c) 2017 Author Name
   140  //
   141  // Licensed under the Apache License, Version 2.0 (the "License");
   142  // you may not use this file except in compliance with the License.
   143  // You may obtain a copy of the License at
   144  //
   145  //     http://www.apache.org/licenses/LICENSE-2.0
   146  //
   147  // Unless required by applicable law or agreed to in writing, software
   148  // distributed under the License is distributed on an "AS IS" BASIS,
   149  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   150  // See the License for the specific language governing permissions and
   151  // limitations under the License.
   152  
   153  package main
   154  
   155  import (
   156  	"flag"
   157  	"fmt"
   158  	"strings"
   159  
   160  	"github.com/nmiyake/echgo/echo"
   161  )
   162  
   163  var version = "none"
   164  
   165  func main() {
   166  	versionVar := flag.Bool("version", false, "print version")
   167  	typeVar := flag.String("type", echo.Simple.String(), "type of echo")
   168  	flag.Parse()
   169  	if *versionVar {
   170  		fmt.Println("echgo version:", version)
   171  		return
   172  	}
   173  	typ, err := echo.TypeFrom(*typeVar)
   174  	if err != nil {
   175  		fmt.Println("invalid echo type:", *typeVar)
   176  		return
   177  	}
   178  	echoer := echo.NewEchoer(typ)
   179  	fmt.Println(echoer.Echo(strings.Join(flag.Args(), " ")))
   180  }' > main.go
   181  ```
   182  
   183  At a high level, this code introduces a new type named `Type` that represents the different types of echo
   184  implementations. The code maintains a mapping from the types to the implementations and provides a function that returns
   185  the Type for a given string. Run the code to verify that this works for the "simple" and "reverse" types that were
   186  defined:
   187  
   188  ```
   189  ➜ go run main.go -type simple foo
   190  foo
   191  ➜ go run main.go -type reverse foo
   192  oof
   193  ```
   194  
   195  The code relies on `Type` having a `String` function that returns its string representation. The current implementation
   196  works, but it is a bit redundant since the string value is always the name of the constant. It is also a maintenance
   197  burden: whenever a new type is added or an existing type is renamed, the `String` function must also be updated.
   198  Furthermore, because the string definitions are simply part of the switch statement, if someone forgets to add or update
   199  the definitions, this will not be caught at compile-time, so it's also a likely source of future bugs.
   200  
   201  We can address this by using `go generate` and the `stringer` tool to generate this code automatically.
   202  
   203  Run the following to ensure that you have the `stringer` tool:
   204  
   205  ```
   206  ➜ go get -u golang.org/x/tools/cmd/stringer
   207  ```
   208  
   209  Now, update `echo.go` to have a `go generate` line that invokes `stringer`:
   210  
   211  ```
   212  ➜ echo '// Copyright (c) 2017 Author Name
   213  //
   214  // Licensed under the Apache License, Version 2.0 (the "License");
   215  // you may not use this file except in compliance with the License.
   216  // You may obtain a copy of the License at
   217  //
   218  //     http://www.apache.org/licenses/LICENSE-2.0
   219  //
   220  // Unless required by applicable law or agreed to in writing, software
   221  // distributed under the License is distributed on an "AS IS" BASIS,
   222  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   223  // See the License for the specific language governing permissions and
   224  // limitations under the License.
   225  
   226  //go:generate stringer -type=Type
   227  
   228  package echo
   229  
   230  import (
   231  	"fmt"
   232  	"strings"
   233  )
   234  
   235  type Type int
   236  
   237  func (t Type) String() string {
   238  	switch t {
   239  	case Simple:
   240  		return "Simple"
   241  	case Reverse:
   242  		return "Reverse"
   243  	default:
   244  		panic(fmt.Sprintf("unrecognized type: %v", t))
   245  	}
   246  }
   247  
   248  const (
   249  	Simple Type = iota
   250  	Reverse
   251  	end
   252  )
   253  
   254  var echoers = []Echoer{
   255  	Simple:  &simpleEchoer{},
   256  	Reverse: &reverseEchoer{},
   257  }
   258  
   259  func NewEchoer(typ Type) Echoer {
   260  	return echoers[typ]
   261  }
   262  
   263  func TypeFrom(typ string) (Type, error) {
   264  	for curr := Simple; curr < end; curr++ {
   265  		if strings.ToLower(typ) == strings.ToLower(curr.String()) {
   266  			return curr, nil
   267  		}
   268  	}
   269  	return end, fmt.Errorf("unrecognized type: %s", typ)
   270  }
   271  
   272  type simpleEchoer struct{}
   273  
   274  func (e *simpleEchoer) Echo(in string) string {
   275  	return in
   276  }
   277  
   278  type reverseEchoer struct{}
   279  
   280  func (e *reverseEchoer) Echo(in string) string {
   281  	out := make([]byte, len(in))
   282  	for i := 0; i < len(out); i++ {
   283  		out[i] = in[len(in)-1-i]
   284  	}
   285  	return string(out)
   286  }' > echo/echo.go
   287  ```
   288  
   289  Now that the `//go:generate` directive exists, the standard Go approach would be to run `go generate` to run the
   290  generation task. However, this approach depends on developers knowing/remembering to run `go generate` when they update
   291  the definitions. Projects typically address this my noting it in their documentation or in comments, but this is
   292  obviously quite fragile, and correctly calling all of the required generators can be especially challenging for larger
   293  projects that may have several `go generate` tasks.
   294  
   295  We can address this by defining the `generate` tasks as part of the declarative configuration for our project. Define a
   296  "generate" task in `godel/config/generate.yml` by running the following:
   297  
   298  ```
   299  ➜ echo 'generators:
   300    stringer:
   301      go-generate-dir: echo
   302      gen-paths:
   303        paths:
   304          - echo/type_string.go' > godel/config/generate.yml
   305  ```
   306  
   307  This specifies that we have a generator task named "stringer" (this name is specified by the user and can be anything).
   308  The `go-generate-dir` specifies the directory (relative to the project root) in which `go generate` should be run. The
   309  `gen-paths` parameter specifies paths to the files or directories that are generated or modified by the `go generate`
   310  task.
   311  
   312  Run the generator task and verify that it generates the expected code:
   313  
   314  ```
   315  ➜ ./godelw generate
   316  ➜ cat ./echo/type_string.go
   317  // Code generated by "stringer -type=Type"; DO NOT EDIT.
   318  
   319  package echo
   320  
   321  import "fmt"
   322  
   323  const _Type_name = "SimpleReverseend"
   324  
   325  var _Type_index = [...]uint8{0, 6, 13, 16}
   326  
   327  func (i Type) String() string {
   328  	if i < 0 || i >= Type(len(_Type_index)-1) {
   329  		return fmt.Sprintf("Type(%d)", i)
   330  	}
   331  	return _Type_name[_Type_index[i]:_Type_index[i+1]]
   332  }
   333  ```
   334  
   335  We can see that `echo/type_string.go` was generated and provides an implementation of the `String` function for `Type`.
   336  Now that this exists, we can remove the one we wrote manually in `echo.go`:
   337  
   338  ```
   339  ➜ echo '// Copyright (c) 2017 Author Name
   340  //
   341  // Licensed under the Apache License, Version 2.0 (the "License");
   342  // you may not use this file except in compliance with the License.
   343  // You may obtain a copy of the License at
   344  //
   345  //     http://www.apache.org/licenses/LICENSE-2.0
   346  //
   347  // Unless required by applicable law or agreed to in writing, software
   348  // distributed under the License is distributed on an "AS IS" BASIS,
   349  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   350  // See the License for the specific language governing permissions and
   351  // limitations under the License.
   352  
   353  //go:generate stringer -type=Type
   354  
   355  package echo
   356  
   357  import (
   358  	"fmt"
   359  	"strings"
   360  )
   361  
   362  type Type int
   363  
   364  const (
   365  	Simple Type = iota
   366  	Reverse
   367  	end
   368  )
   369  
   370  var echoers = []Echoer{
   371  	Simple:  &simpleEchoer{},
   372  	Reverse: &reverseEchoer{},
   373  }
   374  
   375  func NewEchoer(typ Type) Echoer {
   376  	return echoers[typ]
   377  }
   378  
   379  func TypeFrom(typ string) (Type, error) {
   380  	for curr := Simple; curr < end; curr++ {
   381  		if strings.ToLower(typ) == strings.ToLower(curr.String()) {
   382  			return curr, nil
   383  		}
   384  	}
   385  	return end, fmt.Errorf("unrecognized type: %s", typ)
   386  }
   387  
   388  type simpleEchoer struct{}
   389  
   390  func (e *simpleEchoer) Echo(in string) string {
   391  	return in
   392  }
   393  
   394  type reverseEchoer struct{}
   395  
   396  func (e *reverseEchoer) Echo(in string) string {
   397  	out := make([]byte, len(in))
   398  	for i := 0; i < len(out); i++ {
   399  		out[i] = in[len(in)-1-i]
   400  	}
   401  	return string(out)
   402  }' > echo/echo.go
   403  ```
   404  
   405  With this setup, `./godelw generate` can be called on a project to invoke all of its `generate` tasks.
   406  
   407  We will now attempt to commit these changes. If you have followed the tutorial up to this point, the git hook that
   408  enforces formatting for files will reject the commit:
   409  
   410  ```
   411  ➜ git add echo godel main.go
   412  ➜ git commit -m "Add support for echo types"
   413  Unformatted files exist -- run ./godelw format to format these files:
   414    /Volumes/git/go/src/github.com/nmiyake/echgo/echo/type_string.go
   415  ```
   416  
   417  This is because the generated Go file does not match the formatting enforced by `ptimports`. However, because this code
   418  is generated, we do not want to modify it after the fact. In general, we want to simply exclude generated code from all
   419  gödel tasks -- we don't want to add license headers to it, format it, run linting checks on it, etc. We will update the
   420  `godel/config/exclude.yml` to reflect this and specify that the file should be ignored:
   421  
   422  ```
   423  ➜ echo 'names:
   424    - "\\\..+"
   425    - "vendor"
   426  paths:
   427    - "godel"
   428    - "echo/type_string.go"' > godel/config/exclude.yml
   429  ```
   430  
   431  We will go through this file in more detail in the next portion of the tutorial, but for now it is sufficient to know
   432  that this excludes the `echo/type_string.go` file from checks and other tasks (we will make this more generic later).
   433  We can now commit the changes:
   434  
   435  ```
   436  ➜ git add echo godel main.go
   437  ➜ git commit -m "Add support for echo types"
   438  [master 4e528d0] Add support for echo types
   439   6 files changed, 72 insertions(+), 4 deletions(-)
   440   create mode 100644 echo/type_string.go
   441  ```
   442  
   443  Many Go projects would consider this sufficient -- they would document the requirement that developers must run
   444  `go get golang.org/x/tools/cmd/stringer` locally to in order to run "generate" and also ensure that this same action is
   445  performed in their CI environment. However, this introduces an external dependency on the ability to get and install
   446  `stringer`. Furthermore, the version of `stringer` is not defined/locked in anywhere -- the `go get` action will fetch
   447  whatever version is the latest at that time. This may not be an issue for tools that have a completely mature API, but
   448  if there are behavior changes between versions of the tools it can lead to the generation tasks creating inconsistent
   449  output.
   450  
   451  For that reason, if the `generate` task is running a Go program, we have found it helpful to vendor the entire program
   452  within the project and to run it using `go run` to ensure that the `generate` task does not have any external
   453  dependencies. We will use this construction for this project.
   454  
   455  Run the following to create a new directory for the generator and create a `vendor` directory within that directory:
   456  
   457  ```
   458  ➜ mkdir -p generator/vendor
   459  ```
   460  
   461  Putting the `vendor` directory within `generator` ensures that the code we vendor will only be accessible within the
   462  `generator` directory. We will now vendor the `stringer` program. In a real workflow, you would use the vendoring tool
   463  of your choice to do so. For the purposes of this tutorial, we will handle our vendoring manually by copying the code we
   464  need to the expected location:
   465  
   466  ```
   467  ➜ mkdir -p generator/vendor/golang.org/x/tools/cmd/stringer
   468  ➜ cp $(find $GOPATH/src/golang.org/x/tools/cmd/stringer -name '*.go' -not -name '*_test.go' -maxdepth 1 -type f) generator/vendor/golang.org/x/tools/cmd/stringer/
   469  ```
   470  
   471  Note: the `find` command above performs some pruning to copy only the buildable Go files that will be used -- if you
   472  don't care about pulling in extra unneeded files (such as tests and testdata files), you can run
   473  `cp -r $GOPATH/src/golang.org/x/tools/cmd/stringer/* generator/vendor/golang.org/x/tools/cmd/stringer/` instead.
   474  
   475  We will now define a generator that invokes this:
   476  
   477  ```
   478  ➜ echo '// Copyright (c) 2017 Author Name
   479  //
   480  // Licensed under the Apache License, Version 2.0 (the "License");
   481  // you may not use this file except in compliance with the License.
   482  // You may obtain a copy of the License at
   483  //
   484  //     http://www.apache.org/licenses/LICENSE-2.0
   485  //
   486  // Unless required by applicable law or agreed to in writing, software
   487  // distributed under the License is distributed on an "AS IS" BASIS,
   488  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   489  // See the License for the specific language governing permissions and
   490  // limitations under the License.
   491  
   492  //go:generate -command runstringer go run vendor/golang.org/x/tools/cmd/stringer/stringer.go vendor/golang.org/x/tools/cmd/stringer/importer18.go
   493  
   494  //go:generate runstringer -type=Type ../echo
   495  
   496  package generator' > generator/generate.go
   497  ```
   498  
   499  This generator now runs `stringer` directly from the vendor directory. If we had other packages on which we wanted to
   500  invoke `stringer`, we could simply update this file to do so.
   501  
   502  Update the previous code to remove its generation logic:
   503  
   504  ```
   505  ➜ echo '// Copyright (c) 2017 Author Name
   506  //
   507  // Licensed under the Apache License, Version 2.0 (the "License");
   508  // you may not use this file except in compliance with the License.
   509  // You may obtain a copy of the License at
   510  //
   511  //     http://www.apache.org/licenses/LICENSE-2.0
   512  //
   513  // Unless required by applicable law or agreed to in writing, software
   514  // distributed under the License is distributed on an "AS IS" BASIS,
   515  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   516  // See the License for the specific language governing permissions and
   517  // limitations under the License.
   518  
   519  package echo
   520  
   521  import (
   522  	"fmt"
   523  	"strings"
   524  )
   525  
   526  type Type int
   527  
   528  const (
   529  	Simple Type = iota
   530  	Reverse
   531  	end
   532  )
   533  
   534  var echoers = []Echoer{
   535  	Simple:  &simpleEchoer{},
   536  	Reverse: &reverseEchoer{},
   537  }
   538  
   539  func NewEchoer(typ Type) Echoer {
   540  	return echoers[typ]
   541  }
   542  
   543  func TypeFrom(typ string) (Type, error) {
   544  	for curr := Simple; curr < end; curr++ {
   545  		if strings.ToLower(typ) == strings.ToLower(curr.String()) {
   546  			return curr, nil
   547  		}
   548  	}
   549  	return end, fmt.Errorf("unrecognized type: %s", typ)
   550  }
   551  
   552  type simpleEchoer struct{}
   553  
   554  func (e *simpleEchoer) Echo(in string) string {
   555  	return in
   556  }
   557  
   558  type reverseEchoer struct{}
   559  
   560  func (e *reverseEchoer) Echo(in string) string {
   561  	out := make([]byte, len(in))
   562  	for i := 0; i < len(out); i++ {
   563  		out[i] = in[len(in)-1-i]
   564  	}
   565  	return string(out)
   566  }' > echo/echo.go
   567  ```
   568  
   569  Update the `generate.yml` configuration:
   570  
   571  ```
   572  ➜ echo 'generators:
   573    stringer:
   574      go-generate-dir: generator
   575      gen-paths:
   576        paths:
   577          - echo/type_string.go' > godel/config/generate.yml
   578  ```
   579  
   580  Run the `generate` task to verify that it still succeeds:
   581  
   582  ```
   583  ➜ ./godelw generate
   584  ```
   585  
   586  Run the `check` command to verify that the project is still valid:
   587  
   588  ```
   589  ➜ ./godelw check
   590  Running compiles...
   591  Running deadcode...
   592  Running errcheck...
   593  Running extimport...
   594  Running golint...
   595  Running govet...
   596  Running importalias...
   597  Running ineffassign...
   598  Running nobadfuncs...
   599  Running novendor...
   600  golang.org/x/tools
   601  Running outparamcheck...
   602  Running unconvert...
   603  Running varcheck...
   604  Checks produced output: [novendor]
   605  ```
   606  
   607  You can see that the `novendor` check now fails. This is because the `golang.org/x/tools` package is present in the
   608  `vendor` directory, but no packages in the project are importing its packages and the `novendor` check has identified
   609  it as an unused vendored project. However, in this instance we know that this is valid because we call the code directly
   610  from `go generate`. Update the `godel/config/check.yml` configuration to reflect this:
   611  
   612  ```
   613  ➜ echo 'checks:
   614    golint:
   615      filters:
   616        - value: "should have comment or be unexported"
   617        - value: "or a comment on this block"
   618    novendor:
   619      args:
   620        # ignore packages added for generation
   621        - "--ignore"
   622        - "./generator/vendor/golang.org/x/tools"' > godel/config/check.yml
   623  ```
   624  
   625  Run `check` again to verify that the checks now pass:
   626  
   627  ```
   628  ➜ ./godelw check
   629  Running compiles...
   630  Running deadcode...
   631  Running errcheck...
   632  Running extimport...
   633  Running golint...
   634  Running govet...
   635  Running importalias...
   636  Running ineffassign...
   637  Running nobadfuncs...
   638  Running novendor...
   639  Running outparamcheck...
   640  Running unconvert...
   641  Running varcheck...
   642  ```
   643  
   644  Commit these changes by running the following:
   645  
   646  ```
   647  ➜ git add echo generator godel
   648  ➜ git commit -m "Update generator code"
   649  [master 08752b2] Update generator code
   650   8 files changed, 693 insertions(+), 4 deletions(-)
   651   create mode 100644 generator/generate.go
   652   create mode 100644 generator/vendor/golang.org/x/tools/cmd/stringer/importer18.go
   653   create mode 100644 generator/vendor/golang.org/x/tools/cmd/stringer/importer19.go
   654   create mode 100644 generator/vendor/golang.org/x/tools/cmd/stringer/stringer.go
   655  ```
   656  
   657  Tutorial end state
   658  ------------------
   659  
   660  * `$GOPATH/src/github.com/nmiyake/echgo` exists and is the working directory
   661  * Project contains `godel` and `godelw`
   662  * Project contains `main.go`
   663  * Project contains `.gitignore` that ignores IDEA files
   664  * Project contains `echo/echo.go`, `echo/echo_test.go` and `echo/echoer.go`
   665  * `godel/config/dist.yml` is configured to build `echgo`
   666  * Project is tagged as 0.0.1
   667  * `godel/config/dist.yml` is configured to create distributions for `echgo`
   668  * Project is tagged as 0.0.2
   669  * Go files have license headers
   670  * `godel/config/generate.yml` is configured to generate string function
   671  
   672  ([Link](https://github.com/nmiyake/echgo/tree/08752b2ae998c14dd5abb789cebc8f5848f7cf4e))
   673  
   674  Tutorial next step
   675  ------------------
   676  
   677  [Define excludes](https://github.com/palantir/godel/wiki/Exclude)
   678  
   679  More
   680  ----
   681  
   682  ### Verification
   683  
   684  The `generate` task also supports a verification mode that ensures that the code that is generated by the
   685  `./godelw generate` task does not change the contents of the generated target paths. This is useful for use in CI to
   686  verify that developers properly ran `generate`.
   687  
   688  To demonstrate this, update `echo/echo.go` to add another echo type:
   689  
   690  ```
   691  ➜ echo '// Copyright (c) 2017 Author Name
   692  //
   693  // Licensed under the Apache License, Version 2.0 (the "License");
   694  // you may not use this file except in compliance with the License.
   695  // You may obtain a copy of the License at
   696  //
   697  //     http://www.apache.org/licenses/LICENSE-2.0
   698  //
   699  // Unless required by applicable law or agreed to in writing, software
   700  // distributed under the License is distributed on an "AS IS" BASIS,
   701  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   702  // See the License for the specific language governing permissions and
   703  // limitations under the License.
   704  
   705  //go:generate stringer -type=Type
   706  
   707  package echo
   708  
   709  import (
   710  	"fmt"
   711  	"math/rand"
   712  	"strings"
   713  )
   714  
   715  type Type int
   716  
   717  const (
   718  	Simple Type = iota
   719  	Reverse
   720  	Random
   721  	end
   722  )
   723  
   724  var echoers = []Echoer{
   725  	Simple:  &simpleEchoer{},
   726  	Reverse: &reverseEchoer{},
   727  	Random:  &randomEchoer{},
   728  }
   729  
   730  func NewEchoer(typ Type) Echoer {
   731  	return echoers[typ]
   732  }
   733  
   734  func TypeFrom(typ string) (Type, error) {
   735  	for curr := Simple; curr < end; curr++ {
   736  		if strings.ToLower(typ) == strings.ToLower(curr.String()) {
   737  			return curr, nil
   738  		}
   739  	}
   740  	return end, fmt.Errorf("unrecognized type: %s", typ)
   741  }
   742  
   743  type simpleEchoer struct{}
   744  
   745  func (e *simpleEchoer) Echo(in string) string {
   746  	return in
   747  }
   748  
   749  type reverseEchoer struct{}
   750  
   751  func (e *reverseEchoer) Echo(in string) string {
   752  	out := make([]byte, len(in))
   753  	for i := 0; i < len(out); i++ {
   754  		out[i] = in[len(in)-1-i]
   755  	}
   756  	return string(out)
   757  }
   758  
   759  type randomEchoer struct{}
   760  
   761  func (e *randomEchoer) Echo(in string) string {
   762  	inBytes := []byte(in)
   763  	out := make([]byte, len(in))
   764  	for i := 0; i < len(out); i++ {
   765  		randIdx := rand.Intn(len(inBytes))
   766  		out[i] = inBytes[randIdx]
   767  		inBytes = append(inBytes[:randIdx], inBytes[randIdx+1:]...)
   768  	}
   769  	return string(out)
   770  }' > echo/echo.go
   771  ```
   772  
   773  Now, run the generate task with the `--verify` flag:
   774  
   775  ```
   776  ➜ ./godelw generate --verify
   777  Generators produced output that differed from what already exists: [stringer]
   778    stringer:
   779      echo/type_string.go: previously had checksum d594017fce62ad2e2a8a98f9c7d519012d1df157c0f59088aaea2702a24f70e0, now has checksum 5b57686f254b93087a006aa6ab65753356f659015e7dd0c7e3053ea9fc2c024f
   780  ```
   781  
   782  As you can see, the task determined that the `generate` task changed a file that was specified in the `gen-paths`
   783  configuration for the task and prints a warning that specifies this.
   784  
   785  The `gen-paths` configuration consists of a `paths` and `names` list that specify regular expressions that match the
   786  paths or names of files created by the generator. When a `generate` task is run in `--verify` mode, the task determines
   787  all of the paths in the project that match the `gen-paths` configuration, computes the checksums of all of the files,
   788  runs the generation task, computes all of the paths that match after the task is run, and then compares both the file
   789  list and the checksums. If either the file list or checksums differ, the differences are echoed and the verification
   790  fails. Note that the `--verify` mode still runs the `generate` task -- because `go generate` itself doesn't have a
   791  notion of a dry run or verification, the only way to determine the effects of a `generate` task is to run it and to
   792  compare the state before and after the run. Although this same kind of check can be approximated by something like a
   793  `git status`, this configuration mechanism is more explicit/declarative and more robust.
   794  
   795  Revert the changes by running the following:
   796  
   797  ```
   798  ➜ git checkout -- echo
   799  ```
   800  
   801  ### Configuring multiple generate tasks
   802  
   803  The `generate` configuration supports configuring generate tasks organized in any manner. However, for projects that
   804  have multiple different kinds of `go generate` tasks, we have found that the following can be an effective way to
   805  organize the generators:
   806  
   807  * Have a `generators` directory at the top level of the project
   808  * Have a subdirectory for each generator type within the `generators` directory
   809  * The subdirectory contains a `generate.go` file with `go generate` directive and a `vendor` directory that vendors the
   810    program being called by the generator
   811  
   812  Here is an example of such a directory structure:
   813  
   814  ```
   815  ➜ tree generators
   816  generators
   817  ├── mocks
   818  │   ├── generate.go
   819  │   └── vendor
   820  │       └── ...
   821  └── stringer
   822      ├── generate.go
   823      └── vendor
   824          └── ...
   825  ```
   826  
   827  ### Example generators
   828  
   829  The following are examples of tasks that can be set up and configured to run as a `go generate` task:
   830  
   831  * [`protoc`](https://github.com/golang/protobuf) for generating protobufs
   832  * [`mockery`](https://github.com/vektra/mockery) for generating mocks for testing
   833  * [`stringer`](https://godoc.org/golang.org/x/tools/cmd/stringer) for generating String() functions for types
   834  * [`gopherjs`](https://github.com/gopherjs/gopherjs/) to generate Javascript from Go code
   835  * [`go-bindata`](https://github.com/jteeuwen/go-bindata) to generate Go code that embeds static resources
   836  * [`amalgomate`](https://github.com/palantir/amalgomate) for creating libraries out of Go `main` packages
   837  
   838  Generators that leverage all of these tools (as well as other custom/proprietary internal tools) have been successfully
   839  configured and integrated with gödel projects.
   840  
   841  Generators work most effectively if there is a guarantee that, for a given input, the generated outputs is always the
   842  same (this is required if output files are verified).