| // Copyright 2018 The Wuffs 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 ( |
| "bytes" |
| "flag" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "sort" |
| "strings" |
| |
| "github.com/google/wuffs/internal/cgen/data" |
| |
| cf "github.com/google/wuffs/cmd/commonflags" |
| ) |
| |
| func doGenrelease(args []string) error { |
| flags := flag.FlagSet{} |
| commitDateFlag := flags.String("commitdate", "", "git commit date the release was built from") |
| gitRevListCountFlag := flags.Int("gitrevlistcount", 0, `git "rev-list --count" that the release was built from`) |
| revisionFlag := flags.String("revision", "", "git revision the release was built from") |
| versionFlag := flags.String("version", cf.VersionDefault, cf.VersionUsage) |
| |
| if err := flags.Parse(args); err != nil { |
| return err |
| } |
| if (*gitRevListCountFlag < 0) || (0x7FFFFFFF < *gitRevListCountFlag) { |
| return fmt.Errorf("bad -gitrevlistcount flag value %d", *gitRevListCountFlag) |
| } |
| if !cf.IsAlphaNumericIsh(*commitDateFlag) { |
| return fmt.Errorf("bad -commitdate flag value %q", *commitDateFlag) |
| } |
| if !cf.IsAlphaNumericIsh(*revisionFlag) { |
| return fmt.Errorf("bad -revision flag value %q", *revisionFlag) |
| } |
| v, ok := cf.ParseVersion(*versionFlag) |
| if !ok { |
| return fmt.Errorf("bad -version flag value %q", *versionFlag) |
| } |
| args = flags.Args() |
| |
| // Calculate the base directory. |
| baseDir := "" |
| for _, filename := range args { |
| if filepath.Base(filename) != "wuffs-base.c" { |
| continue |
| } |
| baseDir = filepath.Dir(filename) |
| } |
| if baseDir == "" { |
| return fmt.Errorf("could not determine base directory") |
| } |
| baseDirSlash := baseDir + string(filepath.Separator) |
| |
| h := &genReleaseHelper{ |
| filesList: nil, |
| filesMap: map[string]parsedCFile{}, |
| seen: nil, |
| commitDate: *commitDateFlag, |
| gitRevListCount: *gitRevListCountFlag, |
| revision: *revisionFlag, |
| version: v, |
| } |
| |
| for _, filename := range args { |
| if !strings.HasPrefix(filename, baseDirSlash) { |
| return fmt.Errorf("filename %q is not under base directory %q", filename, baseDir) |
| } |
| relFilename := filename[len(baseDirSlash):] |
| |
| s, err := ioutil.ReadFile(filename) |
| if err != nil { |
| return err |
| } |
| |
| if err := h.parse(relFilename, s); err != nil { |
| return err |
| } |
| } |
| sort.Strings(h.filesList) |
| |
| out := bytes.NewBuffer(nil) |
| out.WriteString("#ifndef WUFFS_INCLUDE_GUARD\n") |
| out.WriteString("#define WUFFS_INCLUDE_GUARD\n\n") |
| out.WriteString(grSingleFileGuidance[1:]) // [1:] skips the initial '\n'. |
| out.WriteString(grPragmaPush[1:]) // [1:] skips the initial '\n'. |
| |
| h.seen = map[string]bool{} |
| for _, f := range h.filesList { |
| if err := h.gen(out, f, 0, 0); err != nil { |
| return err |
| } |
| } |
| |
| out.WriteString("#if defined(__cplusplus) && defined(WUFFS_BASE__HAVE_UNIQUE_PTR)\n\n") |
| out.WriteString(data.AuxBaseHh) |
| out.WriteString("\n") |
| for _, f := range data.AuxNonBaseHhFiles { |
| out.WriteString(f) |
| out.WriteString("\n") |
| } |
| out.WriteString("#endif // defined(__cplusplus) && defined(WUFFS_BASE__HAVE_UNIQUE_PTR)\n") |
| |
| out.Write(grImplStartsHere) |
| out.WriteString("\n") |
| |
| h.seen = map[string]bool{} |
| for _, f := range h.filesList { |
| if err := h.gen(out, f, 1, 0); err != nil { |
| return err |
| } |
| } |
| |
| out.WriteString("#if defined(__cplusplus) && defined(WUFFS_BASE__HAVE_UNIQUE_PTR)\n\n") |
| out.WriteString(data.AuxBaseCc) |
| out.WriteString("\n") |
| for _, f := range data.AuxNonBaseCcFiles { |
| out.WriteString(f) |
| out.WriteString("\n") |
| } |
| out.WriteString("#endif // defined(__cplusplus) && defined(WUFFS_BASE__HAVE_UNIQUE_PTR)\n\n") |
| |
| out.Write(grImplEndsHere) |
| out.WriteString(grPragmaPop) |
| out.WriteString("#endif // WUFFS_INCLUDE_GUARD\n") |
| |
| os.Stdout.Write(out.Bytes()) |
| return nil |
| } |
| |
| var ( |
| grImplStartsHere = []byte("\n// ‼ WUFFS C HEADER ENDS HERE.\n#ifdef WUFFS_IMPLEMENTATION\n") |
| grImplEndsHere = []byte("#endif // WUFFS_IMPLEMENTATION\n") |
| grIncludeQuote = []byte("#include \"") |
| grNN = []byte("\n\n") |
| grVOverride = []byte("// ¡ Some code generation programs can override WUFFS_VERSION.\n") |
| grVEnd = []byte(`#define WUFFS_VERSION_STRING "0.0.0+0.00000000"`) |
| grWmrAbove = []byte("// ¡ WUFFS MONOLITHIC RELEASE DISCARDS EVERYTHING ABOVE.\n") |
| grWmrBelow = []byte("// ¡ WUFFS MONOLITHIC RELEASE DISCARDS EVERYTHING BELOW.\n") |
| ) |
| |
| const grSingleFileGuidance = ` |
| // Wuffs ships as a "single file C library" or "header file library" as per |
| // https://github.com/nothings/stb/blob/master/docs/stb_howto.txt |
| // |
| // To use that single file as a "foo.c"-like implementation, instead of a |
| // "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or |
| // compiling it. |
| |
| ` |
| |
| const grPragmaPush = ` |
| // Wuffs' C code is generated automatically, not hand-written. These warnings' |
| // costs outweigh the benefits. |
| #if defined(__GNUC__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wmissing-field-initializers" |
| #pragma GCC diagnostic ignored "-Wunreachable-code" |
| #pragma GCC diagnostic ignored "-Wunused-function" |
| #pragma GCC diagnostic ignored "-Wunused-parameter" |
| #if defined(__cplusplus) |
| #pragma GCC diagnostic ignored "-Wold-style-cast" |
| #endif |
| #endif |
| |
| ` |
| |
| const grPragmaPop = ` |
| #if defined(__GNUC__) |
| #pragma GCC diagnostic pop |
| #endif |
| |
| ` |
| |
| type parsedCFile struct { |
| includes []string |
| // fragments[0] is the header, fragments[1] is the implementation. |
| fragments [2][]byte |
| } |
| |
| type genReleaseHelper struct { |
| filesList []string |
| filesMap map[string]parsedCFile |
| seen map[string]bool |
| commitDate string |
| gitRevListCount int |
| revision string |
| version cf.Version |
| } |
| |
| func (h *genReleaseHelper) parse(relFilename string, s []byte) error { |
| if _, ok := h.filesMap[relFilename]; ok { |
| return fmt.Errorf("duplicate %q", relFilename) |
| } |
| |
| f := parsedCFile{} |
| |
| if i := bytes.Index(s, grWmrAbove); i < 0 { |
| return fmt.Errorf("could not find %q in %s", grWmrAbove, relFilename) |
| } else { |
| f.includes = parseIncludes(s[:i]) |
| s = s[i+len(grWmrAbove):] |
| } |
| |
| if i := bytes.LastIndex(s, grWmrBelow); i < 0 { |
| return fmt.Errorf("could not find %q in %s", grWmrBelow, relFilename) |
| } else { |
| s = s[:i] |
| } |
| |
| if i := bytes.Index(s, grImplStartsHere); i < 0 { |
| return fmt.Errorf("could not find %q in %s", grImplStartsHere, relFilename) |
| } else { |
| f.fragments[0], s = bytes.TrimSpace(s[:i]), s[i+len(grImplStartsHere):] |
| } |
| |
| if i := bytes.LastIndex(s, grImplEndsHere); i < 0 { |
| return fmt.Errorf("could not find %q in %s", grImplEndsHere, relFilename) |
| } else { |
| f.fragments[1] = bytes.TrimSpace(s[:i]) |
| } |
| |
| if relFilename == "wuffs-base.c" && (h.version != cf.Version{}) { |
| if subs, err := h.substituteWuffsVersion(f.fragments[0]); err != nil { |
| return err |
| } else { |
| f.fragments[0] = bytes.TrimSpace(subs) |
| } |
| } |
| |
| h.filesList = append(h.filesList, relFilename) |
| h.filesMap[relFilename] = f |
| return nil |
| } |
| |
| func parseIncludes(s []byte) (ret []string) { |
| for remaining := []byte(nil); len(s) > 0; s, remaining = remaining, nil { |
| if i := bytes.IndexByte(s, '\n'); i >= 0 { |
| s, remaining = s[:i+1], s[i+1:] |
| } |
| if len(s) == 0 || s[0] != '#' || !bytes.HasPrefix(s, grIncludeQuote) { |
| continue |
| } |
| s = s[len(grIncludeQuote):] |
| if len(s) < 2 || s[len(s)-2] != '"' || s[len(s)-1] != '\n' { |
| continue |
| } |
| s = s[:len(s)-2] |
| |
| ret = append(ret, string(s)) |
| } |
| sort.Strings(ret) |
| return ret |
| } |
| |
| func (h *genReleaseHelper) gen(w *bytes.Buffer, relFilename string, which int, depth uint32) error { |
| if depth > 1024 { |
| return fmt.Errorf("genrelease recursion depth too large") |
| } |
| depth++ |
| |
| if strings.HasPrefix(relFilename, "./") { |
| relFilename = relFilename[2:] |
| } |
| |
| if h.seen[relFilename] { |
| return nil |
| } |
| f, ok := h.filesMap[relFilename] |
| if !ok { |
| return fmt.Errorf("cannot resolve %q", relFilename) |
| } |
| |
| // Process the files in #include-ee before #include-er order. |
| for _, inc := range f.includes { |
| if err := h.gen(w, inc, which, depth); err != nil { |
| return err |
| } |
| } |
| |
| w.Write(f.fragments[which]) |
| w.WriteString("\n\n") |
| h.seen[relFilename] = true |
| return nil |
| } |
| |
| func (h *genReleaseHelper) substituteWuffsVersion(s []byte) ([]byte, error) { |
| ret := []byte(nil) |
| if i := bytes.Index(s, grVOverride); i < 0 { |
| return nil, fmt.Errorf("could not find %q in %s", grVOverride, "wuffs-base.c") |
| } else { |
| ret = append(ret, s[:i]...) |
| s = s[i+len(grVOverride):] |
| } |
| |
| cut := []byte(nil) |
| if i := bytes.Index(s, grNN); i < 0 { |
| return nil, fmt.Errorf(`could not find "\n\n" near WUFFS_VERSION`) |
| } else { |
| cut, s = s[:i], s[i+len(grNN):] |
| } |
| |
| if !bytes.HasSuffix(cut, grVEnd) { |
| return nil, fmt.Errorf("%q did not end with %q", cut, grVEnd) |
| } |
| |
| w := bytes.NewBuffer(nil) |
| fmt.Fprintf(w, `// WUFFS_VERSION was overridden by "wuffs gen -version"`) |
| if h.revision != "" && h.commitDate != "" { |
| fmt.Fprintf(w, " based on revision\n// %s committed on %s", h.revision, h.commitDate) |
| } |
| |
| commitDate := "0" |
| if h.commitDate != "" { |
| commitDate = strings.Replace(h.commitDate, "-", "", -1) |
| } |
| |
| buildMetadata := "" |
| if h.gitRevListCount != 0 { |
| buildMetadata = fmt.Sprintf("+%d.%s", h.gitRevListCount, commitDate) |
| } |
| |
| fmt.Fprintf(w, `. |
| #define WUFFS_VERSION 0x%09X |
| #define WUFFS_VERSION_MAJOR %d |
| #define WUFFS_VERSION_MINOR %d |
| #define WUFFS_VERSION_PATCH %d |
| #define WUFFS_VERSION_PRE_RELEASE_LABEL %q |
| #define WUFFS_VERSION_BUILD_METADATA_COMMIT_COUNT %d |
| #define WUFFS_VERSION_BUILD_METADATA_COMMIT_DATE %s |
| #define WUFFS_VERSION_STRING %q |
| |
| `, h.version.Uint64(), h.version.Major, h.version.Minor, h.version.Patch, |
| h.version.Extension, h.gitRevListCount, commitDate, |
| h.version.String()+buildMetadata) |
| |
| ret = append(ret, w.Bytes()...) |
| ret = append(ret, s...) |
| return ret, nil |
| } |