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  }