Skip to content

Commit f679089

Browse files
Split conan resolve by native_external_library targets (takeover). (#6630)
Takeover of #6492 (which has completely passed review) as it was blocked by progress on two other PRs I have up (#6486, #6628) due to potential merge conflicts, which I can resolve when they come up for each of these PRs to unblock landing them in parallel. The body of #6492 was: ### Problem As described in #6178, the `NativeExternalLibraryFiles` products of a `conan` resolve are not currently partitioned by target, which means it isn't possible to expose individual 3rdparty deps to only their declared dependents. ### Solution Partition the `NativeExternalLibraryFiles` product using `UnionProduct` while producing it in `NativeExternalLibraryFetch` (and switch to using isolated `vt.results_dir` directories per `external_native_library` target), and consume the split product in `NativeCompile` and `LinkSharedLibraries`. ### Result Only declared dependents have access to 3rdparty libraries. Fixes #6178.
1 parent 0f80c70 commit f679089

File tree

3 files changed

+66
-64
lines changed

3 files changed

+66
-64
lines changed

src/python/pants/backend/native/tasks/link_shared_libraries.py

+20-21
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,7 @@ class LinkSharedLibraryRequest(datatype([
3232
('external_lib_dirs', tuple),
3333
('external_lib_names', tuple),
3434
])):
35-
36-
@classmethod
37-
def with_external_libs_product(cls, external_libs_product=None, *args, **kwargs):
38-
if external_libs_product is None:
39-
lib_dirs = ()
40-
lib_names = ()
41-
else:
42-
lib_dirs = (external_libs_product.lib_dir,)
43-
lib_names = external_libs_product.lib_names
44-
45-
return cls(*args, external_lib_dirs=lib_dirs, external_lib_names=lib_names, **kwargs)
35+
pass
4636

4737

4838
class LinkSharedLibraries(NativeTask):
@@ -154,21 +144,30 @@ def _make_link_request(self,
154144
deps = self._retrieve_single_product_at_target_base(native_target_deps_product, vt.target)
155145

156146
all_compiled_object_files = []
157-
158147
for dep_tgt in deps:
159-
self.context.log.debug("dep_tgt: {}".format(dep_tgt))
160-
object_files = self._retrieve_single_product_at_target_base(compiled_objects_product, dep_tgt)
161-
self.context.log.debug("object_files: {}".format(object_files))
162-
object_file_paths = object_files.file_paths()
163-
self.context.log.debug("object_file_paths: {}".format(object_file_paths))
164-
all_compiled_object_files.extend(object_file_paths)
165-
166-
return LinkSharedLibraryRequest.with_external_libs_product(
148+
if compiled_objects_product.get(dep_tgt):
149+
self.context.log.debug("dep_tgt: {}".format(dep_tgt))
150+
object_files = self._retrieve_single_product_at_target_base(compiled_objects_product, dep_tgt)
151+
self.context.log.debug("object_files: {}".format(object_files))
152+
object_file_paths = object_files.file_paths()
153+
self.context.log.debug("object_file_paths: {}".format(object_file_paths))
154+
all_compiled_object_files.extend(object_file_paths)
155+
156+
external_lib_dirs = []
157+
external_lib_names = []
158+
if external_libs_product is not None:
159+
for nelf in external_libs_product.get_for_targets(deps):
160+
if nelf.lib_dir:
161+
external_lib_dirs.append(nelf.lib_dir)
162+
external_lib_names.extend(nelf.lib_names)
163+
164+
return LinkSharedLibraryRequest(
167165
linker=self.linker,
168166
object_files=tuple(all_compiled_object_files),
169167
native_artifact=vt.target.ctypes_native_library,
170168
output_dir=vt.results_dir,
171-
external_libs_product=external_libs_product)
169+
external_lib_dirs=tuple(external_lib_dirs),
170+
external_lib_names=tuple(external_lib_names))
172171

173172
_SHARED_CMDLINE_ARGS = {
174173
'darwin': lambda: ['-Wl,-dylib'],

src/python/pants/backend/native/tasks/native_compile.py

+13-11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from collections import defaultdict
1111

1212
from pants.backend.native.config.environment import Executable
13+
from pants.backend.native.targets.external_native_library import ExternalNativeLibrary
1314
from pants.backend.native.targets.native_library import NativeLibrary
1415
from pants.backend.native.tasks.native_external_library_fetch import NativeExternalLibraryFiles
1516
from pants.backend.native.tasks.native_task import NativeTask
@@ -51,7 +52,7 @@ class NativeCompile(NativeTask, AbstractClass):
5152
# operate on for `strict_deps` calculation.
5253
# NB: `source_target_constraint` must be overridden.
5354
source_target_constraint = None
54-
dependent_target_constraint = SubclassesOf(NativeLibrary)
55+
dependent_target_constraint = SubclassesOf(ExternalNativeLibrary, NativeLibrary)
5556

5657
# `NativeCompile` will use `workunit_label` as the name of the workunit when executing the
5758
# compiler process. `workunit_label` must be set to a string.
@@ -128,14 +129,14 @@ def execute(self):
128129
source_targets = self.context.targets(self.source_target_constraint.satisfied_by)
129130

130131
with self.invalidated(source_targets, invalidate_dependents=True) as invalidation_check:
131-
for vt in invalidation_check.invalid_vts:
132+
for vt in invalidation_check.all_vts:
132133
deps = self.native_deps(vt.target)
133134
self._add_product_at_target_base(native_deps_product, vt.target, deps)
134-
compile_request = self._make_compile_request(vt, deps, external_libs_product)
135-
self.context.log.debug("compile_request: {}".format(compile_request))
136-
self._compile(compile_request)
135+
if not vt.valid:
136+
compile_request = self._make_compile_request(vt, deps, external_libs_product)
137+
self.context.log.debug("compile_request: {}".format(compile_request))
138+
self._compile(compile_request)
137139

138-
for vt in invalidation_check.all_vts:
139140
object_files = self.collect_cached_objects(vt)
140141
self._add_product_at_target_base(object_files_product, vt.target, object_files)
141142

@@ -192,18 +193,19 @@ def get_compiler(self):
192193
def _compiler(self):
193194
return self.get_compiler()
194195

195-
def _get_third_party_include_dirs(self, external_libs_product):
196+
def _get_third_party_include_dirs(self, external_libs_product, dependencies):
196197
if not external_libs_product:
197198
return []
198199

199-
directory = external_libs_product.include_dir
200-
return [directory] if directory else []
200+
return [nelf.include_dir
201+
for nelf in external_libs_product.get_for_targets(dependencies)
202+
if nelf.include_dir]
201203

202204
def _make_compile_request(self, versioned_target, dependencies, external_libs_product):
203205
target = versioned_target.target
204206

205207
include_dirs = [self._include_dirs_for_target(dep_tgt) for dep_tgt in dependencies]
206-
include_dirs.extend(self._get_third_party_include_dirs(external_libs_product))
208+
include_dirs.extend(self._get_third_party_include_dirs(external_libs_product, dependencies))
207209

208210
sources_and_headers = self.get_sources_headers_for_target(target)
209211

@@ -221,12 +223,12 @@ def _make_compile_argv(self, compile_request):
221223
err_flags = ['-Werror'] if compile_request.fatal_warnings else []
222224

223225
# We are going to execute in the target output, so get absolute paths for everything.
224-
# TODO: If we need to produce static libs, don't add -fPIC! (could use Variants -- see #5788).
225226
buildroot = get_buildroot()
226227
argv = (
227228
[compiler.exe_filename] +
228229
compiler.extra_args +
229230
err_flags +
231+
# TODO: If we need to produce static libs, don't add -fPIC! (could use Variants -- see #5788).
230232
['-c', '-fPIC'] +
231233
[
232234
'-I{}'.format(os.path.join(buildroot, inc_dir))

src/python/pants/backend/native/tasks/native_external_library_fetch.py

+33-32
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
from pants.backend.native.targets.external_native_library import ExternalNativeLibrary
1616
from pants.base.build_environment import get_pants_cachedir
1717
from pants.base.exceptions import TaskError
18+
from pants.goal.products import UnionProducts
1819
from pants.invalidation.cache_manager import VersionedTargetSet
1920
from pants.task.task import Task
2021
from pants.util.contextutil import environment_as
21-
from pants.util.dirutil import safe_mkdir
2222
from pants.util.memo import memoized_property
2323
from pants.util.objects import Exactly, datatype
2424
from pants.util.process_handler import subprocess
@@ -94,6 +94,10 @@ def register_options(cls, register):
9494
register('--conan-remotes', type=list, default=['https://conan.bintray.com'], advanced=True,
9595
fingerprint=True, help='The conan remote to download conan packages from.')
9696

97+
@classmethod
98+
def implementation_version(cls):
99+
return super(NativeExternalLibraryFetch, cls).implementation_version() + [('NativeExternalLibraryFetch', 0)]
100+
97101
@classmethod
98102
def subsystem_dependencies(cls):
99103
return super(NativeExternalLibraryFetch, cls).subsystem_dependencies() + (Conan.scoped(cls),)
@@ -103,7 +107,10 @@ def product_types(cls):
103107
return [NativeExternalLibraryFiles]
104108

105109
@property
106-
def cache_target_dirs(self):
110+
def create_target_dirs(self):
111+
# We create per-target directories in order to act as isolated collections of fetched files.
112+
# But do not attempt to automatically cache then (cache_target_dirs), because the entire resolve
113+
# must be have its own merged cachekey/VT.
107114
return True
108115

109116
@memoized_property
@@ -128,40 +135,35 @@ def execute(self):
128135
with self.invalidated(native_lib_tgts,
129136
invalidate_dependents=True) as invalidation_check:
130137
resolve_vts = VersionedTargetSet.from_versioned_targets(invalidation_check.all_vts)
131-
vts_results_dir = self._prepare_vts_results_dir(resolve_vts)
132138
if invalidation_check.invalid_vts or not resolve_vts.valid:
133139
for vt in invalidation_check.all_vts:
134-
self._fetch_packages(vt, vts_results_dir)
140+
self._fetch_packages(vt)
135141

136-
native_external_libs_product = self._collect_external_libs(vts_results_dir)
142+
native_external_libs_product = self._collect_external_libs(invalidation_check.all_vts)
137143
self.context.products.register_data(NativeExternalLibraryFiles,
138144
native_external_libs_product)
139145

140-
def _prepare_vts_results_dir(self, vts):
141-
"""
142-
Given a `VersionedTargetSet`, prepare its results dir.
143-
"""
144-
vt_set_results_dir = os.path.join(self.workdir, vts.cache_key.hash)
145-
safe_mkdir(vt_set_results_dir)
146-
return vt_set_results_dir
147-
148-
def _collect_external_libs(self, results_dir):
146+
def _collect_external_libs(self, vts):
149147
"""
150148
Sets the relevant properties of the task product (`NativeExternalLibraryFiles`) object.
151149
"""
152-
lib_dir = os.path.join(results_dir, 'lib')
153-
include_dir = os.path.join(results_dir, 'include')
154-
155-
lib_names = []
156-
if os.path.isdir(lib_dir):
157-
for filename in os.listdir(lib_dir):
158-
lib_name = self._parse_lib_name_from_library_filename(filename)
159-
if lib_name:
160-
lib_names.append(lib_name)
161-
162-
return NativeExternalLibraryFiles(include_dir=include_dir,
163-
lib_dir=lib_dir,
164-
lib_names=tuple(lib_names))
150+
product = UnionProducts()
151+
for vt in vts:
152+
lib_dir = os.path.join(vt.results_dir, 'lib')
153+
include_dir = os.path.join(vt.results_dir, 'include')
154+
155+
lib_names = []
156+
if os.path.isdir(lib_dir):
157+
for filename in os.listdir(lib_dir):
158+
lib_name = self._parse_lib_name_from_library_filename(filename)
159+
if lib_name:
160+
lib_names.append(lib_name)
161+
162+
nelf = NativeExternalLibraryFiles(include_dir=include_dir,
163+
lib_dir=lib_dir,
164+
lib_names=tuple(lib_names))
165+
product.add_for_target(vt.target, [nelf])
166+
return product
165167

166168
def _get_conan_data_dir_path_for_package(self, pkg_dir_path, pkg_sha):
167169
return os.path.join(self.workdir,
@@ -237,14 +239,13 @@ def _copy_package_contents_from_conan_dir(self, results_dir, conan_requirement,
237239
if os.path.exists(src_include):
238240
copy_tree(src_include, dest_include)
239241

240-
def _fetch_packages(self, vt, vts_results_dir):
242+
def _fetch_packages(self, vt):
241243
"""
242244
Invoke the conan pex to fetch conan packages specified by a
243245
`ExternalLibLibrary` target.
244246
245-
:param vt: a versioned target containing conan package specifications.
246-
:param vts_results_dir: the results directory of the VersionedTargetSet
247-
for the purpose of aggregating package contents.
247+
:param vt: a versioned target containing conan package specifications, and with a results_dir
248+
that we can clone outputs into.
248249
"""
249250

250251
# NB: CONAN_USER_HOME specifies the directory to use for the .conan data directory.
@@ -274,4 +275,4 @@ def _fetch_packages(self, vt, vts_results_dir):
274275
)
275276

276277
pkg_sha = conan_requirement.parse_conan_stdout_for_pkg_sha(stdout)
277-
self._copy_package_contents_from_conan_dir(vts_results_dir, conan_requirement, pkg_sha)
278+
self._copy_package_contents_from_conan_dir(vt.results_dir, conan_requirement, pkg_sha)

0 commit comments

Comments
 (0)