Skip to content

Commit

Permalink
Add verification to code gen
Browse files Browse the repository at this point in the history
  • Loading branch information
lavalamp committed Dec 2, 2015
1 parent d524bd8 commit ad925dd
Show file tree
Hide file tree
Showing 16 changed files with 193 additions and 22 deletions.
7 changes: 7 additions & 0 deletions cmd/libs/go2idl/args/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,17 @@ type GeneratorArgs struct {

// Where to get copyright header text.
GoHeaderFilePath string

// If true, only verify, don't write anything.
VerifyOnly bool
}

func (g *GeneratorArgs) AddFlags(fs *pflag.FlagSet) {
fs.StringSliceVarP(&g.InputDirs, "input-dirs", "i", g.InputDirs, "Comma-separated list of import paths to get input types from.")
fs.StringVarP(&g.OutputBase, "output-base", "o", g.OutputBase, "Output base; defaults to $GOPATH/src/ or ./ if $GOPATH is not set.")
fs.StringVarP(&g.OutputPackagePath, "output-package", "p", g.OutputPackagePath, "Base package path.")
fs.StringVarP(&g.GoHeaderFilePath, "go-header-file", "h", g.GoHeaderFilePath, "File containing boilerplate header text. The string YEAR will be replaced with the current 4-digit year.")
fs.BoolVar(&g.VerifyOnly, "verify-only", g.VerifyOnly, "If true, only verify existing output, do not write anything.")
}

// LoadGoBoilerplate loads the boilerplate file passed to --go-header-file.
Expand All @@ -75,6 +79,8 @@ func (g *GeneratorArgs) LoadGoBoilerplate() ([]byte, error) {
return b, nil
}

// NewBuilder makes a new parser.Builder and populates it with the input
// directories.
func (g *GeneratorArgs) NewBuilder() (*parser.Builder, error) {
b := parser.New()
for _, d := range g.InputDirs {
Expand Down Expand Up @@ -112,6 +118,7 @@ func (g *GeneratorArgs) Execute(nameSystems namer.NameSystems, defaultSystem str
return fmt.Errorf("Failed making a context: %v", err)
}

c.Verify = g.VerifyOnly
packages := pkgs(c, g)
if err := c.ExecutePackages(g.OutputBase, packages); err != nil {
return fmt.Errorf("Failed executing generator: %v", err)
Expand Down
74 changes: 68 additions & 6 deletions cmd/libs/go2idl/generator/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"go/format"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
Expand All @@ -30,17 +31,29 @@ import (
"k8s.io/kubernetes/cmd/libs/go2idl/types"
)

func errs2strings(errors []error) []string {
strs := make([]string, len(errors))
for i := range errors {
strs[i] = errors[i].Error()
}
return strs
}

// ExecutePackages runs the generators for every package in 'packages'. 'outDir'
// is the base directory in which to place all the generated packages; it
// should be a physical path on disk, not an import path. e.g.:
// /path/to/home/path/to/gopath/src/
// Each package has its import path already, this will be appended to 'outDir'.
func (c *Context) ExecutePackages(outDir string, packages Packages) error {
var errors []error
for _, p := range packages {
if err := c.ExecutePackage(outDir, p); err != nil {
return err
errors = append(errors, err)
}
}
if len(errors) > 0 {
return fmt.Errorf("some packages had errors:\n%v\n", strings.Join(errs2strings(errors), "\n"))
}
return nil
}

Expand All @@ -61,15 +74,53 @@ func (ft golangFileType) AssembleFile(f *File, pathname string) error {
return et.Error()
}
if formatted, err := format.Source(b.Bytes()); err != nil {
log.Printf("Warning: unable to run gofmt on %q (%v).", pathname, err)
_, err = destFile.Write(b.Bytes())
err = fmt.Errorf("unable to run gofmt on %q (%v).", pathname, err)
// Write the file anyway, so they can see what's going wrong and fix the generator.
if _, err2 := destFile.Write(b.Bytes()); err2 != nil {
return err2
}
return err
} else {
_, err = destFile.Write(formatted)
return err
}
}

func (ft golangFileType) VerifyFile(f *File, pathname string) error {
log.Printf("Verifying file %q", pathname)
friendlyName := filepath.Join(f.PackageName, f.Name)
b := &bytes.Buffer{}
et := NewErrorTracker(b)
ft.assemble(et, f)
if et.Error() != nil {
return et.Error()
}
formatted, err := format.Source(b.Bytes())
if err != nil {
return fmt.Errorf("unable to gofmt the output for %q: %v", friendlyName, err)
}
existing, err := ioutil.ReadFile(pathname)
if err != nil {
return fmt.Errorf("unable to read file %q for comparison: %v", friendlyName, err)
}
if bytes.Compare(formatted, existing) == 0 {
return nil
}
// Be nice and find the first place where they differ
i := 0
for i < len(formatted) && i < len(existing) && formatted[i] == existing[i] {
i++
}
eDiff, fDiff := existing[i:], formatted[i:]
if len(eDiff) > 100 {
eDiff = eDiff[:100]
}
if len(fDiff) > 100 {
fDiff = fDiff[:100]
}
return fmt.Errorf("output for %q differs; first existing/expected diff: \n %q\n %q", friendlyName, string(eDiff), string(fDiff))
}

func (ft golangFileType) assemble(w io.Writer, f *File) {
w.Write(f.Header)
fmt.Fprintf(w, "package %v\n\n", f.PackageName)
Expand Down Expand Up @@ -149,7 +200,7 @@ func (c *Context) addNameSystems(namers namer.NameSystems) *Context {
// import path already, this will be appended to 'outDir'.
func (c *Context) ExecutePackage(outDir string, p Package) error {
path := filepath.Join(outDir, p.Path())
log.Printf("Executing package %v into %v", p.Name(), path)
log.Printf("Processing package %q, disk location %q", p.Name(), path)
// Filter out any types the *package* doesn't care about.
packageContext := c.filteredBy(p.Filter)
os.MkdirAll(path, 0755)
Expand Down Expand Up @@ -207,15 +258,26 @@ func (c *Context) ExecutePackage(outDir string, p Package) error {
}
}

var errors []error
for _, f := range files {
finalPath := filepath.Join(path, f.Name)
assembler, ok := c.FileTypes[f.FileType]
if !ok {
return fmt.Errorf("the file type %q registered for file %q does not exist in the context", f.FileType, f.Name)
}
if err := assembler.AssembleFile(f, filepath.Join(path, f.Name)); err != nil {
return err
var err error
if c.Verify {
err = assembler.VerifyFile(f, finalPath)
} else {
err = assembler.AssembleFile(f, finalPath)
}
if err != nil {
errors = append(errors, err)
}
}
if len(errors) > 0 {
return fmt.Errorf("errors in package %q:\n%v\n", p.Name(), strings.Join(errs2strings(errors), "\n"))
}
return nil
}

Expand Down
5 changes: 5 additions & 0 deletions cmd/libs/go2idl/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type File struct {

type FileType interface {
AssembleFile(f *File, path string) error
VerifyFile(f *File, path string) error
}

// Packages is a list of packages to generate.
Expand Down Expand Up @@ -158,6 +159,10 @@ type Context struct {
// A set of types this context can process. If this is empty or nil,
// the default "golang" filetype will be provided.
FileTypes map[string]FileType

// If true, Execute* calls will just verify that the existing output is
// correct. (You may set this after calling NewContext.)
Verify bool
}

// NewContext generates a context from the given builder, naming systems, and
Expand Down
6 changes: 1 addition & 5 deletions cmd/libs/go2idl/set-gen/generators/sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ package generators

import (
"io"
"os"
"strings"

"k8s.io/kubernetes/cmd/libs/go2idl/args"
"k8s.io/kubernetes/cmd/libs/go2idl/generator"
Expand Down Expand Up @@ -57,9 +55,7 @@ func Packages(_ *generator.Context, arguments *args.GeneratorArgs) generator.Pac
PackagePath: arguments.OutputPackagePath,
HeaderText: append(boilerplate, []byte(
`
// This file was autogenerated by the command:
// $ `+strings.Join(os.Args, " ")+`
// Do not edit it manually!
// This file was autogenerated by set-gen. Do not edit it manually!
`)...),
PackageDocumentation: []byte(
Expand Down
6 changes: 5 additions & 1 deletion cmd/libs/go2idl/set-gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ limitations under the License.
package main

import (
"os"

"k8s.io/kubernetes/cmd/libs/go2idl/args"
"k8s.io/kubernetes/cmd/libs/go2idl/set-gen/generators"

Expand All @@ -44,6 +46,8 @@ func main() {
generators.DefaultNameSystem(),
generators.Packages,
); err != nil {
glog.Fatalf("Error: %v", err)
glog.Errorf("Error: %v", err)
os.Exit(1)
}
glog.Info("Completed successfully.")
}
35 changes: 35 additions & 0 deletions hack/after-build/run-codegen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash

# Copyright 2015 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -o errexit
set -o nounset
set -o pipefail

KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../..
source "${KUBE_ROOT}/hack/lib/init.sh"

kube::golang::setup_env

setgen=$(kube::util::find-binary "set-gen")

# Please do not add any logic to this shell script. Add logic to the go code
# that generates the set-gen program.
#
# This can be called with one flag, --verify-only, so it works for both the
# update- and verify- scripts.
${setgen} "$@"

# You may add additional calls of code generators like set-gen below.
9 changes: 5 additions & 4 deletions hack/update-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ fi

BASH_TARGETS="codecgen
generated-conversions
generated-deep-copies
generated-docs
generated-swagger-docs
generated-deep-copies
generated-docs
generated-swagger-docs
swagger-spec
api-reference-docs"
api-reference-docs
codegen"


for t in $BASH_TARGETS
Expand Down
30 changes: 30 additions & 0 deletions hack/update-codegen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

# Copyright 2014 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -o errexit
set -o nounset
set -o pipefail

KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..

KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
source "${KUBE_ROOT}/hack/lib/init.sh"

kube::golang::setup_env

"${KUBE_ROOT}/hack/build-go.sh" cmd/libs/go2idl/set-gen

"${KUBE_ROOT}/hack/after-build/run-codegen.sh" "$@"
30 changes: 30 additions & 0 deletions hack/verify-codegen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

# Copyright 2014 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -o errexit
set -o nounset
set -o pipefail

KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..

KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
source "${KUBE_ROOT}/hack/lib/init.sh"

kube::golang::setup_env

"${KUBE_ROOT}/hack/build-go.sh" cmd/libs/go2idl/set-gen

"${KUBE_ROOT}/hack/after-build/run-codegen.sh" --verify-only
1 change: 1 addition & 0 deletions hack/verify-flags/known-flags.txt
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ update-period
upgrade-target
use-kubernetes-cluster-service
user-whitelist
verify-only
watch-cache
watch-only
whitelist-override-label
Expand Down
2 changes: 1 addition & 1 deletion pkg/util/sets/byte.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

// This file was autogenerated by the command:
// $ cmd/libs/go2idl/set-gen/set-gen
// $ ./set-gen
// Do not edit it manually!

package sets
Expand Down
2 changes: 1 addition & 1 deletion pkg/util/sets/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

// This file was autogenerated by the command:
// $ cmd/libs/go2idl/set-gen/set-gen
// $ ./set-gen
// Do not edit it manually!

// Package sets has auto-generated set types.
Expand Down
2 changes: 1 addition & 1 deletion pkg/util/sets/empty.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

// This file was autogenerated by the command:
// $ cmd/libs/go2idl/set-gen/set-gen
// $ ./set-gen
// Do not edit it manually!

package sets
Expand Down
2 changes: 1 addition & 1 deletion pkg/util/sets/int.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

// This file was autogenerated by the command:
// $ cmd/libs/go2idl/set-gen/set-gen
// $ ./set-gen
// Do not edit it manually!

package sets
Expand Down
2 changes: 1 addition & 1 deletion pkg/util/sets/int64.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

// This file was autogenerated by the command:
// $ cmd/libs/go2idl/set-gen/set-gen
// $ ./set-gen
// Do not edit it manually!

package sets
Expand Down
Loading

0 comments on commit ad925dd

Please sign in to comment.