github.com/keysonZZZ/kmg@v0.0.0-20151121023212-05317bfd7d39/kmg/SubCommand/goCmd/GoRun.go (about)

     1  package goCmd
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  
     8  	"github.com/bronze1man/kmg/encoding/kmgGob"
     9  	"github.com/bronze1man/kmg/encoding/kmgJson"
    10  	"github.com/bronze1man/kmg/kmgCmd"
    11  	"github.com/bronze1man/kmg/kmgConfig"
    12  	"github.com/bronze1man/kmg/kmgConsole"
    13  	"github.com/bronze1man/kmg/kmgCrypto"
    14  	"github.com/bronze1man/kmg/kmgFile"
    15  	"github.com/bronze1man/kmg/kmgGoSource"
    16  	"github.com/bronze1man/kmg/kmgPlatform"
    17  	//"github.com/bronze1man/kmg/kmgDebug"
    18  	"github.com/OneOfOne/xxhash"
    19  	"github.com/bronze1man/kmg/kmgTask"
    20  	"runtime"
    21  )
    22  
    23  var debug = false
    24  
    25  // go install bug
    26  func GoRunCmd() {
    27  	runtime.GOMAXPROCS(runtime.NumCPU())
    28  
    29  	kmgc, err := kmgConfig.LoadEnvFromWd()
    30  	kmgConsole.ExitOnErr(err)
    31  	goPath := kmgc.GOPATHToString()
    32  
    33  	//假设第一个是文件或者package名称,后面是传给命令行的参数
    34  	if len(os.Args) < 2 {
    35  		kmgConsole.ExitOnErr(fmt.Errorf("you need pass in running filename"))
    36  		return
    37  	}
    38  	pathOrPkg := os.Args[1]
    39  	_, err = os.Stat(pathOrPkg)
    40  	switch {
    41  	case os.IsNotExist(err): //package名称
    42  		goRunPackageName(goPath, pathOrPkg)
    43  		return
    44  	case err != nil: //其他错误
    45  		kmgConsole.ExitOnErr(err)
    46  		return
    47  
    48  	default: //文件或目录
    49  		wd, err := os.Getwd()
    50  		kmgConsole.ExitOnErr(err)
    51  		if wd == filepath.Join(goPath, "src") {
    52  			//用户在src下
    53  			goRunPackageName(goPath, pathOrPkg)
    54  			return
    55  		}
    56  
    57  		//  找出指向的这个文件的所有import的包,全部install一遍,再go run
    58  		//靠谱实现这个东西的复杂度太高,目前已有的方案不能达到目标,暂时先使用go run
    59  		// 如果有需要使用请把这个文件放到package里面,或者运行前删除pkg目录.
    60  		// TODO 速度比较慢.
    61  		//已经证实不行的方案:
    62  		// 1.在临时目录建一个package,并且使用GOPATH指向那个临时目录,缓存会出现问题,并且效果和 go build -i 没有区别
    63  		// 2.使用go build -i 效果和直接go run没有区别(缓存还是会出现问题)
    64  
    65  		//找出这个文件所有的 import然后install 一遍
    66  		importPathList, err := kmgGoSource.GetImportPathListFromFile(pathOrPkg)
    67  		kmgConsole.ExitOnErr(err)
    68  		for _, pkgPath := range importPathList {
    69  			runCmdSliceWithGoPath(goPath, []string{"go", "install", pkgPath})
    70  		}
    71  		runCmdSliceWithGoPath(goPath, append([]string{"go", "run"}, os.Args[1:]...))
    72  		return
    73  	}
    74  	kmgConsole.ExitOnErr(fmt.Errorf("unexpected run path"))
    75  }
    76  
    77  //不回显命令
    78  func runCmdSliceWithGoPath(gopath string, cmdSlice []string) {
    79  	err := kmgCmd.CmdSlice(cmdSlice).
    80  		MustSetEnv("GOPATH", gopath).StdioRun()
    81  	if err != nil {
    82  		err = fmt.Errorf("kmg gorun: %s", err)
    83  		kmgConsole.ExitOnErr(err)
    84  	}
    85  }
    86  
    87  func goRunPackageName(goPath string, pathOrPkg string) {
    88  	//goRunInstall(goPath, pathOrPkg)
    89  	goRunInstallSimple(goPath, pathOrPkg)
    90  	//run
    91  	outPath := filepath.Join(goPath, "bin", filepath.Base(pathOrPkg))
    92  	p := kmgPlatform.GetCompiledPlatform()
    93  	if p.Compatible(kmgPlatform.WindowsAmd64) {
    94  		outPath += ".exe"
    95  	}
    96  	if !kmgFile.MustFileExist(outPath) {
    97  		kmgConsole.ExitOnErr(fmt.Errorf("please make sure you are kmg gorun a main package. (binary file not exist. %s)", outPath))
    98  	}
    99  	runCmdSliceWithGoPath(goPath, append([]string{outPath}, os.Args[2:]...))
   100  }
   101  
   102  type gorunCacheInfo struct {
   103  	PkgMap map[string]*gorunCachePkgInfo // key pkgname
   104  }
   105  
   106  type gorunCachePkgInfo struct {
   107  	GoFileMap map[string]uint64
   108  	PkgMd5    uint64
   109  	IsMain    bool
   110  	Name      string
   111  }
   112  
   113  type gorunTargetPkgCache struct {
   114  	PkgMap map[string]bool
   115  }
   116  
   117  func (info *gorunCachePkgInfo) getPkgBinPath(gopath string, platform string) string {
   118  	if info.IsMain {
   119  		return filepath.Join("bin", filepath.Base(info.Name))
   120  	} else {
   121  		return filepath.Join("pkg", platform, info.Name+".a")
   122  	}
   123  }
   124  
   125  func goRunInstall(goPath string, pathOrPkg string) {
   126  	//只能更新本GOPATH里面的pkg,不能更多多个GOPATH里面其他GOPATH的pkg缓存.
   127  	// go install 已知bug1 删除某个package里面的部分文件,然后由于引用到了旧的实现的代码,不会报错.删除pkg解决问题.
   128  	// go install 已知bug2 如果一个package先是main,然后build了一个东西,然后又改成了非main,再gorun会使用旧的缓存/bin/里面的缓存.
   129  	// TODO 已知bug3 当同一个项目的多个编译目标使用了同一个pkg,然后这个pkg变化了,缓存会出现A/B 问题,导致缓存完全无用.
   130  	ctx := &goRunInstallContext{
   131  		platform:          kmgPlatform.GetCompiledPlatform().String(),
   132  		targetPkgMapCache: map[string]bool{},
   133  		pkgMapCache:       map[string]*gorunCachePkgInfo{},
   134  	}
   135  	ctx.targetCachePath = filepath.Join(goPath, "tmp", "gorun", kmgCrypto.Md5HexFromString("target_"+pathOrPkg+"_"+ctx.platform))
   136  	ctx.pkgCachePath = filepath.Join(goPath, "tmp", "gorun", "allPkgCache")
   137  	ok := ctx.goRunInstallIsValidAndInvalidCache(goPath, pathOrPkg)
   138  	if ok {
   139  		//fmt.Println("use cache")
   140  		return
   141  	}
   142  	//fmt.Println("not use cache")
   143  	runCmdSliceWithGoPath(goPath, []string{"go", "install", pathOrPkg})
   144  	// 填充缓存
   145  	platform := ctx.platform
   146  	ctx.targetPkgMapCache = map[string]bool{}
   147  	outputJson := kmgCmd.CmdSlice([]string{"go", "list", "-json", pathOrPkg}).
   148  		MustSetEnv("GOPATH", goPath).MustCombinedOutput()
   149  	listObj := &struct {
   150  		Deps []string
   151  		Name string
   152  	}{}
   153  	kmgJson.MustUnmarshal(outputJson, &listObj)
   154  	if listObj.Name != "main" {
   155  		fmt.Printf("run non main package %s\n", pathOrPkg)
   156  		return
   157  	}
   158  	listObj.Deps = append(listObj.Deps, pathOrPkg)
   159  	for _, pkgName := range listObj.Deps {
   160  		srcpkgPath := filepath.Join(goPath, "src", pkgName)
   161  		fileList, err := kmgFile.ReadDirFileOneLevel(srcpkgPath)
   162  		if err != nil {
   163  			if !os.IsNotExist(err) {
   164  				panic(err)
   165  			}
   166  			// 没有找到pkg,可能是这个pkg在GOROOT出现过,此处暂时不管.
   167  			continue
   168  		}
   169  		ctx.targetPkgMapCache[pkgName] = true
   170  		pkgInfo := &gorunCachePkgInfo{
   171  			GoFileMap: map[string]uint64{},
   172  		}
   173  		for _, file := range fileList {
   174  			ext := filepath.Ext(file)
   175  			if ext == ".go" {
   176  				pkgInfo.GoFileMap[file] = mustCheckSumFile(filepath.Join(srcpkgPath, file))
   177  			}
   178  		}
   179  		pkgInfo.IsMain = pkgName == pathOrPkg
   180  		pkgInfo.Name = pkgName
   181  		pkgBinPath := pkgInfo.getPkgBinPath(goPath, platform)
   182  		pkgInfo.PkgMd5 = mustCheckSumFile(pkgBinPath)
   183  		ctx.pkgMapCache[pkgName] = pkgInfo
   184  	}
   185  	kmgGob.MustWriteFile(ctx.targetCachePath, ctx.targetPkgMapCache)
   186  	kmgGob.MustWriteFile(ctx.pkgCachePath, ctx.pkgMapCache)
   187  }
   188  
   189  func goRunInstallSimple(goPath string, pathOrPkg string) {
   190  	runCmdSliceWithGoPath(goPath, []string{"go", "install", pathOrPkg})
   191  }
   192  
   193  type goRunInstallContext struct {
   194  	targetPkgMapCache map[string]bool               //当前目标的依赖的缓存,(减少检查缓存的数量,提高速度)
   195  	pkgMapCache       map[string]*gorunCachePkgInfo //当前这个gopath的所有pkg的缓存的文件表.
   196  	platform          string
   197  	targetCachePath   string
   198  	pkgCachePath      string
   199  }
   200  
   201  func (ctx *goRunInstallContext) goRunInstallIsValidAndInvalidCache(goPath string, pathOrPkg string) bool {
   202  	err := kmgGob.ReadFile(ctx.targetCachePath, &ctx.targetPkgMapCache)
   203  	if err != nil { //此处故意忽略错误 没有缓存文件 TODO 此处需要折腾其他东西吗?
   204  		return false
   205  	}
   206  	err = kmgGob.ReadFile(ctx.pkgCachePath, &ctx.pkgMapCache)
   207  	if err != nil { //此处故意忽略错误 没有缓存文件
   208  		return false
   209  	}
   210  	//kmgDebug.Println(info)
   211  	isValid := true
   212  	task := kmgTask.NewLimitThreadTaskManager(runtime.NumCPU())
   213  	for pkgName := range ctx.targetPkgMapCache {
   214  		pkgName := pkgName
   215  		task.AddFunc(func() {
   216  			pkgInfo, ok := ctx.pkgMapCache[pkgName]
   217  			if !ok {
   218  				// 缓存不一致,删文件?
   219  				pkgPath := filepath.Join("pkg", ctx.platform, pkgName+".a")
   220  				if debug {
   221  					fmt.Println("pkgname not found in pkgMapCache delete ", pkgPath)
   222  				}
   223  				kmgFile.MustDelete(pkgPath)
   224  				isValid = false
   225  				return
   226  			}
   227  			pkgPath := pkgInfo.getPkgBinPath(goPath, ctx.platform)
   228  			if !checkFileWithMd5(pkgPath, pkgInfo.PkgMd5) {
   229  				if debug {
   230  					oldMd5 := uint64(0)
   231  					if kmgFile.MustFileExist(pkgPath) {
   232  						oldMd5 = mustCheckSumFile(pkgPath)
   233  					}
   234  					fmt.Println("pkgBinFile md5 not equal delete ", pkgPath, pkgInfo.PkgMd5, oldMd5)
   235  				}
   236  				kmgFile.MustDelete(pkgPath)
   237  				isValid = false
   238  				return
   239  			}
   240  			srcpkgPath := filepath.Join(goPath, "src", pkgName)
   241  			fileList, err := kmgFile.ReadDirFileOneLevel(srcpkgPath)
   242  			if err != nil {
   243  				if !os.IsNotExist(err) {
   244  					panic(err)
   245  				}
   246  				kmgFile.MustDelete(pkgPath)
   247  				if debug {
   248  					fmt.Println("dir not exist File delete ", pkgPath, srcpkgPath)
   249  				}
   250  				isValid = false
   251  				return
   252  			}
   253  			isThisPkgValid := true
   254  			for _, file := range fileList {
   255  				ext := filepath.Ext(file)
   256  				if ext == ".go" {
   257  					// 多了一个文件
   258  					_, ok := pkgInfo.GoFileMap[file]
   259  					if !ok {
   260  						kmgFile.MustDelete(pkgPath)
   261  						if debug {
   262  							fmt.Println("more file ", file, " delete ", pkgPath)
   263  						}
   264  						isValid = false
   265  						isThisPkgValid = false
   266  						break
   267  					}
   268  				}
   269  			}
   270  			if !isThisPkgValid {
   271  				return
   272  			}
   273  			for name, md5 := range pkgInfo.GoFileMap {
   274  				goFilePath := filepath.Join(srcpkgPath, name)
   275  				if !checkFileWithMd5(goFilePath, md5) {
   276  					kmgFile.MustDelete(pkgPath)
   277  					if debug {
   278  						fmt.Println("gofile not match ", name, " delete ", pkgPath)
   279  					}
   280  					isValid = false
   281  					break
   282  				}
   283  			}
   284  		})
   285  	}
   286  	task.Close()
   287  	return isValid
   288  }
   289  
   290  func checkFileWithMd5(path string, shouldMd5 uint64) (ok bool) {
   291  	content, err := kmgFile.ReadFile(path)
   292  	if err != nil {
   293  		if !os.IsNotExist(err) {
   294  			panic(err)
   295  		}
   296  		return false
   297  	}
   298  	return xxhash.Checksum64(content) == shouldMd5
   299  }
   300  
   301  func mustCheckSumFile(path string) uint64 {
   302  	content, err := kmgFile.ReadFile(path)
   303  	if err != nil {
   304  		panic(err)
   305  	}
   306  	return xxhash.Checksum64(content)
   307  }