forked from pantsbuild/pants
-
Notifications
You must be signed in to change notification settings - Fork 0
/
xcode_cli_tools.py
191 lines (154 loc) · 7.12 KB
/
xcode_cli_tools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
import os
from pants.backend.native.config.environment import Assembler, CCompiler, CppCompiler, Linker
from pants.engine.rules import rule
from pants.subsystem.subsystem import Subsystem
from pants.util.dirutil import is_readable_dir
from pants.util.memo import memoized_method, memoized_property
MIN_OSX_SUPPORTED_VERSION = "10.11"
MIN_OSX_VERSION_ARG = f"-mmacosx-version-min={MIN_OSX_SUPPORTED_VERSION}"
class XCodeCLITools(Subsystem):
"""Subsystem to detect and provide the XCode command line developer tools.
This subsystem exists to give a useful error message if the tools aren't installed, and because
the install location may not be on the PATH when Pants is invoked.
"""
options_scope = "xcode-cli-tools"
_REQUIRED_FILES = {
"bin": ["as", "cc", "c++", "clang", "clang++", "ld", "lipo"],
# Any of the entries that would be here are not directly below the 'include' or 'lib' dirs, and
# we haven't yet encountered an invalid XCode/CLI tools installation which has the include dirs,
# but incorrect files. These would need to be updated if such an issue arises.
"include": [],
"lib": [],
}
INSTALL_PREFIXES_DEFAULT = [
# Prefer files from this installation directory, if available. This doesn't appear to be
# populated with e.g. header files on travis.
"/usr",
# Populated by the XCode CLI tools.
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr",
# Populated by the XCode app. These are derived from using the -v or -H switches invoking the
# osx clang compiler.
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.1.0",
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr",
]
class XCodeToolsUnavailable(Exception):
"""Thrown if the XCode CLI tools could not be located."""
class XCodeToolsInvalid(Exception):
"""Thrown if a method within this subsystem requests a nonexistent tool."""
@classmethod
def register_options(cls, register):
super().register_options(register)
register(
"--install-prefixes",
type=list,
default=cls.INSTALL_PREFIXES_DEFAULT,
advanced=True,
help="Locations to search for resources from the XCode CLI tools, including a "
"compiler, linker, header files, and some libraries. "
"Under this directory should be some selection of these subdirectories: "
f"{cls._REQUIRED_FILES.keys()}.",
)
@memoized_property
def _all_existing_install_prefixes(self):
return [pfx for pfx in self.options.install_prefixes if is_readable_dir(pfx)]
# NB: We use @memoized_method in this file for methods which may raise.
@memoized_method
def _get_existing_subdirs(self, subdir_name):
# TODO(#6143): We should attempt to use ParseSearchDirs here or find some documentation on which
# directories we should be adding to the include path, and why. If we do need to manually
# specify paths, use ArchiveFileMapper instead of doing all that logic over again in this file.
maybe_subdirs = [
os.path.join(pfx, subdir_name) for pfx in self._all_existing_install_prefixes
]
existing_dirs = [
existing_dir for existing_dir in maybe_subdirs if is_readable_dir(existing_dir)
]
required_files_for_dir = self._REQUIRED_FILES.get(subdir_name)
if required_files_for_dir:
for fname in required_files_for_dir:
found = False
for subdir in existing_dirs:
full_path = os.path.join(subdir, fname)
if os.path.isfile(full_path):
found = True
continue
if not found:
raise self.XCodeToolsUnavailable(
f"File '{fname}' in subdirectory '{subdir_name}' does not exist at any of the "
"specified prefixes. This file is required to build native code on this platform. You "
"may need to install the XCode command line developer tools from the Mac App Store.\n\n"
"If the XCode tools are installed and you are still seeing this message, please file "
"an issue at https://github.com/pantsbuild/pants/issues/new describing your "
"OSX environment and which file could not be found.\n"
f"The existing install prefixes were: {self._all_existing_install_prefixes}. These can "
f"be extended with --{self.get_options_scope_equivalent_flag_component()}-install-"
f"prefixes."
)
return existing_dirs
@memoized_method
def path_entries(self):
return self._get_existing_subdirs("bin")
@memoized_method
def lib_dirs(self):
return self._get_existing_subdirs("lib")
@memoized_method
def include_dirs(self, include_cpp_inc=False):
return self._get_existing_subdirs("include")
@memoized_method
def assembler(self) -> Assembler:
return Assembler(
path_entries=self.path_entries(),
exe_filename="as",
runtime_library_dirs=(),
extra_args=(),
)
@memoized_method
def linker(self) -> Linker:
return Linker(
path_entries=self.path_entries(),
exe_filename="ld",
runtime_library_dirs=(),
linking_library_dirs=(),
extra_args=(MIN_OSX_VERSION_ARG,),
extra_object_files=(),
)
@memoized_method
def c_compiler(self) -> CCompiler:
return CCompiler(
path_entries=self.path_entries(),
exe_filename="clang",
runtime_library_dirs=self.lib_dirs(),
include_dirs=self.include_dirs(),
extra_args=(MIN_OSX_VERSION_ARG,),
)
@memoized_method
def cpp_compiler(self) -> CppCompiler:
return CppCompiler(
path_entries=self.path_entries(),
exe_filename="clang++",
runtime_library_dirs=self.lib_dirs(),
include_dirs=self.include_dirs(include_cpp_inc=True),
extra_args=(MIN_OSX_VERSION_ARG,),
)
@rule
def get_assembler(xcode_cli_tools: XCodeCLITools) -> Assembler:
return xcode_cli_tools.assembler()
@rule
def get_ld(xcode_cli_tools: XCodeCLITools) -> Linker:
return xcode_cli_tools.linker()
@rule
def get_clang(xcode_cli_tools: XCodeCLITools) -> CCompiler:
return xcode_cli_tools.c_compiler()
@rule
def get_clang_plusplus(xcode_cli_tools: XCodeCLITools) -> CppCompiler:
return xcode_cli_tools.cpp_compiler()
def create_xcode_cli_tools_rules():
return [
get_assembler,
get_ld,
get_clang,
get_clang_plusplus,
]