kcl-lang.io/kpm@v0.8.7-0.20240520061008-9fc4c5efc8c7/pkg/runner/entry.go (about)

     1  package runner
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"sort"
     8  
     9  	"github.com/golang-collections/collections/set"
    10  	"kcl-lang.io/kpm/pkg/constants"
    11  	"kcl-lang.io/kpm/pkg/errors"
    12  	"kcl-lang.io/kpm/pkg/reporter"
    13  	"kcl-lang.io/kpm/pkg/utils"
    14  )
    15  
    16  // EntryKind is the kind of the entry.
    17  // Including:
    18  // 1. WithKclMod: local file which can find 'kcl.mod' in the parent dir of the file.
    19  // 2. WithoutKclMod: local file which can find 'kcl.mod' in the parent dir of the file.
    20  // 3. TarEntry: kcl package tar file.
    21  // 4. UrlEntry: kcl package url.
    22  // 5. RefEntry: kcl package ref.
    23  type EntryKind string
    24  
    25  // Entry is the entry of 'kpm run'.
    26  type Entry struct {
    27  	// The package source of the entry, filepath, tar path, url or ref.
    28  	packageSource string
    29  	// The start files for one compilation.
    30  	entryFiles []string
    31  	// The kind of the entry, file, tar, url or ref.
    32  	kind EntryKind
    33  }
    34  
    35  // SetKind will set the kind of the entry.
    36  func (e *Entry) SetKind(kind EntryKind) {
    37  	e.kind = kind
    38  }
    39  
    40  // Kind will return the kind of the entry.
    41  func (e *Entry) Kind() EntryKind {
    42  	return e.kind
    43  }
    44  
    45  // IsLocalFileWithKclMod will return true if the entry is a local file with 'kcl.mod'.
    46  func (e *Entry) IsLocalFileWithKclMod() bool {
    47  	return e.kind == constants.FileWithKclModEntry
    48  }
    49  
    50  // IsLocalFile will return true if the entry is a local file.
    51  func (e *Entry) IsLocalFile() bool {
    52  	return e.kind == constants.FileEntry
    53  }
    54  
    55  // IsUrl will return true if the entry is a url.
    56  func (e *Entry) IsUrl() bool {
    57  	return e.kind == constants.UrlEntry
    58  }
    59  
    60  // IsRef will return true if the entry is a ref.
    61  func (e *Entry) IsRef() bool {
    62  	return e.kind == constants.RefEntry
    63  }
    64  
    65  // IsTar will return true if the entry is a tar.
    66  func (e *Entry) IsTar() bool {
    67  	return e.kind == constants.TarEntry
    68  }
    69  
    70  func (e *Entry) IsGit() bool {
    71  	return e.kind == constants.GitEntry
    72  }
    73  
    74  // IsEmpty will return true if the entry is empty.
    75  func (e *Entry) IsEmpty() bool {
    76  	return len(e.packageSource) == 0
    77  }
    78  
    79  // PackageSource will return the package source of the entry.
    80  func (e *Entry) PackageSource() string {
    81  	return e.packageSource
    82  }
    83  
    84  // EntryFiles will return the entry files of the entry.
    85  func (e *Entry) EntryFiles() []string {
    86  	return e.entryFiles
    87  }
    88  
    89  // SetPackageSource will set the package source of the entry.
    90  func (e *Entry) SetPackageSource(packageSource string) {
    91  	e.packageSource = packageSource
    92  }
    93  
    94  // AddEntryFile will add a entry file to the entry.
    95  func (e *Entry) AddEntryFile(entrySource string) {
    96  	e.entryFiles = append(e.entryFiles, entrySource)
    97  }
    98  
    99  // FindRunEntryFrom will find the entry of the compilation from the entry sources.
   100  func FindRunEntryFrom(sources []string) (*Entry, *reporter.KpmEvent) {
   101  	entry := Entry{}
   102  	// modPathSet is used to check if there are multiple packages to be compiled at the same time.
   103  	// It is a set of the package source so that the same package source will only be added once.
   104  	var modPathSet = set.New()
   105  	for _, source := range sources {
   106  		// If the entry is a local file but not a tar file,
   107  		if utils.DirExists(source) && !utils.IsTar(source) {
   108  			// Find the 'kcl.mod'
   109  			modPath, err := FindModRootFrom(source)
   110  			if err != (*reporter.KpmEvent)(nil) {
   111  				// If the 'kcl.mod' is not found,
   112  				if err.Type() == reporter.KclModNotFound {
   113  					if utils.IsKfile(source) {
   114  						// If the entry is a kcl file, the parent dir of the kcl file will be package path.
   115  						modPath = filepath.Dir(source)
   116  					} else {
   117  						// If the entry is a dir, the dir will be package path.
   118  						modPath = source
   119  					}
   120  				} else {
   121  					return nil, err
   122  				}
   123  			}
   124  			entry.SetPackageSource(modPath)
   125  			entry.AddEntryFile(source)
   126  			if !utils.DirExists(filepath.Join(modPath, constants.KCL_MOD)) {
   127  				entry.SetKind(constants.FileEntry)
   128  			} else {
   129  				entry.SetKind(constants.FileWithKclModEntry)
   130  			}
   131  			absModPath, bugerr := filepath.Abs(modPath)
   132  			if bugerr != nil {
   133  				return nil, reporter.NewErrorEvent(reporter.Bug, bugerr, errors.InternalBug.Error())
   134  			}
   135  			modPathSet.Insert(absModPath)
   136  		} else if utils.IsURL(source) || utils.IsRef(source) || utils.IsTar(source) {
   137  			modPathSet.Insert(source)
   138  			entry.SetPackageSource(source)
   139  			entry.SetKind(GetSourceKindFrom(source))
   140  		}
   141  	}
   142  
   143  	// kpm only allows one package to be compiled at a time.
   144  	if modPathSet.Len() > 1 {
   145  		// sort the mod paths to make the error message more readable.
   146  		var modPaths []string
   147  		setModPathsMethod := func(modpath interface{}) {
   148  			p, ok := modpath.(string)
   149  			if !ok {
   150  				modPaths = append(modPaths, "")
   151  			} else {
   152  				modPaths = append(modPaths, p)
   153  			}
   154  		}
   155  		modPathSet.Do(setModPathsMethod)
   156  		sort.Strings(modPaths)
   157  		return nil, reporter.NewErrorEvent(
   158  			reporter.CompileFailed,
   159  			fmt.Errorf("cannot compile multiple packages %s at the same time", modPaths),
   160  			"only allows one package to be compiled at a time",
   161  		)
   162  	}
   163  
   164  	return &entry, nil
   165  }
   166  
   167  // GetSourceKindFrom will return the kind of the source.
   168  func GetSourceKindFrom(source string) EntryKind {
   169  	if utils.DirExists(source) && !utils.IsTar(source) {
   170  		return constants.FileEntry
   171  	} else if utils.IsTar(source) {
   172  		return constants.TarEntry
   173  	} else if utils.IsGitRepoUrl(source) {
   174  		return constants.GitEntry
   175  	} else if utils.IsURL(source) {
   176  		return constants.UrlEntry
   177  	} else if utils.IsRef(source) {
   178  		return constants.RefEntry
   179  	}
   180  	return ""
   181  }
   182  
   183  // FindModRootFrom will find the kcl.mod path from the start path.
   184  func FindModRootFrom(startPath string) (string, *reporter.KpmEvent) {
   185  	info, err := os.Stat(startPath)
   186  	if err != nil {
   187  		return "", reporter.NewErrorEvent(reporter.CompileFailed, err, fmt.Sprintf("failed to access path '%s'", startPath))
   188  	}
   189  	var start string
   190  	// If the start path is a kcl file, find from the parent dir of the kcl file.
   191  	if utils.IsKfile(startPath) {
   192  		start = filepath.Dir(startPath)
   193  	} else if info.IsDir() {
   194  		// If the start path is a dir, find from the start path.
   195  		start = startPath
   196  	} else {
   197  		return "", reporter.NewErrorEvent(reporter.CompileFailed, err, fmt.Sprintf("invalid file path '%s'", startPath))
   198  	}
   199  
   200  	if _, err := os.Stat(filepath.Join(start, constants.KCL_MOD)); err == nil {
   201  		return start, nil
   202  	} else {
   203  		parent := filepath.Dir(startPath)
   204  		if parent == startPath {
   205  			return "", reporter.NewErrorEvent(reporter.KclModNotFound, fmt.Errorf("cannot find kcl.mod in '%s'", startPath))
   206  		}
   207  		return FindModRootFrom(filepath.Dir(startPath))
   208  	}
   209  }