• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2018 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	"io/ioutil"
22	"os"
23	"os/exec"
24	"path/filepath"
25	"sort"
26	"strings"
27
28	cf "github.com/google/wuffs/cmd/commonflags"
29)
30
31func doGenrelease(args []string) error {
32	flags := flag.FlagSet{}
33	cformatterFlag := flags.String("cformatter", cf.CformatterDefault, cf.CformatterUsage)
34	commitDateFlag := flags.String("commitdate", "", "git commit date the release was built from")
35	gitRevListCountFlag := flags.Int("gitrevlistcount", 0, `git "rev-list --count" that the release was built from`)
36	revisionFlag := flags.String("revision", "", "git revision the release was built from")
37	versionFlag := flags.String("version", cf.VersionDefault, cf.VersionUsage)
38
39	if err := flags.Parse(args); err != nil {
40		return err
41	}
42	if !cf.IsAlphaNumericIsh(*cformatterFlag) {
43		return fmt.Errorf("bad -cformatter flag value %q", *cformatterFlag)
44	}
45	if (*gitRevListCountFlag < 0) || (0x7FFFFFFF < *gitRevListCountFlag) {
46		return fmt.Errorf("bad -gitrevlistcount flag value %d", *gitRevListCountFlag)
47	}
48	if !cf.IsAlphaNumericIsh(*commitDateFlag) {
49		return fmt.Errorf("bad -commitdate flag value %q", *commitDateFlag)
50	}
51	if !cf.IsAlphaNumericIsh(*revisionFlag) {
52		return fmt.Errorf("bad -revision flag value %q", *revisionFlag)
53	}
54	v, ok := cf.ParseVersion(*versionFlag)
55	if !ok {
56		return fmt.Errorf("bad -version flag value %q", *versionFlag)
57	}
58	args = flags.Args()
59
60	// Calculate the base directory.
61	baseDir := ""
62	for _, filename := range args {
63		if filepath.Base(filename) != "wuffs-base.c" {
64			continue
65		}
66		baseDir = filepath.Dir(filename)
67	}
68	if baseDir == "" {
69		return fmt.Errorf("could not determine base directory")
70	}
71	baseDirSlash := baseDir + string(filepath.Separator)
72
73	h := &genReleaseHelper{
74		filesList:       nil,
75		filesMap:        map[string]parsedCFile{},
76		seen:            nil,
77		commitDate:      *commitDateFlag,
78		gitRevListCount: *gitRevListCountFlag,
79		revision:        *revisionFlag,
80		version:         v,
81	}
82
83	for _, filename := range args {
84		if !strings.HasPrefix(filename, baseDirSlash) {
85			return fmt.Errorf("filename %q is not under base directory %q", filename, baseDir)
86		}
87		relFilename := filename[len(baseDirSlash):]
88
89		s, err := ioutil.ReadFile(filename)
90		if err != nil {
91			return err
92		}
93
94		if err := h.parse(relFilename, s); err != nil {
95			return err
96		}
97	}
98	sort.Strings(h.filesList)
99
100	unformatted := bytes.NewBuffer(nil)
101	unformatted.WriteString("#ifndef WUFFS_INCLUDE_GUARD\n")
102	unformatted.WriteString("#define WUFFS_INCLUDE_GUARD\n\n")
103	unformatted.WriteString(grSingleFileGuidance)
104
105	h.seen = map[string]bool{}
106	for _, f := range h.filesList {
107		if err := h.gen(unformatted, f, 0, 0); err != nil {
108			return err
109		}
110	}
111
112	unformatted.Write(grImplStartsHere)
113	unformatted.WriteString("\n")
114
115	h.seen = map[string]bool{}
116	for _, f := range h.filesList {
117		if err := h.gen(unformatted, f, 1, 0); err != nil {
118			return err
119		}
120	}
121
122	unformatted.WriteString("\n")
123	unformatted.Write(grImplEndsHere)
124	unformatted.WriteString("\n\n#endif  // WUFFS_INCLUDE_GUARD\n\n")
125
126	cmd := exec.Command(*cformatterFlag, "-style=Chromium")
127	cmd.Stdin = unformatted
128	cmd.Stdout = os.Stdout
129	cmd.Stderr = os.Stderr
130	return cmd.Run()
131}
132
133var (
134	grImplStartsHere = []byte("\n// WUFFS C HEADER ENDS HERE.\n#ifdef WUFFS_IMPLEMENTATION\n")
135	grImplEndsHere   = []byte("#endif  // WUFFS_IMPLEMENTATION\n")
136	grIncludeQuote   = []byte("#include \"")
137	grNN             = []byte("\n\n")
138	grVOverride      = []byte("// !! Some code generation programs can override WUFFS_VERSION.\n")
139	grVEnd           = []byte(`#define WUFFS_VERSION_STRING "0.0.0+0.00000000"`)
140	grWmrAbove       = []byte("// !! WUFFS MONOLITHIC RELEASE DISCARDS EVERYTHING ABOVE.\n")
141	grWmrBelow       = []byte("// !! WUFFS MONOLITHIC RELEASE DISCARDS EVERYTHING BELOW.\n")
142)
143
144const grSingleFileGuidance = `
145// Wuffs ships as a "single file C library" or "header file library" as per
146// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
147//
148// To use that single file as a "foo.c"-like implementation, instead of a
149// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
150// compiling it.
151
152`
153
154type parsedCFile struct {
155	includes []string
156	// fragments[0] is the header, fragments[1] is the implementation.
157	fragments [2][]byte
158}
159
160type genReleaseHelper struct {
161	filesList       []string
162	filesMap        map[string]parsedCFile
163	seen            map[string]bool
164	commitDate      string
165	gitRevListCount int
166	revision        string
167	version         cf.Version
168}
169
170func (h *genReleaseHelper) parse(relFilename string, s []byte) error {
171	if _, ok := h.filesMap[relFilename]; ok {
172		return fmt.Errorf("duplicate %q", relFilename)
173	}
174
175	f := parsedCFile{}
176
177	if i := bytes.Index(s, grWmrAbove); i < 0 {
178		return fmt.Errorf("could not find %q in %s", grWmrAbove, relFilename)
179	} else {
180		f.includes = parseIncludes(s[:i])
181		s = s[i+len(grWmrAbove):]
182	}
183
184	if i := bytes.LastIndex(s, grWmrBelow); i < 0 {
185		return fmt.Errorf("could not find %q in %s", grWmrBelow, relFilename)
186	} else {
187		s = s[:i]
188	}
189
190	if i := bytes.Index(s, grImplStartsHere); i < 0 {
191		return fmt.Errorf("could not find %q in %s", grImplStartsHere, relFilename)
192	} else {
193		f.fragments[0], s = s[:i], s[i+len(grImplStartsHere):]
194	}
195
196	if i := bytes.LastIndex(s, grImplEndsHere); i < 0 {
197		return fmt.Errorf("could not find %q in %s", grImplEndsHere, relFilename)
198	} else {
199		f.fragments[1] = s[:i]
200	}
201
202	if relFilename == "wuffs-base.c" && (h.version != cf.Version{}) {
203		if subs, err := h.substituteWuffsVersion(f.fragments[0]); err != nil {
204			return err
205		} else {
206			f.fragments[0] = subs
207		}
208	}
209
210	h.filesList = append(h.filesList, relFilename)
211	h.filesMap[relFilename] = f
212	return nil
213}
214
215func parseIncludes(s []byte) (ret []string) {
216	for remaining := []byte(nil); len(s) > 0; s, remaining = remaining, nil {
217		if i := bytes.IndexByte(s, '\n'); i >= 0 {
218			s, remaining = s[:i+1], s[i+1:]
219		}
220		if len(s) == 0 || s[0] != '#' || !bytes.HasPrefix(s, grIncludeQuote) {
221			continue
222		}
223		s = s[len(grIncludeQuote):]
224		if len(s) < 2 || s[len(s)-2] != '"' || s[len(s)-1] != '\n' {
225			continue
226		}
227		s = s[:len(s)-2]
228
229		ret = append(ret, string(s))
230	}
231	sort.Strings(ret)
232	return ret
233}
234
235func (h *genReleaseHelper) gen(w *bytes.Buffer, relFilename string, which int, depth uint32) error {
236	if depth > 1024 {
237		return fmt.Errorf("genrelease recursion depth too large")
238	}
239	depth++
240
241	if strings.HasPrefix(relFilename, "./") {
242		relFilename = relFilename[2:]
243	}
244
245	if h.seen[relFilename] {
246		return nil
247	}
248	f, ok := h.filesMap[relFilename]
249	if !ok {
250		return fmt.Errorf("cannot resolve %q", relFilename)
251	}
252
253	// Process the files in #include-ee before #include-er order.
254	for _, inc := range f.includes {
255		if err := h.gen(w, inc, which, depth); err != nil {
256			return err
257		}
258	}
259
260	w.Write(f.fragments[which])
261	h.seen[relFilename] = true
262	return nil
263}
264
265func (h *genReleaseHelper) substituteWuffsVersion(s []byte) ([]byte, error) {
266	ret := []byte(nil)
267	if i := bytes.Index(s, grVOverride); i < 0 {
268		return nil, fmt.Errorf("could not find %q in %s", grVOverride, "wuffs-base.c")
269	} else {
270		ret = append(ret, s[:i]...)
271		s = s[i+len(grVOverride):]
272	}
273
274	cut := []byte(nil)
275	if i := bytes.Index(s, grNN); i < 0 {
276		return nil, fmt.Errorf(`could not find "\n\n" near WUFFS_VERSION`)
277	} else {
278		cut, s = s[:i], s[i+len(grNN):]
279	}
280
281	if !bytes.HasSuffix(cut, grVEnd) {
282		return nil, fmt.Errorf("%q did not end with %q", cut, grVEnd)
283	}
284
285	w := bytes.NewBuffer(nil)
286	fmt.Fprintf(w, `// WUFFS_VERSION was overridden by "wuffs gen -version"`)
287	if h.revision != "" && h.commitDate != "" {
288		fmt.Fprintf(w, " based on revision\n// %s committed on %s", h.revision, h.commitDate)
289	}
290
291	commitDate := "0"
292	if h.commitDate != "" {
293		commitDate = strings.Replace(h.commitDate, "-", "", -1)
294	}
295
296	buildMetadata := ""
297	if h.gitRevListCount != 0 {
298		buildMetadata = fmt.Sprintf("+%d.%s", h.gitRevListCount, commitDate)
299	}
300
301	fmt.Fprintf(w, `.
302		#define WUFFS_VERSION ((uint64_t)0x%016X)
303		#define WUFFS_VERSION_MAJOR ((uint64_t)0x%08X)
304		#define WUFFS_VERSION_MINOR ((uint64_t)0x%04X)
305		#define WUFFS_VERSION_PATCH ((uint64_t)0x%04X)
306		#define WUFFS_VERSION_PRE_RELEASE_LABEL %q
307		#define WUFFS_VERSION_BUILD_METADATA_COMMIT_COUNT %d
308		#define WUFFS_VERSION_BUILD_METADATA_COMMIT_DATE %s
309		#define WUFFS_VERSION_STRING %q
310
311	`, h.version.Uint64(), h.version.Major, h.version.Minor, h.version.Patch,
312		h.version.Extension, h.gitRevListCount, commitDate,
313		h.version.String()+buildMetadata)
314
315	ret = append(ret, w.Bytes()...)
316	ret = append(ret, s...)
317	return ret, nil
318}
319