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).