Skip to content

Commit 26e6996

Browse files
authored
Merge pull request #548 from nerdvegas/misc-build
Misc build issues addressed
2 parents 26ff151 + 98bcc46 commit 26e6996

File tree

18 files changed

+321
-92
lines changed

18 files changed

+321
-92
lines changed

src/rez/build_process_.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ def visit_variants(self, func, variants=None, **kwargs):
193193
% (variant.index, self._n_of_m(variant)))
194194
continue
195195

196+
# visit the variant
196197
result = func(variant, **kwargs)
197198
results.append(result)
198199
num_visited += 1

src/rez/build_system.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import os.path
2+
13
from rez.build_process_ import BuildType
24
from rez.exceptions import BuildSystemError
35
from rez.packages_ import get_developer_package
4-
import os.path
6+
from rez.backport.lru_cache import lru_cache
57

68

79
def get_buildsys_types():
@@ -10,8 +12,16 @@ def get_buildsys_types():
1012
return plugin_manager.get_plugins('build_system')
1113

1214

15+
@lru_cache()
1316
def get_valid_build_systems(working_dir):
14-
"""Returns the build system classes that could build the source in given dir."""
17+
"""Returns the build system classes that could build the source in given dir.
18+
19+
Note: This function is cached because the 'custom' build system type causes
20+
a package load (in order to get the 'build_command' package attribute). This
21+
in turn causes early-bound attribs to be evaluated, and those could be
22+
expensive (eg, a smart installer pkg that hits a website to get its valid
23+
versions).
24+
"""
1525
from rez.plugin_managers import plugin_manager
1626

1727
clss = []
@@ -79,8 +89,8 @@ def __init__(self, working_dir, opts=None, package=None,
7989
working_dir: Directory to build source from.
8090
opts: argparse.Namespace object which may contain constructor
8191
params, as set by our bind_cli() classmethod.
82-
package (`Package`): Package to build. If None, defaults to the
83-
unbuilt ('developer') package in the working directory.
92+
package (`DeveloperPackage`): Package to build. If None, defaults to
93+
the package in the working directory.
8494
write_build_scripts: If True, create build scripts rather than
8595
perform the full build. The user can then run these scripts to
8696
place themselves into a build environment and invoke the build
@@ -169,12 +179,14 @@ def get_standard_vars(cls, context, variant, build_type, install,
169179
from rez.config import config
170180

171181
package = variant.parent
182+
variant_requires = map(str, variant.variant_requires)
172183

173184
vars_ = {
174185
'REZ_BUILD_ENV': 1,
175186
'REZ_BUILD_PATH': build_path,
176187
'REZ_BUILD_THREAD_COUNT': package.config.build_thread_count,
177188
'REZ_BUILD_VARIANT_INDEX': variant.index or 0,
189+
'REZ_BUILD_VARIANT_REQUIRES': ' '.join(variant_requires),
178190
'REZ_BUILD_PROJECT_VERSION': str(package.version),
179191
'REZ_BUILD_PROJECT_NAME': package.name,
180192
'REZ_BUILD_PROJECT_DESCRIPTION': (package.description or '').strip(),

src/rez/cli/cp.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ def setup_parser(parser, completions=False):
2727
help="overwrite existing package/variants")
2828
parser.add_argument(
2929
"-s", "--shallow", action="store_true",
30-
help="perform a shallow copy (symlink directories)")
30+
help="perform a shallow copy (symlinks topmost directories)")
31+
parser.add_argument(
32+
"--follow-symlinks", action="store_true",
33+
help="follow symlinks when copying package payload, rather than copying "
34+
"the symlinks themselves.")
3135
parser.add_argument(
3236
"-k", "--keep-timestamp", action="store_true",
3337
help="keep timestamp of source package. Note that this is ignored if "
@@ -134,6 +138,7 @@ def command(opts, parser, extra_arg_groups=None):
134138
variants=variants,
135139
overwrite=opts.overwrite,
136140
shallow=opts.shallow,
141+
follow_symlinks=opts.follow_symlinks,
137142
keep_timestamp=opts.keep_timestamp,
138143
force=opts.force,
139144
verbose=opts.verbose,

src/rez/cli/depends.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def setup_parser(parser, completions=False):
1515
help="Include build requirements")
1616
parser.add_argument(
1717
"-p", "--private-build-requires", action="store_true",
18-
help="Include private build requirements")
18+
help="Include private build requirements of PKG, if any")
1919
parser.add_argument(
2020
"-g", "--graph", action="store_true",
2121
help="display the dependency tree as an image")

src/rez/developer_package.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from rez.config import config
22
from rez.packages_ import Package
3-
from rez.serialise import load_from_file, FileFormat
3+
from rez.serialise import load_from_file, FileFormat, set_objects
44
from rez.packages_ import create_package
55
from rez.exceptions import PackageMetadataError, InvalidPackageError
66
from rez.utils.system import add_sys_paths
@@ -118,6 +118,23 @@ def visit(d):
118118

119119
return package
120120

121+
def get_reevaluated(self, objects):
122+
"""Get a newly loaded and re-evaluated package.
123+
124+
Values in `objects` are made available to early-bound package
125+
attributes. For example, a re-evaluated package might return a different
126+
value for an early-bound 'private_build_requires', depending on the
127+
variant currently being built.
128+
129+
Args:
130+
objects (`dict`): Variables to expose to early-bound package attribs.
131+
132+
Returns:
133+
`DeveloperPackage`: New package.
134+
"""
135+
with set_objects(objects):
136+
return self.from_path(self.root)
137+
121138
def _validate_includes(self):
122139
if not self.includes:
123140
return

src/rez/package_copy.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from functools import partial
12
import os.path
23
import time
34

@@ -11,8 +12,8 @@
1112

1213
def copy_package(package, dest_repository, variants=None, shallow=False,
1314
dest_name=None, dest_version=None, overwrite=False, force=False,
14-
dry_run=False, keep_timestamp=False, skip_payload=False,
15-
overrides=None, verbose=False):
15+
follow_symlinks=False, dry_run=False, keep_timestamp=False,
16+
skip_payload=False, overrides=None, verbose=False):
1617
"""Copy a package from one package repository to another.
1718
1819
This copies the package definition and payload. The package can also be
@@ -58,6 +59,8 @@ def copy_package(package, dest_repository, variants=None, shallow=False,
5859
force (bool): Copy the package regardless of its relocatable attribute.
5960
Use at your own risk (there is no guarantee the resulting package
6061
will be functional).
62+
follow_symlinks (bool): Follow symlinks when copying package payload,
63+
rather than copying the symlinks themselves.
6164
keep_timestamp (bool): By default, a newly copied package will get a
6265
new timestamp (because that's when it was added to the target repo).
6366
By setting this option to True, the original package's timestamp
@@ -182,6 +185,7 @@ def finalize():
182185
src_variant=src_variant,
183186
dest_pkg_repo=dest_pkg_repo,
184187
shallow=shallow,
188+
follow_symlinks=follow_symlinks,
185189
overrides=overrides
186190
)
187191

@@ -207,7 +211,7 @@ def finalize():
207211

208212

209213
def _copy_variant_payload(src_variant, dest_pkg_repo, shallow=False,
210-
overrides=None):
214+
follow_symlinks=False, overrides=None):
211215
# Get payload path of source variant. For some types (eg from a "memory"
212216
# type repo) there may not be a root.
213217
#
@@ -241,7 +245,12 @@ def _copy_variant_payload(src_variant, dest_pkg_repo, shallow=False,
241245
if shallow:
242246
maybe_symlink = replacing_symlink
243247
else:
244-
maybe_symlink = replacing_copy
248+
maybe_symlink = partial(
249+
replacing_copy,
250+
copytree_kwargs={
251+
"symlinks": (not follow_symlinks)
252+
}
253+
)
245254

246255
if src_variant.subpath:
247256
# symlink/copy the last install dir to the variant root

src/rez/package_search.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ def get_reverse_dependency_tree(package_name, depth=None, paths=None,
3737
depth (int): Tree depth limit, unlimited if None.
3838
paths (list of str): paths to search for packages, defaults to
3939
`config.packages_path`.
40+
build_requires (bool): If True, includes packages' build_requires.
41+
private_build_requires (bool): If True, include `package_name`'s
42+
private_build_requires.
4043
4144
Returns:
4245
A 2-tuple:
@@ -71,7 +74,12 @@ def get_reverse_dependency_tree(package_name, depth=None, paths=None,
7174
requires = []
7275

7376
for variant in pkg.iter_variants():
74-
requires += variant.get_requires(build_requires, private_build_requires)
77+
pbr = (private_build_requires and pkg.name == package_name)
78+
79+
requires += variant.get_requires(
80+
build_requires=build_requires,
81+
private_build_requires=pbr
82+
)
7583

7684
for req in requires:
7785
if not req.conflict:

src/rez/packages_.py

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ class PackageBaseResourceWrapper(PackageRepositoryResourceWrapper):
7272
def __init__(self, resource, context=None):
7373
super(PackageBaseResourceWrapper, self).__init__(resource)
7474
self.context = context
75-
self._late_bindings = {}
75+
76+
# cached results of late-bound funcs
77+
self._late_binding_returnvalues = {}
7678

7779
def set_context(self, context):
7880
self.context = context
@@ -134,16 +136,19 @@ def print_info(self, buf=None, format_=FileFormat.yaml,
134136

135137
def _wrap_forwarded(self, key, value):
136138
if isinstance(value, SourceCode) and value.late_binding:
137-
value_ = self._late_bindings.get(key, KeyError)
139+
# get cached return value if present
140+
value_ = self._late_binding_returnvalues.get(key, KeyError)
138141

139142
if value_ is KeyError:
143+
# evaluate the late-bound function
140144
value_ = self._eval_late_binding(value)
141145

142146
schema = self.late_bind_schemas.get(key)
143147
if schema is not None:
144148
value_ = schema.validate(value_)
145149

146-
self._late_bindings[key] = value_
150+
# cache result of late bound func
151+
self._late_binding_returnvalues[key] = value_
147152

148153
return value_
149154
else:
@@ -162,14 +167,12 @@ def _eval_late_binding(self, sourcecode):
162167
bindings = self.context._get_pre_resolve_bindings()
163168
g.update(bindings)
164169

165-
# note that what 'this' actually points to depends on whether the context
166-
# is available or not. If not, then 'this' is a Package instance; if the
167-
# context is available, it is a Variant instance. So for example, if
168-
# in_context() is True, 'this' will have a 'root' attribute, but will
169-
# not if in_context() is False.
170+
# Note that 'this' could be a `Package` or `Variant` instance. This is
171+
# intentional; it just depends on how the package is accessed.
170172
#
171173
g["this"] = self
172174

175+
# evaluate the late-bound function
173176
sourcecode.set_package(self)
174177
return sourcecode.exec_(globals_=g)
175178

@@ -183,6 +186,12 @@ class Package(PackageBaseResourceWrapper):
183186
"""
184187
keys = schema_keys(package_schema)
185188

189+
# This is to allow for a simple check like 'this.is_package' in late-bound
190+
# funcs, where 'this' may be a package or variant.
191+
#
192+
is_package = True
193+
is_variant = False
194+
186195
def __init__(self, resource, context=None):
187196
_check_class(resource, PackageResource)
188197
super(Package, self).__init__(resource, context)
@@ -268,6 +277,10 @@ class Variant(PackageBaseResourceWrapper):
268277
keys = schema_keys(variant_schema)
269278
keys.update(["index", "root", "subpath"])
270279

280+
# See comment in `Package`
281+
is_package = False
282+
is_variant = True
283+
271284
def __init__(self, resource, context=None, parent=None):
272285
_check_class(resource, VariantResource)
273286
super(Variant, self).__init__(resource, context)
@@ -316,23 +329,31 @@ def parent(self):
316329

317330
return self._parent
318331

332+
@property
333+
def variant_requires(self):
334+
"""Get the subset of requirements specific to this variant.
335+
336+
Returns:
337+
List of `Requirement` objects.
338+
"""
339+
if self.index is None:
340+
return []
341+
else:
342+
return self.parent.variants[self.index] or []
343+
319344
@property
320345
def requires(self):
321346
"""Get variant requirements.
322347
323348
This is a concatenation of the package requirements and those of this
324349
specific variant.
325-
"""
326-
try:
327-
package_requires = self.parent.requires or []
328350
329-
if self.index is None:
330-
return package_requires
331-
else:
332-
variant_requires = self.parent.variants[self.index] or []
333-
return package_requires + variant_requires
334-
except AttributeError as e:
335-
reraise(e, ValueError)
351+
Returns:
352+
List of `Requirement` objects.
353+
"""
354+
return (
355+
(self.parent.requires or []) + self.variant_requires
356+
)
336357

337358
def get_requires(self, build_requires=False, private_build_requires=False):
338359
"""Get the requirements of the variant.
@@ -433,9 +454,9 @@ def _repository_uids(self):
433454
return uids
434455

435456

436-
#------------------------------------------------------------------------------
457+
# ------------------------------------------------------------------------------
437458
# resource acquisition functions
438-
#------------------------------------------------------------------------------
459+
# ------------------------------------------------------------------------------
439460

440461
def iter_package_families(paths=None):
441462
"""Iterate over package families, in no particular order.
@@ -551,6 +572,15 @@ def get_package_from_string(txt, paths=None):
551572

552573

553574
def get_developer_package(path, format=None):
575+
"""Create a developer package.
576+
577+
Args:
578+
path (str): Path to dir containing package definition file.
579+
format (str): Package definition file format, detected if None.
580+
581+
Returns:
582+
`DeveloperPackage`.
583+
"""
554584
from rez.developer_package import DeveloperPackage
555585
return DeveloperPackage.from_path(path, format=format)
556586

src/rez/rex.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,8 +1141,8 @@ def compile_code(cls, code, filename=None, exec_namespace=None):
11411141
code (str or SourceCode): The python code to compile.
11421142
filename (str): File to associate with the code, will default to
11431143
'<string>'.
1144-
namespace (dict): Namespace to execute the code in. If None, the
1145-
code is not executed.
1144+
exec_namespace (dict): Namespace to execute the code in. If None,
1145+
the code is not executed.
11461146
11471147
Returns:
11481148
Compiled code object.

0 commit comments

Comments
 (0)