Skip to content

Commit da35177

Browse files
committed
Improve generator logic for cached cores with file inputs
Generators with file_input_parameters stored a file with the hash of the input files. If there was a mismatch, the cache was invalidated. This changes the logic so that there is instead a sub directory created with the hash of the input logic. This allows caching multiple cached results instead of just keeping the most recent one.
1 parent ede1767 commit da35177

File tree

5 files changed

+83
-81
lines changed

5 files changed

+83
-81
lines changed

fusesoc/edalizer.py

+26-63
Original file line numberDiff line numberDiff line change
@@ -581,17 +581,6 @@ def _sha256_file_input_hexdigest(self):
581581

582582
return hash.hexdigest()
583583

584-
def _fwrite_hash(self, hashfile, data):
585-
with open(hashfile, "w") as f:
586-
f.write(data)
587-
588-
def _fread_hash(self, hashfile):
589-
data = ""
590-
with open(hashfile) as f:
591-
data = f.read()
592-
593-
return data
594-
595584
def _run(self, generator_cwd):
596585
logger.info("Generating " + str(self.vlnv))
597586

@@ -633,6 +622,11 @@ def is_generator_cacheable(self):
633622
def is_cacheable(self):
634623
return self.is_input_cacheable() or self.is_generator_cacheable()
635624

625+
def acquire_cache_lock(self):
626+
have_lock = False
627+
# while not have_lock:
628+
# if
629+
636630
def generate(self):
637631
"""Run a parametrized generator
638632
@@ -642,14 +636,24 @@ def generate(self):
642636

643637
hexdigest = self._sha256_input_yaml_hexdigest()
644638

645-
logger.debug("Generator input yaml hash: " + hexdigest)
639+
logger.debug("Generator parameters hash: " + hexdigest)
646640

647641
generator_cwd = os.path.join(
648642
self.gen_root,
649643
"generator_cache",
650644
self.vlnv.sanitized_name + "-" + hexdigest,
651645
)
652646

647+
if "file_input_parameters" in self.generator:
648+
# If file_input_parameters has been configured in the generator
649+
# parameters will be iterated to look for files to add to the
650+
# input files hash calculation.
651+
file_input_hash = self._sha256_file_input_hexdigest()
652+
generator_cwd = os.path.join(generator_cwd, file_input_hash)
653+
logger.debug("Generator input files hash: " + file_input_hash)
654+
655+
logger.debug("Generator cwd: " + generator_cwd)
656+
653657
if os.path.lexists(generator_cwd) and not os.path.isdir(generator_cwd):
654658
raise RuntimeError(
655659
"Unable to create generator working directory since it already exists and is not a directory: "
@@ -658,59 +662,18 @@ def generate(self):
658662
+ "Remove it manually or run 'fusesoc gen clean'"
659663
)
660664

661-
if self.is_input_cacheable():
662-
# Input cache enabled. Check if cached output already exists in generator_cwd.
663-
logger.debug("Input cache enabled.")
664-
665-
# If file_input_parameters has been configured in the generator
666-
# parameters will be iterated to look for files to add to the
667-
# input files hash calculation.
668-
if "file_input_parameters" in self.generator:
669-
file_input_hash = self._sha256_file_input_hexdigest()
670-
671-
logger.debug("Generator file input hash: " + file_input_hash)
672-
673-
hashfile = os.path.join(generator_cwd, ".fusesoc_file_input_hash")
674-
675-
rerun = False
676-
677-
if os.path.isfile(hashfile):
678-
cached_hash = self._fread_hash(hashfile)
679-
logger.debug("Cached file input hash: " + cached_hash)
680-
681-
if not file_input_hash == cached_hash:
682-
logger.debug("File input has changed.")
683-
rerun = True
684-
else:
685-
logger.info("Found cached output for " + str(self.vlnv))
686-
687-
else:
688-
logger.debug("File input hash file does not exist: " + hashfile)
689-
rerun = True
690-
691-
if rerun:
692-
shutil.rmtree(generator_cwd, ignore_errors=True)
693-
self._run(generator_cwd)
694-
self._fwrite_hash(hashfile, file_input_hash)
695-
696-
elif os.path.isdir(generator_cwd):
697-
logger.info("Found cached output for " + str(self.vlnv))
698-
else:
699-
# No directory found. Run generator.
700-
self._run(generator_cwd)
701-
702-
elif self.is_generator_cacheable():
703-
# Generator cache enabled. Call the generator and let it
704-
# decide if the old output still is valid.
705-
logger.debug("Generator cache enabled.")
706-
self._run(generator_cwd)
665+
if self.is_input_cacheable() and os.path.isdir(generator_cwd):
666+
logger.info("Found cached output for " + str(self.vlnv))
707667
else:
708-
# No caching enabled. Try to remove directory if it already exists.
709-
# This could happen if a generator that has been configured with
710-
# caching is changed to no caching.
711-
logger.debug("Generator cache is not enabled.")
712-
shutil.rmtree(generator_cwd, ignore_errors=True)
668+
# TODO: Acquire a lock here to ensure that we are the only users
669+
if self.is_generator_cacheable():
670+
logger.warning(
671+
"Support for generator-side cachable cores are deprecated and will be removed"
672+
)
673+
else:
674+
shutil.rmtree(generator_cwd, ignore_errors=True)
713675
self._run(generator_cwd)
676+
# TODO: Release cache lock
714677

715678
cores = []
716679
logger.debug("Looking for generated or cached cores in " + generator_cwd)

tests/capi2_cores/misc/generate/generate.core

+11-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ targets:
1414
default:
1515
filesets :
1616
- default
17-
generate : [testgenerate_without_params, testgenerate_with_params, testgenerate_with_override : {the_value : 138}, testgenerate_with_cache]
17+
generate :
18+
- testgenerate_without_params
19+
- testgenerate_with_params
20+
- testgenerate_with_override : {the_value : 138}
21+
- testgenerate_with_cache
22+
- testgenerate_with_file_cache
1823
toplevel : na
1924

2025
nogenerate: {generate : []}
@@ -39,5 +44,10 @@ generate:
3944

4045
testgenerate_with_cache:
4146
generator: generator2
47+
parameters:
48+
some_option: some_value
49+
50+
testgenerate_with_file_cache:
51+
generator: generator3
4252
parameters:
4353
file_in_param1: file_cachetest

tests/capi2_cores/misc/generate/generators.core

+5
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,9 @@ generators:
1414
interpreter: python3
1515
command: testgen.py
1616
cache_type: input
17+
18+
generator3:
19+
interpreter: python3
20+
command: testgen.py
21+
cache_type: input
1722
file_input_parameters: file_in_param1

tests/test_capi2.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -324,11 +324,13 @@ def test_capi2_get_generators():
324324
core = Core(Core2Parser(), os.path.join(cores_dir, "generate", "generators.core"))
325325

326326
generators = core.get_generators()
327-
assert len(generators) == 2
327+
assert len(generators) == 3
328328
assert generators["generator1"]["command"] == "testgen.py"
329329
assert generators["generator2"]["command"] == "testgen.py"
330330
assert generators["generator2"]["cache_type"] == "input"
331-
assert generators["generator2"]["file_input_parameters"] == "file_in_param1"
331+
assert generators["generator3"]["command"] == "testgen.py"
332+
assert generators["generator3"]["cache_type"] == "input"
333+
assert generators["generator3"]["file_input_parameters"] == "file_in_param1"
332334

333335

334336
def test_capi2_get_parameters():
@@ -606,6 +608,12 @@ def test_capi2_get_ttptttg():
606608
"name": "testgenerate_with_cache",
607609
"generator": "generator2",
608610
"pos": "append",
611+
"config": {"some_option": "some_value"},
612+
},
613+
{
614+
"name": "testgenerate_with_file_cache",
615+
"generator": "generator3",
616+
"pos": "append",
609617
"config": {"file_in_param1": "file_cachetest"},
610618
},
611619
]

tests/test_edalizer.py

+31-15
Original file line numberDiff line numberDiff line change
@@ -148,18 +148,18 @@ def test_tool_or_flow():
148148

149149

150150
def test_generators():
151-
import os
152151
import shutil
153152
import tempfile
153+
from pathlib import Path
154154

155155
from fusesoc.config import Config
156156
from fusesoc.coremanager import CoreManager
157157
from fusesoc.edalizer import Edalizer
158158
from fusesoc.librarymanager import Library
159159
from fusesoc.vlnv import Vlnv
160160

161-
tests_dir = os.path.dirname(__file__)
162-
cores_dir = os.path.join(tests_dir, "capi2_cores", "misc", "generate")
161+
tests_dir = Path(__file__).parent
162+
cores_dir = tests_dir / "capi2_cores" / "misc" / "generate"
163163

164164
lib = Library("edalizer", cores_dir)
165165

@@ -168,14 +168,14 @@ def test_generators():
168168

169169
core = cm.get_core(Vlnv("::generate"))
170170

171-
build_root = tempfile.mkdtemp(prefix="export_")
172-
export_root = os.path.join(build_root, "exported_files")
171+
build_root = Path(tempfile.mkdtemp(prefix="export_"))
172+
export_root = build_root / "exported_files"
173173

174174
edalizer = Edalizer(
175175
toplevel=core.name,
176176
flags={"tool": "icarus"},
177177
core_manager=cm,
178-
work_root=os.path.join(build_root, "work"),
178+
work_root=build_root / "work",
179179
export_root=export_root,
180180
system_name=None,
181181
)
@@ -193,11 +193,13 @@ def test_generators():
193193
"::generate-testgenerate_with_params:0",
194194
"::generate-testgenerate_with_override:0",
195195
"::generate-testgenerate_with_cache:0",
196+
"::generate-testgenerate_with_file_cache:0",
196197
],
197198
"::generate-testgenerate_without_params:0": [],
198199
"::generate-testgenerate_with_params:0": [],
199200
"::generate-testgenerate_with_override:0": [],
200201
"::generate-testgenerate_with_cache:0": [],
202+
"::generate-testgenerate_with_file_cache:0": [],
201203
},
202204
"parameters": {"p": {"datatype": "str", "paramtype": "vlogparam"}},
203205
"tool_options": {"icarus": {}},
@@ -209,25 +211,39 @@ def test_generators():
209211
}
210212

211213
assert ref_edam == edalizer.edam
212-
edalizer.export()
213214

214215
name_to_core = {str(core.name): core for core in edalizer.cores}
215216
for flavour in ["testgenerate_with_params", "testgenerate_without_params"]:
216217
core_name = f"::generate-{flavour}:0"
217218
assert core_name in name_to_core
218219
core = name_to_core[core_name]
219220

220-
# Test generator input cache and file_input_params
221-
core_name = f"::generate-testgenerate_with_cache:0"
221+
# Test generator input without file_input_params
222+
core_name = "::generate-testgenerate_with_cache:0"
222223
assert core_name in name_to_core
223224
core = name_to_core[core_name]
224-
assert os.path.isdir(core.core_root)
225225

226-
hash = ""
227-
with open(os.path.join(core.core_root, ".fusesoc_file_input_hash")) as f:
228-
hash = f.read()
226+
core_root = Path(core.core_root)
227+
assert core_root.is_dir()
228+
assert (
229+
core_root.name
230+
== "generate-testgenerate_with_cache_0-616d6cf151dba72fcd893c08a8e18e6dba2b81ee25dec08c92e0177064dfc18c"
231+
)
232+
shutil.rmtree(core.core_root, ignore_errors=True)
229233

230-
# SHA256 hash of test file content
231-
assert hash == "da265f9dccc9d9e64d059f677508f9550b403c99e6ce5df07c6fb1d711d0ee99"
234+
# Test generator input file_input_params
235+
core_name = "::generate-testgenerate_with_file_cache:0"
236+
assert core_name in name_to_core
237+
core = name_to_core[core_name]
238+
core_root = Path(core.core_root)
232239

240+
assert core_root.is_dir()
241+
assert (
242+
core_root.name
243+
== "da265f9dccc9d9e64d059f677508f9550b403c99e6ce5df07c6fb1d711d0ee99"
244+
)
245+
assert (
246+
core_root.parent.name
247+
== "generate-testgenerate_with_file_cache_0-f3d9e1e462ef1f7113fafbacd62d6335dd684e69332f75498fb01bfaaa7c11ee"
248+
)
233249
shutil.rmtree(core.core_root, ignore_errors=True)

0 commit comments

Comments
 (0)