| // Copyright 2018 The CUE 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 |
| // |
| // http://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 load |
| |
| import ( |
| "os" |
| "path/filepath" |
| "runtime" |
| |
| "cuelang.org/go/cue" |
| "cuelang.org/go/cue/build" |
| "cuelang.org/go/cue/errors" |
| "cuelang.org/go/cue/token" |
| ) |
| |
| const ( |
| cueSuffix = ".cue" |
| defaultDir = "cue" |
| modFile = "cue.mod" |
| pkgDir = "pkg" // TODO: vendor? |
| ) |
| |
| // FromArgsUsage is a partial usage message that applications calling |
| // FromArgs may wish to include in their -help output. |
| // |
| // Some of the aspects of this documentation, like flags and handling '--' need |
| // to be implemented by the tools. |
| const FromArgsUsage = ` |
| <args> is a list of arguments denoting a set of instances. |
| It may take one of two forms: |
| |
| 1. A list of *.cue source files. |
| |
| All of the specified files are loaded, parsed and type-checked |
| as a single instance. |
| |
| 2. A list of relative directories to denote a package instance. |
| |
| Each directory matching the pattern is loaded as a separate instance. |
| The instance contains all files in this directory and ancestor directories, |
| up to the module root, with the same package name. The package name must |
| be either uniquely determined by the files in the given directory, or |
| explicitly defined using the '-p' flag. |
| |
| Files without a package clause are ignored. |
| |
| Files ending in *_test.cue files are only loaded when testing. |
| |
| 3. A list of import paths, each denoting a package. |
| |
| The package's directory is loaded from the package cache. The version of the |
| package is defined in the modules cue.mod file. |
| |
| A '--' argument terminates the list of packages. |
| ` |
| |
| // A Config configures load behavior. |
| type Config struct { |
| // Context specifies the context for the load operation. |
| // If the context is cancelled, the loader may stop early |
| // and return an ErrCancelled error. |
| // If Context is nil, the load cannot be cancelled. |
| Context *build.Context |
| |
| loader *loader |
| |
| // A Module is a collection of packages and instances that are within the |
| // directory hierarchy rooted at the module root. The module root can be |
| // marked with a cue.mod file. |
| ModuleRoot string |
| |
| // Module specifies the module prefix. If not empty, this value must match |
| // the module field of an existing cue.mod file. |
| Module string |
| |
| // cache specifies the package cache in which to look for packages. |
| cache string |
| |
| // Package defines the name of the package to be loaded. In this is not set, |
| // the package must be uniquely defined from its context. |
| Package string |
| |
| // Dir is the directory in which to run the build system's query tool |
| // that provides information about the packages. |
| // If Dir is empty, the tool is run in the current directory. |
| Dir string |
| |
| // The build and release tags specify build constraints that should be |
| // considered satisfied when processing +build lines. Clients creating a new |
| // context may customize BuildTags, which defaults to empty, but it is |
| // usually an error to customize ReleaseTags, which defaults to the list of |
| // CUE releases the current release is compatible with. |
| BuildTags []string |
| releaseTags []string |
| |
| // If Tests is set, the loader includes not just the packages |
| // matching a particular pattern but also any related test packages. |
| Tests bool |
| |
| // If Tools is set, the loader includes tool files associated with |
| // a package. |
| Tools bool |
| |
| // If DataFiles is set, the loader includes entries for directories that |
| // have no CUE files, but have recognized data files that could be converted |
| // to CUE. |
| DataFiles bool |
| |
| // StdRoot specifies an alternative directory for standard libaries. |
| // This is mostly used for bootstrapping. |
| StdRoot string |
| |
| // Overlay provides a mapping of absolute file paths to file contents. |
| // If the file with the given path already exists, the parser will use the |
| // alternative file contents provided by the map. |
| // |
| // Overlays provide incomplete support for when a given file doesn't |
| // already exist on disk. See the package doc above for more details. |
| // |
| // If the value must be of type string, []byte, io.Reader, or *ast.File. |
| Overlay map[string]Source |
| |
| fileSystem |
| } |
| |
| func (c Config) newInstance(path string) *build.Instance { |
| i := c.Context.NewInstance(path, nil) |
| i.DisplayPath = path |
| return i |
| } |
| |
| func (c Config) newErrInstance(m *match, path string, err error) *build.Instance { |
| i := c.Context.NewInstance(path, nil) |
| i.DisplayPath = path |
| i.ReportError(errors.Promote(err, "instance")) |
| return i |
| } |
| |
| func (c Config) complete() (cfg *Config, err error) { |
| // Each major CUE release should add a tag here. |
| // Old tags should not be removed. That is, the cue1.x tag is present |
| // in all releases >= CUE 1.x. Code that requires CUE 1.x or later should |
| // say "+build cue1.x", and code that should only be built before CUE 1.x |
| // (perhaps it is the stub to use in that case) should say "+build !cue1.x". |
| c.releaseTags = []string{"cue0.1"} |
| |
| if c.Dir == "" { |
| c.Dir, err = os.Getwd() |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| // TODO: we could populate this already with absolute file paths, |
| // but relative paths cannot be added. Consider what is reasonable. |
| if err := c.fileSystem.init(&c); err != nil { |
| return nil, err |
| } |
| |
| // TODO: determine root on a package basis. Maybe we even need a |
| // pkgname.cue.mod |
| // Look to see if there is a cue.mod. |
| if c.ModuleRoot == "" { |
| abs, err := c.findRoot(c.Dir) |
| if err != nil { |
| // Not using modules: only consider the current directory. |
| c.ModuleRoot = c.Dir |
| } else { |
| c.ModuleRoot = abs |
| } |
| } |
| |
| c.loader = &loader{cfg: &c} |
| |
| if c.Context == nil { |
| c.Context = build.NewContext(build.Loader(c.loader.loadFunc(c.Dir))) |
| } |
| |
| if c.cache == "" { |
| c.cache = filepath.Join(home(), defaultDir) |
| } |
| |
| // TODO: also make this work if run from outside the module? |
| switch { |
| case true: |
| mod := filepath.Join(c.ModuleRoot, modFile) |
| f, cerr := c.fileSystem.openFile(mod) |
| if cerr != nil { |
| break |
| } |
| var r cue.Runtime |
| inst, err := r.Parse(mod, f) |
| if err != nil { |
| return nil, errors.Wrapf(err, token.NoPos, "invalid cue.mod file") |
| } |
| prefix := inst.Lookup("module") |
| if prefix.IsValid() { |
| name, err := prefix.String() |
| if err != nil { |
| return nil, err |
| } |
| if c.Module == "" || c.Module != name { |
| return nil, errors.Newf(prefix.Pos(), "inconsistent modules: got %q, want %q", name, c.Module) |
| } |
| c.Module = name |
| } |
| } |
| |
| return &c, nil |
| } |
| |
| func (c Config) findRoot(dir string) (string, error) { |
| fs := &c.fileSystem |
| |
| absDir, err := filepath.Abs(dir) |
| if err != nil { |
| return "", err |
| } |
| abs := absDir |
| for { |
| info, err := fs.stat(filepath.Join(abs, modFile)) |
| if err == nil && !info.IsDir() { |
| return abs, nil |
| } |
| d := filepath.Dir(abs) |
| if len(d) >= len(abs) { |
| break // reached top of file system, no cue.mod |
| } |
| abs = d |
| } |
| abs = absDir |
| for { |
| info, err := fs.stat(filepath.Join(abs, pkgDir)) |
| if err == nil && info.IsDir() { |
| return abs, nil |
| } |
| d := filepath.Dir(abs) |
| if len(d) >= len(abs) { |
| return "", err // reached top of file system, no pkg dir. |
| } |
| abs = d |
| } |
| } |
| |
| func home() string { |
| env := "HOME" |
| if runtime.GOOS == "windows" { |
| env = "USERPROFILE" |
| } else if runtime.GOOS == "plan9" { |
| env = "home" |
| } |
| return os.Getenv(env) |
| } |