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 }