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