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

     1  // Copyright 2023 The KCL Authors. All rights reserved.
     2  // Deprecated: The entire contents of this file will be deprecated.
     3  // Please use the kcl cli - https://github.com/kcl-lang/cli.
     4  
     5  package cmd
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"slices"
    11  	"strings"
    12  
    13  	"github.com/dominikbraun/graph"
    14  	"github.com/urfave/cli/v2"
    15  	"golang.org/x/mod/module"
    16  	"kcl-lang.io/kpm/pkg/client"
    17  	"kcl-lang.io/kpm/pkg/env"
    18  	"kcl-lang.io/kpm/pkg/mvs"
    19  	pkg "kcl-lang.io/kpm/pkg/package"
    20  	"kcl-lang.io/kpm/pkg/reporter"
    21  	"kcl-lang.io/kpm/pkg/semver"
    22  )
    23  
    24  // NewUpdateCmd new a Command for `kpm update`.
    25  func NewUpdateCmd(kpmcli *client.KpmClient) *cli.Command {
    26  	return &cli.Command{
    27  		Hidden: false,
    28  		Name:   "update",
    29  		Usage:  "Update dependencies listed in kcl.mod.lock based on kcl.mod",
    30  		Flags: []cli.Flag{
    31  			&cli.BoolFlag{
    32  				Name:  FLAG_NO_SUM_CHECK,
    33  				Usage: "do not check the checksum of the package and update kcl.mod.lock",
    34  			},
    35  		},
    36  		Action: func(c *cli.Context) error {
    37  			return KpmUpdate(c, kpmcli)
    38  		},
    39  	}
    40  }
    41  
    42  func KpmUpdate(c *cli.Context, kpmcli *client.KpmClient) error {
    43  	kpmcli.SetNoSumCheck(c.Bool(FLAG_NO_SUM_CHECK))
    44  
    45  	// acquire the lock of the package cache.
    46  	err := kpmcli.AcquirePackageCacheLock()
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	defer func() {
    52  		// release the lock of the package cache after the function returns.
    53  		releaseErr := kpmcli.ReleasePackageCacheLock()
    54  		if releaseErr != nil && err == nil {
    55  			err = releaseErr
    56  		}
    57  	}()
    58  
    59  	pkgInfos := c.Args().Slice()
    60  
    61  	pwd, err := os.Getwd()
    62  	if err != nil {
    63  		return reporter.NewErrorEvent(reporter.Bug, err, "internal bugs, please contact us to fix it.")
    64  	}
    65  
    66  	kclPkg, err := kpmcli.LoadPkgFromPath(pwd)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	globalPkgPath, err := env.GetAbsPkgPath()
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	err = kclPkg.ValidateKpmHome(globalPkgPath)
    77  	if err != (*reporter.KpmEvent)(nil) {
    78  		return err
    79  	}
    80  
    81  	var (
    82  		modulesToUpgrade   []module.Version
    83  		modulesToDowngrade []module.Version
    84  	)
    85  
    86  	for _, pkgInfo := range pkgInfos {
    87  		err = GetModulesToUpdate(kclPkg, modulesToUpgrade, modulesToDowngrade, pkgInfo)
    88  		if err != nil {
    89  			reporter.Report(err)
    90  		}
    91  	}
    92  
    93  	_, depGraph, err := kpmcli.InitGraphAndDownloadDeps(kclPkg)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	reqs := mvs.ReqsGraph{
    99  		Graph:     depGraph,
   100  		KpmClient: kpmcli,
   101  		KpmPkg:    kclPkg,
   102  	}
   103  
   104  	target := module.Version{Path: kclPkg.GetPkgName(), Version: kclPkg.GetPkgVersion()}
   105  	buildList, err := mvs.UpdateBuildList(target, modulesToUpgrade, modulesToDowngrade, &reqs)
   106  	if err != nil {
   107  		return reporter.NewErrorEvent(reporter.FailedUpdatingBuildList, err, "failed to update build list")
   108  	}
   109  
   110  	// get all the vertices in the graph 
   111  	modules, err := graph.TopologicalSort(depGraph)
   112  	if err != nil {
   113  		return reporter.NewErrorEvent(reporter.FailedTopologicalSort, err, "failed to sort the dependencies")
   114  	}
   115  
   116  	kclPkg.ModFile.Dependencies.Deps = make(map[string]pkg.Dependency)
   117  
   118  	for _, module := range modules {
   119  		err = InsertModuleToDeps(kclPkg, module, target, buildList, reqs)
   120  		if err != nil {
   121  			return err
   122  		}
   123  	}
   124  
   125  	err = kpmcli.UpdateDeps(kclPkg)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	return nil
   130  }
   131  
   132  // GetModulesToUpdate validates if the packages is present in kcl.mod file and
   133  // find the latest version if version is not specified. Depending on the value of pkgVersion,
   134  // modulesToUpgrade or modulesToDowngrade will be updated. 
   135  func GetModulesToUpdate(kclPkg *pkg.KclPkg, modulesToUpgrade []module.Version,  modulesToDowngrade []module.Version, pkgInfo string) error {
   136  	pkgInfo = strings.TrimSpace(pkgInfo)
   137  	pkgName, pkgVersion, err := ParseOciPkgNameAndVersion(pkgInfo)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	var dep pkg.Dependency
   143  	var ok bool
   144  	if dep, ok = kclPkg.Deps[pkgName]; !ok {
   145  		return err
   146  	}
   147  
   148  	if pkgVersion == "" {
   149  		var releases []string
   150  		releases, err = client.GetReleasesFromSource(dep.GetSourceType(), dep.GetDownloadPath())
   151  		if err != nil {
   152  			return reporter.NewErrorEvent(
   153  				reporter.FailedGetReleases,
   154  				err,
   155  				fmt.Sprintf("failed to get releases for %s", pkgName),
   156  			)
   157  		}
   158  		pkgVersion, err = semver.LatestCompatibleVersion(releases, dep.Version)
   159  		if err != nil {
   160  			return reporter.NewErrorEvent(
   161  				reporter.FailedSelectLatestCompatibleVersion,
   162  				err,
   163  				fmt.Sprintf("failed to find the latest version for %s", pkgName),
   164  			)
   165  		}
   166  	}
   167  	if pkgVersion < dep.Version {
   168  		modulesToDowngrade = append(modulesToDowngrade, module.Version{Path: pkgName, Version: pkgVersion})
   169  	} else if pkgVersion > dep.Version {
   170  		modulesToUpgrade = append(modulesToUpgrade, module.Version{Path: pkgName, Version: pkgVersion})
   171  	}
   172  	return nil
   173  }
   174  
   175  // InsertModuleToDeps checks whether module is present in the buildList and it is not the same as the target module, 
   176  // and inserts it to the dependencies of kclPkg
   177  func InsertModuleToDeps(kclPkg *pkg.KclPkg, module module.Version, target module.Version, buildList []module.Version, reqs mvs.ReqsGraph) (error) {
   178  	if module.Path == target.Path || !slices.Contains(buildList, module) {
   179  		return nil
   180  	}
   181  	d := pkg.Dependency{
   182  		Name:    module.Path,
   183  		Version: module.Version,
   184  	}
   185  	d.FullName = d.GenDepFullName()
   186  	_, properties, err := reqs.VertexWithProperties(module)
   187  	if err != nil {
   188  		return reporter.NewErrorEvent(reporter.FailedGetVertexProperties, err, "failed to get vertex with properties")
   189  	}
   190  	// there must be one property depending on the download source type
   191  	for sourceType, uri := range properties.Attributes {
   192  		d.Source, err = pkg.GenSource(sourceType, uri, module.Version)
   193  		if err != nil {
   194  			return reporter.NewErrorEvent(reporter.FailedGenerateSource, err, "failed to generate source")
   195  		}
   196  	}
   197  	kclPkg.ModFile.Dependencies.Deps[module.Path] = d
   198  	return nil
   199  }