From 652c51f7c8fb1a90bcec89b060ae923b974c8332
Date: Thu, 15 Apr 2021 02:27:15 +0530
Subject: [PATCH] [build] Allow cross-compiling build-script products for
 non-Darwin hosts too

To that end, move the --cross-compile-deps-path flag from build-script-impl to a
publicly documented build-script flag and use it for build-script products'
library dependencies too. Generate a SPM destination JSON file that can be used
both for cross-compiling these build-script products and by users for their own
Swift packages.

Also, make using a prebuilt toolchain explicit and pass an install prefix in to
swift-driver.

diff --git a/swift/utils/build_swift/build_swift/driver_arguments.py b/swift/utils/build_swift/build_swift/driver_arguments.py
index 850c30dce3d86..fcd2f52f349be 100644
--- a/swift/utils/build_swift/build_swift/driver_arguments.py
+++ b/swift/utils/build_swift/build_swift/driver_arguments.py
@@ -558,6 +558,11 @@ def create_argument_parser():
            help='A space separated list of targets to cross-compile host '
                 'Swift tools for. Can be used multiple times.')
 
+    option('--cross-compile-deps-path', store_path,
+           help='The path to a directory that contains prebuilt cross-compiled '
+                'library dependencies of the corelibs and other Swift repos, '
+                'such as the libcurl dependency of FoundationNetworking')
+
     option('--stdlib-deployment-targets', store,
            type=argparse.ShellSplitType(),
            default=None,
diff --git a/swift/utils/build_swift/tests/expected_options.py b/swift/utils/build_swift/tests/expected_options.py
index bc0041beea192..fa15ca93bb897 100644
--- a/swift/utils/build_swift/tests/expected_options.py
+++ b/swift/utils/build_swift/tests/expected_options.py
@@ -128,6 +128,7 @@
     'cmark_build_variant': 'Debug',
     'compiler_vendor': defaults.COMPILER_VENDOR,
     'coverage_db': None,
+    'cross_compile_deps_path': None,
     'cross_compile_hosts': [],
     'darwin_deployment_version_ios':
         defaults.DARWIN_DEPLOYMENT_VERSION_IOS,
@@ -673,6 +674,7 @@ class BuildScriptImplOption(_BaseOption):
     PathOption('--clang-profile-instr-use'),
     PathOption('--cmake'),
     PathOption('--coverage-db'),
+    PathOption('--cross-compile-deps-path'),
     PathOption('--host-cc'),
     PathOption('--host-cxx'),
     PathOption('--host-libtool'),
diff --git a/swift/utils/swift_build_support/swift_build_support/build_script_invocation.py b/swift/utils/swift_build_support/swift_build_support/build_script_invocation.py
index fb1237dd6d6..be697fda761 100644
--- a/swift/utils/swift_build_support/swift_build_support/build_script_invocation.py
+++ b/swift/utils/swift_build_support/swift_build_support/build_script_invocation.py
@@ -181,6 +181,10 @@ class BuildScriptInvocation(object):
         if args.cross_compile_hosts:
             impl_args += [
                 "--cross-compile-hosts", " ".join(args.cross_compile_hosts)]
+        if args.cross_compile_deps_path is not None:
+            impl_args += [
+                "--cross-compile-deps-path=%s" % args.cross_compile_deps_path
+            ]
 
         if args.test_paths:
             impl_args += ["--test-paths", " ".join(args.test_paths)]
@@ -666,9 +670,14 @@ class BuildScriptInvocation(object):
         # Core Lipo...
         self._execute_merged_host_lipo_core_action()
 
+        non_darwin_cross_compile_hostnames = [
+            target for target in self.args.cross_compile_hosts if not
+            StdlibDeploymentTarget.get_target_for_name(target).platform.is_darwin]
         # Non-build-script-impl products...
-        # Note: currently only supports building for the host.
-        for host_target in [self.args.host_target]:
+        # Note: currently only supports cross-compiling for non-Darwin hosts.
+        for host_target in [self.args.host_target] + non_darwin_cross_compile_hostnames:
+            if self.args.skip_local_build and host_target == self.args.host_target:
+                continue
             for product_class in product_classes:
                 if product_class.is_build_script_impl_product():
                     continue
diff --git a/swift/utils/swift_build_support/swift_build_support/products/product.py b/swift/utils/swift_build_support/swift_build_support/products/product.py
index fd202b8cf1738..741353f15ce45 100644
--- a/swift/utils/swift_build_support/swift_build_support/products/product.py
+++ b/swift/utils/swift_build_support/swift_build_support/products/product.py
@@ -195,15 +195,17 @@ def install_toolchain_path(self, host_target):
         """toolchain_path() -> string
 
         Returns the path to the toolchain that is being created as part of this
-        build, or to a native prebuilt toolchain that was passed in.
+        build
         """
-        if self.args.native_swift_tools_path is not None:
-            return os.path.split(self.args.native_swift_tools_path)[0]
-
         install_destdir = self.args.install_destdir
         if self.args.cross_compile_hosts:
-            build_root = os.path.dirname(self.build_dir)
-            install_destdir = '%s/intermediate-install/%s' % (build_root, host_target)
+            if targets.StdlibDeploymentTarget.get_target_for_name(
+                    host_target).platform.is_darwin:
+                build_root = os.path.dirname(self.build_dir)
+                install_destdir = '%s/intermediate-install/%s' % (build_root,
+                                                                  host_target)
+            else:
+                install_destdir = os.path.join(install_destdir, self.args.host_target)
         return targets.toolchain_path(install_destdir,
                                       self.args.install_prefix)
 
diff --git a/swift/utils/swift_build_support/swift_build_support/products/swiftdriver.py b/swift/utils/swift_build_support/swift_build_support/products/swiftdriver.py
index 3bd5755de35be..c30b032886d0a 100644
--- a/swift/utils/swift_build_support/swift_build_support/products/swiftdriver.py
+++ b/swift/utils/swift_build_support/swift_build_support/products/swiftdriver.py
@@ -91,8 +91,10 @@ def run_build_script_helper(action, host_target, product, args):
         install_destdir = swiftpm.SwiftPM.get_install_destdir(args,
                                                               host_target,
                                                               product.build_dir)
-    toolchain_path = targets.toolchain_path(install_destdir,
-                                            args.install_prefix)
+    if args.native_swift_tools_path is not None:
+        toolchain_path = os.path.split(args.native_swift_tools_path)[0]
+    else:
+        toolchain_path = product.install_toolchain_path(host_target)
 
     # Pass Dispatch directory down if we built it
     dispatch_build_dir = os.path.join(
@@ -134,7 +136,29 @@ def run_build_script_helper(action, host_target, product, args):
         helper_cmd += [
             '--lit-test-dir', lit_test_dir
         ]
+    # Pass Cross compile host info
+    if swiftpm.SwiftPM.has_cross_compile_hosts(args):
+        if targets.StdlibDeploymentTarget.get_target_for_name(
+                host_target).platform.is_darwin:
+            helper_cmd += ['--cross-compile-hosts']
+            for cross_compile_host in args.cross_compile_hosts:
+                helper_cmd += [cross_compile_host]
+        elif host_target != args.host_target:
+            helper_cmd += ['--cross-compile-hosts', host_target]
+            build_toolchain_path = install_destdir + args.install_prefix
+            resource_dir = '%s/lib/swift' % build_toolchain_path
+            helper_cmd += [
+                '--cross-compile-config',
+                targets.StdlibDeploymentTarget.get_target_for_name(
+                    host_target).platform.swiftpm_config(
+                    args, output_dir=build_toolchain_path,
+                    swift_toolchain=toolchain_path, resource_path=resource_dir)]
     if args.verbose_build:
         helper_cmd.append('--verbose')
 
+    if action == 'install':
+        helper_cmd += [
+            '--prefix', install_destdir + args.install_prefix
+        ]
+
     shell.call(helper_cmd)
diff --git a/swift/utils/swift_build_support/swift_build_support/products/swiftpm.py b/swift/utils/swift_build_support/swift_build_support/products/swiftpm.py
index 4a97f377ef408..47a893a98ca16 100644
--- a/swift/utils/swift_build_support/swift_build_support/products/swiftpm.py
+++ b/swift/utils/swift_build_support/swift_build_support/products/swiftpm.py
@@ -23,6 +23,7 @@
 from . import swift
 from . import xctest
 from .. import shell
+from ..targets import StdlibDeploymentTarget
 
 
 class SwiftPM(product.Product):
@@ -44,7 +45,11 @@ def should_build(self, host_target):
     def run_bootstrap_script(self, action, host_target, additional_params=[]):
         script_path = os.path.join(
             self.source_dir, 'Utilities', 'bootstrap')
-        toolchain_path = self.install_toolchain_path(host_target)
+
+        if self.args.native_swift_tools_path is not None:
+            toolchain_path = os.path.split(self.args.native_swift_tools_path)[0]
+        else:
+            toolchain_path = self.install_toolchain_path(host_target)
         swiftc = os.path.join(toolchain_path, "bin", "swiftc")
 
         # FIXME: We require llbuild build directory in order to build. Is
@@ -92,9 +97,23 @@ def run_bootstrap_script(self, action, host_target, additional_params=[]):
 
         # Pass Cross compile host info
         if self.has_cross_compile_hosts(self.args):
-            helper_cmd += ['--cross-compile-hosts']
-            for cross_compile_host in self.args.cross_compile_hosts:
-                helper_cmd += [cross_compile_host]
+            if StdlibDeploymentTarget.get_target_for_name(
+                    host_target).platform.is_darwin:
+                helper_cmd += ['--cross-compile-hosts']
+                for cross_compile_host in self.args.cross_compile_hosts:
+                    helper_cmd += [cross_compile_host]
+            elif host_target != self.args.host_target:
+                helper_cmd += ['--cross-compile-hosts', host_target,
+                               '--skip-cmake-bootstrap']
+                build_toolchain_path = self.get_install_destdir(
+                    self.args, host_target, self.build_dir) + self.args.install_prefix
+                resource_dir = '%s/lib/swift' % build_toolchain_path
+                helper_cmd += [
+                    '--cross-compile-config',
+                    StdlibDeploymentTarget.get_target_for_name(host_target).platform
+                    .swiftpm_config(self.args, output_dir=build_toolchain_path,
+                                    swift_toolchain=toolchain_path,
+                                    resource_path=resource_dir)]
 
         helper_cmd.extend(additional_params)
 
@@ -126,8 +145,13 @@ def has_cross_compile_hosts(self, args):
     def get_install_destdir(self, args, host_target, build_dir):
         install_destdir = args.install_destdir
         if self.has_cross_compile_hosts(args):
-            build_root = os.path.dirname(build_dir)
-            install_destdir = '%s/intermediate-install/%s' % (build_root, host_target)
+            if StdlibDeploymentTarget.get_target_for_name(
+                    host_target).platform.is_darwin:
+                build_root = os.path.dirname(build_dir)
+                install_destdir = '%s/intermediate-install/%s' % (
+                                  build_root, host_target)
+            else:
+                install_destdir = os.path.join(install_destdir, host_target)
         return install_destdir
 
     def install(self, host_target):
diff --git a/swift/utils/swift_build_support/swift_build_support/targets.py b/swift/utils/swift_build_support/swift_build_support/targets.py
index 34364458ec736..b6cedb3908f48 100644
--- a/swift/utils/swift_build_support/swift_build_support/targets.py
+++ b/swift/utils/swift_build_support/swift_build_support/targets.py
@@ -80,6 +80,13 @@ def cmake_options(self, args):
         """
         return ''
 
+    def swiftpm_config(self, args, output_dir, swift_toolchain, resource_path):
+        """
+        Generate a JSON file that SPM can use to cross-compile
+        """
+        raise NotImplementedError('Generating a SwiftPM cross-compilation JSON file '
+                                  'for %s is not supported yet' % self.name)
+
 
 class DarwinPlatform(Platform):
     def __init__(self, name, archs, sdk_name=None, is_simulator=False):
@@ -155,8 +162,7 @@ def swift_flags(self, args):
         flags += '-resource-dir %s/swift-%s-%s/lib/swift ' % (
                  args.build_root, self.name, args.android_arch)
 
-        android_toolchain_path = '%s/toolchains/llvm/prebuilt/%s' % (
-            args.android_ndk, StdlibDeploymentTarget.host_target().name)
+        android_toolchain_path = self.ndk_toolchain_path(args)
 
         flags += '-sdk %s/sysroot ' % (android_toolchain_path)
         flags += '-tools-directory %s/bin' % (android_toolchain_path)
@@ -171,6 +177,46 @@ def cmake_options(self, args):
         options += '-DCMAKE_ANDROID_NDK:PATH=%s' % (args.android_ndk)
         return options
 
+    def ndk_toolchain_path(self, args):
+        return '%s/toolchains/llvm/prebuilt/%s' % (
+            args.android_ndk, StdlibDeploymentTarget.host_target().name)
+
+    def swiftpm_config(self, args, output_dir, swift_toolchain, resource_path):
+        config_file = '%s/swiftpm-android-%s.json' % (output_dir, args.android_arch)
+
+        if os.path.exists(config_file):
+            return config_file
+
+        spm_json = '{\n'
+        spm_json += '  "version": 1,\n'
+        spm_json += '  "target": "%s-unknown-linux-android",\n' % args.android_arch
+        spm_json += '  "toolchain-bin-dir": "%s/bin",\n' % swift_toolchain
+        spm_json += '  "sdk": "%s/sysroot",\n' % self.ndk_toolchain_path(args)
+
+        spm_json += '  "extra-cc-flags": [ "-fPIC", "-I%s/usr/include" ],\n' % (
+                    args.cross_compile_deps_path)
+
+        spm_json += '  "extra-swiftc-flags": [\n'
+        spm_json += '    "-resource-dir", "%s",\n' % resource_path
+        spm_json += '    "-tools-directory", "%s/bin",\n' % (
+                    self.ndk_toolchain_path(args))
+        spm_json += '    "-Xcc", "-I%s/usr/include",\n' % args.cross_compile_deps_path
+        spm_json += '    "-L%s/usr/lib",\n' % args.cross_compile_deps_path
+        spm_json += '    "-L%s/lib/gcc/%s-linux-android%s/%s.x/%s"\n' % (
+                    self.ndk_toolchain_path(args),
+                    args.android_arch if not args.android_arch == 'armv7' else 'arm',
+                    '' if not args.android_arch == 'armv7' else 'eabi',
+                    args.android_ndk_gcc_version,
+                    '' if not args.android_arch == 'armv7' else 'armv7-a')
+        spm_json += '  ],\n'
+
+        spm_json += '  "extra-cpp-flags": [ "-lstdc++" ]\n'
+        spm_json += '}'
+
+        with open(config_file, 'w') as f:
+            f.write(spm_json)
+        return config_file
+
 
 class Target(object):
     """