blob: 78444a3cf20338918e41fd355cc61f188d5b9ca2 [file] [log] [blame]
Marcel van Lohuizenbc4d65d2018-12-10 15:40:02 +01001// 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
15package load
16
17import (
18 "os"
19 "path/filepath"
20 "runtime"
21
22 "cuelang.org/go/cue/build"
23)
24
25const (
26 cueSuffix = ".cue"
27 defaultDir = "cue"
28 modFile = "cue.mod"
Marcel van Lohuizen9ccf2732019-02-23 14:32:03 +010029 pkgDir = "pkg" // TODO: vendor?
Marcel van Lohuizenbc4d65d2018-12-10 15:40:02 +010030)
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.
37const FromArgsUsage = `
38<args> is a list of arguments denoting a set of instances.
39It may take one of two forms:
40
411. 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
462. 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
583. 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
63A '--' argument terminates the list of packages.
64`
65
66// A Config configures load behavior.
67type 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 Lohuizen57333362019-04-01 14:35:09 +0200111 // StdRoot specifies an alternative directory for standard libaries.
112 // This is mostly used for bootstrapping.
113 StdRoot string
114
Marcel van Lohuizenbc4d65d2018-12-10 15:40:02 +0100115 fileSystem
116}
117
118func (c Config) newInstance(path string) *build.Instance {
119 i := c.Context.NewInstance(path, nil)
120 i.DisplayPath = path
121 return i
122}
123
124func (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
131func (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 Lohuizenbc4d65d2018-12-10 15:40:02 +0100146 // 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 Lohuizen9ccf2732019-02-23 14:32:03 +0100158
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 Lohuizenbc4d65d2018-12-10 15:40:02 +0100170 return &c, nil
171}
172
173func findRoot(dir string) (string, error) {
Marcel van Lohuizen9ccf2732019-02-23 14:32:03 +0100174 absDir, err := filepath.Abs(dir)
Marcel van Lohuizenbc4d65d2018-12-10 15:40:02 +0100175 if err != nil {
176 return "", err
177 }
Marcel van Lohuizen9ccf2732019-02-23 14:32:03 +0100178 abs := absDir
Marcel van Lohuizenbc4d65d2018-12-10 15:40:02 +0100179 for {
180 info, err := os.Stat(filepath.Join(abs, modFile))
181 if err == nil && !info.IsDir() {
Marcel van Lohuizen9ccf2732019-02-23 14:32:03 +0100182 return abs, nil
Marcel van Lohuizenbc4d65d2018-12-10 15:40:02 +0100183 }
184 d := filepath.Dir(abs)
185 if len(d) >= len(abs) {
Marcel van Lohuizen9ccf2732019-02-23 14:32:03 +0100186 break // reached top of file system, no cue.mod
Marcel van Lohuizenbc4d65d2018-12-10 15:40:02 +0100187 }
188 abs = d
189 }
Marcel van Lohuizen9ccf2732019-02-23 14:32:03 +0100190 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 Lohuizenbc4d65d2018-12-10 15:40:02 +0100202}
203
204func 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}