Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(381)

Side by Side Diff: vpython/wheel/wheel.go

Issue 2700273002: vpython: Add wheel parsing/management package. (Closed)
Patch Set: rebase, comments Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | vpython/wheel/wheel_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « no previous file | vpython/wheel/wheel_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698