blob: ae1b2bcf32172fe44b0c0a82791f9bf9eb18244f [file] [log] [blame]
# swift_build_support/cmake.py - Detect host machine's CMake -*- 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 https://swift.org/LICENSE.txt for license information
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
#
# ----------------------------------------------------------------------------
#
# Find the path to a CMake executable on the host machine.
#
# ----------------------------------------------------------------------------
from __future__ import absolute_import
import os
import platform
import re
from numbers import Number
from . import shell
class CMakeOptions(object):
"""List like object used to define cmake options
"""
def __init__(self, initial_options=None):
self._options = []
if initial_options is not None:
self.extend(initial_options)
def define(self, var, value):
"""Utility to define cmake options in this object.
opts.define("FOO", "BAR") # -> -DFOO=BAR
opts.define("FLAG:BOOL", True) # -> -FLAG:BOOL=TRUE
"""
if var.endswith(':BOOL') or isinstance(value, bool):
value = self.true_false(value)
if value is None:
value = ""
elif not isinstance(value, (str, Number)):
raise ValueError('define: invalid value for key %s: %s (%s)' %
(var, value, type(value)))
self._options.append('-D%s=%s' % (var, value))
def extend(self, tuples_or_options):
if isinstance(tuples_or_options, CMakeOptions):
self += tuples_or_options
else:
for (variable, value) in tuples_or_options:
self.define(variable, value)
@staticmethod
def true_false(value):
if hasattr(value, 'lower'):
value = value.lower()
if value in [True, 1, 'true', 'yes', '1']:
return 'TRUE'
if value in [False, 0, 'false', 'no', '0']:
return 'FALSE'
raise ValueError("true_false: invalid value: %s" % value)
def __len__(self):
return self._options.__len__()
def __iter__(self):
return self._options.__iter__()
def __contains__(self, item):
return self._options.__contains__(item)
def __add__(self, other):
ret = CMakeOptions()
ret._options += self._options
ret._options += list(other)
return ret
def __iadd__(self, other):
self._options += list(other)
return self
class CMake(object):
def __init__(self, args, toolchain):
self.args = args
self.toolchain = toolchain
def common_options(self):
"""Return options used for all products, including LLVM/Clang
"""
args = self.args
toolchain = self.toolchain
options = CMakeOptions()
define = options.define
options += ['-G', args.cmake_generator]
sanitizers = []
if args.enable_asan:
sanitizers.append('Address')
if args.enable_ubsan:
sanitizers.append('Undefined')
if args.enable_tsan:
sanitizers.append('Thread')
if args.enable_lsan:
sanitizers.append('Leaks')
if sanitizers:
define("LLVM_USE_SANITIZER", ";".join(sanitizers))
if args.enable_sanitize_coverage:
define("LLVM_USE_SANITIZE_COVERAGE", "ON")
if args.export_compile_commands:
define("CMAKE_EXPORT_COMPILE_COMMANDS", "ON")
if args.distcc:
define("CMAKE_C_COMPILER_LAUNCHER:PATH", toolchain.distcc)
define("CMAKE_CXX_COMPILER_LAUNCHER:PATH", toolchain.distcc)
if args.cmake_c_launcher:
define("CMAKE_C_COMPILER_LAUNCHER:PATH", args.cmake_c_launcher)
if args.cmake_cxx_launcher:
define("CMAKE_CXX_COMPILER_LAUNCHER:PATH", args.cmake_cxx_launcher)
define("CMAKE_C_COMPILER:PATH", toolchain.cc)
define("CMAKE_CXX_COMPILER:PATH", toolchain.cxx)
define("CMAKE_LIBTOOL:PATH", toolchain.libtool)
if args.cmake_generator == 'Xcode':
define("CMAKE_CONFIGURATION_TYPES",
"Debug;Release;MinSizeRel;RelWithDebInfo")
if args.clang_user_visible_version:
major, minor, patch = \
args.clang_user_visible_version.components[0:3]
define("LLVM_VERSION_MAJOR:STRING", major)
define("LLVM_VERSION_MINOR:STRING", minor)
define("LLVM_VERSION_PATCH:STRING", patch)
define("CLANG_VERSION_MAJOR:STRING", major)
define("CLANG_VERSION_MINOR:STRING", minor)
define("CLANG_VERSION_PATCH:STRING", patch)
if args.build_ninja and args.cmake_generator == 'Ninja':
define('CMAKE_MAKE_PROGRAM', toolchain.ninja)
elif args.cmake_generator == 'Ninja' and toolchain.ninja is not None:
define('CMAKE_MAKE_PROGRAM', toolchain.ninja)
return options
def build_args(self):
"""Return arguments to the build tool used for all products
"""
args = self.args
toolchain = self.toolchain
jobs = args.build_jobs
if args.distcc:
jobs = shell.capture([toolchain.distcc, '-j'],
dry_run=False, echo=False).rstrip()
build_args = list(args.build_args)
if args.cmake_generator == 'Ninja':
build_args += ['-j%s' % jobs]
if args.verbose_build:
build_args += ['-v']
elif args.cmake_generator == 'Unix Makefiles':
build_args += ['-j%s' % jobs]
if args.verbose_build:
build_args += ['VERBOSE=1']
elif args.cmake_generator == 'Xcode':
build_args += ['-parallelizeTargets',
'-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 cmake.
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'
installed_ver = self.installed_cmake_version(cmake_binary)
if installed_ver > self.cmake_source_version(cmake_source_dir):
return
else:
# Build CMake from source and return the path to the executable.
return self.build_cmake(source_root, build_root)