-
Notifications
You must be signed in to change notification settings - Fork 9
/
imports.go
123 lines (100 loc) · 2.94 KB
/
imports.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Copyright 2022 Namespace Labs Inc; All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
package fncue
import (
"context"
"io/fs"
"path/filepath"
"cuelang.org/go/cue/ast/astutil"
"cuelang.org/go/cue/parser"
"namespacelabs.dev/foundation/internal/fnfs/memfs"
"namespacelabs.dev/foundation/schema"
)
// Represents an unparsed Cue package.
type CuePackage struct {
*PackageContents
Files []string // Relative to RelPath
Imports []string // Top level import statements.
}
func (pkg CuePackage) RelFiles() []string {
var files []string
for _, f := range pkg.Files {
files = append(files, filepath.Join(pkg.RelPath, f))
}
return files
}
type PackageContents struct {
ModuleName string
// Snapshot rooted at the module [ModuleName] root.
Snapshot fs.FS
// Path within the module (within [FS]).
RelPath string
// Absolute path.
AbsPath string
}
type WorkspaceLoader interface {
SnapshotDir(context.Context, schema.PackageName, memfs.SnapshotOpts) (*PackageContents, error)
}
// Fills [m] with the transitive closure of packages and files imported by package [pkgname].
// TODO: Use [snapshotCache] instead of re-parsing all packages directly.
func CollectImports(ctx context.Context, resolver WorkspaceLoader, pkgname string, m map[string]*CuePackage) error {
if _, ok := m[pkgname]; ok {
return nil
}
// Leave a marker that this package is already handled, to avoid processing through cycles.
m[pkgname] = &CuePackage{}
pkg, err := loadPackageContents(ctx, resolver, pkgname)
if err != nil {
return err
}
m[pkgname] = pkg
if len(pkg.Files) == 0 {
return nil
}
for _, fp := range pkg.RelFiles() {
contents, err := fs.ReadFile(pkg.Snapshot, fp)
if err != nil {
return err
}
f, err := parser.ParseFile(fp, contents, parser.ImportsOnly)
if err != nil {
return err
}
for _, imp := range f.Imports {
importInfo, _ := astutil.ParseImportSpec(imp)
pkg.Imports = append(pkg.Imports, importInfo.Dir)
if IsStandardImportPath(importInfo.ID) {
continue
}
if err := CollectImports(ctx, resolver, importInfo.Dir, m); err != nil {
return err
}
}
}
return nil
}
func loadPackageContents(ctx context.Context, loader WorkspaceLoader, pkgName string) (*CuePackage, error) {
// Skip all files but CUE files.
exclude := []string{"*", "!**/*.cue"}
pkg, err := loader.SnapshotDir(ctx, schema.PackageName(pkgName), memfs.SnapshotOpts{ExcludePatterns: exclude})
if err != nil {
return nil, err
}
fifs, err := fs.ReadDir(pkg.Snapshot, pkg.RelPath)
if err != nil {
return nil, err
}
// We go wide here and don't take packages into account. Packages are then filtered while building.
var files []string
for _, f := range fifs {
if f.IsDir() || filepath.Ext(f.Name()) != ".cue" {
continue
}
files = append(files, f.Name())
}
return &CuePackage{
PackageContents: pkg,
Files: files,
}, nil
}