blob: 205aa893abfea28d83312b58cfa482200d5a74f0 [file] [log] [blame]
// 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"
"os/exec"
"time"
cf "github.com/google/wuffs/cmd/commonflags"
)
func doGenrelease(args []string) error {
flags := flag.FlagSet{}
cformatterFlag := flags.String("cformatter", cf.CformatterDefault, cf.CformatterUsage)
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 !cf.IsAlphaNumericIsh(*cformatterFlag) {
return fmt.Errorf("bad -cformatter flag value %q", *cformatterFlag)
}
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()
h := &genReleaseHelper{
seen: map[string]bool{},
revision: *revisionFlag,
version: v,
}
unformatted := bytes.NewBuffer(nil)
unformatted.WriteString("#ifndef WUFFS_INCLUDE_GUARD\n")
unformatted.WriteString("#define WUFFS_INCLUDE_GUARD\n\n")
// First, cat all of the headers together, filtering out duplicate
// WUFFS_INCLUDE_GUARD__FOO sections and overriding WUFFS_VERSION.
for _, filename := range args {
s, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
if i := bytes.Index(s, grCHeader); i >= 0 {
s = s[:i]
} else {
return fmt.Errorf("could not find %q", grCHeader)
}
if err := h.gen(unformatted, s); err != nil {
return fmt.Errorf("%v in %s", err, filename)
}
}
unformatted.WriteString("\n\n#endif // WUFFS_INCLUDE_GUARD\n\n")
unformatted.Write(grCHeader)
unformatted.WriteString("\n")
// Then, cat all of the implementations together, filtering out duplicate
// WUFFS_INCLUDE_GUARD__BASE_PRIVATE sections.
for _, filename := range args {
s, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
if i := bytes.Index(s, grCHeader); i >= 0 {
s = s[i+len(grCHeader):]
} else {
continue
}
if err := h.gen(unformatted, s); err != nil {
return fmt.Errorf("%v in %s", err, filename)
}
}
cmd := exec.Command(*cformatterFlag, "-style=Chromium")
cmd.Stdin = unformatted
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
var (
grCHeader = []byte("// !! C HEADER ENDS HERE.\n")
grNN = []byte("\n\n")
grVOverride = []byte("// !! Some code generation programs can override WUFFS_VERSION.\n")
grVString = []byte(`#define WUFFS_VERSION_STRING "0.0.0"`)
grDefine = []byte("#define WUFFS_INCLUDE_GUARD__")
grEndif = []byte("#endif // WUFFS_INCLUDE_GUARD__")
grIfndef = []byte("#ifndef WUFFS_INCLUDE_GUARD__")
)
type genReleaseHelper struct {
seen map[string]bool
revision string
version cf.Version
}
func (h *genReleaseHelper) gen(w *bytes.Buffer, s []byte) error {
skipping := 0
stack := []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 {
continue
}
if s[0] == '#' {
switch {
case bytes.HasPrefix(s, grIfndef):
pkg := string(s[len(grIfndef):])
for _, p := range stack {
if p == pkg {
return fmt.Errorf("unexpected %q", s)
}
}
if h.seen[pkg] {
skipping++
}
stack = append(stack, pkg)
continue
case bytes.HasPrefix(s, grDefine):
pkg := string(s[len(grDefine):])
if (len(stack) == 0) || (stack[len(stack)-1] != pkg) {
return fmt.Errorf("unexpected %q", s)
}
continue
case bytes.HasPrefix(s, grEndif):
pkg := string(s[len(grEndif):])
if (len(stack) == 0) || (stack[len(stack)-1] != pkg) {
return fmt.Errorf("unexpected %q", s)
}
if h.seen[pkg] {
skipping--
} else {
h.seen[pkg] = true
}
stack = stack[:len(stack)-1]
continue
}
}
if skipping > 0 {
continue
}
if s[0] == '/' {
switch {
case bytes.Equal(s, grVOverride):
var err error
remaining, err = h.genWuffsVersion(w, remaining)
if err != nil {
return err
}
continue
}
}
w.Write(s)
}
if len(stack) != 0 {
return fmt.Errorf("unmatched %q", string(grIfndef)+stack[len(stack)-1])
}
return nil
}
func (h *genReleaseHelper) genWuffsVersion(w *bytes.Buffer, s []byte) (remaining []byte, err error) {
cut := []byte(nil)
if i := bytes.Index(s, grNN); i >= 0 {
cut, s = s[:i], s[i+len(grNN):]
} else {
return nil, fmt.Errorf(`could not find "\n\n" near WUFFS_VERSION`)
}
if !bytes.HasSuffix(cut, grVString) {
return nil, fmt.Errorf("%q did not end with %q", cut, grVString)
}
fmt.Fprintf(w, "// WUFFS_VERSION was overridden by \"wuffs genrelease\" on %v UTC",
time.Now().UTC().Format("2006-01-02"))
if h.revision != "" {
fmt.Fprintf(w, ",\n// based on revision %s", h.revision)
}
fmt.Fprintf(w, `.
#define WUFFS_VERSION ((uint64_t)0x%016X)
#define WUFFS_VERSION_MAJOR ((uint64_t)0x%08X)
#define WUFFS_VERSION_MINOR ((uint64_t)0x%04X)
#define WUFFS_VERSION_PATCH ((uint64_t)0x%04X)
#define WUFFS_VERSION_EXTENSION %q
#define WUFFS_VERSION_STRING %q
`, h.version.Uint64(), h.version.Major, h.version.Minor, h.version.Patch,
h.version.Extension, h.version)
return s, nil
}