github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/buildplan/artifact.go (about) 1 package buildplan 2 3 import ( 4 "reflect" 5 "sort" 6 7 "github.com/ActiveState/cli/internal/logging" 8 "github.com/ActiveState/cli/internal/sliceutils" 9 "github.com/ActiveState/cli/pkg/buildplan/raw" 10 "github.com/ActiveState/cli/pkg/platform/api/buildplanner/types" 11 "github.com/go-openapi/strfmt" 12 ) 13 14 // Artifact represents a downloadable artifact. 15 // This artifact may or may not be installable by the State Tool. 16 type Artifact struct { 17 raw *raw.Artifact // Don't expose as this may lead to external packages using low level buildplan logic 18 19 ArtifactID strfmt.UUID 20 DisplayName string 21 MimeType string 22 URL string 23 LogURL string 24 Errors []string 25 Checksum string 26 Status string 27 28 Ingredients []*Ingredient `json:"-"` // While most artifacts only have a single ingredient, some artifacts such as installers can have multiple. 29 30 isRuntimeDependency bool 31 isBuildtimeDependency bool 32 33 platforms []strfmt.UUID 34 children []ArtifactRelation 35 } 36 37 type Relation int 38 39 const ( 40 RuntimeRelation Relation = iota 41 BuildtimeRelation 42 ) 43 44 type ArtifactRelation struct { 45 Artifact *Artifact 46 Relation Relation 47 } 48 49 // Name returns the name of the ingredient for this artifact, if it only has exactly one ingredient associated. 50 // Otherwise it returns the DisplayName, which is less reliable and consistent. 51 func (a *Artifact) Name() string { 52 if len(a.Ingredients) == 1 { 53 return a.Ingredients[0].Name 54 } 55 logging.Debug("Using displayname because artifact has %d ingredients", len(a.Ingredients)) 56 return a.DisplayName 57 } 58 59 // Version returns the name of the ingredient for this artifact, if it only has exactly one ingredient associated. 60 // Otherwise it returns an empty version. 61 func (a *Artifact) Version() string { 62 if len(a.Ingredients) == 1 { 63 return a.Ingredients[0].Version 64 } 65 return "" 66 } 67 68 func (a *Artifact) NameAndVersion() string { 69 version := a.Version() 70 if version == "" { 71 return a.Name() 72 } 73 return a.Name() + "@" + version 74 } 75 76 type Artifacts []*Artifact 77 78 type ArtifactIDMap map[strfmt.UUID]*Artifact 79 80 type ArtifactNameMap map[string]*Artifact 81 82 func (a Artifacts) Filter(filters ...FilterArtifact) Artifacts { 83 if len(filters) == 0 { 84 return a 85 } 86 artifacts := []*Artifact{} 87 for _, ar := range a { 88 include := true 89 for _, filter := range filters { 90 if !filter(ar) { 91 include = false 92 break 93 } 94 } 95 if include { 96 artifacts = append(artifacts, ar) 97 } 98 } 99 return artifacts 100 } 101 102 func (a Artifacts) Ingredients() Ingredients { 103 result := Ingredients{} 104 for _, a := range a { 105 result = append(result, a.Ingredients...) 106 } 107 return sliceutils.Unique(result) 108 } 109 110 func (a Artifacts) ToIDMap() ArtifactIDMap { 111 result := make(map[strfmt.UUID]*Artifact, len(a)) 112 for _, a := range a { 113 result[a.ArtifactID] = a 114 } 115 return result 116 } 117 118 func (a Artifacts) ToIDSlice() []strfmt.UUID { 119 result := make([]strfmt.UUID, len(a)) 120 for n, a := range a { 121 result[n] = a.ArtifactID 122 } 123 return result 124 } 125 126 func (a Artifacts) ToNameMap() ArtifactNameMap { 127 result := make(map[string]*Artifact, len(a)) 128 for _, a := range a { 129 name := a.DisplayName 130 if len(a.Ingredients) == 0 { 131 name = a.Ingredients[0].Name 132 } 133 result[name] = a 134 } 135 return result 136 } 137 138 type ArtifactChangeset struct { 139 Added []*Artifact 140 Removed []*Artifact 141 Updated []ArtifactUpdate 142 } 143 144 type ArtifactUpdate struct { 145 From *Artifact 146 To *Artifact 147 } 148 149 func (a ArtifactUpdate) VersionsChanged() bool { 150 fromVersions := []string{} 151 for _, ing := range a.From.Ingredients { 152 fromVersions = append(fromVersions, ing.Version) 153 } 154 sort.Strings(fromVersions) 155 toVersions := []string{} 156 for _, ing := range a.To.Ingredients { 157 toVersions = append(toVersions, ing.Version) 158 } 159 sort.Strings(toVersions) 160 161 return !reflect.DeepEqual(fromVersions, toVersions) 162 } 163 164 func (as Artifacts) RuntimeDependencies(recursive bool) Artifacts { 165 seen := make(map[strfmt.UUID]struct{}) 166 dependencies := Artifacts{} 167 for _, a := range as { 168 dependencies = append(dependencies, a.runtimeDependencies(recursive, seen)...) 169 } 170 return dependencies 171 } 172 173 func (a *Artifact) RuntimeDependencies(recursive bool) Artifacts { 174 return a.runtimeDependencies(recursive, make(map[strfmt.UUID]struct{})) 175 } 176 177 func (a *Artifact) runtimeDependencies(recursive bool, seen map[strfmt.UUID]struct{}) Artifacts { 178 // Guard against recursion, this shouldn't really be possible but we don't know how the buildplan might evolve 179 // so better safe than sorry. 180 if _, ok := seen[a.ArtifactID]; ok { 181 return Artifacts{} 182 } 183 seen[a.ArtifactID] = struct{}{} 184 185 dependencies := Artifacts{} 186 for _, ac := range a.children { 187 if ac.Relation != RuntimeRelation { 188 continue 189 } 190 dependencies = append(dependencies, ac.Artifact) 191 if recursive { 192 dependencies = append(dependencies, ac.Artifact.RuntimeDependencies(recursive)...) 193 } 194 } 195 return dependencies 196 } 197 198 // SetDownload is used to update the URL and checksum of an artifact. This allows us to keep using the same artifact 199 // type, while also facilitating dressing up in-progress artifacts with their download info later on 200 func (a *Artifact) SetDownload(uri string, checksum string) { 201 a.URL = uri 202 a.Checksum = checksum 203 a.Status = types.ArtifactSucceeded 204 }