Skip to content

Commit

Permalink
feat: 'kpm run' supports compile single kcl file
Browse files Browse the repository at this point in the history
  • Loading branch information
zong-zhe committed Sep 8, 2023
1 parent 730ecef commit 1d7a64d
Show file tree
Hide file tree
Showing 115 changed files with 531 additions and 20 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/go-git/go-git/v5 v5.6.1
github.com/gofrs/flock v0.8.1
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/hashicorp/go-version v1.6.0
github.com/moby/term v0.0.0-20221205130635-1aeaba878587
github.com/onsi/ginkgo/v2 v2.9.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down
21 changes: 20 additions & 1 deletion pkg/api/kpm_run.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"fmt"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -66,6 +67,24 @@ func RunPkgInPath(opts *opt.CompileOptions) (string, error) {
return compileResult.GetRawYamlResult(), nil
}

// CompileWithOpts will compile the kcl program without kcl package.
func CompileWithOpts(opts *opt.CompileOptions) (*kcl.KCLResultList, error) {
if len(opts.Entries()) > 0 {
for _, entry := range opts.Entries() {
if filepath.IsAbs(entry) {
opts.Merge(kcl.WithKFilenames(entry))
} else {
opts.Merge(kcl.WithKFilenames(filepath.Join(opts.PkgPath(), entry)))
}
}
} else {
// no entry
opts.Merge(kcl.WithKFilenames(opts.PkgPath()))
}
opts.Merge(kcl.WithWorkDir(opts.PkgPath()))
return kcl.RunWithOpts(*opts.Option)
}

// absTarPath checks whether path 'tarPath' exists and whether path 'tarPath' ends with '.tar'
// And after checking, absTarPath return the abs path for 'tarPath'.
func absTarPath(tarPath string) (string, error) {
Expand Down Expand Up @@ -110,7 +129,7 @@ func RunPkgWithOpt(opts *opt.CompileOptions) (*kcl.KCLResultList, error) {

kclPkg, err := pkg.LoadKclPkg(pkgPath)
if err != nil {
return nil, errors.FailedToLoadPackage
return nil, fmt.Errorf("kpm: failed to load package, please check the package path '%s' is valid", pkgPath)
}

kclPkg.SetVendorMode(opts.IsVendor())
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/kpm_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/stretchr/testify/assert"
"kcl-lang.io/kcl-go/pkg/kcl"
"kcl-lang.io/kpm/pkg/errors"
"kcl-lang.io/kpm/pkg/opt"
"kcl-lang.io/kpm/pkg/utils"
)
Expand Down Expand Up @@ -84,10 +83,11 @@ func TestRunPkgInPathInvalidPath(t *testing.T) {
func TestRunPkgInPathInvalidPkg(t *testing.T) {
pkgPath := getTestDir("test_run_pkg_in_path")
opts := opt.DefaultCompileOptions()
opts.SetPkgPath(pkgPath)
opts.Merge(kcl.WithKFilenames(filepath.Join(pkgPath, "invalid_pkg", "not_exist.k")))
result, err := RunPkgInPath(opts)
assert.NotEqual(t, err, nil)
assert.Equal(t, err, errors.FailedToLoadPackage)
assert.Equal(t, err.Error(), fmt.Sprintf("kpm: failed to load package, please check the package path '%s' is valid", pkgPath))
assert.Equal(t, result, "")
}

Expand Down
41 changes: 28 additions & 13 deletions pkg/cmd/cmd_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"github.com/urfave/cli/v2"
"kcl-lang.io/kcl-go/pkg/kcl"
"kcl-lang.io/kpm/pkg/api"
"kcl-lang.io/kpm/pkg/errors"
"kcl-lang.io/kpm/pkg/opt"
"kcl-lang.io/kpm/pkg/runner"
)

// NewRunCmd new a Command for `kpm run`.
Expand Down Expand Up @@ -79,29 +79,44 @@ func NewRunCmd() *cli.Command {

func KpmRun(c *cli.Context) error {
kclOpts := CompileOptionFromCli(c)
pkgWillBeCompiled := c.Args().First()
runEntry, errEvent := runner.FindRunEntryFrom(c.Args().Slice())
if errEvent != nil {
return errEvent
}

// 'kpm run' compile the current package undor '$pwd'.
if len(pkgWillBeCompiled) == 0 {
compileResult, err := api.RunPkg(kclOpts)
if runEntry.IsEmpty() {
compileResult, err := api.RunCurrentPkg(kclOpts)
if err != nil {
return err
}
fmt.Println(compileResult)
fmt.Println(compileResult.GetRawYamlResult())
} else {
// 'kpm run <package source>' compile the kcl package from the <package source>.
kclOpts.SetPkgPath(pkgWillBeCompiled)
compileResult, err := api.RunPkgInPath(kclOpts)
if err == errors.FailedToLoadPackage {
compileResult, err = api.RunTar(pkgWillBeCompiled, kclOpts)
if err == errors.InvalidKclPacakgeTar {
compileResult, err = api.RunOci(pkgWillBeCompiled, c.String(FLAG_TAG), kclOpts)
var compileResult *kcl.KCLResultList
var err error
// 'kpm run' compile the package from the local file system.
if runEntry.IsLocalFile() {
kclOpts.SetPkgPath(runEntry.PackageSource())
kclOpts.ExtendEntries(runEntry.EntryFiles())
if runEntry.IsFakePackage() {
// If there is only kcl file without kcl package,
compileResult, err = api.CompileWithOpts(kclOpts)
} else {
// Else compile the kcl pacakge.
compileResult, err = api.RunPkgWithOpt(kclOpts)
}
} else if runEntry.IsTar() {
// 'kpm run' compile the package from the kcl pakcage tar.
compileResult, err = api.RunTarPkg(runEntry.PackageSource(), kclOpts)
} else {
// 'kpm run' compile the package from the OCI reference or url.
compileResult, err = api.RunOciPkg(runEntry.PackageSource(), c.String(FLAG_TAG), kclOpts)
}

if err != nil {
return err
}
fmt.Println(compileResult)
fmt.Println(compileResult.GetRawYamlResult())
}
return nil
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package constants

const (
KFilePathSuffix = ".k"
TarPathSuffix = ".tar"
OciScheme = "oci"
FileEntry = "file"
UrlEntry = "url"
RefEntry = "ref"
TarEntry = "tar"
KCL_MOD = "kcl.mod"
)
1 change: 1 addition & 0 deletions pkg/reporter/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const (
AddItselfAsDep
PkgTagExists
DependencyNotFound
KclModNotFound
CompileFailed
)

Expand Down
199 changes: 199 additions & 0 deletions pkg/runner/entry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package runner

import (
"fmt"
"os"
"path/filepath"
"sort"

"github.com/golang-collections/collections/set"
"kcl-lang.io/kpm/pkg/constants"
"kcl-lang.io/kpm/pkg/errors"
"kcl-lang.io/kpm/pkg/reporter"
"kcl-lang.io/kpm/pkg/utils"
)

// Entry is the entry of 'kpm run'.
type Entry struct {
// The package source of the entry, filepath, tar path, url or ref.
packageSource string
// The start files for one compilation.
entryFiles []string
// The kind of the entry, file, tar, url or ref.
kind string
// If the entry is a fake package,
// which means the entry only contains kcl files without kcl.mod.
isFakePackage bool
}

// SetKind will set the kind of the entry.
func (e *Entry) SetKind(kind string) {
e.kind = kind
}

// Kind will return the kind of the entry.
func (e *Entry) Kind() string {
return e.kind
}

// IsLocalFile will return true if the entry is a local file.
func (e *Entry) IsLocalFile() bool {
return e.kind == constants.FileEntry
}

// IsUrl will return true if the entry is a url.
func (e *Entry) IsUrl() bool {
return e.kind == constants.UrlEntry
}

// IsRef will return true if the entry is a ref.
func (e *Entry) IsRef() bool {
return e.kind == constants.RefEntry
}

// IsTar will return true if the entry is a tar.
func (e *Entry) IsTar() bool {
return e.kind == constants.TarEntry
}

// IsEmpty will return true if the entry is empty.
func (e *Entry) IsEmpty() bool {
return len(e.packageSource) == 0
}

// PackageSource will return the package source of the entry.
func (e *Entry) PackageSource() string {
return e.packageSource
}

// EntryFiles will return the entry files of the entry.
func (e *Entry) EntryFiles() []string {
return e.entryFiles
}

// SetPackageSource will set the package source of the entry.
func (e *Entry) SetPackageSource(packageSource string) {
e.packageSource = packageSource
}

// SetIsFakePackage will set the 'isFakePackage' flag.
func (e *Entry) SetIsFakePackage(isFakePackage bool) {
e.isFakePackage = isFakePackage
}

// IsFakePackage will return the 'isFakePackage' flag.
func (e *Entry) IsFakePackage() bool {
return e.isFakePackage
}

// AddEntryFile will add a entry file to the entry.
func (e *Entry) AddEntryFile(entrySource string) {
e.entryFiles = append(e.entryFiles, entrySource)
}

// FindRunEntryFrom will find the entry of the compilation from the entry sources.
func FindRunEntryFrom(sources []string) (*Entry, *reporter.KpmEvent) {
entry := Entry{}
// modPathSet is used to check if there are multiple packages to be compiled at the same time.
// It is a set of the package source so that the same package source will only be added once.
var modPathSet = set.New()
for _, source := range sources {
// If the entry is a local file but not a tar file,
if utils.DirExists(source) && !utils.IsTar(source) {
// Find the 'kcl.mod'
modPath, err := FindModRootFrom(source)
if err != (*reporter.KpmEvent)(nil) {
// If the 'kcl.mod' is not found,
if err.Type() == reporter.KclModNotFound {
if utils.IsKfile(source) {
// If the entry is a kcl file, the parent dir of the kcl file will be package path.
modPath = filepath.Dir(source)
} else {
// If the entry is a dir, the dir will be package path.
modPath = source
}
} else {
return nil, err
}
}
entry.SetPackageSource(modPath)
entry.AddEntryFile(source)
entry.SetKind(constants.FileEntry)
entry.SetIsFakePackage(!utils.DirExists(filepath.Join(modPath, constants.KCL_MOD)))
absModPath, bugerr := filepath.Abs(modPath)
if bugerr != nil {
return nil, reporter.NewErrorEvent(reporter.Bug, bugerr, errors.InternalBug.Error())
}
modPathSet.Insert(absModPath)
} else if utils.IsURL(source) || utils.IsRef(source) || utils.IsTar(source) {
modPathSet.Insert(source)
entry.SetPackageSource(source)
entry.SetKind(GetSourceKindFrom(source))
}
}

// kpm only allows one package to be compiled at a time.
if modPathSet.Len() > 1 {
// sort the mod paths to make the error message more readable.
var modPaths []string
setModPathsMethod := func(modpath interface{}) {
p, ok := modpath.(string)
if !ok {
modPaths = append(modPaths, "")
} else {
modPaths = append(modPaths, p)
}
}
modPathSet.Do(setModPathsMethod)
sort.Strings(modPaths)
return nil, reporter.NewErrorEvent(
reporter.CompileFailed,
fmt.Errorf("cannot compile multiple packages %s at the same time", modPaths),
"only allows one package to be compiled at a time",
)
}

return &entry, nil
}

// GetSourceKindFrom will return the kind of the source.
func GetSourceKindFrom(source string) string {
if utils.DirExists(source) && !utils.IsTar(source) {
return constants.FileEntry
} else if utils.IsTar(source) {
return constants.TarEntry
} else if utils.IsURL(source) {
return constants.UrlEntry
} else if utils.IsRef(source) {
return constants.RefEntry
}
return ""
}

// FindModRootFrom will find the kcl.mod path from the start path.
func FindModRootFrom(startPath string) (string, *reporter.KpmEvent) {
info, err := os.Stat(startPath)
if err != nil {
return "", reporter.NewErrorEvent(reporter.CompileFailed, fmt.Errorf("failed to access path '%s'", startPath))
}
var start string
// If the start path is a kcl file, find from the parent dir of the kcl file.
if utils.IsKfile(startPath) {
start = filepath.Dir(startPath)
} else if info.IsDir() {
// If the start path is a dir, find from the start path.
start = startPath
} else {
return "", reporter.NewErrorEvent(reporter.CompileFailed, fmt.Errorf("invalid file path '%s'", startPath))
}

if _, err := os.Stat(filepath.Join(start, constants.KCL_MOD)); err == nil {
return start, nil
} else {
parent := filepath.Dir(startPath)
if parent == startPath {
return "", reporter.NewErrorEvent(reporter.KclModNotFound, fmt.Errorf("cannot find kcl.mod in '%s'", startPath))
}
return FindModRootFrom(filepath.Dir(startPath))
}
}
Loading

0 comments on commit 1d7a64d

Please sign in to comment.