github.com/jmigpin/editor@v1.6.0/core/godebug/cmd.go (about) 1 package godebug 2 3 import ( 4 "bytes" 5 "context" 6 "embed" 7 "fmt" 8 "go/ast" 9 "go/printer" 10 "go/token" 11 "go/types" 12 "io" 13 "io/ioutil" 14 "os" 15 "os/exec" 16 "path" 17 "path/filepath" 18 "runtime" 19 "strings" 20 "time" 21 22 "github.com/jmigpin/editor/core/godebug/debug" 23 "github.com/jmigpin/editor/util/astut" 24 "github.com/jmigpin/editor/util/goutil" 25 "github.com/jmigpin/editor/util/iout" 26 "github.com/jmigpin/editor/util/osutil" 27 "github.com/jmigpin/editor/util/parseutil" 28 "github.com/jmigpin/editor/util/pathutil" 29 "golang.org/x/mod/modfile" 30 ) 31 32 // The godebug/debug pkg is writen to a tmp dir and used with the pkg path "godebugconfig/debug" to avoid clashes when self debugging. A config.go file is added with the annotation data. The godebug/debug pkg is included in the editor binary via //go:embed directive. 33 var debugPkgPath = "godebugconfig/debug" 34 35 //---------- 36 37 type Cmd struct { 38 Dir string // running directory 39 40 flags flags 41 gopathMode bool 42 43 Stdin io.Reader 44 Stdout io.Writer 45 Stderr io.Writer 46 47 tmpDir string 48 tmpBuiltFile string // godebug file built 49 tmpGoModFilename string 50 51 mainFuncFilename string // set at annotation time 52 53 fset *token.FileSet 54 env []string // set at start 55 annset *AnnotatorSet 56 57 debugPkgDir string 58 alternativeGoMod string 59 overlayFilename string 60 overlay map[string]string // orig->new 61 62 Client *Client 63 start struct { 64 network string 65 address string 66 cancel context.CancelFunc 67 serverWait func() error // annotated program; can be nil 68 filesData *debug.FilesDataMsg 69 } 70 } 71 72 func NewCmd() *Cmd { 73 cmd := &Cmd{ 74 Stdout: os.Stdout, 75 Stderr: os.Stderr, 76 } 77 cmd.fset = token.NewFileSet() 78 cmd.annset = NewAnnotatorSet(cmd.fset) 79 return cmd 80 } 81 82 //------------ 83 84 func (cmd *Cmd) printf(f string, a ...interface{}) (int, error) { 85 return fmt.Fprintf(cmd.Stdout, "# "+f, a...) 86 } 87 func (cmd *Cmd) logf(f string, a ...interface{}) (int, error) { 88 if cmd.flags.verbose { 89 f = strings.TrimRight(f, "\n") + "\n" // ensure one newline 90 return cmd.printf(f, a...) 91 } 92 return 0, nil 93 } 94 func (cmd *Cmd) Error(err error) { 95 cmd.printf("error: %v\n", err) 96 } 97 98 //------------ 99 100 func (cmd *Cmd) Start(ctx context.Context, args []string) (bool, error) { 101 if err := cmd.start2(ctx, args); err != nil { 102 return true, err 103 } 104 if cmd.flags.mode.build { 105 return true, nil 106 } 107 return false, nil 108 } 109 func (cmd *Cmd) start2(ctx context.Context, args []string) error { 110 defer cmd.cleanupAfterStart() 111 112 cmd.logf("dir=%v\n", cmd.Dir) 113 cmd.logf("testmode=%v\n", cmd.flags.mode.test) 114 115 if err := cmd.neededGoVersion(); err != nil { 116 return err 117 } 118 119 // use absolute dir 120 dir0, err := filepath.Abs(cmd.Dir) 121 if err != nil { 122 return err 123 } 124 cmd.Dir = dir0 125 126 // read flags 127 cmd.flags.stderr = cmd.Stderr 128 if err := cmd.flags.parseArgs(args); err != nil { 129 return err 130 } 131 132 // setup environment 133 cmd.env = goutil.OsAndGoEnv(cmd.Dir) 134 cmd.env = osutil.SetEnvs(cmd.env, cmd.flags.env) 135 136 if err := cmd.detectGopathMode(cmd.env); err != nil { 137 return err 138 } 139 140 // REVIEW 141 // depends on: gopathMode, tmpDir 142 //cmd.env = cmd.setGoPathEnv(cmd.env) 143 144 // depends on cmd.flags.work 145 if err := cmd.setupTmpDir(); err != nil { 146 return err 147 } 148 149 if err := cmd.setupNetworkAddress(); err != nil { 150 return err 151 } 152 153 m := &cmd.flags.mode 154 if m.run || m.test || m.build { 155 if err := cmd.build(ctx); err != nil { 156 return err 157 } 158 } 159 160 switch { 161 case m.build: 162 // inform the address used in the binary 163 cmd.printf("build: %v (builtin address: %v, %v)\n", cmd.tmpBuiltFile, cmd.start.network, cmd.start.address) 164 return nil 165 case m.run || m.test: 166 return cmd.startServerClient(ctx) 167 case m.connect: 168 return cmd.startClient(ctx) 169 default: 170 panic(fmt.Sprintf("unhandled mode: %v", m)) 171 } 172 } 173 174 //---------- 175 176 func (cmd *Cmd) build(ctx context.Context) error { 177 fa := NewFilesToAnnotate(cmd) 178 if err := fa.find(ctx); err != nil { 179 return err 180 } 181 if err := cmd.annotateFiles2(ctx, fa); err != nil { 182 return err 183 } 184 if err := cmd.buildDebugPkg(ctx, fa); err != nil { 185 return err 186 } 187 if !cmd.gopathMode { 188 if err := cmd.buildAlternativeGoMod(ctx, fa); err != nil { 189 return err 190 } 191 } 192 if err := cmd.buildOverlayFile(ctx); err != nil { 193 return err 194 } 195 196 // DEBUG 197 //cmd.printAnnotatedFilesAsts(fa) 198 199 if err := cmd.buildBinary(ctx, fa); err != nil { 200 // auto-set work flag to avoid cleanup; allows clicking on failing work files locations 201 cmd.flags.work = true 202 203 return err 204 } 205 return nil 206 } 207 208 func (cmd *Cmd) buildBinary(ctx context.Context, fa *FilesToAnnotate) error { 209 outFilename, err := cmd.buildOutFilename(fa) 210 if err != nil { 211 return err 212 } 213 cmd.tmpBuiltFile = outFilename 214 215 // build args 216 a := []string{osutil.ExecName("go")} 217 if cmd.flags.mode.test { 218 a = append(a, "test") 219 a = append(a, "-c") // compile binary but don't run 220 //a = append(a, "-vet=off") 221 } else { 222 a = append(a, "build") 223 } 224 if cmd.alternativeGoMod != "" { 225 a = append(a, "-modfile="+cmd.alternativeGoMod) 226 } 227 a = append(a, "-overlay="+cmd.overlayFilename) 228 a = append(a, "-o="+cmd.tmpBuiltFile) 229 a = append(a, cmd.buildArgs()...) 230 a = append(a, cmd.flags.unnamedArgs...) 231 232 cmd.logf("build binary: %v\n", a) 233 ec := cmd.newCmdI(ctx, a) 234 if err := ec.Start(); err != nil { 235 return err 236 } 237 return ec.Wait() 238 } 239 240 //------------ 241 242 // DEBUG 243 func (cmd *Cmd) printAnnotatedFilesAsts(fa *FilesToAnnotate) { 244 for orig, _ := range cmd.overlay { 245 astFile, ok := fa.filesAsts[orig] 246 if ok { 247 astut.PrintNode(cmd.annset.fset, astFile) 248 } 249 } 250 } 251 252 //------------ 253 254 func (cmd *Cmd) startServerClient(ctx context.Context) error { 255 // server/client context to cancel the other when one of them ends 256 ctx2, cancel := context.WithCancel(ctx) 257 cmd.start.cancel = cancel 258 259 if err := cmd.startServer(ctx2); err != nil { 260 return err 261 } 262 return cmd.startClient(ctx2) 263 } 264 func (cmd *Cmd) cancelStart() { 265 if cmd.start.cancel != nil { 266 cmd.start.cancel() 267 } 268 } 269 func (cmd *Cmd) startServer(ctx context.Context) error { 270 return cmd.runBinary(ctx) 271 } 272 func (cmd *Cmd) runBinary(ctx context.Context) error { 273 // args of the built binary to run (annotated program) 274 args := []string{} 275 if cmd.flags.toolExec != "" { 276 args = append(args, cmd.flags.toolExec) 277 } 278 args = append(args, cmd.tmpBuiltFile) 279 args = append(args, cmd.flags.binaryArgs...) 280 281 // callback func to print process id and args 282 cb := func(cmdi osutil.CmdI) { 283 cmd.printf("pid %d: %v\n", cmdi.Cmd().Process.Pid, args) 284 } 285 286 // run the annotated program 287 ci := cmd.newCmdI(ctx, args) 288 ci = osutil.NewCallbackOnStartCmd(ci, cb) 289 if err := ci.Start(); err != nil { 290 cmd.cancelStart() 291 return err 292 } 293 294 //waitErr := error(nil) 295 //wg := sync.WaitGroup{} 296 //wg.Add(1) 297 //go func() { 298 // defer wg.Done() 299 // waitErr = ec.Wait() 300 //}() 301 302 //log.Println("server started") 303 cmd.start.serverWait = func() error { 304 //defer log.Println("server wait done") 305 //wg.Wait() 306 //return waitErr 307 308 return ci.Wait() 309 } 310 return nil 311 } 312 func (cmd *Cmd) startClient(ctx context.Context) error { 313 // blocks until connected 314 client, err := NewClient(ctx, cmd.start.network, cmd.start.address) 315 if err != nil { 316 //log.Println("client ended") 317 cmd.cancelStart() 318 if cmd.start.serverWait != nil { 319 cmd.start.serverWait() 320 } 321 return err 322 } 323 cmd.Client = client 324 325 // set deadline for the starting protocol 326 deadline := time.Now().Add(8 * time.Second) 327 cmd.Client.Conn.SetWriteDeadline(deadline) 328 defer cmd.Client.Conn.SetWriteDeadline(time.Time{}) // clear 329 330 // starting protocol 331 if err := cmd.requestFilesData(); err != nil { 332 return err 333 } 334 // wait for filesdata 335 msg, ok := <-cmd.Client.Messages 336 if !ok { 337 return fmt.Errorf("clients msgs chan closed") 338 } 339 if fd, ok := msg.(*debug.FilesDataMsg); !ok { 340 return fmt.Errorf("unexpected msg: %#v", msg) 341 } else { 342 cmd.start.filesData = fd 343 } 344 // request start 345 if err := cmd.requestStart(); err != nil { 346 return err 347 } 348 return nil 349 } 350 351 func (cmd *Cmd) Wait() error { 352 defer cmd.cleanupAfterWait() 353 defer cmd.cancelStart() 354 err := error(nil) 355 if cmd.start.serverWait != nil { // might be nil (ex: connect mode) 356 err = cmd.start.serverWait() 357 } 358 if cmd.Client != nil { // might be nil (ex: server failed to start) 359 cmd.Client.Wait() 360 } 361 return err 362 } 363 364 //------------ 365 366 func (cmd *Cmd) Messages() chan interface{} { 367 return cmd.Client.Messages 368 } 369 func (cmd *Cmd) FilesData() *debug.FilesDataMsg { 370 return cmd.start.filesData 371 } 372 373 //------------ 374 375 func (cmd *Cmd) requestFilesData() error { 376 msg := &debug.ReqFilesDataMsg{} 377 encoded, err := debug.EncodeMessage(msg) 378 if err != nil { 379 return err 380 } 381 _, err = cmd.Client.Conn.Write(encoded) 382 return err 383 } 384 385 func (cmd *Cmd) requestStart() error { 386 msg := &debug.ReqStartMsg{} 387 encoded, err := debug.EncodeMessage(msg) 388 if err != nil { 389 return err 390 } 391 _, err = cmd.Client.Conn.Write(encoded) 392 return err 393 } 394 395 //------------ 396 397 //func (cmd *Cmd) tmpDirBasedFilename(filename string) string { 398 // // remove volume name 399 // v := filepath.VolumeName(filename) 400 // if len(v) > 0 { 401 // filename = filename[len(v):] 402 // } 403 404 // if cmd.gopathMode { 405 // // trim filename when inside a src dir 406 // rhs := trimAtFirstSrcDir(filename) 407 // return filepath.Join(cmd.tmpDir, "src", rhs) 408 // } 409 410 // // just replicate on tmp dir 411 // return filepath.Join(cmd.tmpDir, filename) 412 //} 413 414 //------------ 415 416 //func (cmd *Cmd) setGoPathEnv(env []string) []string { 417 // // after cmd.flags.env such that this result won't be overriden 418 419 // s := cmd.fullGoPathStr(env) 420 // return osutil.SetEnv(env, "GOPATH", s) 421 //} 422 423 //func (cmd *Cmd) fullGoPathStr(env []string) string { 424 // u := []string{} // first has priority, use new slice 425 426 // // add tmpdir for priority to the annotated files 427 // if cmd.gopathMode { 428 // u = append(u, cmd.tmpDir) 429 // } 430 431 // if s := osutil.GetEnv(cmd.flags.env, "GOPATH"); s != "" { 432 // u = append(u, s) 433 // } 434 435 // // always include default gopath last (includes entry that might not be defined anywhere, needs to be set) 436 // u = append(u, goutil.GetGoPath(env)...) 437 438 // return goutil.JoinPathLists(u...) 439 //} 440 441 func (cmd *Cmd) addToGopathStart(dir string) { 442 varName := "GOPATH" 443 v := osutil.GetEnv(cmd.env, varName) 444 sep := "" 445 if v != "" { 446 sep = string(os.PathListSeparator) 447 } 448 v2 := dir + sep + v 449 cmd.env = osutil.SetEnv(cmd.env, varName, v2) 450 } 451 452 //------------ 453 454 func (cmd *Cmd) detectGopathMode(env []string) error { 455 modsMode, err := cmd.detectModulesMode(env) 456 if err != nil { 457 return err 458 } 459 cmd.gopathMode = !modsMode 460 cmd.logf("gopathmode=%v\n", cmd.gopathMode) 461 return nil 462 } 463 func (cmd *Cmd) detectModulesMode(env []string) (bool, error) { 464 v := osutil.GetEnv(env, "GO111MODULE") 465 switch v { 466 case "on": 467 return true, nil 468 case "off": 469 return false, nil 470 case "auto": 471 return cmd.detectGoMod(), nil 472 default: 473 v, err := goutil.GoVersion() 474 if err != nil { 475 return false, err 476 } 477 // < go1.16, modules mode if go.mod present 478 if parseutil.VersionLessThan(v, "1.16") { 479 return cmd.detectGoMod(), nil 480 } 481 // >= go1.16, modules mode by default 482 return true, nil 483 } 484 } 485 func (cmd *Cmd) detectGoMod() bool { 486 _, ok := goutil.FindGoMod(cmd.Dir) 487 return ok 488 } 489 func (cmd *Cmd) neededGoVersion() error { 490 // need go version that supports overlays 491 v, err := goutil.GoVersion() 492 if err != nil { 493 return err 494 } 495 if parseutil.VersionLessThan(v, "1.16") { 496 return fmt.Errorf("need go version >=1.16 that supports -overlay flag") 497 } 498 return nil 499 } 500 501 //------------ 502 503 func (cmd *Cmd) cleanupAfterStart() { 504 // always remove (written in src dir) 505 if cmd.tmpGoModFilename != "" { 506 _ = os.Remove(cmd.tmpGoModFilename) // best effort 507 } 508 // remove dirs 509 if cmd.tmpDir != "" && !cmd.flags.work { 510 _ = os.RemoveAll(cmd.tmpDir) // best effort 511 } 512 } 513 514 func (cmd *Cmd) cleanupAfterWait() { 515 // cleanup unix socket in case of bad stop 516 if cmd.start.network == "unix" { 517 _ = os.Remove(cmd.start.address) // best effort 518 } 519 520 if cmd.tmpBuiltFile != "" && !cmd.flags.mode.build { 521 _ = os.Remove(cmd.tmpBuiltFile) // best effort 522 } 523 } 524 525 //------------ 526 527 func (cmd *Cmd) mkdirAllWriteAstFile(filename string, astFile *ast.File) error { 528 buf := &bytes.Buffer{} 529 530 pcfg := &printer.Config{Tabwidth: 4} 531 532 // by default, don't print with sourcepos since it will only confuse the user. If the original code doesn't compile, the load packages should fail early before getting to output any ast file. 533 if cmd.flags.srcLines { 534 pcfg.Mode = printer.SourcePos 535 } 536 537 if err := pcfg.Fprint(buf, cmd.fset, astFile); err != nil { 538 return err 539 } 540 return mkdirAllWriteFile(filename, buf.Bytes()) 541 } 542 543 //------------ 544 545 func (cmd *Cmd) setupTmpDir() error { 546 fixedDir := filepath.Join(os.TempDir(), "editor_godebug") 547 if err := iout.MkdirAll(fixedDir); err != nil { 548 return err 549 } 550 dir, err := ioutil.TempDir(fixedDir, "work*") 551 if err != nil { 552 return err 553 } 554 cmd.tmpDir = dir 555 556 // print tmp dir if work flag is present 557 if cmd.flags.work { 558 cmd.printf("tmpDir: %v\n", cmd.tmpDir) 559 } 560 return nil 561 } 562 563 //------------ 564 565 func (cmd *Cmd) buildArgs() []string { 566 u := []string{} 567 u = append(u, envGodebugBuildFlags(cmd.env)...) 568 u = append(u, cmd.flags.unknownArgs...) 569 return u 570 } 571 572 //------------ 573 574 func (cmd *Cmd) setupNetworkAddress() error { 575 // can't consider using stdin/out since the program could use it 576 577 if cmd.flags.address != "" { 578 cmd.start.network = "tcp" 579 cmd.start.address = cmd.flags.address 580 return nil 581 } 582 583 // OS target to choose how to connect 584 goOs := osutil.GetEnv(cmd.env, "GOOS") 585 if goOs == "" { 586 goOs = runtime.GOOS 587 } 588 589 switch goOs { 590 case "linux": 591 cmd.start.network = "unix" 592 cmd.start.address = filepath.Join(cmd.tmpDir, "godebug.sock") 593 default: 594 port, err := osutil.GetFreeTcpPort() 595 if err != nil { 596 return err 597 } 598 cmd.start.network = "tcp" 599 cmd.start.address = fmt.Sprintf("127.0.0.1:%v", port) 600 } 601 return nil 602 } 603 604 //------------ 605 606 func (cmd *Cmd) annotateFiles2(ctx context.Context, fa *FilesToAnnotate) error { 607 // annotate files 608 handledMain := false 609 cmd.overlay = map[string]string{} 610 mainName := mainFuncName(fa.cmd.flags.mode.test) 611 for filename := range fa.toAnnotate { 612 astFile, ok := fa.filesAsts[filename] 613 if !ok { 614 return fmt.Errorf("missing ast file: %v", filename) 615 } 616 617 // annotate 618 ti := (*types.Info)(nil) 619 pkg, ok := fa.filesPkgs[filename] 620 if ok { 621 ti = pkg.TypesInfo 622 } 623 if err := cmd.annset.AnnotateAstFile(astFile, ti, fa.nodeAnnTypes); err != nil { 624 return err 625 } 626 627 // setup main ast with debug.exitserver 628 if fd, ok := findFuncDeclWithBody(astFile, mainName); ok { 629 handledMain = true 630 cmd.mainFuncFilename = filename 631 cmd.annset.setupDebugExitInFuncDecl(fd, astFile) 632 } 633 } 634 635 if !handledMain { 636 if !cmd.flags.mode.test { 637 return fmt.Errorf("main func not handled") 638 } 639 // insert testmains in "*_test.go" files 640 seen := map[string]bool{} 641 for filename := range fa.toAnnotate { 642 if !strings.HasSuffix(filename, "_test.go") { 643 continue 644 } 645 646 // one testmain per dir 647 dir := filepath.Dir(filename) 648 if seen[dir] { 649 continue 650 } 651 seen[dir] = true 652 653 astFile, ok := fa.filesAsts[filename] 654 if !ok { 655 continue 656 } 657 if err := cmd.annset.insertTestMain(astFile); err != nil { 658 return err 659 } 660 661 // use dir of the first file // TODO: just use current dir? 662 if cmd.mainFuncFilename == "" { 663 dir := filepath.Dir(filename) 664 cmd.mainFuncFilename = filepath.Join(dir, "testmain") 665 } 666 } 667 } 668 669 for filename := range fa.toAnnotate { 670 astFile, ok := fa.filesAsts[filename] 671 if !ok { 672 return fmt.Errorf("missing ast file: %v", filename) 673 } 674 675 // encode filename for a flat map 676 ext := filepath.Ext(filename) 677 base := filepath.Base(filename) 678 base = base[:len(base)-len(ext)] 679 //hash := md5.New().Write([]byte(filename)).Sum(nil) 680 //hash := genDigitsStr(8) 681 hash := hashStringN(filename, 10) 682 name := fmt.Sprintf("%s_%s%s", base, hash, ext) 683 684 // write annotated files and keep in map for overlay 685 filename2 := filepath.Join(cmd.tmpDir, "annotated", name) 686 if err := cmd.mkdirAllWriteAstFile(filename2, astFile); err != nil { 687 return err 688 } 689 690 cmd.overlay[filename] = filename2 691 } 692 693 return nil 694 } 695 696 //------------ 697 698 func (cmd *Cmd) buildOverlayFile(ctx context.Context) error { 699 // build entries 700 w := []string{} 701 for src, dest := range cmd.overlay { 702 w = append(w, fmt.Sprintf("%q:%q", src, dest)) 703 } 704 // write overlay file 705 src := []byte(fmt.Sprintf("{%q:{%s}}", "Replace", strings.Join(w, ","))) 706 cmd.overlayFilename = filepath.Join(cmd.tmpDir, "annotated_overlay.json") 707 return mkdirAllWriteFile(cmd.overlayFilename, src) 708 } 709 710 //------------ 711 712 func (cmd *Cmd) buildDebugPkg(ctx context.Context, fa *FilesToAnnotate) error { 713 //// detect if the editor debug pkg is used 714 //cmd.selfMode = false 715 //selfDebugPkgDir := "" 716 //for pkgPath, pkg := range fa.pathsPkgs { 717 // if strings.HasPrefix(pkgPath, editorPkgPath+"/") { 718 // // find dir 719 // f := pkg.GoFiles[0] 720 // k := strings.Index(f, editorPkgPath) 721 // if k >= 0 { 722 // cmd.selfMode = true 723 // selfDebugPkgDir = filepath.Join(f[:k], debugPkgPath) 724 // break 725 // } 726 // } 727 //} 728 729 //// setup current files to be empty by default (attempt to discard if debugging an old version of the editor) 730 //if cmd.selfMode { 731 // fis, err := ioutil.ReadDir(selfDebugPkgDir) 732 // if err == nil { 733 // for _, fi := range fis { 734 // filename := fsutil.JoinPath(selfDebugPkgDir, fi.Name()) 735 // cmd.overlay[filename] = "" 736 // } 737 // } 738 //} 739 740 // target dir 741 cmd.debugPkgDir = filepath.Join(cmd.tmpDir, "debugpkg") 742 if cmd.gopathMode { 743 cmd.addToGopathStart(cmd.debugPkgDir) 744 cmd.debugPkgDir = filepath.Join(cmd.debugPkgDir, "src/"+debugPkgPath) 745 } 746 747 // util to add file to debug pkg dir 748 writeFile := func(name string, src []byte) error { 749 filename2 := filepath.Join(cmd.debugPkgDir, name) 750 751 //if cmd.selfMode { 752 // filename3 := filepath.Join(selfDebugPkgDir, name) 753 // //println("overlay", filename3, filename2) 754 // cmd.overlay[filename3] = filename2 755 //} 756 757 return mkdirAllWriteFile(filename2, src) 758 } 759 760 // local src pkg dir where the debug pkg is located (io/fs) 761 srcDir := "debug" 762 des, err := debugPkgFs.ReadDir(srcDir) 763 if err != nil { 764 return err 765 } 766 for _, de := range des { 767 // must use path.join since dealing with embedFs 768 filename1 := path.Join(srcDir, de.Name()) 769 if strings.HasSuffix(filename1, "_test.go") { 770 continue 771 } 772 src, err := debugPkgFs.ReadFile(filename1) 773 if err != nil { 774 return err 775 } 776 if err := writeFile(de.Name(), src); err != nil { 777 return err 778 } 779 } 780 781 // dynamically create go.mod since go:embed doesn't allow it 782 if !cmd.gopathMode { 783 src3 := []byte(fmt.Sprintf("module %s\n", debugPkgPath)) 784 if err := writeFile("go.mod", src3); err != nil { 785 return err 786 } 787 } 788 789 // init() functions declared across multiple files in a package are processed in alphabetical order of the file name. Use name starting with "a" to setup config vars as early as possible. 790 configFilename := "aaaconfig.go" 791 792 // build config file 793 src4 := cmd.annset.BuildConfigSrc(cmd.start.network, cmd.start.address, &cmd.flags) 794 if err := writeFile(configFilename, src4); err != nil { 795 return err 796 } 797 798 return nil 799 } 800 801 //------------ 802 803 func (cmd *Cmd) buildAlternativeGoMod(ctx context.Context, fa *FilesToAnnotate) error { 804 filename, ok := fa.GoModFilename() 805 if !ok { 806 //return fmt.Errorf("missing go.mod") 807 808 // in the case of a simple main.go without any go.mod (but in modules mode), it needs to create an artificial go.mod in order to reference the debug pkg that is located in the tmp dir 809 810 // TODO: last resort, having to create files in the src dir is to be avoided -- needs review 811 812 // create temporary go.mod in src dir based on main file 813 if cmd.mainFuncFilename == "" { 814 return fmt.Errorf("missing main func filename") 815 } 816 dir := filepath.Dir(cmd.mainFuncFilename) 817 fname2 := filepath.Join(dir, "go.mod") 818 // must not exist 819 if _, err := os.Stat(fname2); !os.IsNotExist(err) { 820 return fmt.Errorf("file should not exist because gomodfilename didn't found it: %v", fname2) 821 } 822 // create 823 src := []byte("module main\n") 824 if err := mkdirAllWriteFile(fname2, src); err != nil { 825 return err 826 } 827 cmd.tmpGoModFilename = fname2 828 cmd.logf("tmpgomodfilename: %v\n", cmd.tmpGoModFilename) 829 filename = fname2 830 } 831 832 // build based on current go.mod 833 src, err := ioutil.ReadFile(filename) 834 if err != nil { 835 return fmt.Errorf("unable to read mod file: %w", err) 836 } 837 f, err := modfile.ParseLax(filename, src, nil) 838 if err != nil { 839 return err 840 } 841 842 if cmd.flags.usePkgLinks { 843 if err := cmd.buildPkgLinks(f, fa); err != nil { 844 return err 845 } 846 } 847 848 // include debug pkg require/replace lines 849 f.AddNewRequire(debugPkgPath, "v0.0.0", false) 850 f.AddReplace(debugPkgPath, "", cmd.debugPkgDir, "") 851 852 src2, err := f.Format() 853 if err != nil { 854 return err 855 } 856 cmd.alternativeGoMod = filepath.Join(cmd.tmpDir, "alternative.mod") 857 if err := mkdirAllWriteFile(cmd.alternativeGoMod, src2); err != nil { 858 return err 859 } 860 861 // REVIEW: commented: using overlay for go.mod, so the original go.sum should be used(?) 862 //// copy as well go.sum or it will fail, just need a best effort 863 //dir := filepath.Dir(filename) 864 //gosum := filepath.Join(dir, "go.sum") 865 //gosumDst := pathutil.ReplaceExt(cmd.alternativeGoMod, ".sum") 866 //_ = copyFile(gosum, gosumDst) 867 868 cmd.overlay[filename] = cmd.alternativeGoMod 869 ////cmd.overlay[gosum] = gosumDst 870 cmd.alternativeGoMod = "" // disable (using overlay) 871 872 return nil 873 } 874 875 func (cmd *Cmd) buildPkgLinks(mf *modfile.File, fa *FilesToAnnotate) error { 876 877 linksDir := filepath.Join(cmd.tmpDir, "pkglinks") 878 if err := iout.MkdirAll(linksDir); err != nil { 879 return err 880 } 881 882 linkFilename := func(dir string) string { 883 hash := hashStringN(dir, 10) 884 base := filepath.Base(dir) 885 name := fmt.Sprintf("%s-%s", base, hash) 886 return filepath.Join(linksDir, name) 887 } 888 889 seen := map[string]bool{} // seen module 890 for _, req := range mf.Require { // modules being required 891 for filename := range fa.toAnnotate { // all annotated files 892 fpkg, ok := fa.filesPkgs[filename] 893 if !ok { 894 continue 895 } 896 897 fmod := pkgMod(fpkg) 898 if fmod == nil || fmod.Dir == "" { 899 continue 900 } 901 902 // visited already 903 if seen[fmod.Dir] { 904 continue 905 } 906 seen[fmod.Dir] = true 907 908 if fmod.Path != req.Mod.Path { 909 continue 910 } 911 912 // required module with annotated files 913 914 // TODO: do this only if in a gopath dir? Seemed to be the issue that only modules in a gopath dir would not honor the overlay including a new package 915 916 // make a link to the package module and use that link dir to bypass the erroneous behaviour introduced by go1.19.x 917 ldir := linkFilename(fmod.Dir) 918 if err := os.Symlink(fmod.Dir, ldir); err != nil { 919 return fmt.Errorf("builddirlinks: %w", err) 920 } 921 922 // add replace directive to go.mod 923 mf.AddReplace(fmod.Path, "", ldir, "") 924 925 // replace all references in the overlay map to the created link dir 926 for oldf, newf := range cmd.overlay { 927 dir2 := fmod.Dir + string(filepath.Separator) 928 if strings.HasPrefix(oldf, dir2) { 929 rest := oldf[len(dir2):] 930 name2 := filepath.Join(ldir, rest) 931 cmd.overlay[name2] = newf 932 delete(cmd.overlay, oldf) 933 } 934 } 935 936 // add module go.mod in overlay (ex: case of module without a go.mod, but with a built go.mod in a cache dir) 937 dir2 := filepath.Dir(fmod.GoMod) 938 if dir2 != fmod.Dir { 939 filename := filepath.Join(ldir, "go.mod") 940 cmd.overlay[filename] = fmod.GoMod 941 } 942 } 943 } 944 return nil 945 } 946 947 //------------ 948 949 func (cmd *Cmd) buildOutFilename(fa *FilesToAnnotate) (string, error) { 950 if cmd.flags.outFilename != "" { 951 return cmd.flags.outFilename, nil 952 } 953 954 if cmd.mainFuncFilename == "" { 955 return "", fmt.Errorf("missing main filename") 956 } 957 958 // commented: output to tmp dir 959 //fname := filepath.Base(cmd.mainFuncFilename) 960 //fname = fsutil.JoinPath(cmd.tmpDir, fname) 961 962 // output to main file dir 963 fname := cmd.mainFuncFilename 964 fname = pathutil.ReplaceExt(fname, "_godebug") // don't use ".godebug", not a file type 965 966 fname = osutil.ExecName(fname) 967 return fname, nil 968 } 969 970 //------------ 971 972 func (cmd *Cmd) newCmdI(ctx context.Context, args []string) osutil.CmdI { 973 ec := exec.CommandContext(ctx, args[0], args[1:]...) 974 ec.Stdin = cmd.Stdin 975 ec.Stdout = cmd.Stdout 976 ec.Stderr = cmd.Stderr 977 ec.Dir = cmd.Dir 978 ec.Env = cmd.env 979 980 ci := osutil.NewCmdI(ec) 981 ci = osutil.NewSetSidCmd(ctx, ci) 982 ci = osutil.NewShellCmd(ci) 983 return ci 984 } 985 986 //------------ 987 //------------ 988 //------------ 989 990 //go:embed debug/* 991 var debugPkgFs embed.FS 992 993 //------------ 994 995 func writeFile(filename string, src []byte) error { 996 return os.WriteFile(filename, src, 0640) 997 } 998 func mkdirAllWriteFile(filename string, src []byte) error { 999 return iout.MkdirAllWriteFile(filename, src, 0640) 1000 } 1001 1002 func mkdirAllCopyFile(src, dst string) error { 1003 return iout.MkdirAllCopyFile(src, dst, 0640) 1004 } 1005 func mkdirAllCopyFileSync(src, dst string) error { 1006 return iout.MkdirAllCopyFileSync(src, dst, 0640) 1007 } 1008 1009 func copyFile(src, dst string) error { 1010 return iout.CopyFile(src, dst, 0640) 1011 } 1012 1013 //------------ 1014 1015 func splitCommaList(val string) []string { 1016 a := strings.Split(val, ",") 1017 u := []string{} 1018 for _, s := range a { 1019 // don't add empty strings 1020 s := strings.TrimSpace(s) 1021 if s == "" { 1022 continue 1023 } 1024 1025 u = append(u, s) 1026 } 1027 return u 1028 } 1029 1030 //------------ 1031 1032 //func trimAtFirstSrcDir(filename string) string { 1033 // v := filename 1034 // w := []string{} 1035 // for { 1036 // base := filepath.Base(v) 1037 // if base == "src" { 1038 // return filepath.Join(w...) // trimmed 1039 // } 1040 // w = append([]string{base}, w...) 1041 // oldv := v 1042 // v = filepath.Dir(v) 1043 // isRoot := oldv == v 1044 // if isRoot { 1045 // break 1046 // } 1047 // } 1048 // return filename 1049 //} 1050 1051 //---------- 1052 1053 // TODO: remove once env vars supported in editor 1054 func envGodebugBuildFlags(env []string) []string { 1055 bfs := osutil.GetEnv(env, "GODEBUG_BUILD_FLAGS") 1056 if len(bfs) == 0 { 1057 return nil 1058 } 1059 return strings.Split(bfs, ",") 1060 }