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 }