diff --git a/utils/build-script b/utils/build-script index 7c538c1da2574..4c6c954e4ff97 100755 --- a/utils/build-script +++ b/utils/build-script @@ -1042,6 +1042,13 @@ def main_normal(): if args.cmake is not None: toolchain.cmake = args.cmake + cmake = CMake(args=args, toolchain=toolchain) + # Check the CMake version is sufficient on Linux and build from source if not. + cmake_path = cmake.check_cmake_version(SWIFT_SOURCE_ROOT, SWIFT_BUILD_ROOT) + if cmake_path is not None: + toolchain.cmake = cmake_path + args.cmake = cmake_path + # Preprocess the arguments to apply defaults. BuildScriptInvocation.apply_default_arguments(toolchain, args) diff --git a/utils/swift_build_support/swift_build_support/cmake.py b/utils/swift_build_support/swift_build_support/cmake.py index 3b29646ddbf17..d2c422f225b47 100644 --- a/utils/swift_build_support/swift_build_support/cmake.py +++ b/utils/swift_build_support/swift_build_support/cmake.py @@ -20,6 +20,10 @@ from . import shell +import re +import os +import platform + class CMakeOptions(object): """List like object used to define cmake options @@ -177,3 +181,95 @@ def build_args(self): '-jobs', str(jobs)] return build_args + + # Determine the version of the installed CMake binary. + def installed_cmake_version(self, cmake_binary): + version = shell.capture([cmake_binary, '--version'], dry_run=False, + echo=True, optional=True) + (c_major, c_minor, c_patch) = (0, 0, 0) + if version is not None: + x = re.findall(r'cmake version (\d+)\.(\d+)\.(\d+)', version.rstrip()) + if len(x) == 1: + (c_major, c_minor, c_patch) = map(int, x[0]) + + return (c_major, c_minor, c_patch) + + # Determine the version of the checked out CMake source. + def cmake_source_version(self, cmake_source_dir): + cmake_version_file = os.path.join(cmake_source_dir, 'Source', + 'CMakeVersion.cmake') + major = -1 + minor = -1 + patch = -1 + + file = open(cmake_version_file, "r") + for line in file.readlines(): + m = re.findall(r'set\(CMake_VERSION_MAJOR (\d+)\)', line) + if len(m) == 1: + major = int(m[0]) + continue + + m = re.findall(r'set\(CMake_VERSION_MINOR (\d+)\)', line) + if len(m) == 1: + minor = int(m[0]) + continue + + m = re.findall(r'set\(CMake_VERSION_PATCH (\d+)\)', line) + if len(m) == 1: + patch = int(m[0]) + continue + + if major == -1 or minor == -1 or patch == -1: + raise RuntimeError("Cant determine CMake version from %s" + % cmake_version_file) + + return (major, minor, patch) + + # Build CMake from source. + def build_cmake(self, source_root, build_root): + cmake_bootstrap = os.path.join(source_root, 'cmake', 'bootstrap') + + if hasattr(self.args, 'build_script_impl_args'): + for opt in self.args.build_script_impl_args: + m = re.findall('--build-dir=(.*)', opt) + if len(m) == 1: + build_root = m[0] + + cmake_build_dir = os.path.join(build_root, 'cmake-%s' % self.args.host_target) + if not os.path.isdir(cmake_build_dir): + os.makedirs(cmake_build_dir) + + cwd = os.getcwd() + os.chdir(cmake_build_dir) + shell.call_without_sleeping([cmake_bootstrap], echo=True) + shell.call_without_sleeping(['make', '-j%s' % self.args.build_jobs], + echo=True) + os.chdir(cwd) + return os.path.join(cmake_build_dir, 'bin', 'cmake') + + # For Linux only, determine the version of the installed CMake compared to the + # source and build the source if necessary. Returns the path to the cmake binary. + def check_cmake_version(self, source_root, build_root): + if platform.system() != 'Linux': + return + + cmake_source_dir = os.path.join(source_root, 'cmake') + # If the source is not checked out then don't attempt to build anything. + if not os.path.isdir(cmake_source_dir): + return + + cmake_binary = 'cmake' + try: + if self.args.cmake is not None: + cmake_binary = self.args.cmake + except AttributeError: + cmake_binary = 'cmake' + + (i_major, i_minor, i_patch) = self.installed_cmake_version(cmake_binary) + (s_major, s_minor, s_patch) = self.cmake_source_version(cmake_source_dir) + if (i_major > s_major or (i_major == s_major and i_minor >= s_minor) or + (i_major == s_major and i_minor == s_minor and i_patch >= s_patch)): + return + else: + # Build CMake from source and return the path to the executable. + return self.build_cmake(source_root, build_root)