1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// +build !windows
16
17// Package generator provides tools for generating clients.
18package generator
19
20import (
21	"context"
22	"fmt"
23	"io/ioutil"
24	"log"
25	"os"
26	"os/exec"
27	"path/filepath"
28	"strings"
29)
30
31// Config contains inputs needed to generate sources.
32type Config struct {
33	GoogleapisDir     string
34	GenprotoDir       string
35	GapicDir          string
36	ProtoDir          string
37	GapicToGenerate   string
38	OnlyGenerateGapic bool
39	LocalMode         bool
40}
41
42// Generate generates genproto and gapics.
43func Generate(ctx context.Context, conf *Config) ([]*ChangeInfo, error) {
44	if !conf.OnlyGenerateGapic {
45		protoGenerator := NewGenprotoGenerator(conf.GenprotoDir, conf.GoogleapisDir, conf.ProtoDir)
46		if err := protoGenerator.Regen(ctx); err != nil {
47			return nil, fmt.Errorf("error generating genproto (may need to check logs for more errors): %v", err)
48		}
49	}
50	gapicGenerator := NewGapicGenerator(conf.GoogleapisDir, conf.ProtoDir, conf.GapicDir, conf.GenprotoDir, conf.GapicToGenerate)
51	if err := gapicGenerator.Regen(ctx); err != nil {
52		return nil, fmt.Errorf("error generating gapics (may need to check logs for more errors): %v", err)
53	}
54
55	var changes []*ChangeInfo
56	if !conf.LocalMode {
57		var err error
58		changes, err = gatherChanges(conf.GoogleapisDir, conf.GenprotoDir)
59		if err != nil {
60			return nil, fmt.Errorf("error gathering commit info")
61		}
62		if err := recordGoogleapisHash(conf.GoogleapisDir, conf.GenprotoDir); err != nil {
63			return nil, err
64		}
65	}
66
67	return changes, nil
68}
69
70func gatherChanges(googleapisDir, genprotoDir string) ([]*ChangeInfo, error) {
71	// Get the last processed googleapis hash.
72	lastHash, err := ioutil.ReadFile(filepath.Join(genprotoDir, "regen.txt"))
73	if err != nil {
74		return nil, err
75	}
76	commits, err := CommitsSinceHash(googleapisDir, string(lastHash), false)
77	if err != nil {
78		return nil, err
79	}
80	changes, err := ParseChangeInfo(googleapisDir, commits)
81	if err != nil {
82		return nil, err
83	}
84
85	return changes, nil
86}
87
88// recordGoogleapisHash parses the latest commit in googleapis and records it to
89// regen.txt in go-genproto.
90func recordGoogleapisHash(googleapisDir, genprotoDir string) error {
91	commits, err := CommitsSinceHash(googleapisDir, "HEAD", true)
92	if err != nil {
93		return err
94	}
95	if len(commits) != 1 {
96		return fmt.Errorf("only expected one commit, got %d", len(commits))
97	}
98
99	f, err := os.OpenFile(filepath.Join(genprotoDir, "regen.txt"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
100	if err != nil {
101		return err
102	}
103	defer f.Close()
104	if _, err := f.WriteString(commits[0]); err != nil {
105		return err
106	}
107	return nil
108}
109
110// build attempts to build all packages recursively from the given directory.
111func build(dir string) error {
112	log.Println("building generated code")
113	c := command("go", "build", "./...")
114	c.Dir = dir
115	return c.Run()
116}
117
118// vet runs linters on all .go files recursively from the given directory.
119func vet(dir string) error {
120	log.Println("vetting generated code")
121	c := command("goimports", "-w", ".")
122	c.Dir = dir
123	if err := c.Run(); err != nil {
124		return err
125	}
126
127	c = command("gofmt", "-s", "-d", "-w", "-l", ".")
128	c.Dir = dir
129	return c.Run()
130}
131
132type cmdWrapper struct {
133	*exec.Cmd
134}
135
136// command wraps a exec.Command to add some logging about commands being run.
137// The commands stdout/stderr default to os.Stdout/os.Stderr respectfully.
138func command(name string, arg ...string) *cmdWrapper {
139	c := &cmdWrapper{exec.Command(name, arg...)}
140	c.Stdout = os.Stdout
141	c.Stderr = os.Stderr
142	return c
143}
144
145func (cw *cmdWrapper) Run() error {
146	log.Printf(">>>> %v <<<<", strings.Join(cw.Cmd.Args, " ")) // NOTE: we have some multi-line commands, make it clear where the command starts and ends
147	return cw.Cmd.Run()
148}
149