github.com/kcmvp/gob@v1.0.17/cmd/gbc/artifact/project.go (about) 1 package artifact 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "github.com/fatih/color" //nolint 8 "github.com/kcmvp/gob/utils" 9 "github.com/samber/lo" //nolint 10 "github.com/spf13/viper" //nolint 11 "io/fs" 12 "log" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "regexp" 17 "runtime" 18 "strings" 19 "sync" 20 ) 21 22 const ( 23 pluginCfgKey = "plugins" 24 defaultCfgKey = "_default_" 25 pluginCfgFile = "plugins.yaml" 26 ) 27 28 var ( 29 project Project 30 ) 31 32 type Project struct { 33 root string 34 module string 35 deps []string 36 cfgs sync.Map // store all the configuration 37 } 38 39 func (project *Project) load() *viper.Viper { 40 testEnv, file := utils.TestCaller() 41 key := lo.If(testEnv, file).Else(defaultCfgKey) 42 obj, ok := project.cfgs.Load(key) 43 if ok { 44 return obj.(*viper.Viper) 45 } 46 v := viper.New() 47 path := lo.If(!testEnv, project.Root()).Else(project.Target()) 48 v.SetConfigFile(filepath.Join(path, "gob.yaml")) 49 if err := v.ReadInConfig(); err != nil { 50 var configFileNotFoundError viper.ConfigFileNotFoundError 51 if errors.As(err, &configFileNotFoundError) { 52 log.Fatal(color.RedString("error: can not find configuration gob.yaml")) 53 } 54 } 55 project.cfgs.Store(key, v) 56 return v 57 } 58 59 func (project *Project) mergeConfig(cfg map[string]any) error { 60 viper := project.load() 61 err := viper.MergeConfigMap(cfg) 62 if err != nil { 63 return err 64 } 65 return viper.WriteConfigAs(viper.ConfigFileUsed()) 66 } 67 68 func (project *Project) HookDir() string { 69 if ok, _ := utils.TestCaller(); ok { 70 mock := filepath.Join(CurProject().Target(), ".git", "hooks") 71 if _, err := os.Stat(mock); err != nil { 72 os.MkdirAll(mock, os.ModePerm) //nolint 73 } 74 return mock 75 } 76 return filepath.Join(CurProject().Root(), ".git", "hooks") 77 } 78 79 func init() { 80 cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}_:_{{.Path}}") 81 output, err := cmd.Output() 82 if err != nil || len(string(output)) == 0 { 83 log.Fatal(color.RedString("Error: please execute command in project root directory %s", string(output))) 84 } 85 86 item := strings.Split(strings.TrimSpace(string(output)), "_:_") 87 project = Project{ 88 root: item[0], 89 module: item[1], 90 cfgs: sync.Map{}, 91 } 92 cmd = exec.Command("go", "list", "-f", "{{if not .Standard}}{{.ImportPath}}{{end}}", "-deps", "./...") 93 output, err = cmd.Output() 94 if err != nil { 95 log.Fatal(color.RedString("Error: please execute command in project root directory")) 96 } 97 scanner := bufio.NewScanner(strings.NewReader(string(output))) 98 var deps []string 99 for scanner.Scan() { 100 line := strings.TrimSpace(scanner.Text()) 101 if len(line) > 0 { 102 deps = append(deps, line) 103 } 104 } 105 project.deps = deps 106 } 107 108 // CurProject return Project struct 109 func CurProject() *Project { 110 return &project 111 } 112 113 // Root return root dir of the project 114 func (project *Project) Root() string { 115 return project.root 116 } 117 118 // Module return current project module name 119 func (project *Project) Module() string { 120 return project.module 121 } 122 123 func (project *Project) Target() string { 124 target := filepath.Join(project.Root(), "target") 125 if test, method := utils.TestCaller(); test { 126 target = filepath.Join(target, method) 127 } 128 if _, err := os.Stat(target); err != nil { 129 _ = os.MkdirAll(target, os.ModePerm) 130 } 131 return target 132 } 133 134 // sourceFileInPkg return all go source file in a package 135 func (project *Project) sourceFileInPkg(pkg string) ([]string, error) { 136 _ = os.Chdir(project.Root()) 137 cmd := exec.Command("go", "list", "-f", fmt.Sprintf("{{if eq .Name \"%s\"}}{{.Dir}}{{end}}", pkg), "./...") 138 output, _ := cmd.Output() 139 scanner := bufio.NewScanner(strings.NewReader(string(output))) 140 var dirs []string 141 for scanner.Scan() { 142 line := strings.TrimSpace(scanner.Text()) 143 if len(line) > 0 { 144 dirs = append(dirs, line) 145 } 146 } 147 return dirs, nil 148 } 149 150 func (project *Project) MainFiles() []string { 151 var mainFiles []string 152 dirs, _ := project.sourceFileInPkg("main") 153 re := regexp.MustCompile(`func\s+main\s*\(\s*\)`) 154 lo.ForEach(dirs, func(dir string, _ int) { 155 _ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { 156 if err != nil { 157 return err 158 } 159 if d.IsDir() && dir != path { 160 return filepath.SkipDir 161 } 162 if d.IsDir() || !strings.HasSuffix(d.Name(), ".go") || strings.HasSuffix(d.Name(), "_test.go") { 163 return nil 164 } 165 file, err := os.Open(path) 166 if err != nil { 167 return err 168 } 169 defer file.Close() 170 scanner := bufio.NewScanner(file) 171 for scanner.Scan() { 172 line := strings.TrimSpace(scanner.Text()) 173 if re.MatchString(line) { 174 mainFiles = append(mainFiles, path) 175 return filepath.SkipDir 176 } 177 } 178 return scanner.Err() 179 }) 180 }) 181 return mainFiles 182 } 183 184 func (project *Project) Plugins() []Plugin { 185 viper := project.load() 186 if v := viper.Get(pluginCfgKey); v != nil { 187 plugins := v.(map[string]any) 188 return lo.MapToSlice(plugins, func(key string, _ any) Plugin { 189 var plugin Plugin 190 key = fmt.Sprintf("%s.%s", pluginCfgKey, key) 191 if err := viper.UnmarshalKey(key, &plugin); err != nil { 192 color.Yellow("failed to parse plugin %s: %s", key, err.Error()) 193 } 194 if err := plugin.init(); err != nil { 195 color.Red("failed to init plugin %s: %s", plugin.name, err.Error()) 196 } 197 return plugin 198 }) 199 } else { 200 return []Plugin{} 201 } 202 } 203 204 func (project *Project) Dependencies() []string { 205 return project.deps 206 } 207 208 func (project *Project) InstallDependency(dep string) error { 209 if !lo.Contains(project.deps, dep) { 210 exec.Command("go", "get", "-u", dep).CombinedOutput() //nolint 211 } 212 return nil 213 } 214 215 func (project *Project) InstallPlugin(plugin Plugin) error { 216 var err error 217 if !project.settled(plugin) { 218 values := lo.MapEntries(map[string]string{ 219 "alias": plugin.Alias, 220 "args": plugin.Args, 221 "url": fmt.Sprintf("%s@%s", plugin.Url, plugin.Version()), 222 }, func(key string, value string) (string, any) { 223 return fmt.Sprintf("%s.%s.%s", pluginCfgKey, plugin.Name(), key), value 224 }) 225 if err = project.mergeConfig(values); err != nil { 226 return err 227 } 228 _ = project.load().ReadInConfig() 229 } 230 _, err = plugin.install() 231 return err 232 } 233 234 func (project *Project) settled(plugin Plugin) bool { 235 return project.load().Get(fmt.Sprintf("plugins.%s.url", plugin.name)) != nil 236 } 237 238 func (project *Project) Validate() error { 239 return project.SetupHooks(false) 240 } 241 242 func InGit() bool { 243 _, err := exec.Command("git", "status").CombinedOutput() 244 return err == nil 245 } 246 247 var latestHash = []string{`log`, `-1`, `--abbrev-commit`, `--date=format-local:%Y-%m-%d %H:%M`, `--format=%h(%ad)`} 248 249 func Version() string { 250 version := "unknown" 251 if output, err := exec.Command("git", latestHash...).CombinedOutput(); err == nil { 252 version = strings.Trim(string(output), "\n") 253 } 254 return version 255 } 256 257 func temporaryGoPath() string { 258 dir, _ := os.MkdirTemp("", "gob-build-") 259 return dir 260 } 261 262 func GoPath() string { 263 if ok, method := utils.TestCaller(); ok { 264 dir := filepath.Join(os.TempDir(), method) 265 _ = os.MkdirAll(dir, os.ModePerm) //nolint 266 return dir 267 } 268 return filepath.Join(os.Getenv("GOPATH"), "bin") 269 } 270 271 // Windows return true when current os is Windows 272 func Windows() bool { 273 return runtime.GOOS == "windows" 274 }