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  }