Skip to content

Commit 83d0882

Browse files
committed
Ensure a spec cannot be instantiate twice for the same context
Having duplicates nodes with the same context leads to creation of corrupted trees Part of it/e3-core#54
1 parent 5b7e8e8 commit 83d0882

File tree

4 files changed

+122
-16
lines changed

4 files changed

+122
-16
lines changed

src/e3/anod/context.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -181,22 +181,54 @@ def load(
181181

182182
if key not in self.cache:
183183
# Spec is not in cache so create a new instance
184-
self.cache[key] = self.repo.load(name)(
185-
qualifier=qualifier, env=env, kind=kind
184+
result = self.repo.load(name)(qualifier=qualifier, env=env, kind=kind)
185+
186+
# Once a spec is loaded the qualifier has been expanded/normalized
187+
# to its final value (a dict with default values set).
188+
# After normalization, check if a spec instance with the same
189+
# context already exists to avoid creating duplicate instances for
190+
# logically equivalent spec instances and ensure cache correctness.
191+
# For that purpose recompute the cache key using the normalized
192+
# qualifier value.
193+
if result.enable_name_generator and kind != "source":
194+
final_qualifier_key = qualifier_dict_to_str(result.args)
195+
else:
196+
final_qualifier_key = result.qualifier
197+
final_key = (
198+
name,
199+
env.build,
200+
env.host,
201+
env.target,
202+
final_qualifier_key,
203+
kind,
204+
source_name,
186205
)
187-
if sandbox is not None:
188-
self.cache[key].bind_to_sandbox(sandbox)
189-
190-
# Update tracking of dependencies
191-
self.dependencies[self.cache[key].uid] = {}
192-
193-
# Update the list of available sources. ??? Should be done
194-
# once per spec (and not once per spec instance). Need some
195-
# spec cleanup to achieve that ???
196-
source_builders = self.cache[key].source_pkg_build
197-
if source_builders is not None:
198-
for s in source_builders:
199-
self.sources[s.name] = (name, s)
206+
207+
if final_key in self.cache:
208+
# The spec is in fact already in the cache so abandon the
209+
# instantiation, after updating the cache
210+
logger.debug(f"loading cached spec instance {result.uid}({kind})")
211+
self.cache[key] = self.cache[final_key]
212+
else:
213+
logger.debug(f"loading new spec instance {result.uid}({kind})")
214+
# By updating both entries in the cache we avoid useless
215+
# instantiation of the specs
216+
self.cache[final_key] = result
217+
self.cache[key] = result
218+
219+
if sandbox is not None:
220+
self.cache[key].bind_to_sandbox(sandbox)
221+
222+
# Update tracking of dependencies
223+
self.dependencies[self.cache[key].uid] = {}
224+
225+
# Update the list of available sources. ??? Should be done
226+
# once per spec (and not once per spec instance). Need some
227+
# spec cleanup to achieve that ???
228+
source_builders = self.cache[key].source_pkg_build
229+
if source_builders is not None:
230+
for s in source_builders:
231+
self.sources[s.name] = (name, s)
200232

201233
return self.cache[key]
202234

src/e3/anod/loader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def load(self, repository: AnodSpecRepository) -> Callable[..., Anod]:
232232
assert self.anod_class is not None
233233
return self.anod_class
234234

235-
logger.debug("loading anod spec: %s", self.name)
235+
logger.debug("loading anod spec class: %s", self.name)
236236

237237
# Create a new module
238238
mod_name = "anod_" + self.name
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from e3.anod.spec import Anod
2+
3+
4+
class Spec15(Anod):
5+
enable_name_generator = True
6+
7+
def declare_qualifiers_and_components(self, qualifiers_manager):
8+
qualifiers_manager.declare_tag_qualifier(name="q1", description="help q1")
9+
qualifiers_manager.declare_key_value_qualifier(
10+
name="q2", description="help of q2", default="default_q2"
11+
)
12+
13+
@Anod.primitive()
14+
def build(self):
15+
pass

tests/tests_e3/anod/context_test.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,65 @@ def test_add_anod_action14(self):
374374
result = ac.schedule(ac.always_download_source_resolver)
375375
assert "x86-linux.spec4.test" in list(result.vertex_data.keys())
376376

377+
def test_add_anod_action15(self):
378+
"""Check for duplicate nodes."""
379+
# The goal of the test is to ensure that only one instance of a given Anod spec
380+
# for a given context exists at a given time, independently of how the context
381+
# was passed.
382+
383+
ac = self.create_context()
384+
result = [
385+
ac.add_anod_action(
386+
"spec15", env=ac.default_env, qualifier=None, primitive="build"
387+
),
388+
ac.add_anod_action(
389+
"spec15", env=ac.default_env, qualifier={}, primitive="build"
390+
),
391+
ac.add_anod_action(
392+
"spec15", env=ac.default_env, qualifier={"q1": False}, primitive="build"
393+
),
394+
ac.add_anod_action(
395+
"spec15",
396+
env=ac.default_env,
397+
qualifier={"q1": False, "q2": "default_q2"},
398+
primitive="build",
399+
),
400+
ac.add_anod_action(
401+
"spec15",
402+
env=ac.default_env,
403+
qualifier="q2=default_q2",
404+
primitive="build",
405+
),
406+
ac.add_anod_action(
407+
"spec15",
408+
env=ac.default_env,
409+
qualifier="",
410+
primitive="build",
411+
),
412+
]
413+
for i in range(len(result) - 1):
414+
assert result[i].anod_instance is result[i + 1].anod_instance
415+
416+
result = [
417+
ac.add_anod_action(
418+
"spec15", env=ac.default_env, qualifier="q1", primitive="build"
419+
),
420+
ac.add_anod_action(
421+
"spec15",
422+
env=ac.default_env,
423+
qualifier="q1,q2=default_q2",
424+
primitive="build",
425+
),
426+
ac.add_anod_action(
427+
"spec15",
428+
env=ac.default_env,
429+
qualifier="q2=default_q2,q1",
430+
primitive="build",
431+
),
432+
]
433+
for i in range(len(result) - 1):
434+
assert result[i].anod_instance is result[i + 1].anod_instance
435+
377436
def test_source_fails_when_missing_source_primitive(self):
378437
"""Source action should fail when the source primitive is undefined.
379438

0 commit comments

Comments
 (0)