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