github.com/goplus/llgo@v0.8.3/internal/build/build.go (about) 1 /* 2 * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package build 18 19 import ( 20 "archive/zip" 21 "fmt" 22 "go/token" 23 "go/types" 24 "io" 25 "os" 26 "os/exec" 27 "path" 28 "path/filepath" 29 "runtime" 30 "strings" 31 32 "golang.org/x/tools/go/packages" 33 "golang.org/x/tools/go/ssa" 34 35 "github.com/goplus/llgo/cl" 36 "github.com/goplus/llgo/xtool/clang" 37 38 llssa "github.com/goplus/llgo/ssa" 39 ) 40 41 type Mode int 42 43 const ( 44 ModeBuild Mode = iota 45 ModeInstall 46 ModeRun 47 ) 48 49 func needLLFile(mode Mode) bool { 50 return mode != ModeBuild 51 } 52 53 type Config struct { 54 BinPath string 55 AppExt string // ".exe" on Windows, empty on Unix 56 OutFile string // only valid for ModeBuild when len(pkgs) == 1 57 RunArgs []string // only valid for ModeRun 58 Mode Mode 59 } 60 61 func NewDefaultConf(mode Mode) *Config { 62 bin := os.Getenv("GOBIN") 63 if bin == "" { 64 bin = filepath.Join(runtime.GOROOT(), "bin") 65 } 66 conf := &Config{ 67 BinPath: bin, 68 Mode: mode, 69 AppExt: DefaultAppExt(), 70 } 71 return conf 72 } 73 74 func DefaultAppExt() string { 75 if runtime.GOOS == "windows" { 76 return ".exe" 77 } 78 return "" 79 } 80 81 // ----------------------------------------------------------------------------- 82 83 const ( 84 loadFiles = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles 85 loadImports = loadFiles | packages.NeedImports 86 loadTypes = loadImports | packages.NeedTypes | packages.NeedTypesSizes 87 loadSyntax = loadTypes | packages.NeedSyntax | packages.NeedTypesInfo 88 ) 89 90 func Do(args []string, conf *Config) { 91 flags, patterns, verbose := ParseArgs(args, buildFlags) 92 cfg := &packages.Config{ 93 Mode: loadSyntax | packages.NeedDeps | packages.NeedModule | packages.NeedExportFile, 94 BuildFlags: flags, 95 } 96 97 if patterns == nil { 98 patterns = []string{"."} 99 } 100 initial, err := packages.Load(cfg, patterns...) 101 check(err) 102 103 mode := conf.Mode 104 if len(initial) == 1 && len(initial[0].CompiledGoFiles) > 0 { 105 if mode == ModeBuild { 106 mode = ModeInstall 107 } 108 } else if mode == ModeRun { 109 if len(initial) > 1 { 110 fmt.Fprintln(os.Stderr, "cannot run multiple packages") 111 } else { 112 fmt.Fprintln(os.Stderr, "no Go files in matched packages") 113 } 114 return 115 } 116 117 llssa.Initialize(llssa.InitAll) 118 if verbose { 119 llssa.SetDebug(llssa.DbgFlagAll) 120 cl.SetDebug(cl.DbgFlagAll) 121 } 122 123 var needRt bool 124 var rt []*packages.Package 125 prog := llssa.NewProgram(nil) 126 load := func() []*packages.Package { 127 if rt == nil { 128 var err error 129 rt, err = packages.Load(cfg, llssa.PkgRuntime, llssa.PkgPython) 130 check(err) 131 } 132 return rt 133 } 134 prog.SetRuntime(func() *types.Package { 135 needRt = true 136 rt := load() 137 return rt[0].Types 138 }) 139 prog.SetPython(func() *types.Package { 140 rt := load() 141 return rt[1].Types 142 }) 143 144 pkgs := buildAllPkgs(prog, initial, mode, verbose) 145 146 var runtimeFiles []string 147 if needRt { 148 runtimeFiles = allLinkFiles(rt) 149 } 150 if mode != ModeBuild { 151 nErr := 0 152 for _, pkg := range initial { 153 if pkg.Name == "main" { 154 nErr += linkMainPkg(pkg, pkgs, runtimeFiles, conf, mode, verbose) 155 } 156 } 157 if nErr > 0 { 158 os.Exit(nErr) 159 } 160 } 161 } 162 163 func setNeedRuntimeOrPyInit(pkg *packages.Package, needRuntime, needPyInit bool) { 164 v := []byte{'0', '0'} 165 if needRuntime { 166 v[0] = '1' 167 } 168 if needPyInit { 169 v[1] = '1' 170 } 171 pkg.ID = string(v) // just use pkg.ID to mark it needs runtime 172 } 173 174 func isNeedRuntimeOrPyInit(pkg *packages.Package) (needRuntime, needPyInit bool) { 175 if len(pkg.ID) == 2 { 176 return pkg.ID[0] == '1', pkg.ID[1] == '1' 177 } 178 return 179 } 180 181 func buildAllPkgs(prog llssa.Program, initial []*packages.Package, mode Mode, verbose bool) (pkgs []*aPackage) { 182 // Create SSA-form program representation. 183 ssaProg, pkgs, errPkgs := allPkgs(initial, ssa.SanityCheckFunctions) 184 ssaProg.Build() 185 for _, errPkg := range errPkgs { 186 for _, err := range errPkg.Errors { 187 fmt.Fprintln(os.Stderr, err) 188 } 189 fmt.Fprintln(os.Stderr, "cannot build SSA for package", errPkg) 190 } 191 for _, aPkg := range pkgs { 192 pkg := aPkg.Package 193 switch kind, param := cl.PkgKindOf(pkg.Types); kind { 194 case cl.PkgDeclOnly: 195 // skip packages that only contain declarations 196 // and set no export file 197 pkg.ExportFile = "" 198 case cl.PkgLinkIR, cl.PkgLinkExtern, cl.PkgPyModule: 199 pkgPath := pkg.PkgPath 200 if isPkgInLLGo(pkgPath) { 201 pkg.ExportFile = concatPkgLinkFiles(pkgPath) 202 } else { 203 // panic("todo") 204 // TODO(xsw): support packages out of llgo 205 pkg.ExportFile = "" 206 } 207 if kind == cl.PkgLinkExtern { // need to be linked with external library 208 linkFile := os.ExpandEnv(strings.TrimSpace(param)) 209 dir, lib := filepath.Split(linkFile) 210 command := " -l " + lib 211 if dir != "" { 212 command += " -L " + dir[:len(dir)-1] 213 } 214 if isSingleLinkFile(pkg.ExportFile) { 215 pkg.ExportFile = command + " " + pkg.ExportFile 216 } else { 217 pkg.ExportFile = command + pkg.ExportFile 218 } 219 } 220 default: 221 buildPkg(prog, aPkg, mode, verbose) 222 setNeedRuntimeOrPyInit(pkg, prog.NeedRuntime, prog.NeedPyInit) 223 } 224 } 225 return 226 } 227 228 func linkMainPkg(pkg *packages.Package, pkgs []*aPackage, runtimeFiles []string, conf *Config, mode Mode, verbose bool) (nErr int) { 229 pkgPath := pkg.PkgPath 230 name := path.Base(pkgPath) 231 app := conf.OutFile 232 if app == "" { 233 app = filepath.Join(conf.BinPath, name+conf.AppExt) 234 } 235 const N = 3 236 args := make([]string, N, len(pkg.Imports)+len(runtimeFiles)+(N+1)) 237 args[0] = "-o" 238 args[1] = app 239 args[2] = "-Wno-override-module" 240 needRuntime := false 241 needPyInit := false 242 packages.Visit([]*packages.Package{pkg}, nil, func(p *packages.Package) { 243 if p.ExportFile != "" && !isRuntimePkg(p.PkgPath) { // skip packages that only contain declarations 244 args = appendLinkFiles(args, p.ExportFile) 245 need1, need2 := isNeedRuntimeOrPyInit(p) 246 if !needRuntime { 247 needRuntime = need1 248 } 249 if !needPyInit { 250 needPyInit = need2 251 } 252 } 253 }) 254 255 var aPkg *aPackage 256 for _, v := range pkgs { 257 if v.Package == pkg { // found this package 258 aPkg = v 259 break 260 } 261 } 262 263 dirty := false 264 if needRuntime && runtimeFiles != nil { 265 args = append(args, runtimeFiles...) 266 } else { 267 dirty = true 268 fn := aPkg.LPkg.FuncOf(cl.RuntimeInit) 269 fn.MakeBody(1).Return() 270 } 271 if needPyInit { 272 dirty = aPkg.LPkg.PyInit() 273 } 274 275 if dirty && needLLFile(mode) { 276 lpkg := aPkg.LPkg 277 os.WriteFile(pkg.ExportFile, []byte(lpkg.String()), 0644) 278 } 279 280 if verbose || mode != ModeRun { 281 fmt.Fprintln(os.Stderr, "#", pkgPath) 282 } 283 defer func() { 284 if e := recover(); e != nil { 285 nErr = 1 286 } 287 }() 288 289 // TODO(xsw): show work 290 if verbose { 291 fmt.Fprintln(os.Stderr, "clang", args) 292 } 293 err := clang.New("").Exec(args...) 294 check(err) 295 296 if mode == ModeRun { 297 cmd := exec.Command(app, conf.RunArgs...) 298 cmd.Stdin = os.Stdin 299 cmd.Stdout = os.Stdout 300 cmd.Stderr = os.Stderr 301 cmd.Run() 302 } 303 return 304 } 305 306 func buildPkg(prog llssa.Program, aPkg *aPackage, mode Mode, verbose bool) { 307 pkg := aPkg.Package 308 pkgPath := pkg.PkgPath 309 if verbose { 310 fmt.Fprintln(os.Stderr, pkgPath) 311 } 312 if canSkipToBuild(pkgPath) { 313 pkg.ExportFile = "" 314 return 315 } 316 ret, err := cl.NewPackage(prog, aPkg.SSA, pkg.Syntax) 317 check(err) 318 if needLLFile(mode) { 319 pkg.ExportFile += ".ll" 320 os.WriteFile(pkg.ExportFile, []byte(ret.String()), 0644) 321 } 322 aPkg.LPkg = ret 323 } 324 325 func canSkipToBuild(pkgPath string) bool { 326 switch pkgPath { 327 case "unsafe", "runtime", "errors", "sync", "sync/atomic": 328 return true 329 default: 330 return strings.HasPrefix(pkgPath, "internal/") || 331 strings.HasPrefix(pkgPath, "runtime/internal/") 332 } 333 } 334 335 type aPackage struct { 336 *packages.Package 337 SSA *ssa.Package 338 LPkg llssa.Package 339 } 340 341 func allPkgs(initial []*packages.Package, mode ssa.BuilderMode) (prog *ssa.Program, all []*aPackage, errs []*packages.Package) { 342 var fset *token.FileSet 343 if len(initial) > 0 { 344 fset = initial[0].Fset 345 } 346 347 prog = ssa.NewProgram(fset, mode) 348 packages.Visit(initial, nil, func(p *packages.Package) { 349 if p.Types != nil && !p.IllTyped { 350 ssaPkg := prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true) 351 all = append(all, &aPackage{p, ssaPkg, nil}) 352 } else { 353 errs = append(errs, p) 354 } 355 }) 356 return 357 } 358 359 var ( 360 // TODO(xsw): complete build flags 361 buildFlags = map[string]bool{ 362 "-C": true, // -C dir: Change to dir before running the command 363 "-a": false, // -a: force rebuilding of packages that are already up-to-date 364 "-n": false, // -n: print the commands but do not run them 365 "-p": true, // -p n: the number of programs to run in parallel 366 "-race": false, // -race: enable data race detection 367 "-cover": false, // -cover: enable coverage analysis 368 "-covermode": true, // -covermode mode: set the mode for coverage analysis 369 "-v": false, // -v: print the names of packages as they are compiled 370 "-work": false, // -work: print the name of the temporary work directory and do not delete it when exiting 371 "-x": false, // -x: print the commands 372 "-tags": true, // -tags 'tag,list': a space-separated list of build tags to consider satisfied during the build 373 "-pkgdir": true, // -pkgdir dir: install and load all packages from dir instead of the usual locations 374 "-ldflags": true, // --ldflags 'flag list': arguments to pass on each go tool link invocation 375 } 376 ) 377 378 func ParseArgs(args []string, swflags map[string]bool) (flags, patterns []string, verbose bool) { 379 n := len(args) 380 for i := 0; i < n; i++ { 381 arg := args[i] 382 if strings.HasPrefix(arg, "-") { 383 checkFlag(arg, &i, &verbose, swflags) 384 } else { 385 flags, patterns = args[:i], args[i:] 386 return 387 } 388 } 389 flags = args 390 return 391 } 392 393 func SkipFlagArgs(args []string) int { 394 n := len(args) 395 for i := 0; i < n; i++ { 396 arg := args[i] 397 if strings.HasPrefix(arg, "-") { 398 checkFlag(arg, &i, nil, buildFlags) 399 } else { 400 return i 401 } 402 } 403 return -1 404 } 405 406 func checkFlag(arg string, i *int, verbose *bool, swflags map[string]bool) { 407 if pos := strings.IndexByte(arg, '='); pos > 0 { 408 if verbose != nil && arg == "-v=true" { 409 *verbose = true 410 } 411 } else if hasarg, ok := swflags[arg]; ok { 412 if hasarg { 413 *i++ 414 } else if verbose != nil && arg == "-v" { 415 *verbose = true 416 } 417 } else { 418 panic("unknown flag: " + arg) 419 } 420 } 421 422 func allLinkFiles(rt []*packages.Package) (outFiles []string) { 423 outFiles = make([]string, 0, len(rt)) 424 packages.Visit(rt, nil, func(p *packages.Package) { 425 pkgPath := p.PkgPath 426 if isRuntimePkg(pkgPath) { 427 llgoPkgLinkFiles(pkgPath, func(linkFile string) { 428 outFiles = append(outFiles, linkFile) 429 }) 430 } 431 }) 432 return 433 } 434 435 const ( 436 pkgAbi = llgoModPath + "/internal/abi" 437 pkgRuntime = llgoModPath + "/internal/runtime" 438 ) 439 440 func isRuntimePkg(pkgPath string) bool { 441 switch pkgPath { 442 case pkgRuntime, pkgAbi: 443 return true 444 } 445 return false 446 } 447 448 var ( 449 rootDir string 450 ) 451 452 func llgoRoot() string { 453 if rootDir == "" { 454 root := os.Getenv("LLGOROOT") 455 if root == "" { 456 panic("todo: LLGOROOT not set") 457 } 458 rootDir, _ = filepath.Abs(root) 459 } 460 return rootDir 461 } 462 463 func appendLinkFiles(args []string, file string) []string { 464 if isSingleLinkFile(file) { 465 return append(args, file) 466 } 467 return append(args, strings.Split(file[1:], " ")...) 468 } 469 470 func isSingleLinkFile(ret string) bool { 471 return len(ret) > 0 && ret[0] != ' ' 472 } 473 474 func concatPkgLinkFiles(pkgPath string) string { 475 var b strings.Builder 476 var ret string 477 var n int 478 llgoPkgLinkFiles(pkgPath, func(linkFile string) { 479 if n == 0 { 480 ret = linkFile 481 } else { 482 b.WriteByte(' ') 483 b.WriteString(linkFile) 484 } 485 n++ 486 }) 487 if n > 1 { 488 b.WriteByte(' ') 489 b.WriteString(ret) 490 return b.String() 491 } 492 return ret 493 } 494 495 func llgoPkgLinkFiles(pkgPath string, procFile func(linkFile string)) { 496 dir := llgoRoot() + pkgPath[len(llgoModPath):] + "/" 497 llFile := dir + "llgo_autogen.ll" 498 llaFile := llFile + "a" 499 zipf, err := zip.OpenReader(llaFile) 500 if err != nil { 501 procFile(llFile) 502 return 503 } 504 defer zipf.Close() 505 506 for _, f := range zipf.File { 507 procFile(dir + f.Name) 508 } 509 if _, err := os.Stat(llFile); os.IsNotExist(err) { 510 for _, f := range zipf.File { 511 decodeFile(dir+f.Name, f) 512 } 513 } 514 } 515 516 const ( 517 llgoModPath = "github.com/goplus/llgo" 518 ) 519 520 func isPkgInLLGo(pkgPath string) bool { 521 return isPkgInMod(pkgPath, llgoModPath) 522 } 523 524 func isPkgInMod(pkgPath, modPath string) bool { 525 if strings.HasPrefix(pkgPath, modPath) { 526 suffix := pkgPath[len(modPath):] 527 return suffix == "" || suffix[0] == '/' 528 } 529 return false 530 } 531 532 /* 533 func llgoPkgLinkFile(pkgPath string) string { 534 // if kind == cl.PkgLinkBitCode { 535 // return filepath.Join(llgoRoot()+pkgPath[len(llgoModPath):], "llgo_autogen.bc") 536 // } 537 llFile := filepath.Join(llgoRoot()+pkgPath[len(llgoModPath):], "llgo_autogen.ll") 538 if _, err := os.Stat(llFile); os.IsNotExist(err) { 539 decodeLinkFile(llFile) 540 } 541 return llFile 542 } 543 544 // *.ll => *.lla 545 func decodeLinkFile(llFile string) { 546 zipFile := llFile + "a" 547 zipf, err := zip.OpenReader(zipFile) 548 if err != nil { 549 return 550 } 551 defer zipf.Close() 552 f, err := zipf.Open("llgo_autogen.ll") 553 if err != nil { 554 return 555 } 556 defer f.Close() 557 data, err := io.ReadAll(f) 558 if err == nil { 559 os.WriteFile(llFile, data, 0644) 560 } 561 } 562 */ 563 564 func decodeFile(outFile string, zipf *zip.File) (err error) { 565 f, err := zipf.Open() 566 if err != nil { 567 return 568 } 569 defer f.Close() 570 data, err := io.ReadAll(f) 571 if err == nil { 572 err = os.WriteFile(outFile, data, 0644) 573 } 574 return 575 } 576 577 func check(err error) { 578 if err != nil { 579 panic(err) 580 } 581 } 582 583 // -----------------------------------------------------------------------------