Skip to content

Commit 6f13e77

Browse files
committed
Merge branch 'version/0-47-0-RC1'
2 parents 97e4b18 + d627c74 commit 6f13e77

File tree

13 files changed

+366
-114
lines changed

13 files changed

+366
-114
lines changed

internal/runbits/dependencies/summary.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func OutputSummary(out output.Outputer, directDependencies buildplan.Artifacts)
1818
}
1919

2020
ingredients := directDependencies.Filter(buildplan.FilterStateArtifacts()).Ingredients()
21+
commonDependencies := ingredients.CommonRuntimeDependencies().ToIDMap()
2122

2223
sort.SliceStable(ingredients, func(i, j int) bool {
2324
return ingredients[i].Name < ingredients[j].Name
@@ -32,12 +33,18 @@ func OutputSummary(out output.Outputer, directDependencies buildplan.Artifacts)
3233
prefix = " " + output.TreeEnd
3334
}
3435

35-
subdependencies := ""
36-
if numSubs := len(ingredient.RuntimeDependencies(true)); numSubs > 0 {
37-
subdependencies = locale.Tl("summary_subdeps", "([ACTIONABLE]{{.V0}}[/RESET] sub-dependencies)", strconv.Itoa(numSubs))
36+
subDependencies := ingredient.RuntimeDependencies(true)
37+
if _, isCommon := commonDependencies[ingredient.IngredientID]; !isCommon {
38+
// If the ingredient is itself not a common sub-dependency; filter out any common sub dependencies so we don't
39+
// report counts multiple times.
40+
subDependencies = subDependencies.Filter(buildplan.FilterOutIngredients{commonDependencies}.Filter)
41+
}
42+
subdepLocale := ""
43+
if numSubs := len(subDependencies); numSubs > 0 {
44+
subdepLocale = locale.Tl("summary_subdeps", "([ACTIONABLE]{{.V0}}[/RESET] sub-dependencies)", strconv.Itoa(numSubs))
3845
}
3946

40-
item := fmt.Sprintf("[ACTIONABLE]%s@%s[/RESET] %s", ingredient.Name, ingredient.Version, subdependencies)
47+
item := fmt.Sprintf("[ACTIONABLE]%s@%s[/RESET] %s", ingredient.Name, ingredient.Version, subdepLocale)
4148

4249
out.Notice(fmt.Sprintf("[DISABLED]%s[/RESET] %s", prefix, item))
4350
}

pkg/buildplan/artifact.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ func (a *Artifact) dependencies(recursive bool, maybeIgnore *map[strfmt.UUID]str
259259
for _, ac := range a.children {
260260
related := len(relations) == 0
261261
for _, relation := range relations {
262-
if ac.Relation == relation && raw.IsStateToolMimeType(ac.Artifact.MimeType) {
262+
if ac.Relation == relation {
263263
related = true
264264
}
265265
}

pkg/buildplan/buildplan.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func Unmarshal(data []byte) (*BuildPlan, error) {
3131

3232
b.raw = &rawBuild
3333

34-
b.cleanup()
34+
b.sanitize()
3535

3636
if err := b.hydrate(); err != nil {
3737
return nil, errs.Wrap(err, "error hydrating build plan")
@@ -46,12 +46,13 @@ func Unmarshal(data []byte) (*BuildPlan, error) {
4646
}
4747

4848
func (b *BuildPlan) Marshal() ([]byte, error) {
49-
return json.Marshal(b.raw)
49+
return json.MarshalIndent(b.raw, "", " ")
5050
}
5151

52-
// cleanup empty targets
53-
// The type aliasing in the query populates the response with emtpy targets that we should remove
54-
func (b *BuildPlan) cleanup() {
52+
// sanitize will remove empty targets and sort slices to ensure consistent interpretation of the same buildplan
53+
// Empty targets: The type aliasing in the query populates the response with emtpy targets that we should remove
54+
// Sorting: The API does not do any slice ordering, meaning the same buildplan retrieved twice can use different ordering
55+
func (b *BuildPlan) sanitize() {
5556
b.raw.Steps = sliceutils.Filter(b.raw.Steps, func(s *raw.Step) bool {
5657
return s.StepID != ""
5758
})
@@ -63,6 +64,27 @@ func (b *BuildPlan) cleanup() {
6364
b.raw.Artifacts = sliceutils.Filter(b.raw.Artifacts, func(a *raw.Artifact) bool {
6465
return a.NodeID != ""
6566
})
67+
68+
sort.Slice(b.raw.Sources, func(i, j int) bool { return b.raw.Sources[i].NodeID < b.raw.Sources[j].NodeID })
69+
sort.Slice(b.raw.Steps, func(i, j int) bool { return b.raw.Steps[i].StepID < b.raw.Steps[j].StepID })
70+
sort.Slice(b.raw.Artifacts, func(i, j int) bool { return b.raw.Artifacts[i].NodeID < b.raw.Artifacts[j].NodeID })
71+
sort.Slice(b.raw.Terminals, func(i, j int) bool { return b.raw.Terminals[i].Tag < b.raw.Terminals[j].Tag })
72+
sort.Slice(b.raw.ResolvedRequirements, func(i, j int) bool {
73+
return b.raw.ResolvedRequirements[i].Source < b.raw.ResolvedRequirements[j].Source
74+
})
75+
for _, t := range b.raw.Terminals {
76+
sort.Slice(t.NodeIDs, func(i, j int) bool { return t.NodeIDs[i] < t.NodeIDs[j] })
77+
}
78+
for _, a := range b.raw.Artifacts {
79+
sort.Slice(a.RuntimeDependencies, func(i, j int) bool { return a.RuntimeDependencies[i] < a.RuntimeDependencies[j] })
80+
}
81+
for _, step := range b.raw.Steps {
82+
sort.Slice(step.Inputs, func(i, j int) bool { return step.Inputs[i].Tag < step.Inputs[j].Tag })
83+
sort.Slice(step.Outputs, func(i, j int) bool { return step.Outputs[i] < step.Outputs[j] })
84+
for _, input := range step.Inputs {
85+
sort.Slice(input.NodeIDs, func(i, j int) bool { return input.NodeIDs[i] < input.NodeIDs[j] })
86+
}
87+
}
6688
}
6789

6890
func (b *BuildPlan) Platforms() []strfmt.UUID {

pkg/buildplan/filters.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,12 @@ func FilterNotBuild() FilterArtifact {
7676
return a.Status != types.ArtifactSucceeded
7777
}
7878
}
79+
80+
type FilterOutIngredients struct {
81+
Ingredients IngredientIDMap
82+
}
83+
84+
func (f FilterOutIngredients) Filter(i *Ingredient) bool {
85+
_, blacklist := f.Ingredients[i.IngredientID]
86+
return !blacklist
87+
}

pkg/buildplan/hydrate.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,6 @@ func (b *BuildPlan) hydrateWithIngredients(artifact *Artifact, platformID *strfm
154154
err := b.raw.WalkViaSteps([]strfmt.UUID{artifact.ArtifactID}, raw.TagSource,
155155
func(node interface{}, parent *raw.Artifact) error {
156156
switch v := node.(type) {
157-
case *raw.Artifact:
158-
return nil // We've already got our artifacts
159157
case *raw.Source:
160158
// logging.Debug("Walking source '%s (%s)'", v.Name, v.NodeID)
161159

@@ -193,8 +191,14 @@ func (b *BuildPlan) hydrateWithIngredients(artifact *Artifact, platformID *strfm
193191

194192
return nil
195193
default:
196-
return errs.New("unexpected node type '%T': %#v", v, v)
194+
if a, ok := v.(*raw.Artifact); ok && a.NodeID == artifact.ArtifactID {
195+
return nil // continue
196+
}
197+
// Source ingredients are only relevant when they link DIRECTLY to the artifact
198+
return raw.WalkInterrupt{}
197199
}
200+
201+
return nil
198202
})
199203
if err != nil {
200204
return errs.Wrap(err, "error hydrating ingredients")
@@ -210,7 +214,7 @@ func (b *BuildPlan) sanityCheck() error {
210214
// Ensure all artifacts have an associated ingredient
211215
// If this fails either the API is bugged or the hydrate logic is bugged
212216
for _, a := range b.Artifacts() {
213-
if len(a.Ingredients) == 0 {
217+
if raw.IsStateToolMimeType(a.MimeType) && len(a.Ingredients) == 0 {
214218
return errs.New("artifact '%s (%s)' does not have an ingredient", a.ArtifactID, a.DisplayName)
215219
}
216220
}

pkg/buildplan/hydrate_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package buildplan
2+
3+
import (
4+
"testing"
5+
6+
"github.com/ActiveState/cli/internal/errs"
7+
"github.com/ActiveState/cli/pkg/buildplan/mock"
8+
"github.com/go-openapi/strfmt"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestBuildPlan_hydrateWithIngredients(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
buildplan *BuildPlan
16+
inputArtifact *Artifact
17+
wantIngredient string
18+
}{
19+
{
20+
"Ingredient solves for simple artifact > src hop",
21+
&BuildPlan{raw: mock.BuildWithRuntimeDepsViaSrc},
22+
&Artifact{ArtifactID: "00000000-0000-0000-0000-000000000007"},
23+
"00000000-0000-0000-0000-000000000009",
24+
},
25+
{
26+
"Installer should not resolve to an ingredient as it doesn't have a direct source",
27+
&BuildPlan{raw: mock.BuildWithRuntimeDepsViaSrc},
28+
&Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002"},
29+
"",
30+
},
31+
}
32+
for _, tt := range tests {
33+
t.Run(tt.name, func(t *testing.T) {
34+
b := tt.buildplan
35+
if err := b.hydrateWithIngredients(tt.inputArtifact, nil, map[strfmt.UUID]*Ingredient{}); err != nil {
36+
t.Fatalf("hydrateWithIngredients() error = %v", errs.JoinMessage(err))
37+
}
38+
if tt.wantIngredient == "" {
39+
require.Empty(t, tt.inputArtifact.Ingredients)
40+
return
41+
}
42+
if len(tt.inputArtifact.Ingredients) != 1 {
43+
t.Fatalf("expected 1 ingredient resolution, got %d", len(tt.inputArtifact.Ingredients))
44+
}
45+
if string(tt.inputArtifact.Ingredients[0].IngredientID) != tt.wantIngredient {
46+
t.Errorf("expected ingredient ID %s, got %s", tt.wantIngredient, tt.inputArtifact.Ingredients[0].IngredientID)
47+
}
48+
})
49+
}
50+
}

pkg/buildplan/ingredient.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type Ingredient struct {
1111

1212
IsBuildtimeDependency bool
1313
IsRuntimeDependency bool
14-
Artifacts []*Artifact
14+
Artifacts Artifacts
1515

1616
platforms []strfmt.UUID
1717
}
@@ -61,7 +61,20 @@ func (i Ingredients) ToNameMap() IngredientNameMap {
6161
// CommonRuntimeDependencies returns the set of runtime dependencies that are common between all ingredients.
6262
// For example, given a set of python ingredients this will return at the very least the python language ingredient.
6363
func (i Ingredients) CommonRuntimeDependencies() Ingredients {
64+
var is []ingredientsWithRuntimeDeps
65+
for _, ig := range i {
66+
is = append(is, ig)
67+
}
68+
return commonRuntimeDependencies(is)
69+
}
70+
71+
type ingredientsWithRuntimeDeps interface {
72+
RuntimeDependencies(recursive bool) Ingredients
73+
}
74+
75+
func commonRuntimeDependencies(i []ingredientsWithRuntimeDeps) Ingredients {
6476
counts := map[strfmt.UUID]int{}
77+
common := Ingredients{}
6578

6679
for _, ig := range i {
6780
runtimeDeps := ig.RuntimeDependencies(true)
@@ -70,13 +83,9 @@ func (i Ingredients) CommonRuntimeDependencies() Ingredients {
7083
counts[rd.IngredientID] = 0
7184
}
7285
counts[rd.IngredientID]++
73-
}
74-
}
75-
76-
common := Ingredients{}
77-
for _, ig := range i {
78-
if counts[ig.IngredientID] == len(i) {
79-
common = append(common, ig)
86+
if counts[rd.IngredientID] == 2 { // only append on 2; we don't want dupes
87+
common = append(common, rd)
88+
}
8089
}
8190
}
8291

@@ -97,9 +106,6 @@ func (i *Ingredient) runtimeDependencies(recursive bool, seen map[strfmt.UUID]st
97106

98107
dependencies := Ingredients{}
99108
for _, a := range i.Artifacts {
100-
if !raw.IsStateToolMimeType(a.MimeType) {
101-
continue
102-
}
103109
for _, ac := range a.children {
104110
if ac.Relation != RuntimeRelation {
105111
continue

pkg/buildplan/ingredient_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package buildplan
22

33
import (
44
"reflect"
5+
"sort"
56
"testing"
7+
8+
"github.com/ActiveState/cli/pkg/buildplan/raw"
69
)
710

811
func TestIngredient_RuntimeDependencies(t *testing.T) {
@@ -55,3 +58,54 @@ func TestIngredient_RuntimeDependencies(t *testing.T) {
5558
})
5659
}
5760
}
61+
62+
type mockIngredient struct {
63+
deps Ingredients
64+
}
65+
66+
func (m mockIngredient) RuntimeDependencies(recursive bool) Ingredients {
67+
return m.deps
68+
}
69+
70+
func TestIngredients_CommonRuntimeDependencies(t *testing.T) {
71+
tests := []struct {
72+
name string
73+
i []ingredientsWithRuntimeDeps
74+
want []string
75+
}{
76+
{
77+
"Simple",
78+
[]ingredientsWithRuntimeDeps{
79+
mockIngredient{
80+
deps: Ingredients{
81+
{
82+
IngredientSource: &raw.IngredientSource{IngredientID: "sub-ingredient-1"},
83+
},
84+
},
85+
},
86+
mockIngredient{
87+
deps: Ingredients{
88+
{
89+
IngredientSource: &raw.IngredientSource{IngredientID: "sub-ingredient-1"},
90+
},
91+
},
92+
},
93+
},
94+
[]string{"sub-ingredient-1"},
95+
},
96+
}
97+
for _, tt := range tests {
98+
t.Run(tt.name, func(t *testing.T) {
99+
got := commonRuntimeDependencies(tt.i)
100+
gotIDs := []string{}
101+
for _, i := range got {
102+
gotIDs = append(gotIDs, string(i.IngredientID))
103+
}
104+
sort.Strings(gotIDs)
105+
sort.Strings(tt.want)
106+
if !reflect.DeepEqual(gotIDs, tt.want) {
107+
t.Errorf("Ingredients.CommonRuntimeDependencies() = %v, want %v", gotIDs, tt.want)
108+
}
109+
})
110+
}
111+
}

0 commit comments

Comments
 (0)