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  }