forked from pgpkg/pgpkg
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcache.go
More file actions
230 lines (186 loc) · 5.76 KB
/
cache.go
File metadata and controls
230 lines (186 loc) · 5.76 KB
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package pgpkg
import (
"errors"
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
)
// Cache represents a dependency cache which contains the source of any dependencies listed in
// the "Uses" section of pgpkg.toml.
//
// Users can manually import project dependencies into the project cache with `pgpkg import`.
type Cache interface {
GetCachedSource(pkgName string) (Source, error)
}
type ReadCache struct {
fs fs.FS // Filesystem derived from the root directory
}
type WriteCache struct {
ReadCache
dir string // root directory of the cache, in local filesystem. Typically rooted at the ".pgpkg" directory.
}
var CachePkgNotFound = errors.New("package not found in cache")
func NewWriteCache(dir string) *WriteCache {
return &WriteCache{dir: dir, ReadCache: ReadCache{fs: os.DirFS(dir)}}
}
func NewReadCache(cfs fs.FS) *ReadCache {
return &ReadCache{fs: cfs}
}
func (c *ReadCache) GetCachedSource(pkgName string) (Source, error) {
pkgFs, err := fs.Sub(c.fs, pkgName)
if err != nil {
return nil, fmt.Errorf("error finding subfs: %w", err)
}
// Check that a package exists here
f, err := pkgFs.Open("pgpkg.toml")
if f != nil {
// we only got the file descriptor to test for existence of the file;
// we're not intending to use it here.
_ = f.Close()
}
if err == nil {
return NewFSSource(pkgFs, ".")
}
if os.IsNotExist(err) {
return nil, CachePkgNotFound
}
return nil, err
}
// Import the build units into the cache
func (c *WriteCache) importUnits(bundle *Bundle, cachePath string) error {
// List of directories we've already created. Note that MkdirAll doesn't
// return an error if a directory exists, so this cache is here simply to avoid calling that
// possibly expensive function more than necessary.
dirs := make(map[string]bool)
for _, unit := range bundle.Units {
unitpath := filepath.Join(cachePath, unit.Path)
if err := unit.Parse(); err != nil {
return fmt.Errorf("unable to parse %s: %w", unitpath, err)
}
unitDir := path.Dir(unitpath)
_, ok := dirs[unitDir]
if !ok {
if err := os.MkdirAll(unitDir, 0777); err != nil {
return err
}
dirs[unitDir] = true
}
uw, err := os.Create(unitpath)
if err != nil {
return fmt.Errorf("unable to create unit file %s: %w", unitpath, err)
}
if _, err := uw.Write([]byte(unit.Source)); err != nil {
_ = uw.Close()
return fmt.Errorf("unable to write unit file %s: %w", unitpath, err)
}
if err := uw.Close(); err != nil {
return err
}
}
return nil
}
// Import the migration file.
func (c *WriteCache) importMigration(srcPkg *Package, targetPath string) error {
srcSchema := srcPkg.Schema
if len(srcSchema.migrationIndex) == 0 {
return nil // no migration scripts; nothing to import.
}
// migrations are managed in the config file, nothing to see here
if len(srcPkg.config.Migrations) > 0 {
return nil
}
// This code can be removed when @migration.pgpkg is fully deprecated
filename := filepath.Join(targetPath, srcSchema.migrationDir, "/@migration.pgpkg")
dir := path.Dir(filename)
if err := os.MkdirAll(dir, 0777); err != nil {
return err
}
mw, err := os.Create(filename)
if err != nil {
return fmt.Errorf("unable to create migration file %s: %w", filename, err)
}
defer mw.Close()
for _, migtationPath := range srcSchema.migrationIndex {
if _, err := mw.Write([]byte(migtationPath + "\n")); err != nil {
return fmt.Errorf("unable to add path to migration file %s: %w", filename, err)
}
}
return nil
}
// RemovePackage removes (deletes) a package from the cache.
func (c *WriteCache) RemovePackage(pkgName string) error {
targetPath := path.Join(c.dir, pkgName)
return os.RemoveAll(targetPath)
}
// Import a single project into the cache.
func (c *WriteCache) importPackage(pkg *Package) error {
targetPath := path.Join(c.dir, pkg.Name)
// If the package being imported is a dependency (presumably of the imported project), and if it
// already exists in the cache, refuse to import it - since this could potentially downgrade the
// existing package.
if pkg.IsDependency {
_, err := os.Stat(targetPath)
if err == nil {
Stdout.Printf("dependency %s already imported, skipping\n", pkg.Name)
return nil
}
if !os.IsNotExist(err) {
return err
}
} else {
// If the package being imported is not a dependency then we can assume it's been
// imported directly, which forces the package to be replaced.
if err := c.RemovePackage(pkg.Name); err != nil {
return err
}
}
if err := os.MkdirAll(targetPath, 0777); err != nil {
return err
}
pkgConfigFile, err := os.Create(path.Join(targetPath, "pgpkg.toml"))
if err != nil {
return err
}
defer pkgConfigFile.Close()
if err := pkg.config.writeConfig(pkgConfigFile); err != nil {
return err
}
if err := c.importMigration(pkg, targetPath); err != nil {
return err // FIXME: add context
}
if err := c.importUnits(pkg.Schema.Bundle, targetPath); err != nil {
return err
}
if err := c.importUnits(pkg.MOB.Bundle, targetPath); err != nil {
return err
}
if err := c.importUnits(pkg.Tests.Bundle, targetPath); err != nil {
return err
}
return nil
}
// ImportProject imports the given project into the cache. If the project has dependencies,
// these are imported from the child project's cache, unless they are already present in the
// target cache.
func (c *WriteCache) ImportProject(srcProject *Project) error {
// Resolve dependencies on the target project.
if err := srcProject.resolveDependencies(); err != nil {
return err
}
// Load the packages before we do anything, in case there are problems.
if err := srcProject.parseSchemas(); err != nil {
return err
}
for _, pkg := range srcProject.pkgs {
// don't export pgpkg itself.
if pkg.Name == "github.com/pgpkg/pgpkg" {
continue
}
if err := c.importPackage(pkg); err != nil {
return err
}
}
return nil
}