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 }