• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 The Wuffs Authors.
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//    https://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
15package main
16
17import (
18	"bytes"
19	"flag"
20	"fmt"
21	"os"
22	"os/exec"
23	"path"
24	"path/filepath"
25	"strings"
26
27	"github.com/google/wuffs/lang/generate"
28	"github.com/google/wuffs/lang/parse"
29
30	cf "github.com/google/wuffs/cmd/commonflags"
31
32	a "github.com/google/wuffs/lang/ast"
33	t "github.com/google/wuffs/lang/token"
34)
35
36func doGen(wuffsRoot string, args []string) error    { return doGenGenlib(wuffsRoot, args, false) }
37func doGenlib(wuffsRoot string, args []string) error { return doGenGenlib(wuffsRoot, args, true) }
38
39func doGenGenlib(wuffsRoot string, args []string, genlib bool) error {
40	flags := flag.NewFlagSet("gen", flag.ExitOnError)
41	cformatterFlag := flags.String("cformatter", cf.CformatterDefault, cf.CformatterUsage)
42	genlinenumFlag := flags.Bool("genlinenum", cf.GenlinenumDefault, cf.GenlinenumUsage)
43	langsFlag := flags.String("langs", langsDefault, langsUsage)
44	skipgendepsFlag := flags.Bool("skipgendeps", skipgendepsDefault, skipgendepsUsage)
45
46	skipgenFlag := (*bool)(nil)
47	if genlib {
48		skipgenFlag = flags.Bool("skipgen", skipgenDefault, skipgenUsage)
49	}
50	versionFlag := (*string)(nil)
51	if !genlib {
52		versionFlag = flags.String("version", cf.VersionDefault, cf.VersionUsage)
53	}
54
55	if err := flags.Parse(args); err != nil {
56		return err
57	}
58	if !cf.IsAlphaNumericIsh(*cformatterFlag) {
59		return fmt.Errorf("bad -cformatter flag value %q", *cformatterFlag)
60	}
61	langs, err := parseLangs(*langsFlag)
62	if err != nil {
63		return err
64	}
65	v := cf.Version{}
66	if !genlib {
67		ok := false
68		v, ok = cf.ParseVersion(*versionFlag)
69		if !ok {
70			return fmt.Errorf("bad -version flag value %q", *versionFlag)
71		}
72	}
73	args = flags.Args()
74	if len(args) == 0 {
75		args = []string{"base", "std/..."}
76	}
77
78	h := genHelper{
79		wuffsRoot:   wuffsRoot,
80		langs:       langs,
81		cformatter:  *cformatterFlag,
82		genlinenum:  *genlinenumFlag,
83		skipgen:     genlib && *skipgenFlag,
84		skipgendeps: *skipgendepsFlag,
85	}
86
87	for _, arg := range args {
88		recursive := strings.HasSuffix(arg, "/...")
89		if recursive {
90			arg = arg[:len(arg)-4]
91		}
92		if arg == "" {
93			continue
94		}
95
96		if err := h.gen(arg, recursive); err != nil {
97			return err
98		}
99	}
100
101	if genlib {
102		return h.genlibAffected()
103	}
104	return genrelease(wuffsRoot, langs, v)
105}
106
107type genHelper struct {
108	wuffsRoot   string
109	langs       []string
110	cformatter  string
111	genlinenum  bool
112	skipgen     bool
113	skipgendeps bool
114
115	affected []string
116	seen     map[string]struct{}
117	tm       t.Map
118}
119
120func (h *genHelper) gen(dirname string, recursive bool) error {
121	for len(dirname) > 0 && dirname[len(dirname)-1] == '/' {
122		dirname = dirname[:len(dirname)-1]
123	}
124
125	if h.seen == nil {
126		h.seen = map[string]struct{}{}
127	} else if _, ok := h.seen[dirname]; ok {
128		return nil
129	}
130	h.seen[dirname] = struct{}{}
131
132	if dirname == "base" {
133		if err := h.genDir(dirname, nil); err != nil {
134			return err
135		}
136		h.affected = append(h.affected, dirname)
137		return nil
138	}
139
140	if !cf.IsValidUsePath(dirname) {
141		return fmt.Errorf("invalid package path %q", dirname)
142	}
143
144	qualFilenames, dirnames, err := listDir(
145		filepath.Join(h.wuffsRoot, filepath.FromSlash(dirname)), ".wuffs", recursive)
146	if err != nil {
147		return err
148	}
149	if len(qualFilenames) > 0 {
150		if err := h.genDir(dirname, qualFilenames); err != nil {
151			return err
152		}
153		h.affected = append(h.affected, dirname)
154	}
155	if len(dirnames) > 0 {
156		for _, d := range dirnames {
157			if err := h.gen(dirname+"/"+d, recursive); err != nil {
158				return err
159			}
160		}
161	}
162	return nil
163}
164
165func (h *genHelper) genDir(dirname string, qualFilenames []string) error {
166	// TODO: skip the generation if the output file already exists and its
167	// mtime is newer than all inputs and the wuffs-gen-foo command.
168
169	packageName := path.Base(dirname)
170	if !validName(packageName) {
171		return fmt.Errorf(`invalid package %q, not in [a-z0-9]+`, packageName)
172	}
173
174	if h.skipgen {
175		return nil
176	}
177	if !h.skipgendeps {
178		if err := h.genDirDependencies(qualFilenames); err != nil {
179			return err
180		}
181	}
182
183	for _, lang := range h.langs {
184		command := "wuffs-" + lang
185		cmdArgs := []string{"gen", "-package_name", packageName}
186		if (lang == "c") && (h.cformatter != cf.CformatterDefault) {
187			cmdArgs = append(cmdArgs, fmt.Sprintf("-cformatter=%s", h.cformatter))
188		}
189		if h.genlinenum != cf.GenlinenumDefault {
190			cmdArgs = append(cmdArgs, fmt.Sprintf("-genlinenum=%t", h.genlinenum))
191		}
192		cmdArgs = append(cmdArgs, qualFilenames...)
193		stdout := &bytes.Buffer{}
194
195		cmd := exec.Command(command, cmdArgs...)
196		cmd.Stdin = nil
197		cmd.Stdout = stdout
198		cmd.Stderr = os.Stderr
199		if err := cmd.Run(); err == nil {
200			// No-op.
201		} else if _, ok := err.(*exec.ExitError); ok {
202			return fmt.Errorf("%s: failed", command)
203		} else {
204			return err
205		}
206		out := stdout.Bytes()
207
208		flatDirname := fmt.Sprintf("wuffs-%s", strings.Replace(dirname, "/", "-", -1))
209		if err := h.genFile(flatDirname, lang, out); err != nil {
210			return err
211		}
212	}
213	if len(h.langs) > 0 && packageName != "base" {
214		if err := h.genWuffs(dirname, qualFilenames); err != nil {
215			return err
216		}
217	}
218	return nil
219}
220
221func (h *genHelper) genDirDependencies(qualifiedFilenames []string) error {
222	files, err := generate.ParseFiles(&h.tm, qualifiedFilenames, nil)
223	if err != nil {
224		return err
225	}
226	for _, f := range files {
227		for _, n := range f.TopLevelDecls() {
228			if n.Kind() != a.KUse {
229				continue
230			}
231			useDirname := h.tm.ByID(n.AsUse().Path())
232			useDirname, _ = t.Unescape(useDirname)
233			if err := h.gen(useDirname, false); err != nil {
234				return err
235			}
236		}
237	}
238	return h.gen("base", false)
239}
240
241func (h *genHelper) genFile(dirname string, lang string, out []byte) error {
242	return writeFile(
243		filepath.Join(h.wuffsRoot, "gen", lang, filepath.FromSlash(dirname)+"."+lang),
244		out,
245	)
246}
247
248func (h *genHelper) genWuffs(dirname string, qualifiedFilenames []string) error {
249	files, err := generate.ParseFiles(&h.tm, qualifiedFilenames, &parse.Options{
250		AllowDoubleUnderscoreNames: true,
251	})
252	if err != nil {
253		return err
254	}
255
256	out := &bytes.Buffer{}
257	fmt.Fprintf(out, "// Code generated by running \"wuffs gen\". DO NOT EDIT.\n\n")
258
259	for _, f := range files {
260		for _, n := range f.TopLevelDecls() {
261			switch n.Kind() {
262			case a.KConst:
263				n := n.AsConst()
264				if !n.Public() {
265					continue
266				}
267				fmt.Fprintf(out, "pub const %s %s = %v\n",
268					n.QID().Str(&h.tm), n.XType().Str(&h.tm), n.Value().Str(&h.tm))
269
270			case a.KFunc:
271				n := n.AsFunc()
272				if !n.Public() {
273					continue
274				}
275				if n.Receiver().IsZero() {
276					return fmt.Errorf("TODO: genWuffs for a free-standing function")
277				}
278				// TODO: look at n.Asserts().
279				fmt.Fprintf(out, "pub func %s.%s%v(", n.Receiver().Str(&h.tm), n.FuncName().Str(&h.tm), n.Effect())
280				for i, field := range n.In().Fields() {
281					field := field.AsField()
282					if i > 0 {
283						fmt.Fprintf(out, ", ")
284					}
285					// TODO: what happens if the XType is from another package?
286					// Similarly for the out-param.
287					fmt.Fprintf(out, "%s %s", field.Name().Str(&h.tm), field.XType().Str(&h.tm))
288				}
289				fmt.Fprintf(out, ") ")
290				if o := n.Out(); o != nil {
291					fmt.Fprintf(out, "%s", o.Str(&h.tm))
292				}
293				fmt.Fprintf(out, " { }\n")
294
295			case a.KStatus:
296				n := n.AsStatus()
297				if !n.Public() {
298					continue
299				}
300				fmt.Fprintf(out, "pub status %s\n", n.QID().Str(&h.tm))
301
302			case a.KStruct:
303				n := n.AsStruct()
304				if !n.Public() {
305					continue
306				}
307				classy := ""
308				if n.Classy() {
309					classy = "?"
310				}
311				fmt.Fprintf(out, "pub struct %s%s()\n", n.QID().Str(&h.tm), classy)
312			}
313		}
314	}
315	return h.genFile(dirname, "wuffs", out.Bytes())
316}
317
318func (h *genHelper) genlibAffected() error {
319	for _, lang := range h.langs {
320		command := "wuffs-" + lang
321		args := []string{"genlib"}
322		args = append(args, "-dstdir", filepath.Join(h.wuffsRoot, "gen", "lib", lang))
323		args = append(args, "-srcdir", filepath.Join(h.wuffsRoot, "gen", lang))
324		args = append(args, h.affected...)
325		cmd := exec.Command(command, args...)
326		cmd.Stdout = os.Stdout
327		cmd.Stderr = os.Stderr
328		if err := cmd.Run(); err != nil {
329			return err
330		}
331	}
332	return nil
333}
334