github.com/KusionStack/kpm@v0.8.4-0.20240326033734-dc72298a30e5/pkg/cmd/cmd_add.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  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/urfave/cli/v2"
    14  	"kcl-lang.io/kpm/pkg/client"
    15  	"kcl-lang.io/kpm/pkg/env"
    16  	"kcl-lang.io/kpm/pkg/errors"
    17  	"kcl-lang.io/kpm/pkg/opt"
    18  	pkg "kcl-lang.io/kpm/pkg/package"
    19  	"kcl-lang.io/kpm/pkg/reporter"
    20  )
    21  
    22  // NewAddCmd new a Command for `kpm add`.
    23  func NewAddCmd(kpmcli *client.KpmClient) *cli.Command {
    24  	return &cli.Command{
    25  		Hidden: false,
    26  		Name:   "add",
    27  		Usage:  "add new dependency",
    28  		Flags: []cli.Flag{
    29  			&cli.StringSliceFlag{
    30  				Name:  "git",
    31  				Usage: "Git repository location",
    32  			},
    33  			&cli.StringSliceFlag{
    34  				Name:  "tag",
    35  				Usage: "Git repository tag",
    36  			},
    37  			&cli.StringSliceFlag{
    38  				Name:  "commit",
    39  				Usage: "Git repository commit",
    40  			},
    41  			&cli.BoolFlag{
    42  				Name:  FLAG_NO_SUM_CHECK,
    43  				Usage: "do not check the checksum of the package and update kcl.mod.lock",
    44  			},
    45  		},
    46  
    47  		Action: func(c *cli.Context) error {
    48  			return KpmAdd(c, kpmcli)
    49  		},
    50  	}
    51  }
    52  
    53  func KpmAdd(c *cli.Context, kpmcli *client.KpmClient) error {
    54  	// acquire the lock of the package cache.
    55  	err := kpmcli.AcquirePackageCacheLock()
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	defer func() {
    61  		// release the lock of the package cache after the function returns.
    62  		releaseErr := kpmcli.ReleasePackageCacheLock()
    63  		if releaseErr != nil && err == nil {
    64  			err = releaseErr
    65  		}
    66  	}()
    67  
    68  	pwd, err := os.Getwd()
    69  
    70  	if err != nil {
    71  		return reporter.NewErrorEvent(reporter.Bug, err, "internal bugs, please contact us to fix it.")
    72  	}
    73  
    74  	globalPkgPath, err := env.GetAbsPkgPath()
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	kclPkg, err := pkg.LoadKclPkg(pwd)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	err = kclPkg.ValidateKpmHome(globalPkgPath)
    85  	if err != (*reporter.KpmEvent)(nil) {
    86  		return err
    87  	}
    88  
    89  	addOpts, err := parseAddOptions(c, kpmcli, globalPkgPath)
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	if addOpts.RegistryOpts.Local != nil {
    95  		absAddPath, err := filepath.Abs(addOpts.RegistryOpts.Local.Path)
    96  		if err != nil {
    97  			return reporter.NewErrorEvent(reporter.Bug, err, "internal bugs, please contact us to fix it.")
    98  		}
    99  		if absAddPath == kclPkg.HomePath {
   100  			return reporter.NewErrorEvent(
   101  				reporter.AddItselfAsDep,
   102  				fmt.Errorf("cannot add '%s' as a dependency to itself", kclPkg.GetPkgName()),
   103  			)
   104  		}
   105  	}
   106  
   107  	err = addOpts.Validate()
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	_, err = kpmcli.AddDepWithOpts(kclPkg, addOpts)
   113  	if err != nil {
   114  		return err
   115  	}
   116  	return nil
   117  }
   118  
   119  // onlyOnceOption is used to check that the value of some parameters can only appear once.
   120  func onlyOnceOption(c *cli.Context, name string) (string, *reporter.KpmEvent) {
   121  	inputOpt := c.StringSlice(name)
   122  	if len(inputOpt) > 1 {
   123  		return "", reporter.NewErrorEvent(reporter.InvalidCmd, fmt.Errorf("the argument '%s' cannot be used multiple times", name))
   124  	} else if len(inputOpt) == 1 {
   125  		return inputOpt[0], nil
   126  	} else {
   127  		return "", nil
   128  	}
   129  }
   130  
   131  // parseAddOptions will parse the user cli inputs.
   132  func parseAddOptions(c *cli.Context, kpmcli *client.KpmClient, localPath string) (*opt.AddOptions, error) {
   133  	noSumCheck := c.Bool(FLAG_NO_SUM_CHECK)
   134  	// parse from 'kpm add -git https://xxx/xxx.git -tag v0.0.1'.
   135  	if c.NArg() == 0 {
   136  		gitOpts, err := parseGitRegistryOptions(c)
   137  		if err != (*reporter.KpmEvent)(nil) {
   138  			if err.Type() == reporter.InvalidGitUrl {
   139  				return nil, reporter.NewErrorEvent(reporter.InvalidCmd, errors.InvalidAddOptions)
   140  			}
   141  			return nil, err
   142  		}
   143  		return &opt.AddOptions{
   144  			LocalPath:    localPath,
   145  			RegistryOpts: *gitOpts,
   146  			NoSumCheck:   noSumCheck,
   147  		}, nil
   148  	} else {
   149  		localPkg, err := parseLocalPathOptions(c)
   150  		if err != (*reporter.KpmEvent)(nil) {
   151  			// parse from 'kpm add xxx:0.0.1'.
   152  			ociReg, err := parseOciRegistryOptions(c, kpmcli)
   153  			if err != nil {
   154  				return nil, err
   155  			}
   156  			return &opt.AddOptions{
   157  				LocalPath:    localPath,
   158  				RegistryOpts: *ociReg,
   159  				NoSumCheck:   noSumCheck,
   160  			}, nil
   161  		} else {
   162  			return &opt.AddOptions{
   163  				LocalPath:    localPath,
   164  				RegistryOpts: *localPkg,
   165  				NoSumCheck:   noSumCheck,
   166  			}, nil
   167  		}
   168  	}
   169  }
   170  
   171  // parseGitRegistryOptions will parse the git registry information from user cli inputs.
   172  func parseGitRegistryOptions(c *cli.Context) (*opt.RegistryOptions, *reporter.KpmEvent) {
   173  	gitUrl, err := onlyOnceOption(c, "git")
   174  
   175  	if err != (*reporter.KpmEvent)(nil) {
   176  		return nil, err
   177  	}
   178  
   179  	gitTag, err := onlyOnceOption(c, "tag")
   180  
   181  	if err != (*reporter.KpmEvent)(nil) {
   182  		return nil, err
   183  	}
   184  
   185  	gitCommit, err := onlyOnceOption(c, "commit")
   186  
   187  	if err != (*reporter.KpmEvent)(nil) {
   188  		return nil, err
   189  	}
   190  
   191  	if gitUrl == "" {
   192  		return nil, reporter.NewErrorEvent(reporter.InvalidGitUrl, fmt.Errorf("the argument 'git' is required"))
   193  	}
   194  
   195  	if (gitTag == "" && gitCommit == "") || (gitTag != "" && gitCommit != "") {
   196  		return nil, reporter.NewErrorEvent(reporter.WithoutGitTag, fmt.Errorf("invalid arguments, one of commit or tag should be passed"))
   197  	}
   198  
   199  	return &opt.RegistryOptions{
   200  		Git: &opt.GitOptions{
   201  			Url:    gitUrl,
   202  			Tag:    gitTag,
   203  			Commit: gitCommit,
   204  		},
   205  	}, nil
   206  }
   207  
   208  // parseOciRegistryOptions will parse the oci registry information from user cli inputs.
   209  func parseOciRegistryOptions(c *cli.Context, kpmcli *client.KpmClient) (*opt.RegistryOptions, error) {
   210  	ociPkgRef := c.Args().First()
   211  	name, version, err := parseOciPkgNameAndVersion(ociPkgRef)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  
   216  	return &opt.RegistryOptions{
   217  		Oci: &opt.OciOptions{
   218  			Reg:     kpmcli.GetSettings().DefaultOciRegistry(),
   219  			Repo:    kpmcli.GetSettings().DefaultOciRepo(),
   220  			PkgName: name,
   221  			Tag:     version,
   222  		},
   223  	}, nil
   224  }
   225  
   226  // parseLocalPathOptions will parse the local path information from user cli inputs.
   227  func parseLocalPathOptions(c *cli.Context) (*opt.RegistryOptions, *reporter.KpmEvent) {
   228  	localPath := c.Args().First()
   229  	if localPath == "" {
   230  		return nil, reporter.NewErrorEvent(reporter.PathIsEmpty, errors.PathIsEmpty)
   231  	}
   232  	// check if the local path exists.
   233  	if _, err := os.Stat(localPath); os.IsNotExist(err) {
   234  		return nil, reporter.NewErrorEvent(reporter.LocalPathNotExist, err)
   235  	} else {
   236  		return &opt.RegistryOptions{
   237  			Local: &opt.LocalOptions{
   238  				Path: localPath,
   239  			},
   240  		}, nil
   241  	}
   242  }
   243  
   244  // parseOciPkgNameAndVersion will parse package name and version
   245  // from string "<pkg_name>:<pkg_version>".
   246  func parseOciPkgNameAndVersion(s string) (string, string, error) {
   247  	parts := strings.Split(s, ":")
   248  	if len(parts) == 1 {
   249  		return parts[0], "", nil
   250  	}
   251  
   252  	if len(parts) > 2 {
   253  		return "", "", reporter.NewErrorEvent(reporter.InvalidPkgRef, fmt.Errorf("invalid oci package reference '%s'", s))
   254  	}
   255  
   256  	if parts[1] == "" {
   257  		return "", "", reporter.NewErrorEvent(reporter.InvalidPkgRef, fmt.Errorf("invalid oci package reference '%s'", s))
   258  	}
   259  
   260  	return parts[0], parts[1], nil
   261  }