blob: 43147a1b4cc913778ff01e5957f70cc64d75d987 [file] [log] [blame]
Todd Wang232d6492015-02-25 18:04:54 -08001// Package build provides utilities to collect VDL build information, and
2// helpers to kick off the parser and compiler.
3//
4// VDL Packages
5//
6// VDL is organized into packages, where a package is a collection of one or
7// more source files. The files in a package collectively define the types,
8// constants, services and errors belonging to the package; these are called
9// package elements.
10//
11// The package elements in package P may be used in another package Q. First
12// package Q must import package P, and then refer to the package elements in P.
13// Imports define the package dependency graph, which must be acyclic.
14//
15// Build Strategy
16//
17// The steps to building a VDL package P:
18// 1) Compute the transitive closure of P's dependencies DEPS.
19// 2) Sort DEPS in dependency order.
20// 3) Build each package D in DEPS.
21// 3) Build package P.
22//
23// Building a package P requires that all elements used by P are understood,
24// including elements defined outside of P. The only way for a change to
25// package Q to affect the build of P is if Q is in the transitive closure of
26// P's package dependencies. However there may be false positives; the change
27// to Q might not actually affect P.
28//
29// The build process may perform more work than is strictly necessary, because
30// of these false positives. However it is simple and correct.
31//
32// The TransitivePackages* functions implement build steps 1 and 2.
33//
34// The Build* functions implement build steps 3 and 4.
35//
36// Other functions provide related build information and utilities.
37package build
38
39import (
40 "fmt"
41 "io"
42 "io/ioutil"
43 "os"
44 "path"
45 "path/filepath"
46 "reflect"
47 "regexp"
48 "sort"
49 "strings"
50
Jiri Simsa87cea302015-02-26 10:39:41 -080051 "v.io/core/veyron/lib/vdl/compile"
52 "v.io/core/veyron/lib/vdl/parse"
53 "v.io/core/veyron/lib/vdl/vdlutil"
Todd Wang232d6492015-02-25 18:04:54 -080054 "v.io/v23/vdl"
Todd Wang232d6492015-02-25 18:04:54 -080055 "v.io/v23/vdlroot/vdltool"
Jiri Simsa24a71552015-02-27 11:31:36 -080056 "v.io/x/lib/toposort"
Todd Wang232d6492015-02-25 18:04:54 -080057)
58
59const vdlrootImportPrefix = "v.io/v23/vdlroot"
60
61// Package represents the build information for a vdl package.
62type Package struct {
63 // Name is the name of the package, specified in the vdl files.
64 // E.g. "bar"
65 Name string
66 // Path is the package path; the path used in VDL import clauses.
67 // E.g. "foo/bar".
68 Path string
69 // GenPath is the package path to use for code generation. It is typically
70 // the same as Path, except for vdlroot standard packages.
71 // E.g. "v.io/v23/vdlroot/time"
72 GenPath string
73 // Dir is the absolute directory containing the package files.
74 // E.g. "/home/user/veyron/vdl/src/foo/bar"
75 Dir string
76 // BaseFileNames is the list of sorted base vdl file names for this package.
77 // Join these with Dir to get absolute file names.
78 BaseFileNames []string
79 // Config is the configuration for this package, specified by an optional
80 // "vdl.config" file in the package directory. If no "vdl.config" file
81 // exists, the zero value of Config is used.
82 Config vdltool.Config
83
84 // OpenFilesFunc is a function that opens the files with the given filenames,
85 // and returns a map from base file name to file contents.
86 OpenFilesFunc func(filenames []string) (map[string]io.ReadCloser, error)
87
88 openedFiles []io.Closer // files that need to be closed
89}
90
91// UnknownPathMode specifies the behavior when an unknown path is encountered.
92type UnknownPathMode int
93
94const (
95 UnknownPathIsIgnored UnknownPathMode = iota // Silently ignore unknown paths
96 UnknownPathIsError // Produce error for unknown paths
97)
98
99func (m UnknownPathMode) String() string {
100 switch m {
101 case UnknownPathIsIgnored:
102 return "UnknownPathIsIgnored"
103 case UnknownPathIsError:
104 return "UnknownPathIsError"
105 default:
106 return fmt.Sprintf("UnknownPathMode(%d)", m)
107 }
108}
109
110func (m UnknownPathMode) logOrErrorf(errs *vdlutil.Errors, format string, v ...interface{}) {
111 if m == UnknownPathIsIgnored {
112 vdlutil.Vlog.Printf(format, v...)
113 } else {
114 errs.Errorf(format, v...)
115 }
116}
117
118func pathPrefixDotOrDotDot(path string) bool {
119 // The point of this helper is to catch cases where the path starts with a
120 // . or .. element; note that ... returns false.
121 spath := filepath.ToSlash(path)
122 return path == "." || path == ".." || strings.HasPrefix(spath, "./") || strings.HasPrefix(spath, "../")
123}
124
125func ignorePathElem(elem string) bool {
126 return (strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_")) &&
127 !pathPrefixDotOrDotDot(elem)
128}
129
130// validPackagePath returns true iff the path is valid; i.e. if none of the path
131// elems is ignored.
132func validPackagePath(path string) bool {
133 for _, elem := range strings.Split(path, "/") {
134 if ignorePathElem(elem) {
135 return false
136 }
137 }
138 return true
139}
140
141// New packages always start with an empty Name, which is filled in when we call
142// ds.addPackageAndDeps.
143func newPackage(path, genPath, dir string, mode UnknownPathMode, opts Opts, vdlenv *compile.Env) *Package {
144 pkg := &Package{Path: path, GenPath: genPath, Dir: dir, OpenFilesFunc: openFiles}
145 if err := pkg.initBaseFileNames(opts.exts()); err != nil {
146 mode.logOrErrorf(vdlenv.Errors, "%s: bad package dir (%v)", pkg.Dir, err)
147 return nil
148 }
149 // TODO(toddw): Add a mechanism in vdlutil.Errors to distinguish categories of
150 // errors, so that it's more obvious when errors are coming from vdl.config
151 // files vs *.vdl files.
152 origErrors := vdlenv.Errors.NumErrors()
153 if pkg.initVDLConfig(opts, vdlenv); origErrors != vdlenv.Errors.NumErrors() {
154 return nil
155 }
156 return pkg
157}
158
159// initBaseFileNames initializes p.BaseFileNames from the contents of p.Dir.
160func (p *Package) initBaseFileNames(exts map[string]bool) error {
161 infos, err := ioutil.ReadDir(p.Dir)
162 if err != nil {
163 return err
164 }
165 for _, info := range infos {
166 if info.IsDir() {
167 continue
168 }
169 if ignorePathElem(info.Name()) || !exts[filepath.Ext(info.Name())] {
170 vdlutil.Vlog.Printf("%s: ignoring file", filepath.Join(p.Dir, info.Name()))
171 continue
172 }
173 vdlutil.Vlog.Printf("%s: adding vdl file", filepath.Join(p.Dir, info.Name()))
174 p.BaseFileNames = append(p.BaseFileNames, info.Name())
175 }
176 if len(p.BaseFileNames) == 0 {
177 return fmt.Errorf("no vdl files")
178 }
179 return nil
180}
181
182// initVDLConfig initializes p.Config based on the optional vdl.config file.
183func (p *Package) initVDLConfig(opts Opts, vdlenv *compile.Env) {
184 name := path.Join(p.Path, opts.vdlConfigName())
185 configData, err := os.Open(filepath.Join(p.Dir, opts.vdlConfigName()))
186 switch {
187 case os.IsNotExist(err):
188 return
189 case err != nil:
190 vdlenv.Errors.Errorf("%s: couldn't open (%v)", name, err)
191 return
192 }
193 // Build the vdl.config file with an implicit "vdltool" import. Note that the
194 // actual "vdltool" package has already been populated into vdlenv.
195 BuildConfigValue(name, configData, []string{"vdltool"}, vdlenv, &p.Config)
196 if err := configData.Close(); err != nil {
197 vdlenv.Errors.Errorf("%s: couldn't close (%v)", name, err)
198 }
199}
200
201// OpenFiles opens all files in the package and returns a map from base file
202// name to file contents. CloseFiles must be called to close the files.
203func (p *Package) OpenFiles() (map[string]io.Reader, error) {
204 var filenames []string
205 for _, baseName := range p.BaseFileNames {
206 filenames = append(filenames, filepath.Join(p.Dir, baseName))
207 }
208 files, err := p.OpenFilesFunc(filenames)
209 if err != nil {
210 for _, c := range files {
211 c.Close()
212 }
213 return nil, err
214 }
215 // Convert map elem type from io.ReadCloser to io.Reader.
216 res := make(map[string]io.Reader, len(files))
217 for n, f := range files {
218 res[n] = f
219 p.openedFiles = append(p.openedFiles, f)
220 }
221 return res, nil
222}
223
224func openFiles(filenames []string) (map[string]io.ReadCloser, error) {
225 files := make(map[string]io.ReadCloser, len(filenames))
226 for _, filename := range filenames {
227 file, err := os.Open(filename)
228 if err != nil {
229 for _, c := range files {
230 c.Close()
231 }
232 return nil, err
233 }
234 files[path.Base(filename)] = file
235 }
236 return files, nil
237}
238
239// CloseFiles closes all files returned by OpenFiles. Returns nil if all files
240// were closed successfully, otherwise returns one of the errors, dropping the
241// others. Regardless of whether an error is returned, Close will be called on
242// all files.
243func (p *Package) CloseFiles() error {
244 var err error
245 for _, c := range p.openedFiles {
246 if err2 := c.Close(); err == nil {
247 err = err2
248 }
249 }
250 p.openedFiles = nil
251 return err
252}
253
254// SrcDirs returns a list of package root source directories, based on the
255// VDLPATH, VDLROOT and VANADIUM_ROOT environment variables.
256//
257// VDLPATH is a list of directories separated by filepath.ListSeparator;
258// e.g. the separator is ":" on UNIX, and ";" on Windows. Each VDLPATH
259// directory must have a "src/" directory that holds vdl source code. The path
260// below "src/" determines the import path.
261//
262// VDLROOT is a single directory specifying the location of the standard vdl
263// packages. It has the same requirements as VDLPATH components. If VDLROOT is
264// empty, we use VANADIUM_ROOT to construct the VDLROOT. An error is reported if
265// neither VDLROOT nor VANADIUM_ROOT is specified.
266func SrcDirs(errs *vdlutil.Errors) []string {
267 var srcDirs []string
268 if root := vdlRootDir(errs); root != "" {
269 srcDirs = append(srcDirs, root)
270 }
271 return append(srcDirs, vdlPathSrcDirs(errs)...)
272}
273
274func vdlRootDir(errs *vdlutil.Errors) string {
275 vdlroot := os.Getenv("VDLROOT")
276 if vdlroot == "" {
277 // Try to construct VDLROOT out of VANADIUM_ROOT.
278 vroot := os.Getenv("VANADIUM_ROOT")
279 if vroot == "" {
280 errs.Error("Either VDLROOT or VANADIUM_ROOT must be set")
281 return ""
282 }
283 vdlroot = filepath.Join(vroot, "release", "go", "src", "v.io", "v23", "vdlroot")
284 }
285 abs, err := filepath.Abs(vdlroot)
286 if err != nil {
287 errs.Errorf("VDLROOT %q can't be made absolute (%v)", vdlroot, err)
288 return ""
289 }
290 return abs
291}
292
293func vdlPathSrcDirs(errs *vdlutil.Errors) []string {
294 var srcDirs []string
295 for _, dir := range filepath.SplitList(os.Getenv("VDLPATH")) {
296 if dir != "" {
297 src := filepath.Join(dir, "src")
298 abs, err := filepath.Abs(src)
299 if err != nil {
300 errs.Errorf("VDLPATH src dir %q can't be made absolute (%v)", src, err)
301 continue // keep going to collect all errors
302 }
303 srcDirs = append(srcDirs, abs)
304 }
305 }
306 if len(srcDirs) == 0 {
307 errs.Error("No src dirs; set VDLPATH to a valid value")
308 return nil
309 }
310 return srcDirs
311}
312
313// IsDirPath returns true iff the path is absolute, or begins with a . or
314// .. element. The path denotes the package in that directory.
315func IsDirPath(path string) bool {
316 return filepath.IsAbs(path) || pathPrefixDotOrDotDot(path)
317}
318
319// IsImportPath returns true iff !IsDirPath. The path P denotes the package in
320// directory DIR/src/P, for some DIR listed in SrcDirs.
321func IsImportPath(path string) bool {
322 return !IsDirPath(path)
323}
324
325// depSorter does the main work of collecting and sorting packages and their
326// dependencies. The full syntax from the go cmdline tool is supported; we
327// allow both dirs and import paths, as well as the "all" and "..." wildcards.
328//
329// This is slightly complicated because of dirs, and the potential for symlinks.
330// E.g. let's say we have two directories, one a symlink to the other:
331// /home/user/release/go/src/veyron/rt/base
332// /home/user/release/go/src/veyron/rt2 symlink to rt
333//
334// The problem is that if the user has cwd pointing at one of the two "base"
335// dirs and specifies a relative directory ".." it's ambiguous which absolute
336// dir we'll end up with; file paths form a graph rather than a tree. For more
337// details see http://plan9.bell-labs.com/sys/doc/lexnames.html
338//
339// This means that if the user builds a dir (rather than an import path), we
340// might not be able to deduce the package path. Note that the error definition
341// mechanism relies on the package path to create implicit error ids, and this
342// must be known at the time the package is compiled. To handle this we call
343// deducePackagePath and attempt to deduce the package path even if the user
344// builds a directory, and return errors if this fails.
345//
346// TODO(toddw): If we care about performance we could serialize the compiled
347// compile.Package information and write it out as compiler-generated artifacts,
348// similar to how the regular go tool generates *.a files under the top-level
349// pkg directory.
350type depSorter struct {
351 opts Opts
352 srcDirs []string
353 pathMap map[string]*Package
354 dirMap map[string]*Package
355 sorter *toposort.Sorter
356 errs *vdlutil.Errors
357 vdlenv *compile.Env
358}
359
360func newDepSorter(opts Opts, errs *vdlutil.Errors) *depSorter {
361 ds := &depSorter{
362 opts: opts,
363 srcDirs: SrcDirs(errs),
364 errs: errs,
365 vdlenv: compile.NewEnvWithErrors(errs),
366 }
367 ds.reset()
368 // Resolve the "vdltool" import and build all transitive packages into vdlenv.
369 // This special env is used when building "vdl.config" files, which have the
370 // implicit "vdltool" import.
371 if !ds.ResolvePath("vdltool", UnknownPathIsError) {
372 errs.Errorf(`Can't resolve "vdltool" package`)
373 }
374 for _, pkg := range ds.Sort() {
375 BuildPackage(pkg, ds.vdlenv)
376 }
377 // We must reset back to an empty depSorter, to ensure the transitive packages
378 // returned by the depSorter don't include "vdltool".
379 ds.reset()
380 return ds
381}
382
383func (ds *depSorter) reset() {
384 ds.pathMap = make(map[string]*Package)
385 ds.dirMap = make(map[string]*Package)
386 ds.sorter = new(toposort.Sorter)
387}
388
389func (ds *depSorter) errorf(format string, v ...interface{}) {
390 ds.errs.Errorf(format, v...)
391}
392
393// ResolvePath resolves path into package(s) and adds them to the sorter.
394// Returns true iff path could be resolved.
395func (ds *depSorter) ResolvePath(path string, mode UnknownPathMode) bool {
396 if path == "all" {
397 // Special-case "all", with the same behavior as Go.
398 path = "..."
399 }
400 isDirPath := IsDirPath(path)
401 dots := strings.Index(path, "...")
402 switch {
403 case dots >= 0:
404 return ds.resolveWildcardPath(isDirPath, path[:dots], path[dots:])
405 case isDirPath:
406 return ds.resolveDirPath(path, mode) != nil
407 default:
408 return ds.resolveImportPath(path, mode) != nil
409 }
410}
411
412// resolveWildcardPath resolves wildcards for both dir and import paths. The
413// prefix is everything before the first "...", and the suffix is everything
414// including and after the first "..."; note that multiple "..." wildcards may
415// occur within the suffix. Returns true iff any packages were resolved.
416//
417// The strategy is to compute one or more root directories that contain
418// everything that could possibly be matched, along with a filename pattern to
419// match against. Then we walk through each root directory, matching against
420// the pattern.
421func (ds *depSorter) resolveWildcardPath(isDirPath bool, prefix, suffix string) bool {
422 type dirAndSrc struct {
423 dir, src string
424 }
425 var rootDirs []dirAndSrc // root directories to walk through
426 var pattern string // pattern to match against, starting after root dir
427 switch {
428 case isDirPath:
429 // prefix and suffix are directory paths.
430 dir, pre := filepath.Split(prefix)
431 pattern = filepath.Clean(pre + suffix)
432 rootDirs = append(rootDirs, dirAndSrc{filepath.Clean(dir), ""})
433 default:
434 // prefix and suffix are slash-separated import paths.
435 slashDir, pre := path.Split(prefix)
436 pattern = filepath.Clean(pre + filepath.FromSlash(suffix))
437 dir := filepath.FromSlash(slashDir)
438 for _, srcDir := range ds.srcDirs {
439 rootDirs = append(rootDirs, dirAndSrc{filepath.Join(srcDir, dir), srcDir})
440 }
441 }
442 matcher, err := createMatcher(pattern)
443 if err != nil {
444 ds.errorf("%v", err)
445 return false
446 }
447 // Walk through root dirs and subdirs, looking for matches.
448 resolvedAny := false
449 for _, root := range rootDirs {
450 filepath.Walk(root.dir, func(rootAndPath string, info os.FileInfo, err error) error {
451 // Ignore errors and non-directory elements.
452 if err != nil || !info.IsDir() {
453 return nil
454 }
455 // Skip the dir and subdirs if the elem should be ignored.
456 _, elem := filepath.Split(rootAndPath)
457 if ignorePathElem(elem) {
458 vdlutil.Vlog.Printf("%s: ignoring dir", rootAndPath)
459 return filepath.SkipDir
460 }
461 // Special-case to skip packages with the vdlroot import prefix. These
462 // packages should only appear at the root of the package path space.
463 if root.src != "" {
464 pkgPath := strings.TrimPrefix(rootAndPath, root.src)
465 pkgPath = strings.TrimPrefix(pkgPath, "/")
466 if strings.HasPrefix(pkgPath, vdlrootImportPrefix) {
467 return filepath.SkipDir
468 }
469 }
470 // Ignore the dir if it doesn't match our pattern. We still process the
471 // subdirs since they still might match.
472 //
473 // TODO(toddw): We could add an optimization to skip subdirs that can't
474 // possibly match the matcher. E.g. given pattern "a..." we can skip
475 // the subdirs if the dir doesn't start with "a".
476 matchPath := rootAndPath[len(root.dir):]
477 if strings.HasPrefix(matchPath, pathSeparator) {
478 matchPath = matchPath[len(pathSeparator):]
479 }
480 if !matcher.MatchString(matchPath) {
481 return nil
482 }
483 // Finally resolve the dir.
484 if ds.resolveDirPath(rootAndPath, UnknownPathIsIgnored) != nil {
485 resolvedAny = true
486 }
487 return nil
488 })
489 }
490 return resolvedAny
491}
492
493const pathSeparator = string(filepath.Separator)
494
495// createMatcher creates a regexp matcher out of the file pattern.
496func createMatcher(pattern string) (*regexp.Regexp, error) {
497 rePat := regexp.QuoteMeta(pattern)
498 rePat = strings.Replace(rePat, `\.\.\.`, `.*`, -1)
499 // Add special-case so that x/... also matches x.
500 slashDotStar := regexp.QuoteMeta(pathSeparator) + ".*"
501 if strings.HasSuffix(rePat, slashDotStar) {
502 rePat = rePat[:len(rePat)-len(slashDotStar)] + "(" + slashDotStar + ")?"
503 }
504 rePat = `^` + rePat + `$`
505 matcher, err := regexp.Compile(rePat)
506 if err != nil {
507 return nil, fmt.Errorf("Can't compile package path regexp %s: %v", rePat, err)
508 }
509 return matcher, nil
510}
511
512// resolveDirPath resolves dir into a Package. Returns the package, or nil if
513// it can't be resolved.
514func (ds *depSorter) resolveDirPath(dir string, mode UnknownPathMode) *Package {
515 // If the package already exists in our dir map, we can just return it.
516 absDir, err := filepath.Abs(dir)
517 if err != nil {
518 ds.errorf("%s: can't make absolute (%v)", dir, err)
519 }
520 if pkg := ds.dirMap[absDir]; pkg != nil {
521 return pkg
522 }
523 // Deduce the package path, and ensure it corresponds to exactly one package.
524 // We always deduce the package path from the package directory, even if we
525 // originally resolved from an import path, and thus already "know" the
526 // package path. This is to ensure we correctly handle vdl standard packages.
527 // E.g. if we're given "v.io/v23/vdlroot/vdltool" as an import path, the
528 // resulting package path must be "vdltool".
529 pkgPath, genPath, err := ds.deducePackagePath(absDir)
530 if err != nil {
531 ds.errorf("%s: can't deduce package path (%v)", absDir, err)
532 return nil
533 }
534 if !validPackagePath(pkgPath) {
535 mode.logOrErrorf(ds.errs, "%s: package path %q is invalid", absDir, pkgPath)
536 return nil
537 }
538 if pkg := ds.pathMap[pkgPath]; pkg != nil {
539 mode.logOrErrorf(ds.errs, "%s: package path %q already resolved from %s", absDir, pkgPath, pkg.Dir)
540 return nil
541 }
542 // Make sure the directory really exists, and add the package and deps.
543 fileInfo, err := os.Stat(absDir)
544 if err != nil {
545 mode.logOrErrorf(ds.errs, "%v", err)
546 return nil
547 }
548 if !fileInfo.IsDir() {
549 mode.logOrErrorf(ds.errs, "%s: package isn't a directory", absDir)
550 return nil
551 }
552 return ds.addPackageAndDeps(pkgPath, genPath, absDir, mode)
553}
554
555// resolveImportPath resolves pkgPath into a Package. Returns the package, or
556// nil if it can't be resolved.
557func (ds *depSorter) resolveImportPath(pkgPath string, mode UnknownPathMode) *Package {
558 pkgPath = path.Clean(pkgPath)
559 if pkg := ds.pathMap[pkgPath]; pkg != nil {
560 return pkg
561 }
562 if !validPackagePath(pkgPath) {
563 mode.logOrErrorf(ds.errs, "Import path %q is invalid", pkgPath)
564 return nil
565 }
566 // Special-case to disallow packages under the vdlroot dir.
567 if strings.HasPrefix(pkgPath, vdlrootImportPrefix) {
568 mode.logOrErrorf(ds.errs, "Import path %q is invalid (packages under vdlroot must be specified without the vdlroot prefix)", pkgPath)
569 return nil
570 }
571 // Look through srcDirs in-order until we find a valid package dir.
572 var dirs []string
573 for _, srcDir := range ds.srcDirs {
574 dir := filepath.Join(srcDir, filepath.FromSlash(pkgPath))
575 if pkg := ds.resolveDirPath(dir, UnknownPathIsIgnored); pkg != nil {
576 vdlutil.Vlog.Printf("%s: resolved import path %q", pkg.Dir, pkgPath)
577 return pkg
578 }
579 dirs = append(dirs, dir)
580 }
581 // We can't find a valid dir corresponding to this import path.
582 detail := " " + strings.Join(dirs, "\n ")
583 mode.logOrErrorf(ds.errs, "Can't resolve import path %q in any of:\n%s", pkgPath, detail)
584 return nil
585}
586
587// addPackageAndDeps adds the pkg and its dependencies to the sorter.
588func (ds *depSorter) addPackageAndDeps(path, genPath, dir string, mode UnknownPathMode) *Package {
589 pkg := newPackage(path, genPath, dir, mode, ds.opts, ds.vdlenv)
590 if pkg == nil {
591 return nil
592 }
593 vdlutil.Vlog.Printf("%s: resolved package path %q", pkg.Dir, pkg.Path)
594 ds.dirMap[pkg.Dir] = pkg
595 ds.pathMap[pkg.Path] = pkg
596 ds.sorter.AddNode(pkg)
597 pfiles := ParsePackage(pkg, parse.Opts{ImportsOnly: true}, ds.errs)
598 pkg.Name = parse.InferPackageName(pfiles, ds.errs)
599 for _, pf := range pfiles {
600 ds.addImportDeps(pkg, pf.Imports)
601 }
602 return pkg
603}
604
605// addImportDeps adds transitive dependencies represented by imports to the
606// sorter. If the pkg is non-nil, an edge is added between the pkg and its
607// dependencies; otherwise each dependency is added as an independent node.
608func (ds *depSorter) addImportDeps(pkg *Package, imports []*parse.Import) {
609 for _, imp := range imports {
610 if dep := ds.resolveImportPath(imp.Path, UnknownPathIsError); dep != nil {
611 if pkg != nil {
612 ds.sorter.AddEdge(pkg, dep)
613 } else {
614 ds.sorter.AddNode(dep)
615 }
616 }
617 }
618}
619
620// AddConfigDeps takes a config file represented by its file name and src data,
621// and adds all transitive dependencies to the sorter.
622func (ds *depSorter) AddConfigDeps(fileName string, src io.Reader) {
623 if pconfig := parse.ParseConfig(fileName, src, parse.Opts{ImportsOnly: true}, ds.errs); pconfig != nil {
624 ds.addImportDeps(nil, pconfig.Imports)
625 }
626}
627
628// deducePackagePath deduces the package path for dir, by looking for prefix
629// matches against the src dirs. The resulting package path may be incorrect
630// even if no errors are reported; see the depSorter comment for details.
631func (ds *depSorter) deducePackagePath(dir string) (string, string, error) {
632 for ix, srcDir := range ds.srcDirs {
633 if strings.HasPrefix(dir, srcDir) {
634 relPath, err := filepath.Rel(srcDir, dir)
635 if err != nil {
636 return "", "", err
637 }
638 pkgPath := path.Clean(filepath.ToSlash(relPath))
639 genPath := pkgPath
640 if ix == 0 {
641 // We matched against the first srcDir, which is the VDLROOT dir. The
642 // genPath needs to include the vdlroot prefix.
643 genPath = path.Join(vdlrootImportPrefix, pkgPath)
644 }
645 return pkgPath, genPath, nil
646 }
647 }
648 return "", "", fmt.Errorf("no matching SrcDirs")
649}
650
651// Sort sorts all targets and returns the resulting list of Packages.
652func (ds *depSorter) Sort() []*Package {
653 sorted, cycles := ds.sorter.Sort()
654 if len(cycles) > 0 {
655 cycleStr := toposort.DumpCycles(cycles, printPackagePath)
656 ds.errorf("Cyclic package dependency: %v", cycleStr)
657 return nil
658 }
659 if len(sorted) == 0 {
660 return nil
661 }
662 targets := make([]*Package, len(sorted))
663 for ix, iface := range sorted {
664 targets[ix] = iface.(*Package)
665 }
666 return targets
667}
668
669func printPackagePath(v interface{}) string {
670 return v.(*Package).Path
671}
672
673// Opts specifies additional options for collecting build information.
674type Opts struct {
675 // Extensions specifies the file name extensions for valid vdl files. If
676 // empty we use ".vdl" by default.
677 Extensions []string
678
679 // VDLConfigName specifies the name of the optional config file in each vdl
680 // source package. If empty we use "vdl.config" by default.
681 VDLConfigName string
682}
683
684func (o Opts) exts() map[string]bool {
685 ret := make(map[string]bool)
686 for _, e := range o.Extensions {
687 ret[e] = true
688 }
689 if len(ret) == 0 {
690 ret[".vdl"] = true
691 }
692 return ret
693}
694
695func (o Opts) vdlConfigName() string {
696 if o.VDLConfigName != "" {
697 return o.VDLConfigName
698 }
699 return "vdl.config"
700}
701
702// TransitivePackages takes a list of paths, and returns the corresponding
703// packages and transitive dependencies, ordered by dependency. Each path may
704// either be a directory (IsDirPath) or an import (IsImportPath).
705//
706// A path is a pattern if it includes one or more "..." wildcards, each of which
707// can match any string, including the empty string and strings containing
708// slashes. Such a pattern expands to all packages found in SrcDirs with names
709// matching the pattern. As a special-case, x/... matches x as well as x's
710// subdirectories.
711//
712// The special-case "all" is a synonym for "...", and denotes all packages found
713// in SrcDirs.
714//
715// Import path elements and file names are not allowed to begin with "." or "_";
716// such paths are ignored in wildcard matches, and return errors if specified
717// explicitly.
718//
719// The mode specifies whether we should ignore or produce errors for paths that
720// don't resolve to any packages. The opts arg specifies additional options.
721func TransitivePackages(paths []string, mode UnknownPathMode, opts Opts, errs *vdlutil.Errors) []*Package {
722 ds := newDepSorter(opts, errs)
723 for _, path := range paths {
724 if !ds.ResolvePath(path, mode) {
725 mode.logOrErrorf(errs, "Can't resolve %q to any packages", path)
726 }
727 }
728 return ds.Sort()
729}
730
731// TransitivePackagesForConfig takes a config file represented by its file name
732// and src data, and returns all package dependencies in transitive order.
733//
734// The opts arg specifies additional options.
735func TransitivePackagesForConfig(fileName string, src io.Reader, opts Opts, errs *vdlutil.Errors) []*Package {
736 ds := newDepSorter(opts, errs)
737 ds.AddConfigDeps(fileName, src)
738 return ds.Sort()
739}
740
741// ParsePackage parses the given pkg with the given parse opts, and returns a
742// slice of parsed files, sorted by name. Errors are reported in errs.
743func ParsePackage(pkg *Package, opts parse.Opts, errs *vdlutil.Errors) (pfiles []*parse.File) {
744 vdlutil.Vlog.Printf("Parsing package %s %q, dir %s", pkg.Name, pkg.Path, pkg.Dir)
745 files, err := pkg.OpenFiles()
746 if err != nil {
747 errs.Errorf("Can't open vdl files %v, %v", pkg.BaseFileNames, err)
748 return nil
749 }
750 for filename, src := range files {
751 if pf := parse.ParseFile(path.Join(pkg.Path, filename), src, opts, errs); pf != nil {
752 pfiles = append(pfiles, pf)
753 }
754 }
755 sort.Sort(byBaseName(pfiles))
756 pkg.CloseFiles()
757 return
758}
759
760// byBaseName implements sort.Interface
761type byBaseName []*parse.File
762
763func (b byBaseName) Len() int { return len(b) }
764func (b byBaseName) Less(i, j int) bool { return b[i].BaseName < b[j].BaseName }
765func (b byBaseName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
766
767// BuildPackage parses and compiles the given pkg, updates env with the compiled
768// package and returns it. Errors are reported in env.
769//
770// All imports that pkg depend on must have already been compiled and populated
771// into env.
772func BuildPackage(pkg *Package, env *compile.Env) *compile.Package {
773 pfiles := ParsePackage(pkg, parse.Opts{}, env.Errors)
774 return compile.CompilePackage(pkg.Path, pkg.GenPath, pfiles, pkg.Config, env)
775}
776
777// BuildConfig parses and compiles the given config src and returns it. Errors
778// are reported in env; fileName is only used for error reporting.
779//
780// The implicit type is applied to the exported config const; untyped consts and
781// composite literals with no explicit type assume the implicit type. Errors
782// are reported if the implicit type isn't assignable from the final value. If
783// the implicit type is nil, the exported config const must be explicitly typed.
784//
785// All packages that the config src depends on must have already been compiled
786// and populated into env. The imports are injected into the parsed src,
787// behaving as if the src had listed the imports explicitly.
788func BuildConfig(fileName string, src io.Reader, implicit *vdl.Type, imports []string, env *compile.Env) *vdl.Value {
789 pconfig := parse.ParseConfig(fileName, src, parse.Opts{}, env.Errors)
790 if pconfig != nil {
791 pconfig.AddImports(imports...)
792 }
793 return compile.CompileConfig(implicit, pconfig, env)
794}
795
796// BuildConfigValue is a convenience function that runs BuildConfig, and then
797// converts the result into value. The implicit type used by BuildConfig is
798// inferred from the value.
799func BuildConfigValue(fileName string, src io.Reader, imports []string, env *compile.Env, value interface{}) {
800 rv := reflect.ValueOf(value)
801 tt, err := vdl.TypeFromReflect(rv.Type())
802 if err != nil {
803 env.Errors.Errorf(err.Error())
804 return
805 }
806 if tt.Kind() == vdl.Optional {
807 // The value is typically a Go pointer, which translates into VDL optional.
808 // Remove the optional when determining the implicit type for BuildConfig.
809 tt = tt.Elem()
810 }
811 vconfig := BuildConfig(fileName, src, tt, imports, env)
812 if vconfig == nil {
813 return
814 }
815 target, err := vdl.ReflectTarget(rv)
816 if err != nil {
817 env.Errors.Errorf("Can't create reflect target for %T (%v)", value, err)
818 return
819 }
820 if err := vdl.FromValue(target, vconfig); err != nil {
821 env.Errors.Errorf("Can't convert to %T from %v (%v)", value, vconfig, err)
822 return
823 }
824}
825
826// BuildExprs parses and compiles the given data into a slice of values. The
827// input data is specified in VDL syntax, with commas separating multiple
828// expressions. There must be at least one expression specified in data.
829// Errors are reported in env.
830//
831// The given types specify the type of each returned value with the same slice
832// position. If there are more types than returned values, the extra types are
833// ignored. If there are fewer types than returned values, the last type is
834// used for all remaining values. Nil entries in types are allowed, and
835// indicate that the expression itself must be fully typed.
836//
837// All imports that the input data depends on must have already been compiled
838// and populated into env.
839func BuildExprs(data string, types []*vdl.Type, env *compile.Env) []*vdl.Value {
840 var values []*vdl.Value
841 var t *vdl.Type
842 for ix, pexpr := range parse.ParseExprs(data, env.Errors) {
843 if ix < len(types) {
844 t = types[ix]
845 }
846 values = append(values, compile.CompileExpr(t, pexpr, env))
847 }
848 return values
849}