| #!/usr/bin/env python2.7 |
| # Copyright 2017 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # Build script for a Go app. |
| |
| import argparse |
| import os |
| import subprocess |
| import sys |
| import string |
| import shutil |
| import errno |
| |
| from gen_library_metadata import get_sources |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| '--godepfile', help='Path to godepfile tool', required=True) |
| parser.add_argument( |
| '--root-out-dir', help='Path to root of build output', required=True) |
| parser.add_argument( |
| '--cc', help='The C compiler to use', required=False, default='cc') |
| parser.add_argument( |
| '--cxx', help='The C++ compiler to use', required=False, default='c++') |
| parser.add_argument( |
| '--objcopy', |
| help='The objcopy tool to use', |
| required=False, |
| default='objcopy') |
| parser.add_argument('--sysroot', help='The sysroot to use', required=False) |
| parser.add_argument( |
| '--target', help='The compiler target to use', required=False) |
| parser.add_argument( |
| '--depfile', help='The path to the depfile', required=True) |
| parser.add_argument( |
| '--current-cpu', |
| help='Target architecture.', |
| choices=['x64', 'arm64'], |
| required=True) |
| parser.add_argument( |
| '--current-os', |
| help='Target operating system.', |
| choices=['fuchsia', 'linux', 'mac', 'win'], |
| required=True) |
| parser.add_argument( |
| '--buildidtool', help='The path to the buildidtool.') |
| parser.add_argument( |
| '--build-id-dir', |
| help='The path to the .build-id directory.') |
| parser.add_argument( |
| '--go-root', help='The go root to use for builds.', required=True) |
| parser.add_argument( |
| '--go-cache', help='Cache directory to use for builds.', required=False) |
| parser.add_argument( |
| '--is-test', help='True if the target is a go test', default=False) |
| parser.add_argument('--buildmode', help='Build mode to use') |
| parser.add_argument('--gcflags', help='Arguments to pass to Go compiler') |
| parser.add_argument( |
| '--go-dep-files', |
| help='List of files describing library dependencies', |
| nargs='*', |
| default=[]) |
| parser.add_argument('--binname', help='Output file', required=True) |
| parser.add_argument( |
| '--output-path', |
| help='Where to output the (unstripped) binary', |
| required=True) |
| parser.add_argument( |
| '--stripped-output-path', |
| help='Where to output a stripped binary, if supplied') |
| parser.add_argument( |
| '--verbose', |
| help='Tell the go tool to be verbose about what it is doing', |
| action='store_true') |
| parser.add_argument('--package', help='The package name', required=True) |
| parser.add_argument( |
| '--include-dir', |
| help='-isystem path to add', |
| action='append', |
| default=[]) |
| parser.add_argument( |
| '--lib-dir', help='-L path to add', action='append', default=[]) |
| parser.add_argument('--vet', help='Run go vet', action='store_true') |
| parser.add_argument( |
| '--tag', help='Add a go build tag', default=[], action='append') |
| args = parser.parse_args() |
| |
| try: |
| os.makedirs(args.go_cache) |
| except OSError as e: |
| if e.errno == errno.EEXIST and os.path.isdir(args.go_cache): |
| pass |
| else: |
| raise |
| |
| goarch = { |
| 'x64': 'amd64', |
| 'arm64': 'arm64', |
| }[args.current_cpu] |
| goos = { |
| 'fuchsia': 'fuchsia', |
| 'linux': 'linux', |
| 'mac': 'darwin', |
| 'win': 'windows', |
| }[args.current_os] |
| |
| build_id_dir = os.path.join(args.root_out_dir, '.build-id') |
| dist = args.stripped_output_path or args.output_path |
| |
| # Project path is a package specific gopath, also known as a "project" in go parlance. |
| project_path = os.path.join( |
| args.root_out_dir, 'gen', 'gopaths', args.binname) |
| |
| # Clean up any old project path to avoid leaking old dependencies |
| shutil.rmtree(os.path.join(project_path, 'src'), ignore_errors=True) |
| os.makedirs(os.path.join(project_path, 'src')) |
| |
| if args.go_dep_files: |
| # Create a gopath for the packages dependency tree |
| for dst, src in get_sources(args.go_dep_files).items(): |
| dstdir = os.path.join(project_path, 'src', os.path.dirname(dst)) |
| try: |
| os.makedirs(dstdir) |
| except OSError as e: |
| # EEXIST occurs if two gopath entries share the same parent name |
| if e.errno != errno.EEXIST: |
| raise |
| # TODO(BLD-228): the following check might not be necessary anymore. |
| tgt = os.path.join(dstdir, os.path.basename(dst)) |
| # The source tree is effectively read-only once the build begins. |
| # Therefore it is an error if tgt is in the source tree. At first |
| # glance this may seem impossible, but it can happen if dst is foo/bar |
| # and foo is a symlink back to the source tree. |
| canon_root_out_dir = os.path.realpath(args.root_out_dir) |
| canon_tgt = os.path.realpath(tgt) |
| if not canon_tgt.startswith(canon_root_out_dir): |
| raise ValueError( |
| "Dependency destination not in --root-out-dir: provided=%s, path=%s, realpath=%s" |
| % (dst, tgt, canon_tgt)) |
| os.symlink(os.path.relpath(src, os.path.dirname(tgt)), tgt) |
| |
| cflags = [] |
| if args.sysroot: |
| cflags.append('--sysroot=' + args.sysroot) |
| if args.target: |
| cflags.append('--target=' + args.target) |
| ldflags = cflags[:] |
| |
| cflags += ['-isystem' + dir for dir in args.include_dir] |
| ldflags += ['-L' + dir for dir in args.lib_dir] |
| |
| cflags_joined = ' '.join(cflags) |
| ldflags_joined = ' '.join(ldflags) |
| |
| gopath = os.path.abspath(project_path) |
| build_goroot = os.path.abspath(args.go_root) |
| |
| env = { |
| # /usr/bin:/bin are required for basic things like bash(1) and env(1). Note |
| # that on Mac, ld is also found from /usr/bin. |
| 'PATH': "/usr/bin:/bin", |
| 'GOARCH': goarch, |
| 'GOOS': goos, |
| 'GOPATH': gopath, |
| # Some users have GOROOT set in their parent environment, which can break |
| # things, so it is always set explicitly here. |
| 'GOROOT': build_goroot, |
| 'GOCACHE': args.go_cache, |
| 'CC': args.cc, |
| 'CXX': args.cxx, |
| 'CGO_CFLAGS': cflags_joined, |
| 'CGO_CPPFLAGS': cflags_joined, |
| 'CGO_CXXFLAGS': cflags_joined, |
| 'CGO_LDFLAGS': ldflags_joined, |
| 'CGO_ENABLED': '1', |
| } |
| |
| if args.target: |
| env['CC_FOR_TARGET'] = env['CC'] |
| env['CXX_FOR_TARGET'] = env['CXX'] |
| |
| go_tool = os.path.join(build_goroot, 'bin', 'go') |
| |
| if args.vet: |
| retcode = subprocess.call([go_tool, 'vet', args.package], env=env) |
| if retcode != 0: |
| return retcode |
| |
| cmd = [go_tool] |
| if args.is_test: |
| cmd += ['test', '-c'] |
| else: |
| cmd += ['build'] |
| if args.verbose: |
| cmd += ['-x'] |
| if args.tag: |
| # Separate tags by spaces. This behavior is actually deprecated in the |
| # go command line, but Fuchsia currently has an older version of go |
| # that hasn't switched to commas. |
| cmd += ['-tags', ' '.join(args.tag)] |
| if args.buildmode: |
| cmd += ['-buildmode', args.buildmode] |
| if args.gcflags: |
| cmd += ['-gcflags', args.gcflags] |
| cmd += [ |
| '-pkgdir', |
| os.path.join(project_path, 'pkg'), '-o', args.output_path, args.package |
| ] |
| retcode = subprocess.call(cmd, env=env) |
| |
| if retcode == 0 and args.stripped_output_path: |
| if args.current_os == 'mac': |
| retcode = subprocess.call( |
| [ |
| 'xcrun', 'strip', '-x', args.output_path, '-o', |
| args.stripped_output_path |
| ], |
| env=env) |
| else: |
| retcode = subprocess.call( |
| [ |
| args.objcopy, '--strip-sections', args.output_path, |
| args.stripped_output_path |
| ], |
| env=env) |
| |
| # TODO(fxbug.dev/27215): Also invoke the buildidtool in the case of linux |
| # once buildidtool knows how to deal in Go's native build ID format. |
| supports_build_id = args.current_os == 'fuchsia' |
| if retcode == 0 and args.buildidtool and supports_build_id: |
| if not args.build_id_dir: |
| raise ValueError('Using --buildidtool requires --build-id-dir') |
| retcode = subprocess.call( |
| [ |
| args.buildidtool, |
| "-build-id-dir", args.build_id_dir, |
| "-stamp", dist + ".build-id.stamp", |
| "-entry", ".debug=" + args.output_path, |
| "-entry", "=" + dist, |
| ]) |
| |
| if retcode == 0: |
| if args.depfile is not None: |
| with open(args.depfile, "wb") as out: |
| godepfile_args = [args.godepfile, '-o', dist] |
| if args.is_test: |
| godepfile_args += ['-test'] |
| godepfile_args += [args.package] |
| subprocess.Popen(godepfile_args, stdout=out, env=env) |
| |
| return retcode |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |