mirror of
https://github.com/TECHNOFAB11/jsonnet-bundler.git
synced 2026-02-02 09:25:08 +01:00
feat: absolute imports (#63)
* feat: go-like import style jb now creates a directory structure inside of vendor/ that is similar to how go does (github.com/grafana/jsonnet-libs). This is reflected in the final import paths, which means they will be go-like * refactor(spec/deps): named regexs * feat: make goImportStyle configurable Defaults to off, can be enabled in `jsonnetfile.json` * fix: integration test * doc: license headers * fix(deps): remove GO_IMPORT_STYLE not an option anymore, will always do so and symlink * feat: symlink to legacy location * feat: allow to disable legacy links * fix(test): legacyImports in integration tests * fix(spec): test * fix: respect legacyName aliases It was possible to alias packages by changing `name` previously. While names are now absolute (and computed), legacy links should still respect old aliases to avoid breaking code. * fix(test): integration * fix(init): keep legacyImports enabled for now * feat: rewrite imports adds a command to automatically rewrite imports from legacy to absolute style * fix(tool): rewrite confused by prefixing packages When a package was a prefix of another one, it broke. Fixed that by using a proper regular expression. Added a test to make sure it works as expected * Update cmd/jb/init.go * fix: exclude local packages from legacy linking They actually still use the old style, which is fine. LegacyLinking messed them up, but from now on it just ignores symlinks that match a localPackage.
This commit is contained in:
parent
184841238b
commit
7b8a7836a4
22 changed files with 1129 additions and 361 deletions
|
|
@ -106,6 +106,9 @@ Commands:
|
||||||
update
|
update
|
||||||
Update all dependencies.
|
Update all dependencies.
|
||||||
|
|
||||||
|
rewrite
|
||||||
|
Automatically rewrite legacy imports to absolute ones
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,32 +15,37 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initCommand(dir string) int {
|
func initCommand(dir string) int {
|
||||||
exists, err := jsonnetfile.Exists(jsonnetfile.File)
|
exists, err := jsonnetfile.Exists(jsonnetfile.File)
|
||||||
if err != nil {
|
kingpin.FatalIfError(err, "Failed to check for jsonnetfile.json")
|
||||||
kingpin.Errorf("Failed to check for jsonnetfile.json: %v", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
kingpin.Errorf("jsonnetfile.json already exists")
|
kingpin.Errorf("jsonnetfile.json already exists")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s := spec.New()
|
||||||
|
// TODO: disable them by default eventually
|
||||||
|
// s.LegacyImports = false
|
||||||
|
|
||||||
|
contents, err := json.MarshalIndent(s, "", " ")
|
||||||
|
kingpin.FatalIfError(err, "formatting jsonnetfile contents as json")
|
||||||
|
contents = append(contents, []byte("\n")...)
|
||||||
|
|
||||||
filename := filepath.Join(dir, jsonnetfile.File)
|
filename := filepath.Join(dir, jsonnetfile.File)
|
||||||
|
|
||||||
if err := ioutil.WriteFile(filename, []byte("{}\n"), 0644); err != nil {
|
ioutil.WriteFile(filename, contents, 0644)
|
||||||
kingpin.Errorf("Failed to write new jsonnetfile.json: %v", err)
|
kingpin.FatalIfError(err, "Failed to write new jsonnetfile.json")
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/pkg"
|
"github.com/jsonnet-bundler/jsonnet-bundler/pkg"
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
)
|
)
|
||||||
|
|
||||||
func installCommand(dir, jsonnetHome string, uris []string) int {
|
func installCommand(dir, jsonnetHome string, uris []string) int {
|
||||||
|
|
@ -53,23 +54,25 @@ func installCommand(dir, jsonnetHome string, uris []string) int {
|
||||||
"creating vendor folder")
|
"creating vendor folder")
|
||||||
|
|
||||||
for _, u := range uris {
|
for _, u := range uris {
|
||||||
d := parseDependency(dir, u)
|
d := deps.Parse(dir, u)
|
||||||
if d == nil {
|
if d == nil {
|
||||||
kingpin.Fatalf("Unable to parse package URI `%s`", u)
|
kingpin.Fatalf("Unable to parse package URI `%s`", u)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !depEqual(jsonnetFile.Dependencies[d.Name], *d) {
|
if !depEqual(jsonnetFile.Dependencies[d.Name()], *d) {
|
||||||
// the dep passed on the cli is different from the jsonnetFile
|
// the dep passed on the cli is different from the jsonnetFile
|
||||||
jsonnetFile.Dependencies[d.Name] = *d
|
jsonnetFile.Dependencies[d.Name()] = *d
|
||||||
|
|
||||||
// we want to install the passed version (ignore the lock)
|
// we want to install the passed version (ignore the lock)
|
||||||
delete(lockFile.Dependencies, d.Name)
|
delete(lockFile.Dependencies, d.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
locked, err := pkg.Ensure(jsonnetFile, jsonnetHome, lockFile.Dependencies)
|
locked, err := pkg.Ensure(jsonnetFile, jsonnetHome, lockFile.Dependencies)
|
||||||
kingpin.FatalIfError(err, "failed to install packages")
|
kingpin.FatalIfError(err, "failed to install packages")
|
||||||
|
|
||||||
|
pkg.CleanLegacyName(jsonnetFile.Dependencies)
|
||||||
|
|
||||||
kingpin.FatalIfError(
|
kingpin.FatalIfError(
|
||||||
writeChangedJsonnetFile(jbfilebytes, &jsonnetFile, filepath.Join(dir, jsonnetfile.File)),
|
writeChangedJsonnetFile(jbfilebytes, &jsonnetFile, filepath.Join(dir, jsonnetfile.File)),
|
||||||
"updating jsonnetfile.json")
|
"updating jsonnetfile.json")
|
||||||
|
|
@ -81,8 +84,8 @@ func installCommand(dir, jsonnetHome string, uris []string) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func depEqual(d1, d2 spec.Dependency) bool {
|
func depEqual(d1, d2 deps.Dependency) bool {
|
||||||
name := d1.Name == d2.Name
|
name := d1.Name() == d2.Name()
|
||||||
version := d1.Version == d2.Version
|
version := d1.Version == d2.Version
|
||||||
source := reflect.DeepEqual(d1.Source, d2.Source)
|
source := reflect.DeepEqual(d1.Source, d2.Source)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,12 @@ import (
|
||||||
|
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: Change legacyImports to false eventually
|
||||||
|
const initContents = `{"dependencies": [], "legacyImports": true}`
|
||||||
|
|
||||||
func TestInstallCommand(t *testing.T) {
|
func TestInstallCommand(t *testing.T) {
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
@ -38,19 +42,21 @@ func TestInstallCommand(t *testing.T) {
|
||||||
{
|
{
|
||||||
Name: "NoURLs",
|
Name: "NoURLs",
|
||||||
ExpectedCode: 0,
|
ExpectedCode: 0,
|
||||||
ExpectedJsonnetFile: []byte(`{}`),
|
ExpectedJsonnetFile: []byte(initContents),
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
Name: "OneURL",
|
Name: "OneURL",
|
||||||
URIs: []string{"github.com/jsonnet-bundler/jsonnet-bundler@v0.1.0"},
|
URIs: []string{"github.com/jsonnet-bundler/jsonnet-bundler@v0.1.0"},
|
||||||
ExpectedCode: 0,
|
ExpectedCode: 0,
|
||||||
ExpectedJsonnetFile: []byte(`{"dependencies": [{"name": "jsonnet-bundler", "source": {"git": {"remote": "https://github.com/jsonnet-bundler/jsonnet-bundler", "subdir": ""}}, "version": "v0.1.0"}]}`),
|
ExpectedJsonnetFile: []byte(`{"dependencies": [{"source": {"git": {"remote": "https://github.com/jsonnet-bundler/jsonnet-bundler", "subdir": ""}}, "version": "v0.1.0"}], "legacyImports": true}`),
|
||||||
ExpectedJsonnetLockFile: []byte(`{"dependencies": [{"name": "jsonnet-bundler", "source": {"git": {"remote": "https://github.com/jsonnet-bundler/jsonnet-bundler", "subdir": ""}}, "version": "080f157c7fb85ad0281ea78f6c641eaa570a582f", "sum": "W1uI550rQ66axRpPXA2EZDquyPg/5PHZlvUz1NEzefg="}]}`),
|
ExpectedJsonnetLockFile: []byte(`{"dependencies": [{"source": {"git": {"remote": "https://github.com/jsonnet-bundler/jsonnet-bundler", "subdir": ""}}, "version": "080f157c7fb85ad0281ea78f6c641eaa570a582f", "sum": "W1uI550rQ66axRpPXA2EZDquyPg/5PHZlvUz1NEzefg="}], "legacyImports": false}`),
|
||||||
}, {
|
},
|
||||||
Name: "Relative",
|
{
|
||||||
|
Name: "Local",
|
||||||
URIs: []string{"jsonnet/foobar"},
|
URIs: []string{"jsonnet/foobar"},
|
||||||
ExpectedCode: 0,
|
ExpectedCode: 0,
|
||||||
ExpectedJsonnetFile: []byte(`{"dependencies": [{"name": "foobar", "source": {"local": {"directory": "jsonnet/foobar"}}, "version": ""}]}`),
|
ExpectedJsonnetFile: []byte(`{"dependencies": [{"source": {"local": {"directory": "jsonnet/foobar"}}, "version": ""}], "legacyImports": true}`),
|
||||||
ExpectedJsonnetLockFile: []byte(`{"dependencies": [{"name": "foobar", "source": {"local": {"directory": "jsonnet/foobar"}}, "version": ""}]}`),
|
ExpectedJsonnetLockFile: []byte(`{"dependencies": [{"source": {"local": {"directory": "jsonnet/foobar"}}, "version": ""}], "legacyImports": false}`),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,12 +76,12 @@ func TestInstallCommand(t *testing.T) {
|
||||||
err := os.MkdirAll(localDependency, os.ModePerm)
|
err := os.MkdirAll(localDependency, os.ModePerm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// init + check it works correctly (legacyImports true, empty dependencies)
|
||||||
initCommand("")
|
initCommand("")
|
||||||
|
jsonnetFileContent(t, jsonnetfile.File, []byte(initContents))
|
||||||
|
|
||||||
jsonnetFileContent(t, jsonnetfile.File, []byte(`{}`))
|
// install something, check it writes only if required, etc.
|
||||||
|
|
||||||
installCommand("", "vendor", tc.URIs)
|
installCommand("", "vendor", tc.URIs)
|
||||||
|
|
||||||
jsonnetFileContent(t, jsonnetfile.File, tc.ExpectedJsonnetFile)
|
jsonnetFileContent(t, jsonnetfile.File, tc.ExpectedJsonnetFile)
|
||||||
if tc.ExpectedJsonnetLockFile != nil {
|
if tc.ExpectedJsonnetLockFile != nil {
|
||||||
jsonnetFileContent(t, jsonnetfile.LockFile, tc.ExpectedJsonnetLockFile)
|
jsonnetFileContent(t, jsonnetfile.LockFile, tc.ExpectedJsonnetLockFile)
|
||||||
|
|
@ -111,11 +117,11 @@ func TestWriteChangedJsonnetFile(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "NoDiffNotEmpty",
|
Name: "NoDiffNotEmpty",
|
||||||
JsonnetFileBytes: []byte(`{"dependencies": [{"name": "foobar"}]}`),
|
JsonnetFileBytes: []byte(`{"dependencies": [{"version": "master"}]}`),
|
||||||
NewJsonnetFile: spec.JsonnetFile{
|
NewJsonnetFile: spec.JsonnetFile{
|
||||||
Dependencies: map[string]spec.Dependency{
|
Dependencies: map[string]deps.Dependency{
|
||||||
"foobar": {
|
"": {
|
||||||
Name: "foobar",
|
Version: "master",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -123,11 +129,10 @@ func TestWriteChangedJsonnetFile(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "DiffVersion",
|
Name: "DiffVersion",
|
||||||
JsonnetFileBytes: []byte(`{"dependencies": [{"name": "foobar", "version": "1.0"}]}`),
|
JsonnetFileBytes: []byte(`{"dependencies": [{"version": "1.0"}]}`),
|
||||||
NewJsonnetFile: spec.JsonnetFile{
|
NewJsonnetFile: spec.JsonnetFile{
|
||||||
Dependencies: map[string]spec.Dependency{
|
Dependencies: map[string]deps.Dependency{
|
||||||
"foobar": {
|
"": {
|
||||||
Name: "foobar",
|
|
||||||
Version: "2.0",
|
Version: "2.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -138,17 +143,18 @@ func TestWriteChangedJsonnetFile(t *testing.T) {
|
||||||
Name: "Diff",
|
Name: "Diff",
|
||||||
JsonnetFileBytes: []byte(`{}`),
|
JsonnetFileBytes: []byte(`{}`),
|
||||||
NewJsonnetFile: spec.JsonnetFile{
|
NewJsonnetFile: spec.JsonnetFile{
|
||||||
Dependencies: map[string]spec.Dependency{
|
Dependencies: map[string]deps.Dependency{
|
||||||
"foobar": {
|
"github.com/foobar/foobar": {
|
||||||
Name: "foobar",
|
Source: deps.Source{
|
||||||
Source: spec.Source{
|
GitSource: &deps.Git{
|
||||||
GitSource: &spec.GitSource{
|
Scheme: deps.GitSchemeHTTPS,
|
||||||
Remote: "https://github.com/foobar/foobar",
|
Host: "github.com",
|
||||||
|
User: "foobar",
|
||||||
|
Repo: "foobar",
|
||||||
Subdir: "",
|
Subdir: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Version: "master",
|
Version: "master",
|
||||||
DepSource: "",
|
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
ExpectWrite: true,
|
ExpectWrite: true,
|
||||||
|
|
|
||||||
161
cmd/jb/main.go
161
cmd/jb/main.go
|
|
@ -17,32 +17,16 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
installActionName = "install"
|
installActionName = "install"
|
||||||
updateActionName = "update"
|
updateActionName = "update"
|
||||||
initActionName = "init"
|
initActionName = "init"
|
||||||
)
|
rewriteActionName = "rewrite"
|
||||||
|
|
||||||
var (
|
|
||||||
gitSSHRegex = regexp.MustCompile(`git\+ssh://git@([^:]+):([^/]+)/([^/]+).git`)
|
|
||||||
gitSSHWithVersionRegex = regexp.MustCompile(`git\+ssh://git@([^:]+):([^/]+)/([^/]+).git@(.*)`)
|
|
||||||
gitSSHWithPathRegex = regexp.MustCompile(`git\+ssh://git@([^:]+):([^/]+)/([^/]+).git/(.*)`)
|
|
||||||
gitSSHWithPathAndVersionRegex = regexp.MustCompile(`git\+ssh://git@([^:]+):([^/]+)/([^/]+).git/(.*)@(.*)`)
|
|
||||||
|
|
||||||
githubSlugRegex = regexp.MustCompile("github.com/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)")
|
|
||||||
githubSlugWithVersionRegex = regexp.MustCompile("github.com/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)@(.*)")
|
|
||||||
githubSlugWithPathRegex = regexp.MustCompile("github.com/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/(.*)")
|
|
||||||
githubSlugWithPathAndVersionRegex = regexp.MustCompile("github.com/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/(.*)@(.*)")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -69,6 +53,8 @@ func Main() int {
|
||||||
|
|
||||||
updateCmd := a.Command(updateActionName, "Update all dependencies.")
|
updateCmd := a.Command(updateActionName, "Update all dependencies.")
|
||||||
|
|
||||||
|
rewriteCmd := a.Command(rewriteActionName, "Automatically rewrite legacy imports to absolute ones")
|
||||||
|
|
||||||
command, err := a.Parse(os.Args[1:])
|
command, err := a.Parse(os.Args[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, errors.Wrapf(err, "Error parsing commandline arguments"))
|
fmt.Fprintln(os.Stderr, errors.Wrapf(err, "Error parsing commandline arguments"))
|
||||||
|
|
@ -88,148 +74,11 @@ func Main() int {
|
||||||
return installCommand(workdir, cfg.JsonnetHome, *installCmdURIs)
|
return installCommand(workdir, cfg.JsonnetHome, *installCmdURIs)
|
||||||
case updateCmd.FullCommand():
|
case updateCmd.FullCommand():
|
||||||
return updateCommand(workdir, cfg.JsonnetHome)
|
return updateCommand(workdir, cfg.JsonnetHome)
|
||||||
|
case rewriteCmd.FullCommand():
|
||||||
|
return rewriteCommand(workdir, cfg.JsonnetHome)
|
||||||
default:
|
default:
|
||||||
installCommand(workdir, cfg.JsonnetHome, []string{})
|
installCommand(workdir, cfg.JsonnetHome, []string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDependency(dir, uri string) *spec.Dependency {
|
|
||||||
if uri == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if githubSlugRegex.MatchString(uri) {
|
|
||||||
return parseGithubDependency(uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
if gitSSHRegex.MatchString(uri) {
|
|
||||||
return parseGitSSHDependency(uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseLocalDependency(dir, uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseGitSSHDependency(p string) *spec.Dependency {
|
|
||||||
subdir := ""
|
|
||||||
host := ""
|
|
||||||
org := ""
|
|
||||||
repo := ""
|
|
||||||
version := "master"
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case gitSSHWithPathAndVersionRegex.MatchString(p):
|
|
||||||
matches := gitSSHWithPathAndVersionRegex.FindStringSubmatch(p)
|
|
||||||
host = matches[1]
|
|
||||||
org = matches[2]
|
|
||||||
repo = matches[3]
|
|
||||||
subdir = matches[4]
|
|
||||||
version = matches[5]
|
|
||||||
case gitSSHWithPathRegex.MatchString(p):
|
|
||||||
matches := gitSSHWithPathRegex.FindStringSubmatch(p)
|
|
||||||
host = matches[1]
|
|
||||||
org = matches[2]
|
|
||||||
repo = matches[3]
|
|
||||||
subdir = matches[4]
|
|
||||||
case gitSSHWithVersionRegex.MatchString(p):
|
|
||||||
matches := gitSSHWithVersionRegex.FindStringSubmatch(p)
|
|
||||||
host = matches[1]
|
|
||||||
org = matches[2]
|
|
||||||
repo = matches[3]
|
|
||||||
version = matches[4]
|
|
||||||
default:
|
|
||||||
matches := gitSSHRegex.FindStringSubmatch(p)
|
|
||||||
host = matches[1]
|
|
||||||
org = matches[2]
|
|
||||||
repo = matches[3]
|
|
||||||
}
|
|
||||||
|
|
||||||
return &spec.Dependency{
|
|
||||||
Name: repo,
|
|
||||||
Source: spec.Source{
|
|
||||||
GitSource: &spec.GitSource{
|
|
||||||
Remote: fmt.Sprintf("git@%s:%s/%s", host, org, repo),
|
|
||||||
Subdir: subdir,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Version: version,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseGithubDependency(p string) *spec.Dependency {
|
|
||||||
if !githubSlugRegex.MatchString(p) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
name := ""
|
|
||||||
user := ""
|
|
||||||
repo := ""
|
|
||||||
subdir := ""
|
|
||||||
version := "master"
|
|
||||||
|
|
||||||
if githubSlugWithPathRegex.MatchString(p) {
|
|
||||||
if githubSlugWithPathAndVersionRegex.MatchString(p) {
|
|
||||||
matches := githubSlugWithPathAndVersionRegex.FindStringSubmatch(p)
|
|
||||||
user = matches[1]
|
|
||||||
repo = matches[2]
|
|
||||||
subdir = matches[3]
|
|
||||||
version = matches[4]
|
|
||||||
name = path.Base(subdir)
|
|
||||||
} else {
|
|
||||||
matches := githubSlugWithPathRegex.FindStringSubmatch(p)
|
|
||||||
user = matches[1]
|
|
||||||
repo = matches[2]
|
|
||||||
subdir = matches[3]
|
|
||||||
name = path.Base(subdir)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if githubSlugWithVersionRegex.MatchString(p) {
|
|
||||||
matches := githubSlugWithVersionRegex.FindStringSubmatch(p)
|
|
||||||
user = matches[1]
|
|
||||||
repo = matches[2]
|
|
||||||
name = repo
|
|
||||||
version = matches[3]
|
|
||||||
} else {
|
|
||||||
matches := githubSlugRegex.FindStringSubmatch(p)
|
|
||||||
user = matches[1]
|
|
||||||
repo = matches[2]
|
|
||||||
name = repo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &spec.Dependency{
|
|
||||||
Name: name,
|
|
||||||
Source: spec.Source{
|
|
||||||
GitSource: &spec.GitSource{
|
|
||||||
Remote: fmt.Sprintf("https://github.com/%s/%s", user, repo),
|
|
||||||
Subdir: subdir,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Version: version,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseLocalDependency(dir, p string) *spec.Dependency {
|
|
||||||
clean := filepath.Clean(p)
|
|
||||||
abs := filepath.Join(dir, clean)
|
|
||||||
|
|
||||||
info, err := os.Stat(abs)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &spec.Dependency{
|
|
||||||
Name: info.Name(),
|
|
||||||
Source: spec.Source{
|
|
||||||
LocalSource: &spec.LocalSource{
|
|
||||||
Directory: clean,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Version: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseDependency(t *testing.T) {
|
func TestParseDependency(t *testing.T) {
|
||||||
|
|
@ -34,7 +34,7 @@ func TestParseDependency(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
want *spec.Dependency
|
want *deps.Dependency
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Empty",
|
name: "Empty",
|
||||||
|
|
@ -49,11 +49,13 @@ func TestParseDependency(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "GitHub",
|
name: "GitHub",
|
||||||
path: "github.com/jsonnet-bundler/jsonnet-bundler",
|
path: "github.com/jsonnet-bundler/jsonnet-bundler",
|
||||||
want: &spec.Dependency{
|
want: &deps.Dependency{
|
||||||
Name: "jsonnet-bundler",
|
Source: deps.Source{
|
||||||
Source: spec.Source{
|
GitSource: &deps.Git{
|
||||||
GitSource: &spec.GitSource{
|
Scheme: deps.GitSchemeHTTPS,
|
||||||
Remote: "https://github.com/jsonnet-bundler/jsonnet-bundler",
|
Host: "github.com",
|
||||||
|
User: "jsonnet-bundler",
|
||||||
|
Repo: "jsonnet-bundler",
|
||||||
Subdir: "",
|
Subdir: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -63,11 +65,13 @@ func TestParseDependency(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "SSH",
|
name: "SSH",
|
||||||
path: "git+ssh://git@github.com:jsonnet-bundler/jsonnet-bundler.git",
|
path: "git+ssh://git@github.com:jsonnet-bundler/jsonnet-bundler.git",
|
||||||
want: &spec.Dependency{
|
want: &deps.Dependency{
|
||||||
Name: "jsonnet-bundler",
|
Source: deps.Source{
|
||||||
Source: spec.Source{
|
GitSource: &deps.Git{
|
||||||
GitSource: &spec.GitSource{
|
Scheme: deps.GitSchemeSSH,
|
||||||
Remote: "git@github.com:jsonnet-bundler/jsonnet-bundler",
|
Host: "github.com",
|
||||||
|
User: "jsonnet-bundler",
|
||||||
|
Repo: "jsonnet-bundler",
|
||||||
Subdir: "",
|
Subdir: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -77,10 +81,9 @@ func TestParseDependency(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "local",
|
name: "local",
|
||||||
path: testFolder,
|
path: testFolder,
|
||||||
want: &spec.Dependency{
|
want: &deps.Dependency{
|
||||||
Name: "foobar",
|
Source: deps.Source{
|
||||||
Source: spec.Source{
|
LocalSource: &deps.Local{
|
||||||
LocalSource: &spec.LocalSource{
|
|
||||||
Directory: "test/jsonnet/foobar",
|
Directory: "test/jsonnet/foobar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -90,7 +93,7 @@ func TestParseDependency(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
_ = t.Run(tt.name, func(t *testing.T) {
|
_ = t.Run(tt.name, func(t *testing.T) {
|
||||||
dependency := parseDependency("", tt.path)
|
dependency := deps.Parse("", tt.path)
|
||||||
|
|
||||||
if tt.path == "" {
|
if tt.path == "" {
|
||||||
assert.Nil(t, dependency)
|
assert.Nil(t, dependency)
|
||||||
|
|
|
||||||
36
cmd/jb/rewrite.go
Normal file
36
cmd/jb/rewrite.go
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/tool/rewrite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func rewriteCommand(dir, vendorDir string) int {
|
||||||
|
locks, err := jsonnetfile.Load(filepath.Join(dir, jsonnetfile.LockFile))
|
||||||
|
if err != nil {
|
||||||
|
kingpin.Fatalf("Failed to load lockFile: %s.\nThe locks are required to compute the new import names. Make sure to run `jb install` first.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rewrite.Rewrite(dir, vendorDir, locks.Dependencies); err != nil {
|
||||||
|
kingpin.FatalIfError(err, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/pkg"
|
"github.com/jsonnet-bundler/jsonnet-bundler/pkg"
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
)
|
)
|
||||||
|
|
||||||
func updateCommand(dir, jsonnetHome string, urls ...*url.URL) int {
|
func updateCommand(dir, jsonnetHome string, urls ...*url.URL) int {
|
||||||
|
|
@ -39,7 +40,7 @@ func updateCommand(dir, jsonnetHome string, urls ...*url.URL) int {
|
||||||
"creating vendor folder")
|
"creating vendor folder")
|
||||||
|
|
||||||
// When updating, locks are ignored.
|
// When updating, locks are ignored.
|
||||||
locks := map[string]spec.Dependency{}
|
locks := map[string]deps.Dependency{}
|
||||||
locked, err := pkg.Ensure(jsonnetFile, jsonnetHome, locks)
|
locked, err := pkg.Ensure(jsonnetFile, jsonnetHome, locks)
|
||||||
kingpin.FatalIfError(err, "failed to install packages")
|
kingpin.FatalIfError(err, "failed to install packages")
|
||||||
|
|
||||||
|
|
|
||||||
23
pkg/git.go
23
pkg/git.go
|
|
@ -33,14 +33,14 @@ import (
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GitPackage struct {
|
type GitPackage struct {
|
||||||
Source *spec.GitSource
|
Source *deps.Git
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGitPackage(source *spec.GitSource) Interface {
|
func NewGitPackage(source *deps.Git) Interface {
|
||||||
return &GitPackage{
|
return &GitPackage{
|
||||||
Source: source,
|
Source: source,
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +169,7 @@ func remoteResolveRef(ctx context.Context, remote string, ref string) (string, e
|
||||||
func (p *GitPackage) Install(ctx context.Context, name, dir, version string) (string, error) {
|
func (p *GitPackage) Install(ctx context.Context, name, dir, version string) (string, error) {
|
||||||
destPath := path.Join(dir, name)
|
destPath := path.Join(dir, name)
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir(filepath.Join(dir, ".tmp"), fmt.Sprintf("jsonnetpkg-%s-%s", name, version))
|
tmpDir, err := ioutil.TempDir(filepath.Join(dir, ".tmp"), fmt.Sprintf("jsonnetpkg-%s-%s", strings.Replace(name, "/", "-", -1), version))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to create tmp dir")
|
return "", errors.Wrap(err, "failed to create tmp dir")
|
||||||
}
|
}
|
||||||
|
|
@ -177,11 +177,11 @@ func (p *GitPackage) Install(ctx context.Context, name, dir, version string) (st
|
||||||
|
|
||||||
// Optimization for GitHub sources: download a tarball archive of the requested
|
// Optimization for GitHub sources: download a tarball archive of the requested
|
||||||
// version instead of cloning the entire repository.
|
// version instead of cloning the entire repository.
|
||||||
isGitHubRemote, err := regexp.MatchString(`^(https|ssh)://github\.com/.+$`, p.Source.Remote)
|
isGitHubRemote, err := regexp.MatchString(`^(https|ssh)://github\.com/.+$`, p.Source.Remote())
|
||||||
if isGitHubRemote {
|
if isGitHubRemote {
|
||||||
// Let git ls-remote decide if "version" is a ref or a commit SHA in the unlikely
|
// Let git ls-remote decide if "version" is a ref or a commit SHA in the unlikely
|
||||||
// but possible event that a ref is comprised of 40 or more hex characters
|
// but possible event that a ref is comprised of 40 or more hex characters
|
||||||
commitSha, err := remoteResolveRef(ctx, p.Source.Remote, version)
|
commitSha, err := remoteResolveRef(ctx, p.Source.Remote(), version)
|
||||||
|
|
||||||
// If the ref resolution failed and "version" looks like a SHA,
|
// If the ref resolution failed and "version" looks like a SHA,
|
||||||
// assume it is one and proceed.
|
// assume it is one and proceed.
|
||||||
|
|
@ -190,7 +190,7 @@ func (p *GitPackage) Install(ctx context.Context, name, dir, version string) (st
|
||||||
commitSha = version
|
commitSha = version
|
||||||
}
|
}
|
||||||
|
|
||||||
archiveUrl := fmt.Sprintf("%s/archive/%s.tar.gz", p.Source.Remote, commitSha)
|
archiveUrl := fmt.Sprintf("%s/archive/%s.tar.gz", p.Source.Remote(), commitSha)
|
||||||
archiveFilepath := fmt.Sprintf("%s.tar.gz", tmpDir)
|
archiveFilepath := fmt.Sprintf("%s.tar.gz", tmpDir)
|
||||||
|
|
||||||
defer os.Remove(archiveFilepath)
|
defer os.Remove(archiveFilepath)
|
||||||
|
|
@ -205,7 +205,12 @@ func (p *GitPackage) Install(ctx context.Context, name, dir, version string) (st
|
||||||
|
|
||||||
// Move the extracted directory to its final destination
|
// Move the extracted directory to its final destination
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = os.Rename(path.Join(tmpDir, p.Source.Subdir), destPath)
|
if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := os.Rename(path.Join(tmpDir, p.Source.Subdir), destPath); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +235,7 @@ func (p *GitPackage) Install(ctx context.Context, name, dir, version string) (st
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.CommandContext(ctx, "git", "remote", "add", "origin", p.Source.Remote)
|
cmd = exec.CommandContext(ctx, "git", "remote", "add", "origin", p.Source.Remote())
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -52,7 +53,7 @@ func Unmarshal(bytes []byte) (spec.JsonnetFile, error) {
|
||||||
return m, errors.Wrap(err, "failed to unmarshal file")
|
return m, errors.Wrap(err, "failed to unmarshal file")
|
||||||
}
|
}
|
||||||
if m.Dependencies == nil {
|
if m.Dependencies == nil {
|
||||||
m.Dependencies = make(map[string]spec.Dependency)
|
m.Dependencies = make(map[string]deps.Dependency)
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
|
||||||
|
|
@ -24,17 +24,17 @@ import (
|
||||||
|
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
)
|
)
|
||||||
|
|
||||||
const notExist = "/this/does/not/exist"
|
const notExist = "/this/does/not/exist"
|
||||||
|
|
||||||
func TestLoad(t *testing.T) {
|
func TestLoad(t *testing.T) {
|
||||||
empty := spec.New()
|
jsonnetfileContent := `
|
||||||
|
{
|
||||||
jsonnetfileContent := `{
|
"legacyImports": false,
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"name": "foobar",
|
|
||||||
"source": {
|
"source": {
|
||||||
"git": {
|
"git": {
|
||||||
"remote": "https://github.com/foobar/foobar",
|
"remote": "https://github.com/foobar/foobar",
|
||||||
|
|
@ -46,53 +46,29 @@ func TestLoad(t *testing.T) {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
jsonnetFileExpected := spec.JsonnetFile{
|
jsonnetFileExpected := spec.JsonnetFile{
|
||||||
Dependencies: map[string]spec.Dependency{
|
LegacyImports: false,
|
||||||
"foobar": {
|
Dependencies: map[string]deps.Dependency{
|
||||||
Name: "foobar",
|
"github.com/foobar/foobar": {
|
||||||
Source: spec.Source{
|
Source: deps.Source{
|
||||||
GitSource: &spec.GitSource{
|
GitSource: &deps.Git{
|
||||||
Remote: "https://github.com/foobar/foobar",
|
Scheme: deps.GitSchemeHTTPS,
|
||||||
|
Host: "github.com",
|
||||||
|
User: "foobar",
|
||||||
|
Repo: "foobar",
|
||||||
Subdir: "",
|
Subdir: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Version: "master",
|
Version: "master",
|
||||||
DepSource: "",
|
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
jf, err := jsonnetfile.Load(notExist)
|
|
||||||
assert.Equal(t, empty, jf)
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
tempDir, err := ioutil.TempDir("", "jb-load-jsonnetfile")
|
tempDir, err := ioutil.TempDir("", "jb-load-jsonnetfile")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer os.RemoveAll(tempDir)
|
||||||
err := os.RemoveAll(tempDir)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
tempFile := filepath.Join(tempDir, jsonnetfile.File)
|
|
||||||
err = ioutil.WriteFile(tempFile, []byte(`{}`), os.ModePerm)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
jf, err := jsonnetfile.Load(tempFile)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, empty, jf)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
tempDir, err := ioutil.TempDir("", "jb-load-jsonnetfile")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err := os.RemoveAll(tempDir)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
tempFile := filepath.Join(tempDir, jsonnetfile.File)
|
tempFile := filepath.Join(tempDir, jsonnetfile.File)
|
||||||
err = ioutil.WriteFile(tempFile, []byte(jsonnetfileContent), os.ModePerm)
|
err = ioutil.WriteFile(tempFile, []byte(jsonnetfileContent), os.ModePerm)
|
||||||
|
|
@ -102,6 +78,29 @@ func TestLoad(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, jsonnetFileExpected, jf)
|
assert.Equal(t, jsonnetFileExpected, jf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadEmpty(t *testing.T) {
|
||||||
|
tempDir, err := ioutil.TempDir("", "jb-load-empty")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
// write empty json file
|
||||||
|
tempFile := filepath.Join(tempDir, jsonnetfile.File)
|
||||||
|
err = ioutil.WriteFile(tempFile, []byte(`{}`), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// expect it to be loaded properly
|
||||||
|
got, err := jsonnetfile.Load(tempFile)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, spec.New(), got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNotExist(t *testing.T) {
|
||||||
|
jf, err := jsonnetfile.Load(notExist)
|
||||||
|
assert.Equal(t, spec.New(), jf)
|
||||||
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileExists(t *testing.T) {
|
func TestFileExists(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,16 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LocalPackage struct {
|
type LocalPackage struct {
|
||||||
Source *spec.LocalSource
|
Source *deps.Local
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalPackage(source *spec.LocalSource) Interface {
|
func NewLocalPackage(source *deps.Local) Interface {
|
||||||
return &LocalPackage{
|
return &LocalPackage{
|
||||||
Source: source,
|
Source: source,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
207
pkg/packages.go
207
pkg/packages.go
|
|
@ -22,19 +22,21 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
"github.com/jsonnet-bundler/jsonnet-bundler/pkg/jsonnetfile"
|
||||||
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec"
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
VersionMismatch = errors.New("multiple colliding versions specified")
|
VersionMismatch = errors.New("multiple colliding versions specified")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure receives all direct packages as, the directory to vendor in and all known locks.
|
// Ensure receives all direct packages, the directory to vendor into and all known locks.
|
||||||
// It then makes sure all direct and nested dependencies are present in vendor at the correct version:
|
// It then makes sure all direct and nested dependencies are present in vendor at the correct version:
|
||||||
//
|
//
|
||||||
// If the package is locked and the files in vendor match the sha256 checksum,
|
// If the package is locked and the files in vendor match the sha256 checksum,
|
||||||
|
|
@ -46,57 +48,183 @@ var (
|
||||||
// desired version in case by `jb install`ing it.
|
// desired version in case by `jb install`ing it.
|
||||||
//
|
//
|
||||||
// Finally, all unknown files and directories are removed from vendor/
|
// Finally, all unknown files and directories are removed from vendor/
|
||||||
func Ensure(direct spec.JsonnetFile, vendorDir string, locks map[string]spec.Dependency) (map[string]spec.Dependency, error) {
|
// The full list of locked depedencies is returned
|
||||||
|
func Ensure(direct spec.JsonnetFile, vendorDir string, oldLocks map[string]deps.Dependency) (map[string]deps.Dependency, error) {
|
||||||
// ensure all required files are in vendor
|
// ensure all required files are in vendor
|
||||||
deps, err := ensure(direct.Dependencies, vendorDir, locks)
|
// This is the actual installation
|
||||||
|
locks, err := ensure(direct.Dependencies, vendorDir, oldLocks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanup unknown dirs from vendor/
|
// remove unchanged legacyNames
|
||||||
f, err := os.Open(vendorDir)
|
CleanLegacyName(locks)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
// find unknown dirs in vendor/
|
||||||
|
names := []string{}
|
||||||
|
err = filepath.Walk(vendorDir, func(path string, i os.FileInfo, err error) error {
|
||||||
|
if path == vendorDir {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
names, err := f.Readdirnames(0)
|
if !i.IsDir() {
|
||||||
if err != nil {
|
return nil
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
for _, name := range names {
|
|
||||||
if _, ok := deps[name]; !ok {
|
names = append(names, path)
|
||||||
dir := filepath.Join(vendorDir, name)
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// remove them
|
||||||
|
for _, dir := range names {
|
||||||
|
name := strings.TrimPrefix(dir, "vendor/")
|
||||||
|
if !known(locks, name) {
|
||||||
if err := os.RemoveAll(dir); err != nil {
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if name != ".tmp" {
|
if !strings.HasPrefix(name, ".tmp") {
|
||||||
color.Magenta("CLEAN %s", dir)
|
color.Magenta("CLEAN %s", dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the final lockfile contents
|
// remove all symlinks, optionally adding known ones back later if wished
|
||||||
return deps, nil
|
if err := cleanLegacySymlinks(vendorDir, locks); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !direct.LegacyImports {
|
||||||
|
return locks, nil
|
||||||
|
}
|
||||||
|
if err := linkLegacy(vendorDir, locks); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensure(direct map[string]spec.Dependency, vendorDir string, locks map[string]spec.Dependency) (map[string]spec.Dependency, error) {
|
// return the final lockfile contents
|
||||||
deps := make(map[string]spec.Dependency)
|
return locks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CleanLegacyName(list map[string]deps.Dependency) {
|
||||||
|
for k, d := range list {
|
||||||
|
// unset if not changed by user
|
||||||
|
if d.LegacyNameCompat == d.Source.LegacyName() {
|
||||||
|
dep := list[k]
|
||||||
|
dep.LegacyNameCompat = ""
|
||||||
|
list[k] = dep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanLegacySymlinks(vendorDir string, locks map[string]deps.Dependency) error {
|
||||||
|
// local packages need to be ignored
|
||||||
|
locals := map[string]bool{}
|
||||||
|
for _, d := range locks {
|
||||||
|
if d.Source.LocalSource == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
locals[filepath.Join(vendorDir, d.Name())] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all symlinks first
|
||||||
|
return filepath.Walk(vendorDir, func(path string, i os.FileInfo, err error) error {
|
||||||
|
if locals[path] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Mode()&os.ModeSymlink != 0 {
|
||||||
|
if err := os.Remove(path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func linkLegacy(vendorDir string, locks map[string]deps.Dependency) error {
|
||||||
|
// create only the ones we want
|
||||||
|
for _, d := range locks {
|
||||||
|
// localSource still uses the relative style
|
||||||
|
if d.Source.LocalSource != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
legacyName := filepath.Join("vendor", d.LegacyName())
|
||||||
|
pkgName := d.Name()
|
||||||
|
|
||||||
|
taken, err := checkLegacyNameTaken(legacyName, pkgName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if taken {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the symlink
|
||||||
|
if err := os.Symlink(
|
||||||
|
filepath.Join(pkgName),
|
||||||
|
filepath.Join(legacyName),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkLegacyNameTaken(legacyName string, pkgName string) (bool, error) {
|
||||||
|
fi, err := os.Lstat(legacyName)
|
||||||
|
if err != nil {
|
||||||
|
// does not exist: not taken
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
// a real error
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// is it a symlink?
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
s, err := os.Readlink(legacyName)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
color.Yellow("WARN: cannot link '%s' to '%s', because package '%s' already uses that name. The absolute import still works\n", pkgName, legacyName, s)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sth else
|
||||||
|
color.Yellow("WARN: cannot link '%s' to '%s', because the file/directory already exists. The absolute import still works.\n", pkgName, legacyName)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func known(deps map[string]deps.Dependency, p string) bool {
|
||||||
|
for _, d := range deps {
|
||||||
|
k := d.Name()
|
||||||
|
if strings.HasPrefix(p, k) || strings.HasPrefix(k, p) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensure(direct map[string]deps.Dependency, vendorDir string, locks map[string]deps.Dependency) (map[string]deps.Dependency, error) {
|
||||||
|
deps := make(map[string]deps.Dependency)
|
||||||
|
|
||||||
for _, d := range direct {
|
for _, d := range direct {
|
||||||
l, present := locks[d.Name]
|
l, present := locks[d.Name()]
|
||||||
|
|
||||||
// already locked and the integrity is intact
|
// already locked and the integrity is intact
|
||||||
if present {
|
if present {
|
||||||
d.Version = locks[d.Name].Version
|
d.Version = locks[d.Name()].Version
|
||||||
|
|
||||||
if check(l, vendorDir) {
|
if check(l, vendorDir) {
|
||||||
deps[d.Name] = l
|
deps[d.Name()] = l
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expectedSum := locks[d.Name].Sum
|
expectedSum := locks[d.Name()].Sum
|
||||||
|
|
||||||
// either not present or not intact: download again
|
// either not present or not intact: download again
|
||||||
dir := filepath.Join(vendorDir, d.Name)
|
dir := filepath.Join(vendorDir, d.Name())
|
||||||
os.RemoveAll(dir)
|
os.RemoveAll(dir)
|
||||||
|
|
||||||
locked, err := download(d, vendorDir)
|
locked, err := download(d, vendorDir)
|
||||||
|
|
@ -104,15 +232,15 @@ func ensure(direct map[string]spec.Dependency, vendorDir string, locks map[strin
|
||||||
return nil, errors.Wrap(err, "downloading")
|
return nil, errors.Wrap(err, "downloading")
|
||||||
}
|
}
|
||||||
if expectedSum != "" && locked.Sum != expectedSum {
|
if expectedSum != "" && locked.Sum != expectedSum {
|
||||||
return nil, fmt.Errorf("checksum mismatch for %s. Expected %s but got %s", d.Name, expectedSum, locked.Sum)
|
return nil, fmt.Errorf("checksum mismatch for %s. Expected %s but got %s", d.Name(), expectedSum, locked.Sum)
|
||||||
}
|
}
|
||||||
deps[d.Name] = *locked
|
deps[d.Name()] = *locked
|
||||||
// we settled on a new version, add it to the locks for recursion
|
// we settled on a new version, add it to the locks for recursion
|
||||||
locks[d.Name] = *locked
|
locks[d.Name()] = *locked
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range deps {
|
for _, d := range deps {
|
||||||
f, err := jsonnetfile.Load(filepath.Join(vendorDir, d.Name, jsonnetfile.File))
|
f, err := jsonnetfile.Load(filepath.Join(vendorDir, d.Name(), jsonnetfile.File))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
continue
|
continue
|
||||||
|
|
@ -126,8 +254,8 @@ func ensure(direct map[string]spec.Dependency, vendorDir string, locks map[strin
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range nested {
|
for _, d := range nested {
|
||||||
if _, ok := deps[d.Name]; !ok {
|
if _, ok := deps[d.Name()]; !ok {
|
||||||
deps[d.Name] = d
|
deps[d.Name()] = d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +265,7 @@ func ensure(direct map[string]spec.Dependency, vendorDir string, locks map[strin
|
||||||
|
|
||||||
// download retrieves a package from a remote upstream. The checksum of the
|
// download retrieves a package from a remote upstream. The checksum of the
|
||||||
// files is generated afterwards.
|
// files is generated afterwards.
|
||||||
func download(d spec.Dependency, vendorDir string) (*spec.Dependency, error) {
|
func download(d deps.Dependency, vendorDir string) (*deps.Dependency, error) {
|
||||||
var p Interface
|
var p Interface
|
||||||
switch {
|
switch {
|
||||||
case d.Source.GitSource != nil:
|
case d.Source.GitSource != nil:
|
||||||
|
|
@ -150,32 +278,29 @@ func download(d spec.Dependency, vendorDir string) (*spec.Dependency, error) {
|
||||||
return nil, errors.New("either git or local source is required")
|
return nil, errors.New("either git or local source is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
version, err := p.Install(context.TODO(), d.Name, vendorDir, d.Version)
|
version, err := p.Install(context.TODO(), d.Name(), vendorDir, d.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var sum string
|
var sum string
|
||||||
if d.Source.LocalSource == nil {
|
if d.Source.LocalSource == nil {
|
||||||
sum = hashDir(filepath.Join(vendorDir, d.Name))
|
sum = hashDir(filepath.Join(vendorDir, d.Name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &spec.Dependency{
|
d.Version = version
|
||||||
Name: d.Name,
|
d.Sum = sum
|
||||||
Source: d.Source,
|
return &d, nil
|
||||||
Version: version,
|
|
||||||
Sum: sum,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check returns whether the files present at the vendor/ folder match the
|
// check returns whether the files present at the vendor/ folder match the
|
||||||
// sha256 sum of the package. local-directory dependencies are not checked as
|
// sha256 sum of the package. local-directory dependencies are not checked as
|
||||||
// their purpose is to change during development where integrity checking would
|
// their purpose is to change during development where integrity checking would
|
||||||
// be a hindrance.
|
// be a hindrance.
|
||||||
func check(d spec.Dependency, vendorDir string) bool {
|
func check(d deps.Dependency, vendorDir string) bool {
|
||||||
// assume a local dependency is intact as long as it exists
|
// assume a local dependency is intact as long as it exists
|
||||||
if d.Source.LocalSource != nil {
|
if d.Source.LocalSource != nil {
|
||||||
x, err := jsonnetfile.Exists(filepath.Join(vendorDir, d.Name))
|
x, err := jsonnetfile.Exists(filepath.Join(vendorDir, d.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +312,7 @@ func check(d spec.Dependency, vendorDir string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
dir := filepath.Join(vendorDir, d.Name)
|
dir := filepath.Join(vendorDir, d.Name())
|
||||||
sum := hashDir(dir)
|
sum := hashDir(dir)
|
||||||
return d.Sum == sum
|
return d.Sum == sum
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,83 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKnown(t *testing.T) {
|
||||||
|
deps := map[string]deps.Dependency{
|
||||||
|
"ksonnet-lib": deps.Dependency{
|
||||||
|
Source: deps.Source{GitSource: &deps.Git{
|
||||||
|
Scheme: deps.GitSchemeHTTPS,
|
||||||
|
Host: "github.com",
|
||||||
|
User: "ksonnet",
|
||||||
|
Repo: "ksonnet-lib",
|
||||||
|
Subdir: "/ksonnet.beta.4",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
paths := []string{
|
||||||
|
"github.com",
|
||||||
|
"github.com/ksonnet",
|
||||||
|
"github.com/ksonnet/ksonnet-lib",
|
||||||
|
"github.com/ksonnet/ksonnet-lib/ksonnet.beta.4",
|
||||||
|
"github.com/ksonnet/ksonnet-lib/ksonnet.beta.4/k.libsonnet",
|
||||||
|
"github.com/ksonnet-util", // don't know that one
|
||||||
|
"ksonnet.beta.4", // the symlink
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []string{
|
||||||
|
"github.com",
|
||||||
|
"github.com/ksonnet",
|
||||||
|
"github.com/ksonnet/ksonnet",
|
||||||
|
"github.com/ksonnet/ksonnet-lib",
|
||||||
|
"github.com/ksonnet/ksonnet-lib/ksonnet.beta.4",
|
||||||
|
"github.com/ksonnet/ksonnet-lib/ksonnet.beta.4/k.libsonnet",
|
||||||
|
}
|
||||||
|
|
||||||
|
w := make(map[string]bool)
|
||||||
|
for _, k := range want {
|
||||||
|
w[k] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range paths {
|
||||||
|
if known(deps, p) != w[p] {
|
||||||
|
t.Fatalf("expected %s to be %v", p, w[p])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanLegacyName(t *testing.T) {
|
||||||
|
deps := func(name string) map[string]deps.Dependency {
|
||||||
|
return map[string]deps.Dependency{
|
||||||
|
"ksonnet-lib": deps.Dependency{
|
||||||
|
LegacyNameCompat: name,
|
||||||
|
Source: deps.Source{GitSource: &deps.Git{
|
||||||
|
Scheme: deps.GitSchemeHTTPS,
|
||||||
|
Host: "github.com",
|
||||||
|
User: "ksonnet",
|
||||||
|
Repo: "ksonnet-lib",
|
||||||
|
Subdir: "/ksonnet.beta.4",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
cases := map[string]bool{
|
||||||
|
"ksonnet": false,
|
||||||
|
"ksonnet.beta.4": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, want := range cases {
|
||||||
|
list := deps(name)
|
||||||
|
CleanLegacyName(list)
|
||||||
|
if (list["ksonnet-lib"].LegacyNameCompat == "") != want {
|
||||||
|
t.Fatalf("expected `%s` to be removed: %v", name, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
106
spec/deps/dependencies.go
Normal file
106
spec/deps/dependencies.go
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
// 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 deps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Dependency struct {
|
||||||
|
Source Source `json:"source"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Sum string `json:"sum,omitempty"`
|
||||||
|
|
||||||
|
// older schema used to have `name`. We still need that data for
|
||||||
|
// `LegacyName`
|
||||||
|
LegacyNameCompat string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(dir, uri string) *Dependency {
|
||||||
|
if uri == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if d := parseGit(uri); d != nil {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseLocal(dir, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Dependency) Name() string {
|
||||||
|
return d.Source.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Dependency) LegacyName() string {
|
||||||
|
if d.LegacyNameCompat != "" {
|
||||||
|
return d.LegacyNameCompat
|
||||||
|
}
|
||||||
|
return d.Source.LegacyName()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Source struct {
|
||||||
|
GitSource *Git `json:"git,omitempty"`
|
||||||
|
LocalSource *Local `json:"local,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Source) Name() string {
|
||||||
|
switch {
|
||||||
|
case s.GitSource != nil:
|
||||||
|
return s.GitSource.Name()
|
||||||
|
case s.LocalSource != nil:
|
||||||
|
return s.LegacyName()
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Source) LegacyName() string {
|
||||||
|
switch {
|
||||||
|
case s.GitSource != nil:
|
||||||
|
return s.GitSource.LegacyName()
|
||||||
|
case s.LocalSource != nil:
|
||||||
|
return filepath.Base(s.LocalSource.Directory)
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Local struct {
|
||||||
|
Directory string `json:"directory"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLocal(dir, p string) *Dependency {
|
||||||
|
clean := filepath.Clean(p)
|
||||||
|
abs := filepath.Join(dir, clean)
|
||||||
|
|
||||||
|
info, err := os.Stat(abs)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Dependency{
|
||||||
|
Source: Source{
|
||||||
|
LocalSource: &Local{
|
||||||
|
Directory: clean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Version: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
70
spec/deps/dependencies_test.go
Normal file
70
spec/deps/dependencies_test.go
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
// 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 deps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseDependency(t *testing.T) {
|
||||||
|
const testFolder = "test/jsonnet/foobar"
|
||||||
|
err := os.MkdirAll(testFolder, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll("test")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
want *Dependency
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty",
|
||||||
|
path: "",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid",
|
||||||
|
path: "github.com/foo",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "local",
|
||||||
|
path: testFolder,
|
||||||
|
want: &Dependency{
|
||||||
|
Source: Source{
|
||||||
|
LocalSource: &Local{
|
||||||
|
Directory: "test/jsonnet/foobar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Version: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
_ = t.Run(tt.name, func(t *testing.T) {
|
||||||
|
dependency := Parse("", tt.path)
|
||||||
|
|
||||||
|
if tt.path == "" {
|
||||||
|
assert.Nil(t, dependency)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, tt.want, dependency)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
203
spec/deps/git.go
Normal file
203
spec/deps/git.go
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
// 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 deps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GitSchemeSSH = "ssh://git@"
|
||||||
|
GitSchemeHTTPS = "https://"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Git holds all required information for cloning a package from git
|
||||||
|
type Git struct {
|
||||||
|
// Scheme (Protocol) used (https, git+ssh)
|
||||||
|
Scheme string
|
||||||
|
|
||||||
|
// Hostname the repo is located at
|
||||||
|
Host string
|
||||||
|
// User (github.com/<user>)
|
||||||
|
User string
|
||||||
|
// Repo (github.com/<user>/<repo>)
|
||||||
|
Repo string
|
||||||
|
// Subdir (github.com/<user>/<repo>/<subdir>)
|
||||||
|
Subdir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// json representation of Git (for compatiblity with old format)
|
||||||
|
type jsonGit struct {
|
||||||
|
Remote string `json:"remote"`
|
||||||
|
Subdir string `json:"subdir"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON takes care of translating between Git and jsonGit
|
||||||
|
func (gs *Git) MarshalJSON() ([]byte, error) {
|
||||||
|
j := jsonGit{
|
||||||
|
Remote: gs.Remote(),
|
||||||
|
Subdir: strings.TrimPrefix(gs.Subdir, "/"),
|
||||||
|
}
|
||||||
|
return json.Marshal(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON takes care of translating between Git and jsonGit
|
||||||
|
func (gs *Git) UnmarshalJSON(data []byte) error {
|
||||||
|
var j jsonGit
|
||||||
|
if err := json.Unmarshal(data, &j); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if j.Subdir != "" {
|
||||||
|
gs.Subdir = "/" + strings.TrimPrefix(j.Subdir, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := parseGit(j.Remote)
|
||||||
|
gs.Host = tmp.Source.GitSource.Host
|
||||||
|
gs.User = tmp.Source.GitSource.User
|
||||||
|
gs.Repo = tmp.Source.GitSource.Repo
|
||||||
|
gs.Scheme = tmp.Source.GitSource.Scheme
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the repository in a go-like format (github.com/user/repo/subdir)
|
||||||
|
func (gs *Git) Name() string {
|
||||||
|
return fmt.Sprintf("%s/%s/%s%s", gs.Host, gs.User, gs.Repo, gs.Subdir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyName returns the last element of the packages path
|
||||||
|
// example: github.com/ksonnet/ksonnet-lib/ksonnet.beta.4 becomes ksonnet.beta.4
|
||||||
|
func (gs *Git) LegacyName() string {
|
||||||
|
return filepath.Base(gs.Repo + gs.Subdir)
|
||||||
|
}
|
||||||
|
|
||||||
|
var gitProtoFmts = map[string]string{
|
||||||
|
GitSchemeSSH: GitSchemeSSH + "%s:%s/%s.git",
|
||||||
|
GitSchemeHTTPS: GitSchemeHTTPS + "%s/%s/%s",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote returns a remote string that can be passed to git
|
||||||
|
func (gs *Git) Remote() string {
|
||||||
|
return fmt.Sprintf(gitProtoFmts[gs.Scheme],
|
||||||
|
gs.Host, gs.User, gs.Repo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// regular expressions for matching package uris
|
||||||
|
const (
|
||||||
|
gitSSHExp = `git\+ssh://git@(?P<host>[^:]+):(?P<user>[^/]+)/(?P<repo>[^/]+).git`
|
||||||
|
githubSlugExp = `github.com/(?P<user>[-_a-zA-Z0-9]+)/(?P<repo>[-_a-zA-Z0-9]+)`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
gitSSHRegex = regexp.MustCompile(gitSSHExp)
|
||||||
|
gitSSHWithVersionRegex = regexp.MustCompile(gitSSHExp + `@(?P<version>.*)`)
|
||||||
|
gitSSHWithPathRegex = regexp.MustCompile(gitSSHExp + `/(?P<subdir>.*)`)
|
||||||
|
gitSSHWithPathAndVersionRegex = regexp.MustCompile(gitSSHExp + `/(?P<subdir>.*)@(?P<version>.*)`)
|
||||||
|
|
||||||
|
githubSlugRegex = regexp.MustCompile(githubSlugExp)
|
||||||
|
githubSlugWithVersionRegex = regexp.MustCompile(githubSlugExp + `@(?P<version>.*)`)
|
||||||
|
githubSlugWithPathRegex = regexp.MustCompile(githubSlugExp + `/(?P<subdir>.*)`)
|
||||||
|
githubSlugWithPathAndVersionRegex = regexp.MustCompile(githubSlugExp + `/(?P<subdir>.*)@(?P<version>.*)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseGit(uri string) *Dependency {
|
||||||
|
var d = Dependency{
|
||||||
|
Version: "master",
|
||||||
|
Source: Source{},
|
||||||
|
}
|
||||||
|
var gs *Git
|
||||||
|
var version string
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case githubSlugRegex.MatchString(uri):
|
||||||
|
gs, version = parseGitHub(uri)
|
||||||
|
case gitSSHRegex.MatchString(uri):
|
||||||
|
gs, version = parseGitSSH(uri)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if gs.Subdir != "" {
|
||||||
|
gs.Subdir = "/" + gs.Subdir
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Source.GitSource = gs
|
||||||
|
if version != "" {
|
||||||
|
d.Version = version
|
||||||
|
}
|
||||||
|
return &d
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGitSSH(p string) (gs *Git, version string) {
|
||||||
|
gs, version = match(p, []*regexp.Regexp{
|
||||||
|
gitSSHWithPathAndVersionRegex,
|
||||||
|
gitSSHWithPathRegex,
|
||||||
|
gitSSHWithVersionRegex,
|
||||||
|
gitSSHRegex,
|
||||||
|
})
|
||||||
|
|
||||||
|
gs.Scheme = GitSchemeSSH
|
||||||
|
return gs, version
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGitHub(p string) (gs *Git, version string) {
|
||||||
|
gs, version = match(p, []*regexp.Regexp{
|
||||||
|
githubSlugWithPathAndVersionRegex,
|
||||||
|
githubSlugWithPathRegex,
|
||||||
|
githubSlugWithVersionRegex,
|
||||||
|
githubSlugRegex,
|
||||||
|
})
|
||||||
|
|
||||||
|
gs.Scheme = GitSchemeHTTPS
|
||||||
|
gs.Host = "github.com"
|
||||||
|
return gs, version
|
||||||
|
}
|
||||||
|
|
||||||
|
func match(p string, exps []*regexp.Regexp) (gs *Git, version string) {
|
||||||
|
gs = &Git{}
|
||||||
|
for _, e := range exps {
|
||||||
|
if !e.MatchString(p) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := reSubMatchMap(e, p)
|
||||||
|
gs.Host = matches["host"]
|
||||||
|
gs.User = matches["user"]
|
||||||
|
gs.Repo = matches["repo"]
|
||||||
|
|
||||||
|
if sd, ok := matches["subdir"]; ok {
|
||||||
|
gs.Subdir = sd
|
||||||
|
}
|
||||||
|
|
||||||
|
return gs, matches["version"]
|
||||||
|
}
|
||||||
|
return gs, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func reSubMatchMap(r *regexp.Regexp, str string) map[string]string {
|
||||||
|
match := r.FindStringSubmatch(str)
|
||||||
|
subMatchMap := make(map[string]string)
|
||||||
|
for i, name := range r.SubexpNames() {
|
||||||
|
if i != 0 {
|
||||||
|
subMatchMap[name] = match[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return subMatchMap
|
||||||
|
}
|
||||||
68
spec/deps/git_test.go
Normal file
68
spec/deps/git_test.go
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
// 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 deps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseGit(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uri string
|
||||||
|
want *Dependency
|
||||||
|
wantRemote string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "GitHub",
|
||||||
|
uri: "github.com/ksonnet/ksonnet-lib/ksonnet.beta.3",
|
||||||
|
want: &Dependency{
|
||||||
|
Version: "master",
|
||||||
|
Source: Source{GitSource: &Git{
|
||||||
|
Scheme: GitSchemeHTTPS,
|
||||||
|
Host: "github.com",
|
||||||
|
User: "ksonnet",
|
||||||
|
Repo: "ksonnet-lib",
|
||||||
|
Subdir: "/ksonnet.beta.3",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
wantRemote: "https://github.com/ksonnet/ksonnet-lib",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SSH",
|
||||||
|
uri: "git+ssh://git@my.host:user/repo.git/foobar@v1",
|
||||||
|
want: &Dependency{
|
||||||
|
Version: "v1",
|
||||||
|
Source: Source{GitSource: &Git{
|
||||||
|
Scheme: GitSchemeSSH,
|
||||||
|
Host: "my.host",
|
||||||
|
User: "user",
|
||||||
|
Repo: "repo",
|
||||||
|
Subdir: "/foobar",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
wantRemote: "ssh://git@my.host:user/repo.git",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range tests {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
got := Parse("", c.uri)
|
||||||
|
assert.Equal(t, c.want, got)
|
||||||
|
assert.Equal(t, c.wantRemote, got.Source.GitSource.Remote())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
50
spec/spec.go
50
spec/spec.go
|
|
@ -17,73 +17,67 @@ package spec
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JsonnetFile is the structure of a `.json` file describing a set of jsonnet
|
// JsonnetFile is the structure of a `.json` file describing a set of jsonnet
|
||||||
// dependencies. It is used for both, the jsonnetFile and the lockFile.
|
// dependencies. It is used for both, the jsonnetFile and the lockFile.
|
||||||
type JsonnetFile struct {
|
type JsonnetFile struct {
|
||||||
Dependencies map[string]Dependency
|
// List of dependencies
|
||||||
|
Dependencies map[string]deps.Dependency
|
||||||
|
|
||||||
|
// Symlink files to old location
|
||||||
|
LegacyImports bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new JsonnetFile with the dependencies map initialized
|
// New returns a new JsonnetFile with the dependencies map initialized
|
||||||
func New() JsonnetFile {
|
func New() JsonnetFile {
|
||||||
return JsonnetFile{
|
return JsonnetFile{
|
||||||
Dependencies: make(map[string]Dependency),
|
Dependencies: make(map[string]deps.Dependency),
|
||||||
|
LegacyImports: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// jsonFile is the json representation of a JsonnetFile, which is different for
|
// jsonFile is the json representation of a JsonnetFile, which is different for
|
||||||
// compatibility reasons.
|
// compatibility reasons.
|
||||||
type jsonFile struct {
|
type jsonFile struct {
|
||||||
Dependencies []Dependency `json:"dependencies"`
|
Dependencies []deps.Dependency `json:"dependencies"`
|
||||||
|
LegacyImports bool `json:"legacyImports"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals a `jsonFile`'s json into a JsonnetFile
|
// UnmarshalJSON unmarshals a `jsonFile`'s json into a JsonnetFile
|
||||||
func (jf *JsonnetFile) UnmarshalJSON(data []byte) error {
|
func (jf *JsonnetFile) UnmarshalJSON(data []byte) error {
|
||||||
var s jsonFile
|
var s jsonFile
|
||||||
|
s.LegacyImports = jf.LegacyImports // adpot default
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &s); err != nil {
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
jf.Dependencies = make(map[string]Dependency)
|
jf.Dependencies = make(map[string]deps.Dependency)
|
||||||
for _, d := range s.Dependencies {
|
for _, d := range s.Dependencies {
|
||||||
jf.Dependencies[d.Name] = d
|
jf.Dependencies[d.Name()] = d
|
||||||
}
|
}
|
||||||
|
jf.LegacyImports = s.LegacyImports
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON serializes a JsonnetFile into json of the format of a `jsonFile`
|
// MarshalJSON serializes a JsonnetFile into json of the format of a `jsonFile`
|
||||||
func (jf JsonnetFile) MarshalJSON() ([]byte, error) {
|
func (jf JsonnetFile) MarshalJSON() ([]byte, error) {
|
||||||
var s jsonFile
|
var s jsonFile
|
||||||
|
s.LegacyImports = jf.LegacyImports
|
||||||
for _, d := range jf.Dependencies {
|
for _, d := range jf.Dependencies {
|
||||||
s.Dependencies = append(s.Dependencies, d)
|
s.Dependencies = append(s.Dependencies, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.SliceStable(s.Dependencies, func(i int, j int) bool {
|
sort.SliceStable(s.Dependencies, func(i int, j int) bool {
|
||||||
return s.Dependencies[i].Name < s.Dependencies[j].Name
|
return s.Dependencies[i].Name() < s.Dependencies[j].Name()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if s.Dependencies == nil {
|
||||||
|
s.Dependencies = make([]deps.Dependency, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
return json.Marshal(s)
|
return json.Marshal(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Dependency struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Source Source `json:"source"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Sum string `json:"sum,omitempty"`
|
|
||||||
DepSource string `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Source struct {
|
|
||||||
GitSource *GitSource `json:"git,omitempty"`
|
|
||||||
LocalSource *LocalSource `json:"local,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GitSource struct {
|
|
||||||
Remote string `json:"remote"`
|
|
||||||
Subdir string `json:"subdir"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocalSource struct {
|
|
||||||
Directory string `json:"directory"`
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
@ -25,7 +26,6 @@ import (
|
||||||
const jsonJF = `{
|
const jsonJF = `{
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"name": "grafana-builder",
|
|
||||||
"source": {
|
"source": {
|
||||||
"git": {
|
"git": {
|
||||||
"remote": "https://github.com/grafana/jsonnet-libs",
|
"remote": "https://github.com/grafana/jsonnet-libs",
|
||||||
|
|
@ -36,7 +36,7 @@ const jsonJF = `{
|
||||||
"sum": "ELsYwK+kGdzX1mee2Yy+/b2mdO4Y503BOCDkFzwmGbE="
|
"sum": "ELsYwK+kGdzX1mee2Yy+/b2mdO4Y503BOCDkFzwmGbE="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "prometheus-mixin",
|
"name": "prometheus",
|
||||||
"source": {
|
"source": {
|
||||||
"git": {
|
"git": {
|
||||||
"remote": "https://github.com/prometheus/prometheus",
|
"remote": "https://github.com/prometheus/prometheus",
|
||||||
|
|
@ -46,29 +46,36 @@ const jsonJF = `{
|
||||||
"version": "7c039a6b3b4b2a9d7c613ac8bd3fc16e8ca79684",
|
"version": "7c039a6b3b4b2a9d7c613ac8bd3fc16e8ca79684",
|
||||||
"sum": "bVGOsq3hLOw2irNPAS91a5dZJqQlBUNWy3pVwM4+kIY="
|
"sum": "bVGOsq3hLOw2irNPAS91a5dZJqQlBUNWy3pVwM4+kIY="
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"legacyImports": false
|
||||||
}`
|
}`
|
||||||
|
|
||||||
func testData() JsonnetFile {
|
func testData() JsonnetFile {
|
||||||
return JsonnetFile{
|
return JsonnetFile{
|
||||||
Dependencies: map[string]Dependency{
|
LegacyImports: false,
|
||||||
"grafana-builder": {
|
Dependencies: map[string]deps.Dependency{
|
||||||
Name: "grafana-builder",
|
"github.com/grafana/jsonnet-libs/grafana-builder": {
|
||||||
Source: Source{
|
Source: deps.Source{
|
||||||
GitSource: &GitSource{
|
GitSource: &deps.Git{
|
||||||
Remote: "https://github.com/grafana/jsonnet-libs",
|
Scheme: deps.GitSchemeHTTPS,
|
||||||
Subdir: "grafana-builder",
|
Host: "github.com",
|
||||||
|
User: "grafana",
|
||||||
|
Repo: "jsonnet-libs",
|
||||||
|
Subdir: "/grafana-builder",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Version: "54865853ebc1f901964e25a2e7a0e4d2cb6b9648",
|
Version: "54865853ebc1f901964e25a2e7a0e4d2cb6b9648",
|
||||||
Sum: "ELsYwK+kGdzX1mee2Yy+/b2mdO4Y503BOCDkFzwmGbE=",
|
Sum: "ELsYwK+kGdzX1mee2Yy+/b2mdO4Y503BOCDkFzwmGbE=",
|
||||||
},
|
},
|
||||||
"prometheus-mixin": {
|
"github.com/prometheus/prometheus/documentation/prometheus-mixin": {
|
||||||
Name: "prometheus-mixin",
|
LegacyNameCompat: "prometheus",
|
||||||
Source: Source{
|
Source: deps.Source{
|
||||||
GitSource: &GitSource{
|
GitSource: &deps.Git{
|
||||||
Remote: "https://github.com/prometheus/prometheus",
|
Scheme: deps.GitSchemeHTTPS,
|
||||||
Subdir: "documentation/prometheus-mixin",
|
Host: "github.com",
|
||||||
|
User: "prometheus",
|
||||||
|
Repo: "prometheus",
|
||||||
|
Subdir: "/documentation/prometheus-mixin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Version: "7c039a6b3b4b2a9d7c613ac8bd3fc16e8ca79684",
|
Version: "7c039a6b3b4b2a9d7c613ac8bd3fc16e8ca79684",
|
||||||
|
|
|
||||||
127
tool/rewrite/rewrite.go
Normal file
127
tool/rewrite/rewrite.go
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
// 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 rewrite provides a tool that automatically rewrites legacy imports to
|
||||||
|
// absolute ones
|
||||||
|
package rewrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
|
)
|
||||||
|
|
||||||
|
var expr = regexp.MustCompile(`(?mU)(import ["'])(.*)(\/.*["'])`)
|
||||||
|
|
||||||
|
// Rewrite changes all imports in `dir` from legacy to absolute style
|
||||||
|
// All files in `vendorDir` are ignored
|
||||||
|
func Rewrite(dir, vendorDir string, packages map[string]deps.Dependency) error {
|
||||||
|
imports := make(map[string]string)
|
||||||
|
for _, p := range packages {
|
||||||
|
if p.LegacyName() == p.Name() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
imports[p.LegacyName()] = p.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
vendorFi, err := os.Stat(filepath.Join(dir, vendorDir))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// list all Jsonnet files
|
||||||
|
files := []string{}
|
||||||
|
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.SameFile(vendorFi, info) {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if ext := filepath.Ext(path); ext == ".jsonnet" || ext == ".libsonnet" {
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// change the imports
|
||||||
|
for _, s := range files {
|
||||||
|
if err := replaceFile(s, imports); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrap(s, q string) string {
|
||||||
|
return fmt.Sprintf(`import %s%s`, q, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceFile(name string, imports map[string]string) error {
|
||||||
|
raw, err := ioutil.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := replace(string(raw), imports)
|
||||||
|
return ioutil.WriteFile(name, out, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func replace(data string, imports map[string]string) []byte {
|
||||||
|
contents := strings.Split(string(data), "\n")
|
||||||
|
|
||||||
|
// try to fix imports line by line
|
||||||
|
buf := make([]string, 0, len(contents))
|
||||||
|
for _, line := range contents {
|
||||||
|
match := expr.FindStringSubmatch(line)
|
||||||
|
// no import in this line: push unmodified
|
||||||
|
if len(match) == 0 {
|
||||||
|
buf = append(buf, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// the legacyName
|
||||||
|
matchedName := match[2]
|
||||||
|
|
||||||
|
replaced := false
|
||||||
|
for legacy, absolute := range imports {
|
||||||
|
// not this import
|
||||||
|
if matchedName != legacy {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix the import
|
||||||
|
replaced = true
|
||||||
|
buf = append(buf, expr.ReplaceAllString(line, "${1}"+absolute+"${3}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// no matching known import found? push unmodified
|
||||||
|
if !replaced {
|
||||||
|
buf = append(buf, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(strings.Join(buf, "\n"))
|
||||||
|
}
|
||||||
76
tool/rewrite/rewrite_test.go
Normal file
76
tool/rewrite/rewrite_test.go
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
// 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 rewrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/jsonnet-bundler/jsonnet-bundler/spec/deps"
|
||||||
|
)
|
||||||
|
|
||||||
|
const sample = `
|
||||||
|
(import "k.libsonnet") + // not vendored
|
||||||
|
(import "ksonnet/abc.jsonnet") + // prefix of next
|
||||||
|
(import "ksonnet.beta.4/k.libsonnet") + // normal import
|
||||||
|
(import "github.com/ksonnet/ksonnet/def.jsonnet") + // already absolute
|
||||||
|
(import "prometheus/mixin/whatever/abc.libsonnet") + // nested
|
||||||
|
(import "mylib/foo.libsonnet") + // not managed by jb
|
||||||
|
// completely unrelated line:
|
||||||
|
[ "nice" ]
|
||||||
|
`
|
||||||
|
|
||||||
|
const want = `
|
||||||
|
(import "k.libsonnet") + // not vendored
|
||||||
|
(import "github.com/ksonnet/ksonnet/abc.jsonnet") + // prefix of next
|
||||||
|
(import "github.com/ksonnet/ksonnet-lib/ksonnet.beta.4/k.libsonnet") + // normal import
|
||||||
|
(import "github.com/ksonnet/ksonnet/def.jsonnet") + // already absolute
|
||||||
|
(import "github.com/prometheus/prometheus/mixin/whatever/abc.libsonnet") + // nested
|
||||||
|
(import "mylib/foo.libsonnet") + // not managed by jb
|
||||||
|
// completely unrelated line:
|
||||||
|
[ "nice" ]
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestRewrite(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "jbrewrite")
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
name := filepath.Join(dir, "test.jsonnet")
|
||||||
|
err = ioutil.WriteFile(name, []byte(sample), 0644)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
vendorDir := filepath.Join(dir, "vendor")
|
||||||
|
err = os.MkdirAll(vendorDir, os.ModePerm)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
err = Rewrite(dir, "vendor", locks)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
content, err := ioutil.ReadFile(name)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, want, string(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
var locks = map[string]deps.Dependency{
|
||||||
|
"ksonnet": *deps.Parse("", "github.com/ksonnet/ksonnet"),
|
||||||
|
"ksonnet.beta.4": *deps.Parse("", "github.com/ksonnet/ksonnet-lib/ksonnet.beta.4"),
|
||||||
|
"prometheus": *deps.Parse("", "github.com/prometheus/prometheus"),
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue