Chromium Code Reviews| 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 return strings.Join([]string{ | |
| 32 wn.Distribution, | |
| 33 wn.Version, | |
| 34 wn.BuildTag, | |
|
iannucci
2017/02/21 08:34:01
if BuildTag is empty, does this render correctly,
dnj
2017/02/21 18:19:07
It does not. Fixed.
| |
| 35 wn.PythonTag, | |
| 36 wn.ABITag, | |
| 37 wn.PlatformTag, | |
| 38 }, "-") + ".whl" | |
| 39 } | |
| 40 | |
| 41 // ParseName parses a wheel Name from its filename. | |
| 42 func ParseName(v string) (wn Name, err error) { | |
| 43 base := strings.TrimSuffix(v, ".whl") | |
| 44 if len(base) == len(v) { | |
| 45 err = errors.Reason("missing .whl suffix").Err() | |
| 46 return | |
| 47 } | |
| 48 | |
| 49 skip := 0 | |
| 50 switch parts := strings.Split(base, "-"); len(parts) { | |
| 51 case 6: | |
| 52 // Extra part: build tag. | |
| 53 wn.BuildTag = parts[2] | |
| 54 skip = 1 | |
| 55 fallthrough | |
| 56 | |
| 57 case 5: | |
| 58 wn.Distribution = parts[0] | |
| 59 wn.Version = parts[1] | |
| 60 wn.PythonTag = parts[2+skip] | |
| 61 wn.ABITag = parts[3+skip] | |
| 62 wn.PlatformTag = parts[4+skip] | |
| 63 | |
| 64 default: | |
| 65 err = errors.Reason("unknown number of segments (%(segments)d)") . | |
| 66 D("segments", len(parts)). | |
| 67 Err() | |
| 68 return | |
| 69 } | |
| 70 return | |
| 71 } | |
| 72 | |
| 73 // GlobFrom identifies all wheel files in the directory dir and returns their | |
| 74 // parsed wheel names. | |
|
iannucci
2017/02/21 08:34:02
note: non-recursively.
Is this really glob-relate
dnj
2017/02/21 18:19:07
Done. Changed to "ScanDir".
| |
| 75 func GlobFrom(dir string) ([]Name, error) { | |
| 76 globPattern := filepath.Join(dir, "*.whl") | |
| 77 matches, err := filepath.Glob(globPattern) | |
| 78 if err != nil { | |
| 79 return nil, errors.Annotate(err).Reason("failed to list wheel di rectory: %(dir)s"). | |
| 80 D("dir", dir). | |
| 81 D("pattern", globPattern). | |
|
iannucci
2017/02/21 08:34:01
not in reason?
dnj
2017/02/21 18:19:07
Acknowledged.
| |
| 82 Err() | |
| 83 } | |
| 84 | |
| 85 names := make([]Name, 0, len(matches)) | |
| 86 for _, match := range matches { | |
| 87 switch st, err := os.Stat(match); { | |
| 88 case err != nil: | |
| 89 return nil, errors.Annotate(err).Reason("failed to stat wheel: %(path)s"). | |
| 90 D("path", match). | |
| 91 Err() | |
| 92 | |
| 93 case st.IsDir(): | |
| 94 // Ignore directories. | |
| 95 continue | |
| 96 | |
| 97 default: | |
| 98 // A ".whl" file. | |
| 99 name := filepath.Base(match) | |
| 100 wheelName, err := ParseName(name) | |
| 101 if err != nil { | |
| 102 return nil, errors.Annotate(err).Reason("failed to parse wheel from: %(name)s"). | |
| 103 D("name", name). | |
| 104 D("dir", dir). | |
|
iannucci
2017/02/21 08:34:01
in reason?
dnj
2017/02/21 18:19:07
Acknowledged.
| |
| 105 Err() | |
| 106 } | |
| 107 names = append(names, wheelName) | |
| 108 } | |
| 109 } | |
| 110 return names, nil | |
| 111 } | |
| 112 | |
| 113 // WriteRequirementsFile writes a valid "requirements.txt"-style pip reuirements | |
|
iannucci
2017/02/21 08:34:01
requirements
dnj
2017/02/21 18:19:07
Done.
| |
| 114 // file containing the supplied wheels. | |
| 115 // | |
| 116 // The generated requirements will request the exact wheel version. | |
|
iannucci
2017/02/21 08:34:02
"exact wheel version" meaning a hash? Or the appro
dnj
2017/02/21 18:19:07
semver "==", clarified.
| |
| 117 func WriteRequirementsFile(path string, wheels []Name) error { | |
| 118 fd, err := os.Create(path) | |
| 119 if err != nil { | |
| 120 return errors.Annotate(err).Reason("failed to create requirement s file").Err() | |
| 121 } | |
|
iannucci
2017/02/21 08:34:01
why not defer close?
dnj
2017/02/21 18:19:07
meh didn't want to use awkward defer error assignm
| |
| 122 | |
| 123 // Emit a series of "Distribution==Version" strings. | |
| 124 seen := make(map[Name]struct{}, len(wheels)) | |
| 125 for _, wheel := range wheels { | |
| 126 // Only mention a given Distribution/Version once. | |
| 127 archetype := Name{ | |
| 128 Distribution: wheel.Distribution, | |
| 129 Version: wheel.Version, | |
| 130 } | |
| 131 if _, ok := seen[archetype]; ok { | |
| 132 // Already seen a package for this archetype, skip it. | |
| 133 continue | |
| 134 } | |
| 135 seen[archetype] = struct{}{} | |
| 136 | |
| 137 if _, err := fmt.Fprintf(fd, "%s==%s\n", archetype.Distribution, archetype.Version); err != nil { | |
| 138 fd.Close() | |
| 139 return errors.Annotate(err).Reason("failed to write to r equirements file").Err() | |
| 140 } | |
| 141 } | |
| 142 | |
| 143 if err := fd.Close(); err != nil { | |
| 144 return errors.Annotate(err).Reason("failed to Close").Err() | |
| 145 } | |
| 146 return nil | |
| 147 } | |
| OLD | NEW |