github.com/atrn/dcc@v0.0.0-20220806184050-4470d2553272/main.go (about) 1 // dcc - dependency-driven C/C++ compiler front end 2 // 3 // Copyright © A.Newman 2015. 4 // 5 // This source code is released under version 2 of the GNU Public License. 6 // See the file LICENSE for details. 7 // 8 9 package main 10 11 import ( 12 "fmt" 13 "io" 14 "log" 15 "os" 16 "path/filepath" 17 "runtime" 18 "strconv" 19 "strings" 20 ) 21 22 const ( 23 // The default "hidden" directory where options files reside. 24 // 25 DefaultDccDir = ".dcc" 26 27 // The default "hidden" directory where compiler-generated 28 // dependency files reside. 29 // 30 DefaultDepsDir = ".dcc.d" 31 ) 32 33 var ( 34 // Myname is the program's invocation name. We use it to 35 // prefix log messages and check it to see if we should 36 // work as a C++ compiler driver. 37 // 38 Myname string 39 40 // DccCurrentDirectory is the path of the process working directory at startup 41 // 42 DccCurrentDirectory = MustGetwd() 43 44 // IgnoreDependencies has dcc not perform any dependency checking 45 // and makes it assume everything is out of date, forcing everything 46 // to be rebuilt. 47 // 48 // This is set by the --force command line option. 49 // 50 IgnoreDependencies bool 51 52 // ActualCompiler is the Compiler (compiler.go) for the real 53 // compiler executable. The Compiler type abstracts the 54 // functions of the underlying compiler and lets dcc work with 55 // different compilers, i.e. Microsoft's cl.exe requires 56 // different handling to the gcc-style compilers. 57 // 58 ActualCompiler Compiler 59 60 // DefaultNumJobs is the number of concurrent compilations 61 // dcc will perform by default. The "2 + numcpu" is the 62 // same value used by the ninja build tool. 63 // 64 DefaultNumJobs = 2 + runtime.NumCPU() 65 66 // NumJobs is the number of concurrent compilations dcc will 67 // perform. By default this is two times the number of CPUs. 68 // Why? Well basically because that's what works well for me. 69 // The old rule-of-thumb used to be 1+NCPU but we do more I/O 70 // in our builds these days (C++ especially) so there's more 71 // I/O to overlap. Ninja uses 2+NCPU by default and dcc 72 // used to do that but 2*NCPU works better for what I build 73 // on my machines (not so modern C++, SSDs, 4/8 cores). 74 // 75 NumJobs = GetenvInt("NUMJOBS", DefaultNumJobs) 76 77 // DccDir is the name of the directory where dcc-related files 78 // are stored. If this directory does not exist the current 79 // directory is used. DccDir defaults to ".dcc" allowing users 80 // to "hide" the various build files by putting them in a "dot" 81 // directory (which makes for cleaner source directories). 82 // 83 DccDir = Getenv("DCCDIR", DefaultDccDir) 84 85 // DepsDir is the name of the directory where the per-object 86 // file dependency files (.d files) are stored. This is usually 87 // ".dcc.d" except on windows. 88 // 89 DepsDir = Getenv("DEPSDIR", DefaultDepsDir) 90 91 // ObjsDir is the name of the directory where object files 92 // are written. It is usually a relative path, relative to 93 // the source file. 94 // 95 ObjsDir = "." 96 97 // Quiet disables most messages when true. Usually dcc will 98 // print, to stderr, the commands being executed. 99 // 100 Quiet = false 101 102 // Verbose, when true, enables various messages from dcc. 103 // 104 Verbose = false 105 106 // Debug, when true, enables debug output. 107 // 108 Debug = false 109 110 // Debug "FindFile" operations when true. 111 // 112 DebugFind = false 113 114 // CCFILE is the name of the "options file" that defines 115 // the name of the C compiler. 116 // 117 CCFILE = Getenv("CCFILE", "CC") 118 119 // CXXFILE is the name of the "options file" that defines 120 // the name of the C++ compiler. 121 // 122 CXXFILE = Getenv("CXXFILE", "CXX") 123 124 // CFLAGSFILE is the name of the "options file" that 125 // defines the options passed to the C compiler. 126 // 127 CFLAGSFILE = Getenv("CFLAGSFILE", "CFLAGS") 128 129 // CXXFLAGSFILE is the name of the "options file" that 130 // defines the options passed to the C++ compiler. 131 // 132 CXXFLAGSFILE = Getenv("CXXFLAGSFILE", "CXXFLAGS") 133 134 // LDFLAGSFILE is the name of the "options file" that defines 135 // the options passed to the linker (aka "loader"). 136 // 137 LDFLAGSFILE = Getenv("LDFLAGSFILE", "LDFLAGS") 138 139 // LIBSFILE is the name of the "options file" that defines 140 // the names of the libraries used when linking. 141 // 142 LIBSFILE = Getenv("LIBSFILE", "LIBS") 143 ) 144 145 func main() { 146 Myname = GetProgramName() 147 ConfigureLogger(Myname) 148 149 // Enable debug as early as possible. 150 151 if s := os.Getenv("DCCDEBUG"); s != "" { 152 if s == "find" { 153 DebugFind = true 154 } else if s == "all" { 155 Debug = true 156 DebugFind = true 157 } else { 158 Debug = true 159 } 160 } else { 161 for i := 1; i < len(os.Args); i++ { 162 if os.Args[i] == "--debug" { 163 Debug = true 164 } 165 if os.Args[i] == "--debug-find" { 166 DebugFind = true 167 } 168 } 169 } 170 171 if !Debug { 172 defer CatchPanics() 173 } 174 175 runningMode := ModeNotSpecified 176 outputPathname := "" 177 dasho := "" 178 dashm := "" 179 writeCompileCommands := false 180 appendCompileCommands := false 181 182 cCompiler := makeCompilerOption(CCFILE, platform.DefaultCC) 183 cppCompiler := makeCompilerOption(CXXFILE, platform.DefaultCXX) 184 185 // We assume we're compiling C and define a function to switch 186 // things to C++. We'll check for that next. 187 // 188 underlyingCompiler, optionsFilename := cCompiler, CFLAGSFILE 189 cplusplus := func() { 190 underlyingCompiler, optionsFilename = cppCompiler, CXXFLAGSFILE 191 } 192 193 // Rules for deciding if we're need to compile with C++: 194 // 195 // - our invocation name ends in "++" 196 // - the --cpp option was supplied 197 // - any of the input files are C++ source files 198 // 199 if strings.HasSuffix(Myname, "++") { 200 cplusplus() 201 } else { 202 for i := 1; i < len(os.Args); i++ { 203 if os.Args[i] == "--cpp" { 204 cplusplus() 205 break 206 } 207 if os.Args[i][0] != '-' && IsCPlusPlusFile(os.Args[i]) { 208 cplusplus() 209 break 210 } 211 } 212 } 213 214 // The state... 215 // 216 // Options can be read from files, other state variables are 217 // set by interpreting command line options. The 218 // sourceFileIndex is explained below, basically it defines 219 // which files in inputFilenames are the names of source files. 220 // 221 var ( 222 err error 223 compilerOptions = new(Options) 224 linkerOptions = new(Options) 225 libraryFiles = new(Options) 226 otherFiles = new(Options) 227 libraryDirs = make([]string, 0) 228 inputFilenames = make([]string, 0) 229 sourceFilenames = make([]string, 0) 230 sourceFileIndex = make([]int, 0) // position of sourceFilenames[x] within inputFilenames 231 frameworks = make([]string, 0) 232 ) 233 234 // The default set of libraryDirs comes from the platform's standard directories. 235 // 236 libraryDirs = append(libraryDirs, platform.LibraryPaths...) 237 238 // Get compiler options from the options file. 239 // 240 if path, found := FindFile(optionsFilename); found { 241 _, err := compilerOptions.ReadFromFile(path, nil) 242 if err != nil { 243 log.Fatal(err) 244 } 245 } 246 247 // A -o in an options file is problematic, we keep that information elsewhere. 248 // So we have to look for a -o and transfer it's value to our variable and 249 // remove it from the options. 250 // 251 if index := compilerOptions.OptionIndex("-o"); index != -1 { 252 if index == len(compilerOptions.Values)-1 { 253 log.Fatalf("invalid -o option in compiler options file %q", optionsFilename) 254 } 255 dasho = compilerOptions.Values[index+1] 256 compilerOptions.Values = append(compilerOptions.Values[0:index], compilerOptions.Values[index+2:]...) 257 } 258 259 // We use the compilerOptions modtime as a dependency but in 260 // the unlikely case the compiler's modtime is newer we'll use 261 // that instead - automatic rebuilds when the compiler 262 // changes (v.unsafe - ref. Thompson's Reflections on Trust). 263 // 264 compilerOptions.SetModTime(MostRecentModTime(compilerOptions, underlyingCompiler)) 265 266 // Now we do the same for the linker options. We use the "LDFLAGS" file. 267 // 268 if path, found := FindFile(LDFLAGSFILE); found { 269 _, err = linkerOptions.ReadFromFile(path, func(s string) string { 270 // Collect directories named via -L in libraryDirs 271 if strings.HasPrefix(s, "-L") { 272 libraryDirs = append(libraryDirs, s[2:]) 273 } 274 return s 275 }) 276 } 277 setLinkOpts := false 278 if os.IsNotExist(err) { 279 setLinkOpts = true 280 // remember that we need to set the linker options 281 // from the compiler options ... but only when 282 // linking an executable. 283 // linkerOptions.SetFrom(compilerOptions) 284 } else if err != nil { 285 log.Fatal(err) 286 } 287 288 // Libraries 289 // 290 if err = readLibs(LIBSFILE, libraryFiles, &libraryDirs, &frameworks); err != nil { 291 log.Fatal(err) 292 } 293 if Debug { 294 log.Printf("LIBS: %d libraries, %d dirs, %d frameworks", libraryFiles.Len(), len(libraryDirs), len(frameworks)) 295 log.Println("LIBS:", libraryFiles.String()) 296 log.Printf("LIBS: paths %v", libraryDirs) 297 log.Printf("LIBS: frameworks %v", frameworks) 298 } 299 300 // Helper to set our running mode, once and once only. 301 // 302 setMode := func(arg string, mode RunningMode) { 303 if runningMode != ModeNotSpecified { 304 fmt.Fprintf(os.Stderr, "%s: a running mode has already been defined\n\n", arg) 305 UsageError(os.Stderr, 1) 306 } 307 runningMode = mode 308 } 309 310 // Helper to take a filename supplied on the command line 311 // and incorporate it in the appropriate place according 312 // to its type. 313 // 314 collectInputFile := func(path string) { 315 inputFilenames = append(inputFilenames, path) 316 switch { 317 case FileWillBeCompiled(path): 318 sourceFilenames = append(sourceFilenames, path) 319 sourceFileIndex = append(sourceFileIndex, len(inputFilenames)-1) 320 321 case IsLibraryFile(path): 322 libraryFiles.Append(path) 323 324 default: 325 otherFiles.Append(path) 326 } 327 } 328 329 // Helper to issue a warning message re. the -o option being set more 330 // that once and that we accept the first -o to set an output path 331 // and ignore subsequent ones. 332 // 333 warnOutputPathAlreadySet := func(ignoredPath string) { 334 log.Printf("warning: output pathname has already been set, ignoring this -o (%s)", ignoredPath) 335 } 336 337 // Windows is special... When using Microsoft's "cl.exe" 338 // as the underlying compiler, on Windows, we need to support 339 // its command line option syntax which allows for '/' as an 340 // option specifier. 341 // 342 // Likewise darwin (MacOS) is special in that it has the 343 // concept of frameworks. 344 // 345 windows := runtime.GOOS == "windows" 346 macos := runtime.GOOS == "darwin" 347 348 // Go over the command line and collect option and the names of 349 // source files to be compiled and/or files to pass to the linker. 350 // 351 for i := 1; i < len(os.Args); i++ { 352 arg := os.Args[i] 353 switch { 354 case arg == "--help": 355 UsageError(os.Stdout, 0) 356 357 case windows && arg[0] == '/': 358 // Compiler option for cl.exe 359 // 360 switch { 361 case arg == "/c": 362 setMode(arg, CompileSourceFiles) 363 364 case strings.HasPrefix(arg, "/Fo"): 365 dasho = arg[3:] 366 367 case strings.HasPrefix(arg, "/Fe"): 368 outputPathname = arg[3:] 369 370 default: 371 if _, err := Stat(arg); err != nil { 372 compilerOptions.Append(arg) 373 compilerOptions.Changed() 374 } else { 375 collectInputFile(arg) 376 } 377 } 378 379 case arg[0] != '-': 380 // Non-option arguments are filenames, possibly sources to compile or 381 // files to pass to the linker - classical cc(1) behaviour. 382 // 383 collectInputFile(arg) 384 385 case arg == "--write-compile-commands": 386 writeCompileCommands = true 387 388 case arg == "--append-compile-commands": 389 appendCompileCommands = true 390 391 case arg == "--cpp": 392 break // ignore, handled above 393 394 case arg == "--force": 395 IgnoreDependencies = true 396 397 case arg == "--quiet": 398 Quiet = true 399 400 case arg == "--verbose": 401 Verbose = true 402 403 case arg == "--debug": 404 break // also handled above 405 406 case arg == "--version": 407 fmt.Print(versionNumber) 408 os.Exit(0) 409 410 case strings.HasPrefix(arg, "-j"): 411 if arg == "-j" { 412 NumJobs = runtime.NumCPU() 413 } else if n, err := strconv.Atoi(arg[2:]); err != nil { 414 log.Fatalf("%s: %s", arg, err) 415 } else if n < 1 { 416 log.Fatalf("%s: bad number of jobs", arg) 417 } else { 418 NumJobs = n 419 } 420 421 case arg == "--objdir": 422 if i++; i < len(os.Args) { 423 ObjsDir = os.Args[i] 424 } else { 425 log.Fatalf("%s: directory name required", arg) 426 } 427 428 case arg == "--exe": 429 if i++; i < len(os.Args) { 430 outputPathname = os.Args[i] 431 } else { 432 log.Fatalf("%s: program filename required", arg) 433 } 434 setMode(arg, CompileAndLink) 435 436 case arg == "--lib": 437 if i++; i < len(os.Args) { 438 outputPathname = os.Args[i] 439 } else { 440 log.Fatalf("%s: library filename required", arg) 441 } 442 setMode(arg, CompileAndMakeLib) 443 444 case arg == "--dll": 445 if i++; i < len(os.Args) { 446 outputPathname = os.Args[i] 447 } else { 448 log.Fatalf("%s: library filename required", arg) 449 } 450 setMode(arg, CompileAndMakeDLL) 451 452 case arg == "--plugin": 453 if i++; i < len(os.Args) { 454 outputPathname = os.Args[i] 455 } else { 456 log.Fatalf("%s: library filename required", arg) 457 } 458 setMode(arg, CompileAndMakePlugin) 459 460 case macos && arg == "-framework": 461 libraryFiles.Append(arg) 462 if i++; i < len(os.Args) { 463 libraryFiles.Append(os.Args[i]) 464 } 465 libraryFiles.Changed() 466 467 case macos && (arg == "-macosx_version_min" || arg == "-macosx_version_max"): 468 linkerOptions.Append(arg) 469 if i++; i < len(os.Args) { 470 linkerOptions.Append(os.Args[i]) 471 } 472 linkerOptions.Changed() 473 474 case macos && arg == "-bundle_loader": 475 linkerOptions.Append(arg) 476 if i++; i < len(os.Args) { 477 linkerOptions.Append(os.Args[i]) 478 } 479 linkerOptions.Changed() 480 481 case arg == "-o": 482 if i++; i < len(os.Args) { 483 if outputPathname != "" { 484 warnOutputPathAlreadySet(outputPathname) 485 } else { 486 dasho = os.Args[i] 487 } 488 } else { 489 log.Fatal(arg + " pathname parameter required") 490 } 491 492 case strings.HasPrefix(arg, "-o"): 493 if outputPathname != "" { 494 warnOutputPathAlreadySet(outputPathname) 495 } else { 496 dasho = arg[2:] 497 } 498 499 case strings.HasPrefix(arg, "-L"): 500 linkerOptions.Append(arg) 501 linkerOptions.Changed() 502 libraryDirs = append(libraryDirs, arg[2:]) 503 504 case strings.HasPrefix(arg, "-l"): 505 libraryFiles.Prepend(arg) 506 507 case arg == "-c": 508 setMode(arg, CompileSourceFiles) 509 510 case arg == "-m32": 511 if dashm != "" && dashm != arg { 512 log.Fatalf("conflicting options %s and %s", dashm, arg) 513 } 514 dashm = arg 515 516 case arg == "-m64": 517 if dashm != "" && dashm != arg { 518 log.Fatalf("conflicting options %s and %s", dashm, arg) 519 } 520 dashm = arg 521 522 default: 523 compilerOptions.Append(arg) 524 compilerOptions.Changed() 525 } 526 } 527 528 // We have to at least have one filename to process. It doesn't 529 // need to be a source file but we need something. 530 // 531 if len(inputFilenames) == 0 { 532 UsageError(os.Stderr, 1) 533 } 534 535 // ---------------------------------------------------------------- 536 537 // If no mode was explicitly specified compile and link like cc(1). 538 // 539 if runningMode == ModeNotSpecified { 540 runningMode = CompileAndLink 541 } 542 543 // Deal with any -m32/-m64 option 544 // 545 if dashm != "" { 546 if platform.SelectTarget != nil { 547 err = platform.SelectTarget(&platform, dashm) 548 } 549 if err != nil { 550 log.Fatal(err) 551 } 552 } 553 554 // Deal with any -o option. 555 // 556 // The cc(1) behaviour is: 557 // 558 // - without -c the -o's parameter names the executable 559 // - -c -o<path> names the object file but is only permitted when compiling a single file 560 // 561 // Dcc should add: 562 // 563 // -c -o <path> permitted with multiple files if <path> names 564 // a directory. 565 // 566 if dasho != "" && runningMode == CompileSourceFiles && len(sourceFilenames) > 1 { 567 log.Fatal("-o <file> may not be supplied with -c and more one input file") 568 } 569 570 // outputPathname will be empty if no dcc-specific option that sets it 571 // was used. If a -o was supplied we propogate its value to outputPathname. 572 // 573 if outputPathname == "" && dasho != "" { 574 outputPathname = dasho 575 dasho = "" 576 } 577 578 // objdir is the object file directory. 579 // 580 objdir := dasho 581 if objdir == "" { 582 objdir = ObjsDir 583 } 584 585 // Next, replace any source file names with their object file 586 // name in the inputFilenams slice. This is then the list of 587 // files given to the linker, or librarian. During this 588 // replacement we replace any header file names with empty 589 // strings as they are not used as inputs to the linker or 590 // librarian. We do this so we don't invalidate the indices 591 // stored in the sourceFileIndex slice during the loop. The 592 // empty strings are removed once we've done this pass over 593 // the names. And we only do all this if we need to since 594 // pre-compiling headers is relatively rare. 595 // 596 removeEmptyNames := false 597 for index, filename := range sourceFilenames { 598 if IsSourceFile(filename) { 599 inputFilenames[sourceFileIndex[index]] = ObjectFilename(filename, objdir) 600 } else if IsHeaderFile(filename) { 601 inputFilenames[sourceFileIndex[index]] = "" 602 removeEmptyNames = true 603 } 604 } 605 606 // NB. sourceFileIndex is no longer used/required and we're 607 // possibly about to re-write th inputFilenames slice which 608 // would invalidate the sourceFileIndex anyway so we nil it 609 // to help detect now invalid accesses. 610 // 611 sourceFileIndex = nil 612 613 // We typically don't do this. Empty strings only end up in 614 // inputFilenames if a header file is being pre-compiled. 615 // 616 if removeEmptyNames { 617 tmp := make([]string, 0, len(inputFilenames)) 618 for _, filename := range inputFilenames { 619 if filename != "" { 620 tmp = append(tmp, filename) 621 } 622 } 623 inputFilenames = tmp 624 // If there are no inputs remaining then we do not need to 625 // link (or create a library). 626 // 627 if len(inputFilenames) == 0 { 628 runningMode = CompileSourceFiles 629 } 630 } 631 632 // Update any -l<name> library references in LibraryFiles with 633 // the actual file path. We want to "stat" these files to determine 634 // if they're newer than the executable/DLL that depends on them. 635 // 636 for index, name := range libraryFiles.Values { 637 if strings.HasPrefix(name, "-l") { 638 path, _, found, err := FindLibrary(libraryDirs, name[2:]) 639 switch { 640 case err != nil: 641 log.Fatal(err) 642 case !found: 643 log.Printf("warning: %q library not found on path %v", name, libraryDirs) 644 // ... and we let the linker deal with it 645 default: 646 libraryFiles.Values[index] = path 647 if Debug { 648 log.Print("DEBUG LIB: '", name[2:], "' -> ", path) 649 } 650 } 651 } 652 } 653 654 // Set the actual compiler we'll be using. 655 // 656 ActualCompiler = GetCompiler(underlyingCompiler.String()) 657 658 // Generate a compile_commands.json if requested. 659 // 660 if appendCompileCommands { 661 if err := AppendCompileCommandsDotJson(filepath.Join(objdir, CompileCommandsFilename), sourceFilenames, compilerOptions, objdir); err != nil { 662 log.Fatal(err) 663 } 664 } else if writeCompileCommands { 665 if err := WriteCompileCommandsDotJson(filepath.Join(objdir, CompileCommandsFilename), sourceFilenames, compilerOptions, objdir); err != nil { 666 log.Fatal(err) 667 } 668 } 669 670 // And now we're ready to compile everything. 671 // 672 if !CompileAll(sourceFilenames, compilerOptions, objdir) { 673 os.Exit(1) 674 } 675 676 // Then, if required, link an executable or DLL, or create a 677 // static library. 678 // 679 switch runningMode { 680 case CompileAndLink: 681 if setLinkOpts && linkerOptions.Empty() { 682 linkerOptions.SetFrom(compilerOptions) 683 } 684 err = Link(outputPathname, inputFilenames, libraryFiles, linkerOptions, otherFiles, frameworks) 685 686 case CompileAndMakeDLL: 687 err = Dll(outputPathname, inputFilenames, libraryFiles, linkerOptions, otherFiles, frameworks) 688 689 case CompileAndMakePlugin: 690 err = Plugin(outputPathname, inputFilenames, libraryFiles, linkerOptions, otherFiles, frameworks) 691 692 case CompileAndMakeLib: 693 err = Lib(outputPathname, inputFilenames) 694 } 695 696 // And that's it. Report any final error and exit. 697 // 698 if err != nil { 699 log.Print(err) 700 os.Exit(1) 701 } 702 os.Exit(0) 703 } 704 705 // Helper function to create an Options (see options.go) that 706 // defines the underlying compiler, either CC and CXX depending 707 // upon mode. 708 // 709 // This function is given the name of the file and the default 710 // command name. If an environment variable with the same name 711 // as the file is set we use its value, or a default. Then we 712 // search for a, possibly platform and/or architecture 713 // specific, file with that name. If that file exists we use 714 // its contents. 715 // 716 // We need to do the above twice which is why its a function. 717 // 718 func makeCompilerOption(name, defcmd string) *Options { 719 cmd := Getenv(name, defcmd) 720 opts := new(Options) 721 path, fileExists := FindFile(name) 722 if fileExists { 723 _, err := opts.ReadFromFile(path, nil) 724 if err != nil { 725 log.Fatal(err) 726 } 727 } else { 728 opts.Append(cmd) 729 } 730 if opts.Empty() { 731 opts.Append(defcmd) 732 } 733 return opts 734 } 735 736 // UsageError outputs a program usage message to the given 737 // writer and exits the process with the given status. 738 // 739 func UsageError(w io.Writer, status int) { 740 fmt.Fprintf(w, `Usage: %s [options] [compiler-options] filename... 741 742 Options, other than those listed below, are passed to the underlying 743 compiler. Any -c or -o and similar options are noted and used to control 744 linking and object file locations. Compilation is done in parallel using, 745 by default, as many jobs as there are CPUs and dependency files written 746 to a directory alongside the object files. 747 748 Options: 749 --exe path Create executable program 'path'. 750 --lib path Create static library 'path'. 751 --dll path Create shared/dynamic library 'path'. 752 --objdir path Create object files in 'path'. 753 -j[N] Use 'N' compile jobs (note single dash, default is one per CPU). 754 --cpp Compile source files as C++. 755 --force Ignore dependencies, always compile/link/lib. 756 --clean Remove dcc-maintained files. 757 --quiet Disable non-error messages. 758 --verbose Show more output. 759 --debug Enable debug messages. 760 --version Report dcc version and exit. 761 --write-compile-commands 762 Output a compile_commands.json file. 763 --append-compile-commands 764 Append to an existng compile_commands.json file 765 if it exists. 766 767 With anything else is passed to the underlying compiler. 768 769 Environment 770 CC C compiler (%s). 771 CXX C++ compiler (%s). 772 DEPSDIR Name of .d file directory (%s). 773 OBJDIR Name of .o file directory (%s). 774 DCCDIR Name of the dcc-options directory (%s). 775 NJOBS Number of compile jobs (%d). 776 777 The following variables define the actual names used for 778 the options files (see "Files" below). 779 780 CCFILE Name of the CC options file. 781 CXXFILE Name of the CXX options files. 782 CFLAGSFILE Name of the CFLAGS options file. 783 CXXFLAGSFILE Name of the CXXFLAGS options file. 784 LDFLAGSFILE Name of the LDFLAGS options file. 785 LIBSFILE Name of the LIBS options file. 786 787 Files 788 CC C compiler name. 789 CXX C++ compiler name. 790 CFLAGS Compiler options for C. 791 CXXFLAGS Compiler options for C++. 792 LDFLAGS Linker options. 793 LIBS Libraries and library paths. 794 795 Options files may be put in a directory named by the DCCDIR 796 environment variable, ".dcc" by default, or if that directory 797 does not exist, the current directory. 798 799 Options files are text files storing compiler and linker options. 800 The contents are turned into program options by removing #-style 801 line comments and using the whitespace separated words as individual 802 program arguments. Options files act as dependencies and invoke 803 rebuilds when changed. 804 805 Platform-specific file naming may be used to have different options 806 files for different platforms and/or architectures. When opening a 807 file dcc appends extensions formed from the host OS and archtecture 808 to the file name and tries to use those files. The first extension 809 tried is of the form ".<os>_<arch>", followed by ".<os>" and finally 810 no extension. 811 812 <os> is something like "windows", "linux", "darwin", "freebsd". 813 <arch> is one of "amd64", "386", "arm8le" , "s390x", etc... 814 The actual names come from the Go runtime. This host is "%s_%s". 815 816 Environment variables may be used to define the names of the 817 different options files and select specific options. 818 819 `, 820 Myname, 821 platform.DefaultCC, 822 platform.DefaultCXX, 823 DefaultDepsDir, 824 ObjsDir, 825 DccDir, 826 DefaultNumJobs, 827 runtime.GOOS, 828 runtime.GOARCH, 829 ) 830 os.Exit(status) 831 } 832 833 // CatchPanics catches and reports panics. It is intended to be used 834 // at the top level of main to avoid printing unsightly stack traces. 835 // 836 func CatchPanics() { 837 if x := recover(); x != nil { 838 if err, ok := x.(error); ok { 839 fmt.Fprintf(os.Stderr, "UNHANDLED ERROR: %s\n", err.Error()) 840 } else { 841 fmt.Fprintf(os.Stderr, "PANIC: %v\n", x) 842 } 843 os.Exit(1) 844 } 845 } 846 847 func readLibs(libsFile string, libraryFiles *Options, libraryDirs *[]string, frameworks *[]string) error { 848 var frameworkDirs []string 849 captureNext := false 850 prevs := "" 851 path, found := FindFile(libsFile) 852 if !found { 853 return nil 854 } 855 _, err := libraryFiles.ReadFromFile(path, func(s string) string { 856 if Debug { 857 log.Printf("LIBS: filter %q", s) 858 } 859 if captureNext { 860 if prevs == "-L" { 861 *libraryDirs = append(*libraryDirs, s) 862 } else if prevs == "-F" { 863 frameworkDirs = append(frameworkDirs, s) 864 } else { 865 *frameworks = append(*frameworks, s) 866 } 867 captureNext = false 868 prevs = s 869 return "" 870 } 871 if s == "-framework" { 872 *frameworks = append(*frameworks, s) 873 captureNext = true 874 prevs = s 875 return "" 876 } 877 if s == "-F" || s == "-L" { 878 captureNext = true 879 prevs = s 880 return "" 881 } 882 if strings.HasPrefix(s, "-F") { 883 frameworkDirs = append(frameworkDirs, s[2:]) 884 prevs = s 885 return "" 886 } 887 if strings.HasPrefix(s, "-L") { 888 *libraryDirs = append(*libraryDirs, s[2:]) 889 prevs = s 890 return "" 891 } 892 if strings.HasPrefix(s, "-l") { 893 if path, _, found, err := FindLibrary(*libraryDirs, s[2:]); err != nil { 894 log.Printf("warning: failed to find %s: %s", *libraryDirs, err) 895 } else if found { 896 return path 897 } 898 prevs = s 899 } 900 return s 901 }) 902 if err != nil { 903 return err 904 } 905 for index := range frameworkDirs { 906 frameworkDirs[index] = "-F" + frameworkDirs[index] 907 } 908 *frameworks = append(frameworkDirs, (*frameworks)...) 909 return nil 910 }