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 }