Esempio n. 1
0
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(
        '--dump-syms',
        help='The dump_syms tool to use',
        required=False)
    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=False)
    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(
        '--gcflag',
        help='Arguments to pass to Go compiler',
        action='append',
        default=[])
    parser.add_argument(
        '--ldflag',
        help='Arguments to pass to Go linker',
        action='append',
        default=[])
    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', '-trimpath']
    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.gcflag:
        cmd += ['-gcflags', ' '.join(args.gcflag)]
    if args.ldflag:
        cmd += ['-ldflags=' + ' '.join(args.ldflag)]
    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.dump_syms and supports_build_id:
        if args.current_os == 'fuchsia':
            with open(dist + ".sym", "w") as f:
                retcode = subprocess.call(
                    [args.dump_syms, '-r', '-o', 'Fuchsia', args.output_path],
                    stdout = f
                )

    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
Esempio n. 2
0
def main():
    parser = argparse.ArgumentParser()
    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('--dump-syms',
                        help='The dump_syms tool to use',
                        required=False)
    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=False)
    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('--golibs-dir',
                        help='The directory containing third party libraries.',
                        required=True)
    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('--gcflag',
                        help='Arguments to pass to Go compiler',
                        action='append',
                        default=[])
    parser.add_argument('--ldflag',
                        help='Arguments to pass to Go linker',
                        action='append',
                        default=[])
    parser.add_argument('--go-dep-files',
                        help='List of files describing library dependencies',
                        nargs='*',
                        default=[])
    parser.add_argument(
        '--root-build-dir',
        help='Root build directory. Required if --go-dep-files is used.')
    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')
    parser.add_argument('--cgo',
                        help='Whether to enable CGo',
                        action='store_true')
    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]

    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.
    gopath_src = os.path.join(project_path, 'src')
    # Manually removing all subdirectories and files instead of using
    # shutil.rmtree, to avoid registering spurious reads on stale
    # subdirectories. See https://fxbug.dev/74084.
    if os.path.exists(gopath_src):
        for root, dirs, files in os.walk(gopath_src, topdown=False):
            for file in files:
                os.unlink(os.path.join(root, file))
            for dir in dirs:
                full_path = os.path.join(root, dir)
                if os.path.islink(full_path):
                    os.unlink(full_path)
                else:
                    os.rmdir(full_path)

    dst_vendor = os.path.join(gopath_src, 'vendor')
    os.makedirs(dst_vendor)
    for src in ['go.mod', 'go.sum']:
        os.symlink(os.path.join(args.golibs_dir, src),
                   os.path.join(gopath_src, src))
    src_vendor = os.path.join(args.golibs_dir, 'vendor')
    os.symlink(os.path.join(src_vendor, 'modules.txt'),
               os.path.join(dst_vendor, 'modules.txt'))

    link_to_source_list = []
    if args.go_dep_files:
        assert args.root_build_dir, (
            '--root-build-dir is required with --go-dep-files')

        root_build_dir = os.path.abspath(args.root_build_dir)
        link_to_source = {}

        # Create a GOPATH for the packages dependency tree.
        for dst, src in sorted(get_sources(args.go_dep_files).items()):
            # If the destination is part of the "main module", strip off the
            # module path. Otherwise, put it in the vendor directory.
            if dst.startswith(FUCHSIA_MODULE):
                dst = os.path.relpath(dst, FUCHSIA_MODULE)
            else:
                dst = os.path.join('vendor', dst)

            # Determine if the src path should
            # - be mapped as-is which, if src is a directory, includes all subdirectories
            # - have its contents enumerated and mapped directly
            map_directly = False

            if dst.endswith('/...'):
                # When a directory and all its subdirectories must be made available, map
                # the directory directly.
                map_directly = True

                # - src can have a '/...' suffix like with 'github.com/google/go-cmp/...'.
                #   - This means all subpackages are being symlinked to the GOPATH.
                # - dst have the suffix when defining a package.
                # - src can only have the suffix if dst has it too.
                assert dst.endswith('/...') >= src.endswith('/...'), (dst, src)
                dst = dst[:-4]
                if src.endswith('/...'):
                    src = src[:-4]
            elif os.path.isfile(src):
                # When sources are explicitly listed in the BUILD.gn file, each `src` will
                # be a path to a file that must be mapped directly.
                map_directly = True
                # Paths with /.../ in the middle designate go packages that include
                # subpackages, but also explicitly list all their source files.
                #
                # The construction of these paths is done in the go list invocation, so we
                # remove these sentinel values here.
                dst = dst.replace('/.../', '/')

            dstdir = os.path.join(gopath_src, dst)

            if map_directly:
                # Make a symlink to the src directory or file.
                parent = os.path.dirname(dstdir)
                if not os.path.exists(parent):
                    os.makedirs(parent)
                os.symlink(src, dstdir)
                link_to_source[os.path.join(root_build_dir, dstdir)] = src
            else:
                # Map individual files since the dependency is only on the
                # package itself, not Go subpackages. The only exception is
                # 'testdata'.
                os.makedirs(dstdir)
                for filename in os.listdir(src):
                    src_file = os.path.join(src, filename)
                    if filename == 'testdata' or os.path.isfile(src_file):
                        os.symlink(src_file, os.path.join(dstdir, filename))
                        link_to_source[os.path.join(root_build_dir, dstdir,
                                                    filename)] = src

        # Create a sorted list of (link, src) pairs, with longest paths before
        # short one. This ensures that 'foobar' will appear before 'foo'.
        link_to_source_list = sorted(link_to_source.items(),
                                     key=lambda x: x[0],
                                     reverse=True)

    cflags = []
    if args.sysroot:
        cflags.extend(['--sysroot', args.sysroot])
    if args.target:
        cflags.extend(['-target', args.target])

    ldflags = cflags[:]
    if args.current_os == 'linux':
        ldflags.extend([
            '-stdlib=libc++',
            # TODO(fxbug.dev/64336): the following flags are not recognized by CGo.
            # '-rtlib=compiler-rt',
            # '-unwindlib=',
        ])

    for dir in args.include_dir:
        cflags.extend(['-isystem', dir])
    ldflags.extend(['-L' + dir for dir in args.lib_dir])

    cflags_joined = ' '.join(cflags)
    ldflags_joined = ' '.join(ldflags)

    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': os.path.join(build_goroot, 'bin') + ':/usr/bin:/bin',
        'GOARCH': goarch,
        'GOOS': goos,
        # GOPATH won't be used, but Go still insists that we set it. Without it,
        # Go emits the succinct error: `missing $GOPATH`. Go further insists
        # that $GOPATH/go.mod not exist; if we pass `gopath_src` here (which
        # is where we symlinked our go.mod), we get another succinct error:
        # `$GOPATH/go.mod exists but should not`. Finally, GOPATH must be
        # absolute, otherwise:
        #
        # go: GOPATH entry is relative; must be absolute path: ...
        # For more details see: 'go help gopath'
        #
        # and here we are.
        'GOPATH': os.path.abspath(project_path),
        # Disallow downloading modules from any source.
        #
        # See https://golang.org/ref/mod#environment-variables under `GOPROXY`.
        'GOPROXY': 'off',
        # 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,
    }

    # Infra sets $TMPDIR which is cleaned between builds.
    if os.getenv('TMPDIR'):
        env['TMPDIR'] = os.getenv('TMPDIR')

    if args.cgo:
        env['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,
                                  cwd=gopath_src)
        if retcode != 0:
            return retcode

    cmd = [go_tool]
    if args.is_test:
        cmd += ['test', '-c']
    else:
        cmd += ['build', '-trimpath']
    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.gcflag:
        cmd += ['-gcflags', ' '.join(args.gcflag)]
    if args.ldflag:
        cmd += ['-ldflags=' + ' '.join(args.ldflag)]
    cmd += [
        '-pkgdir',
        os.path.join(project_path, 'pkg'),
        '-o',
        os.path.relpath(args.output_path, gopath_src),
        args.package,
    ]
    retcode = subprocess.call(cmd, env=env, cwd=gopath_src)

    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.dump_syms and supports_build_id:
        if args.current_os == 'fuchsia':
            with open(dist + '.sym', 'w') as f:
                retcode = subprocess.call(
                    [args.dump_syms, '-r', '-o', 'Fuchsia', args.output_path],
                    stdout=f)

    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:
        go_list_args = [go_tool, 'list', '-json', '-deps']
        if args.is_test:
            go_list_args += ['-test']
        go_list_args += [args.package]
        output = subprocess.check_output(go_list_args,
                                         env=env,
                                         cwd=gopath_src,
                                         text=True)
        with open(args.depfile, 'w') as into:
            into.write(os.path.relpath(dist))
            into.write(':')
            while output:
                try:
                    package = json.loads(output)
                    output = output[:0]
                except json.JSONDecodeError as e:
                    package = json.loads(output[:e.pos])
                    output = output[e.pos:]

                files_fields = [
                    'GoFiles',
                    'CgoFiles',
                    'CompiledGoFiles',
                    'CFiles',
                    'CXXFiles',
                    'MFiles',
                    'HFiles',
                    'FFiles',
                    'SFiles',
                    'SwigFiles',
                    'SwigCXXFiles',
                    'SysoFiles',
                ]
                if 'ForTest' in package:
                    files_fields += [
                        'TestGoFiles',
                        'XTestGoFiles',
                    ]

                src_dir = package['Dir']
                for f, t in link_to_source_list:
                    if src_dir.startswith(f):
                        src_dir = t + src_dir[len(f):]
                        break

                src_dir = os.path.relpath(src_dir)
                for field in files_fields:
                    files = package.get(field)
                    if files:
                        for file in files:
                            if args.go_cache in file:
                                continue
                            into.write(' ')
                            into.write(os.path.join(src_dir, file))

    return retcode
Esempio n. 3
0
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.',
                        required=True)
    parser.add_argument('--build-id-dir',
                        help='The path to the .build-id directory.',
                        required=True)
    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('--go-dep-files',
                        help='List of files describing library dependencies',
                        nargs='*',
                        default=[])
    parser.add_argument('--binname', help='Output file', required=True)
    parser.add_argument('--unstripped-binname', help='Unstripped output file')
    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')
    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]

    output_name = os.path.join(args.root_out_dir, args.binname)
    build_id_dir = os.path.join(args.root_out_dir, '.build-id')
    depfile_output = output_name
    if args.unstripped_binname:
        stripped_output_name = output_name
        output_name = os.path.join(args.root_out_dir, 'exe.unstripped',
                                   args.binname)

    # 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)

    gopath = os.path.abspath(project_path)
    build_goroot = os.path.abspath(args.go_root)

    env = {}
    env['GOARCH'] = goarch
    env['GOOS'] = goos
    env['GOPATH'] = gopath
    # Some users have GOROOT set in their parent environment, which can break
    # things, so it is always set explicitly here.
    env['GOROOT'] = build_goroot
    env['GOCACHE'] = args.go_cache

    env['CC'] = args.cc
    if args.target:
        env['CC_FOR_TARGET'] = args.cc
    env['CXX'] = args.cxx
    if args.target:
        env['CXX_FOR_TARGET'] = args.cxx

    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]

    env['CGO_CFLAGS'] = env['CGO_CPPFLAGS'] = env['CGO_CXXFLAGS'] = ' '.join(
        cflags)
    env['CGO_LDFLAGS'] = ' '.join(ldflags)
    env['CGO_ENABLED'] = '1'

    # /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.
    env['PATH'] = "/usr/bin:/bin"

    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']
    cmd += [
        '-pkgdir',
        os.path.join(project_path, 'pkg'), '-o', output_name, args.package
    ]
    retcode = subprocess.call(cmd, env=env)

    if retcode == 0 and args.unstripped_binname:
        if args.current_os == 'mac':
            retcode = subprocess.call([
                'xcrun', 'strip', '-x', output_name, '-o', stripped_output_name
            ],
                                      env=env)
        else:
            retcode = subprocess.call([
                args.objcopy, '--strip-sections', output_name,
                stripped_output_name
            ],
                                      env=env)

    # If args.current_os == 'linux' then the go linker will be used which
    # doesn't use the GNU build ID format.
    if retcode == 0 and args.current_os == 'fuchsia' and args.unstripped_binname:
        retcode = subprocess.call([
            args.buildidtool, "-build-id-dir", args.build_id_dir, "-stamp",
            output_name + ".build-id.stamp", "-entry",
            ".debug=" + args.unstripped_binname, "-entry", "=" + output_name
        ])

    if retcode == 0:
        if args.depfile is not None:
            with open(args.depfile, "wb") as out:
                godepfile_args = [args.godepfile, '-o', depfile_output]
                if args.is_test:
                    godepfile_args += ['-test']
                godepfile_args += [args.package]
                subprocess.Popen(godepfile_args, stdout=out, env=env)

    return retcode
Esempio n. 4
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--fuchsia-root',
                        help='Path to root of Fuchsia project',
                        required=True)
    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('--zircon-sysroot',
                        help='The Zircon sysroot 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('--go-root',
                        help='The go root to use for builds.',
                        required=True)
    parser.add_argument('--is-test',
                        help='True if the target is a go test',
                        default=False)
    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('--unstripped-binname', help='Unstripped output file')
    parser.add_argument('--toolchain-prefix',
                        help='Path to toolchain binaries',
                        required=False)
    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('--shared-libs-root',
                        help='Path to the build shared libraries',
                        required=False)
    parser.add_argument('--fdio-include',
                        help='Path to the FDIO include directory',
                        required=False)
    args = parser.parse_args()

    goarch = {
        'x64': 'amd64',
        'arm64': 'arm64',
    }[args.current_cpu]
    goos = {
        'fuchsia': 'fuchsia',
        'linux': 'linux',
        'mac': 'darwin',
        'win': 'windows',
    }[args.current_os]

    output_name = os.path.join(args.root_out_dir, args.binname)
    depfile_output = output_name
    if args.unstripped_binname:
        stripped_output_name = output_name
        output_name = os.path.join(args.root_out_dir, 'exe.unstripped',
                                   args.binname)

    # 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)

    gopath = os.path.abspath(project_path)
    build_goroot = os.path.abspath(args.go_root)

    env = {}
    env['GOARCH'] = goarch
    env['GOOS'] = goos
    env['GOPATH'] = gopath
    # Some users have GOROOT set in their parent environment, which can break
    # things, so it is always set explicitly here.
    env['GOROOT'] = build_goroot

    if goos == 'fuchsia':
        env['CGO_ENABLED'] = '1'
        env['CC'] = os.path.join(build_goroot, 'misc', 'fuchsia',
                                 'clangwrap.sh')
        env['ZIRCON_SYSROOT'] = args.zircon_sysroot

        # These are used by gccwrap.sh
        env['ZIRCON'] = os.path.join(args.fuchsia_root, 'zircon')
        env['FUCHSIA_ROOT_OUT_DIR'] = os.path.abspath(args.root_out_dir)

        # These are used by clangwrap.sh
        env['FUCHSIA_SHARED_LIBS'] = args.shared_libs_root
        env['CLANG_PREFIX'] = args.toolchain_prefix
        env['FDIO_INCLUDE'] = args.fdio_include

    # /usr/bin:/bin are required for basic things like bash(1) and env(1), but
    # preference the toolchain path. Note that on Mac, ld is also found from
    # /usr/bin.
    env['PATH'] = args.toolchain_prefix + ":/usr/bin:/bin"

    go_tool = os.path.join(build_goroot, 'bin/go')

    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']
    cmd += [
        '-pkgdir',
        os.path.join(project_path, 'pkg'), '-o', output_name, args.package
    ]
    retcode = subprocess.call(cmd, env=env)

    if retcode == 0 and args.unstripped_binname:
        if args.current_os == 'mac':
            retcode = subprocess.call([
                'xcrun', 'strip', '-x', output_name, '-o', stripped_output_name
            ],
                                      env=env)
        else:
            retcode = subprocess.call([
                os.path.join(args.toolchain_prefix, 'llvm-objcopy'),
                '--strip-sections', output_name, stripped_output_name
            ],
                                      env=env)

    if retcode == 0:
        if args.depfile is not None:
            with open(args.depfile, "wb") as out:
                godepfile_args = [args.godepfile, '-o', depfile_output]
                if args.is_test:
                    godepfile_args += ['-test']
                godepfile_args += [args.package]
                subprocess.Popen(godepfile_args, stdout=out, env=env)

    return retcode
Esempio n. 5
0
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('--dump-syms',
                        help='The dump_syms tool to use',
                        required=False)
    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=False)
    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('--gcflag',
                        help='Arguments to pass to Go compiler',
                        action='append',
                        default=[])
    parser.add_argument('--ldflag',
                        help='Arguments to pass to Go linker',
                        action='append',
                        default=[])
    parser.add_argument('--go-dep-files',
                        help='List of files describing library dependencies',
                        nargs='*',
                        default=[])
    parser.add_argument(
        '--root-build-dir',
        help='Root build directory. Required if --go-dep-files is used.')
    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')
    parser.add_argument('--cgo',
                        help='Whether to enable CGo',
                        action='store_true')
    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.
    gopath_src = os.path.join(project_path, 'src')
    if os.path.exists(gopath_src):
        shutil.rmtree(gopath_src)
    os.makedirs(gopath_src)

    link_to_source_list = []
    if args.go_dep_files:
        assert args.root_build_dir, (
            '--root-build-dir is required with --go-dep-files')

        root_build_dir = os.path.abspath(args.root_build_dir)
        link_to_source = {}

        # Create a GOPATH for the packages dependency tree.
        for dst, src in sorted(get_sources(args.go_dep_files).items()):
            # Determine if the src path should
            # - be mapped as-is which, if src is a directory, includes all subdirectories
            # - have its contents enumerated and mapped directly
            map_directly = False

            if dst.endswith('/...'):
                # When a directory and all its subdirectories must be made available, map
                # the directory directly.
                map_directly = True

                # - src can have a '/...' suffix like with 'github.com/google/go-cmp/...'.
                #   - This means all subpackages are being symlinked to the GOPATH.
                # - dst have the suffix when defining a package.
                # - src can only have the suffix if dst has it too.
                assert dst.endswith('/...') >= src.endswith('/...'), (dst, src)
                dst = dst[:-4]
                if src.endswith('/...'):
                    src = src[:-4]
            elif os.path.isfile(src):
                # When sources are explicitly listed in the BUILD.gn file, each `src` will
                # be a path to a file that must be mapped directly.
                map_directly = True
                # Paths with /.../ in the middle designate go packages that include
                # subpackages, but also explicitly list all their source files.
                # The construction of these paths is done in the
                # godepfile tool, so we remove these sentinel values here.
                dst = dst.replace('/.../', '/')

            dstdir = os.path.join(gopath_src, dst)

            if map_directly:
                # Make a symlink to the src directory or file.
                parent = os.path.dirname(dstdir)
                if not os.path.exists(parent):
                    os.makedirs(parent)
                os.symlink(src, dstdir)
                link_to_source[os.path.join(root_build_dir, dstdir)] = src
            else:
                # Map individual files since the dependency is only on the
                # package itself, not Go subpackages. The only exception is
                # 'testdata'.
                os.makedirs(dstdir)
                for filename in os.listdir(src):
                    src_file = os.path.join(src, filename)
                    if filename == 'testdata' or os.path.isfile(src_file):
                        os.symlink(src_file, os.path.join(dstdir, filename))
                        link_to_source[os.path.join(root_build_dir, dstdir,
                                                    filename)] = src

        # Create a sorted list of (link, src) pairs, with longest paths before
        # short one. This ensures that 'foobar' will appear before 'foo'.
        link_to_source_list = sorted(link_to_source.items(),
                                     key=lambda x: x[0],
                                     reverse=True)

    cflags = []
    if args.sysroot:
        cflags.append('--sysroot=' + args.sysroot)
    if args.target:
        cflags.append('--target=' + args.target)

    ldflags = cflags[:]
    if args.current_os == 'linux':
        ldflags.extend([
            '-rtlib=compiler-rt',
            '-stdlib=libc++',
            '-unwindlib=',
        ])

    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': os.path.join(build_goroot, 'bin') + ':/usr/bin:/bin',
        # Disable modules to ensure Go doesn't try to download dependencies.
        'GO111MODULE': 'off',
        '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,
    }

    # Infra sets $TMPDIR which is cleaned between builds.
    if os.getenv('TMPDIR'):
        env['TMPDIR'] = os.getenv('TMPDIR')

    if args.cgo:
        env['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', '-trimpath']
    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.gcflag:
        cmd += ['-gcflags', ' '.join(args.gcflag)]
    if args.ldflag:
        cmd += ['-ldflags=' + ' '.join(args.ldflag)]
    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.dump_syms and supports_build_id:
        if args.current_os == 'fuchsia':
            with open(dist + '.sym', 'w') as f:
                retcode = subprocess.call(
                    [args.dump_syms, '-r', '-o', 'Fuchsia', args.output_path],
                    stdout=f)

    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:
            godepfile_args = [args.godepfile, '-o', dist]
            for f, t in link_to_source_list:
                godepfile_args += ['-prefixmap', '%s=%s' % (f, t)]
            if args.is_test:
                godepfile_args += ['-test']
            godepfile_args += [args.package]
            with open(args.depfile, 'wb') as into:
                subprocess.check_call(godepfile_args, env=env, stdout=into)

    return retcode