| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2017 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. |
| 4 |
| 5 package wheel |
| 6 |
| 7 import ( |
| 8 "fmt" |
| 9 "os" |
| 10 "path/filepath" |
| 11 "strings" |
| 12 |
| 13 "github.com/luci/luci-go/common/errors" |
| 14 ) |
| 15 |
| 16 // Name is a parsed Python wheel name, defined here: |
| 17 // https://www.python.org/dev/peps/pep-0427/#file-name-convention |
| 18 // |
| 19 // {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-\ |
| 20 // {platform tag}.whl . |
| 21 type Name struct { |
| 22 Distribution string |
| 23 Version string |
| 24 BuildTag string |
| 25 PythonTag string |
| 26 ABITag string |
| 27 PlatformTag string |
| 28 } |
| 29 |
| 30 func (wn *Name) String() string { |
| 31 parts := make([]string, 0, 6) |
| 32 parts = append(parts, []string{ |
| 33 wn.Distribution, |
| 34 wn.Version, |
| 35 }...) |
| 36 if wn.BuildTag != "" { |
| 37 parts = append(parts, wn.BuildTag) |
| 38 } |
| 39 parts = append(parts, []string{ |
| 40 wn.PythonTag, |
| 41 wn.ABITag, |
| 42 wn.PlatformTag, |
| 43 }...) |
| 44 return strings.Join(parts, "-") + ".whl" |
| 45 } |
| 46 |
| 47 // ParseName parses a wheel Name from its filename. |
| 48 func ParseName(v string) (wn Name, err error) { |
| 49 base := strings.TrimSuffix(v, ".whl") |
| 50 if len(base) == len(v) { |
| 51 err = errors.Reason("missing .whl suffix").Err() |
| 52 return |
| 53 } |
| 54 |
| 55 skip := 0 |
| 56 switch parts := strings.Split(base, "-"); len(parts) { |
| 57 case 6: |
| 58 // Extra part: build tag. |
| 59 wn.BuildTag = parts[2] |
| 60 skip = 1 |
| 61 fallthrough |
| 62 |
| 63 case 5: |
| 64 wn.Distribution = parts[0] |
| 65 wn.Version = parts[1] |
| 66 wn.PythonTag = parts[2+skip] |
| 67 wn.ABITag = parts[3+skip] |
| 68 wn.PlatformTag = parts[4+skip] |
| 69 |
| 70 default: |
| 71 err = errors.Reason("unknown number of segments (%(segments)d)")
. |
| 72 D("segments", len(parts)). |
| 73 Err() |
| 74 return |
| 75 } |
| 76 return |
| 77 } |
| 78 |
| 79 // ScanDir identifies all wheel files in the immediate directory dir and |
| 80 // returns their parsed wheel names. |
| 81 func ScanDir(dir string) ([]Name, error) { |
| 82 globPattern := filepath.Join(dir, "*.whl") |
| 83 matches, err := filepath.Glob(globPattern) |
| 84 if err != nil { |
| 85 return nil, errors.Annotate(err).Reason("failed to list wheel di
rectory: %(dir)s"). |
| 86 D("dir", dir). |
| 87 D("pattern", globPattern). |
| 88 Err() |
| 89 } |
| 90 |
| 91 names := make([]Name, 0, len(matches)) |
| 92 for _, match := range matches { |
| 93 switch st, err := os.Stat(match); { |
| 94 case err != nil: |
| 95 return nil, errors.Annotate(err).Reason("failed to stat
wheel: %(path)s"). |
| 96 D("path", match). |
| 97 Err() |
| 98 |
| 99 case st.IsDir(): |
| 100 // Ignore directories. |
| 101 continue |
| 102 |
| 103 default: |
| 104 // A ".whl" file. |
| 105 name := filepath.Base(match) |
| 106 wheelName, err := ParseName(name) |
| 107 if err != nil { |
| 108 return nil, errors.Annotate(err).Reason("failed
to parse wheel from: %(name)s"). |
| 109 D("name", name). |
| 110 D("dir", dir). |
| 111 Err() |
| 112 } |
| 113 names = append(names, wheelName) |
| 114 } |
| 115 } |
| 116 return names, nil |
| 117 } |
| 118 |
| 119 // WriteRequirementsFile writes a valid "requirements.txt"-style pip |
| 120 // requirements file containing the supplied wheels. |
| 121 // |
| 122 // The generated requirements will request the exact wheel senver version (using |
| 123 // "=="). |
| 124 func WriteRequirementsFile(path string, wheels []Name) (err error) { |
| 125 fd, err := os.Create(path) |
| 126 if err != nil { |
| 127 return errors.Annotate(err).Reason("failed to create requirement
s file").Err() |
| 128 } |
| 129 defer func() { |
| 130 closeErr := fd.Close() |
| 131 if closeErr != nil && err == nil { |
| 132 err = errors.Annotate(closeErr).Reason("failed to Close"
).Err() |
| 133 } |
| 134 }() |
| 135 |
| 136 // Emit a series of "Distribution==Version" strings. |
| 137 seen := make(map[Name]struct{}, len(wheels)) |
| 138 for _, wheel := range wheels { |
| 139 // Only mention a given Distribution/Version once. |
| 140 archetype := Name{ |
| 141 Distribution: wheel.Distribution, |
| 142 Version: wheel.Version, |
| 143 } |
| 144 if _, ok := seen[archetype]; ok { |
| 145 // Already seen a package for this archetype, skip it. |
| 146 continue |
| 147 } |
| 148 seen[archetype] = struct{}{} |
| 149 |
| 150 if _, err := fmt.Fprintf(fd, "%s==%s\n", archetype.Distribution,
archetype.Version); err != nil { |
| 151 return errors.Annotate(err).Reason("failed to write to r
equirements file").Err() |
| 152 } |
| 153 } |
| 154 |
| 155 return nil |
| 156 } |
| OLD | NEW |