diff --git a/.github/codecov.yml b/.github/codecov.yml index 318a3749..2e846f9e 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -8,4 +8,4 @@ coverage: - "internal/packages" - "internal/typepatch" - "internal/github" - - "xtool/cppkg" + - "xtool" diff --git a/go.mod b/go.mod index c1b78658..b5f0a9e5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/goplus/llgo -go 1.22.0 +go 1.23.0 toolchain go1.24.1 diff --git a/xtool/cpgithubpkg/cpgithubpkg.go b/xtool/cpgithubpkg/cpgithubpkg.go new file mode 100644 index 00000000..b10b8b30 --- /dev/null +++ b/xtool/cpgithubpkg/cpgithubpkg.go @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cpgithubpkg + +import ( + "log" + "os" + "os/exec" + "slices" + "strings" + + "github.com/goccy/go-yaml" + "golang.org/x/mod/semver" +) + +type version struct { + Folder string `yaml:"folder"` +} + +type config struct { + Versions map[string]version `yaml:"versions"` +} + +type template struct { + FromVer string `yaml:"from"` + Folder string `yaml:"folder"` + Tag string `yaml:"tag,omitempty"` // pattern with *, empty if dynamic tag +} + +type configEx struct { + PkgName string `yaml:"name"` + Versions map[string]version `yaml:"versions"` + Template template `yaml:"template"` +} + +// Main is the entry point for copying GitHub packages. +func Main(pkgName string) { + localDir := conanRoot() + "recipes/" + pkgName + "/" + + confFile := localDir + "config.yml" + b, err := os.ReadFile(confFile) + check(err) + + var conf config + err = yaml.Unmarshal(b, &conf) + check(err) + + tryCp := func(src map[string]any, ver string, v version) { + switch url := src["url"].(type) { + case string: + if pkgPath, tagPattern, ok := checkGithbPkg(url, ver); ok { + cpGithubPkg(pkgName, pkgPath, tagPattern, localDir, conf, ver, v) + } + case []any: + for _, u := range url { + url := u.(string) + if pkgPath, tagPattern, ok := checkGithbPkg(url, ver); ok { + cpGithubPkg(pkgName, pkgPath, tagPattern, localDir, conf, ver, v) + } + } + default: + log.Println("[INFO] skip source:", src) + } + } + + conandatas := make(map[string]conandata) // folder -> conandata + rangeVerDesc(conf.Versions, func(ver string, v version) { + cd, err := getConanData(conandatas, v.Folder, localDir) + if err != nil { + if os.IsNotExist(err) { + return + } + check(err) + } + + if src, ok := cd.Sources[ver]; ok { + switch src := src.(type) { + case map[string]any: + tryCp(src, ver, v) + case []any: + for _, u := range src { + tryCp(u.(map[string]any), ver, v) + } + default: + log.Panicln("[FATAL] source:", src) + } + } + }) +} + +func cpGithubPkg(pkgName, pkgPath, tagPattern, srcDir string, conf config, fromVer string, v version) { + destDir := cppkgRoot() + pkgPath + os.MkdirAll(destDir, os.ModePerm) + + err := exec.Command("cp", "-r", srcDir, destDir).Run() + check(err) + + confex := &configEx{ + PkgName: pkgName, + Versions: conf.Versions, + Template: template{ + FromVer: fromVer, + Folder: v.Folder, + Tag: tagPattern, + }, + } + b, err := yaml.Marshal(confex) + check(err) + + err = os.WriteFile(destDir+"/config.yml", b, os.ModePerm) + check(err) + + log.Println("[INFO] copy", pkgPath) + os.Exit(0) +} + +func checkGithbPkg(url, ver string) (pkgPath, tagPattern string, ok bool) { + const githubPrefix = "https://github.com/" + if strings.HasPrefix(url, githubPrefix) { + path := url[len(githubPrefix):] + parts := strings.SplitN(path, "/", 3) + if len(parts) == 3 { // user/repo/xxx + if pos := strings.Index(parts[2], ver); pos >= 0 { + userRepo := parts[0] + "/" + parts[1] + at := len(githubPrefix) + len(userRepo) + 1 + pos + tagPattern = tagPatternOf(url, ver, at) + pkgPath, ok = strings.ToLower(userRepo), true + } + } + } + return +} + +func tagPatternOf(url, ver string, at int) (tagPattern string) { + var tag string + if pos := strings.LastIndexByte(url[:at], '/'); pos >= 0 { + last := at + len(ver) + left := url[last:] + if end, ok := checkTagEnd(left); ok { + pos++ + tag = url[pos:last] + tagPattern = url[pos:at] + end + } + } + if tag == "" { + log.Println("[INFO] dynamic tag found:", url) + } + return +} + +func checkTagEnd(left string) (end string, ok bool) { + if n := len(left); n > 0 { + if left[0] == '/' { + return "*", true + } + if n >= 4 && left[0] == '.' { + ext := left[1:4] + return "*", ext == "tar" || ext == "zip" || ext == "tgz" + } + if strings.HasPrefix(left, "-stable.") { + return "*-stable", true + } + } + return "", false +} + +type conandata struct { + Sources map[string]any `yaml:"sources"` +} + +func getConanData(conandatas map[string]conandata, folder, localDir string) (ret conandata, err error) { + if v, ok := conandatas[folder]; ok { + return v, nil + } + file := localDir + folder + "/conandata.yml" + b, err := os.ReadFile(file) + if err != nil { + return + } + if err = yaml.Unmarshal(b, &ret); err != nil { + return + } + conandatas[folder] = ret + return +} + +func rangeVerDesc[V any](data map[string]V, f func(string, V)) { + keys := make([]string, 0, len(data)) + for k := range data { + keys = append(keys, "v"+k) + } + semver.Sort(keys) + for _, k := range slices.Backward(keys) { + k = k[1:] // remove 'v' + f(k, data[k]) + } +} + +func conanRoot() string { + home, _ := os.UserHomeDir() + return home + "/conan-center-index/" +} + +func cppkgRoot() string { + home, _ := os.UserHomeDir() + return home + "/cppkg/" +} + +func check(err error) { + if err != nil { + panic(err) + } +} diff --git a/xtool/cppkg/cppkg.go b/xtool/cppkg/cppkg.go index e5bbfde7..734c13bf 100644 --- a/xtool/cppkg/cppkg.go +++ b/xtool/cppkg/cppkg.go @@ -25,9 +25,9 @@ const ( DefaultFlags = IndexAutoUpdate | ToolQuietInstall ) -// Main is the main entry point for the cppkg package. +// Install installs a package with the given name and version. // pkgAndVer: 7bitcoder/7bitconf@1.2.0 -func Main(pkgAndVer string, flags int) { +func Install(pkgAndVer string, flags int) { pkgPath, ver := parsePkgVer(pkgAndVer) if ver == "" { panic("TODO: get latest version")