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