From 7d56907ceaae5d2b3393fec90adb4a25ea09ebd3 Mon Sep 17 00:00:00 2001 From: Marcel Bargull Date: Tue, 27 Feb 2024 08:18:05 +0100 Subject: [PATCH 01/10] Use pickle's loads/dumps as a faster deepcopy Signed-off-by: Marcel Bargull --- conda_build/config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/conda_build/config.py b/conda_build/config.py index 5d4ba590d3..73b12c37a1 100644 --- a/conda_build/config.py +++ b/conda_build/config.py @@ -8,6 +8,7 @@ import copy import math import os +import pickle import re import shutil import time @@ -822,9 +823,10 @@ def clean_pkgs(self): def copy(self): new = copy.copy(self) - new.variant = copy.deepcopy(self.variant) + # Use picke.loads(pickle.dumps(...) as a faster copy.deepcopy alternative. + new.variant = pickle.loads(pickle.dumps(self.variant, -1)) if hasattr(self, "variants"): - new.variants = copy.deepcopy(self.variants) + new.variants = pickle.loads(pickle.dumps(self.variants, -1)) return new # context management - automatic cleanup if self.dirty or self.keep_old_work is not True From a7d86c35364f786369bbd0dafa59005bde4c1a24 Mon Sep 17 00:00:00 2001 From: Marcel Bargull Date: Tue, 27 Feb 2024 08:18:05 +0100 Subject: [PATCH 02/10] Properly re.escape variant variable in find_used_variables_* Signed-off-by: Marcel Bargull --- conda_build/variants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conda_build/variants.py b/conda_build/variants.py index 2ece5f4bd6..0df40136ef 100644 --- a/conda_build/variants.py +++ b/conda_build/variants.py @@ -773,7 +773,7 @@ def find_used_variables_in_shell_script(variant, file_path): text = f.read() used_variables = set() for v in variant: - variant_regex = r"(^[^$]*?\$\{?\s*%s\s*[\s|\}])" % v + variant_regex = rf"(^[^$]*?\$\{{?\s*{re.escape(v)}\s*[\s|\}}])" if re.search(variant_regex, text, flags=re.MULTILINE | re.DOTALL): used_variables.add(v) return used_variables @@ -784,7 +784,7 @@ def find_used_variables_in_batch_script(variant, file_path): text = f.read() used_variables = set() for v in variant: - variant_regex = r"\%" + v + r"\%" + variant_regex = rf"\%{re.escape(v)}\%" if re.search(variant_regex, text, flags=re.MULTILINE | re.DOTALL): used_variables.add(v) return used_variables From 0d3a0d43d8cdb23122c7b5389bdb3f8573d8d555 Mon Sep 17 00:00:00 2001 From: Marcel Bargull Date: Tue, 27 Feb 2024 08:18:05 +0100 Subject: [PATCH 03/10] Search static strings before costlier regex search Signed-off-by: Marcel Bargull --- conda_build/metadata.py | 3 +-- conda_build/variants.py | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index 633b6de8fc..b46acb187e 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -280,8 +280,7 @@ def select_lines(data, namespace, variants_in_place): if line.lstrip().startswith("#"): # Don't bother with comment only lines continue - m = sel_pat.match(line) - if m: + if "[" in line and "]" in line and (m := sel_pat.match(line)): cond = m.group(3) try: if eval_selector(cond, namespace, variants_in_place): diff --git a/conda_build/variants.py b/conda_build/variants.py index 0df40136ef..2e142c0298 100644 --- a/conda_build/variants.py +++ b/conda_build/variants.py @@ -773,6 +773,8 @@ def find_used_variables_in_shell_script(variant, file_path): text = f.read() used_variables = set() for v in variant: + if v not in text: + continue variant_regex = rf"(^[^$]*?\$\{{?\s*{re.escape(v)}\s*[\s|\}}])" if re.search(variant_regex, text, flags=re.MULTILINE | re.DOTALL): used_variables.add(v) @@ -784,6 +786,8 @@ def find_used_variables_in_batch_script(variant, file_path): text = f.read() used_variables = set() for v in variant: + if v not in text: + continue variant_regex = rf"\%{re.escape(v)}\%" if re.search(variant_regex, text, flags=re.MULTILINE | re.DOTALL): used_variables.add(v) From bd2dc1043c3b2ca0c7dfe719561674b79dd1a7f7 Mon Sep 17 00:00:00 2001 From: Marcel Bargull Date: Tue, 27 Feb 2024 08:18:05 +0100 Subject: [PATCH 04/10] Remove/avoid redundant function calls Signed-off-by: Marcel Bargull --- conda_build/metadata.py | 4 ++-- conda_build/variants.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index b46acb187e..fb44031b16 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -1665,7 +1665,6 @@ def build_id(self): raise RuntimeError( f"Couldn't extract raw recipe text for {self.name()} output" ) - raw_recipe_text = self.extract_package_and_build_text() raw_manual_build_string = re.search(r"\s*string:", raw_recipe_text) # user setting their own build string. Don't modify it. if manual_build_string and not ( @@ -2641,12 +2640,13 @@ def get_loop_vars(self): return variants.get_vars(_variants, loop_only=True) def get_used_loop_vars(self, force_top_level=False, force_global=False): + loop_vars = set(self.get_loop_vars()) return { var for var in self.get_used_vars( force_top_level=force_top_level, force_global=force_global ) - if var in self.get_loop_vars() + if var in loop_vars } def get_rendered_recipe_text( diff --git a/conda_build/variants.py b/conda_build/variants.py index 2e142c0298..76d2d93785 100644 --- a/conda_build/variants.py +++ b/conda_build/variants.py @@ -707,15 +707,16 @@ def get_package_variants(recipedir_or_metadata, config=None, variants=None): def get_vars(variants, loop_only=False): """For purposes of naming/identifying, provide a way of identifying which variables contribute to the matrix dimensionality""" + first_variant, *other_variants = variants special_keys = {"pin_run_as_build", "zip_keys", "ignore_version"} - special_keys.update(set(ensure_list(variants[0].get("extend_keys")))) + special_keys.update(set(ensure_list(first_variant.get("extend_keys")))) loop_vars = [ k - for k in variants[0] + for k, v in first_variant.items() if k not in special_keys and ( not loop_only - or any(variant[k] != variants[0][k] for variant in variants[1:]) + or any(variant[k] != v for variant in other_variants) ) ] return loop_vars From 0b774d08bc9380104a79c87ec96de1e3343aeb9f Mon Sep 17 00:00:00 2001 From: Marcel Bargull Date: Tue, 27 Feb 2024 08:18:05 +0100 Subject: [PATCH 05/10] Memoize re.match/re.findall calls in .metadata Signed-off-by: Marcel Bargull --- conda_build/metadata.py | 65 +++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index fb44031b16..2ee2b03735 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -110,6 +110,27 @@ def remove_constructor(cls, tag): used_vars_cache = {} +@lru_cache(maxsize=200) +def re_findall(pattern, text, flags=0): + if isinstance(pattern, re.Pattern): + return pattern.findall(text, flags) + return re.findall(pattern, text, flags) + + +@lru_cache(maxsize=200) +def re_match(pattern, text, flags=0): + if isinstance(pattern, re.Pattern): + return pattern.match(text, flags) + return re.match(pattern, text, flags) + + +@lru_cache(maxsize=200) +def re_search(pattern, text, flags=0): + if isinstance(pattern, re.Pattern): + return pattern.search(text, flags) + return re.search(pattern, text, flags) + + def get_selectors(config: Config) -> dict[str, bool]: """Aggregates selectors for use in recipe templating. @@ -241,7 +262,7 @@ def ns_cfg(config: Config) -> dict[str, bool]: # "NameError: name 'var' is not defined", where var is the variable that is not defined. This gets # returned def parseNameNotFound(error): - m = re.search("'(.+?)'", str(error)) + m = re_search("'(.+?)'", str(error)) if len(m.groups()) == 1: return m.group(1) else: @@ -280,7 +301,7 @@ def select_lines(data, namespace, variants_in_place): if line.lstrip().startswith("#"): # Don't bother with comment only lines continue - if "[" in line and "]" in line and (m := sel_pat.match(line)): + if "[" in line and "]" in line and (m := re_match(sel_pat, line)): cond = m.group(3) try: if eval_selector(cond, namespace, variants_in_place): @@ -1015,7 +1036,7 @@ def get_updated_output_dict_from_reparsed_metadata(original_dict, new_outputs): def _filter_recipe_text(text, extract_pattern=None): if extract_pattern: - match = re.search(extract_pattern, text, flags=re.MULTILINE | re.DOTALL) + match = re_search(extract_pattern, text, flags=re.MULTILINE | re.DOTALL) text = ( "\n".join({string for string in match.groups() if string}) if match else "" ) @@ -1621,7 +1642,7 @@ def get_hash_contents(self): dependencies = [ req for req in dependencies - if not exclude_pattern.match(req) or " " in self.config.variant[req] + if not re_match(exclude_pattern, req) or " " in self.config.variant[req] ] # retrieve values - this dictionary is what makes up the hash. @@ -1665,11 +1686,11 @@ def build_id(self): raise RuntimeError( f"Couldn't extract raw recipe text for {self.name()} output" ) - raw_manual_build_string = re.search(r"\s*string:", raw_recipe_text) + raw_manual_build_string = re_search(r"\s*string:", raw_recipe_text) # user setting their own build string. Don't modify it. if manual_build_string and not ( raw_manual_build_string - and re.findall(r"h\{\{\s*PKG_HASH\s*\}\}", raw_manual_build_string.string) + and re_findall(r"h\{\{\s*PKG_HASH\s*\}\}", raw_manual_build_string.string) ): check_bad_chrs(manual_build_string, "build/string") out = manual_build_string @@ -1678,7 +1699,7 @@ def build_id(self): out = build_string_from_metadata(self) if self.config.filename_hashing and self.final: hash_ = self.hash_dependencies() - if not re.findall("h[0-9a-f]{%s}" % self.config.hash_length, out): + if not re_findall("h[0-9a-f]{%s}" % self.config.hash_length, out): ret = out.rsplit("_", 1) try: int(ret[0]) @@ -2036,7 +2057,7 @@ def uses_jinja(self): return False with open(self.meta_path, "rb") as f: meta_text = UnicodeDammit(f.read()).unicode_markup - matches = re.findall(r"{{.*}}", meta_text) + matches = re_findall(r"{{.*}}", meta_text) return len(matches) > 0 @property @@ -2051,7 +2072,7 @@ def uses_vcs_in_meta(self) -> Literal["git", "svn", "mercurial"] | None: with open(self.meta_path, "rb") as f: meta_text = UnicodeDammit(f.read()).unicode_markup for _vcs in vcs_types: - matches = re.findall(rf"{_vcs.upper()}_[^\.\s\'\"]+", meta_text) + matches = re_findall(rf"{_vcs.upper()}_[^\.\s\'\"]+", meta_text) if len(matches) > 0 and _vcs != self.get_value("package/name"): if _vcs == "hg": _vcs = "mercurial" @@ -2074,7 +2095,7 @@ def uses_vcs_in_build(self) -> Literal["git", "svn", "mercurial"] | None: # 1. the vcs command, optionally with an exe extension # 2. a subcommand - for example, "clone" # 3. a target url or other argument - matches = re.findall( + matches = re_findall( rf"{vcs}(?:\.exe)?(?:\s+\w+\s+[\w\/\.:@]+)", build_script, flags=re.IGNORECASE, @@ -2144,7 +2165,7 @@ def extract_single_output_text( # first, need to figure out which index in our list of outputs the name matches. # We have to do this on rendered data, because templates can be used in output names recipe_text = self.extract_outputs_text(apply_selectors=apply_selectors) - output_matches = output_re.findall(recipe_text) + output_matches = re_findall(output_re, recipe_text) outputs = self.meta.get("outputs") or ( self.parent_outputs if hasattr(self, "parent_outputs") else None ) @@ -2185,15 +2206,15 @@ def numpy_xx(self) -> bool: """This is legacy syntax that we need to support for a while. numpy x.x means "pin run as build" for numpy. It was special-cased to only numpy.""" text = self.extract_requirements_text() - return bool(numpy_xx_re.search(text)) + return bool(re_search(numpy_xx_re, text)) @property def uses_numpy_pin_compatible_without_xx(self) -> tuple[bool, bool]: text = self.extract_requirements_text() - compatible_search = numpy_compatible_re.search(text) + compatible_search = re_search(numpy_compatible_re, text) max_pin_search = None if compatible_search: - max_pin_search = numpy_compatible_x_re.search(text) + max_pin_search = re_search(numpy_compatible_x_re, text) # compatible_search matches simply use of pin_compatible('numpy') # max_pin_search quantifies the actual number of x's in the max_pin field. The max_pin # field can be absent, which is equivalent to a single 'x' @@ -2210,7 +2231,7 @@ def uses_subpackage(self): if "name" in out: name_re = re.compile(r"^{}(\s|\Z|$)".format(out["name"])) in_reqs = any( - name_re.match(req) for req in self.get_depends_top_and_out("run") + re_match(name_re, req) for req in self.get_depends_top_and_out("run") ) if in_reqs: break @@ -2218,13 +2239,13 @@ def uses_subpackage(self): if not in_reqs and self.meta_path: data = self.extract_requirements_text(force_top_level=True) if data: - subpackage_pin = re.search(r"{{\s*pin_subpackage\(.*\)\s*}}", data) + subpackage_pin = re_search(r"{{\s*pin_subpackage\(.*\)\s*}}", data) return in_reqs or bool(subpackage_pin) @property def uses_new_style_compiler_activation(self): text = self.extract_requirements_text() - return bool(re.search(r"\{\{\s*compiler\(.*\)\s*\}\}", text)) + return bool(re_search(r"\{\{\s*compiler\(.*\)\s*\}\}", text)) def validate_features(self): if any( @@ -2285,7 +2306,7 @@ def variant_in_source(self): # We use this variant in the top-level recipe. # constrain the stored variants to only this version in the output # variant mapping - if re.search( + if re_search( r"\s*\{\{\s*%s\s*(?:.*?)?\}\}" % key, self.extract_source_text() ): return True @@ -2369,15 +2390,15 @@ def get_output_metadata(self, output): ) if build_reqs: build_reqs = [ - req for req in build_reqs if not subpackage_pattern.match(req) + req for req in build_reqs if not re_match(subpackage_pattern, req) ] if host_reqs: host_reqs = [ - req for req in host_reqs if not subpackage_pattern.match(req) + req for req in host_reqs if not re_match(subpackage_pattern, req) ] if run_reqs: run_reqs = [ - req for req in run_reqs if not subpackage_pattern.match(req) + req for req in run_reqs if not re_match(subpackage_pattern, req) ] requirements = {} @@ -2812,7 +2833,7 @@ def _get_used_vars_meta_yaml_helper( reqs_re = re.compile( r"requirements:.+?(?=^\w|\Z|^\s+-\s(?=name|type))", flags=re.M | re.S ) - reqs_text = reqs_re.search(recipe_text) + reqs_text = re_search(reqs_re, recipe_text) reqs_text = reqs_text.group() if reqs_text else "" return reqs_text, recipe_text From 31fefdb0ae9b556a9800a6b06acc2a2011263b68 Mon Sep 17 00:00:00 2001 From: Marcel Bargull Date: Tue, 27 Feb 2024 08:18:06 +0100 Subject: [PATCH 06/10] Memoize .metadata.select_lines/_filter_recipe_text Signed-off-by: Marcel Bargull --- conda_build/metadata.py | 59 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index 2ee2b03735..ed42b417d0 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -109,6 +109,10 @@ def remove_constructor(cls, tag): # used to avoid recomputing/rescanning recipe contents for used variables used_vars_cache = {} +# Placeholders singletons for os/os.environ used to memoize select_lines(). +_selector_placeholder_os = object() +_selector_placeholder_os_environ = object() + @lru_cache(maxsize=200) def re_findall(pattern, text, flags=0): @@ -289,6 +293,60 @@ def eval_selector(selector_string, namespace, variants_in_place): def select_lines(data, namespace, variants_in_place): + # Try to turn namespace into a hashable representation for memoization. + try: + namespace_copy = namespace.copy() + if namespace_copy.get("os") is os: + namespace_copy["os"] = _selector_placeholder_os + if namespace_copy.get("environ") is os.environ: + namespace_copy["environ"] = _selector_placeholder_os_environ + if "pin_run_as_build" in namespace_copy: + # This raises TypeError if pin_run_as_build is not a dict of dicts. + try: + namespace_copy["pin_run_as_build"] = tuple( + (key, tuple((k, v) for k, v in value.items())) + for key, value in namespace_copy["pin_run_as_build"].items() + ) + except (AttributeError, TypeError, ValueError): + # AttributeError: no .items method + # TypeError: .items() not iterable of iterables + # ValueError: .items() not iterable of only (k, v) tuples + raise TypeError + for k, v in namespace_copy.items(): + # Convert list/sets/tuple to tuples (of tuples if it contains + # list/set elements). Copy any other type verbatim and rather fall + # back to the non-memoized version to avoid wrong/lossy conversions. + if isinstance(v, (list, set, tuple)): + namespace_copy[k] = tuple( + tuple(e) if isinstance(e, (list, set)) else e + for e in v + ) + namespace_tuple = tuple(namespace_copy.items()) + # Raise TypeError if anything in namespace_tuple is not hashable. + hash(namespace_tuple) + except TypeError: + return _select_lines(data, namespace, variants_in_place) + return _select_lines_memoized(data, namespace_tuple, variants_in_place) + + +@lru_cache(maxsize=200) +def _select_lines_memoized(data, namespace_tuple, variants_in_place): + # Convert namespace_tuple to dict and undo the os/environ/pin_run_as_build + # replacements done in select_lines. + namespace = dict(namespace_tuple) + if namespace.get("os") is _selector_placeholder_os: + namespace["os"] = os + if namespace.get("environ") is _selector_placeholder_os_environ: + namespace["environ"] = os.environ + if "pin_run_as_build" in namespace: + namespace["pin_run_as_build"] = { + key: dict(value) + for key, value in namespace["pin_run_as_build"] + } + return _select_lines(data, namespace, variants_in_place) + + +def _select_lines(data, namespace, variants_in_place): lines = [] for i, line in enumerate(data.splitlines()): @@ -1034,6 +1092,7 @@ def get_updated_output_dict_from_reparsed_metadata(original_dict, new_outputs): return output_d +@lru_cache(maxsize=200) def _filter_recipe_text(text, extract_pattern=None): if extract_pattern: match = re_search(extract_pattern, text, flags=re.MULTILINE | re.DOTALL) From 0d3a9b867dda3108a8bc51cc6052621ddac2bb90 Mon Sep 17 00:00:00 2001 From: Marcel Bargull Date: Tue, 27 Feb 2024 08:18:05 +0100 Subject: [PATCH 07/10] Remove/avoid redundant function calls Signed-off-by: Marcel Bargull --- conda_build/metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index ed42b417d0..8e1f94f869 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -966,8 +966,8 @@ def get_output_dicts_from_metadata(metadata, outputs=None): outputs.append(OrderedDict(name=metadata.name())) for out in outputs: if ( - "package:" in metadata.get_recipe_text() - and out.get("name") == metadata.name() + out.get("name") == metadata.name() + and "package:" in metadata.get_recipe_text() ): combine_top_level_metadata_with_output(metadata, out) return outputs From 7355c72e8386d140af6d275cd7adc48bdd25b104 Mon Sep 17 00:00:00 2001 From: Marcel Bargull Date: Mon, 11 Mar 2024 14:56:56 +0100 Subject: [PATCH 08/10] Add news/5225-render-misc-speedups Signed-off-by: Marcel Bargull --- news/5225-render-misc-speedups | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 news/5225-render-misc-speedups diff --git a/news/5225-render-misc-speedups b/news/5225-render-misc-speedups new file mode 100644 index 0000000000..c6f84eab00 --- /dev/null +++ b/news/5225-render-misc-speedups @@ -0,0 +1,19 @@ +### Enhancements + +* Miscellaneous recipe rendering speed ups. (#5224 via #5225) + +### Bug fixes + +* + +### Deprecations + +* + +### Docs + +* + +### Other + +* From 42fc34dd28f289f67399593d48ee086caeec2378 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:00:05 +0000 Subject: [PATCH 09/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- conda_build/metadata.py | 17 ++++++++++------- conda_build/variants.py | 5 +---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index 8e1f94f869..a5bcd938a0 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -318,8 +318,7 @@ def select_lines(data, namespace, variants_in_place): # back to the non-memoized version to avoid wrong/lossy conversions. if isinstance(v, (list, set, tuple)): namespace_copy[k] = tuple( - tuple(e) if isinstance(e, (list, set)) else e - for e in v + tuple(e) if isinstance(e, (list, set)) else e for e in v ) namespace_tuple = tuple(namespace_copy.items()) # Raise TypeError if anything in namespace_tuple is not hashable. @@ -340,8 +339,7 @@ def _select_lines_memoized(data, namespace_tuple, variants_in_place): namespace["environ"] = os.environ if "pin_run_as_build" in namespace: namespace["pin_run_as_build"] = { - key: dict(value) - for key, value in namespace["pin_run_as_build"] + key: dict(value) for key, value in namespace["pin_run_as_build"] } return _select_lines(data, namespace, variants_in_place) @@ -2290,7 +2288,8 @@ def uses_subpackage(self): if "name" in out: name_re = re.compile(r"^{}(\s|\Z|$)".format(out["name"])) in_reqs = any( - re_match(name_re, req) for req in self.get_depends_top_and_out("run") + re_match(name_re, req) + for req in self.get_depends_top_and_out("run") ) if in_reqs: break @@ -2449,11 +2448,15 @@ def get_output_metadata(self, output): ) if build_reqs: build_reqs = [ - req for req in build_reqs if not re_match(subpackage_pattern, req) + req + for req in build_reqs + if not re_match(subpackage_pattern, req) ] if host_reqs: host_reqs = [ - req for req in host_reqs if not re_match(subpackage_pattern, req) + req + for req in host_reqs + if not re_match(subpackage_pattern, req) ] if run_reqs: run_reqs = [ diff --git a/conda_build/variants.py b/conda_build/variants.py index 76d2d93785..1627dcc5df 100644 --- a/conda_build/variants.py +++ b/conda_build/variants.py @@ -714,10 +714,7 @@ def get_vars(variants, loop_only=False): k for k, v in first_variant.items() if k not in special_keys - and ( - not loop_only - or any(variant[k] != v for variant in other_variants) - ) + and (not loop_only or any(variant[k] != v for variant in other_variants)) ] return loop_vars From 66cd6d87c2e3a099fad7935a053012f776b05ddf Mon Sep 17 00:00:00 2001 From: Marcel Bargull Date: Mon, 11 Mar 2024 19:26:46 +0100 Subject: [PATCH 10/10] Fallback to deepcopy if pickle.loads/dumps fails Signed-off-by: Marcel Bargull Co-authored-by: Matthew R. Becker --- conda_build/config.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/conda_build/config.py b/conda_build/config.py index 73b12c37a1..99c6853def 100644 --- a/conda_build/config.py +++ b/conda_build/config.py @@ -823,10 +823,16 @@ def clean_pkgs(self): def copy(self): new = copy.copy(self) - # Use picke.loads(pickle.dumps(...) as a faster copy.deepcopy alternative. - new.variant = pickle.loads(pickle.dumps(self.variant, -1)) + # Use picke.loads(pickle.dumps(...) as a faster deepcopy alternative, if possible. + try: + new.variant = pickle.loads(pickle.dumps(self.variant, -1)) + except: + new.variant = copy.deepcopy(self.variant) if hasattr(self, "variants"): - new.variants = pickle.loads(pickle.dumps(self.variants, -1)) + try: + new.variants = pickle.loads(pickle.dumps(self.variants, -1)) + except: + new.variants = copy.deepcopy(self.variants) return new # context management - automatic cleanup if self.dirty or self.keep_old_work is not True