1106 lines
32 KiB
Go
1106 lines
32 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/zip"
|
|
_ "embed"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/ssgo/httpclient"
|
|
"github.com/ssgo/tool/sskey/sskeylib"
|
|
"github.com/ssgo/u"
|
|
)
|
|
|
|
var version = "v0.0.1"
|
|
|
|
//go:embed templates/_main.go
|
|
var mainCodeTPL string
|
|
|
|
//go:embed templates/_cacheFiles.go
|
|
var cacheFilesTPL string
|
|
|
|
//go:embed templates/_gitignore
|
|
var gitignoreTPL string
|
|
|
|
var goPath = "go"
|
|
|
|
type Command struct {
|
|
Name string
|
|
ShortName string
|
|
Args string
|
|
Comment string
|
|
Func func([]string) bool
|
|
}
|
|
|
|
type Config struct {
|
|
Name string
|
|
Version string
|
|
Main string
|
|
Target map[string]string
|
|
Module map[string]string
|
|
ModuleAlias map[string]string
|
|
ExtraImport map[string]string
|
|
CacheFile []string
|
|
SSKey string
|
|
CgoEnable bool
|
|
BuildFrom string
|
|
BuildEnv map[string][]string
|
|
BuildLdFlags map[string]string
|
|
}
|
|
|
|
var commands = []Command{
|
|
{"init", "i", "[service|client]", "init a new project for empty dir", initProject},
|
|
{"build", "b", "[all|os arch]", "build project", buildProject},
|
|
{"pack", "p", "[all|os arch]", "pack project", packageProject},
|
|
// {"login", "l", "", "login to apigo.cloud", login},
|
|
// {"deploy", "dp", "", "deploy project to apigo.cloud", depolyProject},
|
|
{"run", "r", "[args...]", "run project", runProject},
|
|
{"buildrun", "rr", "[args...]", "build and run project", buildRunProject},
|
|
{"watch", "w", "[args...]", "run and watch project", watchProject},
|
|
{"buildwatch", "ww", "[args...]", "build and run and watch project", buildWatchProject},
|
|
{"test", "t", "[args...]", "test project", testProject},
|
|
{"buildtest", "tt", "[args...]", "build and test project", buildTestProject},
|
|
{"tidy", "td", "", "tidy project, run go mod tidy", tidyProject},
|
|
{"check", "ck", "", "check go modules version", checkProject},
|
|
{"export", "e", "", "export typescript code for dev", exportForDev},
|
|
{"tags", "", "", "show git tags", showGitTags},
|
|
{"add tag", "t+", "[version]", "add git tag and push, if no new tag specified will use last tag +1", addGitTag},
|
|
{"commit", "c+", "comment", "commit git repo and push, comment is need", commitGitRepo},
|
|
}
|
|
|
|
var hc = httpclient.GetClient(30 * time.Second)
|
|
var cachePath = ".ag"
|
|
|
|
func init() {
|
|
hc.SetGlobalHeader("Content-Type", "application/json")
|
|
if homePath, err := os.UserHomeDir(); err == nil {
|
|
cachePath = filepath.Join(homePath, ".ag", "cache")
|
|
}
|
|
}
|
|
|
|
func showGitTags(args []string) bool {
|
|
return nil == runCommand("git", "tag", "-l", "v*", "--sort=-taggerdate", "--format=%(refname:short) %(taggerdate:short) %(*objectname:short)")
|
|
}
|
|
|
|
func commitAndTagGitRepo(args []string) bool {
|
|
if commitGitRepo(args) {
|
|
return addGitTag([]string{})
|
|
}
|
|
return false
|
|
}
|
|
|
|
func commitGitRepo(args []string) bool {
|
|
comment := strings.Join(args, " ")
|
|
if comment != "" {
|
|
if err := runCommand("git", "commit", "-a", "-m", comment); err == nil {
|
|
if err := runCommand("git", "push"); err == nil {
|
|
return true
|
|
} else {
|
|
fmt.Println("git push failed:", err.Error())
|
|
}
|
|
} else {
|
|
fmt.Println("git commit failed:", err.Error())
|
|
}
|
|
} else {
|
|
fmt.Println("commit message is empty")
|
|
}
|
|
return false
|
|
}
|
|
|
|
func addGitTag(args []string) bool {
|
|
newVer := ""
|
|
if len(args) > 0 {
|
|
newVer = args[0]
|
|
}
|
|
if newVer == "" {
|
|
if outs, err := u.RunCommand("git", "tag", "-l", "v*", "--sort=taggerdate"); err == nil {
|
|
oldVer := "v0.0.0"
|
|
for i := len(outs) - 1; i >= 0; i-- {
|
|
if outs[i][0] == 'v' && strings.IndexByte(outs[i], '.') != -1 {
|
|
oldVer = outs[len(outs)-1]
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
newVer = args[0]
|
|
} else {
|
|
versionParts := strings.Split(oldVer, ".")
|
|
v, err := strconv.Atoi(versionParts[len(versionParts)-1])
|
|
if err != nil {
|
|
v = 0
|
|
}
|
|
versionParts[len(versionParts)-1] = strconv.Itoa(v + 1)
|
|
newVer = strings.Join(versionParts, ".")
|
|
}
|
|
} else {
|
|
fmt.Println("get last tag failed:", err.Error())
|
|
}
|
|
}
|
|
|
|
if newVer != "" {
|
|
if err := runCommand("git", "tag", "-a", newVer, "-m", "update tag by 'ag tag+'"); err == nil {
|
|
if err := runCommand("git", "push", "origin", newVer); err != nil {
|
|
fmt.Println("git push failed:", err.Error())
|
|
}
|
|
} else {
|
|
fmt.Println("git add tag failed:", err.Error())
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func findTool() (sskeyPath string, logVPath string) {
|
|
sskeyPath = "sskey"
|
|
logVPath = "logv"
|
|
if binPath, err := exec.LookPath("sskey"); err == nil && binPath != "" {
|
|
sskeyPath = binPath
|
|
}
|
|
if binPath, err := exec.LookPath("logv"); err == nil && binPath != "" {
|
|
logVPath = binPath
|
|
}
|
|
|
|
if sskeyPath == "sskey" || logVPath == "logv" {
|
|
_ = runCommand(goPath, "get", "-u", "github.com/ssgo/tool")
|
|
if sskeyPath == "sskey" {
|
|
_ = runCommand(goPath, "install", "github.com/ssgo/tool/sskey")
|
|
}
|
|
if logVPath == "logv" {
|
|
_ = runCommand(goPath, "install", "github.com/ssgo/tool/logv")
|
|
}
|
|
}
|
|
return sskeyPath, logVPath
|
|
}
|
|
|
|
func parseRepo(url string) (apiUrl, owner, repo string) {
|
|
name := ""
|
|
if strings.HasPrefix(url, "github.com/") {
|
|
apiUrl = "https://api.github.com/"
|
|
name = url[11:]
|
|
} else if strings.HasPrefix(url, "gitee.com/") {
|
|
apiUrl = "https://gitee.com/api/v5/"
|
|
name = url[10:]
|
|
} else if strings.HasPrefix(url, "apigo.cc/") {
|
|
apiUrl = "https://apigo.cc/api/v1/"
|
|
name = url[16:]
|
|
} else {
|
|
apiUrl = "https://apigo.cc/api/v1/"
|
|
if strings.ContainsRune(url, '/') {
|
|
name = url
|
|
} else {
|
|
name = "apigo/" + url
|
|
}
|
|
}
|
|
|
|
if !strings.ContainsRune(name, '/') {
|
|
name = name + "/" + name
|
|
}
|
|
|
|
a := strings.Split(name, "/")
|
|
owner = a[0]
|
|
repo = a[1]
|
|
|
|
return
|
|
}
|
|
|
|
type Tag struct {
|
|
Name string
|
|
Commit struct {
|
|
Sha string
|
|
Url string
|
|
}
|
|
Zipball_url string
|
|
Tarball_url string
|
|
}
|
|
|
|
func fetchRepo(name string) (repoPath string) {
|
|
apiUrl, owner, repo := parseRepo(name)
|
|
tagsUrl := apiUrl + filepath.Join("repos", owner, repo, "tags")
|
|
tags := make([]Tag, 0)
|
|
fmt.Println("try to fetch tags", tagsUrl)
|
|
r := hc.Get(tagsUrl)
|
|
if r.Error == nil {
|
|
r.To(&tags)
|
|
} else if apiUrl != "https://api.github.com/" {
|
|
tagsUrl = "https://api.github.com/" + filepath.Join("repos", owner, repo, "tags")
|
|
fmt.Println("try to fetch tags", tagsUrl)
|
|
r = hc.Get(tagsUrl)
|
|
if r.Error == nil {
|
|
r.To(&tags)
|
|
} else {
|
|
fmt.Println("fetch tags error", r.Error)
|
|
}
|
|
}
|
|
if len(tags) > 0 {
|
|
lastVersion := tags[0].Name
|
|
zipUrl := tags[0].Zipball_url
|
|
tagPath := filepath.Join(cachePath, owner, repo, lastVersion)
|
|
if !u.FileExists(tagPath) {
|
|
zipFile := filepath.Join(cachePath, owner, repo, lastVersion+".zip")
|
|
fmt.Println("Download file ", zipUrl)
|
|
_, err := hc.Download(zipFile, zipUrl, func(start, end int64, ok bool, finished, total int64) {
|
|
fmt.Printf(" %.2f%%", float32(finished)/float32(total)*100)
|
|
})
|
|
fmt.Println()
|
|
if err != nil {
|
|
fmt.Println(u.BRed("download file failed"))
|
|
return
|
|
}
|
|
if u.FileExists(zipFile) {
|
|
if reader, err := zip.OpenReader(zipFile); err == nil {
|
|
for _, f := range reader.File {
|
|
toFile := filepath.Join(tagPath, f.Name)
|
|
if f.FileInfo().IsDir() {
|
|
//fmt.Println("extract dir", f.Name, toFile)
|
|
os.MkdirAll(toFile, 0755)
|
|
} else {
|
|
//fmt.Println("extract file", f.Name, toFile)
|
|
u.CheckPath(toFile)
|
|
if fp, err := os.OpenFile(toFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644); err == nil {
|
|
if rd, err := f.Open(); err == nil {
|
|
if _, err := io.Copy(fp, rd); err != nil {
|
|
fmt.Println(u.BRed("write file failed"), toFile, err.Error())
|
|
} else {
|
|
//fmt.Println(u.BGreen("write file success"), toFile)
|
|
}
|
|
_ = rd.Close()
|
|
} else {
|
|
fmt.Println(u.BRed("open file in zip failed"), f.Name, err.Error())
|
|
}
|
|
_ = fp.Close()
|
|
} else {
|
|
fmt.Println(u.BRed("open dst file failed"), toFile, err.Error())
|
|
}
|
|
}
|
|
}
|
|
_ = reader.Close()
|
|
} else {
|
|
fmt.Println(u.BRed("open zip file failed"), zipFile, err.Error())
|
|
}
|
|
_ = os.Remove(zipFile)
|
|
}
|
|
}
|
|
return filepath.Join(tagPath, repo)
|
|
} else {
|
|
fmt.Println(u.BRed("no repo found for"), name)
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func getConfig() *Config {
|
|
conf := &Config{
|
|
Name: "",
|
|
Version: "v0.0.1",
|
|
Main: "main.js",
|
|
Module: map[string]string{},
|
|
ModuleAlias: map[string]string{},
|
|
ExtraImport: map[string]string{},
|
|
CacheFile: []string{},
|
|
BuildFrom: "golang",
|
|
}
|
|
u.LoadYaml("apigo.yml", conf)
|
|
|
|
if conf.Name == "" {
|
|
if pathname, err := os.Getwd(); err != nil {
|
|
conf.Name = "apigo"
|
|
} else {
|
|
conf.Name = filepath.Base(pathname)
|
|
}
|
|
}
|
|
|
|
if conf.Target == nil {
|
|
conf.Target = map[string]string{
|
|
"darwin": "amd64 arm64",
|
|
"linux": "amd64 arm64",
|
|
"windows": "amd64",
|
|
}
|
|
}
|
|
|
|
if conf.Module == nil {
|
|
conf.Module = map[string]string{}
|
|
}
|
|
|
|
if conf.ModuleAlias == nil {
|
|
conf.ModuleAlias = map[string]string{}
|
|
}
|
|
|
|
if conf.ExtraImport == nil {
|
|
conf.ExtraImport = map[string]string{}
|
|
}
|
|
|
|
if conf.CacheFile == nil {
|
|
conf.CacheFile = []string{}
|
|
}
|
|
|
|
for pkgName, pkgVersion := range conf.Module {
|
|
if pkgVersion == "" {
|
|
conf.Module[pkgName] = "latest"
|
|
}
|
|
|
|
aliasName := pkgName
|
|
if strings.HasPrefix(aliasName, "apigo.cc/gojs/") {
|
|
aliasName = aliasName[14:]
|
|
} else if strings.HasPrefix(aliasName, "apigo.cc/") {
|
|
aliasName = aliasName[9:]
|
|
}
|
|
if aliasName != pkgName && conf.ModuleAlias[aliasName] == "" {
|
|
conf.ModuleAlias[aliasName] = pkgName
|
|
}
|
|
}
|
|
|
|
for pkgName, pkgVersion := range conf.ExtraImport {
|
|
if pkgVersion == "" {
|
|
conf.ExtraImport[pkgName] = "latest"
|
|
}
|
|
}
|
|
return conf
|
|
}
|
|
|
|
func initProject(args []string) bool {
|
|
if u.FileExists("main.go") || u.FileExists("go.mod") {
|
|
fmt.Println("main.go or go.mod is exists, are you sure to overwrite it? (y/n)")
|
|
sure := ""
|
|
_, _ = fmt.Scanln(&sure)
|
|
if sure != "y" && sure != "Y" {
|
|
return false
|
|
}
|
|
}
|
|
|
|
if !u.FileExists("apigo.yml") {
|
|
refProjName := "default"
|
|
if len(args) > 0 {
|
|
refProjName = args[0]
|
|
}
|
|
repoPath := fetchRepo(refProjName)
|
|
if u.FileExists(repoPath) {
|
|
if err := u.CopyFile(repoPath, "."); err == nil {
|
|
fmt.Println(u.Green("copy project files success"), repoPath)
|
|
} else {
|
|
fmt.Println(u.BRed("copy project files failed"), repoPath, err.Error())
|
|
return false
|
|
}
|
|
} else {
|
|
fmt.Println(u.BRed("fetch repo failed"), repoPath)
|
|
return false
|
|
}
|
|
}
|
|
|
|
conf := getConfig()
|
|
writeFile("main.go", mainCodeTPL, conf)
|
|
|
|
if !u.FileExists("go.mod") {
|
|
_ = runCommand(goPath, "mod", "init", conf.Name)
|
|
_ = runCommand(goPath, "mod", "edit", "-go=1.18")
|
|
}
|
|
|
|
for pkgName, pkgVersion := range conf.Module {
|
|
_ = runCommand(goPath, "get", pkgName+"@"+pkgVersion)
|
|
}
|
|
|
|
for pkgName, pkgVersion := range conf.ExtraImport {
|
|
_ = runCommand(goPath, "get", pkgName+"@"+pkgVersion)
|
|
}
|
|
|
|
if !u.FileExists(".gitignore") {
|
|
writeFile(".gitignore", gitignoreTPL, nil)
|
|
}
|
|
_ = runCommand(goPath, "mod", "tidy")
|
|
findTool()
|
|
fmt.Println(u.BGreen("new project " + conf.Name + " created"))
|
|
fmt.Println(u.Cyan("run \"ag build\" to use it"))
|
|
fmt.Println(u.Cyan("run \"ag test\" to test"))
|
|
fmt.Println(u.Cyan("run \"ag build all\" to publish"))
|
|
return true
|
|
}
|
|
|
|
func packageProject(args []string) bool {
|
|
return buildProjectWithOption(args, true)
|
|
}
|
|
|
|
func buildProject(args []string) bool {
|
|
return buildProjectWithOption(args, false)
|
|
}
|
|
|
|
func buildProjectWithOption(args []string, isPack bool) bool {
|
|
conf := getConfig()
|
|
targets := make([][]string, 0)
|
|
if len(args) > 0 {
|
|
if args[0] == "all" {
|
|
for k, v := range conf.Target {
|
|
for _, arch := range strings.Split(v, " ") {
|
|
targets = append(targets, []string{k, arch})
|
|
}
|
|
}
|
|
} else {
|
|
if len(args) > 1 {
|
|
targets = append(targets, []string{args[0], args[1]})
|
|
} else {
|
|
targets = append(targets, []string{args[0], runtime.GOARCH})
|
|
}
|
|
}
|
|
} else {
|
|
targets = append(targets, []string{runtime.GOOS, runtime.GOARCH})
|
|
}
|
|
|
|
output := "build"
|
|
action := "build"
|
|
if isPack {
|
|
output = "release"
|
|
action = "pack"
|
|
cacheFiles := conf.CacheFile
|
|
cachedFiles := make([]string, 0)
|
|
if conf.Main != "" {
|
|
cacheFiles = append([]string{conf.Main}, cacheFiles...)
|
|
}
|
|
for _, file := range cacheFiles {
|
|
if cache := u.LoadFileToB64(file); cache != nil {
|
|
cachedFiles = append(cachedFiles, strings.ReplaceAll(u.Json(cache), `"`, `\"`))
|
|
}
|
|
}
|
|
if len(cachedFiles) > 0 {
|
|
writeFile("cacheFiles.go", cacheFilesTPL, map[string]any{
|
|
"Files": cachedFiles,
|
|
})
|
|
}
|
|
if conf.SSKey != "" {
|
|
homeDir, _ := os.UserHomeDir()
|
|
keyBuf := u.UnBase64(u.ReadFileN(filepath.Join(homeDir, "sskeys", conf.SSKey)))
|
|
if len(keyBuf) == 81 && keyBuf[80] == 217 {
|
|
if sskeyCode, err := sskeylib.MakeCode("go", keyBuf[0:40], keyBuf[40:80]); err == nil {
|
|
u.WriteFile("setSSKey.go", sskeyCode)
|
|
} else {
|
|
fmt.Println(u.Red("failed to make sskey file"), conf.SSKey, err.Error())
|
|
}
|
|
} else {
|
|
fmt.Println(u.Red("no sskey ["+conf.SSKey+"] config found, please run [sskey -c "+conf.SSKey+"] to make"), filepath.Join(homeDir, "sskeys", conf.SSKey))
|
|
}
|
|
}
|
|
defer func() {
|
|
os.Remove("cacheFiles.go")
|
|
os.Remove("setSSKey.go")
|
|
}()
|
|
}
|
|
|
|
_ = runCommand(goPath, "mod", "tidy")
|
|
|
|
cgoEnable := conf.CgoEnable
|
|
// 未指定时检测是否需要启用CGO
|
|
if !cgoEnable {
|
|
if lines, err := u.RunCommand(goPath, "list", "-f", "{{.ImportPath}}: {{.CgoFiles}}", "all"); err == nil {
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || strings.HasSuffix(line, ": []") || strings.HasPrefix(line, "github.com/shirou/gopsutil/") || strings.HasPrefix(line, "net:") {
|
|
continue
|
|
}
|
|
cgoEnable = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检测是否有docker环境
|
|
upxBin := ""
|
|
_upxArgs := []string{}
|
|
dockerBin := ""
|
|
if dockerBinPath, err := exec.LookPath("docker"); err == nil {
|
|
dockerBin = dockerBinPath
|
|
}
|
|
|
|
if isPack {
|
|
if upxBinPath, err := exec.LookPath("upx"); err == nil {
|
|
upxBin = upxBinPath
|
|
} else if dockerBin != "" {
|
|
upxBin = dockerBin
|
|
_upxArgs = append(_upxArgs, "run", "--rm", "-v", ".:/build", "-w", "/build", "apigocc/upx")
|
|
}
|
|
|
|
if upxBin == "" {
|
|
if runtime.GOOS == "darwin" {
|
|
fmt.Println(u.Red("upx not found, skip compress"))
|
|
fmt.Println(u.Red("you can install upx by \"brew install upx\""))
|
|
} else if runtime.GOOS == "linux" {
|
|
fmt.Println(u.Red("upx not found, skip compress"))
|
|
fmt.Println(u.Red("you can install upx by \"apt install upx\" or \"yum install upx\" or https://upx.github.io/"))
|
|
} else {
|
|
fmt.Println(u.Red("upx not found, skip compress"))
|
|
fmt.Println(u.Red("you can install upx https://upx.github.io/"))
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, target := range targets {
|
|
buildOS := target[0]
|
|
buildArch := target[1]
|
|
name := path.Base(conf.Name)
|
|
ext := ""
|
|
if buildOS == "mac" {
|
|
buildOS = "darwin"
|
|
}
|
|
if buildOS == "windows" {
|
|
ext = ".exe"
|
|
}
|
|
buildPath := filepath.Join(output, fmt.Sprintf("%s_%s_%s%s", name, buildOS, buildArch, ext))
|
|
|
|
ldFlags := "-s -w"
|
|
if conf.BuildLdFlags != nil && conf.BuildLdFlags[buildOS] != "" {
|
|
ldFlags = conf.BuildLdFlags[buildOS]
|
|
}
|
|
|
|
env := append(conf.BuildEnv[buildOS], "GOOS="+buildOS, "GOARCH="+buildArch)
|
|
buildArgs := []string{}
|
|
buildBin := goPath
|
|
workOS := runtime.GOOS
|
|
useDocker := false
|
|
if cgoEnable {
|
|
env = append(env, "CGO_ENABLED=1")
|
|
|
|
if cgoEnable && dockerBin != "" && workOS != buildOS {
|
|
buildBin = dockerBin
|
|
workOS = "linux"
|
|
homePath, _ := os.UserHomeDir()
|
|
buildArgs = append(buildArgs, "run", "--rm", "-v", filepath.Join(homePath, "go")+":/go", "-v", ".:/build", "-w", "/build")
|
|
useDocker = true
|
|
}
|
|
|
|
// 检查 CC 工具
|
|
ccBin := ""
|
|
cxxBin := ""
|
|
installCmd := ""
|
|
switch buildOS {
|
|
case "windows":
|
|
ldFlags += " -H windowsgui"
|
|
case "linux":
|
|
// env = append(env, "CGO_LDFLAGS=-static")
|
|
}
|
|
if workOS != buildOS {
|
|
switch workOS + buildOS {
|
|
case "darwinlinux":
|
|
ccBin = "x86_64-linux-musl-gcc"
|
|
cxxBin = "x86_64-linux-musl-g++"
|
|
installCmd = "brew install FiloSottile/musl-cross/musl-cross"
|
|
case "darwinwindows":
|
|
ccBin = "x86_64-w64-mingw32-gcc"
|
|
cxxBin = "x86_64-w64-mingw32-g++"
|
|
installCmd = "brew install mingw-w64"
|
|
case "linuxdarwin":
|
|
ccBin = "o64-clang"
|
|
cxxBin = "o64-clang++"
|
|
installCmd = "apt-get install -y cmake clang bison flex"
|
|
case "linuxwindows":
|
|
ccBin = "x86_64-w64-mingw32-gcc"
|
|
cxxBin = "x86_64-w64-mingw32-g++"
|
|
installCmd = "apt-get install -y gcc-mingw-w64"
|
|
case "windowswindows":
|
|
ccBin = "gcc.exe"
|
|
cxxBin = "g++.exe"
|
|
installCmd = "https://jmeubank.github.io/tdm-gcc/"
|
|
case "windowslinux", "windowsdarwin":
|
|
fmt.Println(u.Red("not support cross compile on windws when CGO_ENABLED=1, you can use WSL"))
|
|
return false
|
|
default:
|
|
fmt.Println(u.Red("not support cross compile on " + buildOS + " " + buildArch + " when CGO_ENABLED=1"))
|
|
return false
|
|
}
|
|
} else {
|
|
if buildOS == "linux" && buildArch == "arm64" {
|
|
ccBin = "aarch64-linux-gnu-gcc"
|
|
cxxBin = "aarch64-linux-gnu-g++"
|
|
installCmd = "apt-get install -y gcc-aarch64-linux-gnu"
|
|
}
|
|
}
|
|
if ccBin != "" && cxxBin != "" {
|
|
if !useDocker {
|
|
ccBinPath, err1 := exec.LookPath(ccBin)
|
|
cxxBinPath, err2 := exec.LookPath(cxxBin)
|
|
if err1 == nil && err2 == nil {
|
|
env = append(env, "CC="+ccBinPath, "CXX="+cxxBinPath)
|
|
} else {
|
|
fmt.Println(u.Red("not found " + ccBin + " or " + cxxBin))
|
|
fmt.Println("you can install by", u.BCyan(installCmd))
|
|
return false
|
|
}
|
|
} else {
|
|
env = append(env, "CC="+ccBin, "CXX="+cxxBin)
|
|
}
|
|
}
|
|
}
|
|
if useDocker {
|
|
for _, v := range env {
|
|
if strings.Contains(v, "%cd%") {
|
|
v = strings.ReplaceAll(v, "%cd%", "/build/")
|
|
}
|
|
buildArgs = append(buildArgs, "-e", v)
|
|
}
|
|
env = nil
|
|
// buildArgs = append(buildArgs, "apigocc/gobuild", "go")
|
|
// buildArgs = append(buildArgs, "gob:v1", "bash", "-c")
|
|
buildArgs = append(buildArgs, conf.BuildFrom, "bash", "-c")
|
|
buildArgs = append(buildArgs, fmt.Sprintln("go", "build", "-o", buildPath, "-ldflags", "'"+ldFlags+"'", "."))
|
|
} else {
|
|
curPath, _ := os.Getwd()
|
|
for i, v := range env {
|
|
if strings.Contains(v, "%cd%") {
|
|
env[i] = strings.ReplaceAll(v, "%cd%", filepath.Join(curPath, ""))
|
|
}
|
|
buildArgs = append(buildArgs, "-e", v)
|
|
}
|
|
buildArgs = append(buildArgs, "build", "-o", buildPath, "-ldflags", ldFlags, ".")
|
|
}
|
|
// err := runCommandWithEnv("go", env, "build", "-o", buildPath, "-ldflags", ldFlags, ".")
|
|
err := runCommandWithEnv(buildBin, env, buildArgs...)
|
|
if isPack && upxBin != "" && buildOS != "darwin" {
|
|
upxArgs := append(_upxArgs, buildPath)
|
|
err = runCommand(upxBin, upxArgs...)
|
|
// if upxBin, err := exec.LookPath("upx"); err == nil {
|
|
// if buildOS == "darwin" {
|
|
// fmt.Println(u.Yellow("upx not support macos, skip compress"))
|
|
// } else {
|
|
// if err = runCommand(upxBin, buildPath); err == nil {
|
|
// fmt.Println(u.Green("compress file success"), buildPath)
|
|
// } else {
|
|
// fmt.Println(u.BRed("compress file failed"), buildPath, err.Error())
|
|
// }
|
|
// }
|
|
// } else {
|
|
// if workOS == "darwin" {
|
|
// fmt.Println(u.Red("upx not found, skip compress"))
|
|
// fmt.Println(u.Red("you can install upx by \"brew install upx\""))
|
|
// } else if workOS == "linux" {
|
|
// fmt.Println(u.Red("upx not found, skip compress"))
|
|
// fmt.Println(u.Red("you can install upx by \"apt install upx\" or \"yum install upx\" or https://upx.github.io/"))
|
|
// } else {
|
|
// fmt.Println(u.Red("upx not found, skip compress"))
|
|
// fmt.Println(u.Red("you can install upx https://upx.github.io/"))
|
|
// }
|
|
// }
|
|
}
|
|
if err != nil {
|
|
fmt.Println(u.BRed(action+" failed"), buildOS, buildArch, err.Error())
|
|
} else {
|
|
fmt.Println(u.Green(action+" for"), buildOS, buildArch, "to", buildPath)
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// func CopyFile(from, to string) error {
|
|
// fromStat, _ := os.Stat(from)
|
|
// if fromStat.IsDir() {
|
|
// // copy dir
|
|
// for _, f := range u.ReadDirN(from) {
|
|
// err := CopyFile(filepath.Join(from, f.Name), filepath.Join(to, f.Name))
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// }
|
|
// return nil
|
|
// } else {
|
|
// // copy file
|
|
// toStat, err := os.Stat(to)
|
|
// if err == nil && toStat.IsDir() {
|
|
// to = filepath.Join(to, filepath.Base(from))
|
|
// }
|
|
// u.CheckPath(to)
|
|
// if writer, err := os.OpenFile(to, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err == nil {
|
|
// defer writer.Close()
|
|
// if reader, err := os.OpenFile(from, os.O_RDONLY, 0644); err == nil {
|
|
// defer reader.Close()
|
|
// _, err = io.Copy(writer, reader)
|
|
// return err
|
|
// } else {
|
|
// return err
|
|
// }
|
|
// } else {
|
|
// return err
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
func runProject(args []string) bool {
|
|
_, logVPath := findTool()
|
|
cmdArgs := append([]string{}, args...)
|
|
conf := getConfig()
|
|
name := path.Base(conf.Name)
|
|
binPath := filepath.Join("build", fmt.Sprintf("%s_%s_%s", name, runtime.GOOS, runtime.GOARCH))
|
|
if !u.FileExists(binPath) {
|
|
buildProject([]string{})
|
|
}
|
|
return nil == runCommandPipe(logVPath, binPath, cmdArgs...)
|
|
}
|
|
|
|
func buildRunProject(args []string) bool {
|
|
buildProject(args)
|
|
return runProject(args)
|
|
}
|
|
|
|
func testProject(args []string) bool {
|
|
args = append([]string{"test"}, args...)
|
|
return runProject(args)
|
|
}
|
|
|
|
func buildTestProject(args []string) bool {
|
|
buildProject(args)
|
|
args = append([]string{"test"}, args...)
|
|
return runProject(args)
|
|
}
|
|
|
|
func watchProject(args []string) bool {
|
|
args = append([]string{"-w"}, args...)
|
|
return runProject(args)
|
|
}
|
|
|
|
func buildWatchProject(args []string) bool {
|
|
buildProject(args)
|
|
args = append([]string{"-w"}, args...)
|
|
return runProject(args)
|
|
}
|
|
|
|
func exportForDev(args []string) bool {
|
|
return runProject([]string{"-export"})
|
|
}
|
|
|
|
// func devProject(args []string) bool {
|
|
// return _runProject(args, true)
|
|
// }
|
|
|
|
// func testProject(args []string) bool {
|
|
// return _testProject(args, false)
|
|
// }
|
|
|
|
// func devTestProject(args []string) bool {
|
|
// return _testProject(args, true)
|
|
// }
|
|
|
|
// func _testProject(args []string, isWatch bool) bool {
|
|
// if u.FileExists("tests") {
|
|
// if u.FileExists(filepath.Join("tests", "go.mod")) {
|
|
// _ = os.Chdir("tests")
|
|
// if isWatch {
|
|
// args = append(args, "-p", "..")
|
|
// }
|
|
// isRun := false
|
|
// for _, f := range u.ReadDirN(".") {
|
|
// if strings.HasSuffix(f.Name, ".go") {
|
|
// goStr := u.ReadFileN(f.FullName)
|
|
// if strings.Contains(goStr, "package main") && !strings.Contains(goStr, "package main_test") {
|
|
// isRun = true
|
|
// }
|
|
// break
|
|
// }
|
|
// }
|
|
// if !isRun {
|
|
// args = append(args, "test", "-v", ".")
|
|
// } else {
|
|
// args = append(args, "run", ".")
|
|
// }
|
|
// } else {
|
|
// args = append(args, "test", "-v", "tests")
|
|
// }
|
|
// } else {
|
|
// args = append(args, "test", "-v", ".")
|
|
// }
|
|
// _ = runCommand(goPath, "mod", "tidy")
|
|
// sskeyPath, logVPath := findTool()
|
|
// if isWatch {
|
|
// args2 := append([]string{"-pt", ".go,.js,.yml"}, args...)
|
|
// _ = runCommandPipeWithEnv(logVPath, sskeyPath, goRunEnv, args2...)
|
|
// } else {
|
|
// _ = runCommandPipeWithEnv(logVPath, goPath, goRunEnv, args...)
|
|
// }
|
|
// return true
|
|
// }
|
|
|
|
// var pkgMatcher = regexp.MustCompile(`(?im)^\s*(import)?\s*_\s+"([\w-_/.]+)"`)
|
|
// var importMatcher = regexp.MustCompile(`(?im)^\s*import\s+([\w{}, ]+)\s+from\s+['"]([\w./\\\- ]+)['"]`)
|
|
|
|
// func makeJsImports(path string, jsImports *[]string, pkgName string) {
|
|
// if u.FileExists(filepath.Join(path, "go.mod")) {
|
|
// if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(path, "go.mod"))); m != nil {
|
|
// if pkgName != m[1] {
|
|
// return
|
|
// }
|
|
// }
|
|
// }
|
|
// for _, f := range u.ReadDirN(path) {
|
|
// filename := f.Name
|
|
// fullName := filepath.Join(path, filename)
|
|
// if !strings.HasPrefix(filename, ".") && !strings.HasPrefix(filename, "_") && filename != "_makePluginCode" && filename != "node_modules" && filename != "www" && filename != "src" {
|
|
// if f.IsDir {
|
|
// makeJsImports(fullName, jsImports, pkgName)
|
|
// } else if strings.HasSuffix(filename, ".js") {
|
|
// if code, err := u.ReadFile(fullName); err == nil {
|
|
// for _, m := range importMatcher.FindAllStringSubmatch(code, 1024) {
|
|
// if !strings.HasPrefix(m[2], ".") && !strings.HasPrefix(m[2], "_") && m[2] != "current-plugin" && m[2] != "console" && m[2] != "logger" {
|
|
// checkFile := filepath.Join(path, m[2])
|
|
// checkFileInfo := u.GetFileInfo(checkFile)
|
|
// if checkFileInfo != nil && checkFileInfo.IsDir {
|
|
// checkFile = filepath.Join(checkFile, "index.js")
|
|
// } else if !strings.HasSuffix(checkFile, ".js") {
|
|
// checkFile += ".js"
|
|
// }
|
|
// //fmt.Println(u.BMagenta(checkFile), u.FileExists(checkFile))
|
|
// if !u.FileExists(checkFile) {
|
|
// *jsImports = u.AppendUniqueString(*jsImports, m[2])
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// func makePluginCodeImports(from string, imports *[]string, parentModuleName string) {
|
|
// jsImports := make([]string, 0)
|
|
// currentParentModuleName := parentModuleName
|
|
// if u.FileExists(filepath.Join(from, "go.mod")) {
|
|
// pkgName := ""
|
|
// if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(from, "go.mod"))); m != nil {
|
|
// pkgName = m[1]
|
|
// }
|
|
// makeJsImports(from, &jsImports, pkgName)
|
|
// parentModuleName = pkgName
|
|
// }
|
|
|
|
// currentPkgName := ""
|
|
// for _, f := range u.ReadDirN(from) {
|
|
// if f.IsDir {
|
|
// if !strings.HasPrefix(f.Name, ".") {
|
|
// makePluginCodeImports(filepath.Join(from, f.Name), imports, parentModuleName)
|
|
// }
|
|
// } else {
|
|
// if strings.HasSuffix(f.Name, ".go") && !strings.HasPrefix(f.Name, ".") && f.Name != "makePluginCode.go" {
|
|
// if code, err := u.ReadFile(filepath.Join(from, f.Name)); err == nil {
|
|
// if m := pkgNameMatcher.FindStringSubmatch(code); m != nil {
|
|
// if currentPkgName+"_test" != m[1] {
|
|
// currentPkgName = m[1]
|
|
// }
|
|
// }
|
|
// for _, m := range pkgMatcher.FindAllStringSubmatch(code, 1024) {
|
|
// if m[2] != "current-plugin" && !strings.HasPrefix(f.Name, "jsImports") {
|
|
// *imports = u.AppendUniqueString(*imports, m[2])
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// if currentPkgName != "" && u.FileExists(filepath.Join(from, "go.mod")) {
|
|
// pkgList := make([]string, 0)
|
|
// for _, plgPkg := range jsImports {
|
|
// if plgPkg != currentParentModuleName && !u.StringIn(*imports, plgPkg) {
|
|
// pkgList = append(pkgList, plgPkg)
|
|
// *imports = u.AppendUniqueString(*imports, plgPkg)
|
|
// }
|
|
// }
|
|
// if len(pkgList) > 0 {
|
|
// _ = u.WriteFile(filepath.Join(from, u.StringIf(strings.HasSuffix(currentPkgName, "_test"), "jsImports_test.go", "jsImports.go")), `package `+currentPkgName+`
|
|
// import _ "`+strings.Join(pkgList, "\"\nimport _ \"")+`"
|
|
// `)
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
func writeFile(filename string, fileContent string, data any) {
|
|
if fp, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600); err == nil {
|
|
tpl, _ := template.New(filename).Parse(fileContent)
|
|
if err = tpl.Execute(fp, data); err != nil {
|
|
fmt.Println(u.Red(err.Error()))
|
|
}
|
|
_ = fp.Close()
|
|
} else {
|
|
fmt.Println(u.Red(err.Error()))
|
|
}
|
|
}
|
|
|
|
// var replaceMatcher = regexp.MustCompile(`([a-zA-Z0-9._\-/]+)\s+(v[0-9.]+)\s+=>\s+([a-zA-Z0-9._\-/\\]+)`)
|
|
// var modNameMatcher = regexp.MustCompile(`(?m)^module\s+([a-zA-Z0-9._\-/]+)$`)
|
|
// var pkgNameMatcher = regexp.MustCompile(`(?m)^package\s+([a-zA-Z0-9._\-/]+)$`)
|
|
|
|
func tidyProject(args []string) bool {
|
|
_ = runCommand(goPath, "mod", "tidy")
|
|
return true
|
|
}
|
|
|
|
func checkProject(args []string) bool {
|
|
if lines, err := u.RunCommand(goPath, "list", "-m", "-u", "all"); err == nil {
|
|
moduleList := map[string]string{}
|
|
indirectModuleList := map[string]string{}
|
|
for _, line := range u.ReadFileLinesN("go.mod") {
|
|
if strings.Contains(line, " v") {
|
|
a := strings.Split(strings.TrimSpace(line), " ")
|
|
if strings.Contains(line, "// indirect") {
|
|
indirectModuleList[a[0]] = a[1]
|
|
} else {
|
|
moduleList[a[0]] = a[1]
|
|
}
|
|
}
|
|
}
|
|
for _, line := range lines {
|
|
a := strings.Split(line, " ")
|
|
if len(a) > 2 {
|
|
if oldVer, ok := moduleList[a[0]]; ok {
|
|
fmt.Println(u.BCyan(a[0]), u.BMagenta(oldVer), u.BGreen(a[2]))
|
|
} else if _, ok := indirectModuleList[a[0]]; ok {
|
|
fmt.Println(u.Cyan(a[0]), u.BMagenta(indirectModuleList[a[0]]), u.BGreen(a[2]))
|
|
} else {
|
|
fmt.Println(a[0], u.BMagenta(a[1]), u.BGreen(a[2]))
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
fmt.Println(strings.Join(lines, "\n"))
|
|
fmt.Println(u.Red(err.Error()))
|
|
}
|
|
return true
|
|
}
|
|
|
|
func runCommand(name string, args ...string) error {
|
|
return runCommandWithEnv(name, nil, args...)
|
|
}
|
|
|
|
func printCmd(name string, env []string, args []string) {
|
|
fmt.Print(u.BMagenta(name))
|
|
for _, arg := range args {
|
|
fmt.Print(" ", u.BCyan(arg))
|
|
}
|
|
for _, v := range env {
|
|
fmt.Print(" ", u.BYellow(v))
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
func runCommandWithEnv(name string, env []string, args ...string) error {
|
|
// pathname, _ := os.Getwd()
|
|
// fmt.Println(u.BMagenta(pathname), u.BCyan(name), u.Cyan(strings.Join(args, " ")))
|
|
printCmd(name, env, args)
|
|
cmd := exec.Command(name, args...)
|
|
if env != nil {
|
|
// if runtime.GOOS == "windows" {
|
|
// env = append(env, "GOTMPDIR="+pathname, "GOWORK="+pathname)
|
|
// }
|
|
cmd.Env = append(os.Environ(), env...)
|
|
}
|
|
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
fmt.Println(u.Red(err.Error()))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func runCommandPipe(pipeCommandName, commandName string, args ...string) error {
|
|
return runCommandPipeWithEnv(pipeCommandName, commandName, nil, args...)
|
|
}
|
|
|
|
func runCommandPipeWithEnv(pipeCommandName, commandName string, env []string, args ...string) error {
|
|
printCmd(commandName, env, args)
|
|
cmd1 := exec.Command(commandName, args...)
|
|
cmd2 := exec.Command(pipeCommandName)
|
|
if env != nil {
|
|
// if runtime.GOOS == "windows" {
|
|
// env = append(env, "GOTMPDIR="+pathname, "GOWORK="+pathname)
|
|
// }
|
|
cmd1.Env = append(os.Environ(), env...)
|
|
cmd2.Env = append(os.Environ(), env...)
|
|
}
|
|
|
|
r, w := io.Pipe()
|
|
wClosed := false
|
|
defer func() {
|
|
if !wClosed {
|
|
w.Close()
|
|
}
|
|
}()
|
|
cmd1.Stdin = os.Stdin
|
|
cmd1.Stdout = w
|
|
cmd1.Stderr = w
|
|
cmd2.Stdin = r
|
|
cmd2.Stdout = os.Stdout
|
|
cmd2.Stderr = os.Stderr
|
|
var err error
|
|
if err = cmd2.Start(); err == nil {
|
|
if err = cmd1.Start(); err == nil {
|
|
if err = cmd1.Wait(); err == nil {
|
|
w.Close()
|
|
wClosed = true
|
|
// 等待第二个命令完成
|
|
if err = cmd2.Wait(); err == nil {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fmt.Println(u.Red(err.Error()))
|
|
return err
|
|
}
|
|
|
|
func main() {
|
|
var err error
|
|
if goPath, err = exec.LookPath("go"); err != nil || goPath == "" {
|
|
fmt.Println(u.Red("Please install Go SDK first"))
|
|
fmt.Println(u.Cyan("https://go.dev/"))
|
|
return
|
|
}
|
|
|
|
if len(os.Args) > 1 {
|
|
cmd1 := os.Args[1]
|
|
cmd2 := cmd1
|
|
if len(os.Args) > 2 {
|
|
cmd2 += " " + os.Args[2]
|
|
}
|
|
for i := len(commands) - 1; i >= 0; i-- {
|
|
cmdInfo := commands[i]
|
|
if len(os.Args) > 2 && (cmd2 == cmdInfo.Name || cmd2 == cmdInfo.ShortName) {
|
|
cmdInfo.Func(os.Args[3:])
|
|
return
|
|
} else if cmd1 == cmdInfo.Name || cmd1 == cmdInfo.ShortName {
|
|
cmdInfo.Func(os.Args[2:])
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Println("tools for apigo.cc/apigo", version)
|
|
fmt.Println("go sdk", goPath)
|
|
fmt.Println()
|
|
fmt.Println("Usage:")
|
|
fmt.Println(" ", u.Cyan("ag [command] [...]"))
|
|
fmt.Println(" ", u.Magenta("ag [short command] [...]"))
|
|
fmt.Println()
|
|
fmt.Println("Commands:")
|
|
for _, cmdInfo := range commands {
|
|
padStr := ""
|
|
padN := 30 - len(cmdInfo.Name) - len(cmdInfo.ShortName) - len(cmdInfo.Args)
|
|
if padN > 0 {
|
|
padStr = strings.Repeat(" ", padN)
|
|
}
|
|
fmt.Println(" ", u.Cyan(cmdInfo.Name), u.Dim("[")+u.Magenta(cmdInfo.ShortName)+u.Dim("]"), cmdInfo.Args, padStr, cmdInfo.Comment)
|
|
}
|
|
fmt.Println()
|
|
fmt.Println("Examples:")
|
|
fmt.Println(" ", u.Magenta("ag init"), " init a new default project")
|
|
fmt.Println(" ", u.Magenta("ag init service"), " init a new service project")
|
|
fmt.Println(" ", u.Magenta("ag init client"), " init a new client project")
|
|
fmt.Println(" ", u.Magenta("ag init all"), " init a new project use all modules by apigo included")
|
|
fmt.Println(" ", u.Magenta("ag build"), " build project")
|
|
fmt.Println(" ", u.Magenta("ag build all"), " build project for all platforms")
|
|
fmt.Println(" ", u.Magenta("ag run"), " run project")
|
|
fmt.Println(" ", u.Magenta("ag test"), " test project")
|
|
fmt.Println()
|
|
}
|