Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Vanadium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 5 | // The following enables go generate to generate the doc.go file. |
Jiri Simsa | 32f76fb | 2015-04-07 15:39:23 -0700 | [diff] [blame] | 6 | //go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 7 | |
| 8 | package main |
| 9 | |
| 10 | import ( |
| 11 | "bytes" |
| 12 | "fmt" |
| 13 | "io/ioutil" |
| 14 | "log" |
| 15 | "os" |
| 16 | "path/filepath" |
| 17 | "strings" |
| 18 | |
| 19 | "v.io/v23/vdlroot/vdltool" |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 20 | "v.io/x/lib/cmdline" |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 21 | "v.io/x/lib/textutil" |
| 22 | "v.io/x/ref/lib/vdl/build" |
| 23 | "v.io/x/ref/lib/vdl/codegen/golang" |
| 24 | "v.io/x/ref/lib/vdl/codegen/java" |
| 25 | "v.io/x/ref/lib/vdl/codegen/javascript" |
| 26 | "v.io/x/ref/lib/vdl/compile" |
| 27 | "v.io/x/ref/lib/vdl/vdlutil" |
| 28 | ) |
| 29 | |
| 30 | func init() { |
| 31 | log.SetFlags(log.Lshortfile | log.Ltime | log.Lmicroseconds) |
| 32 | } |
| 33 | |
| 34 | func main() { |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 35 | cmdline.Main(cmdVDL) |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 36 | } |
| 37 | |
| 38 | func checkErrors(errs *vdlutil.Errors) error { |
| 39 | if errs.IsEmpty() { |
| 40 | return nil |
| 41 | } |
| 42 | return fmt.Errorf(` |
| 43 | %s (run with "vdl -v" for verbose logging or "vdl help" for help)`, errs) |
| 44 | } |
| 45 | |
| 46 | // runHelper returns a function that generates a sorted list of transitive |
| 47 | // targets, and calls the supplied run function. |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 48 | func runHelper(run func(targets []*build.Package, env *compile.Env)) cmdline.Runner { |
| 49 | return cmdline.RunnerFunc(func(_ *cmdline.Env, args []string) error { |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 50 | if flagVerbose { |
| 51 | vdlutil.SetVerbose() |
| 52 | } |
| 53 | if len(args) == 0 { |
| 54 | // If the user doesn't specify any targets, the cwd is implied. |
| 55 | args = append(args, ".") |
| 56 | } |
| 57 | env := compile.NewEnv(flagMaxErrors) |
| 58 | env.DisallowPathQualifiers() |
| 59 | mode := build.UnknownPathIsError |
| 60 | if flagIgnoreUnknown { |
| 61 | mode = build.UnknownPathIsIgnored |
| 62 | } |
| 63 | var opts build.Opts |
| 64 | opts.Extensions = strings.Split(flagExts, ",") |
| 65 | opts.VDLConfigName = flagVDLConfig |
| 66 | targets := build.TransitivePackages(args, mode, opts, env.Errors) |
| 67 | if err := checkErrors(env.Errors); err != nil { |
| 68 | return err |
| 69 | } |
| 70 | run(targets, env) |
| 71 | return checkErrors(env.Errors) |
Todd Wang | f1550cf | 2015-05-11 10:58:41 -0700 | [diff] [blame] | 72 | }) |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 73 | } |
| 74 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 75 | var topicPackages = cmdline.Topic{ |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 76 | Name: "packages", |
| 77 | Short: "Description of package lists", |
| 78 | Long: ` |
| 79 | Most vdl commands apply to a list of packages: |
| 80 | |
| 81 | vdl command <packages> |
| 82 | |
| 83 | <packages> are a list of packages to process, similar to the standard go tool. |
| 84 | In its simplest form each package is an import path; e.g. |
| 85 | "v.io/x/ref/lib/vdl" |
| 86 | |
| 87 | A package that is an absolute path or that begins with a . or .. element is |
| 88 | interpreted as a file system path, and denotes the package in that directory. |
| 89 | |
| 90 | A package is a pattern if it includes one or more "..." wildcards, each of which |
| 91 | can match any string, including the empty string and strings containing |
| 92 | slashes. Such a pattern expands to all packages found in VDLPATH with names |
| 93 | matching the pattern. As a special-case, x/... matches x as well as x's |
| 94 | subdirectories. |
| 95 | |
| 96 | The special-case "all" is a synonym for "...", and denotes all packages found |
| 97 | in VDLPATH. |
| 98 | |
| 99 | Import path elements and file names are not allowed to begin with "." or "_"; |
| 100 | such paths are ignored in wildcard matches, and return errors if specified |
| 101 | explicitly. |
| 102 | |
James Ring | c36fad6 | 2015-06-09 13:31:00 -0700 | [diff] [blame] | 103 | Note that whereas GOPATH requires *.go source files and packages to appear |
| 104 | under a "src" directory, VDLPATH requires *.vdl source files and packages to |
| 105 | appear directly under the VDLPATH directories. |
| 106 | |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 107 | Run "vdl help vdlpath" to see docs on VDLPATH. |
| 108 | Run "go help packages" to see the standard go package docs. |
| 109 | `, |
| 110 | } |
| 111 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 112 | var topicVdlPath = cmdline.Topic{ |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 113 | Name: "vdlpath", |
| 114 | Short: "Description of VDLPATH environment variable", |
| 115 | Long: ` |
| 116 | The VDLPATH environment variable is used to resolve import statements. |
| 117 | It must be set to compile and generate vdl packages. |
| 118 | |
James Ring | c36fad6 | 2015-06-09 13:31:00 -0700 | [diff] [blame] | 119 | The format is a colon-separated list of directories containing vdl source code. |
| 120 | These directories are searched recursively for VDL source files. The path |
| 121 | below the directory determines the import path. If VDLPATH specifies multiple |
| 122 | directories, imports are resolved by picking the first directory with a |
| 123 | matching import name. |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 124 | |
| 125 | An example: |
| 126 | |
| 127 | VDPATH=/home/user/vdlA:/home/user/vdlB |
| 128 | |
James Ring | c36fad6 | 2015-06-09 13:31:00 -0700 | [diff] [blame] | 129 | /home/user/vdlA |
| 130 | foo/ (import "foo" refers here) |
| 131 | foo1.vdl |
| 132 | /home/user/vdlB |
| 133 | foo/ (this package is ignored) |
| 134 | foo2.vdl |
| 135 | bar/ |
| 136 | baz/ (import "bar/baz" refers here) |
| 137 | baz.vdl |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 138 | `, |
| 139 | } |
| 140 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 141 | var topicVdlRoot = cmdline.Topic{ |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 142 | Name: "vdlroot", |
| 143 | Short: "Description of VDLROOT environment variable", |
| 144 | Long: ` |
| 145 | The VDLROOT environment variable is similar to VDLPATH, but instead of pointing |
| 146 | to multiple user source directories, it points at a single source directory |
| 147 | containing the standard vdl packages. |
| 148 | |
| 149 | Setting VDLROOT is optional. |
| 150 | |
Jiri Simsa | 32f76fb | 2015-04-07 15:39:23 -0700 | [diff] [blame] | 151 | If VDLROOT is empty, we try to construct it out of the V23_ROOT environment |
| 152 | variable. It is an error if both VDLROOT and V23_ROOT are empty. |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 153 | `, |
| 154 | } |
| 155 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 156 | var topicVdlConfig = cmdline.Topic{ |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 157 | Name: "vdl.config", |
| 158 | Short: "Description of vdl.config files", |
| 159 | Long: ` |
| 160 | Each vdl source package P may contain an optional file "vdl.config" within the P |
| 161 | directory. This file specifies additional configuration for the vdl tool. |
| 162 | |
| 163 | The format of this file is described by the vdltool.Config type in the "vdltool" |
| 164 | standard package, located at VDLROOT/vdltool/config.vdl. |
| 165 | |
| 166 | If the file does not exist, we use the zero value of vdl.Config. |
| 167 | `, |
| 168 | } |
| 169 | |
| 170 | const pkgArgName = "<packages>" |
| 171 | const pkgArgLong = ` |
| 172 | <packages> are a list of packages to process, similar to the standard go tool. |
| 173 | For more information, run "vdl help packages". |
| 174 | ` |
| 175 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 176 | var cmdCompile = &cmdline.Command{ |
Todd Wang | f1550cf | 2015-05-11 10:58:41 -0700 | [diff] [blame] | 177 | Runner: runHelper(runCompile), |
| 178 | Name: "compile", |
| 179 | Short: "Compile packages and dependencies, but don't generate code", |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 180 | Long: ` |
| 181 | Compile compiles packages and their transitive dependencies, but does not |
| 182 | generate code. This is useful to sanity-check that your VDL files are valid. |
| 183 | `, |
| 184 | ArgsName: pkgArgName, |
| 185 | ArgsLong: pkgArgLong, |
| 186 | } |
| 187 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 188 | var cmdGenerate = &cmdline.Command{ |
Todd Wang | f1550cf | 2015-05-11 10:58:41 -0700 | [diff] [blame] | 189 | Runner: runHelper(runGenerate), |
| 190 | Name: "generate", |
| 191 | Short: "Compile packages and dependencies, and generate code", |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 192 | Long: ` |
| 193 | Generate compiles packages and their transitive dependencies, and generates code |
| 194 | in the specified languages. |
| 195 | `, |
| 196 | ArgsName: pkgArgName, |
| 197 | ArgsLong: pkgArgLong, |
| 198 | } |
| 199 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 200 | var cmdAudit = &cmdline.Command{ |
Todd Wang | f1550cf | 2015-05-11 10:58:41 -0700 | [diff] [blame] | 201 | Runner: runHelper(runAudit), |
| 202 | Name: "audit", |
| 203 | Short: "Check if any packages are stale and need generation", |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 204 | Long: ` |
| 205 | Audit runs the same logic as generate, but doesn't write out generated files. |
| 206 | Returns a 0 exit code if all packages are up-to-date, otherwise returns a |
| 207 | non-0 exit code indicating some packages need generation. |
| 208 | `, |
| 209 | ArgsName: pkgArgName, |
| 210 | ArgsLong: pkgArgLong, |
| 211 | } |
| 212 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 213 | var cmdList = &cmdline.Command{ |
Todd Wang | f1550cf | 2015-05-11 10:58:41 -0700 | [diff] [blame] | 214 | Runner: runHelper(runList), |
| 215 | Name: "list", |
| 216 | Short: "List package and dependency info in transitive order", |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 217 | Long: ` |
| 218 | List returns information about packages and their transitive dependencies, in |
| 219 | transitive order. This is the same order the generate and compile commands use |
| 220 | for processing. If "vdl list A" is run and A depends on B, which depends on C, |
| 221 | the returned order will be C, B, A. If multiple packages are specified the |
| 222 | ordering is over all combined dependencies. |
| 223 | |
| 224 | Reminder: cyclic dependencies between packages are not allowed. Cyclic |
| 225 | dependencies between VDL files within the same package are also not allowed. |
| 226 | This is more strict than regular Go; it makes it easier to generate code for |
| 227 | other languages like C++. |
| 228 | `, |
| 229 | ArgsName: pkgArgName, |
| 230 | ArgsLong: pkgArgLong, |
| 231 | } |
| 232 | |
Bogdan Caprita | dc81f5f | 2015-03-18 10:08:03 -0700 | [diff] [blame] | 233 | var genLangAll = genLangs(vdltool.GenLanguageAll[:]) |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 234 | |
| 235 | type genLangs []vdltool.GenLanguage |
| 236 | |
| 237 | func (gls genLangs) String() string { |
| 238 | var ret string |
| 239 | for i, gl := range gls { |
| 240 | if i > 0 { |
| 241 | ret += "," |
| 242 | } |
| 243 | ret += gl.String() |
| 244 | } |
| 245 | return ret |
| 246 | } |
| 247 | |
| 248 | func (gls *genLangs) Set(value string) error { |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 249 | // If the flag is repeated on the cmdline it is overridden. Duplicates within |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 250 | // the comma separated list are ignored, and retain their original ordering. |
| 251 | *gls = genLangs{} |
| 252 | seen := make(map[vdltool.GenLanguage]bool) |
| 253 | for _, str := range strings.Split(value, ",") { |
| 254 | gl, err := vdltool.GenLanguageFromString(str) |
| 255 | if err != nil { |
| 256 | return err |
| 257 | } |
| 258 | if !seen[gl] { |
| 259 | seen[gl] = true |
| 260 | *gls = append(*gls, gl) |
| 261 | } |
| 262 | } |
| 263 | return nil |
| 264 | } |
| 265 | |
| 266 | // genOutDir has three modes: |
| 267 | // 1) If dir is non-empty, we use it as the out dir. |
| 268 | // 2) If rules is non-empty, we translate using the xlate rules. |
| 269 | // 3) If everything is empty, we generate in-place. |
| 270 | type genOutDir struct { |
| 271 | dir string |
| 272 | rules xlateRules |
| 273 | } |
| 274 | |
| 275 | // xlateSrcDst specifies a translation rule, where src must match the suffix of |
| 276 | // the path just before the package path, and dst is the replacement for src. |
| 277 | // If dst is the special string "SKIP" we'll skip generation of packages |
| 278 | // matching the src. |
| 279 | type xlateSrcDst struct { |
| 280 | src, dst string |
| 281 | } |
| 282 | |
| 283 | // xlateRules specifies a collection of translation rules. |
| 284 | type xlateRules []xlateSrcDst |
| 285 | |
| 286 | func (x *xlateRules) String() (ret string) { |
| 287 | for _, srcdst := range *x { |
| 288 | if len(ret) > 0 { |
| 289 | ret += "," |
| 290 | } |
| 291 | ret += srcdst.src + "->" + srcdst.dst |
| 292 | } |
| 293 | return |
| 294 | } |
| 295 | |
| 296 | func (x *xlateRules) Set(value string) error { |
| 297 | for _, rule := range strings.Split(value, ",") { |
| 298 | srcdst := strings.Split(rule, "->") |
| 299 | if len(srcdst) != 2 { |
| 300 | return fmt.Errorf("invalid out dir xlate rule %q (not src->dst format)", rule) |
| 301 | } |
| 302 | *x = append(*x, xlateSrcDst{srcdst[0], srcdst[1]}) |
| 303 | } |
| 304 | return nil |
| 305 | } |
| 306 | |
| 307 | func (x *genOutDir) String() string { |
| 308 | if x.dir != "" { |
| 309 | return x.dir |
| 310 | } |
| 311 | return x.rules.String() |
| 312 | } |
| 313 | |
| 314 | func (x *genOutDir) Set(value string) error { |
| 315 | if strings.Contains(value, "->") { |
| 316 | x.dir = "" |
| 317 | return x.rules.Set(value) |
| 318 | } |
| 319 | x.dir = value |
| 320 | return nil |
| 321 | } |
| 322 | |
| 323 | var ( |
| 324 | // Common flags for the tool itself, applicable to all commands. |
| 325 | flagVerbose bool |
| 326 | flagMaxErrors int |
| 327 | flagExts string |
| 328 | flagVDLConfig string |
| 329 | flagIgnoreUnknown bool |
| 330 | |
| 331 | // Options for each command. |
| 332 | optCompileStatus bool |
| 333 | optGenStatus bool |
| 334 | optGenGoOutDir = genOutDir{} |
| 335 | optGenJavaOutDir = genOutDir{ |
| 336 | rules: xlateRules{ |
Srdjan Petrovic | dde7ba0 | 2015-06-18 17:41:30 -0700 | [diff] [blame] | 337 | {"release/go/src", "release/java/lib/generated-src/vdl"}, |
| 338 | {"roadmap/go/src", "release/java/lib/generated-src/vdl"}, |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 339 | }, |
| 340 | } |
| 341 | optGenJavascriptOutDir = genOutDir{ |
| 342 | rules: xlateRules{ |
| 343 | {"release/go/src", "release/javascript/core/src"}, |
| 344 | {"roadmap/go/src", "release/javascript/core/src"}, |
| 345 | {"third_party/go/src", "SKIP"}, |
| 346 | {"tools/go/src", "SKIP"}, |
| 347 | // TODO(toddw): Skip vdlroot javascript generation for now. |
| 348 | {"release/go/src/v.io/v23/vdlroot", "SKIP"}, |
| 349 | }, |
| 350 | } |
| 351 | optGenJavaOutPkg = xlateRules{ |
| 352 | {"v.io", "io/v"}, |
| 353 | } |
| 354 | optPathToJSCore string |
Todd Wang | 786cf4c | 2015-04-24 19:07:16 -0700 | [diff] [blame] | 355 | // Default to just running the go lang; other langs need special setup. |
| 356 | optGenLangs = genLangs{vdltool.GenLanguageGo} |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 357 | ) |
| 358 | |
| 359 | // Root returns the root command for the VDL tool. |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 360 | var cmdVDL = &cmdline.Command{ |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 361 | Name: "vdl", |
Todd Wang | 6ed3b6c | 2015-04-08 14:37:04 -0700 | [diff] [blame] | 362 | Short: "manages Vanadium Definition Language source code", |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 363 | Long: ` |
Todd Wang | 6ed3b6c | 2015-04-08 14:37:04 -0700 | [diff] [blame] | 364 | Command vdl manages Vanadium Definition Language source code. It's similar to |
| 365 | the go tool used for managing Go source code. |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 366 | `, |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 367 | Children: []*cmdline.Command{cmdGenerate, cmdCompile, cmdAudit, cmdList}, |
| 368 | Topics: []cmdline.Topic{topicPackages, topicVdlPath, topicVdlRoot, topicVdlConfig}, |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 369 | } |
| 370 | |
| 371 | func init() { |
| 372 | // Common flags for the tool itself, applicable to all commands. |
| 373 | cmdVDL.Flags.BoolVar(&flagVerbose, "v", false, "Turn on verbose logging.") |
Suharsh Sivakumar | 6901a2e | 2015-04-02 11:39:19 -0700 | [diff] [blame] | 374 | cmdVDL.Flags.IntVar(&flagMaxErrors, "max-errors", -1, "Stop processing after this many errors, or -1 for unlimited.") |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 375 | cmdVDL.Flags.StringVar(&flagExts, "exts", ".vdl", "Comma-separated list of valid VDL file name extensions.") |
| 376 | cmdVDL.Flags.StringVar(&flagVDLConfig, "vdl.config", "vdl.config", "Basename of the optional per-package config file.") |
| 377 | cmdVDL.Flags.BoolVar(&flagIgnoreUnknown, "ignore_unknown", false, "Ignore unknown packages provided on the command line.") |
| 378 | |
| 379 | // Options for compile. |
| 380 | cmdCompile.Flags.BoolVar(&optCompileStatus, "status", true, "Show package names while we compile") |
| 381 | |
| 382 | // Options for generate. |
| 383 | cmdGenerate.Flags.Var(&optGenLangs, "lang", "Comma-separated list of languages to generate, currently supporting "+genLangAll.String()) |
| 384 | cmdGenerate.Flags.BoolVar(&optGenStatus, "status", true, "Show package names as they are updated") |
| 385 | // TODO(toddw): Move out_dir configuration into vdl.config, and provide a |
| 386 | // generic override mechanism for vdl.config. |
Suharsh Sivakumar | 6901a2e | 2015-04-02 11:39:19 -0700 | [diff] [blame] | 387 | cmdGenerate.Flags.Var(&optGenGoOutDir, "go-out-dir", ` |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 388 | Go output directory. There are three modes: |
| 389 | "" : Generate output in-place in the source tree |
| 390 | "dir" : Generate output rooted at dir |
| 391 | "src->dst[,s2->d2...]" : Generate output using translation rules |
| 392 | Assume your source tree is organized as follows: |
| 393 | VDLPATH=/home/vdl |
James Ring | c36fad6 | 2015-06-09 13:31:00 -0700 | [diff] [blame] | 394 | /home/vdl/test_base/base1.vdl |
| 395 | /home/vdl/test_base/base2.vdl |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 396 | Here's example output under the different modes: |
Suharsh Sivakumar | 6901a2e | 2015-04-02 11:39:19 -0700 | [diff] [blame] | 397 | --go-out-dir="" |
James Ring | c36fad6 | 2015-06-09 13:31:00 -0700 | [diff] [blame] | 398 | /home/vdl/test_base/base1.vdl.go |
| 399 | /home/vdl/test_base/base2.vdl.go |
Suharsh Sivakumar | 6901a2e | 2015-04-02 11:39:19 -0700 | [diff] [blame] | 400 | --go-out-dir="/tmp/foo" |
Suharsh Sivakumar | 8646ba6 | 2015-03-18 15:22:28 -0700 | [diff] [blame] | 401 | /tmp/foo/test_base/base1.vdl.go |
| 402 | /tmp/foo/test_base/base2.vdl.go |
James Ring | c36fad6 | 2015-06-09 13:31:00 -0700 | [diff] [blame] | 403 | --go-out-dir="vdl->foo/bar" |
| 404 | /home/foo/bar/test_base/base1.vdl.go |
| 405 | /home/foo/bar/test_base/base2.vdl.go |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 406 | When the src->dst form is used, src must match the suffix of the path just |
| 407 | before the package path, and dst is the replacement for src. Use commas to |
| 408 | separate multiple rules; the first rule matching src is used. The special dst |
| 409 | SKIP indicates matching packages are skipped.`) |
Suharsh Sivakumar | 6901a2e | 2015-04-02 11:39:19 -0700 | [diff] [blame] | 410 | cmdGenerate.Flags.Var(&optGenJavaOutDir, "java-out-dir", |
| 411 | "Same semantics as --go-out-dir but applies to java code generation.") |
| 412 | cmdGenerate.Flags.Var(&optGenJavaOutPkg, "java-out-pkg", ` |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 413 | Java output package translation rules. Must be of the form: |
| 414 | "src->dst[,s2->d2...]" |
| 415 | If a VDL package has a prefix src, the prefix will be replaced with dst. Use |
| 416 | commas to separate multiple rules; the first rule matching src is used, and if |
| 417 | there are no matching rules, the package remains unchanged. The special dst |
| 418 | SKIP indicates matching packages are skipped.`) |
Suharsh Sivakumar | 6901a2e | 2015-04-02 11:39:19 -0700 | [diff] [blame] | 419 | cmdGenerate.Flags.Var(&optGenJavascriptOutDir, "js-out-dir", |
| 420 | "Same semantics as --go-out-dir but applies to js code generation.") |
| 421 | cmdGenerate.Flags.StringVar(&optPathToJSCore, "js-relative-path-to-core", "", |
| 422 | "If set, this is the relative path from js-out-dir to the root of the JS core") |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 423 | |
| 424 | // Options for audit are identical to generate. |
| 425 | cmdAudit.Flags = cmdGenerate.Flags |
| 426 | } |
| 427 | |
| 428 | func runCompile(targets []*build.Package, env *compile.Env) { |
| 429 | for _, target := range targets { |
| 430 | pkg := build.BuildPackage(target, env) |
| 431 | if pkg != nil && optCompileStatus { |
| 432 | fmt.Println(pkg.Path) |
| 433 | } |
| 434 | } |
| 435 | } |
| 436 | |
| 437 | func runGenerate(targets []*build.Package, env *compile.Env) { |
| 438 | gen(false, targets, env) |
| 439 | } |
| 440 | |
| 441 | func runAudit(targets []*build.Package, env *compile.Env) { |
| 442 | if gen(true, targets, env) && env.Errors.IsEmpty() { |
| 443 | // Some packages are stale, and there were no errors; return an arbitrary |
| 444 | // non-0 exit code. Errors are handled in runHelper, as usual. |
| 445 | os.Exit(10) |
| 446 | } |
| 447 | } |
| 448 | |
| 449 | func shouldGenerate(config vdltool.Config, lang vdltool.GenLanguage) bool { |
| 450 | // If config.GenLanguages is empty, all languages are allowed to be generated. |
| 451 | _, ok := config.GenLanguages[lang] |
| 452 | return len(config.GenLanguages) == 0 || ok |
| 453 | } |
| 454 | |
| 455 | // gen generates the given targets with env. If audit is true, only checks |
| 456 | // whether any packages are stale; otherwise files will actually be written out. |
| 457 | // Returns true if any packages are stale. |
| 458 | func gen(audit bool, targets []*build.Package, env *compile.Env) bool { |
| 459 | anychanged := false |
| 460 | for _, target := range targets { |
| 461 | pkg := build.BuildPackage(target, env) |
| 462 | if pkg == nil { |
| 463 | // Stop at the first package that fails to compile. |
| 464 | if env.Errors.IsEmpty() { |
| 465 | env.Errors.Errorf("%s: internal error (compiled into nil package)", target.Path) |
| 466 | } |
| 467 | return true |
| 468 | } |
| 469 | // TODO(toddw): Skip code generation if the semantic contents of the |
| 470 | // generated file haven't changed. |
| 471 | pkgchanged := false |
| 472 | for _, gl := range optGenLangs { |
| 473 | switch gl { |
| 474 | case vdltool.GenLanguageGo: |
| 475 | if !shouldGenerate(pkg.Config, vdltool.GenLanguageGo) { |
| 476 | continue |
| 477 | } |
| 478 | dir, err := xlateOutDir(target.Dir, target.GenPath, optGenGoOutDir, pkg.GenPath) |
Suharsh Sivakumar | 6901a2e | 2015-04-02 11:39:19 -0700 | [diff] [blame] | 479 | if handleErrorOrSkip("--go-out-dir", err, env) { |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 480 | continue |
| 481 | } |
| 482 | for _, file := range pkg.Files { |
| 483 | data := golang.Generate(file, env) |
| 484 | if writeFile(audit, data, dir, file.BaseName+".go", env) { |
| 485 | pkgchanged = true |
| 486 | } |
| 487 | } |
| 488 | case vdltool.GenLanguageJava: |
| 489 | if !shouldGenerate(pkg.Config, vdltool.GenLanguageJava) { |
| 490 | continue |
| 491 | } |
| 492 | pkgPath, err := xlatePkgPath(pkg.GenPath, optGenJavaOutPkg) |
Suharsh Sivakumar | 6901a2e | 2015-04-02 11:39:19 -0700 | [diff] [blame] | 493 | if handleErrorOrSkip("--java-out-pkg", err, env) { |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 494 | continue |
| 495 | } |
| 496 | dir, err := xlateOutDir(target.Dir, target.GenPath, optGenJavaOutDir, pkgPath) |
Suharsh Sivakumar | 6901a2e | 2015-04-02 11:39:19 -0700 | [diff] [blame] | 497 | if handleErrorOrSkip("--java-out-dir", err, env) { |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 498 | continue |
| 499 | } |
| 500 | java.SetPkgPathXlator(func(pkgPath string) string { |
| 501 | result, _ := xlatePkgPath(pkgPath, optGenJavaOutPkg) |
| 502 | return result |
| 503 | }) |
| 504 | for _, file := range java.Generate(pkg, env) { |
| 505 | fileDir := filepath.Join(dir, file.Dir) |
| 506 | if writeFile(audit, file.Data, fileDir, file.Name, env) { |
| 507 | pkgchanged = true |
| 508 | } |
| 509 | } |
| 510 | case vdltool.GenLanguageJavascript: |
| 511 | if !shouldGenerate(pkg.Config, vdltool.GenLanguageJavascript) { |
| 512 | continue |
| 513 | } |
| 514 | dir, err := xlateOutDir(target.Dir, target.GenPath, optGenJavascriptOutDir, pkg.GenPath) |
Suharsh Sivakumar | 6901a2e | 2015-04-02 11:39:19 -0700 | [diff] [blame] | 515 | if handleErrorOrSkip("--js-out-dir", err, env) { |
Matt Rosencrantz | bca4981 | 2015-03-01 21:32:54 -0800 | [diff] [blame] | 516 | continue |
| 517 | } |
| 518 | path := func(importPath string) string { |
| 519 | prefix := filepath.Clean(target.Dir[0 : len(target.Dir)-len(target.GenPath)]) |
| 520 | pkgDir := filepath.Join(prefix, filepath.FromSlash(importPath)) |
| 521 | fullDir, err := xlateOutDir(pkgDir, importPath, optGenJavascriptOutDir, importPath) |
| 522 | if err != nil { |
| 523 | panic(err) |
| 524 | } |
| 525 | cleanPath, err := filepath.Rel(dir, fullDir) |
| 526 | if err != nil { |
| 527 | panic(err) |
| 528 | } |
| 529 | return cleanPath |
| 530 | } |
| 531 | data := javascript.Generate(pkg, env, path, optPathToJSCore) |
| 532 | if writeFile(audit, data, dir, "index.js", env) { |
| 533 | pkgchanged = true |
| 534 | } |
| 535 | default: |
| 536 | env.Errors.Errorf("Generating code for language %v isn't supported", gl) |
| 537 | } |
| 538 | } |
| 539 | if pkgchanged { |
| 540 | anychanged = true |
| 541 | if optGenStatus { |
| 542 | fmt.Println(pkg.Path) |
| 543 | } |
| 544 | } |
| 545 | } |
| 546 | return anychanged |
| 547 | } |
| 548 | |
| 549 | // writeFile writes data into the standard location for file, using the given |
| 550 | // suffix. Errors are reported via env. Returns true iff the file doesn't |
| 551 | // already exist with the given data. |
| 552 | func writeFile(audit bool, data []byte, dirName, baseName string, env *compile.Env) bool { |
| 553 | dstName := filepath.Join(dirName, baseName) |
| 554 | // Don't change anything if old and new are the same. |
| 555 | if oldData, err := ioutil.ReadFile(dstName); err == nil && bytes.Equal(oldData, data) { |
| 556 | return false |
| 557 | } |
| 558 | if !audit { |
| 559 | // Create containing directory, if it doesn't already exist. |
| 560 | if err := os.MkdirAll(dirName, os.FileMode(0777)); err != nil { |
| 561 | env.Errors.Errorf("Couldn't create directory %s: %v", dirName, err) |
| 562 | return true |
| 563 | } |
| 564 | if err := ioutil.WriteFile(dstName, data, os.FileMode(0666)); err != nil { |
| 565 | env.Errors.Errorf("Couldn't write file %s: %v", dstName, err) |
| 566 | return true |
| 567 | } |
| 568 | } |
| 569 | return true |
| 570 | } |
| 571 | |
| 572 | func handleErrorOrSkip(prefix string, err error, env *compile.Env) bool { |
| 573 | if err != nil { |
| 574 | if err != errSkip { |
| 575 | env.Errors.Errorf("%s error: %v", prefix, err) |
| 576 | } |
| 577 | return true |
| 578 | } |
| 579 | return false |
| 580 | } |
| 581 | |
| 582 | var errSkip = fmt.Errorf("SKIP") |
| 583 | |
| 584 | func xlateOutDir(dir, path string, outdir genOutDir, outPkgPath string) (string, error) { |
| 585 | path = filepath.FromSlash(path) |
| 586 | outPkgPath = filepath.FromSlash(outPkgPath) |
| 587 | // Strip package path from the directory. |
| 588 | if !strings.HasSuffix(dir, path) { |
| 589 | return "", fmt.Errorf("package dir %q doesn't end with package path %q", dir, path) |
| 590 | } |
| 591 | dir = filepath.Clean(dir[:len(dir)-len(path)]) |
| 592 | |
| 593 | switch { |
| 594 | case outdir.dir != "": |
| 595 | return filepath.Join(outdir.dir, outPkgPath), nil |
| 596 | case len(outdir.rules) == 0: |
| 597 | return filepath.Join(dir, outPkgPath), nil |
| 598 | } |
| 599 | // Try translation rules in order. |
| 600 | for _, xlate := range outdir.rules { |
| 601 | d := dir |
| 602 | if !strings.HasSuffix(d, xlate.src) { |
| 603 | continue |
| 604 | } |
| 605 | if xlate.dst == "SKIP" { |
| 606 | return "", errSkip |
| 607 | } |
| 608 | d = filepath.Clean(d[:len(d)-len(xlate.src)]) |
| 609 | return filepath.Join(d, xlate.dst, outPkgPath), nil |
| 610 | } |
| 611 | return "", fmt.Errorf("package prefix %q doesn't match translation rules %q", dir, outdir) |
| 612 | } |
| 613 | |
| 614 | func xlatePkgPath(pkgPath string, rules xlateRules) (string, error) { |
| 615 | for _, xlate := range rules { |
| 616 | if !strings.HasPrefix(pkgPath, xlate.src) { |
| 617 | continue |
| 618 | } |
| 619 | if xlate.dst == "SKIP" { |
| 620 | return pkgPath, errSkip |
| 621 | } |
| 622 | return xlate.dst + pkgPath[len(xlate.src):], nil |
| 623 | } |
| 624 | return pkgPath, nil |
| 625 | } |
| 626 | |
| 627 | func runList(targets []*build.Package, env *compile.Env) { |
| 628 | for tx, target := range targets { |
| 629 | num := fmt.Sprintf("%d", tx) |
| 630 | fmt.Printf("%s %s\n", num, strings.Repeat("=", termWidth()-len(num)-1)) |
| 631 | fmt.Printf("Name: %v\n", target.Name) |
| 632 | fmt.Printf("Config: %+v\n", target.Config) |
| 633 | fmt.Printf("Path: %v\n", target.Path) |
| 634 | fmt.Printf("GenPath: %v\n", target.GenPath) |
| 635 | fmt.Printf("Dir: %v\n", target.Dir) |
| 636 | if len(target.BaseFileNames) > 0 { |
| 637 | fmt.Print("Files:\n") |
| 638 | for _, file := range target.BaseFileNames { |
| 639 | fmt.Printf(" %v\n", file) |
| 640 | } |
| 641 | } |
| 642 | } |
| 643 | } |
| 644 | |
| 645 | func termWidth() int { |
| 646 | if _, width, err := textutil.TerminalSize(); err == nil && width > 0 { |
| 647 | return width |
| 648 | } |
| 649 | return 80 // have a reasonable default |
| 650 | } |