Skip to content

Commit

Permalink
Merge branch 'version/0-47-0-RC1'
Browse files Browse the repository at this point in the history
  • Loading branch information
Naatan committed Sep 20, 2024
2 parents 97e4b18 + d627c74 commit 6f13e77
Show file tree
Hide file tree
Showing 13 changed files with 366 additions and 114 deletions.
15 changes: 11 additions & 4 deletions internal/runbits/dependencies/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func OutputSummary(out output.Outputer, directDependencies buildplan.Artifacts)
}

ingredients := directDependencies.Filter(buildplan.FilterStateArtifacts()).Ingredients()
commonDependencies := ingredients.CommonRuntimeDependencies().ToIDMap()

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

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

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

out.Notice(fmt.Sprintf("[DISABLED]%s[/RESET] %s", prefix, item))
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/buildplan/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ func (a *Artifact) dependencies(recursive bool, maybeIgnore *map[strfmt.UUID]str
for _, ac := range a.children {
related := len(relations) == 0
for _, relation := range relations {
if ac.Relation == relation && raw.IsStateToolMimeType(ac.Artifact.MimeType) {
if ac.Relation == relation {
related = true
}
}
Expand Down
32 changes: 27 additions & 5 deletions pkg/buildplan/buildplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func Unmarshal(data []byte) (*BuildPlan, error) {

b.raw = &rawBuild

b.cleanup()
b.sanitize()

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

func (b *BuildPlan) Marshal() ([]byte, error) {
return json.Marshal(b.raw)
return json.MarshalIndent(b.raw, "", " ")
}

// cleanup empty targets
// The type aliasing in the query populates the response with emtpy targets that we should remove
func (b *BuildPlan) cleanup() {
// sanitize will remove empty targets and sort slices to ensure consistent interpretation of the same buildplan
// Empty targets: The type aliasing in the query populates the response with emtpy targets that we should remove
// Sorting: The API does not do any slice ordering, meaning the same buildplan retrieved twice can use different ordering
func (b *BuildPlan) sanitize() {
b.raw.Steps = sliceutils.Filter(b.raw.Steps, func(s *raw.Step) bool {
return s.StepID != ""
})
Expand All @@ -63,6 +64,27 @@ func (b *BuildPlan) cleanup() {
b.raw.Artifacts = sliceutils.Filter(b.raw.Artifacts, func(a *raw.Artifact) bool {
return a.NodeID != ""
})

sort.Slice(b.raw.Sources, func(i, j int) bool { return b.raw.Sources[i].NodeID < b.raw.Sources[j].NodeID })
sort.Slice(b.raw.Steps, func(i, j int) bool { return b.raw.Steps[i].StepID < b.raw.Steps[j].StepID })
sort.Slice(b.raw.Artifacts, func(i, j int) bool { return b.raw.Artifacts[i].NodeID < b.raw.Artifacts[j].NodeID })
sort.Slice(b.raw.Terminals, func(i, j int) bool { return b.raw.Terminals[i].Tag < b.raw.Terminals[j].Tag })
sort.Slice(b.raw.ResolvedRequirements, func(i, j int) bool {
return b.raw.ResolvedRequirements[i].Source < b.raw.ResolvedRequirements[j].Source
})
for _, t := range b.raw.Terminals {
sort.Slice(t.NodeIDs, func(i, j int) bool { return t.NodeIDs[i] < t.NodeIDs[j] })
}
for _, a := range b.raw.Artifacts {
sort.Slice(a.RuntimeDependencies, func(i, j int) bool { return a.RuntimeDependencies[i] < a.RuntimeDependencies[j] })
}
for _, step := range b.raw.Steps {
sort.Slice(step.Inputs, func(i, j int) bool { return step.Inputs[i].Tag < step.Inputs[j].Tag })
sort.Slice(step.Outputs, func(i, j int) bool { return step.Outputs[i] < step.Outputs[j] })
for _, input := range step.Inputs {
sort.Slice(input.NodeIDs, func(i, j int) bool { return input.NodeIDs[i] < input.NodeIDs[j] })
}
}
}

func (b *BuildPlan) Platforms() []strfmt.UUID {
Expand Down
9 changes: 9 additions & 0 deletions pkg/buildplan/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,12 @@ func FilterNotBuild() FilterArtifact {
return a.Status != types.ArtifactSucceeded
}
}

type FilterOutIngredients struct {
Ingredients IngredientIDMap
}

func (f FilterOutIngredients) Filter(i *Ingredient) bool {
_, blacklist := f.Ingredients[i.IngredientID]
return !blacklist
}
12 changes: 8 additions & 4 deletions pkg/buildplan/hydrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,6 @@ func (b *BuildPlan) hydrateWithIngredients(artifact *Artifact, platformID *strfm
err := b.raw.WalkViaSteps([]strfmt.UUID{artifact.ArtifactID}, raw.TagSource,
func(node interface{}, parent *raw.Artifact) error {
switch v := node.(type) {
case *raw.Artifact:
return nil // We've already got our artifacts
case *raw.Source:
// logging.Debug("Walking source '%s (%s)'", v.Name, v.NodeID)

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

return nil
default:
return errs.New("unexpected node type '%T': %#v", v, v)
if a, ok := v.(*raw.Artifact); ok && a.NodeID == artifact.ArtifactID {
return nil // continue
}
// Source ingredients are only relevant when they link DIRECTLY to the artifact
return raw.WalkInterrupt{}
}

return nil
})
if err != nil {
return errs.Wrap(err, "error hydrating ingredients")
Expand All @@ -210,7 +214,7 @@ func (b *BuildPlan) sanityCheck() error {
// Ensure all artifacts have an associated ingredient
// If this fails either the API is bugged or the hydrate logic is bugged
for _, a := range b.Artifacts() {
if len(a.Ingredients) == 0 {
if raw.IsStateToolMimeType(a.MimeType) && len(a.Ingredients) == 0 {
return errs.New("artifact '%s (%s)' does not have an ingredient", a.ArtifactID, a.DisplayName)
}
}
Expand Down
50 changes: 50 additions & 0 deletions pkg/buildplan/hydrate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package buildplan

import (
"testing"

"github.com/ActiveState/cli/internal/errs"
"github.com/ActiveState/cli/pkg/buildplan/mock"
"github.com/go-openapi/strfmt"
"github.com/stretchr/testify/require"
)

func TestBuildPlan_hydrateWithIngredients(t *testing.T) {
tests := []struct {
name string
buildplan *BuildPlan
inputArtifact *Artifact
wantIngredient string
}{
{
"Ingredient solves for simple artifact > src hop",
&BuildPlan{raw: mock.BuildWithRuntimeDepsViaSrc},
&Artifact{ArtifactID: "00000000-0000-0000-0000-000000000007"},
"00000000-0000-0000-0000-000000000009",
},
{
"Installer should not resolve to an ingredient as it doesn't have a direct source",
&BuildPlan{raw: mock.BuildWithRuntimeDepsViaSrc},
&Artifact{ArtifactID: "00000000-0000-0000-0000-000000000002"},
"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := tt.buildplan
if err := b.hydrateWithIngredients(tt.inputArtifact, nil, map[strfmt.UUID]*Ingredient{}); err != nil {
t.Fatalf("hydrateWithIngredients() error = %v", errs.JoinMessage(err))
}
if tt.wantIngredient == "" {
require.Empty(t, tt.inputArtifact.Ingredients)
return
}
if len(tt.inputArtifact.Ingredients) != 1 {
t.Fatalf("expected 1 ingredient resolution, got %d", len(tt.inputArtifact.Ingredients))
}
if string(tt.inputArtifact.Ingredients[0].IngredientID) != tt.wantIngredient {
t.Errorf("expected ingredient ID %s, got %s", tt.wantIngredient, tt.inputArtifact.Ingredients[0].IngredientID)
}
})
}
}
28 changes: 17 additions & 11 deletions pkg/buildplan/ingredient.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Ingredient struct {

IsBuildtimeDependency bool
IsRuntimeDependency bool
Artifacts []*Artifact
Artifacts Artifacts

platforms []strfmt.UUID
}
Expand Down Expand Up @@ -61,7 +61,20 @@ func (i Ingredients) ToNameMap() IngredientNameMap {
// CommonRuntimeDependencies returns the set of runtime dependencies that are common between all ingredients.
// For example, given a set of python ingredients this will return at the very least the python language ingredient.
func (i Ingredients) CommonRuntimeDependencies() Ingredients {
var is []ingredientsWithRuntimeDeps
for _, ig := range i {
is = append(is, ig)
}
return commonRuntimeDependencies(is)
}

type ingredientsWithRuntimeDeps interface {
RuntimeDependencies(recursive bool) Ingredients
}

func commonRuntimeDependencies(i []ingredientsWithRuntimeDeps) Ingredients {
counts := map[strfmt.UUID]int{}
common := Ingredients{}

for _, ig := range i {
runtimeDeps := ig.RuntimeDependencies(true)
Expand All @@ -70,13 +83,9 @@ func (i Ingredients) CommonRuntimeDependencies() Ingredients {
counts[rd.IngredientID] = 0
}
counts[rd.IngredientID]++
}
}

common := Ingredients{}
for _, ig := range i {
if counts[ig.IngredientID] == len(i) {
common = append(common, ig)
if counts[rd.IngredientID] == 2 { // only append on 2; we don't want dupes
common = append(common, rd)
}
}
}

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

dependencies := Ingredients{}
for _, a := range i.Artifacts {
if !raw.IsStateToolMimeType(a.MimeType) {
continue
}
for _, ac := range a.children {
if ac.Relation != RuntimeRelation {
continue
Expand Down
54 changes: 54 additions & 0 deletions pkg/buildplan/ingredient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package buildplan

import (
"reflect"
"sort"
"testing"

"github.com/ActiveState/cli/pkg/buildplan/raw"
)

func TestIngredient_RuntimeDependencies(t *testing.T) {
Expand Down Expand Up @@ -55,3 +58,54 @@ func TestIngredient_RuntimeDependencies(t *testing.T) {
})
}
}

type mockIngredient struct {
deps Ingredients
}

func (m mockIngredient) RuntimeDependencies(recursive bool) Ingredients {
return m.deps
}

func TestIngredients_CommonRuntimeDependencies(t *testing.T) {
tests := []struct {
name string
i []ingredientsWithRuntimeDeps
want []string
}{
{
"Simple",
[]ingredientsWithRuntimeDeps{
mockIngredient{
deps: Ingredients{
{
IngredientSource: &raw.IngredientSource{IngredientID: "sub-ingredient-1"},
},
},
},
mockIngredient{
deps: Ingredients{
{
IngredientSource: &raw.IngredientSource{IngredientID: "sub-ingredient-1"},
},
},
},
},
[]string{"sub-ingredient-1"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := commonRuntimeDependencies(tt.i)
gotIDs := []string{}
for _, i := range got {
gotIDs = append(gotIDs, string(i.IngredientID))
}
sort.Strings(gotIDs)
sort.Strings(tt.want)
if !reflect.DeepEqual(gotIDs, tt.want) {
t.Errorf("Ingredients.CommonRuntimeDependencies() = %v, want %v", gotIDs, tt.want)
}
})
}
}
Loading

0 comments on commit 6f13e77

Please sign in to comment.