| #!/usr/bin/python |
| """Wrapper to selectively run gollvm instead of gccgo. |
| |
| This is a shim script that intercepts invocations of 'gccgo' and then |
| in turn invokes either the real gccgo driver or a copy of gollvm |
| instead, depending on the arguments and on environment variables. |
| |
| When performing a Go build with gccgo, the Go command will typically |
| invoke gccgo once for each compilation step, which might look like |
| |
| gccgo -I ... -o objfile.o -g <options> file.go file2.go ... fileN.go |
| |
| and then a final invocation will be made at the link step, e.g. |
| |
| gccgo -L ... somearchive.a ... -o binary |
| |
| The goal of this shim is to convert invocations of the first form to |
| llvm-goparse invocations, and to ignore invocations of the second form |
| and just pass them on to gccgo. |
| |
| We also tack on a set of additional "-L" options to the llvm-goparse |
| invocation so that it can find the go runtime libraries, and intercept |
| the "-o" option so that we can run the asembler afterwards. |
| |
| To use this script, you will need a copy of GCCGO, e.g. the directory |
| produced by running "make all && make install" in a GCCGO build tree. |
| From within the gccgo install dir, run |
| |
| gollvm-wrap.py --install |
| |
| This will modify the install directory to insert the wrapper into the |
| compilation path. |
| |
| """ |
| |
| import getopt |
| import os |
| import re |
| import subprocess |
| import sys |
| |
| import script_utils as u |
| |
| # Echo command before executing |
| flag_echo = True |
| |
| # Dry run mode |
| flag_dryrun = False |
| |
| # gccgo only mode |
| flag_nollvm = False |
| |
| # trace llvm-goparse invocations |
| flag_trace_llinvoc = False |
| |
| |
| def docmd(cmd): |
| """Execute a command.""" |
| if flag_echo: |
| sys.stderr.write("executing: " + cmd + "\n") |
| if flag_dryrun: |
| return |
| u.docmd(cmd) |
| |
| |
| def form_golibargs(driver): |
| """Form correct go library args.""" |
| ddir = os.path.dirname(driver) |
| bdir = os.path.dirname(ddir) |
| cmd = "find %s/lib64 -name runtime.gox -print" % bdir |
| lines = u.docmdlines(cmd) |
| if not lines: |
| u.error("no output from %s -- bad gccgo install dir?" % cmd) |
| line = lines[0] |
| rdir = os.path.dirname(line) |
| u.verbose(1, "libdir is %s" % rdir) |
| return ["-L", rdir] |
| |
| |
| def perform(): |
| """Main driver routine.""" |
| |
| u.verbose(1, "argv: %s" % " ".join(sys.argv)) |
| |
| # llvm-goparse should be available somewhere in PATH, error if not |
| lines = u.docmdlines("which llvm-goparse", True) |
| if not lines: |
| u.error("no 'llvm-goparse' in PATH -- can't proceed") |
| |
| # Perform a walk of the command line arguments looking for Go files. |
| reg = re.compile(r"^\S+\.go$") |
| foundgo = False |
| for clarg in sys.argv[1:]: |
| m = reg.match(clarg) |
| if m: |
| foundgo = True |
| break |
| |
| if not foundgo or flag_nollvm: |
| # No go files. Invoke real gccgo. |
| bd = os.path.dirname(sys.argv[0]) |
| driver = "%s/gccgo.real" % bd |
| u.verbose(1, "driver path is %s" % driver) |
| args = [sys.argv[0]] + sys.argv[1:] |
| u.verbose(1, "args: '%s'" % " ".join(args)) |
| if not os.path.exists(driver): |
| usage("internal error: %s does not exist [most likely this " |
| "script was not installed correctly]" % driver) |
| os.execv(driver, args) |
| u.error("exec failed: %s" % driver) |
| |
| # These hold the arguments of -I and -L options |
| largs = [] |
| iargs = [] |
| |
| # Create a set of massaged args. |
| nargs = [] |
| skipc = 0 |
| outfile = None |
| asmfile = None |
| for ii in range(1, len(sys.argv)): |
| clarg = sys.argv[ii] |
| if skipc != 0: |
| skipc -= 1 |
| continue |
| if clarg == "-o": |
| skipc = 1 |
| outfile = sys.argv[ii+1] |
| asmfile = "%s.s" % outfile |
| nargs.append("-o") |
| nargs.append(asmfile) |
| continue |
| if clarg == "-I": |
| skipc = 1 |
| iarg = sys.argv[ii+1] |
| iargs.append(iarg) |
| continue |
| if clarg == "-L": |
| skipc = 1 |
| larg = sys.argv[ii+1] |
| largs.append(larg) |
| continue |
| nargs.append(clarg) |
| |
| if not asmfile or not outfile: |
| u.error("fatal error: unable to find -o " |
| "option in clargs: %s" % " ".join(sys.argv)) |
| golibargs = form_golibargs(sys.argv[0]) |
| nargs += golibargs |
| if largs: |
| nargs.append("-L") |
| nargs.append(":".join(largs)) |
| if iargs: |
| nargs.append("-I") |
| nargs.append(":".join(iargs)) |
| u.verbose(1, "revised args: %s" % " ".join(nargs)) |
| |
| # Invoke gollvm. |
| driver = "llvm-goparse" |
| u.verbose(1, "driver path is %s" % driver) |
| nargs = ["llvm-goparse"] + nargs |
| if flag_trace_llinvoc: |
| u.verbose(0, "+ %s" % " ".join(nargs)) |
| rc = subprocess.call(nargs) |
| if rc != 0: |
| u.verbose(1, "return code %d from %s" % (rc, " ".join(nargs))) |
| return 1 |
| |
| # Invoke the assembler |
| ascmd = "as %s -o %s" % (asmfile, outfile) |
| u.verbose(1, "asm command is: %s" % ascmd) |
| rc = u.docmdnf(ascmd) |
| if rc != 0: |
| u.verbose(1, "return code %d from %s" % (rc, ascmd)) |
| return 1 |
| |
| return 0 |
| |
| |
| def install_shim(scriptpath): |
| """Install shim into gccgo install dir.""" |
| |
| # Make sure we're in the right place (gccgo install dir) |
| if not os.path.exists("bin"): |
| usage("expected to find bin subdir") |
| if not os.path.exists("lib64/libgo.so"): |
| usage("expected to find lib64/libgo.so") |
| if not os.path.exists("bin/gccgo"): |
| usage("expected to find bin/gccgo") |
| |
| # Copy script, or update if already in place. |
| docmd("cp %s bin" % scriptpath) |
| sdir = os.path.dirname(scriptpath) |
| docmd("cp %s/script_utils.py bin" % sdir) |
| |
| # Check to see if installed already |
| if os.path.exists("bin/gccgo.real") and os.path.exists("bin/gollvm-wrap.py"): |
| u.error("wrapper appears to be installed already in this dir") |
| |
| # Move aside the real gccgo binary |
| docmd("mv bin/gccgo bin/gccgo.real") |
| |
| # Emit a script into gccgo |
| if flag_dryrun: |
| sys.stderr.write("<emit script into bin/gccgo>\n") |
| else: |
| try: |
| with open("./bin/gccgo", "w") as wf: |
| here = os.getcwd() |
| wf.write("#!/bin/sh\n") |
| wf.write("P=%s/bin/gollvm-wrap.py\n" % here) |
| wf.write("exec python ${P} $*\n") |
| except IOError: |
| u.error("open/write failed for bin/gccgo wrapper") |
| docmd("chmod 0755 bin/gccgo") |
| |
| # Success |
| u.verbose(0, "wrapper installed successfully") |
| |
| # Done |
| return 0 |
| |
| |
| def usage(msgarg): |
| """Print usage and exit.""" |
| if msgarg: |
| sys.stderr.write("error: %s\n" % msgarg) |
| print """\ |
| usage: %s <gccgo args> |
| |
| Options (via command line) |
| --install installs wrapper into gccgo directory |
| |
| Options (via GOLLVM_WRAP_OPTIONS): |
| -t trace llvm-goparse executions |
| -d increase debug msg verbosity level |
| -e show commands being invoked |
| -D dry run (echo cmds but do not execute) |
| -G pure gccgo compile (no llvm-goparse invocations) |
| |
| """ % os.path.basename(sys.argv[0]) |
| sys.exit(1) |
| |
| |
| def parse_env_options(): |
| """Option parsing from env var.""" |
| global flag_echo, flag_dryrun, flag_nollvm, flag_trace_llinvoc |
| |
| optstr = os.getenv("GOLLVM_WRAP_OPTIONS") |
| if not optstr: |
| return |
| args = optstr.split() |
| |
| try: |
| optlist, _ = getopt.getopt(args, "detDG") |
| except getopt.GetoptError as err: |
| # unrecognized option |
| usage(str(err)) |
| |
| for opt, _ in optlist: |
| if opt == "-d": |
| u.increment_verbosity() |
| elif opt == "-e": |
| flag_echo = True |
| elif opt == "-t": |
| flag_trace_llinvoc = True |
| elif opt == "-D": |
| flag_dryrun = True |
| elif opt == "-G": |
| flag_nollvm = True |
| u.verbose(1, "env var options parsing complete") |
| |
| |
| # Setup |
| u.setdeflanglocale() |
| parse_env_options() |
| |
| # Either --install mode or regular mode |
| if len(sys.argv) == 2 and sys.argv[1] == "--install": |
| prc = install_shim(sys.argv[0]) |
| else: |
| prc = perform() |
| sys.exit(prc) |