Repository: google/wire
Pull request #163 (2019-05-03 23:58:51)

wire: support using '-' tag to prevent filling struct fields
Updates #110 

1. When using "*", wire will omit the fields with "-" tag.
1. When specifying a prevented field, wire will give an error.


Patch:
diff --git a/.travis.yml b/.travis.yml
index 5bd17c4..34beb03 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,14 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-os:
-  - linux
-  - osx
-  - windows
-
 language: go
 go_import_path: github.com/google/wire
-go: "1.11.x"
 
 before_install:
   # The Bash that comes with OS X is ancient.
@@ -34,13 +28,8 @@ before_install:
 install:
   # Re-checkout files preserving line feeds. This prevents Windows builds from
   # converting \n to \r\n.
-  - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then
-      cd ../..;
-      mv $TRAVIS_REPO_SLUG _old;
-      git config --global core.autocrlf false;
-      git clone _old $TRAVIS_REPO_SLUG;
-      cd $TRAVIS_REPO_SLUG;
-    fi
+  - "git config --global core.autocrlf input"
+  - "git checkout -- ."
 
 script:
   - 'internal/runtests.sh'
@@ -48,3 +37,19 @@ script:
 env:
   global:
   - GO111MODULE=on
+  - GOPROXY=https://proxy.golang.org
+
+# When updating Go versions:
+# In addition to changing the "go:" versions below, edit the version
+# test in internal/runtests.sh.
+
+jobs:
+  include:
+    - go: "1.11.x"
+      os: linux
+    - go: "1.12.x"
+      os: linux
+    - go: "1.12.x"
+      os: osx
+    - go: "1.12.x"
+      os: windows
diff --git a/cmd/wire/main.go b/cmd/wire/main.go
index 35d9ea0..202be20 100644
--- a/cmd/wire/main.go
+++ b/cmd/wire/main.go
@@ -19,90 +19,129 @@ package main
 
 import (
 	"context"
-	"errors"
+	"flag"
 	"fmt"
 	"go/token"
 	"go/types"
 	"io/ioutil"
+	"log"
 	"os"
 	"reflect"
 	"sort"
 	"strconv"
 	"strings"
 
+	"github.com/google/subcommands"
 	"github.com/google/wire/internal/wire"
 	"github.com/pmezard/go-difflib/difflib"
 	"golang.org/x/tools/go/types/typeutil"
 )
 
-const usage = "usage: wire [gen|diff|show|check] [...]"
-
 func main() {
-	var (
-		exitCode = 0
-		err      error
-	)
-	switch {
-	case len(os.Args) == 2 && (os.Args[1] == "help" || os.Args[1] == "-h" || os.Args[1] == "-help" || os.Args[1] == "--help"):
-		fmt.Fprintln(os.Stderr, usage)
-		os.Exit(0)
-	case len(os.Args) == 2 && os.Args[1] == "show":
-		err = show(".")
-	case len(os.Args) > 2 && os.Args[1] == "show":
-		err = show(os.Args[2:]...)
-	case len(os.Args) == 2 && os.Args[1] == "check":
-		err = check(".")
-	case len(os.Args) > 2 && os.Args[1] == "check":
-		err = check(os.Args[2:]...)
-	case len(os.Args) == 2 && os.Args[1] == "diff":
-		exitCode, err = diff(".")
-	case len(os.Args) > 2 && os.Args[1] == "diff":
-		exitCode, err = diff(os.Args[2:]...)
-	case len(os.Args) == 2 && os.Args[1] == "gen":
-		err = generate(".")
-	case len(os.Args) > 2 && os.Args[1] == "gen":
-		err = generate(os.Args[2:]...)
-	// No explicit command given, assume "gen".
-	case len(os.Args) == 1:
-		err = generate(".")
-	case len(os.Args) > 1:
-		err = generate(os.Args[1:]...)
-	default:
-		fmt.Fprintln(os.Stderr, usage)
-		exitCode = 64
+	subcommands.Register(subcommands.CommandsCommand(), "")
+	subcommands.Register(subcommands.FlagsCommand(), "")
+	subcommands.Register(subcommands.HelpCommand(), "")
+	subcommands.Register(&checkCmd{}, "")
+	subcommands.Register(&diffCmd{}, "")
+	subcommands.Register(&genCmd{}, "")
+	subcommands.Register(&showCmd{}, "")
+	flag.Parse()
+
+	// Initialize the default logger to log to stderr.
+	log.SetFlags(0)
+	log.SetPrefix("wire: ")
+	log.SetOutput(os.Stderr)
+
+	// TODO(rvangent): Use subcommands's VisitCommands instead of hardcoded map,
+	// once there is a release that contains it:
+	// allCmds := map[string]bool{}
+	// subcommands.DefaultCommander.VisitCommands(func(_ *subcommands.CommandGroup, cmd subcommands.Command) { allCmds[cmd.Name()] = true })
+	allCmds := map[string]bool{
+		"commands": true, // builtin
+		"help":     true, // builtin
+		"flags":    true, // builtin
+		"check":    true,
+		"diff":     true,
+		"gen":      true,
+		"show":     true,
 	}
-	if err != nil {
-		fmt.Fprintln(os.Stderr, "wire:", err)
-		// Don't override more specific error codes from above
-		// (e.g., diff returns 2 on error).
-		if exitCode == 0 {
-			exitCode = 1
+	// Default to running the "gen" command.
+	if args := flag.Args(); len(args) == 0 || !allCmds[args[0]] {
+		genCmd := &genCmd{}
+		os.Exit(int(genCmd.Execute(context.Background(), flag.CommandLine)))
+	}
+	os.Exit(int(subcommands.Execute(context.Background())))
+}
+
+// packages returns the slice of packages to run wire over based on f.
+// It defaults to ".".
+func packages(f *flag.FlagSet) []string {
+	pkgs := f.Args()
+	if len(pkgs) == 0 {
+		pkgs = []string{"."}
+	}
+	return pkgs
+}
+
+// newGenerateOptions returns an initialized wire.GenerateOptions, possibly
+// with the Header option set.
+func newGenerateOptions(headerFile string) (*wire.GenerateOptions, error) {
+	opts := new(wire.GenerateOptions)
+	if headerFile != "" {
+		var err error
+		opts.Header, err = ioutil.ReadFile(headerFile)
+		if err != nil {
+			return nil, fmt.Errorf("failed to read header file %q: %v", headerFile, err)
 		}
 	}
-	os.Exit(exitCode)
+	return opts, nil
 }
 
-// generate runs the gen subcommand.
-//
-// Given one or more packages, gen will create the wire_gen.go file for each.
-func generate(pkgs ...string) error {
+type genCmd struct {
+	headerFile string
+}
+
+func (*genCmd) Name() string { return "gen" }
+func (*genCmd) Synopsis() string {
+	return "generate the wire_gen.go file for each package"
+}
+func (*genCmd) Usage() string {
+	return `gen [packages]
+
+  Given one or more packages, gen creates the wire_gen.go file for each.
+
+  If no packages are listed, it defaults to ".".
+`
+}
+func (cmd *genCmd) SetFlags(f *flag.FlagSet) {
+	f.StringVar(&cmd.headerFile, "header_file", "", "path to file to insert as a header in wire_gen.go")
+}
+
+func (cmd *genCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
 	wd, err := os.Getwd()
 	if err != nil {
-		return err
+		log.Println("failed to get working directory: ", err)
+		return subcommands.ExitFailure
 	}
-	outs, errs := wire.Generate(context.Background(), wd, os.Environ(), pkgs)
+	opts, err := newGenerateOptions(cmd.headerFile)
+	if err != nil {
+		log.Println(err)
+		return subcommands.ExitFailure
+	}
+	outs, errs := wire.Generate(ctx, wd, os.Environ(), packages(f), opts)
 	if len(errs) > 0 {
 		logErrors(errs)
-		return errors.New("generate failed")
+		log.Println("generate failed")
+		return subcommands.ExitFailure
 	}
 	if len(outs) == 0 {
-		return nil
+		return subcommands.ExitSuccess
 	}
 	success := true
 	for _, out := range outs {
 		if len(out.Errs) > 0 {
-			fmt.Fprintf(os.Stderr, "%s: generate failed\n", out.PkgPath)
 			logErrors(out.Errs)
+			log.Printf("%s: generate failed\n", out.PkgPath)
 			success = false
 		}
 		if len(out.Content) == 0 {
@@ -110,53 +149,72 @@ func generate(pkgs ...string) error {
 			continue
 		}
 		if err := out.Commit(); err == nil {
-			fmt.Fprintf(os.Stderr, "%s: wrote %s\n", out.PkgPath, out.OutputPath)
+			log.Printf("%s: wrote %s\n", out.PkgPath, out.OutputPath)
 		} else {
-			fmt.Fprintf(os.Stderr, "%s: failed to write %s: %v\n", out.PkgPath, out.OutputPath, err)
+			log.Printf("%s: failed to write %s: %v\n", out.PkgPath, out.OutputPath, err)
 			success = false
 		}
 	}
 	if !success {
-		return errors.New("at least one generate failure")
+		log.Println("at least one generate failure")
+		return subcommands.ExitFailure
 	}
-	return nil
+	return subcommands.ExitSuccess
 }
 
-// diff runs the diff subcommand.
-//
-// Given one or more packages, diff will generate the content for the
-// wire_gen.go file, and output the diff against the existing file.
-//
-// Similar to the diff command, it returns 0 if no diff, 1 if different, 2
-// plus an error if trouble.
-func diff(pkgs ...string) (int, error) {
-	errReturn := func(err error) (int, error) {
-		return 2, err
-	}
-	okReturn := func(hadDiff bool) (int, error) {
-		if hadDiff {
-			return 1, nil
-		}
-		return 0, nil
-	}
+type diffCmd struct {
+	headerFile string
+}
+
+func (*diffCmd) Name() string { return "diff" }
+func (*diffCmd) Synopsis() string {
+	return "output a diff between existing wire_gen.go files and what gen would generate"
+}
+func (*diffCmd) Usage() string {
+	return `diff [packages]
+
+  Given one or more packages, diff generates the content for their wire_gen.go
+  files and outputs the diff against the existing files.
+
+  If no packages are listed, it defaults to ".".
+
+  Similar to the diff command, it returns 0 if no diff, 1 if different, 2
+  plus an error if trouble.
+`
+}
+func (cmd *diffCmd) SetFlags(f *flag.FlagSet) {
+	f.StringVar(&cmd.headerFile, "header_file", "", "path to file to insert as a header in wire_gen.go")
+}
+func (cmd *diffCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
+	const (
+		errReturn  = subcommands.ExitStatus(2)
+		diffReturn = subcommands.ExitStatus(1)
+	)
 	wd, err := os.Getwd()
 	if err != nil {
-		return errReturn(err)
+		log.Println("failed to get working directory: ", err)
+		return errReturn
 	}
-	outs, errs := wire.Generate(context.Background(), wd, os.Environ(), pkgs)
+	opts, err := newGenerateOptions(cmd.headerFile)
+	if err != nil {
+		log.Println(err)
+		return subcommands.ExitFailure
+	}
+	outs, errs := wire.Generate(ctx, wd, os.Environ(), packages(f), opts)
 	if len(errs) > 0 {
 		logErrors(errs)
-		return errReturn(errors.New("generate failed"))
+		log.Println("generate failed")
+		return errReturn
 	}
 	if len(outs) == 0 {
-		return okReturn(false)
+		return subcommands.ExitSuccess
 	}
 	success := true
 	hadDiff := false
 	for _, out := range outs {
 		if len(out.Errs) > 0 {
-			fmt.Fprintf(os.Stderr, "%s: generate failed\n", out.PkgPath)
 			logErrors(out.Errs)
+			log.Printf("%s: generate failed\n", out.PkgPath)
 			success = false
 		}
 		if len(out.Content) == 0 {
@@ -170,32 +228,50 @@ func diff(pkgs ...string) (int, error) {
 			B: difflib.SplitLines(string(out.Content)),
 		}); err == nil {
 			if diff != "" {
-				fmt.Fprintf(os.Stdout, "%s: diff from %s:\n%s", out.PkgPath, out.OutputPath, diff)
+				// Print the actual diff to stdout, not stderr.
+				fmt.Printf("%s: diff from %s:\n%s\n", out.PkgPath, out.OutputPath, diff)
 				hadDiff = true
 			}
 		} else {
-			fmt.Fprintf(os.Stderr, "%s: failed to diff %s: %v\n", out.PkgPath, out.OutputPath, err)
+			log.Printf("%s: failed to diff %s: %v\n", out.PkgPath, out.OutputPath, err)
 			success = false
 		}
 	}
 	if !success {
-		return errReturn(errors.New("at least one generate failure"))
+		log.Println("at least one generate failure")
+		return errReturn
+	}
+	if hadDiff {
+		return diffReturn
 	}
-	return okReturn(hadDiff)
+	return subcommands.ExitSuccess
 }
 
-// show runs the show subcommand.
-//
-// Given one or more packages, show will find all the provider sets
-// declared as top-level variables and print what other provider sets it
-// imports and what outputs it can produce, given possible inputs.
-// It also lists any injector functions defined in the package.
-func show(pkgs ...string) error {
+type showCmd struct{}
+
+func (*showCmd) Name() string { return "show" }
+func (*showCmd) Synopsis() string {
+	return "describe all top-level provider sets"
+}
+func (*showCmd) Usage() string {
+	return `show [packages]
+
+  Given one or more packages, show finds all the provider sets declared as
+  top-level variables and prints what other provider sets they import and what
+  outputs they can produce, given possible inputs. It also lists any injector
+  functions defined in the package.
+
+  If no packages are listed, it defaults to ".".
+`
+}
+func (*showCmd) SetFlags(_ *flag.FlagSet) {}
+func (*showCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
 	wd, err := os.Getwd()
 	if err != nil {
-		return err
+		log.Println("failed to get working directory: ", err)
+		return subcommands.ExitFailure
 	}
-	info, errs := wire.Load(context.Background(), wd, os.Environ(), pkgs)
+	info, errs := wire.Load(ctx, wd, os.Environ(), packages(f))
 	if info != nil {
 		keys := make([]wire.ProviderSetID, 0, len(info.Sets))
 		for k := range info.Sets {
@@ -261,27 +337,41 @@ func show(pkgs ...string) error {
 	}
 	if len(errs) > 0 {
 		logErrors(errs)
-		return errors.New("error loading packages")
+		log.Println("error loading packages")
+		return subcommands.ExitFailure
 	}
-	return nil
+	return subcommands.ExitSuccess
 }
 
-// check runs the check subcommand.
-//
-// Given one or more packages, check will print any type-checking or
-// Wire errors found with top-level variable provider sets or injector
-// functions.
-func check(pkgs ...string) error {
+type checkCmd struct{}
+
+func (*checkCmd) Name() string { return "check" }
+func (*checkCmd) Synopsis() string {
+	return "print any Wire errors found"
+}
+func (*checkCmd) Usage() string {
+	return `check [packages]
+
+  Given one or more packages, check prints any type-checking or Wire errors
+  found with top-level variable provider sets or injector functions.
+
+  If no packages are listed, it defaults to ".".
+`
+}
+func (*checkCmd) SetFlags(_ *flag.FlagSet) {}
+func (*checkCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
 	wd, err := os.Getwd()
 	if err != nil {
-		return err
+		log.Println("failed to get working directory: ", err)
+		return subcommands.ExitFailure
 	}
-	_, errs := wire.Load(context.Background(), wd, os.Environ(), pkgs)
+	_, errs := wire.Load(ctx, wd, os.Environ(), packages(f))
 	if len(errs) > 0 {
 		logErrors(errs)
-		return errors.New("error loading packages")
+		log.Println("error loading packages")
+		return subcommands.ExitFailure
 	}
-	return nil
+	return subcommands.ExitSuccess
 }
 
 type outGroup struct {
@@ -503,6 +593,6 @@ func formatProviderSetName(importPath, varName string) string {
 
 func logErrors(errs []error) {
 	for _, err := range errs {
-		fmt.Fprintln(os.Stderr, strings.Replace(err.Error(), "\n", "\n\t", -1))
+		log.Println(strings.Replace(err.Error(), "\n", "\n\t", -1))
 	}
 }
diff --git a/go.mod b/go.mod
index 019cbce..5a3912f 100644
--- a/go.mod
+++ b/go.mod
@@ -2,9 +2,7 @@ module github.com/google/wire
 
 require (
 	github.com/google/go-cmp v0.2.0
+	github.com/google/subcommands v1.0.1
 	github.com/pmezard/go-difflib v1.0.0
-	golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd // indirect
-	golang.org/x/net v0.0.0-20190420063019-afa5a82059c6 // indirect
-	golang.org/x/sys v0.0.0-20190422165155-953cdadca894 // indirect
 	golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b
 )
diff --git a/go.sum b/go.sum
index 3a6a581..88ea58c 100644
--- a/go.sum
+++ b/go.sum
@@ -1,19 +1,12 @@
 github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/subcommands v1.0.1 h1:/eqq+otEXm5vhfBrbREPCSVQbvofip6kIz+mX5TUH7k=
+github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20181017214349-06f26fdaaa28 h1:vnbqcYKfOxPnXXUlBo7t+R4pVIh0wInyOSNxih1S9Dc=
-golang.org/x/tools v0.0.0-20181017214349-06f26fdaaa28/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190205201329-379209517ffe h1:wgpl8ZFbbyCTwUEyUPYafSzqRqUUGAuN9JSXU9iBjts=
-golang.org/x/tools v0.0.0-20190205201329-379209517ffe/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b h1:NVD8gBK33xpdqCaZVVtd6OFJp+3dxkXuz7+U7KaVN6s=
 golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
diff --git a/internal/alldeps b/internal/alldeps
index fc533ad..e2ede73 100644
--- a/internal/alldeps
+++ b/internal/alldeps
@@ -1,3 +1,4 @@
+github.com/google/subcommands
 github.com/google/wire
 github.com/pmezard/go-difflib
 golang.org/x/tools
diff --git a/internal/check_api_change.sh b/internal/check_api_change.sh
new file mode 100755
index 0000000..fdbf988
--- /dev/null
+++ b/internal/check_api_change.sh
@@ -0,0 +1,96 @@
+#!/usr/bin/env bash
+# Copyright 2019 The Wire Authors
+#
+# 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
+#
+#     https://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.
+
+# This script checks to see if there are any incompatible API changes on the
+# current branch relative to the upstream branch.
+# It fails if it finds any, unless there is a commit with BREAKING_CHANGE_OK
+# in the first line of the commit message.
+
+# This script expects:
+# a) to be run at the root of the repository
+# b) HEAD is pointing to a commit that merges between the pull request and the
+#    upstream branch (TRAVIS_BRANCH). This is what Travis does (see
+#    https://docs.travis-ci.com/user/pull-requests/ for details), but if you
+#    are testing this script manually, you may need to manually create a merge
+#    commit.
+
+set -euo pipefail
+
+UPSTREAM_BRANCH="${TRAVIS_BRANCH:-master}"
+echo "Checking for incompatible API changes relative to ${UPSTREAM_BRANCH}..."
+
+INSTALL_DIR="$(mktemp -d)"
+MASTER_CLONE_DIR="$(mktemp -d)"
+PKGINFO_BRANCH=$(mktemp)
+PKGINFO_MASTER=$(mktemp)
+
+function cleanup() {
+  rm -rf "$INSTALL_DIR"
+  rm -rf "$MASTER_CLONE_DIR"
+  rm -f "$PKGINFO_BRANCH"
+  rm -f "$PKGINFO_MASTER"
+}
+trap cleanup EXIT
+
+# Move to a temporary directory while installing apidiff to avoid changing
+# the local .mod file.
+( cd "$INSTALL_DIR" && exec go mod init unused )
+( cd "$INSTALL_DIR" && exec go install golang.org/x/exp/cmd/apidiff )
+
+git clone -b "$UPSTREAM_BRANCH" . "$MASTER_CLONE_DIR" &> /dev/null
+
+incompatible_change_pkgs=()
+PKGS=$(cd "$MASTER_CLONE_DIR"; go list ./... | grep -v test | grep -v internal)
+for pkg in $PKGS; do
+  echo "  Testing ${pkg}..."
+
+  # Compute export data for the current branch.
+  package_deleted=0
+  apidiff -w "$PKGINFO_BRANCH" "$pkg" || package_deleted=1
+  if [[ $package_deleted -eq 1 ]]; then
+    echo "  Package ${pkg} was deleted! Recording as an incompatible change.";
+    incompatible_change_pkgs+=(${pkg});
+    continue;
+  fi
+
+  # Compute export data for master@HEAD.
+  (cd "$MASTER_CLONE_DIR"; apidiff -w "$PKGINFO_MASTER" "$pkg")
+
+  # Print all changes for posterity.
+  apidiff "$PKGINFO_MASTER" "$PKGINFO_BRANCH"
+
+  # Note if there's an incompatible change.
+  ic=$(apidiff -incompatible "$PKGINFO_MASTER" "$PKGINFO_BRANCH")
+  if [ ! -z "$ic" ]; then
+    incompatible_change_pkgs+=("$pkg");
+  fi
+done
+
+if [ ${#incompatible_change_pkgs[@]} -eq 0 ]; then
+  # No incompatible changes, we are good.
+  echo "OK: No incompatible changes found."
+  exit 0;
+fi
+echo "Found breaking API change(s) in: ${incompatible_change_pkgs[*]}."
+
+# Found incompatible changes; see if they were declared as OK via a commit.
+if git cherry -v master | grep -q "BREAKING_CHANGE_OK"; then
+  echo "Allowing them due to a commit message with BREAKING_CHANGE_OK.";
+  exit 0;
+fi
+
+echo "FAIL. If this is expected and OK, you can pass this check by adding a commit with BREAKING_CHANGE_OK in the first line of the message."
+exit 1
+
diff --git a/internal/wire/analyze.go b/internal/wire/analyze.go
index e6c3d67..68fe6e5 100644
--- a/internal/wire/analyze.go
+++ b/internal/wire/analyze.go
@@ -276,7 +276,7 @@ func verifyArgsUsed(set *ProviderSet, used []*providerSetSrc) []error {
 			}
 		}
 		if !found {
-			errs = append(errs, fmt.Errorf("unused provider %q", p.Pkg.Name() + "." + p.Name))
+			errs = append(errs, fmt.Errorf("unused provider %q", p.Pkg.Name()+"."+p.Name))
 		}
 	}
 	for _, v := range set.Values {
diff --git a/internal/wire/parse.go b/internal/wire/parse.go
index cbe5b17..543418a 100644
--- a/internal/wire/parse.go
+++ b/internal/wire/parse.go
@@ -744,6 +744,8 @@ func funcOutput(sig *types.Signature) (outputSignature, error) {
 // It produces pointer and non-pointer variants via two values in Out.
 //
 // This is a copy of the old processStructProvider, which is deprecated now.
+// It will not support any new feature introduced after v0.2. Please use the new
+// wire.Struct syntax for those.
 func processStructLiteralProvider(fset *token.FileSet, typeName *types.TypeName) (*Provider, []error) {
 	out := typeName.Type()
 	st, ok := out.Underlying().(*types.Struct)
@@ -813,13 +815,15 @@ func processStructProvider(fset *token.FileSet, info *types.Info, call *ast.Call
 		Out:      []types.Type{structPtr.Elem(), structPtr},
 	}
 	if allFields(call) {
-		provider.Args = make([]ProviderInput, st.NumFields())
 		for i := 0; i < st.NumFields(); i++ {
+			if isPrevented(st, i) {
+				continue
+			}
 			f := st.Field(i)
-			provider.Args[i] = ProviderInput{
+			provider.Args = append(provider.Args, ProviderInput{
 				Type:      f.Type(),
 				FieldName: f.Name(),
-			}
+			})
 		}
 	} else {
 		provider.Args = make([]ProviderInput, len(call.Args)-1)
@@ -856,6 +860,14 @@ func allFields(call *ast.CallExpr) bool {
 	return strings.EqualFold(strconv.Quote("*"), b.Value)
 }
 
+// isPrevented checks whether field i is prevented by tag "-".
+// Since this is the only tag used by wire, we can do string comparison
+// without using reflect.
+// TODO(#179): parse the wire tag more robustly.
+func isPrevented(st *types.Struct, i int) bool {
+	return strings.Contains(st.Tag(i), `wire:"-"`)
+}
+
 // processBind creates an interface binding from a wire.Bind call.
 func processBind(fset *token.FileSet, info *types.Info, call *ast.CallExpr) (*IfaceBinding, error) {
 	// Assumes that call.Fun is wire.Bind.
@@ -1037,6 +1049,9 @@ func checkField(f ast.Expr, st *types.Struct) (*types.Var, error) {
 	}
 	for i := 0; i < st.NumFields(); i++ {
 		if strings.EqualFold(strconv.Quote(st.Field(i).Name()), b.Value) {
+			if isPrevented(st, i) {
+				return nil, fmt.Errorf("%s is prevented from injecting by wire", b.Value)
+			}
 			return st.Field(i), nil
 		}
 	}
diff --git a/internal/wire/wire.go b/internal/wire/wire.go
index 4aabf10..b585c8d 100644
--- a/internal/wire/wire.go
+++ b/internal/wire/wire.go
@@ -60,6 +60,12 @@ func (gen GenerateResult) Commit() error {
 	return ioutil.WriteFile(gen.OutputPath, gen.Content, 0666)
 }
 
+// GenerateOptions holds options for Generate.
+type GenerateOptions struct {
+	// Header will be inserted at the start of each generated file.
+	Header []byte
+}
+
 // Generate performs dependency injection for the packages that match the given
 // patterns, return a GenerateResult for each package. The package pattern is
 // defined by the underlying build system. For the go tool, this is described at
@@ -72,7 +78,10 @@ func (gen GenerateResult) Commit() error {
 // takes precedence.
 //
 // Generate may return one or more errors if it failed to load the packages.
-func Generate(ctx context.Context, wd string, env []string, patterns []string) ([]GenerateResult, []error) {
+func Generate(ctx context.Context, wd string, env []string, patterns []string, opts *GenerateOptions) ([]GenerateResult, []error) {
+	if opts == nil {
+		opts = &GenerateOptions{}
+	}
 	pkgs, errs := load(ctx, wd, env, patterns)
 	if len(errs) > 0 {
 		return nil, errs
@@ -94,6 +103,9 @@ func Generate(ctx context.Context, wd string, env []string, patterns []string) (
 		}
 		copyNonInjectorDecls(g, injectorFiles, pkg.TypesInfo)
 		goSrc := g.frame()
+		if len(opts.Header) > 0 {
+			goSrc = append(opts.Header, goSrc...)
+		}
 		fmtSrc, err := format.Source(goSrc)
 		if err != nil {
 			// This is likely a bug from a poorly generated source file.
diff --git a/wire.go b/wire.go
index e0c5121..ea2479c 100644
--- a/wire.go
+++ b/wire.go
@@ -36,9 +36,9 @@ type ProviderSet struct{}
 // Passing a function value to NewSet declares that the function's first
 // return value type will be provided by calling the function. The arguments
 // to the function will come from the providers for their types. As such, all
-// the parameters must be of non-identical types. The function may optionally
-// return an error as its last return value and a cleanup function as the
-// second return value. A cleanup function must be of type func() and is
+// the function's parameters must be of non-identical types. The function may
+// optionally return an error as its last return value and a cleanup function
+// as the second return value. A cleanup function must be of type func() and is
 // guaranteed to be called before the cleanup function of any of the
 // provider's inputs. If any provider returns an error, the injector function
 // will call all the appropriate cleanup functions and return the error from


Test patch:
diff --git a/internal/runtests.sh b/internal/runtests.sh
index e4e0db4..2e7915e 100755
--- a/internal/runtests.sh
+++ b/internal/runtests.sh
@@ -14,32 +14,68 @@
 # limitations under the License.
 
 # https://coderwall.com/p/fkfaqq/safer-bash-scripts-with-set-euxo-pipefail
-set -euxo pipefail
+set -euo pipefail
 
 if [[ $# -gt 0 ]]; then
   echo "usage: runtests.sh" 1>&2
   exit 64
 fi
 
-result=0
-
 # Run Go tests. Only do coverage for the Linux build
 # because it is slow, and codecov will only save the last one anyway.
-if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
-  go test -race -coverpkg=./... -coverprofile=coverage.out ./... || result=1
-  if [ -f coverage.out ]; then
+result=0
+if [[ "${TRAVIS_OS_NAME:-}" == "linux" ]]; then
+  echo "Running Go tests (with coverage)..."
+  go test -mod=readonly -race -coverpkg=./... -coverprofile=coverage.out ./... || result=1
+  if [ -f coverage.out ] && [ $result -eq 0 ]; then
     bash <(curl -s https://codecov.io/bash)
   fi
-  # Ensure that the code has no extra dependencies (including transitive
-  # dependencies) that we're not already aware of by comparing with
-  # ./internal/alldeps
-  #
-  # Whenever project dependencies change, rerun ./internal/listdeps.sh
-  ./internal/listdeps.sh | diff ./internal/alldeps - || {
-    echo "FAIL: dependencies changed; compare listdeps.sh output with alldeps" && result=1
-  }
 else
-  go test -race ./... || result=1
+  echo "Running Go tests..."
+  go test -mod=readonly -race ./... || result=1
+fi
+
+# No need to run other checks on OSs other than linux.
+# We default TRAVIS_OS_NAME to "linux" so that we don't abort here when run locally.
+if [[ "${TRAVIS_OS_NAME:-linux}" != "linux" ]]; then
+  exit $result
+fi
+
+echo
+echo "Ensuring .go files are formatted with gofmt -s..."
+mapfile -t go_files < <(find . -name '*.go' -type f | grep -v testdata)
+DIFF="$(gofmt -s -d "${go_files[@]}")"
+if [ -n "$DIFF" ]; then
+  echo "FAIL: please run gofmt -s and commit the result"
+  echo "$DIFF";
+  result=1;
+else
+  echo "OK"
+fi;
+
+
+# Ensure that the code has no extra dependencies (including transitive
+# dependencies) that we're not already aware of by comparing with
+# ./internal/alldeps
+#
+# Whenever project dependencies change, rerun ./internal/listdeps.sh
+if [[ $(go version) == *go1\.12* ]]; then
+  echo
+  echo "Ensuring that there are no dependencies not listed in ./internal/alldeps..."
+  (./internal/listdeps.sh | diff ./internal/alldeps - && echo "OK") || {
+    echo "FAIL: dependencies changed; run: internal/listdeps.sh > internal/alldeps"
+    # Module behavior may differ across versions.
+    echo "using go version 1.12."
+    result=1
+  }
+fi
+
+
+# For pull requests, check if there are undeclared incompatible API changes.
+# Skip this if we're already going to fail since it is expensive.
+if [[ ${result} -eq 0 ]] && [[ ! -z "${TRAVIS_BRANCH:-x}" ]] && [[ ! -z "${TRAVIS_PULL_REQUEST_SHA:-x}" ]]; then
+  echo
+  ./internal/check_api_change.sh || result=1;
 fi
 
 exit $result
diff --git a/internal/wire/testdata/Header/foo/foo.go b/internal/wire/testdata/Header/foo/foo.go
new file mode 100644
index 0000000..67bc8a3
--- /dev/null
+++ b/internal/wire/testdata/Header/foo/foo.go
@@ -0,0 +1,29 @@
+// Copyright 2019 The Wire Authors
+//
+// 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
+//
+//     https://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.
+
+package main
+
+import (
+	"fmt"
+)
+
+func main() {
+	fmt.Println(injectFoo())
+}
+
+type Foo int
+
+func provideFoo() Foo {
+	return 41
+}
diff --git a/internal/wire/testdata/Header/foo/wire.go b/internal/wire/testdata/Header/foo/wire.go
new file mode 100644
index 0000000..957f177
--- /dev/null
+++ b/internal/wire/testdata/Header/foo/wire.go
@@ -0,0 +1,26 @@
+// Copyright 2019 The Wire Authors
+//
+// 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
+//
+//     https://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.
+
+//+build wireinject
+
+package main
+
+import (
+	"github.com/google/wire"
+)
+
+func injectFoo() Foo {
+	wire.Build(provideFoo)
+	return Foo(0)
+}
diff --git a/internal/wire/testdata/Header/header b/internal/wire/testdata/Header/header
new file mode 100644
index 0000000..5ad7152
--- /dev/null
+++ b/internal/wire/testdata/Header/header
@@ -0,0 +1,2 @@
+// This is a sample header file.
+//
diff --git a/internal/wire/testdata/Header/pkg b/internal/wire/testdata/Header/pkg
new file mode 100644
index 0000000..f7a5c8c
--- /dev/null
+++ b/internal/wire/testdata/Header/pkg
@@ -0,0 +1,1 @@
+example.com/foo
diff --git a/internal/wire/testdata/Header/want/program_out.txt b/internal/wire/testdata/Header/want/program_out.txt
new file mode 100644
index 0000000..87523dd
--- /dev/null
+++ b/internal/wire/testdata/Header/want/program_out.txt
@@ -0,0 +1,1 @@
+41
diff --git a/internal/wire/testdata/Header/want/wire_gen.go b/internal/wire/testdata/Header/want/wire_gen.go
new file mode 100644
index 0000000..5acba8f
--- /dev/null
+++ b/internal/wire/testdata/Header/want/wire_gen.go
@@ -0,0 +1,15 @@
+// This is a sample header file.
+//
+// Code generated by Wire. DO NOT EDIT.
+
+//go:generate wire
+//+build !wireinject
+
+package main
+
+// Injectors from wire.go:
+
+func injectFoo() Foo {
+	foo := provideFoo()
+	return foo
+}
diff --git a/internal/wire/testdata/Struct/foo/foo.go b/internal/wire/testdata/Struct/foo/foo.go
index 9bb1e52..18d187a 100644
--- a/internal/wire/testdata/Struct/foo/foo.go
+++ b/internal/wire/testdata/Struct/foo/foo.go
@@ -16,6 +16,7 @@ package main
 
 import (
 	"fmt"
+	"sync"
 
 	"github.com/google/wire"
 )
@@ -31,6 +32,7 @@ type Foo int
 type Bar int
 
 type FooBar struct {
+	mu  sync.Mutex `wire:"-"`
 	Foo Foo
 	Bar Bar
 }
diff --git a/internal/wire/testdata/StructWithPreventTag/foo/foo.go b/internal/wire/testdata/StructWithPreventTag/foo/foo.go
new file mode 100644
index 0000000..045f282
--- /dev/null
+++ b/internal/wire/testdata/StructWithPreventTag/foo/foo.go
@@ -0,0 +1,48 @@
+// Copyright 2018 The Wire Authors
+//
+// 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
+//
+//     https://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.
+
+package main
+
+import (
+	"fmt"
+	"sync"
+
+	"github.com/google/wire"
+)
+
+func main() {
+	pfb := injectPartFooBar()
+	fmt.Println(pfb.Foo)
+}
+
+type Foo int
+
+type FooBar struct {
+	mu  sync.Mutex `wire:"-"`
+	Foo Foo
+}
+
+func provideFoo() Foo {
+	return 42
+}
+
+func provideMutex() sync.Mutex {
+	return sync.Mutex{}
+}
+
+var ProhibitSet = wire.NewSet(
+	wire.Struct(new(FooBar), "mu", "Foo"),
+	provideMutex,
+	provideFoo,
+)
diff --git a/internal/wire/testdata/StructWithPreventTag/foo/wire.go b/internal/wire/testdata/StructWithPreventTag/foo/wire.go
new file mode 100644
index 0000000..2f1ee55
--- /dev/null
+++ b/internal/wire/testdata/StructWithPreventTag/foo/wire.go
@@ -0,0 +1,26 @@
+// Copyright 2018 The Wire Authors
+//
+// 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
+//
+//     https://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.
+
+//+build wireinject
+
+package main
+
+import (
+	"github.com/google/wire"
+)
+
+func injectPartFooBar() FooBar {
+	wire.Build(ProhibitSet)
+	return FooBar{}
+}
diff --git a/internal/wire/testdata/StructWithPreventTag/pkg b/internal/wire/testdata/StructWithPreventTag/pkg
new file mode 100644
index 0000000..f7a5c8c
--- /dev/null
+++ b/internal/wire/testdata/StructWithPreventTag/pkg
@@ -0,0 +1,1 @@
+example.com/foo
diff --git a/internal/wire/testdata/StructWithPreventTag/want/wire_errs.txt b/internal/wire/testdata/StructWithPreventTag/want/wire_errs.txt
new file mode 100644
index 0000000..2482bc5
--- /dev/null
+++ b/internal/wire/testdata/StructWithPreventTag/want/wire_errs.txt
@@ -0,0 +1,1 @@
+example.com/foo/foo.go:x:y: "mu" is prevented from injecting by wire
\ No newline at end of file
diff --git a/internal/wire/testdata/UnusedProviders/foo/foo.go b/internal/wire/testdata/UnusedProviders/foo/foo.go
index 8c7b3d7..f1def3a 100644
--- a/internal/wire/testdata/UnusedProviders/foo/foo.go
+++ b/internal/wire/testdata/UnusedProviders/foo/foo.go
@@ -21,7 +21,7 @@ import (
 )
 
 func main() {
-	fmt.Println(injectBar())
+	fmt.Println(injectFooBar())
 }
 
 type Foo int
@@ -31,6 +31,12 @@ type UnusedInSet int
 type OneOfTwo int
 type TwoOfTwo int
 
+type FooBar struct {
+	MyFoo    *Foo
+	MyBar    Bar
+	MyUnused Unused
+}
+
 var (
 	unusedSet        = wire.NewSet(provideUnusedInSet)
 	partiallyUsedSet = wire.NewSet(provideOneOfTwo, provideTwoOfTwo)
diff --git a/internal/wire/testdata/UnusedProviders/foo/wire.go b/internal/wire/testdata/UnusedProviders/foo/wire.go
index 779207d..62bebd8 100644
--- a/internal/wire/testdata/UnusedProviders/foo/wire.go
+++ b/internal/wire/testdata/UnusedProviders/foo/wire.go
@@ -20,16 +20,17 @@ import (
 	"github.com/google/wire"
 )
 
-func injectBar() Bar {
+func injectFooBar() FooBar {
 	wire.Build(
 		provideFoo,                       // needed as input for provideBar
-		provideBar,                       // needed for Bar
+		provideBar,                       // needed for FooBar
 		partiallyUsedSet,                 // 1/2 providers in the set are needed
 		provideUnused,                    // not needed -> error
 		wire.Value("unused"),             // not needed -> error
 		unusedSet,                        // nothing in set is needed -> error
 		wire.Bind(new(Fooer), new(*Foo)), // binding to Fooer is not needed -> error
 		wire.FieldsOf(new(S), "Cfg"),     // S.Cfg not needed -> error
+		wire.Struct(new(FooBar), "MyFoo", "MyBar"), // needed for FooBar
 	)
-	return 0
+	return FooBar{}
 }
diff --git a/internal/wire/testdata/UnusedProviders/want/wire_errs.txt b/internal/wire/testdata/UnusedProviders/want/wire_errs.txt
index a34cab2..54dd582 100644
--- a/internal/wire/testdata/UnusedProviders/want/wire_errs.txt
+++ b/internal/wire/testdata/UnusedProviders/want/wire_errs.txt
@@ -1,9 +1,9 @@
-example.com/foo/wire.go:x:y: inject injectBar: unused provider set "unusedSet"
+example.com/foo/wire.go:x:y: inject injectFooBar: unused provider set "unusedSet"
 
-example.com/foo/wire.go:x:y: inject injectBar: unused provider "main.provideUnused"
+example.com/foo/wire.go:x:y: inject injectFooBar: unused provider "main.provideUnused"
 
-example.com/foo/wire.go:x:y: inject injectBar: unused value of type string
+example.com/foo/wire.go:x:y: inject injectFooBar: unused value of type string
 
-example.com/foo/wire.go:x:y: inject injectBar: unused interface binding to type example.com/foo.Fooer
+example.com/foo/wire.go:x:y: inject injectFooBar: unused interface binding to type example.com/foo.Fooer
 
-example.com/foo/wire.go:x:y: inject injectBar: unused field "example.com/foo.S".Cfg
\ No newline at end of file
+example.com/foo/wire.go:x:y: inject injectFooBar: unused field "example.com/foo.S".Cfg
\ No newline at end of file
diff --git a/internal/wire/wire_test.go b/internal/wire/wire_test.go
index 82dce4c..4717c74 100644
--- a/internal/wire/wire_test.go
+++ b/internal/wire/wire_test.go
@@ -90,7 +90,7 @@ func TestWire(t *testing.T) {
 				t.Fatal(err)
 			}
 			wd := filepath.Join(gopath, "src", "example.com")
-			gens, errs := Generate(ctx, wd, append(os.Environ(), "GOPATH="+gopath), []string{test.pkg})
+			gens, errs := Generate(ctx, wd, append(os.Environ(), "GOPATH="+gopath), []string{test.pkg}, &GenerateOptions{Header: test.header})
 			var gen GenerateResult
 			if len(gens) > 1 {
 				t.Fatalf("got %d generated files, want 0 or 1", len(gens))
@@ -428,6 +428,7 @@ func scrubLineColumn(s string) (replacement string, n int) {
 type testCase struct {
 	name                 string
 	pkg                  string
+	header               []byte
 	goFiles              map[string][]byte
 	wantProgramOutput    []byte
 	wantWireOutput       []byte
@@ -471,6 +472,7 @@ func loadTestCase(root string, wireGoSrc []byte) (*testCase, error) {
 	if err != nil {
 		return nil, fmt.Errorf("load test case %s: %v", name, err)
 	}
+	header, _ := ioutil.ReadFile(filepath.Join(root, "header"))
 	var wantProgramOutput []byte
 	var wantWireOutput []byte
 	wireErrb, err := ioutil.ReadFile(filepath.Join(root, "want", "wire_errs.txt"))
@@ -521,6 +523,7 @@ func loadTestCase(root string, wireGoSrc []byte) (*testCase, error) {
 	return &testCase{
 		name:                 name,
 		pkg:                  string(bytes.TrimSpace(pkg)),
+		header:               header,
 		goFiles:              goFiles,
 		wantWireOutput:       wantWireOutput,
 		wantProgramOutput:    wantProgramOutput,