From 74c4caa0cf717e4745f05f7d167e38a544e19c80 Mon Sep 17 00:00:00 2001 From: Frederic Branczyk Date: Sun, 13 May 2018 06:55:13 -0700 Subject: [PATCH] Add correct lockfile handling --- README.md | 23 ++++++++++-- cmd/jb/main.go | 59 +++++++++++++++++++----------- pkg/packages.go | 87 +++++++++++++++++++++++++++++++++----------- pkg/packages_test.go | 35 ++++++++++++++++++ spec/spec.go | 7 ++-- 5 files changed, 162 insertions(+), 49 deletions(-) create mode 100644 pkg/packages_test.go diff --git a/README.md b/README.md index f431d6d..abd974d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,24 @@ All command line flags: [embedmd]:# (_output/help.txt) ```txt $ jb -h -Usage of jb: - -jsonnetpkg-home string - The directory used to cache packages in. (default "vendor") +usage: jb [] [ ...] + +A jsonnet package manager + +Flags: + -h, --help Show context-sensitive help (also try --help-long and --help-man). + --jsonnetpkg-home="vendor" + The directory used to cache packages in. + +Commands: + help [...] + Show help. + + init + Initialize a new empty jsonnetfile + + install [...] + Install all dependencies or install specific ones + + ``` diff --git a/cmd/jb/main.go b/cmd/jb/main.go index 34a46d4..24852ab 100644 --- a/cmd/jb/main.go +++ b/cmd/jb/main.go @@ -220,7 +220,21 @@ func parseGithubDependency(urlString string) *spec.Dependency { } func installCommand(jsonnetHome string, urls ...*url.URL) int { - m, err := pkg.LoadJsonnetfile(pkg.JsonnetFile) + workdir := "." + + useLock, err := pkg.LockExists(workdir) + if err != nil { + kingpin.Fatalf("failed to check if jsonnetfile.lock.json exists: %v", err) + return 1 + } + + jsonnetfile, err := pkg.ChooseJsonnetFile(workdir) + if err != nil { + kingpin.Fatalf("failed to choose jsonnetfile: %v", err) + return 1 + } + + m, err := pkg.LoadJsonnetfile(jsonnetfile) if err != nil { kingpin.Fatalf("failed to load jsonnetfile: %v", err) return 1 @@ -269,34 +283,37 @@ func installCommand(jsonnetHome string, urls ...*url.URL) int { return 3 } - lock, err := pkg.Install(context.TODO(), m, jsonnetHome) + lock, err := pkg.Install(context.TODO(), jsonnetfile, m, jsonnetHome) if err != nil { kingpin.Fatalf("failed to install: %v", err) return 3 } - b, err := json.MarshalIndent(m, "", " ") - if err != nil { - kingpin.Fatalf("failed to encode jsonnet file: %v", err) - return 3 - } + // If installing from lock file there is no need to write any files back. + if !useLock { + b, err := json.MarshalIndent(m, "", " ") + if err != nil { + kingpin.Fatalf("failed to encode jsonnet file: %v", err) + return 3 + } - err = ioutil.WriteFile(pkg.JsonnetFile, b, 0644) - if err != nil { - kingpin.Fatalf("failed to write jsonnet file: %v", err) - return 3 - } + err = ioutil.WriteFile(pkg.JsonnetFile, b, 0644) + if err != nil { + kingpin.Fatalf("failed to write jsonnet file: %v", err) + return 3 + } - b, err = json.MarshalIndent(lock, "", " ") - if err != nil { - kingpin.Fatalf("failed to encode jsonnet file: %v", err) - return 3 - } + b, err = json.MarshalIndent(lock, "", " ") + if err != nil { + kingpin.Fatalf("failed to encode jsonnet file: %v", err) + return 3 + } - err = ioutil.WriteFile(pkg.JsonnetLockFile, b, 0644) - if err != nil { - kingpin.Fatalf("failed to write lock file: %v", err) - return 3 + err = ioutil.WriteFile(pkg.JsonnetLockFile, b, 0644) + if err != nil { + kingpin.Fatalf("failed to write lock file: %v", err) + return 3 + } } return 0 diff --git a/pkg/packages.go b/pkg/packages.go index 49a6899..af2f668 100644 --- a/pkg/packages.go +++ b/pkg/packages.go @@ -33,7 +33,7 @@ var ( VersionMismatch = errors.New("multiple colliding versions specified") ) -func Install(ctx context.Context, m spec.JsonnetFile, dir string) (lock *spec.JsonnetFile, err error) { +func Install(ctx context.Context, dependencySourceIdentifier string, m spec.JsonnetFile, dir string) (lock *spec.JsonnetFile, err error) { lock = &spec.JsonnetFile{} for _, dep := range m.Dependencies { tmp := filepath.Join(dir, ".tmp") @@ -58,11 +58,11 @@ func Install(ctx context.Context, m spec.JsonnetFile, dir string) (lock *spec.Js if err != nil { return nil, errors.Wrap(err, "failed to install package") } - // need to deduplicate/error when multiple entries lock.Dependencies, err = insertDependency(lock.Dependencies, spec.Dependency{ - Name: dep.Name, - Source: dep.Source, - Version: lockVersion, + Name: dep.Name, + Source: dep.Source, + Version: lockVersion, + DepSource: dependencySourceIdentifier, }) if err != nil { return nil, errors.Wrap(err, "failed to insert dependency to lock dependencies") @@ -87,22 +87,26 @@ func Install(ctx context.Context, m spec.JsonnetFile, dir string) (lock *spec.Js return nil, errors.Wrap(err, "failed to move package") } - if _, err := os.Stat(path.Join(destPath, JsonnetFile)); !os.IsNotExist(err) { - depsDeps, err := LoadJsonnetfile(path.Join(destPath, JsonnetFile)) - if err != nil { - return nil, err - } + filepath, err := ChooseJsonnetFile(destPath) + if err != nil { + return nil, err + } + depsDeps, err := LoadJsonnetfile(filepath) + // It is ok for depedencies not to have a JsonnetFile, it just means + // they do not have transitive dependencies of their own. + if err != nil && !os.IsNotExist(err) { + return nil, err + } - depsInstalledByDependency, err := Install(ctx, depsDeps, dir) - if err != nil { - return nil, err - } + depsInstalledByDependency, err := Install(ctx, filepath, depsDeps, dir) + if err != nil { + return nil, err + } - for _, d := range depsInstalledByDependency.Dependencies { - lock.Dependencies, err = insertDependency(lock.Dependencies, d) - if err != nil { - return nil, errors.Wrap(err, "failed to insert dependency to lock dependencies") - } + for _, d := range depsInstalledByDependency.Dependencies { + lock.Dependencies, err = insertDependency(lock.Dependencies, d) + if err != nil { + return nil, errors.Wrap(err, "failed to insert dependency to lock dependencies") } } } @@ -116,24 +120,63 @@ func insertDependency(deps []spec.Dependency, newDep spec.Dependency) ([]spec.De } res := []spec.Dependency{} + newDepPreviouslyPresent := false for _, d := range deps { if d.Name == newDep.Name { if d.Version != newDep.Version { - return nil, VersionMismatch + return nil, fmt.Errorf("multiple colliding versions specified for %s: %s (from %s) and %s (from %s)", d.Name, d.Version, d.DepSource, newDep.Version, newDep.DepSource) } res = append(res, d) + newDepPreviouslyPresent = true } else { res = append(res, d) } } + if !newDepPreviouslyPresent { + res = append(res, newDep) + } return res, nil } -func LoadJsonnetfile(filename string) (spec.JsonnetFile, error) { +func LockExists(dir string) (bool, error) { + lockfile := path.Join(dir, JsonnetLockFile) + _, err := os.Stat(lockfile) + if os.IsNotExist(err) { + return false, nil + } + if err != nil { + return false, err + } + + return true, nil +} + +func ChooseJsonnetFile(dir string) (string, error) { + lockfile := path.Join(dir, JsonnetLockFile) + jsonnetfile := path.Join(dir, JsonnetFile) + filename := lockfile + + lockExists, err := LockExists(dir) + if err != nil { + return "", err + } + + if !lockExists { + filename = jsonnetfile + } + + return filename, err +} + +func LoadJsonnetfile(filepath string) (spec.JsonnetFile, error) { m := spec.JsonnetFile{} - f, err := os.Open(filename) + if _, err := os.Stat(filepath); err != nil { + return m, err + } + + f, err := os.Open(filepath) if err != nil { return m, err } diff --git a/pkg/packages_test.go b/pkg/packages_test.go new file mode 100644 index 0000000..a4366a7 --- /dev/null +++ b/pkg/packages_test.go @@ -0,0 +1,35 @@ +// Copyright 2018 jsonnet-bundler authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pkg + +import ( + "testing" + + "github.com/jsonnet-bundler/jsonnet-bundler/spec" +) + +func TestInsert(t *testing.T) { + deps := []*spec.Dependency{&spec.Dependency{Name: "test1", Version: "latest"}} + dep := &spec.Dependency{Name: "test2", Version: "latest"} + + res, err := insertDependency(deps, dep) + if err != nil { + t.Fatal(err) + } + + if len(res) != 2 { + t.Fatal("Incorrectly inserted") + } +} diff --git a/spec/spec.go b/spec/spec.go index e7d8c37..5f10ea8 100644 --- a/spec/spec.go +++ b/spec/spec.go @@ -28,7 +28,8 @@ type GitSource struct { } type Dependency struct { - Name string `json:"name"` - Source Source `json:"source"` - Version string `json:"version"` + Name string `json:"name"` + Source Source `json:"source"` + Version string `json:"version"` + DepSource string `json:"-"` }