go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/vpython/spec/spec.go (about) 1 // Copyright 2017 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package spec contains implementation for search, parse, normalize vpython 16 // spec. 17 package spec 18 19 import ( 20 "sort" 21 22 "go.chromium.org/luci/common/data/sortby" 23 "go.chromium.org/luci/common/errors" 24 25 "go.chromium.org/luci/vpython/api/vpython" 26 ) 27 28 // NormalizeSpec normalizes the specification Message such that two messages 29 // with identical meaning will have identical representation. 30 // 31 // If multiple wheel entries exist for the same package name, they must also 32 // share a version. If they don't, an error will be returned. Otherwise, they 33 // will be merged into a single wheel entry. 34 // 35 // NormalizeSpec will prune any Wheel entries that don't match the specified 36 // tags, and will remove the match entries from any remaining Wheel entries. 37 func NormalizeSpec(spec *vpython.Spec, tags []*vpython.PEP425Tag) error { 38 // Apply match filters, prune any entries that don't match, and clear the 39 // MatchTag entries for those that do. 40 // 41 // Make sure the VirtualEnv package isn't listed in the wheels list. 42 // 43 // Track the versions for each package and assert that any duplicate packages 44 // don't share a version. 45 pos := 0 46 packageVersions := make(map[string]string, len(spec.Wheel)) 47 for _, w := range spec.Wheel { 48 // If this package doesn't match the tag set, skip it. 49 if !PackageMatches(w, tags) { 50 continue 51 } 52 53 // If this package has already been included, assert version consistency. 54 if v, ok := packageVersions[w.Name]; ok { 55 if v != w.Version { 56 return errors.Reason("multiple versions for package %q: %q != %q", w.Name, w.Version, v).Err() 57 } 58 59 // This package has already been included, so we can ignore it. 60 continue 61 } 62 63 // Mark that this package was included, so we can assert version consistency 64 // and avoid duplicates. 65 packageVersions[w.Name] = w.Version 66 w.MatchTag = nil 67 spec.Wheel[pos] = w 68 pos++ 69 } 70 spec.Wheel = spec.Wheel[:pos] 71 sort.Sort(specPackageSlice(spec.Wheel)) 72 return nil 73 } 74 75 type specPackageSlice []*vpython.Spec_Package 76 77 func (s specPackageSlice) Len() int { return len(s) } 78 func (s specPackageSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 79 80 func (s specPackageSlice) Less(i, j int) bool { 81 return sortby.Chain{ 82 func(i, j int) bool { return s[i].Name < s[j].Name }, 83 func(i, j int) bool { return s[i].Version < s[j].Version }, 84 }.Use(i, j) 85 }