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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | vpython/wheel/wheel_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: vpython/wheel/wheel.go
diff --git a/vpython/wheel/wheel.go b/vpython/wheel/wheel.go
new file mode 100644
index 0000000000000000000000000000000000000000..042c9f8d1107d4e6609e09e7d62711f473de0289
--- /dev/null
+++ b/vpython/wheel/wheel.go
@@ -0,0 +1,156 @@
+// Copyright 2017 The LUCI Authors. All rights reserved.
+// Use of this source code is governed under the Apache License, Version 2.0
+// that can be found in the LICENSE file.
+
+package wheel
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/luci/luci-go/common/errors"
+)
+
+// Name is a parsed Python wheel name, defined here:
+// https://www.python.org/dev/peps/pep-0427/#file-name-convention
+//
+// {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-\
+// {platform tag}.whl .
+type Name struct {
+ Distribution string
+ Version string
+ BuildTag string
+ PythonTag string
+ ABITag string
+ PlatformTag string
+}
+
+func (wn *Name) String() string {
+ parts := make([]string, 0, 6)
+ parts = append(parts, []string{
+ wn.Distribution,
+ wn.Version,
+ }...)
+ if wn.BuildTag != "" {
+ parts = append(parts, wn.BuildTag)
+ }
+ parts = append(parts, []string{
+ wn.PythonTag,
+ wn.ABITag,
+ wn.PlatformTag,
+ }...)
+ return strings.Join(parts, "-") + ".whl"
+}
+
+// ParseName parses a wheel Name from its filename.
+func ParseName(v string) (wn Name, err error) {
+ base := strings.TrimSuffix(v, ".whl")
+ if len(base) == len(v) {
+ err = errors.Reason("missing .whl suffix").Err()
+ return
+ }
+
+ skip := 0
+ switch parts := strings.Split(base, "-"); len(parts) {
+ case 6:
+ // Extra part: build tag.
+ wn.BuildTag = parts[2]
+ skip = 1
+ fallthrough
+
+ case 5:
+ wn.Distribution = parts[0]
+ wn.Version = parts[1]
+ wn.PythonTag = parts[2+skip]
+ wn.ABITag = parts[3+skip]
+ wn.PlatformTag = parts[4+skip]
+
+ default:
+ err = errors.Reason("unknown number of segments (%(segments)d)").
+ D("segments", len(parts)).
+ Err()
+ return
+ }
+ return
+}
+
+// ScanDir identifies all wheel files in the immediate directory dir and
+// returns their parsed wheel names.
+func ScanDir(dir string) ([]Name, error) {
+ globPattern := filepath.Join(dir, "*.whl")
+ matches, err := filepath.Glob(globPattern)
+ if err != nil {
+ return nil, errors.Annotate(err).Reason("failed to list wheel directory: %(dir)s").
+ D("dir", dir).
+ D("pattern", globPattern).
+ Err()
+ }
+
+ names := make([]Name, 0, len(matches))
+ for _, match := range matches {
+ switch st, err := os.Stat(match); {
+ case err != nil:
+ return nil, errors.Annotate(err).Reason("failed to stat wheel: %(path)s").
+ D("path", match).
+ Err()
+
+ case st.IsDir():
+ // Ignore directories.
+ continue
+
+ default:
+ // A ".whl" file.
+ name := filepath.Base(match)
+ wheelName, err := ParseName(name)
+ if err != nil {
+ return nil, errors.Annotate(err).Reason("failed to parse wheel from: %(name)s").
+ D("name", name).
+ D("dir", dir).
+ Err()
+ }
+ names = append(names, wheelName)
+ }
+ }
+ return names, nil
+}
+
+// WriteRequirementsFile writes a valid "requirements.txt"-style pip
+// requirements file containing the supplied wheels.
+//
+// The generated requirements will request the exact wheel senver version (using
+// "==").
+func WriteRequirementsFile(path string, wheels []Name) (err error) {
+ fd, err := os.Create(path)
+ if err != nil {
+ return errors.Annotate(err).Reason("failed to create requirements file").Err()
+ }
+ defer func() {
+ closeErr := fd.Close()
+ if closeErr != nil && err == nil {
+ err = errors.Annotate(closeErr).Reason("failed to Close").Err()
+ }
+ }()
+
+ // Emit a series of "Distribution==Version" strings.
+ seen := make(map[Name]struct{}, len(wheels))
+ for _, wheel := range wheels {
+ // Only mention a given Distribution/Version once.
+ archetype := Name{
+ Distribution: wheel.Distribution,
+ Version: wheel.Version,
+ }
+ if _, ok := seen[archetype]; ok {
+ // Already seen a package for this archetype, skip it.
+ continue
+ }
+ seen[archetype] = struct{}{}
+
+ if _, err := fmt.Fprintf(fd, "%s==%s\n", archetype.Distribution, archetype.Version); err != nil {
+ return errors.Annotate(err).Reason("failed to write to requirements file").Err()
+ }
+ }
+
+ return nil
+}
« 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