github.com/KusionStack/kpm@v0.8.4-0.20240326033734-dc72298a30e5/pkg/client/client.go (about) 1 package client 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "reflect" 10 "runtime" 11 "strings" 12 13 "github.com/BurntSushi/toml" 14 "github.com/dominikbraun/graph" 15 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 16 "github.com/otiai10/copy" 17 "kcl-lang.io/kcl-go/pkg/kcl" 18 "oras.land/oras-go/v2" 19 20 "kcl-lang.io/kpm/pkg/constants" 21 "kcl-lang.io/kpm/pkg/env" 22 "kcl-lang.io/kpm/pkg/errors" 23 "kcl-lang.io/kpm/pkg/git" 24 "kcl-lang.io/kpm/pkg/oci" 25 "kcl-lang.io/kpm/pkg/opt" 26 pkg "kcl-lang.io/kpm/pkg/package" 27 "kcl-lang.io/kpm/pkg/reporter" 28 "kcl-lang.io/kpm/pkg/runner" 29 "kcl-lang.io/kpm/pkg/settings" 30 "kcl-lang.io/kpm/pkg/utils" 31 ) 32 33 // KpmClient is the client of kpm. 34 type KpmClient struct { 35 // The writer of the log. 36 logWriter io.Writer 37 // The home path of kpm for global configuration file and kcl package storage path. 38 homePath string 39 // The settings of kpm loaded from the global configuration file. 40 settings settings.Settings 41 // The flag of whether to check the checksum of the package and update kcl.mod.lock. 42 noSumCheck bool 43 } 44 45 // NewKpmClient will create a new kpm client with default settings. 46 func NewKpmClient() (*KpmClient, error) { 47 settings := settings.GetSettings() 48 49 if settings.ErrorEvent != (*reporter.KpmEvent)(nil) { 50 return nil, settings.ErrorEvent 51 } 52 53 homePath, err := env.GetAbsPkgPath() 54 if err != nil { 55 return nil, err 56 } 57 58 return &KpmClient{ 59 logWriter: os.Stdout, 60 settings: *settings, 61 homePath: homePath, 62 }, nil 63 } 64 65 // SetNoSumCheck will set the 'noSumCheck' flag. 66 func (c *KpmClient) SetNoSumCheck(noSumCheck bool) { 67 c.noSumCheck = noSumCheck 68 } 69 70 // GetNoSumCheck will return the 'noSumCheck' flag. 71 func (c *KpmClient) GetNoSumCheck() bool { 72 return c.noSumCheck 73 } 74 75 func (c *KpmClient) SetLogWriter(writer io.Writer) { 76 c.logWriter = writer 77 } 78 79 func (c *KpmClient) GetLogWriter() io.Writer { 80 return c.logWriter 81 } 82 83 // SetHomePath will set the home path of kpm. 84 func (c *KpmClient) SetHomePath(homePath string) { 85 c.homePath = homePath 86 } 87 88 // AcquirePackageCacheLock will acquire the lock of the package cache. 89 func (c *KpmClient) AcquirePackageCacheLock() error { 90 return c.settings.AcquirePackageCacheLock(c.logWriter) 91 } 92 93 // ReleasePackageCacheLock will release the lock of the package cache. 94 func (c *KpmClient) ReleasePackageCacheLock() error { 95 return c.settings.ReleasePackageCacheLock() 96 } 97 98 // GetSettings will return the settings of kpm client. 99 func (c *KpmClient) GetSettings() *settings.Settings { 100 return &c.settings 101 } 102 103 func (c *KpmClient) LoadPkgFromPath(pkgPath string) (*pkg.KclPkg, error) { 104 modFile, err := c.LoadModFile(pkgPath) 105 if err != nil { 106 return nil, reporter.NewErrorEvent(reporter.FailedLoadKclMod, err, fmt.Sprintf("could not load 'kcl.mod' in '%s'", pkgPath)) 107 } 108 109 // Get dependencies from kcl.mod.lock. 110 deps, err := c.LoadLockDeps(pkgPath) 111 112 if err != nil { 113 return nil, reporter.NewErrorEvent(reporter.FailedLoadKclMod, err, fmt.Sprintf("could not load 'kcl.mod.lock' in '%s'", pkgPath)) 114 } 115 116 return &pkg.KclPkg{ 117 ModFile: *modFile, 118 HomePath: pkgPath, 119 Dependencies: *deps, 120 }, nil 121 } 122 123 func (c *KpmClient) LoadModFile(pkgPath string) (*pkg.ModFile, error) { 124 modFile := new(pkg.ModFile) 125 err := modFile.LoadModFile(filepath.Join(pkgPath, pkg.MOD_FILE)) 126 if err != nil { 127 return nil, err 128 } 129 130 modFile.HomePath = pkgPath 131 132 if modFile.Dependencies.Deps == nil { 133 modFile.Dependencies.Deps = make(map[string]pkg.Dependency) 134 } 135 err = c.FillDependenciesInfo(modFile) 136 if err != nil { 137 return nil, err 138 } 139 140 return modFile, nil 141 } 142 143 func (c *KpmClient) LoadLockDeps(pkgPath string) (*pkg.Dependencies, error) { 144 return pkg.LoadLockDeps(pkgPath) 145 } 146 147 // ResolveDepsIntoMap will calculate the map of kcl package name and local storage path of the external packages. 148 func (c *KpmClient) ResolveDepsIntoMap(kclPkg *pkg.KclPkg) (map[string]string, error) { 149 err := c.ResolvePkgDepsMetadata(kclPkg, true) 150 if err != nil { 151 return nil, err 152 } 153 154 depMetadatas, err := kclPkg.GetDepsMetadata() 155 if err != nil { 156 return nil, err 157 } 158 var pkgMap map[string]string = make(map[string]string) 159 for _, d := range depMetadatas.Deps { 160 pkgMap[d.GetAliasName()] = d.GetLocalFullPath(kclPkg.HomePath) 161 } 162 163 return pkgMap, nil 164 } 165 166 // ResolveDepsMetadata will calculate the local storage path of the external package, 167 // and check whether the package exists locally. 168 // If the package does not exist, it will re-download to the local. 169 func (c *KpmClient) ResolvePkgDepsMetadata(kclPkg *pkg.KclPkg, update bool) error { 170 var searchPath string 171 kclPkg.NoSumCheck = c.noSumCheck 172 173 if kclPkg.IsVendorMode() { 174 // In the vendor mode, the search path is the vendor subdirectory of the current package. 175 err := c.VendorDeps(kclPkg) 176 if err != nil { 177 return err 178 } 179 searchPath = kclPkg.LocalVendorPath() 180 } else { 181 // Otherwise, the search path is the $KCL_PKG_PATH. 182 searchPath = c.homePath 183 } 184 185 // If under the mode of '--no_sum_check', the checksum of the package will not be checked. 186 // There is no kcl.mod.lock, and the dependencies in kcl.mod and kcl.mod.lock do not need to be aligned. 187 if !c.noSumCheck { 188 // If not under the mode of '--no_sum_check', 189 // all the dependencies in kcl.mod.lock are the dependencies of the current package. 190 // 191 // alian the dependencies between kcl.mod and kcl.mod.lock 192 // clean the dependencies in kcl.mod.lock which not in kcl.mod 193 // clean the dependencies in kcl.mod.lock and kcl.mod which have different version 194 for name, dep := range kclPkg.Dependencies.Deps { 195 modDep, ok := kclPkg.ModFile.Dependencies.Deps[name] 196 if !ok || !dep.WithTheSameVersion(modDep) { 197 reporter.ReportMsgTo( 198 fmt.Sprintf("removing '%s' with version '%s'", name, dep.Version), 199 c.logWriter, 200 ) 201 delete(kclPkg.Dependencies.Deps, name) 202 } 203 } 204 // add the dependencies in kcl.mod which not in kcl.mod.lock 205 for name, d := range kclPkg.ModFile.Dependencies.Deps { 206 if _, ok := kclPkg.Dependencies.Deps[name]; !ok { 207 reporter.ReportMsgTo( 208 fmt.Sprintf("adding '%s' with version '%s'", name, d.Version), 209 c.logWriter, 210 ) 211 kclPkg.Dependencies.Deps[name] = d 212 } 213 } 214 } else { 215 // If under the mode of '--no_sum_check', the checksum of the package will not be checked. 216 // All the dependencies in kcl.mod are the dependencies of the current package. 217 kclPkg.Dependencies.Deps = kclPkg.ModFile.Dependencies.Deps 218 } 219 220 for name, d := range kclPkg.Dependencies.Deps { 221 searchFullPath := filepath.Join(searchPath, d.FullName) 222 if !update { 223 if d.IsFromLocal() { 224 searchFullPath = d.GetLocalFullPath(kclPkg.HomePath) 225 } 226 227 // Find it and update the local path of the dependency. 228 d.LocalFullPath = searchFullPath 229 kclPkg.Dependencies.Deps[name] = d 230 231 } else { 232 if utils.DirExists(searchFullPath) && (c.GetNoSumCheck() || utils.CheckPackageSum(d.Sum, searchFullPath)) { 233 // Find it and update the local path of the dependency. 234 d.LocalFullPath = searchFullPath 235 kclPkg.Dependencies.Deps[name] = d 236 } else if d.IsFromLocal() && !utils.DirExists(d.GetLocalFullPath(kclPkg.HomePath)) { 237 return reporter.NewErrorEvent(reporter.DependencyNotFound, fmt.Errorf("dependency '%s' not found in '%s'", d.Name, searchFullPath)) 238 } else if d.IsFromLocal() && utils.DirExists(d.GetLocalFullPath(kclPkg.HomePath)) { 239 sum, err := utils.HashDir(d.GetLocalFullPath(kclPkg.HomePath)) 240 if err != nil { 241 return reporter.NewErrorEvent(reporter.CalSumFailed, err, fmt.Sprintf("failed to calculate checksum for '%s' in '%s'", d.Name, searchFullPath)) 242 } 243 d.Sum = sum 244 kclPkg.Dependencies.Deps[name] = d 245 } else { 246 // Otherwise, re-vendor it. 247 if kclPkg.IsVendorMode() { 248 err := c.VendorDeps(kclPkg) 249 if err != nil { 250 return err 251 } 252 } else { 253 // Or, re-download it. 254 err := c.AddDepToPkg(kclPkg, &d) 255 if err != nil { 256 return err 257 } 258 } 259 // After re-downloading or re-vendoring, 260 // re-resolving is required to update the dependent paths. 261 err := c.ResolvePkgDepsMetadata(kclPkg, update) 262 if err != nil { 263 return err 264 } 265 return nil 266 } 267 } 268 } 269 if update { 270 // update the kcl.mod and kcl.mod.lock. 271 err := kclPkg.UpdateModAndLockFile() 272 if err != nil { 273 return err 274 } 275 } 276 return nil 277 } 278 279 // UpdateDeps will update the dependencies. 280 func (c *KpmClient) UpdateDeps(kclPkg *pkg.KclPkg) error { 281 _, err := c.ResolveDepsMetadataInJsonStr(kclPkg, true) 282 if err != nil { 283 return err 284 } 285 286 err = kclPkg.UpdateModAndLockFile() 287 if err != nil { 288 return err 289 } 290 return nil 291 } 292 293 // ResolveDepsMetadataInJsonStr will calculate the local storage path of the external package, 294 // and check whether the package exists locally. If the package does not exist, it will re-download to the local. 295 // Finally, the calculated metadata of the dependent packages is serialized into a json string and returned. 296 func (c *KpmClient) ResolveDepsMetadataInJsonStr(kclPkg *pkg.KclPkg, update bool) (string, error) { 297 // 1. Calculate the dependency path, check whether the dependency exists 298 // and re-download the dependency that does not exist. 299 err := c.ResolvePkgDepsMetadata(kclPkg, update) 300 if err != nil { 301 return "", err 302 } 303 304 // 2. Serialize to JSON 305 depMetadatas, err := kclPkg.GetDepsMetadata() 306 if err != nil { 307 return "", err 308 } 309 jsonData, err := json.Marshal(&depMetadatas) 310 if err != nil { 311 return "", reporter.NewErrorEvent(reporter.Bug, err, "internal bug: failed to marshal the dependencies into json") 312 } 313 314 return string(jsonData), nil 315 } 316 317 // Compile will call kcl compiler to compile the current kcl package and its dependent packages. 318 func (c *KpmClient) Compile(kclPkg *pkg.KclPkg, kclvmCompiler *runner.Compiler) (*kcl.KCLResultList, error) { 319 pkgMap, err := c.ResolveDepsIntoMap(kclPkg) 320 if err != nil { 321 return nil, err 322 } 323 324 // Fill the dependency path. 325 for dName, dPath := range pkgMap { 326 if !filepath.IsAbs(dPath) { 327 dPath = filepath.Join(c.homePath, dPath) 328 } 329 kclvmCompiler.AddDepPath(dName, dPath) 330 } 331 332 return kclvmCompiler.Run() 333 } 334 335 // CompileWithOpts will compile the kcl program with the compile options. 336 func (c *KpmClient) CompileWithOpts(opts *opt.CompileOptions) (*kcl.KCLResultList, error) { 337 pkgPath, err := filepath.Abs(opts.PkgPath()) 338 if err != nil { 339 return nil, reporter.NewErrorEvent(reporter.Bug, err, "internal bugs, please contact us to fix it.") 340 } 341 342 c.noSumCheck = opts.NoSumCheck() 343 344 kclPkg, err := pkg.LoadKclPkg(pkgPath) 345 if err != nil { 346 return nil, err 347 } 348 349 kclPkg.SetVendorMode(opts.IsVendor()) 350 351 globalPkgPath, err := env.GetAbsPkgPath() 352 if err != nil { 353 return nil, err 354 } 355 356 err = kclPkg.ValidateKpmHome(globalPkgPath) 357 if err != (*reporter.KpmEvent)(nil) { 358 return nil, err 359 } 360 // add all the options from 'kcl.mod' 361 opts.Merge(*kclPkg.GetKclOpts()) 362 if len(opts.Entries()) > 0 { 363 // add entry from '--input' 364 for _, entry := range opts.Entries() { 365 if filepath.IsAbs(entry) { 366 opts.Merge(kcl.WithKFilenames(entry)) 367 } else { 368 opts.Merge(kcl.WithKFilenames(filepath.Join(opts.PkgPath(), entry))) 369 } 370 } 371 } else if len(kclPkg.GetEntryKclFilesFromModFile()) == 0 && !opts.HasSettingsYaml() { 372 // no entry 373 opts.Merge(kcl.WithKFilenames(opts.PkgPath())) 374 } 375 opts.Merge(kcl.WithWorkDir(opts.PkgPath())) 376 377 // Calculate the absolute path of entry file described by '--input'. 378 compiler := runner.NewCompilerWithOpts(opts) 379 380 // Call the kcl compiler. 381 compileResult, err := c.Compile(kclPkg, compiler) 382 383 if err != nil { 384 return nil, reporter.NewErrorEvent(reporter.CompileFailed, err, "failed to compile the kcl package") 385 } 386 387 return compileResult, nil 388 } 389 390 // CompilePkgWithOpts will compile the kcl package with the compile options. 391 func (c *KpmClient) CompilePkgWithOpts(kclPkg *pkg.KclPkg, opts *opt.CompileOptions) (*kcl.KCLResultList, error) { 392 opts.SetPkgPath(kclPkg.HomePath) 393 if len(opts.Entries()) > 0 { 394 // add entry from '--input' 395 for _, entry := range opts.Entries() { 396 if filepath.IsAbs(entry) { 397 opts.Merge(kcl.WithKFilenames(entry)) 398 } else { 399 opts.Merge(kcl.WithKFilenames(filepath.Join(opts.PkgPath(), entry))) 400 } 401 } 402 // add entry from 'kcl.mod' 403 } else if len(kclPkg.GetEntryKclFilesFromModFile()) > 0 { 404 opts.Merge(*kclPkg.GetKclOpts()) 405 } else if !opts.HasSettingsYaml() { 406 // no entry 407 opts.Merge(kcl.WithKFilenames(opts.PkgPath())) 408 } 409 opts.Merge(kcl.WithWorkDir(opts.PkgPath())) 410 // Calculate the absolute path of entry file described by '--input'. 411 compiler := runner.NewCompilerWithOpts(opts) 412 // Call the kcl compiler. 413 compileResult, err := c.Compile(kclPkg, compiler) 414 415 if err != nil { 416 return nil, reporter.NewErrorEvent(reporter.CompileFailed, err, "failed to compile the kcl package") 417 } 418 419 return compileResult, nil 420 } 421 422 // CompileTarPkg will compile the kcl package from the tar package. 423 func (c *KpmClient) CompileTarPkg(tarPath string, opts *opt.CompileOptions) (*kcl.KCLResultList, error) { 424 absTarPath, err := utils.AbsTarPath(tarPath) 425 if err != nil { 426 return nil, err 427 } 428 // Extract the tar package to a directory with the same name. 429 // e.g. 430 // 'xxx/xxx/xxx/test.tar' will be extracted to the directory 'xxx/xxx/xxx/test'. 431 destDir := strings.TrimSuffix(absTarPath, filepath.Ext(absTarPath)) 432 err = utils.UnTarDir(absTarPath, destDir) 433 if err != nil { 434 return nil, err 435 } 436 437 opts.SetPkgPath(destDir) 438 // The directory after extracting the tar package is taken as the root directory of the package, 439 // and kclvm is called to compile the kcl program under the 'destDir'. 440 // e.g. 441 // if the tar path is 'xxx/xxx/xxx/test.tar', 442 // the 'xxx/xxx/xxx/test' will be taken as the root path of the kcl package to compile. 443 return c.CompileWithOpts(opts) 444 } 445 446 // CompileGitPkg will compile the kcl package from the git url. 447 func (c *KpmClient) CompileGitPkg(gitOpts *git.CloneOptions, compileOpts *opt.CompileOptions) (*kcl.KCLResultList, error) { 448 // 1. Create the temporary directory to pull the tar. 449 tmpDir, err := os.MkdirTemp("", "") 450 if err != nil { 451 return nil, reporter.NewErrorEvent(reporter.Bug, err, "internal bugs, please contact us to fix it.") 452 } 453 tmpDir = filepath.Join(tmpDir, constants.GitEntry) 454 455 // clean the temp dir. 456 defer os.RemoveAll(tmpDir) 457 458 // 2. clone the git repo 459 _, err = git.CloneWithOpts( 460 git.WithCommit(gitOpts.Commit), 461 git.WithBranch(gitOpts.Branch), 462 git.WithTag(gitOpts.Tag), 463 git.WithRepoURL(gitOpts.RepoURL), 464 git.WithLocalPath(tmpDir), 465 ) 466 if err != nil { 467 return nil, reporter.NewErrorEvent(reporter.FailedGetPkg, err, "failed to get the git repository") 468 } 469 470 compileOpts.SetPkgPath(tmpDir) 471 472 return c.CompileWithOpts(compileOpts) 473 } 474 475 // CompileOciPkg will compile the kcl package from the OCI reference or url. 476 func (c *KpmClient) CompileOciPkg(ociSource, version string, opts *opt.CompileOptions) (*kcl.KCLResultList, error) { 477 ociOpts, err := c.ParseOciOptionFromString(ociSource, version) 478 479 if err != nil { 480 return nil, err 481 } 482 483 // 1. Create the temporary directory to pull the tar. 484 tmpDir, err := os.MkdirTemp("", "") 485 if err != nil { 486 return nil, reporter.NewErrorEvent(reporter.Bug, err, "internal bugs, please contact us to fix it.") 487 } 488 // clean the temp dir. 489 defer os.RemoveAll(tmpDir) 490 491 localPath := ociOpts.SanitizePathWithSuffix(tmpDir) 492 493 // 2. Pull the tar. 494 err = c.pullTarFromOci(localPath, ociOpts) 495 496 if err != nil { 497 return nil, err 498 } 499 500 // 3.Get the (*.tar) file path. 501 matches, err := filepath.Glob(filepath.Join(localPath, constants.KCL_PKG_TAR)) 502 if err != nil || len(matches) != 1 { 503 if err != nil { 504 return nil, reporter.NewErrorEvent(reporter.FailedGetPkg, err, "failed to pull kcl package") 505 } else { 506 return nil, errors.FailedPull 507 } 508 } 509 510 return c.CompileTarPkg(matches[0], opts) 511 } 512 513 // createIfNotExist will create a file if it does not exist. 514 func (c *KpmClient) createIfNotExist(filepath string, storeFunc func() error) error { 515 reporter.ReportMsgTo(fmt.Sprintf("creating new :%s", filepath), c.GetLogWriter()) 516 err := utils.CreateFileIfNotExist( 517 filepath, 518 storeFunc, 519 ) 520 if err != nil { 521 if errEvent, ok := err.(*reporter.KpmEvent); ok { 522 if errEvent.Type() != reporter.FileExists { 523 return err 524 } else { 525 reporter.ReportMsgTo(fmt.Sprintf("'%s' already exists", filepath), c.GetLogWriter()) 526 } 527 } else { 528 return err 529 } 530 } 531 532 return nil 533 } 534 535 // InitEmptyPkg will initialize an empty kcl package. 536 func (c *KpmClient) InitEmptyPkg(kclPkg *pkg.KclPkg) error { 537 err := c.createIfNotExist(kclPkg.ModFile.GetModFilePath(), kclPkg.ModFile.StoreModFile) 538 if err != nil { 539 return err 540 } 541 542 err = c.createIfNotExist(kclPkg.ModFile.GetModLockFilePath(), kclPkg.LockDepsVersion) 543 if err != nil { 544 return err 545 } 546 547 err = c.createIfNotExist(filepath.Join(kclPkg.ModFile.HomePath, constants.DEFAULT_KCL_FILE_NAME), kclPkg.CreateDefauleMain) 548 if err != nil { 549 return err 550 } 551 552 return nil 553 } 554 555 // AddDepWithOpts will add a dependency to the current kcl package. 556 func (c *KpmClient) AddDepWithOpts(kclPkg *pkg.KclPkg, opt *opt.AddOptions) (*pkg.KclPkg, error) { 557 c.noSumCheck = opt.NoSumCheck 558 kclPkg.NoSumCheck = opt.NoSumCheck 559 560 // 1. get the name and version of the repository from the input arguments. 561 d, err := pkg.ParseOpt(&opt.RegistryOpts) 562 if err != nil { 563 return nil, err 564 } 565 566 reporter.ReportMsgTo( 567 fmt.Sprintf("adding dependency '%s'", d.Name), 568 c.logWriter, 569 ) 570 // 2. download the dependency to the local path. 571 err = c.AddDepToPkg(kclPkg, d) 572 if err != nil { 573 return nil, err 574 } 575 576 // 3. update the kcl.mod and kcl.mod.lock. 577 err = kclPkg.UpdateModAndLockFile() 578 if err != nil { 579 return nil, err 580 } 581 582 succeedMsgInfo := d.Name 583 if len(d.Version) != 0 { 584 succeedMsgInfo = fmt.Sprintf("%s:%s", d.Name, d.Version) 585 } 586 587 reporter.ReportMsgTo( 588 fmt.Sprintf("add dependency '%s' successfully", succeedMsgInfo), 589 c.logWriter, 590 ) 591 return kclPkg, nil 592 } 593 594 // AddDepToPkg will add a dependency to the kcl package. 595 func (c *KpmClient) AddDepToPkg(kclPkg *pkg.KclPkg, d *pkg.Dependency) error { 596 597 if !reflect.DeepEqual(kclPkg.ModFile.Dependencies.Deps[d.Name], *d) { 598 // the dep passed on the cli is different from the kcl.mod. 599 kclPkg.ModFile.Dependencies.Deps[d.Name] = *d 600 } 601 602 // download all the dependencies. 603 changedDeps, _, err := c.InitGraphAndDownloadDeps(kclPkg) 604 605 if err != nil { 606 return err 607 } 608 609 // Update kcl.mod and kcl.mod.lock 610 for k, v := range changedDeps.Deps { 611 kclPkg.ModFile.Dependencies.Deps[k] = v 612 kclPkg.Dependencies.Deps[k] = v 613 } 614 615 return err 616 } 617 618 // PackagePkg will package the current kcl package into a "*.tar" file in under the package path. 619 func (c *KpmClient) PackagePkg(kclPkg *pkg.KclPkg, vendorMode bool) (string, error) { 620 globalPkgPath, err := env.GetAbsPkgPath() 621 if err != nil { 622 return "", err 623 } 624 625 err = kclPkg.ValidateKpmHome(globalPkgPath) 626 if err != (*reporter.KpmEvent)(nil) { 627 return "", err 628 } 629 630 err = c.Package(kclPkg, kclPkg.DefaultTarPath(), vendorMode) 631 632 if err != nil { 633 reporter.ExitWithReport("failed to package pkg " + kclPkg.GetPkgName() + ".") 634 return "", err 635 } 636 return kclPkg.DefaultTarPath(), nil 637 } 638 639 // Package will package the current kcl package into a "*.tar" file into 'tarPath'. 640 func (c *KpmClient) Package(kclPkg *pkg.KclPkg, tarPath string, vendorMode bool) error { 641 // Vendor all the dependencies into the current kcl package. 642 if vendorMode { 643 err := c.VendorDeps(kclPkg) 644 if err != nil { 645 return reporter.NewErrorEvent(reporter.FailedVendor, err, "failed to vendor dependencies") 646 } 647 } 648 649 // Tar the current kcl package into a "*.tar" file. 650 err := utils.TarDir(kclPkg.HomePath, tarPath) 651 if err != nil { 652 return reporter.NewErrorEvent(reporter.FailedPackage, err, "failed to package the kcl module") 653 } 654 return nil 655 } 656 657 // VendorDeps will vendor all the dependencies of the current kcl package. 658 func (c *KpmClient) VendorDeps(kclPkg *pkg.KclPkg) error { 659 // Mkdir the dir "vendor". 660 vendorPath := kclPkg.LocalVendorPath() 661 err := os.MkdirAll(vendorPath, 0755) 662 if err != nil { 663 return err 664 } 665 666 lockDeps := make([]pkg.Dependency, 0, len(kclPkg.Dependencies.Deps)) 667 668 for _, d := range kclPkg.Dependencies.Deps { 669 lockDeps = append(lockDeps, d) 670 } 671 672 // Traverse all dependencies in kcl.mod.lock. 673 for i := 0; i < len(lockDeps); i++ { 674 d := lockDeps[i] 675 if len(d.Name) == 0 { 676 return errors.InvalidDependency 677 } 678 vendorFullPath := filepath.Join(vendorPath, d.FullName) 679 // If the package already exists in the 'vendor', do nothing. 680 if utils.DirExists(vendorFullPath) && check(d, vendorFullPath) { 681 continue 682 } else { 683 // If not in the 'vendor', check the global cache. 684 cacheFullPath := filepath.Join(c.homePath, d.FullName) 685 if utils.DirExists(cacheFullPath) && check(d, cacheFullPath) { 686 // If there is, copy it into the 'vendor' directory. 687 err := copy.Copy(cacheFullPath, vendorFullPath) 688 if err != nil { 689 return err 690 } 691 } else if utils.DirExists(d.GetLocalFullPath(kclPkg.HomePath)) && check(d, d.GetLocalFullPath(kclPkg.HomePath)) { 692 // If there is, copy it into the 'vendor' directory. 693 err := copy.Copy(d.GetLocalFullPath(kclPkg.HomePath), vendorFullPath) 694 if err != nil { 695 return err 696 } 697 } else { 698 // re-download if not. 699 err = c.AddDepToPkg(kclPkg, &d) 700 if err != nil { 701 return err 702 } 703 // re-vendor again with new kcl.mod and kcl.mod.lock 704 err = c.VendorDeps(kclPkg) 705 if err != nil { 706 return err 707 } 708 return nil 709 } 710 } 711 } 712 713 return nil 714 } 715 716 // FillDepInfo will fill registry information for a dependency. 717 func (c *KpmClient) FillDepInfo(dep *pkg.Dependency) error { 718 if dep.Source.Local != nil { 719 dep.LocalFullPath = dep.Source.Local.Path 720 return nil 721 } 722 if dep.Source.Oci != nil { 723 dep.Source.Oci.Reg = c.GetSettings().DefaultOciRegistry() 724 urlpath := utils.JoinPath(c.GetSettings().DefaultOciRepo(), dep.Name) 725 dep.Source.Oci.Repo = urlpath 726 manifest := ocispec.Manifest{} 727 jsonDesc, err := c.FetchOciManifestIntoJsonStr(opt.OciFetchOptions{ 728 FetchBytesOptions: oras.DefaultFetchBytesOptions, 729 OciOptions: opt.OciOptions{ 730 Reg: c.GetSettings().DefaultOciRegistry(), 731 Repo: fmt.Sprintf("%s/%s", c.GetSettings().DefaultOciRepo(), dep.Name), 732 Tag: dep.Version, 733 }, 734 }) 735 736 if err != nil { 737 return err 738 } 739 740 err = json.Unmarshal([]byte(jsonDesc), &manifest) 741 if err != nil { 742 return err 743 } 744 745 if value, ok := manifest.Annotations[constants.DEFAULT_KCL_OCI_MANIFEST_SUM]; ok { 746 dep.Sum = value 747 } 748 return nil 749 } 750 return nil 751 } 752 753 // FillDependenciesInfo will fill registry information for all dependencies in a kcl.mod. 754 func (c *KpmClient) FillDependenciesInfo(modFile *pkg.ModFile) error { 755 for k, v := range modFile.Deps { 756 err := c.FillDepInfo(&v) 757 if err != nil { 758 return err 759 } 760 modFile.Deps[k] = v 761 } 762 return nil 763 } 764 765 // Download will download the dependency to the local path. 766 func (c *KpmClient) Download(dep *pkg.Dependency, localPath string) (*pkg.Dependency, error) { 767 if dep.Source.Git != nil { 768 _, err := c.DownloadFromGit(dep.Source.Git, localPath) 769 if err != nil { 770 return nil, err 771 } 772 773 dep.LocalFullPath = localPath 774 // Creating symbolic links in a global cache is not an optimal solution. 775 // This allows kclvm to locate the package by default. 776 // This feature is unstable and will be removed soon. 777 err = createDepRef(dep.LocalFullPath, filepath.Join(filepath.Dir(localPath), dep.Name)) 778 if err != nil { 779 return nil, err 780 } 781 dep.FullName = dep.GenDepFullName() 782 // If the dependency is from git commit, the version is the commit id. 783 // If the dependency is from git tag, the version is the tag. 784 dep.Version, err = dep.Source.Git.GetValidGitReference() 785 if err != nil { 786 return nil, err 787 } 788 } 789 790 if dep.Source.Oci != nil { 791 localPath, err := c.DownloadFromOci(dep.Source.Oci, localPath) 792 if err != nil { 793 return nil, err 794 } 795 dep.Version = dep.Source.Oci.Tag 796 dep.LocalFullPath = localPath 797 // Creating symbolic links in a global cache is not an optimal solution. 798 // This allows kclvm to locate the package by default. 799 // This feature is unstable and will be removed soon. 800 err = createDepRef(dep.LocalFullPath, filepath.Join(filepath.Dir(localPath), dep.Name)) 801 if err != nil { 802 return nil, err 803 } 804 dep.FullName = dep.GenDepFullName() 805 } 806 807 if dep.Source.Local != nil { 808 dep.LocalFullPath = dep.Source.Local.Path 809 } 810 811 var err error 812 dep.Sum, err = utils.HashDir(dep.LocalFullPath) 813 if err != nil { 814 return nil, reporter.NewErrorEvent( 815 reporter.FailedHashPkg, 816 err, 817 fmt.Sprintf("failed to hash the kcl package '%s' in '%s'.", dep.Name, dep.LocalFullPath), 818 ) 819 } 820 821 return dep, nil 822 } 823 824 // DownloadFromGit will download the dependency from the git repository. 825 func (c *KpmClient) DownloadFromGit(dep *pkg.Git, localPath string) (string, error) { 826 var msg string 827 if len(dep.Tag) != 0 { 828 msg = fmt.Sprintf("with tag '%s'", dep.Tag) 829 } 830 831 if len(dep.Commit) != 0 { 832 msg = fmt.Sprintf("with commit '%s'", dep.Commit) 833 } 834 835 reporter.ReportMsgTo( 836 fmt.Sprintf("cloning '%s' %s", dep.Url, msg), 837 c.logWriter, 838 ) 839 840 _, err := git.CloneWithOpts( 841 git.WithCommit(dep.Commit), 842 git.WithTag(dep.Tag), 843 git.WithRepoURL(dep.Url), 844 git.WithLocalPath(localPath), 845 git.WithWriter(c.logWriter), 846 ) 847 848 if err != nil { 849 return localPath, reporter.NewErrorEvent( 850 reporter.FailedCloneFromGit, 851 err, 852 fmt.Sprintf("failed to clone from '%s' into '%s'.", dep.Url, localPath), 853 ) 854 } 855 856 return localPath, err 857 } 858 859 func (c *KpmClient) ParseKclModFile(kclPkg *pkg.KclPkg) (map[string]map[string]string, error) { 860 // Get path to kcl.mod file 861 modFilePath := kclPkg.ModFile.GetModFilePath() 862 863 // Read the content of the kcl.mod file 864 modFileBytes, err := os.ReadFile(modFilePath) 865 if err != nil { 866 return nil, err 867 } 868 869 // Normalize line endings for Windows systems 870 modFileContent := strings.ReplaceAll(string(modFileBytes), "\r\n", "\n") 871 872 // Parse the TOML content 873 var modFileData map[string]interface{} 874 if err := toml.Unmarshal([]byte(modFileContent), &modFileData); err != nil { 875 return nil, err 876 } 877 878 // Extract dependency information 879 dependencies := make(map[string]map[string]string) 880 if deps, ok := modFileData["dependencies"].(map[string]interface{}); ok { 881 for dep, details := range deps { 882 dependency := make(map[string]string) 883 switch d := details.(type) { 884 case string: 885 // For simple version strings 886 dependency["version"] = d 887 case map[string]interface{}: 888 // For dependencies with attributes 889 for key, value := range d { 890 dependency[key] = fmt.Sprintf("%v", value) 891 } 892 default: 893 return nil, fmt.Errorf("unsupported dependency format") 894 } 895 dependencies[dep] = dependency 896 } 897 } 898 899 return dependencies, nil 900 } 901 902 // DownloadFromOci will download the dependency from the oci repository. 903 func (c *KpmClient) DownloadFromOci(dep *pkg.Oci, localPath string) (string, error) { 904 ociClient, err := oci.NewOciClient(dep.Reg, dep.Repo, &c.settings) 905 if err != nil { 906 return "", err 907 } 908 ociClient.SetLogWriter(c.logWriter) 909 // Select the latest tag, if the tag, the user inputed, is empty. 910 var tagSelected string 911 if len(dep.Tag) == 0 { 912 tagSelected, err = ociClient.TheLatestTag() 913 if err != nil { 914 return "", err 915 } 916 917 reporter.ReportMsgTo( 918 fmt.Sprintf("the lastest version '%s' will be added", tagSelected), 919 c.logWriter, 920 ) 921 922 dep.Tag = tagSelected 923 localPath = localPath + dep.Tag 924 } else { 925 tagSelected = dep.Tag 926 } 927 928 reporter.ReportMsgTo( 929 fmt.Sprintf("downloading '%s:%s' from '%s/%s:%s'", dep.Repo, tagSelected, dep.Reg, dep.Repo, tagSelected), 930 c.logWriter, 931 ) 932 933 // Pull the package with the tag. 934 err = ociClient.Pull(localPath, tagSelected) 935 if err != nil { 936 return "", err 937 } 938 939 matches, _ := filepath.Glob(filepath.Join(localPath, "*.tar")) 940 if matches == nil || len(matches) != 1 { 941 // then try to glob tgz file 942 matches, _ = filepath.Glob(filepath.Join(localPath, "*.tgz")) 943 if matches == nil || len(matches) != 1 { 944 err = reporter.NewErrorEvent( 945 reporter.InvalidKclPkg, 946 err, 947 fmt.Sprintf("failed to find the kcl package tar from '%s'.", localPath), 948 ) 949 } 950 951 return "", reporter.NewErrorEvent( 952 reporter.InvalidKclPkg, 953 err, 954 fmt.Sprintf("failed to find the kcl package tar from '%s'.", localPath), 955 ) 956 } 957 958 tarPath := matches[0] 959 if utils.IsTar(tarPath) { 960 err = utils.UnTarDir(tarPath, localPath) 961 } else { 962 err = utils.ExtractTarball(tarPath, localPath) 963 } 964 if err != nil { 965 return "", reporter.NewErrorEvent( 966 reporter.FailedUntarKclPkg, 967 err, 968 fmt.Sprintf("failed to untar the kcl package tar from '%s' into '%s'.", tarPath, localPath), 969 ) 970 } 971 972 // After untar the downloaded kcl package tar file, remove the tar file. 973 if utils.DirExists(tarPath) { 974 rmErr := os.Remove(tarPath) 975 if rmErr != nil { 976 return "", reporter.NewErrorEvent( 977 reporter.FailedUntarKclPkg, 978 err, 979 fmt.Sprintf("failed to untar the kcl package tar from '%s' into '%s'.", tarPath, localPath), 980 ) 981 } 982 } 983 984 return localPath, nil 985 } 986 987 // PullFromOci will pull a kcl package from oci registry and unpack it. 988 func (c *KpmClient) PullFromOci(localPath, source, tag string) error { 989 localPath, err := filepath.Abs(localPath) 990 if err != nil { 991 return reporter.NewErrorEvent(reporter.Bug, err) 992 } 993 if len(source) == 0 { 994 return reporter.NewErrorEvent( 995 reporter.UnKnownPullWhat, 996 errors.FailedPull, 997 "oci url or package name must be specified", 998 ) 999 } 1000 1001 if len(tag) == 0 { 1002 reporter.ReportMsgTo( 1003 fmt.Sprintf("start to pull '%s'", source), 1004 c.logWriter, 1005 ) 1006 } else { 1007 reporter.ReportMsgTo( 1008 fmt.Sprintf("start to pull '%s' with tag '%s'", source, tag), 1009 c.logWriter, 1010 ) 1011 } 1012 1013 ociOpts, err := c.ParseOciOptionFromString(source, tag) 1014 if err != nil { 1015 return err 1016 } 1017 1018 tmpDir, err := os.MkdirTemp("", "") 1019 if err != nil { 1020 return reporter.NewErrorEvent(reporter.Bug, err, fmt.Sprintf("failed to create temp dir '%s'.", tmpDir)) 1021 } 1022 // clean the temp dir. 1023 defer os.RemoveAll(tmpDir) 1024 1025 storepath := ociOpts.SanitizePathWithSuffix(tmpDir) 1026 err = c.pullTarFromOci(storepath, ociOpts) 1027 if err != nil { 1028 return err 1029 } 1030 1031 // Get the (*.tar) file path. 1032 tarPath := filepath.Join(storepath, constants.KCL_PKG_TAR) 1033 matches, err := filepath.Glob(tarPath) 1034 if err != nil || len(matches) != 1 { 1035 if err == nil { 1036 err = errors.InvalidPkg 1037 } 1038 1039 return reporter.NewErrorEvent( 1040 reporter.InvalidKclPkg, 1041 err, 1042 fmt.Sprintf("failed to find the kcl package tar from '%s'.", tarPath), 1043 ) 1044 } 1045 1046 // Untar the tar file. 1047 storagePath := ociOpts.SanitizePathWithSuffix(localPath) 1048 err = utils.UnTarDir(matches[0], storagePath) 1049 if err != nil { 1050 return reporter.NewErrorEvent( 1051 reporter.FailedUntarKclPkg, 1052 err, 1053 fmt.Sprintf("failed to untar the kcl package tar from '%s' into '%s'.", matches[0], storagePath), 1054 ) 1055 } 1056 1057 reporter.ReportMsgTo( 1058 fmt.Sprintf("pulled '%s' in '%s' successfully", source, storagePath), 1059 c.logWriter, 1060 ) 1061 return nil 1062 } 1063 1064 // PushToOci will push a kcl package to oci registry. 1065 func (c *KpmClient) PushToOci(localPath string, ociOpts *opt.OciOptions) error { 1066 ociCli, err := oci.NewOciClient(ociOpts.Reg, ociOpts.Repo, &c.settings) 1067 if err != nil { 1068 return err 1069 } 1070 1071 ociCli.SetLogWriter(c.logWriter) 1072 1073 exist, err := ociCli.ContainsTag(ociOpts.Tag) 1074 if err != (*reporter.KpmEvent)(nil) { 1075 return err 1076 } 1077 1078 if exist { 1079 return reporter.NewErrorEvent( 1080 reporter.PkgTagExists, 1081 fmt.Errorf("package version '%s' already exists", ociOpts.Tag), 1082 ) 1083 } 1084 1085 return ociCli.PushWithOciManifest(localPath, ociOpts.Tag, &opt.OciManifestOptions{ 1086 Annotations: ociOpts.Annotations, 1087 }) 1088 } 1089 1090 // LoginOci will login to the oci registry. 1091 func (c *KpmClient) LoginOci(hostname, username, password string) error { 1092 return oci.Login(hostname, username, password, &c.settings) 1093 } 1094 1095 // LogoutOci will logout from the oci registry. 1096 func (c *KpmClient) LogoutOci(hostname string) error { 1097 return oci.Logout(hostname, &c.settings) 1098 } 1099 1100 // ParseOciRef will parser '<repo_name>:<repo_tag>' into an 'OciOptions'. 1101 func (c *KpmClient) ParseOciRef(ociRef string) (*opt.OciOptions, error) { 1102 oci_address := strings.Split(ociRef, constants.OCI_SEPARATOR) 1103 if len(oci_address) == 1 { 1104 return &opt.OciOptions{ 1105 Reg: c.GetSettings().DefaultOciRegistry(), 1106 Repo: utils.JoinPath(c.GetSettings().DefaultOciRepo(), oci_address[0]), 1107 }, nil 1108 } else if len(oci_address) == 2 { 1109 return &opt.OciOptions{ 1110 Reg: c.GetSettings().DefaultOciRegistry(), 1111 Repo: utils.JoinPath(c.GetSettings().DefaultOciRepo(), oci_address[0]), 1112 Tag: oci_address[1], 1113 }, nil 1114 } else { 1115 return nil, reporter.NewEvent(reporter.IsNotRef) 1116 } 1117 } 1118 1119 // ParseOciOptionFromString will parser '<repo_name>:<repo_tag>' into an 'OciOptions' with an OCI registry. 1120 // the default OCI registry is 'docker.io'. 1121 // if the 'ociUrl' is only '<repo_name>', ParseOciOptionFromString will take 'latest' as the default tag. 1122 func (c *KpmClient) ParseOciOptionFromString(oci string, tag string) (*opt.OciOptions, error) { 1123 ociOpt, event := opt.ParseOciUrl(oci) 1124 if event != nil && (event.Type() == reporter.IsNotUrl || event.Type() == reporter.UrlSchemeNotOci) { 1125 ociOpt, err := c.ParseOciRef(oci) 1126 if err != nil { 1127 return nil, err 1128 } 1129 if len(tag) != 0 { 1130 reporter.ReportEventTo( 1131 reporter.NewEvent( 1132 reporter.InvalidFlag, 1133 "kpm get version from oci reference '<repo_name>:<repo_tag>'", 1134 ), 1135 c.logWriter, 1136 ) 1137 reporter.ReportEventTo( 1138 reporter.NewEvent( 1139 reporter.InvalidFlag, 1140 "arg '--tag' is invalid for oci reference", 1141 ), 1142 c.logWriter, 1143 ) 1144 } 1145 return ociOpt, nil 1146 } 1147 1148 ociOpt.Tag = tag 1149 1150 return ociOpt, nil 1151 } 1152 1153 // InitGraphAndDownloadDeps initializes a dependency graph and call downloadDeps function. 1154 func (c *KpmClient) InitGraphAndDownloadDeps(kclPkg *pkg.KclPkg) (*pkg.Dependencies, graph.Graph[string, string], error) { 1155 1156 depGraph := graph.New(graph.StringHash, graph.Directed(), graph.PreventCycles()) 1157 1158 // add the root vertex(package name) to the dependency graph. 1159 root := fmt.Sprintf("%s@%s", kclPkg.GetPkgName(), kclPkg.GetPkgVersion()) 1160 err := depGraph.AddVertex(root) 1161 if err != nil { 1162 return nil, nil, err 1163 } 1164 1165 changedDeps, err := c.downloadDeps(kclPkg.ModFile.Dependencies, kclPkg.Dependencies, depGraph, root) 1166 if err != nil { 1167 return nil, nil, err 1168 } 1169 1170 return changedDeps, depGraph, nil 1171 } 1172 1173 // dependencyExists will check whether the dependency exists in the local filesystem. 1174 func (c *KpmClient) dependencyExists(dep *pkg.Dependency, lockDeps *pkg.Dependencies) *pkg.Dependency { 1175 1176 // If the flag '--no_sum_check' is set, skip the checksum check. 1177 if c.noSumCheck { 1178 // If the dependent package does exist locally 1179 if utils.DirExists(filepath.Join(c.homePath, dep.FullName)) { 1180 return dep 1181 } 1182 } 1183 1184 lockDep, present := lockDeps.Deps[dep.Name] 1185 // Check if the sum of this dependency in kcl.mod.lock has been changed. 1186 if !c.noSumCheck && present { 1187 // If the dependent package does not exist locally, then method 'check' will return false. 1188 if c.noSumCheck || check(lockDep, filepath.Join(c.homePath, dep.FullName)) { 1189 return dep 1190 } 1191 } 1192 1193 return nil 1194 } 1195 1196 // downloadDeps will download all the dependencies of the current kcl package. 1197 func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencies, depGraph graph.Graph[string, string], parent string) (*pkg.Dependencies, error) { 1198 newDeps := pkg.Dependencies{ 1199 Deps: make(map[string]pkg.Dependency), 1200 } 1201 1202 // Traverse all dependencies in kcl.mod 1203 for _, d := range deps.Deps { 1204 if len(d.Name) == 0 { 1205 return nil, errors.InvalidDependency 1206 } 1207 1208 existDep := c.dependencyExists(&d, &lockDeps) 1209 if existDep != nil { 1210 newDeps.Deps[d.Name] = *existDep 1211 continue 1212 } 1213 1214 expectedSum := lockDeps.Deps[d.Name].Sum 1215 // Clean the cache 1216 if len(c.homePath) == 0 || len(d.FullName) == 0 { 1217 return nil, errors.InternalBug 1218 } 1219 dir := filepath.Join(c.homePath, d.FullName) 1220 os.RemoveAll(dir) 1221 1222 // download dependencies 1223 lockedDep, err := c.Download(&d, dir) 1224 if err != nil { 1225 return nil, err 1226 } 1227 1228 if !lockedDep.IsFromLocal() { 1229 if !c.noSumCheck && expectedSum != "" && 1230 lockedDep.Sum != expectedSum && 1231 existDep != nil && 1232 existDep.FullName == d.FullName { 1233 return nil, reporter.NewErrorEvent( 1234 reporter.CheckSumMismatch, 1235 errors.CheckSumMismatchError, 1236 fmt.Sprintf("checksum for '%s' changed in lock file", lockedDep.Name), 1237 ) 1238 } 1239 } 1240 1241 // Update kcl.mod and kcl.mod.lock 1242 newDeps.Deps[d.Name] = *lockedDep 1243 lockDeps.Deps[d.Name] = *lockedDep 1244 } 1245 1246 // necessary to make a copy as when we are updating kcl.mod in below for loop 1247 // then newDeps.Deps gets updated and range gets an extra value to iterate through 1248 // this messes up the dependency graph 1249 newDepsCopy := make(map[string]pkg.Dependency) 1250 for k, v := range newDeps.Deps { 1251 newDepsCopy[k] = v 1252 } 1253 1254 // Recursively download the dependencies of the new dependencies. 1255 for _, d := range newDepsCopy { 1256 // Load kcl.mod file of the new downloaded dependencies. 1257 deppkg, err := pkg.LoadKclPkg(filepath.Join(c.homePath, d.FullName)) 1258 if len(d.LocalFullPath) != 0 { 1259 deppkg, err = pkg.LoadKclPkg(d.LocalFullPath) 1260 } 1261 1262 if err != nil { 1263 if os.IsNotExist(err) { 1264 continue 1265 } 1266 return nil, err 1267 } 1268 source := fmt.Sprintf("%s@%s", d.Name, d.Version) 1269 source = strings.TrimRight(source, "@") 1270 err = depGraph.AddVertex(source) 1271 if err != nil && err != graph.ErrVertexAlreadyExists { 1272 return nil, err 1273 } 1274 1275 err = depGraph.AddEdge(parent, source) 1276 if err != nil { 1277 if err == graph.ErrEdgeCreatesCycle { 1278 return nil, reporter.NewErrorEvent( 1279 reporter.CircularDependencyExist, 1280 nil, 1281 fmt.Sprintf("adding %s as a dependency results in a cycle", source), 1282 ) 1283 } 1284 return nil, err 1285 } 1286 1287 // Download the dependencies. 1288 nested, err := c.downloadDeps(deppkg.ModFile.Dependencies, lockDeps, depGraph, source) 1289 if err != nil { 1290 return nil, err 1291 } 1292 1293 // Update kcl.mod. 1294 for _, d := range nested.Deps { 1295 if _, ok := newDeps.Deps[d.Name]; !ok { 1296 newDeps.Deps[d.Name] = d 1297 } 1298 } 1299 } 1300 1301 return &newDeps, nil 1302 } 1303 1304 // pullTarFromOci will pull a kcl package tar file from oci registry. 1305 func (c *KpmClient) pullTarFromOci(localPath string, ociOpts *opt.OciOptions) error { 1306 absPullPath, err := filepath.Abs(localPath) 1307 if err != nil { 1308 return reporter.NewErrorEvent(reporter.Bug, err) 1309 } 1310 1311 ociCli, err := oci.NewOciClient(ociOpts.Reg, ociOpts.Repo, &c.settings) 1312 if err != nil { 1313 return err 1314 } 1315 1316 ociCli.SetLogWriter(c.logWriter) 1317 1318 var tagSelected string 1319 if len(ociOpts.Tag) == 0 { 1320 tagSelected, err = ociCli.TheLatestTag() 1321 if err != nil { 1322 return err 1323 } 1324 reporter.ReportMsgTo( 1325 fmt.Sprintf("the lastest version '%s' will be pulled", tagSelected), 1326 c.logWriter, 1327 ) 1328 } else { 1329 tagSelected = ociOpts.Tag 1330 } 1331 1332 full_repo := utils.JoinPath(ociOpts.Reg, ociOpts.Repo) 1333 reporter.ReportMsgTo( 1334 fmt.Sprintf("pulling '%s:%s' from '%s'", ociOpts.Repo, tagSelected, full_repo), 1335 c.logWriter, 1336 ) 1337 1338 err = ociCli.Pull(absPullPath, tagSelected) 1339 if err != nil { 1340 return err 1341 } 1342 1343 return nil 1344 } 1345 1346 // FetchOciManifestConfIntoJsonStr will fetch the oci manifest config of the kcl package from the oci registry and return it into json string. 1347 func (c *KpmClient) FetchOciManifestIntoJsonStr(opts opt.OciFetchOptions) (string, error) { 1348 ociCli, err := oci.NewOciClient(opts.Reg, opts.Repo, &c.settings) 1349 if err != nil { 1350 return "", err 1351 } 1352 1353 manifestJson, err := ociCli.FetchManifestIntoJsonStr(opts) 1354 if err != nil { 1355 return "", err 1356 } 1357 return manifestJson, nil 1358 } 1359 1360 // check sum for a Dependency. 1361 func check(dep pkg.Dependency, newDepPath string) bool { 1362 if dep.Sum == "" { 1363 return false 1364 } 1365 1366 sum, err := utils.HashDir(newDepPath) 1367 1368 if err != nil { 1369 return false 1370 } 1371 1372 return dep.Sum == sum 1373 } 1374 1375 // createDepRef will create a dependency reference for the dependency saved on the local filesystem. 1376 // On the unix-like system, it will create a symbolic link. 1377 // On the windows system, it will create a junction. 1378 func createDepRef(depName, refName string) error { 1379 if runtime.GOOS == "windows" { 1380 // 'go-getter' continuously occupies files in '.git', causing the copy operation to fail 1381 opt := copy.Options{ 1382 Skip: func(srcinfo os.FileInfo, src, dest string) (bool, error) { 1383 return filepath.Base(src) == constants.GitPathSuffix, nil 1384 }, 1385 } 1386 return copy.Copy(depName, refName, opt) 1387 } else { 1388 return utils.CreateSymlink(depName, refName) 1389 } 1390 }