#!/usr/bin/env python

"""
 This source file is part of the Swift.org open source project

 Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
 Licensed under Apache License v2.0 with Runtime Library Exception

 See http://swift.org/LICENSE.txt for license information
 See http://swift.org/CONTRIBUTORS.txt for Swift project authors

 -------------------------------------------------------------------------
 This script is used to bootstrap the Swift package manager build.

 It does so by writing out a build task file which can be used to build a
 stage1 package manager. That package manager is then expected to be able
 to build itself to produce the production tools.

 Note that currently this script is also responsible for building the package
 manager in such a way that it can be installed along with the Swift package.
 In particular, it knows how to build the runtime PackageDescription library
 correctly and install it.  It can also build libSwiftPM, allowing clients to
 access package manager functionality.

"""

from __future__ import print_function

try:
    from cStringIO import StringIO
except ImportError:
    from io import StringIO
import argparse
import codecs
import copy
import distutils.dir_util
import distutils.file_util
import errno
import json
import multiprocessing
import os
import pipes
import platform
import re
import shlex
import shutil
import stat
import subprocess
import sys
import tempfile

def note(message):
    print("--- %s: note: %s" % (os.path.basename(sys.argv[0]), message))
    sys.stdout.flush()


def error(message):
    print("--- %s: error: %s" % (os.path.basename(sys.argv[0]), message))
    sys.stdout.flush()
    raise SystemExit(1)

def symlink_force(target, link_name):
    if os.path.isdir(link_name):
        link_name = os.path.join(link_name, os.path.basename(target))
    try:
        os.symlink(target, link_name)
    except OSError as e:
        if e.errno == errno.EEXIST:
            os.remove(link_name)
            os.symlink(target, link_name)
        else:
            raise e

def mkdir_p(path):
    """
    mkdir_p(path)

    Create the given directory, if it does not exist.
    """

    try:
        os.makedirs(path)
    except OSError as e:
        # Ignore EEXIST, which may occur during a race condition.
        if e.errno != errno.EEXIST:
            raise


# FIXME: Consider eliminating this once the build task format supports node
# hashing.
def write_file_if_changed(path, data):
    """
    write_file_if_changed(path, data)

    Write the given data to the path, only updating the file if the contents are
    different than the current ones.
    """

    try:
        with open(path) as f:
            old_data = f.read()
    except:
        old_data = None
    if old_data == data:
        return

    # Create directory if needed.
    mkdir_p(os.path.dirname(path))

    # Write the contents.
    with open(path, "w") as f:
        f.write(data)

###

def get_current_sha(project_root):
    try:
        return subprocess.check_output(
            ["git", "-C", project_root, "rev-parse", "--short", "HEAD"], universal_newlines=True).strip()
    except:
        return None

g_num_cpus = multiprocessing.cpu_count()
g_default_sysroot = None
if platform.system() == 'Darwin':
    g_platform_path = subprocess.check_output(
        ["xcrun", "--sdk", "macosx", "--show-sdk-platform-path"],
        universal_newlines=True).strip()
    g_default_sysroot = subprocess.check_output(
        ["xcrun", "--sdk", "macosx", "--show-sdk-path"],
        universal_newlines=True).strip()

if platform.system() == 'Linux':
    g_shared_lib_ext = ".so"
else:
    g_shared_lib_ext = ".dylib"


# Represents a target definition extracted from the SwiftPM package manifest.
class Target(object):
    @property
    def virtual_node(self):
        return "<target-%s>" % (self.name)

    @property
    def linked_virtual_node(self):
        return "<link-%s>" % (self.name)

    def __init__(self, name, dependencies=[], swiftflags=[], extra_libs=[],
                 subpath=None):
        self.name = name
        self.release = False
        self.dependencies = list(dependencies)
        self.swiftflags = list(swiftflags)
        self.extra_libs = list(extra_libs)

        # Discover the source files, and whether or not this is a library.
        self.is_library = True
        self.is_swift = False
        # FIXME: Currently only C libraries are supported in bootstrap script.
        self.is_c = False
        self.enable_module_interface = False

        self.sources = []
        self.module_root_dir = os.path.join(g_source_root, subpath or self.name)

        for (dirpath, dirnames, filenames) in os.walk(self.module_root_dir):
            for name in filenames:
                path = os.path.join(dirpath, name)
                _, ext = os.path.splitext(name)
                if ext == '.swift':
                    self.is_swift = True
                    if name == 'main.swift':
                        self.is_library = False
                    self.sources.append(path)
                if ext == '.c':
                    self.is_c = True
                    self.sources.append(path)

        if self.is_swift and self.is_c:
            error("Target %s contains mixed C and Swift sources which is unsupported." % (self.name))

        self.sources.sort()

    def module_name(self):
        return self.name.replace("-", "_")

    def write_compile_commands(self, args, target_build_dir,
                                     module_dir, include_dir, output, objects,
                                     link_input_nodes, predecessor_node, target_map):
        if self.is_swift:
            self.write_swift_compile_commands(args, target_build_dir, module_dir,
                                              include_dir, output, objects, link_input_nodes,
                                              predecessor_node, target_map)
        elif self.is_c:
            self.write_c_compile_commands(args, target_build_dir, module_dir,
                                              include_dir, output, objects, link_input_nodes,
                                              predecessor_node)


    def write_swift_compile_commands(self, args, target_build_dir,
                                     module_dir, include_dir, output, objects,
                                     link_input_nodes, predecessor_node, target_map):
        # Compute the derived paths.
        module_path = os.path.join(module_dir, "%s.swiftmodule" % (self.name,))
        module_interface_path = os.path.join(module_dir, "%s.swiftinterface" % (self.name,))

        # Create the per-file entries.
        swift_objects = []
        for path in self.sources:
            filename = os.path.basename(path)
            base_path = os.path.join(
                target_build_dir, os.path.splitext(filename)[0])
            object_path = base_path + ".o"
            swift_objects.append(object_path)
            objects.append(object_path)

        # Form the command to build all the swift files.
        #
        # FIXME: The -j doesn't belong here, and should move into the
        # 'swift' tool.
        other_args = []
        if self.release:
            other_args.append("-O")
        else:
            other_args.extend(["-Onone", "-g"])
        other_args.extend(['-j%d' % g_num_cpus] + self.swiftflags)
        if platform.system() == 'Darwin':
            other_args.extend(["-target", "x86_64-apple-macosx10.10"])
        if args.sysroot:
            other_args.extend(["-sdk", args.sysroot])
        compile_swift_node = '<compile-swift-%s>' % (self.name,)
        link_input_nodes.append(compile_swift_node)

        if self.enable_module_interface:
            other_args.extend(["-enable-library-evolution", "-emit-module-interface-path", module_interface_path])

        other_args.extend(["-swift-version", "5"])

        other_args.extend(["-module-cache-path", os.path.join(args.sandbox_path, "ModuleCache")])
        
        print("  %s:" % json.dumps(compile_swift_node), file=output)
        print("    tool: swift-compiler", file=output)
        print("    executable: %s" % json.dumps(args.swiftc_path), file=output)
        # FIXME: We shouldn't even need to specify the sources here once we have
        # discovered dependencies support.
        print("    inputs: %s" % json.dumps(
            [predecessor_node] + self.sources), file=output)
        print("    outputs: %s" % json.dumps(
            [compile_swift_node, module_path] + swift_objects), file=output)
        print("    module-name: %s" % json.dumps(self.module_name()), file=output)
        print("    module-output-path: %s" % json.dumps(module_path),
              file=output)
        print("    sources: %s" % json.dumps(self.sources), file=output)
        print("    objects: %s" % json.dumps(swift_objects), file=output)

        import_paths = [module_dir, include_dir]

        # To import C target, add import path to where modulemap is located.
        for dependency in self.dependencies:
            dep_target = target_map[dependency]
            if dep_target.is_c:
                import_paths.append(os.path.join(dep_target.module_root_dir, "include"))

        # Add llbuild import paths.
        llbuild_import_paths_value = llbuild_import_paths(args)
        if llbuild_import_paths_value:
            import_paths.extend(llbuild_import_paths(args))

        if args.llbuild_link_framework:
            other_args.extend(["-F", args.llbuild_build_dir])

        # Don't use GNU strerror_r on Android.
        if 'ANDROID_DATA' in os.environ:
            other_args.extend(["-Xcc", "-U_GNU_SOURCE"])

        print("    import-paths: %s" % json.dumps(import_paths), file=output)
        print("    other-args: %s" % json.dumps(other_args),
              file=output)
        print("    temps-path: %s" % json.dumps(target_build_dir), file=output)
        print("    is-library: %s" % json.dumps(
            str(bool(self.is_library)).lower()), file=output)
        print(file=output)

    def write_c_compile_commands(self, args, target_build_dir,
                                     module_dir, include_dir, output, objects,
                                     link_input_nodes, predecessor_node):

        common_args = ["-fobjc-arc", "-fmodule-name=%s" % self.module_name()]
        common_args.extend(["-fmodules-cache-path=%s" % os.path.join(args.sandbox_path, "ModuleCache")])

        if platform.system() == 'Darwin':
            common_args.extend(["-arch", "x86_64", "-mmacosx-version-min=10.10"])

        if args.sysroot:
            common_args.extend(["-isysroot", args.sysroot])

        common_args.extend(["-g", "-O0", "-MD", "-MT", "dependencies", "-MF"])

        for source in self.sources:
            filename = os.path.basename(source)
            base_path = os.path.join(
                target_build_dir, os.path.splitext(filename)[0])
            object_path = base_path + ".o"
            deps_path = base_path + ".d"
            objects.append(object_path)

            link_input_nodes.append(object_path)

            args = [args.clang_path]
            args.extend(common_args)
            args.extend([deps_path, "-c", source,"-o", object_path])

            print("  %s:" % json.dumps(object_path), file=output)
            print("    tool: clang", file=output)
            print("    description: Compiling %s" % filename, file=output)
            print("    inputs: %s" % json.dumps([source]), file=output)
            print("    outputs: %s" % json.dumps([object_path]), file=output)
            print("    args: %s" % json.dumps(args), file=output)
            print("    deps: %s" % json.dumps(deps_path), file=output)
            print(file=output)


# Represents the type of a product extracted from the manifest.
ProductType = type('Enum', (), dict(
    EXECUTABLE = 'executable',
    STATIC_LIBRARY = 'static_library',
    DYNAMIC_LIBRARY = 'dynamic_library'
))


# Represents a product definition extracted from the SwiftPM package manifest.
class Product(object):
    @property
    def product_name(self):
        return {
            ProductType.EXECUTABLE: self.name,
            ProductType.DYNAMIC_LIBRARY: "lib" + self.name + g_shared_lib_ext,
            ProductType.STATIC_LIBRARY: "lib" + self.name + ".a"
        }[self.type]

    def __init__(self, name, type, targets=[]):
        self.name = name
        self.type = type
        self.targets = list(targets)


# Loads and parses the Package.swift manifest, returning the lists of Targets
# and Product objects discovered by doing so.
def parse_manifest():
    # We have a *very* strict format for our manifest to make parsing more
    # robust.  We use this to determine the target and product definitions.
    target_pattern = re.compile(
        r'\.target\(.*?name: "(.*?)",\n *dependencies: (\[.*?\])\)',
        re.DOTALL | re.MULTILINE)
    product_pattern = re.compile(
        r'.library\([\n ]*name: \"(.*?)\",[\n ]*type: (.*?),[\n ]*targets: (\[.*?\])[\n ]*\)',
        re.DOTALL | re.MULTILINE)

    # Load the manifest as a string (we always assume UTF-8 encoding).
    manifest_data = codecs.open(os.path.join(g_project_root,
                                "Package.swift"), encoding='utf-8',
                                errors='strict').read()

    # Extract the target definitions.
    def make_target(match):
        name = match.group(1)
        deps = eval(match.group(2))
        return Target(name, deps)
    targets = list(map(make_target, target_pattern.finditer(manifest_data)))

    # Extract the product definitions.
    def make_product(match):
        name = match.group(1)
        try:
            type = {
                '.executable': ProductType.EXECUTABLE,
                '.dynamic': ProductType.DYNAMIC_LIBRARY,
                '.static': ProductType.STATIC_LIBRARY
            }[match.group(2)]
        except:
            error("unknown type '%s for product '%s' in manifest" % (match.group(2), name))
        targets = eval(match.group(3))
        return Product(name, type, targets)
    products = list(map(make_product, product_pattern.finditer(manifest_data)))

    # Filter out the targets that should be ignored in stage 1.
    targets = [target for target in targets
               if target.name not in g_ignored_targets]

    # Convert each target's array of dependencies from strings to corresponding
    # Target objects.
    for target in targets:
        def convert(targetName):
            try:
                return next(a for a in targets if a.name == targetName)
            except StopIteration:
                error("Couldn't find target %s which is a dependency of %s" % (targetName, target.name))
        target.dependencies = list(map(convert, target.dependencies))

    # Construct the dependency graph, and convert each Target's dependencies
    # back to an array of strings.
    def convert(target):
        myset = set()

        def recurse(root):
            deps = []
            for dep in root.dependencies:
                if dep.name not in myset:
                    myset.add(dep.name)
                    deps += recurse(dep) + [dep.name]
            return deps
        # `reversed` because Linux link order must be reverse-topological
        return Target(target.name, reversed(recurse(target)))
    targets = list(map(convert, targets))
    
    # Finally, return the lists of targets and products.
    return (targets, products)

# Hard-coded target definition.
g_project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
g_source_root = os.path.join(g_project_root, "Sources")
g_ignored_targets = ["swiftpm-xctest-helper", "SPMTestSupport", "TSCTestSupport", "TSCTestSupportExecutable"]
(targets, products) = parse_manifest()
product_map = dict((p.name, p) for p in products)

# Create a quoted C string literal from an arbitrary string.
def make_c_string_literal(str):
    return str.replace("\\", "\\\\").replace("\"", "\\\"")

# Create a quoted XML string literal from an arbitrary string.
def make_xml_string_literal(str):
    return "\"" + str.replace("&", "&amp;").replace("\"", "&quot;") + "\""

def create_versoning_args(args):
    build_flags = []
    if args.vendor_name:
        vendor_name = make_c_string_literal(args.vendor_name)
        build_flags.extend(["-Xswiftc", "-Xcc", "-Xswiftc", "-DSPM_VENDOR_NAME=%s" % (vendor_name)])

    # Try to use current SHA if build_identifier is not provided.
    build_identifier = args.build_identifier
    if not build_identifier:
        build_identifier = get_current_sha(g_project_root)
    
    if build_identifier:
        build_identifier = make_c_string_literal(build_identifier)
        build_flags.extend(["-Xswiftc", "-Xcc", "-Xswiftc", "-DSPM_BUILD_IDENT=%s" % (build_identifier)])
    return build_flags

class llbuild(object):

    def __init__(self, sandbox_path, targets, args):

        # Compute output directories paths.
        self.output_file = os.path.join(sandbox_path, "build.yaml")
        self.inc_dir = os.path.join(sandbox_path, "inc")
        self.lib_dir = os.path.join(sandbox_path, "lib")
        self.bin_dir = os.path.join(sandbox_path, "bin")
        self.module_dir = os.path.join(sandbox_path, "modules")
        self.targets = targets
        self.target_map = dict((t.name, t) for t in targets)
        self.sandbox_path = sandbox_path
        self.args = args

    def run(self):
        cmd = [self.args.sbt_path, "-f", self.output_file]
        if self.args.verbose:
            cmd.append("-v")
        result = subprocess.call(cmd)
        if result != 0:
            error("build failed with exit status %d" % (result,))

    def create_manifest(self):

        # Write out the build file.
        output = StringIO()

        # Write out the task file header.
        print("client:", file=output)
        print("  name: swift-build", file=output)
        print(file=output)

        # Write out the tools section.
        #
        # FIXME: Not yet defined.
        print("tools: {}", file=output)
        print(file=output)

        # Write out the targets list.
        #
        # We support 'all' (build all targets) and an individual name for each
        # target.
        #
        # FIXME: Need support for default target.
        print("targets:", file=output)
        print("  \"\": %s" % (json.dumps(
            [target.virtual_node for target in self.targets]),), file=output)
        print("  all: %s" % (json.dumps(
            [target.virtual_node for target in self.targets]),), file=output)
        for target in self.targets:
            print("  %s: %s" % (
                target.name, json.dumps([target.virtual_node])), file=output)
        print(file=output)

        print("commands:", file=output)
        for target in self.targets:
            print("  # Target Commands: %r" % (target.name,), file=output)
            target_build_dir = os.path.join(self.sandbox_path, target.name + ".build")
            mkdir_p(target_build_dir)

            predecessor_node = "<entry-%s>" % (target.name,)
            objects = []
            link_input_nodes = [predecessor_node]

            # Write out the predecessor node used to order w.r.t. other targets.
            print("  %s:" % json.dumps(predecessor_node), file=output)
            print("    tool: phony", file=output)
            print("    inputs: %s" % json.dumps(
                [self.target_map[name].virtual_node for name in target.dependencies]),
                file=output)
            print("    outputs: %s" % json.dumps([predecessor_node]), file=output)
            print(file=output)

            # Write out the target build commands (we just name the command and node
            # the same).

            # Write the compile commands, if used.
            if target.sources:
                target.write_compile_commands(
                    self.args, target_build_dir, self.module_dir, self.inc_dir, output, objects,
                    link_input_nodes, predecessor_node, self.target_map)


            # llbuild tool to use for this command.
            tool = "shell"

            # Form the command line to link.
            linked_libraries = []
            if target.is_swift and target.is_library:
                tool = "archive"
                link_output_path = os.path.join(self.lib_dir, "%s.a" % (target.name,))
                link_command = [] # Archive tool auto infers objects from inputs.
            elif target.is_c and target.is_library:
                tool = "archive"
                link_output_path = os.path.join(self.lib_dir, "%s.a" % (target.name,))
                # Archive tool auto infers objects from inputs.
                objects = []
                link_command = []
            elif target.is_c and not target.is_library:
                error("Executable C target not supported by bootstrap yet")
            elif target.is_swift and not target.is_library:
                link_output_path = os.path.join(self.bin_dir, target.name)
    
                link_command = [self.args.swiftc_path,
                                '-o', link_output_path]
                if self.args.sysroot:
                    link_command.extend(["-sdk", self.args.sysroot])
                if platform.system() == 'Darwin':
                    link_command.extend(["-target", "x86_64-apple-macosx10.10"])
                link_command.extend(objects)
                for dependency in target.dependencies:
                    dep_target = self.target_map[dependency]
                    dependency_lib_path = os.path.join(self.lib_dir, "%s.a" % dependency)
                    link_command.append(dependency_lib_path)
                    linked_libraries.append(dependency_lib_path)
                if platform.system() == 'Darwin':
                    link_command.extend(["-Xlinker", "-all_load"])
                    link_command.extend(["-Xlinker", "-rpath", "-Xlinker", self.args.swift_stdlib_path])

                for lib in target.extra_libs:
                    link_command.extend(["-Xlinker", "-l%s" % (lib,)])
                if platform.system() == 'Linux':
                    link_command.extend(
                        ["-Xlinker", "-rpath=$ORIGIN/../lib/swift/linux"])

                # Add llbuild link flags.
                link_command.extend(llbuild_link_args(self.args))

            # Write out the link command.
            if target.is_library:
                description = "Linking %s" % target.name
            else:
                description = "Linking %s" % link_output_path
            self.writeTool(
                tool,
                target.linked_virtual_node,
                description,
                link_input_nodes + objects + linked_libraries,
                [target.linked_virtual_node, link_output_path],
                link_command,
                output)

            # Write the top-level target group command.
            print("  %s:" % json.dumps(target.virtual_node), file=output)
            print("    tool: phony", file=output)
            print("    inputs: %s" % json.dumps(
                [target.linked_virtual_node]), file=output)
            print("    outputs: %s" % json.dumps([target.virtual_node]),
                  file=output)
            print(file=output)

        # Write the output file.
        write_file_if_changed(
                self.output_file, output.getvalue())

    # Write out the tool into output buffer.
    # Supported tools: shell and archive. args is ignored for archive tool.
    def writeTool(self, tool, node, description, inputs, outputs, args, output):
        print("  %s:" % json.dumps(node), file=output)
        print("    tool: %s" % tool, file=output)
        print("    description: %s" % description, file=output)
        print("    inputs: %s" % json.dumps(inputs), file=output)
        print("    outputs: %s" % json.dumps(outputs), file=output)
        if tool == "shell":
            print("    args: %s" % json.dumps(args), file=output)
        print(file=output)

def build_runtimes(targets, sandbox_path, args):
    target_map = dict((t.name, t) for t in targets)
    runtimes = {}
    runtimes['4'] = target_map['PackageDescription4']
    runtimes['4_2'] = target_map['PackageDescription4']

    built_runtimes = {}
    for version, target in runtimes.items():
        note("building runtime v%s target: %s: " % (version, target.name))
        target_copy = copy.copy(target)
        target_copy.name = 'PackageDescription'
        target_copy.release = args.release
        target_copy.swiftflags = ["-DPACKAGE_DESCRIPTION_%s" % (version)]
        if platform.system() == 'Darwin':
            target_copy.enable_module_interface = True
        build = llbuild(
                os.path.join(sandbox_path, "runtimes", version), [target_copy], args)
        build.create_manifest()
        build.run()
        built_runtimes[version] = build
    return built_runtimes

def process_runtime_libraries(build, args, lib_path):
    module_input_path = os.path.join(
        build.module_dir, "PackageDescription.swiftmodule")
    moduleinterface_input_path = os.path.join(
        build.module_dir, "PackageDescription.swiftinterface")
    swiftdoc_input_path = os.path.join(
        build.module_dir, "PackageDescription.swiftdoc")
    input_lib_path = os.path.join(
        build.lib_dir, "PackageDescription.a")

    mkdir_p(lib_path)
    runtime_module_path = os.path.join(
        lib_path, "PackageDescription.swiftmodule")
    runtime_moduleinterface_path = os.path.join(
        lib_path, "PackageDescription.swiftinterface")
    runtime_swiftdoc_path = os.path.join(
        lib_path, "PackageDescription.swiftdoc")

    if platform.system() == 'Darwin':
        distutils.file_util.copy_file(moduleinterface_input_path, runtime_moduleinterface_path,
            update=1)
    else:
        distutils.file_util.copy_file(module_input_path, runtime_module_path,
            update=1)
        distutils.file_util.copy_file(swiftdoc_input_path, runtime_swiftdoc_path,
            update=1)

    if platform.system() == 'Darwin':
        runtime_lib_path = os.path.join(lib_path, "libPackageDescription.dylib")
        cmd = [args.swiftc_path, "-Xlinker", "-dylib", "-o",
               runtime_lib_path,
               "-Xlinker", "-all_load",
               input_lib_path,
               "-sdk", args.sysroot,
               "-enable-library-evolution",
               "-target", "x86_64-apple-macosx10.10"]
    elif platform.system() == 'Windows':
        runtime_lib_path = os.path.join(lib_path, "PackageDescription.dll")
        cmd = [args.swiftc_path, "-emit-library", "-o", runtime_lib_path,
               "-Xlinker", input_lib_path]
    else:
        # We include an RPATH entry so that the Swift libraries can be found
        # relative to where it will be installed (in 'lib/swift/pm/<version>/...').
        runtime_lib_path = os.path.join(lib_path, "libPackageDescription.so")
        cmd = [args.swiftc_path, "-emit-library", "-o", runtime_lib_path,
               "-Xlinker", "--whole-archive",
               "-Xlinker", input_lib_path,
               "-Xlinker", "--no-whole-archive", "-lswiftGlibc", "-lFoundation",
               "-Xlinker", "-rpath=$ORIGIN/../../linux"]

    # We need to pass one swift file here to bypass the "no input files"
    # error.
    tf = tempfile.NamedTemporaryFile(suffix=".swift")
    cmd += [tf.name]

    cmd.extend(["-module-cache-path", os.path.join(args.sandbox_path, "ModuleCache")])

    subprocess.check_call(cmd)
    return (runtime_moduleinterface_path, runtime_module_path, runtime_swiftdoc_path, runtime_lib_path)


def get_swift_build_tool_path():
    # Search for a 'swift-build-tool' to use.

    # First, look in $(BUILT_PRODUCTS_DIR) in case we are being built from Xcode
    # along with the llbuild project.
    built_products_dir = os.environ.get("BUILT_PRODUCTS_DIR")
    if built_products_dir:
        sbt_path = os.path.join(built_products_dir, "swift-build-tool")
        if os.path.exists(sbt_path):
            return sbt_path

    SWIFT_EXEC = os.getenv("SWIFT_EXEC")
    if SWIFT_EXEC:
        maybe_path = os.path.join(SWIFT_EXEC, "../swift-build-tool")
        if os.path.exists(maybe_path):
            return maybe_path

    # If that failed, on Darwin use xcrun to search for the tool.
    if platform.system() == 'Darwin':
        try:
            sbt_path = subprocess.check_output(
                ["xcrun", "--find", "swift-build-tool"],
                stderr=subprocess.PIPE, universal_newlines=True).strip()
            if os.path.exists(sbt_path):
                return sbt_path
        except subprocess.CalledProcessError:
            pass
    else:
        # Otherwise, search for it in PATH.
        try:
            return subprocess.check_output(["which", "swift-build-tool"],
                                           universal_newlines=True).strip()
        except:
            pass

    # If all else failed, report an error.
    error("unable to find 'swift-build-tool' tool for bootstrap build")

def get_llbuild_source_path():
    llbuild_path = os.path.join(g_project_root, "..", "llbuild")
    if os.path.exists(llbuild_path):
        return llbuild_path
    note("clone llbuild next to swiftpm directory; see development docs: https://github.com/apple/swift-package-manager/blob/master/Documentation/Development.md#using-trunk-snapshot")
    error("unable to find llbuild source directory at %s" % llbuild_path)

def get_clang_path():
    try:
        if os.getenv("CC"):
            clang_path=os.path.realpath(os.getenv("CC"))
            return clang_path
        elif platform.system() == 'Darwin':
            return subprocess.check_output(["xcrun", "--find", "clang"],
                stderr=subprocess.PIPE, universal_newlines=True).strip()
        elif  platform.system() == 'Windows':
            return subprocess.check_output(["where", "clang"],
                stderr=subprocess.PIPE, universal_newlines=True).strip()
        else:
            return subprocess.check_output(["which", "clang"],
            universal_newlines=True).strip()
    except:
        error("unable to find 'clang' tool for bootstrap build")

def get_swiftc_path():
    try:
        if os.getenv("SWIFT_EXEC"):
            swiftc_path=os.path.realpath(os.getenv("SWIFT_EXEC"))
            if os.path.basename(swiftc_path) == 'swift':
                swiftc_path = swiftc_path + 'c'
            return swiftc_path
        elif platform.system() == 'Darwin':
            return subprocess.check_output(["xcrun", "--find", "swiftc"],
                stderr=subprocess.PIPE, universal_newlines=True).strip()
        else:
            return subprocess.check_output(["which", "swiftc"],
            universal_newlines=True).strip()
    except:
        error("unable to find 'swiftc' tool for bootstrap build")

def delete_rpath(rpath, binary, allowFailing=False):
    if platform.system() == 'Darwin':
        cmd = ["install_name_tool", "-delete_rpath", rpath, binary]
        note("removing RPATH from %s: %s" % (
                binary, ' '.join(cmd)))
        try:
            result = subprocess.check_output(cmd, stderr=subprocess.PIPE)
        except subprocess.CalledProcessError:
            if not allowFailing:
                error("failed to remove the rpath")
    else:
        error("unable to remove RPATHs on this platform")

def add_rpath(rpath, binary):
    if platform.system() == 'Darwin':
        cmd = ["install_name_tool", "-add_rpath", rpath, binary]
        note("adding RPATH in %s: %s" % (
                binary, ' '.join(cmd)))
        result = subprocess.call(cmd)
        if result != 0:
            error("command failed with exit status %d" % (result,))
    else:
        error("unable to add RPATHs on this platform")

# Install a binary file at the install path.
#
# This method removes the hardcoded stdlib path from the binary.
def installBinary(binary_path, install_path, args, add_rpaths=[], delete_rpaths=[]):
    mkdir_p(install_path)
    cmd = ["rsync", "-a", binary_path, install_path]
    note("installing %s: %s" % (
        os.path.basename(binary_path), ' '.join(cmd)))
    result = subprocess.call(cmd)
    if result != 0:
        error("install failed with exit status %d" % (result,))

    # Remove the hard-coded stdlib RPATHs that `swiftc` bakes in on
    # Darwin.
    #
    # FIXME: We need a way to control this, instead of having to rip it
    # out after the fact. https://bugs.swift.org/browse/SR-1967
    if platform.system() == 'Darwin':
        installed_path = os.path.join(
            install_path, os.path.basename(binary_path))
        delete_rpath(args.swift_stdlib_path, installed_path, True)

        # Remove additional RPATHs, if requested.
        for rpath in delete_rpaths:
            delete_rpath(rpath, installed_path)

        # Add additional RPATHs, if requested.
        for rpath in add_rpaths:
            add_rpath(rpath, installed_path)


def llbuild_import_paths(args):
    if args.llbuild_link_framework:
        return []

    if not args.llbuild_build_dir:
        return None

    import_paths = []
    import_paths.append(os.path.join(args.llbuild_build_dir, "products/llbuildSwift"))
    import_paths.append(os.path.join(args.llbuild_source_dir, "products/libllbuild/include"))
    return import_paths

def llbuild_lib_path(args):
    if args.llbuild_link_framework:
        llbuild_libdir = args.llbuild_build_dir
    else: 
        llbuild_libdir = os.path.join(args.llbuild_build_dir, "lib")
    return llbuild_libdir

def llbuild_link_args(args):
    build_flags = []
    llbuild_libdir = llbuild_lib_path(args)
    if args.llbuild_link_framework:
        build_flags.extend(["-Xlinker", "-F%s" % llbuild_libdir])
    else: 
        build_flags.extend(["-Xlinker", "-L%s" % llbuild_libdir])
    build_flags.extend(["-Xlinker", "-rpath", "-Xlinker", llbuild_libdir])
    return build_flags

def write_self_hosting_script(path, args):
    # Construct the build flags.
    swiftpm_flags = []
    swiftpm_flags.extend(create_versoning_args(args))
    for import_path in llbuild_import_paths(args):
        swiftpm_flags.extend(["-Xswiftc", "-I%s" % import_path])
    if args.llbuild_link_framework:
        swiftpm_flags.extend(["-Xswiftc", "-F%s" % args.llbuild_build_dir])
    swiftpm_flags.extend(llbuild_link_args(args))

    # Construct the string from the build flags.
    swiftpm_flags_str = ' '.join(('"%s"' % flag) for flag in swiftpm_flags)

    script_contents = """#!/usr/bin/env bash

# Helper script for self-hosting SwiftPM tools.
 
set -e

if [ ! -z "$SPM_VERBOSE" ]; then
    set -x
fi

if [ -z "$1" ]; then
    echo "provide the executable name: build|test|package|run" && exit 1
fi

__dir="$(cd "$(dirname "${{BASH_SOURCE[0]}}")" && pwd)"

SPM_FLAGS=({options})
export SWIFTPM_BOOTSTRAP=1

exec ${{__dir}}/swift-$1 ${{SPM_FLAGS[@]}} "${{@:2}}" """.format(options=swiftpm_flags_str)

    script_path = os.path.join(path, "spm")
    with open(script_path, "w") as f:
        print(script_contents, file=f)

    st = os.stat(script_path)
    os.chmod(script_path, st.st_mode | stat.S_IEXEC)

def checkTool(cmd):
    try:
        subprocess.check_output(cmd, stderr=subprocess.STDOUT)
    except Exception as e:
        error("%s is not available: %s" % (cmd[0], e))

def build_llbuild(args):
    note("Building llbuild")

    checkTool(["ninja", "--version"])    
    checkTool(["cmake", "--version"])
    
    llbuild_build_dir = args.llbuild_build_dir
    llbuild_source_dir = args.llbuild_source_dir

    # Run CMake if needed.
    cmake_cache_path = os.path.join(llbuild_build_dir, "CMakeCache.txt")
    if not os.path.isfile(cmake_cache_path) or not args.swiftc_path in open(cmake_cache_path).read():
        mkdir_p(llbuild_build_dir)
        cmd = [
            "cmake",
            "-G", "Ninja",
            "-DCMAKE_BUILD_TYPE:=Debug",
            "-DCMAKE_C_COMPILER:=clang",
            "-DCMAKE_CXX_COMPILER:=clang++",
            "-DLLBUILD_SUPPORT_BINDINGS:=Swift",
            "-DCMAKE_Swift_FLAGS=-sdk %s" % (g_default_sysroot),
            "-DCMAKE_Swift_COMPILER:=%s" % (args.swiftc_path),
            "-DSQLite3_INCLUDE_DIR=%s/usr/include" % (g_default_sysroot),
            llbuild_source_dir
        ]
        subprocess.check_call(cmd, cwd=llbuild_build_dir)

    # Build.
    subprocess.check_call(["ninja"], cwd=llbuild_build_dir)

def main():
    parser = argparse.ArgumentParser(
        usage="%(prog)s [options] [clean|all|test|install|build-runtimes]",
        description="This script will build a bootstrapped copy of the Swift "
                    "Package Manager, and optionally perform extra actions "
                    "like installing the result (with 'install') to a "
                    "location ('--prefix').")
    parser.add_argument("build_actions",
                        help="Extra actions to perform. Can be any number of "
                             "the following: [clean, all, test, install]",
                        nargs="*", default=["all"])
    parser.add_argument("--swiftc", dest="swiftc_path",
                        help="path to the swift compiler [%(default)s]",
                        metavar="PATH")
    parser.add_argument("--sbt", dest="sbt_path",
                        help="path to the 'swift-build-tool' tool "
                             "[%(default)s]",
                        metavar="PATH")
    parser.add_argument("--sysroot",
                        help="compiler sysroot to pass to Swift [%(default)s]",
                        default=g_default_sysroot, metavar="PATH")
    parser.add_argument("--build", dest="build_path",
                        help="create build products at PATH [%(default)s]",
                        default=".build", metavar="PATH")
    parser.add_argument("--prefix", dest="install_prefixes", nargs='*',
                        help="use PATHS as the prefixes for installing "
                             "[%(default)s]",
                        default=["/usr/local"], metavar="PATHS")
    parser.add_argument("-v", "--verbose", action="store_true",
                        help="use verbose output")
    parser.add_argument("--link-llbuild", dest="link_llbuild",
                        action="store_true", help="link llbuild", default=True)
    parser.add_argument("--llbuild-link-framework", dest="llbuild_link_framework",
                        action="store_true", help="link llbuild framework")
    parser.add_argument("--llbuild-framework-paths", dest="llbuild_framework_paths",
                        nargs='*', help="Path to directories containing the llbuild framework")
    parser.add_argument("--llbuild-build-dir", dest="llbuild_build_dir",
                        help="Path to llbuild build directory")
    parser.add_argument("--llbuild-source-dir", dest="llbuild_source_dir",
                        help="Path to llbuild source directory")
    parser.add_argument("--release", action="store_true",
                        help="Build stage 2 for release")
    parser.add_argument("--generate-xcodeproj", action="store_true",
                        help="Also generate an Xcode project for SwiftPM")
    parser.add_argument("--test-parallel", action="store_true",
                        help="Run tests in parallel", default=True)
    parser.add_argument("--test-filter", action="append",
                        help="Filter on a given test", default=[])
    parser.add_argument("--enable-perf-tests", action="store_true",
                        help="Enables performance tests in the generated Xcode project")
    parser.add_argument("--vendor-name", dest="vendor_name",
                        help="vendor name for use in --version")
    parser.add_argument("--build-identifier", dest="build_identifier",
                        help="build identifier for use in --version")
    parser.add_argument("--libswiftpm-library-dir", dest="libswiftpm_library_dir",
                        help="Directory in which to put libSwiftPM library")
    parser.add_argument("--libswiftpm-modules-dir", dest="libswiftpm_modules_dir",
                        help="Directory in which to put libSwiftPM modules")
    parser.add_argument("--libswiftpm-add-rpath", dest="libswiftpm_add_rpath",
                        help="Add the given rpath to libswiftpm during installation")
    parser.add_argument("--libswiftpm-skip-c-headers", action="store_true",
                        help="Skip installing C headers in libSwiftPM install directory")
    parser.add_argument("--stdlib-rpath", dest="stdlib_rpath",
                        help="The rpath of swift standard libraries relative to the executables")
    args = parser.parse_args()

    if not args.swiftc_path:
        args.swiftc_path = get_swiftc_path()
    args.clang_path = get_clang_path()

    args.swift_stdlib_path = os.path.normpath(
        os.path.join(os.path.dirname(os.path.realpath(args.swiftc_path)), "..",
                     "lib", "swift", "macosx"))

    # Absolutize input paths.
    if args.build_path:
        args.build_path = os.path.abspath(args.build_path)
    if args.swiftc_path:
        args.swiftc_path = os.path.abspath(args.swiftc_path)
    if args.sbt_path:
        args.sbt_path = os.path.abspath(args.sbt_path)
    if args.llbuild_source_dir:
        args.llbuild_source_dir = os.path.abspath(args.llbuild_source_dir)
        
    build_actions = set(args.build_actions)

    # Save the build configuration.
    if args.release:
        conf = "release"
    else:
        conf = "debug"
            
    # Validate the build actions.
    for action in build_actions:
        if action not in ("clean", "all", "test", "install", "build-runtimes"):
            raise SystemExit("unknown build action: %r" % (action,))

    # Compute the build paths.
    if platform.system() == 'Darwin':
        build_target = "x86_64-apple-macosx"
    elif platform.system() == 'Linux':              
        if 'ANDROID_DATA' in os.environ:
            if platform.machine().startswith("armv7"):
                build_target = 'armv7-unknown-linux-androideabi'
            elif platform.machine() == 'aarch64':
                build_target = 'aarch64-unknown-linux-android'
            else:
                raise SystemExit("ERROR: unsupported Android platform:",platform.machine())
        elif platform.machine() == 'x86_64':
            build_target = "x86_64-unknown-linux-gnu"
        elif platform.machine() == "i686":
            build_target = "i686-unknown-linux"
        elif platform.machine() == 's390x':
            build_target = "s390x-unknown-linux"
        elif platform.machine() == 'ppc64le':
            build_target = 'powerpc64le-unknown-linux'
        elif platform.machine().startswith("armv7"):
            build_target = 'armv7-unknown-linux-gnueabihf'
        elif platform.machine() == 'aarch64':
            build_target = 'aarch64-unknown-linux'
        else:
            raise SystemExit("ERROR: unsupported machine:",platform.machine())
    elif platform.system() == 'Windows':
        build_target = 'x86_64-unknown-windows-msvc'
    else:
        raise SystemExit("ERROR: unsupported system:",platform.system(), platform.machine())

    build_path = os.path.join(g_project_root, args.build_path)
    sandbox_path = os.path.join(build_path, ".bootstrap")
    args.sandbox_path = sandbox_path
    target_path = os.path.join(build_path, build_target)

    libdir = os.path.join(target_path, "lib")
    bindir = os.path.join(target_path, conf)
    
    # If the action is "clean", just remove the bootstrap and build directories.
    if "clean" in build_actions:
        cmd = ["rm", "-rf", sandbox_path]
        note("cleaning stage1: %s" % (' '.join(cmd),))
        result = subprocess.call(cmd)
        if result != 0:
            error("build failed with exit status %d" % (result,))

        cmd = ["rm", "-rf", build_path]
        note("cleaning self-hosted: %s" % (' '.join(cmd),))
        result = subprocess.call(cmd)
        if result != 0:
            error("build failed with exit status %d" % (result,))
        raise SystemExit(0)

    # All other actions build.

    # Create the sandbox.
    mkdir_p(sandbox_path)

    # Determine the swift-build-tool to use.
    args.sbt_path = os.path.abspath(
        args.sbt_path or get_swift_build_tool_path())

    # Build runtime libraries.
    built_runtimes = build_runtimes(targets, sandbox_path, args)

    processed_runtimes = {}
    for version, build in built_runtimes.items():
        # Path where runtime library will be copied from sandbox.
        lib_path = os.path.join(sandbox_path, "lib", "swift", "pm", version)

        # Stage the stage1 runtime library.
        processed_runtimes[version] = process_runtime_libraries(
                build, args, lib_path)

    if "build-runtimes" in build_actions:
        return

    # We either support linking the llbuild framework or dynamic library.
    #
    # llbuild-build-dir is expected to be path llbuild's CMake build
    # directory when linking the dylib. Otherwise, llbuild-framework-paths
    # should contain llbuild.framework and we don't require the llbuild source
    # directory.
    if args.llbuild_link_framework:
        if args.llbuild_source_dir:
            error("--llbuild-link-framework can't be used with --llbuild-source-dir")
        if not args.llbuild_framework_paths:
            error("--llbuild-link-framework requires --llbuild-framework-paths")

        # Search for llbuild.framework in the given paths.
        for path in args.llbuild_framework_paths:
            candidate = os.path.join(path, "llbuild.framework")
            if os.path.exists(candidate):
                args.llbuild_build_dir = path
                break

        if not args.llbuild_build_dir:
            error("could not locate llbuild.framework in %s" % args.llbuild_framework_paths)

    elif not args.llbuild_source_dir:
        args.llbuild_source_dir = get_llbuild_source_path()
    if args.llbuild_build_dir:
        args.llbuild_build_dir = os.path.abspath(args.llbuild_build_dir)

    # Build llbuild if needed.
    if not args.llbuild_build_dir:
        args.llbuild_build_dir = os.path.join(target_path, conf, "llbuild")
        build_llbuild(args)

    # Run the stage1 build.
    note("building stage1")
    stage1 = llbuild(sandbox_path, targets, args)
    stage1.create_manifest()
    stage1.run()

    mkdir_p(bindir)
    bootstrapped_product = os.path.join(bindir, "swift-build-stage1")

    # Construct a fake toolchain so swift-build can build itself
    # without requiring it to have hacky-edge-case logic
    def make_fake_toolchain():
        symlink_force(args.swiftc_path, os.path.join(bindir, "swift"))
        symlink_force(args.swiftc_path, os.path.join(bindir, "swiftc"))
        symlink_force(args.clang_path, os.path.join(bindir, "clang"))
        symlink_force(os.path.join(sandbox_path, "bin", "swift-build"),
                      bootstrapped_product)
        if os.path.isdir(libdir) and not os.path.islink(libdir):
            # TODO remove, here to prevent revlock incremental CI build failures
            shutil.rmtree(libdir)
        symlink_force(os.path.join(sandbox_path, "lib"), target_path)

        # To simulate usr/lib directory.
        usrdir = os.path.join(target_path, "usr")
        mkdir_p(usrdir)
        symlink_force(os.path.join(sandbox_path, "lib"), usrdir)

    make_fake_toolchain()

    # Build the package manager with itself.

    # Compute the build flags, needed for swift-build and swift-test.
    build_flags = []

    # Disable sandboxing when bootstraping.
    build_flags.append("--disable-sandbox")
    
    # We need to embed an RPATH so swift-{build,test} can find the core
    # libraries.
    if args.stdlib_rpath:
        embed_rpath = args.stdlib_rpath
    else:
        if platform.system() == 'Linux':
            embed_rpath = "$ORIGIN/../lib/swift/linux"
        else:
            embed_rpath = "@executable_path/../lib/swift/macosx"
    build_flags.extend(["-Xlinker", "-rpath", "-Xlinker", embed_rpath])
    if args.verbose:
        build_flags.append("-v")

    # Append the versioning build flags.
    build_flags.extend(create_versoning_args(args))

    # Don't use GNU strerror_r on Android.
    if 'ANDROID_DATA' in os.environ:
        build_flags.extend(["-Xswiftc", "-Xcc", "-Xswiftc", "-U_GNU_SOURCE"])

    # Add llbuild import flags.
    for import_path in llbuild_import_paths(args):
        build_flags.extend(["-Xswiftc", "-I%s" % import_path])
    if args.llbuild_link_framework:
        build_flags.extend(["-Xswiftc", "-F%s" % args.llbuild_build_dir])

    # Embed rpath to find llbuild library or framework at runtime.
    llbuild_libs_rpaths = []
    if args.llbuild_link_framework:
        llbuild_libs_rpaths.append("@executable_path/../../../../../SharedFrameworks")
        llbuild_libs_rpaths.append("@executable_path/../lib/swift/pm/llbuild")
    else:
        if platform.system() == 'Darwin':
            llbuild_libs_rpaths.append("@executable_path/../lib/swift/pm/llbuild")
        else:
            llbuild_libs_rpaths.append("$ORIGIN/../lib/swift/pm/llbuild")
    for rpath in llbuild_libs_rpaths:
        build_flags.extend(["-Xlinker", "-rpath", "-Xlinker", rpath])

    # Add llbuild link flags.
    build_flags.extend(llbuild_link_args(args))
    
    # Enable testing in release mode because SwiftPM's tests uses @testable imports.
    #
    # Note: Testing is already enabled in debug mode by SwiftPM itself.
    if args.release:
        build_flags.extend(["-Xswiftc", "-enable-testing"])
        build_flags.extend(["--configuration", "release"])

    env_cmd = ["env", "SWIFT_EXEC=" + os.path.join(bindir, "swiftc"),
        "SWIFTPM_BUILD_DIR=" + build_path,
        "SWIFTPM_BOOTSTRAP=1",
    ]
    if args.sysroot:
        if platform.system() == 'Darwin':
            env_cmd.append("SDKROOT=" + args.sysroot)
        else:
            env_cmd.append("SYSROOT=" + args.sysroot)
    cmd = env_cmd + [bootstrapped_product] + build_flags

    # Always build tests in stage2.
    cmd.extend(["--build-tests"])

    note("building self-hosted 'swift-build': %s" % (
        ' '.join(cmd),))
    result = subprocess.call(cmd, cwd=g_project_root)
    if result != 0:
        error("build failed with exit status %d" % (result,))

    # Write the script to allow self-hosting for development.
    write_self_hosting_script(os.path.join(target_path, conf), args)

    swift_package_path = os.path.join(target_path, conf, "swift-package")
    swiftpm_build_dir = os.path.join(target_path, conf, "swift-build")
    swift_test_path = os.path.join(target_path, conf, "swift-test")
    swift_run_path = os.path.join(target_path, conf, "swift-run")
    swiftpm_xctest_helper_path = os.path.join(
        target_path, conf, "swiftpm-xctest-helper")

    # If testing, run each of the test bundles.
    if "test" in build_actions:
        # Construct the test environment.
        cmd = env_cmd + [swift_test_path] + build_flags
        if args.test_parallel:
            cmd.append("--parallel")
        for arg in args.test_filter:
            cmd.extend(["--filter", arg])
        note("testing with command: %s" % (
            ' '.join(cmd),))
        result = subprocess.call(cmd, cwd=g_project_root)
        if result != 0:
            error("tests failed with exit status %d" % (result,))

    # If installing, put the build products in the appropriate locations.
    if "install" in build_actions:
        # Install in each prefix.
        for install_prefix in args.install_prefixes:
            bin_install_path = os.path.join(g_project_root, install_prefix,
                                            "bin")
            lib_install_path = os.path.join(g_project_root, install_prefix,
                                            "lib", "swift", "pm")
            libexec_install_path = os.path.join(g_project_root, install_prefix,
                                            "libexec", "swift", "pm")
            mkdir_p(bin_install_path)
            mkdir_p(lib_install_path)
            mkdir_p(libexec_install_path)

            delete_rpaths = [llbuild_lib_path(args)]

            # Install XCTest helper inside libexec.
            if platform.system() == 'Darwin':
                # Append the required RPATH for finding the standard libraries from
                # the libexec install path.
                #
                # FIXME: We need to find a way to express this from within the
                # package manager. https://bugs.swift.org/browse/SR-1968
                installBinary(swiftpm_xctest_helper_path, libexec_install_path,
                              args,
                              add_rpaths=["@executable_path/../../../lib/swift/macosx"],
                              delete_rpaths=delete_rpaths)

            # Install the main executables.
            for product_path in [swift_package_path, swiftpm_build_dir,
                                 swift_test_path, swift_run_path]:
                installBinary(product_path, bin_install_path, args, 
                              delete_rpaths=delete_rpaths)

            # Install the runtimes.
            for version, runtime in processed_runtimes.items():
                runtime_moduleinterface_path, runtime_module_path, runtime_swiftdoc_path, runtime_lib_path = runtime
                install_path = os.path.join(lib_install_path, version)

                # Install library.
                installBinary(runtime_lib_path, install_path, args)

                if platform.system() == 'Darwin':
                    cmd = ["install", "-m", "0644",
                           runtime_moduleinterface_path, install_path]
                    note("installing %s: %s" % (
                        os.path.basename(runtime_moduleinterface_path), ' '.join(cmd)))
                    result = subprocess.call(cmd)
                    if result != 0:
                        error("module install failed with exit status %d" % (result,))
                else:
                    # Install module.
                    cmd = ["install", "-m", "0644",
                           runtime_module_path, install_path]
                    note("installing %s: %s" % (
                        os.path.basename(runtime_module_path), ' '.join(cmd)))
                    result = subprocess.call(cmd)
                    if result != 0:
                        error("module install failed with exit status %d" % (result,))

                    # Install swiftdoc.
                    cmd = ["install", "-m", "0644",
                           runtime_swiftdoc_path, install_path]
                    note("installing %s: %s" % (
                        os.path.basename(runtime_swiftdoc_path), ' '.join(cmd)))
                    result = subprocess.call(cmd)
                    if result != 0:
                        error("swiftdoc install failed with exit status %d" % (result,))

    
    # Copy the libSwiftPM library to a directory, if we've been asked to do so.
    if args.libswiftpm_library_dir:
        # Make the directory absolute, if needed.
        libswiftpm_library_dir = os.path.abspath(args.libswiftpm_library_dir)
        
        # Create the output directory, if needed.
        mkdir_p(libswiftpm_library_dir)
        
        # Find the libSwiftPM product.  It's an error if we don't.
        try:
            libswiftpm_product = product_map["SwiftPM"]
        except:
            error("unable to find 'SwiftPM' product in package manifest; cannot copy library to '%s'" % (libswiftpm_library_dir, ))

        # Compute the RPATHs that should be added to libSwiftPM.
        libswiftpm_add_rpaths = []
        if args.libswiftpm_add_rpath:
            libswiftpm_add_rpaths = [args.libswiftpm_add_rpath]

        libswiftpm_delete_rpaths = [llbuild_lib_path(args)]
        
        # Copy the libSwiftPM library.
        # FIXME: This shouldn't require so much knowledge of the manifest.
        # FIXME: We should handle what happens if it's a static library.
        src_path = os.path.join(target_path, conf, libswiftpm_product.product_name)
        installBinary(src_path, libswiftpm_library_dir, args,
                      add_rpaths=libswiftpm_add_rpaths,
                      delete_rpaths=libswiftpm_delete_rpaths)
        
        # If it's a dynamic library, and if we're on Darwin, fix the dylib id.
        if platform.system() == 'Darwin':
            if libswiftpm_product.type == ProductType.DYNAMIC_LIBRARY:
                dst_path = os.path.join(libswiftpm_library_dir, libswiftpm_product.product_name)
                dylib_id = os.path.join("@rpath", libswiftpm_product.product_name)
                cmd = ["install_name_tool", "-id", dylib_id, dst_path]
                note("resetting dylib id: %s" % ' '.join(cmd))
                result = subprocess.call(cmd)
                if result != 0:
                    error("command  failed with exit status %d" % (result,))
    
    # Copy the libSwiftPM modules to a directory, if we've been asked to do so.
    # FIXME: There is too much duplication between this part and the one above.
    if args.libswiftpm_modules_dir:
        # Make the directory absolute, if needed.
        libswiftpm_modules_dir = os.path.abspath(args.libswiftpm_modules_dir)
        
        # Create the output directory, if needed.
        mkdir_p(libswiftpm_modules_dir)
        
        # Find the libSwiftPM product.  It's an error if we don't find it.
        try:
            libswiftpm_product = product_map["SwiftPM"]
        except:
            error("unable to find 'SwiftPM' product in package manifest; cannot copy modules to '%s'", libswiftpm_library_dir)
        
        # Copy the libSwiftPM modules.
        # FIXME: This shouldn't require so much knowledge of the manifest.
        # FIXME: We should handle what happens if it's a static library.
        target_map = dict((t.name, t) for t in targets)
        for target_name in libswiftpm_product.targets:
            target = target_map[target_name]
            if target.is_c:
                # Skip if requested.
                if args.libswiftpm_skip_c_headers:
                    continue
                # Copy headers of the C target.
                include_dir = os.path.join(target.module_root_dir, "include/")
                if not os.path.exists(include_dir):
                    continue
                dst_path = os.path.join(libswiftpm_modules_dir, target.name)
                cmd = ["rsync", "-a", include_dir, dst_path]
                note("copying to %s: %s" % (dst_path, ' '.join(cmd)))
                result = subprocess.call(cmd)
                if result != 0:
                    error("copying failed with exit status %d" % result)
            elif target.is_swift:
                # Copy swiftmodule and swiftdoc i.e. headers of the Swift target.
                for suffix in [".swiftmodule", ".swiftdoc"]:
                    src_path = os.path.join(target_path, conf, target.name + suffix)
                    if not os.path.exists(src_path):
                        continue
                    dst_path = os.path.join(libswiftpm_modules_dir, target.name + suffix)
                    cmd = ["rsync", "-a", src_path, dst_path]
                    note("copying to %s: %s" % (dst_path, ' '.join(cmd)))
                    result = subprocess.call(cmd)
                    if result != 0:
                        error("copying failed with exit status %d" % result)
            else:
                error("Unknown target type " + target)
    
    # If generating an Xcode project, also use the build product to do that.
    if args.generate_xcodeproj:
        # We will use the `swift-package` binary we just built to generate the
        # Xcode project.
        cmd = ["env", "SWIFTPM_BOOTSTRAP=1", swift_package_path]
        if args.enable_perf_tests:
            cmd.extend(["-Xswiftc", "-DENABLE_PERF_TESTS"])
        cmd.extend(["generate-xcodeproj"])
        
        # If there is a `llvm-profdata` binary next to `swiftc`, we enable code
        # coverage in the generated project.
        llvm_profdata_path = os.path.join(os.path.dirname(args.swiftc_path),
            "llvm-profdata")
        if os.path.exists(llvm_profdata_path):
            cmd.append("--enable-code-coverage")

        # Generate the .xcconfig file.
        xcconfig_path = os.path.join("SwiftPM.xcodeproj/overrides.xcconfig")
        if not os.path.exists(os.path.dirname(xcconfig_path)):
            mkdir_p(os.path.dirname(xcconfig_path))
        with open(xcconfig_path, "w") as f:
            # Add required build settings for llbuild.
            swift_include_paths = " ".join(llbuild_import_paths(args))
            lib_path = llbuild_lib_path(args)
            print("SWIFT_INCLUDE_PATHS = %s" % (swift_include_paths), file=f)
            print("LIBRARY_SEARCH_PATHS = %s" % (lib_path), file=f)
            print("LD_RUNPATH_SEARCH_PATHS = %s" % (lib_path), file=f)
        cmd += ["--xcconfig-overrides", xcconfig_path]
            
        # Call `swift-package` to generate the Xcode project.
        note("generating Xcode project: %s" % (' '.join(cmd),))
        result = subprocess.call(cmd)
        if result != 0:
            error("xcodeproj generation failed with exit status %d" % (result,))

        # Copy the perf test scheme if perf tests should be enabled.
        #
        # This is hacky but we need a way to run only performance tests and there
        # is no way to do this with SwiftPM right now.
        if args.enable_perf_tests:
            shutil.copyfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "PerformanceTests.xcscheme"), "SwiftPM.xcodeproj/xcshareddata/xcschemes/PerformanceTests.xcscheme")
                
if __name__ == '__main__':
    main()
