forked from cloudfoundry/bosh-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dependency_compiler.go
142 lines (112 loc) · 4.39 KB
/
dependency_compiler.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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package job
import (
"fmt"
"strings"
bosherr "github.com/cloudfoundry/bosh-utils/errors"
boshlog "github.com/cloudfoundry/bosh-utils/logger"
bireljob "github.com/cloudfoundry/bosh-cli/release/job"
birelpkg "github.com/cloudfoundry/bosh-cli/release/pkg"
bistatepkg "github.com/cloudfoundry/bosh-cli/state/pkg"
biui "github.com/cloudfoundry/bosh-cli/ui"
)
type CompiledPackageRef struct {
Name string
Version string
BlobstoreID string
SHA1 string
}
type DependencyCompiler interface {
Compile([]bireljob.Job, biui.Stage) ([]CompiledPackageRef, error)
}
type dependencyCompiler struct {
packageCompiler bistatepkg.Compiler
logTag string
logger boshlog.Logger
}
func NewDependencyCompiler(packageCompiler bistatepkg.Compiler, logger boshlog.Logger) DependencyCompiler {
return &dependencyCompiler{
packageCompiler: packageCompiler,
logTag: "dependencyCompiler",
logger: logger,
}
}
// Compile resolves and compiles all transitive dependencies of multiple release jobs
func (c *dependencyCompiler) Compile(jobs []bireljob.Job, stage biui.Stage) ([]CompiledPackageRef, error) {
compileOrderReleasePackages, err := c.resolveJobCompilationDependencies(jobs)
if err != nil {
return nil, bosherr.WrapError(err, "Resolving job package dependencies")
}
compiledPackageRefs, err := c.compilePackages(compileOrderReleasePackages, stage)
if err != nil {
return nil, bosherr.WrapError(err, "Compiling job package dependencies")
}
return compiledPackageRefs, nil
}
// resolveJobPackageCompilationDependencies returns all packages required by all specified jobs, in compilation order (reverse dependency order)
func (c *dependencyCompiler) resolveJobCompilationDependencies(jobs []bireljob.Job) ([]birelpkg.Compilable, error) {
// collect and de-dupe all required packages (dependencies of jobs)
packageMap := map[string]birelpkg.Compilable{}
for _, releaseJob := range jobs {
for _, releasePackage := range releaseJob.Packages {
pkgKey := c.pkgKey(releasePackage)
packageMap[pkgKey] = releasePackage
c.resolvePackageDependencies(releasePackage, packageMap)
}
}
// flatten map values to array
packages := make([]birelpkg.Compilable, 0, len(packageMap))
for _, releasePackage := range packageMap {
packages = append(packages, releasePackage)
}
// sort in compilation order
sortedPackages, err := birelpkg.Sort(packages)
if err != nil {
return nil, err
}
pkgs := []string{}
for _, pkg := range sortedPackages {
pkgs = append(pkgs, fmt.Sprintf("%s/%s", pkg.Name(), pkg.Fingerprint()))
}
c.logger.Debug(c.logTag, "Sorted dependencies:\n%s", strings.Join(pkgs, "\n"))
return sortedPackages, nil
}
// resolvePackageDependencies adds the releasePackage's dependencies to the packageMap recursively
func (c *dependencyCompiler) resolvePackageDependencies(releasePackage birelpkg.Compilable, packageMap map[string]birelpkg.Compilable) {
for _, dependency := range releasePackage.Deps() {
// only add un-added packages, to avoid endless looping in case of cycles
pkgKey := c.pkgKey(dependency)
if _, found := packageMap[pkgKey]; !found {
packageMap[pkgKey] = dependency
c.resolvePackageDependencies(dependency, packageMap)
}
}
}
// compilePackages compiles the specified packages, in the order specified, uploads them to the Blobstore, and returns the blob references
func (c *dependencyCompiler) compilePackages(requiredPackages []birelpkg.Compilable, stage biui.Stage) ([]CompiledPackageRef, error) {
packageRefs := make([]CompiledPackageRef, 0, len(requiredPackages))
for _, pkg := range requiredPackages {
stepName := fmt.Sprintf("Compiling package '%s/%s'", pkg.Name(), pkg.Fingerprint())
err := stage.Perform(stepName, func() error {
compiledPackageRecord, isAlreadyCompiled, err := c.packageCompiler.Compile(pkg)
if err != nil {
return err
}
packageRef := CompiledPackageRef{
Name: pkg.Name(),
Version: pkg.Fingerprint(),
BlobstoreID: compiledPackageRecord.BlobID,
SHA1: compiledPackageRecord.BlobSHA1,
}
packageRefs = append(packageRefs, packageRef)
if isAlreadyCompiled {
return biui.NewSkipStageError(bosherr.Error(fmt.Sprintf("Package '%s' is already compiled. Skipped compilation", pkg.Name())), "Package already compiled")
}
return nil
})
if err != nil {
return nil, err
}
}
return packageRefs, nil
}
func (c *dependencyCompiler) pkgKey(pkg birelpkg.Compilable) string { return pkg.Name() }