Marcel van Lohuizen | bc4d65d | 2018-12-10 15:40:02 +0100 | [diff] [blame] | 1 | // Copyright 2018 The CUE 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 | // http://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 | |
| 15 | package load |
| 16 | |
| 17 | import ( |
| 18 | "os" |
| 19 | "path/filepath" |
| 20 | "runtime" |
| 21 | |
| 22 | "cuelang.org/go/cue/build" |
| 23 | ) |
| 24 | |
| 25 | const ( |
| 26 | cueSuffix = ".cue" |
| 27 | defaultDir = "cue" |
| 28 | modFile = "cue.mod" |
Marcel van Lohuizen | 9ccf273 | 2019-02-23 14:32:03 +0100 | [diff] [blame] | 29 | pkgDir = "pkg" // TODO: vendor? |
Marcel van Lohuizen | bc4d65d | 2018-12-10 15:40:02 +0100 | [diff] [blame] | 30 | ) |
| 31 | |
| 32 | // FromArgsUsage is a partial usage message that applications calling |
| 33 | // FromArgs may wish to include in their -help output. |
| 34 | // |
| 35 | // Some of the aspects of this documentation, like flags and handling '--' need |
| 36 | // to be implemented by the tools. |
| 37 | const FromArgsUsage = ` |
| 38 | <args> is a list of arguments denoting a set of instances. |
| 39 | It may take one of two forms: |
| 40 | |
| 41 | 1. A list of *.cue source files. |
| 42 | |
| 43 | All of the specified files are loaded, parsed and type-checked |
| 44 | as a single instance. |
| 45 | |
| 46 | 2. A list of relative directories to denote a package instance. |
| 47 | |
| 48 | Each directory matching the pattern is loaded as a separate instance. |
| 49 | The instance contains all files in this directory and ancestor directories, |
| 50 | up to the module root, with the same package name. The package name must |
| 51 | be either uniquely determined by the files in the given directory, or |
| 52 | explicitly defined using the '-p' flag. |
| 53 | |
| 54 | Files without a package clause are ignored. |
| 55 | |
| 56 | Files ending in *_test.cue files are only loaded when testing. |
| 57 | |
| 58 | 3. A list of import paths, each denoting a package. |
| 59 | |
| 60 | The package's directory is loaded from the package cache. The version of the |
| 61 | package is defined in the modules cue.mod file. |
| 62 | |
| 63 | A '--' argument terminates the list of packages. |
| 64 | ` |
| 65 | |
| 66 | // A Config configures load behavior. |
| 67 | type Config struct { |
| 68 | // Context specifies the context for the load operation. |
| 69 | // If the context is cancelled, the loader may stop early |
| 70 | // and return an ErrCancelled error. |
| 71 | // If Context is nil, the load cannot be cancelled. |
| 72 | Context *build.Context |
| 73 | |
| 74 | loader *loader |
| 75 | |
| 76 | modRoot string // module root for package paths ("" if unknown) |
| 77 | |
| 78 | // cache specifies the package cache in which to look for packages. |
| 79 | cache string |
| 80 | |
| 81 | // Package defines the name of the package to be loaded. In this is not set, |
| 82 | // the package must be uniquely defined from its context. |
| 83 | Package string |
| 84 | |
| 85 | // Dir is the directory in which to run the build system's query tool |
| 86 | // that provides information about the packages. |
| 87 | // If Dir is empty, the tool is run in the current directory. |
| 88 | Dir string |
| 89 | |
| 90 | // The build and release tags specify build constraints that should be |
| 91 | // considered satisfied when processing +build lines. Clients creating a new |
| 92 | // context may customize BuildTags, which defaults to empty, but it is |
| 93 | // usually an error to customize ReleaseTags, which defaults to the list of |
| 94 | // CUE releases the current release is compatible with. |
| 95 | BuildTags []string |
| 96 | releaseTags []string |
| 97 | |
| 98 | // If Tests is set, the loader includes not just the packages |
| 99 | // matching a particular pattern but also any related test packages. |
| 100 | Tests bool |
| 101 | |
| 102 | // If Tools is set, the loader includes tool files associated with |
| 103 | // a package. |
| 104 | Tools bool |
| 105 | |
| 106 | // If DataFiles is set, the loader includes entries for directories that |
| 107 | // have no CUE files, but have recognized data files that could be converted |
| 108 | // to CUE. |
| 109 | DataFiles bool |
| 110 | |
Marcel van Lohuizen | 5733336 | 2019-04-01 14:35:09 +0200 | [diff] [blame] | 111 | // StdRoot specifies an alternative directory for standard libaries. |
| 112 | // This is mostly used for bootstrapping. |
| 113 | StdRoot string |
| 114 | |
Marcel van Lohuizen | bc4d65d | 2018-12-10 15:40:02 +0100 | [diff] [blame] | 115 | fileSystem |
| 116 | } |
| 117 | |
| 118 | func (c Config) newInstance(path string) *build.Instance { |
| 119 | i := c.Context.NewInstance(path, nil) |
| 120 | i.DisplayPath = path |
| 121 | return i |
| 122 | } |
| 123 | |
| 124 | func (c Config) newErrInstance(m *match, path string, err error) *build.Instance { |
| 125 | i := c.Context.NewInstance(path, nil) |
| 126 | i.DisplayPath = path |
| 127 | i.ReportError(err) |
| 128 | return i |
| 129 | } |
| 130 | |
| 131 | func (c Config) complete() (cfg *Config, err error) { |
| 132 | // Each major CUE release should add a tag here. |
| 133 | // Old tags should not be removed. That is, the cue1.x tag is present |
| 134 | // in all releases >= CUE 1.x. Code that requires CUE 1.x or later should |
| 135 | // say "+build cue1.x", and code that should only be built before CUE 1.x |
| 136 | // (perhaps it is the stub to use in that case) should say "+build !cue1.x". |
| 137 | c.releaseTags = []string{"cue0.1"} |
| 138 | |
| 139 | if c.Dir == "" { |
| 140 | c.Dir, err = os.Getwd() |
| 141 | if err != nil { |
| 142 | return nil, err |
| 143 | } |
| 144 | } |
| 145 | |
Marcel van Lohuizen | bc4d65d | 2018-12-10 15:40:02 +0100 | [diff] [blame] | 146 | // TODO: determine root on a package basis. Maybe we even need a |
| 147 | // pkgname.cue.mod |
| 148 | // Look to see if there is a cue.mod. |
| 149 | if c.modRoot == "" { |
| 150 | abs, err := findRoot(c.Dir) |
| 151 | if err != nil { |
| 152 | // Not using modules: only consider the current directory. |
| 153 | c.modRoot = c.Dir |
| 154 | } else { |
| 155 | c.modRoot = abs |
| 156 | } |
| 157 | } |
Marcel van Lohuizen | 9ccf273 | 2019-02-23 14:32:03 +0100 | [diff] [blame] | 158 | |
| 159 | c.loader = &loader{cfg: &c} |
| 160 | |
| 161 | if c.Context == nil { |
| 162 | c.Context = build.NewContext(build.Loader(c.loader.loadFunc(c.Dir))) |
| 163 | } |
| 164 | |
| 165 | if c.cache == "" { |
| 166 | c.cache = filepath.Join(home(), defaultDir) |
| 167 | // os.MkdirAll(c.Cache, 0755) // TODO: tools task |
| 168 | } |
| 169 | |
Marcel van Lohuizen | bc4d65d | 2018-12-10 15:40:02 +0100 | [diff] [blame] | 170 | return &c, nil |
| 171 | } |
| 172 | |
| 173 | func findRoot(dir string) (string, error) { |
Marcel van Lohuizen | 9ccf273 | 2019-02-23 14:32:03 +0100 | [diff] [blame] | 174 | absDir, err := filepath.Abs(dir) |
Marcel van Lohuizen | bc4d65d | 2018-12-10 15:40:02 +0100 | [diff] [blame] | 175 | if err != nil { |
| 176 | return "", err |
| 177 | } |
Marcel van Lohuizen | 9ccf273 | 2019-02-23 14:32:03 +0100 | [diff] [blame] | 178 | abs := absDir |
Marcel van Lohuizen | bc4d65d | 2018-12-10 15:40:02 +0100 | [diff] [blame] | 179 | for { |
| 180 | info, err := os.Stat(filepath.Join(abs, modFile)) |
| 181 | if err == nil && !info.IsDir() { |
Marcel van Lohuizen | 9ccf273 | 2019-02-23 14:32:03 +0100 | [diff] [blame] | 182 | return abs, nil |
Marcel van Lohuizen | bc4d65d | 2018-12-10 15:40:02 +0100 | [diff] [blame] | 183 | } |
| 184 | d := filepath.Dir(abs) |
| 185 | if len(d) >= len(abs) { |
Marcel van Lohuizen | 9ccf273 | 2019-02-23 14:32:03 +0100 | [diff] [blame] | 186 | break // reached top of file system, no cue.mod |
Marcel van Lohuizen | bc4d65d | 2018-12-10 15:40:02 +0100 | [diff] [blame] | 187 | } |
| 188 | abs = d |
| 189 | } |
Marcel van Lohuizen | 9ccf273 | 2019-02-23 14:32:03 +0100 | [diff] [blame] | 190 | abs = absDir |
| 191 | for { |
| 192 | info, err := os.Stat(filepath.Join(abs, pkgDir)) |
| 193 | if err == nil && info.IsDir() { |
| 194 | return abs, nil |
| 195 | } |
| 196 | d := filepath.Dir(abs) |
| 197 | if len(d) >= len(abs) { |
| 198 | return "", err // reached top of file system, no pkg dir. |
| 199 | } |
| 200 | abs = d |
| 201 | } |
Marcel van Lohuizen | bc4d65d | 2018-12-10 15:40:02 +0100 | [diff] [blame] | 202 | } |
| 203 | |
| 204 | func home() string { |
| 205 | env := "HOME" |
| 206 | if runtime.GOOS == "windows" { |
| 207 | env = "USERPROFILE" |
| 208 | } else if runtime.GOOS == "plan9" { |
| 209 | env = "home" |
| 210 | } |
| 211 | return os.Getenv(env) |
| 212 | } |