__init__.py

Summary

Lines:

763 of 1018 covered (75.0%)

Branches:

179 of 352 taken (50.9%), 81 lines partially

Coverage:

68.8%

Directory:

ProjectModel

Missed lines:

72, 142, 186, 194-197, 225-226, 241, 244-245, 247, 249, 252, 254, 256, 264, 274, 281-284, 295, 306, 311, 427-438, 445-447, 457-458, 461-462, 494, 571-573, 578-583, 587, 753, 758, 779, 790-791, 806, 809, 812-816, 823, 828, 850-852, 855-856, 861-871, 880, 882, 884, 901, 906-910, 925-927, 945, 950-954, 959, 962, 965-966, 968, 970, 973, 975, 978, 980, 1001-1004, 1020, 1036, 1052, 1060-1067, 1071, 1079, 1089, 1105, 1116, 1122, 1161, 1171, 1195, 1199, 1212-1232, 1237-1238, 1248, 1255, 1259-1261, 1266-1270, 1275, 1283, 1292-1298, 1308-1311, 1319-1322, 1326, 1403, 1408, 1412, 1436, 1444-1447, 1456-1467, 1490-1493, 1501, 1504, 1507-1508, 1510, 1512, 1515, 1518-1520, 1522, 1538, 1551, 1564, 1572-1577, 1581, 1585, 1588-1597, 1600-1601, 1606, 1611-1615, 1621, 1624-1625, 1628-1632, 1641, 1651, 1667, 1678, 1683, 1762, 1765, 1768-1769, 1771, 1773, 1776, 1779-1781, 1789, 1821, 1825, 1833, 1843, 1847-1848, 1859, 1870, 1875

Source

pyEDAA/ProjectModel/__init__.pycoveredpartialuncoveredexcludednot executable
1# ==================================================================================================================== #
2#               _____ ____    _        _      ____            _           _   __  __           _      _                #
3#   _ __  _   _| ____|  _ \  / \      / \    |  _ \ _ __ ___ (_) ___  ___| |_|  \/  | ___   __| | ___| |               #
4#  | '_ \| | | |  _| | | | |/ _ \    / _ \   | |_) | '__/ _ \| |/ _ \/ __| __| |\/| |/ _ \ / _` |/ _ \ |               #
5#  | |_) | |_| | |___| |_| / ___ \  / ___ \ _|  __/| | | (_) | |  __/ (__| |_| |  | | (_) | (_| |  __/ |               #
6#  | .__/ \__, |_____|____/_/   \_\/_/   \_(_)_|   |_|  \___// |\___|\___|\__|_|  |_|\___/ \__,_|\___|_|               #
7#  |_|    |___/                                            |__/                                                        #
8# ==================================================================================================================== #
9# Authors:                                                                                                             #
10#   Patrick Lehmann                                                                                                    #
11#                                                                                                                      #
12# License:                                                                                                             #
13# ==================================================================================================================== #
14# Copyright 2021-2026 Electronic Design Automation Abstraction (EDA²)                                                  #
15# Copyright 2017-2020 Patrick Lehmann - Boetzingen, Germany                                                            #
16# Copyright 2014-2016 Technische Universitaet Dresden - Germany, Chair of VLSI-Design, Diagnostics and Architecture    #
17#                                                                                                                      #
18# Licensed under the Apache License, Version 2.0 (the "License");                                                      #
19# you may not use this file except in compliance with the License.                                                     #
20# You may obtain a copy of the License at                                                                              #
21#                                                                                                                      #
22#   http://www.apache.org/licenses/LICENSE-2.0                                                                         #
23#                                                                                                                      #
24# Unless required by applicable law or agreed to in writing, software                                                  #
25# distributed under the License is distributed on an "AS IS" BASIS,                                                    #
26# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.                                             #
27# See the License for the specific language governing permissions and                                                  #
28# limitations under the License.                                                                                       #
29#                                                                                                                      #
30# SPDX-License-Identifier: Apache-2.0                                                                                  #
31# ==================================================================================================================== #
32#
33"""An abstract model of EDA tool projects."""
34__author__ =            "Patrick Lehmann"
35__email__ =             "Paebbels@gmail.com"
36__copyright__ =         "2021-2026, Electronic Design Automation Abstraction (EDA²)"
37__license__ =           "Apache License, Version 2.0"
38__version__ =           "0.7.0"
39__keywords__ =          ["eda project", "model", "abstract", "xilinx", "vivado", "osvvm", "file set", "file group", "test bench", "test harness"]
40__project_url__ =       "https://github.com/edaa-org/pyEDAA.ProjectModel"
41__documentation_url__ = "https://edaa-org.github.io/pyEDAA.ProjectModel"
42__issue_tracker_url__ = "https://GitHub.com/edaa-org/pyEDAA.ProjectModel/issues"
43
44from os.path               import relpath as path_relpath
45from pathlib               import Path as pathlib_Path
46from typing                import Dict, Union, Optional as Nullable, List, Iterable, Generator, Tuple, \
47                                  Any as typing_Any, Type, Set, Self
48
49from pyTooling.Common      import getFullyQualifiedName
50from pyTooling.Decorators  import export
51from pyTooling.MetaClasses import ExtendedType
52from pyTooling.Graph       import Graph, Vertex
53from pySVModel             import SystemVerilogVersion
54from pyVHDLModel           import VHDLVersion
55from pySystemRDLModel      import SystemRDLVersion
56
57
58@export
59class Attribute(metaclass=ExtendedType):
60	KEY: str
61	VALUE_TYPE: typing_Any
62
63	@staticmethod
64	def resolve(obj: typing_Any, key: Type['Attribute']):
65		if isinstance(obj, File):
66			return obj._fileSet[key]
67		elif isinstance(obj, FileSet):
68			return obj._design[key]
69		elif isinstance(obj, Design):
70			return obj._project[key]
71		else:
72			raise Exception("Resolution error")
73
74
75@export
76class FileType(ExtendedType):
77	"""
78	A :term:`meta-class` to construct *FileType* classes.
79
80	Modifications done by this meta-class:
81	* Register all classes of type :class:`FileType` or derived variants in a class field :attr:`FileType.FileTypes` in this meta-class.
82	"""
83
84	FileTypes: Dict[str, 'FileType'] = {}     #: Dictionary of all classes of type :class:`FileType` or derived variants
85	Any: 'FileType'
86
87	def __init__(cls, name: str, bases: Tuple[type, ...], dictionary: Dict[str, typing_Any], **kwargs) -> None:
88		super().__init__(name, bases, dictionary, **kwargs)
89		cls.Any = cls
90
91	def __new__(cls, className, baseClasses, classMembers: Dict, *args, **kwargs) -> Self:
92		fileType = super().__new__(cls, className, baseClasses, classMembers, *args, **kwargs)
93		cls.FileTypes[className] = fileType
94		return fileType
95
96	def __getattr__(cls, item) -> 'FileType':
97		if item[:2] != "__" and item[-2:] != "__":
98			return cls.FileTypes[item]
99		else:
100			return super().__getattribute__(item)
101
102	def __contains__(cls, item) -> bool:
103		return issubclass(item, cls)
104
105
106@export
107class File(metaclass=FileType, slots=True):
108	"""
109	A :term:`File` represents a file in a design. This :term:`base-class` is used
110	for all derived file classes.
111
112	A file can be created standalone and later associated to a fileset, design and
113	project. Or a fileset, design and/or project can be associated immediately
114	while creating a file.
115
116	:arg path:    Relative or absolute path to the file.
117	:arg project: Project the file is associated with.
118	:arg design:  Design the file is associated with.
119	:arg fileSet: Fileset the file is associated with.
120	"""
121
122	_path:       pathlib_Path
123	_fileType:   'FileType'
124	_project:    Nullable['Project']
125	_design:     Nullable['Design']
126	_fileSet:    Nullable['FileSet']
127	_attributes: Dict[Type[Attribute], typing_Any]
128
129	def __init__(
130		self,
131		path: pathlib_Path,
132		project: Nullable["Project"] = None,
133		design:  Nullable["Design"] =  None,
134		fileSet: Nullable["FileSet"] = None
135	) -> None:
136		self._fileType =  getattr(FileTypes, self.__class__.__name__)
137		self._path =      path
138		if project is not None:
139			self._project = project
140			self._design =  design
141			if fileSet is not None:
142				self.FileSet =  fileSet
143		elif design is not None:
144			self._project = design._project
145			self._design =  design
146			self.FileSet =  design.DefaultFileSet if fileSet is None else fileSet
147		elif fileSet is not None:
148			design = fileSet._design
149			if design is not None:
150				self._project = design._project
151			else:
152				self._project = None
153			self._design =    design
154			self.FileSet =    fileSet
155		else:
156			self._project = None
157			self._design =  None
158			self._fileSet = None
159
160		self._attributes = {}
161		self._registerAttributes()
162
163	def _registerAttributes(self) -> None:
164		pass
165
166	@property
167	def FileType(self) -> 'FileType':
168		"""Read-only property to return the file type of this file."""
169		return self._fileType
170
171	@property
172	def Path(self) -> pathlib_Path:
173		"""
174		Read-only property to access the path to the file.
175
176		:returns: The file's path.
177		"""
178		return self._path
179
180	# TODO: setter?
181
182	@property
183	def ResolvedPath(self) -> pathlib_Path:
184		"""Read-only property returning the resolved path of this file."""
185		if self._path.is_absolute():
186			return self._path.resolve()
187		elif self._fileSet is not None:
188			path = (self._fileSet.ResolvedPath / self._path).resolve()
189
190			if path.is_absolute():
191				return path
192			else:
193				# WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
194				return pathlib_Path(path_relpath(path, pathlib_Path.cwd()))
195		else:
196			# TODO: message and exception type
197			raise Exception("")
198
199	@property
200	def Project(self) -> Nullable['Project']:
201		"""Property setting or returning the project this file is used in."""
202		return self._project
203
204	@Project.setter
205	def Project(self, value: 'Project') -> None:
206		self._project = value
207
208		if self._fileSet is None:
209			self._project.DefaultDesign.DefaultFileSet.AddFile(self)
210
211	@property
212	def Design(self) -> Nullable['Design']:
213		"""Property setting or returning the design this file is used in."""
214		return self._design
215
216	@Design.setter
217	def Design(self, value: 'Design') -> None:
218		self._design = value
219
220		if self._fileSet is None:
221			self._design.DefaultFileSet.AddFile(self)
222
223		if self._project is None:
224			self._project = value._project
225		elif self._project is not value._project:
226			raise Exception("The design's project is not identical to the already assigned project.")
227
228	@property
229	def FileSet(self) -> Nullable['FileSet']:
230		"""Property setting or returning the fileset this file is used in."""
231		return self._fileSet
232
233	@FileSet.setter
234	def FileSet(self, value: 'FileSet') -> None:
235		self._fileSet = value
236		value._files.append(self)
237
238	def Validate(self) -> None:
239		"""Validate this file."""
240		if self._path is None:
241			raise Exception("Validation: File has no path.")
242		try:
243			path = self.ResolvedPath
244		except Exception as ex:
245			raise Exception(f"Validation: File '{self._path}' could not compute resolved path.") from ex
246		if not path.exists():
247			raise Exception(f"Validation: File '{self._path}' (={path}) does not exist.")
248		if not path.is_file():
249			raise Exception(f"Validation: File '{self._path}' (={path}) is not a file.")
250
251		if self._fileSet is None:
252			raise Exception(f"Validation: File '{self._path}' has no fileset.")
253		if self._design is None:
254			raise Exception(f"Validation: File '{self._path}' has no design.")
255		if self._project is None:
256			raise Exception(f"Validation: File '{self._path}' has no project.")
257
258	def __len__(self) -> int:
259		"""
260		Returns number of attributes set on this file.
261
262		:returns: The number of attributes set on this file.
263		"""
264		return len(self._attributes)
265
266	def __getitem__(self, key: Type[Attribute]) -> typing_Any:
267		"""Index access for returning attributes on this file.
268
269		:param key:        The attribute type.
270		:returns:          The attribute's value.
271		:raises TypeError: When parameter 'key' is not a subclass of Attribute.
272		"""
273		if not issubclass(key, Attribute):
274			raise TypeError("Parameter 'key' is not an 'Attribute'.")
275
276		try:
277			return self._attributes[key]
278		except KeyError:
279			try:
280				return key.resolve(self, key)
281			except KeyError:
282				attribute = key()
283				self._attributes[key] = attribute
284				return attribute
285
286	def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
287		"""
288		Index access for adding or setting attributes on this file.
289
290		:param key:        The attribute type.
291		:param value:      The attributes value.
292		:raises TypeError: When parameter 'key' is not a subclass of Attribute.
293		"""
294		if not issubclass(key, Attribute):
295			raise TypeError("Parameter 'key' is not an 'Attribute'.")
296
297		self._attributes[key] = value
298
299	def __delitem__(self, key: Type[Attribute]) -> None:
300		"""
301		Index access for deleting attributes on this file.
302
303		:param key: The attribute type.
304		"""
305		if not issubclass(key, Attribute):
306			raise TypeError("Parameter 'key' is not an 'Attribute'.")
307
308		del self._attributes[key]
309
310	def __str__(self) -> str:
311		return f"{self._path}"
312
313
314FileTypes = File
315
316
317@export
318class HumanReadableContent(metaclass=ExtendedType, mixin=True):
319	"""A file type representing human-readable contents."""
320
321
322@export
323class XMLContent(HumanReadableContent, mixin=True):
324	"""A file type representing XML contents."""
325
326
327@export
328class YAMLContent(HumanReadableContent, mixin=True):
329	"""A file type representing YAML contents."""
330
331
332@export
333class JSONContent(HumanReadableContent, mixin=True):
334	"""A file type representing JSON contents."""
335
336
337@export
338class INIContent(HumanReadableContent, mixin=True):
339	"""A file type representing INI contents."""
340
341
342@export
343class TOMLContent(HumanReadableContent, mixin=True):
344	"""A file type representing TOML contents."""
345
346
347@export
348class TCLContent(HumanReadableContent, mixin=True):
349	"""A file type representing content in TCL code."""
350
351
352@export
353class SDCContent(TCLContent, mixin=True):
354	"""A file type representing contents as Synopsys Design Constraints (SDC)."""
355
356
357@export
358class PythonContent(HumanReadableContent, mixin=True):
359	"""A file type representing contents as Python source code."""
360
361
362@export
363class TextFile(File, HumanReadableContent):
364	"""A text file (``*.txt``)."""
365
366
367@export
368class LogFile(File, HumanReadableContent):
369	"""A log file (``*.log``)."""
370
371
372@export
373class XMLFile(File, XMLContent):
374	"""An XML file (``*.xml``)."""
375
376
377@export
378class SourceFile(File):
379	"""Base-class of all source files."""
380
381
382@export
383class HDLSourceFile(SourceFile):
384	"""Base-class of all HDL source files."""
385
386
387@export
388class RDLSourceFile(SourceFile):
389	"""Base-class of all RDL source files."""
390
391
392@export
393class NetlistFile(SourceFile):
394	"""Base-class of all netlist source files."""
395
396
397@export
398class EDIFNetlistFile(NetlistFile):
399	"""Netlist file in EDIF (Electronic Design Interchange Format)."""
400
401
402@export
403class TCLSourceFile(SourceFile, TCLContent):
404	"""A TCL source file."""
405
406
407@export
408class VHDLSourceFile(HDLSourceFile, HumanReadableContent):
409	"""
410	A VHDL source file (of any language version).
411
412	:arg path:        Relative or absolute path to the file.
413	:arg vhdlLibrary: VHDLLibrary this VHDL source file is associated wih.
414	:arg vhdlVersion: VHDLVersion this VHDL source file is associated wih.
415	:arg project:     Project the file is associated with.
416	:arg design:      Design the file is associated with.
417	:arg fileSet:     Fileset the file is associated with.
418	"""
419
420	_vhdlLibrary: Nullable['VHDLLibrary']
421	_vhdlVersion: VHDLVersion
422
423	def __init__(self, path: pathlib_Path, vhdlLibrary: Union[str, 'VHDLLibrary'] = None, vhdlVersion: Nullable[VHDLVersion] = None, project: Nullable["Project"] = None, design: Nullable["Design"] = None, fileSet: Nullable["FileSet"] = None) -> None:
424		super().__init__(path, project, design, fileSet)
425
426		if isinstance(vhdlLibrary, str):
427			if design is not None:
428				try:
429					vhdlLibrary = design.VHDLLibraries[vhdlLibrary]
430				except KeyError as ex:
431					raise Exception(f"VHDL library '{vhdlLibrary}' not found in design '{design.Name}'.") from ex
432			elif project is not None:
433				try:
434					vhdlLibrary = project.DefaultDesign.VHDLLibraries[vhdlLibrary]
435				except KeyError as ex:
436					raise Exception(f"VHDL library '{vhdlLibrary}' not found in default design '{project.DefaultDesign.Name}'.") from ex
437			else:
438				raise Exception("Can't lookup VHDL library because neither 'project' nor 'design' is given as a parameter.")
439		elif isinstance(vhdlLibrary, VHDLLibrary):
440			self._vhdlLibrary = vhdlLibrary
441			vhdlLibrary.AddFile(self)
442		elif vhdlLibrary is None:
443			self._vhdlLibrary = None
444		else:
445			ex = TypeError("Parameter 'vhdlLibrary' is neither a 'str' nor 'VHDLibrary'.")
446			ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibrary)}'.")
447			raise ex
448
449		self._vhdlVersion = vhdlVersion
450
451	def Validate(self) -> None:
452		"""Validate this VHDL source file."""
453		super().Validate()
454
455		try:
456			_ = self.VHDLLibrary
457		except Exception as ex:
458			raise Exception(f"Validation: VHDLSourceFile '{self._path}' (={self.ResolvedPath}) has no VHDLLibrary assigned.") from ex
459		try:
460			_ = self.VHDLVersion
461		except Exception as ex:
462			raise Exception(f"Validation: VHDLSourceFile '{self._path}' (={self.ResolvedPath}) has no VHDLVersion assigned.") from ex
463
464	@property
465	def VHDLLibrary(self) -> 'VHDLLibrary':
466		"""Property setting or returning the VHDL library this VHDL source file is used in."""
467		if self._vhdlLibrary is not None:
468			return self._vhdlLibrary
469		elif self._fileSet is not None:
470			return self._fileSet.VHDLLibrary
471		else:
472			raise Exception("VHDLLibrary was neither set locally nor globally.")
473
474	@VHDLLibrary.setter
475	def VHDLLibrary(self, value: 'VHDLLibrary') -> None:
476		self._vhdlLibrary = value
477		value._files.append(self)
478
479	@property
480	def VHDLVersion(self) -> VHDLVersion:
481		"""Property setting or returning the VHDL version this VHDL source file is used in."""
482		if self._vhdlVersion is not None:
483			return self._vhdlVersion
484		elif self._fileSet is not None:
485			return self._fileSet.VHDLVersion
486		else:
487			raise Exception("VHDLVersion was neither set locally nor globally.")
488
489	@VHDLVersion.setter
490	def VHDLVersion(self, value: VHDLVersion) -> None:
491		self._vhdlVersion = value
492
493	def __repr__(self) -> str:
494		return f"<VHDL file: '{self.ResolvedPath}'; lib: '{self.VHDLLibrary}'; version: {self.VHDLVersion}>"
495
496
497class VerilogMixIn(metaclass=ExtendedType, mixin=True):
498	@property
499	def VerilogVersion(self) -> SystemVerilogVersion:
500		"""Property setting or returning the Verilog version this Verilog source file is used in."""
501		if self._version is not None:
502			return self._version
503		elif self._fileSet is not None:
504			return self._fileSet.VerilogVersion
505		else:
506			raise Exception("VerilogVersion was neither set locally nor globally.")
507
508	@VerilogVersion.setter
509	def VerilogVersion(self, value: SystemVerilogVersion) -> None:
510		self._version = value
511
512
513class SystemVerilogMixIn(metaclass=ExtendedType, mixin=True):
514	@property
515	def SVVersion(self) -> SystemVerilogVersion:
516		"""Property setting or returning the SystemVerilog version this SystemVerilog source file is used in."""
517		if self._version is not None:
518			return self._version
519		elif self._fileSet is not None:
520			return self._fileSet.SVVersion
521		else:
522			raise Exception("SVVersion was neither set locally nor globally.")
523
524	@SVVersion.setter
525	def SVVersion(self, value: SystemVerilogVersion) -> None:
526		self._version = value
527
528
529@export
530class VerilogBaseFile(HDLSourceFile, HumanReadableContent):
531	_version: SystemVerilogVersion
532
533	def __init__(self, path: pathlib_Path, version: Nullable[SystemVerilogVersion] = None, project: Nullable["Project"] = None, design: Nullable["Design"] = None, fileSet: Nullable["FileSet"] = None) -> None:
534		super().__init__(path, project, design, fileSet)
535
536		self._version = version
537
538
539@export
540class VerilogSourceFile(VerilogBaseFile, VerilogMixIn):
541	"""A Verilog source file (of any language version)."""
542
543
544@export
545class VerilogHeaderFile(VerilogBaseFile, VerilogMixIn):
546	"""A Verilog header file (of any language version)."""
547
548
549@export
550class SystemVerilogBaseFile(VerilogBaseFile):
551	...
552
553
554@export
555class SystemVerilogSourceFile(SystemVerilogBaseFile, SystemVerilogMixIn):
556	"""A SystemVerilog source file (of any language version)."""
557
558
559@export
560class SystemVerilogHeaderFile(SystemVerilogBaseFile, SystemVerilogMixIn):
561	"""A SystemVerilog header file (of any language version)."""
562
563
564@export
565class SystemRDLSourceFile(RDLSourceFile, HumanReadableContent):
566	"""A SystemRDL source file (of any language version)."""
567
568	_srdlVersion: SystemRDLVersion
569
570	def __init__(self, path: pathlib_Path, srdlVersion: Nullable[SystemRDLVersion] = None, project: Nullable["Project"] = None, design: Nullable["Design"] = None, fileSet: Nullable["FileSet"] = None) -> None:
571		super().__init__(path, project, design, fileSet)
572
573		self._srdlVersion = srdlVersion
574
575	@property
576	def SystemRDLVersion(self) -> SystemRDLVersion:
577		"""Property setting or returning the SystemRDL version this SystemRDL source file is used in."""
578		if self._srdlVersion is not None:
579			return self._srdlVersion
580		elif self._fileSet is not None:
581			return self._fileSet.SRDLVersion
582		else:
583			raise Exception("SRDLVersion was neither set locally nor globally.")
584
585	@SystemRDLVersion.setter
586	def SystemRDLVersion(self, value: SystemRDLVersion) -> None:
587		self._srdlVersion = value
588
589
590@export
591class PythonSourceFile(SourceFile, PythonContent):
592	"""A Python source file."""
593
594
595# TODO: move to a Cocotb module
596@export
597class CocotbPythonFile(PythonSourceFile):
598	"""A Python source file used by Cocotb."""
599
600
601@export
602class ConstraintFile(File, HumanReadableContent):
603	"""Base-class of all constraint files."""
604
605
606@export
607class ProjectFile(File):
608	"""Base-class of all tool-specific project files."""
609
610
611@export
612class CSourceFile(SourceFile):
613	"""Base-class of all ANSI-C source files."""
614
615
616@export
617class CppSourceFile(SourceFile):
618	"""Base-class of all ANSI-C++ source files."""
619
620
621@export
622class SettingFile(File):
623	"""Base-class of all tool-specific setting files."""
624
625
626@export
627class SimulationAnalysisFile(File):
628	"""Base-class of all tool-specific analysis files."""
629
630
631@export
632class SimulationElaborationFile(File):
633	"""Base-class of all tool-specific elaboration files."""
634
635
636@export
637class SimulationStartFile(File):
638	"""Base-class of all tool-specific simulation start-up files."""
639
640
641@export
642class SimulationRunFile(File):
643	"""Base-class of all tool-specific simulation run (execution) files."""
644
645
646@export
647class WaveformConfigFile(File):
648	"""Base-class of all tool-specific waveform configuration files."""
649
650
651@export
652class WaveformDatabaseFile(File):
653	"""Base-class of all tool-specific waveform database files."""
654
655
656@export
657class WaveformExchangeFile(File):
658	"""Base-class of all tool-independent waveform exchange files."""
659
660
661@export
662class FileSet(metaclass=ExtendedType, slots=True):
663	"""
664	A :term:`FileSet` represents a group of files. Filesets can have sub-filesets.
665
666	The order of insertion is preserved. A fileset can be created standalone and
667	later associated to another fileset, design and/or project. Or a fileset,
668	design and/or project can be associated immediately while creating the
669	fileset.
670
671	:arg name:            Name of this fileset.
672	:arg topLevel:        Name of the fileset's toplevel.
673	:arg directory:       Path of this fileset (absolute or relative to a parent fileset or design).
674	:arg project:         Project the file is associated with.
675	:arg design:          Design the file is associated with.
676	:arg parent:          Parent fileset if this fileset is nested.
677	:arg vhdlLibrary:     Default VHDL library for files in this fileset, if not specified for the file itself.
678	:arg vhdlVersion:     Default VHDL version for files in this fileset, if not specified for the file itself.
679	:arg verilogVersion:  Default Verilog version for files in this fileset, if not specified for the file itself.
680	:arg svVersion:       Default SystemVerilog version for files in this fileset, if not specified for the file itself.
681	:arg srdlVersion:     Default SystemRDL version for files in this fileset, if not specified for the file itself.
682	"""
683
684	_name:            str
685	_topLevel:        Nullable[str]
686	_project:         Nullable['Project']
687	_design:          Nullable['Design']
688	_directory:       pathlib_Path
689	_parent:          Nullable['FileSet']
690	_fileSets:        Dict[str, 'FileSet']
691	_files:           List[File]
692	_set:             Set
693	_attributes:      Dict[Type[Attribute], typing_Any]
694	_vhdlLibraries:   Dict[str, 'VHDLLibrary']
695	_vhdlLibrary:     'VHDLLibrary'
696	_vhdlVersion:     VHDLVersion
697	_verilogVersion:  SystemVerilogVersion
698	_svVersion:       SystemVerilogVersion
699	_srdlVersion:     SystemRDLVersion
700
701	def __init__(
702		self,
703		name: str,
704		topLevel:       Nullable[str] =                  None,
705		directory:      pathlib_Path =                   pathlib_Path("."),
706		project:        Nullable["Project"] =            None,
707		design:         Nullable["Design"] =             None,
708		parent:         Nullable['FileSet'] =            None,
709		vhdlLibrary:    Union[str, 'VHDLLibrary'] =      None,
710		vhdlVersion:    Nullable[VHDLVersion] =          None,
711		verilogVersion: Nullable[SystemVerilogVersion] = None,
712		svVersion:      Nullable[SystemVerilogVersion] = None,
713		srdlVersion:    Nullable[SystemRDLVersion] =     None
714	) -> None:
715		self._name =      name
716		self._topLevel =  topLevel
717		if project is not None:
718			self._project = project
719			self._design =  design if design is not None else project.DefaultDesign
720
721		elif design is not None:
722			self._project = design._project
723			self._design =  design
724		else:
725			self._project = None
726			self._design =  None
727		self._directory = directory
728		self._parent =    parent
729		self._fileSets =  {}
730		self._files =     []
731		self._set =     set()
732
733		if design is not None:
734			design._fileSets[name] = self
735
736		self._attributes =      {}
737		self._vhdlLibraries =   {}
738
739		# TODO: handle if vhdlLibrary is a string
740		self._vhdlLibrary =     vhdlLibrary
741		self._vhdlVersion =     vhdlVersion
742		self._verilogVersion =  verilogVersion
743		self._svVersion =       svVersion
744		self._srdlVersion =     srdlVersion
745
746	@property
747	def Name(self) -> str:
748		"""Property setting or returning the fileset's name."""
749		return self._name
750
751	@Name.setter
752	def Name(self, value: str) -> None:
753		self._name = value
754
755	@property
756	def TopLevel(self) -> str:
757		"""Property setting or returning the fileset's toplevel."""
758		return self._topLevel
759
760	@TopLevel.setter
761	def TopLevel(self, value: str) -> None:
762		self._topLevel = value
763
764	@property
765	def Project(self) -> Nullable['Project']:
766		"""Property setting or returning the project this fileset is used in."""
767		return self._project
768
769	@Project.setter
770	def Project(self, value: 'Project') -> None:
771		self._project = value
772
773	@property
774	def Design(self) -> Nullable['Design']:
775		"""Property setting or returning the design this fileset is used in."""
776		if self._design is not None:
777			return self._design
778		elif self._parent is not None:
779			return self._parent.Design
780		else:
781			return None
782			# TODO: raise exception instead
783			# QUESTION: how to handle if design and parent is set?
784
785	@Design.setter
786	def Design(self, value: 'Design') -> None:
787		self._design = value
788		if self._project is None:
789			self._project = value._project
790		elif self._project is not value._project:
791			raise Exception("The design's project is not identical to the already assigned project.")
792
793	@property
794	def Directory(self) -> pathlib_Path:
795		"""Property setting or returning the directory this fileset is located in."""
796		return self._directory
797
798	@Directory.setter
799	def Directory(self, value: pathlib_Path) -> None:
800		self._directory = value
801
802	@property
803	def ResolvedPath(self) -> pathlib_Path:
804		"""Read-only property returning the resolved path of this fileset."""
805		if self._directory.is_absolute():
806			return self._directory.resolve()
807		else:
808			if self._parent is not None:
809				directory = self._parent.ResolvedPath
810			elif self._design is not None:
811				directory = self._design.ResolvedPath
812			elif self._project is not None:
813				directory = self._project.ResolvedPath
814			else:
815				# TODO: message and exception type
816				raise Exception("")
817
818			directory = (directory / self._directory).resolve()
819			if directory.is_absolute():
820				return directory
821			else:
822				# WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
823				return pathlib_Path(path_relpath(directory, pathlib_Path.cwd()))
824
825	@property
826	def Parent(self) -> Nullable['FileSet']:
827		"""Property setting or returning the parent fileset this fileset is used in."""
828		return self._parent
829
830	@Parent.setter
831	def Parent(self, value: 'FileSet') -> None:
832		self._parent = value
833		value._fileSets[self._name] = self
834		# TODO: check it it already exists
835		# QUESTION: make an Add fileset method?
836
837	@property
838	def FileSets(self) -> Dict[str, 'FileSet']:
839		"""Read-only property returning the dictionary of sub-filesets."""
840		return self._fileSets
841
842	def Files(self, fileType: FileType = FileTypes.Any, fileSet: Union[bool, str, 'FileSet'] = None) -> Generator[File, None, None]:
843		"""
844		Method returning the files of this fileset.
845
846		:arg fileType: A filter for file types. Default: ``Any``.
847		:arg fileSet:  Specifies how to handle sub-filesets.
848		"""
849		if fileSet is False:
850			for file in self._files:
851				if file.FileType in fileType:
852					yield file
853		elif fileSet is None:
854			for fileSet in self._fileSets.values():
855				for file in fileSet.Files(fileType):
856					yield file
857			for file in self._files:
858				if file.FileType in fileType:
859					yield file
860		else:
861			if isinstance(fileSet, str):
862				fileSetName = fileSet
863				try:
864					fileSet = self._fileSets[fileSetName]
865				except KeyError as ex:
866					raise Exception(f"Fileset {fileSetName} not bound to fileset {self.Name}.") from ex
867			elif not isinstance(fileSet, FileSet):
868				raise TypeError("Parameter 'fileSet' is not of type 'str' or 'FileSet' nor value 'None'.")
869
870			for file in fileSet.Files(fileType):
871				yield file
872
873	def AddFileSet(self, fileSet: "FileSet") -> None:
874		"""
875		Method to add a single sub-fileset to this fileset.
876
877		:arg fileSet: A fileset to add to this fileset as sub-fileset.
878		"""
879		if not isinstance(fileSet, FileSet):
880			raise ValueError("Parameter 'fileSet' is not of type ProjectModel.FileSet.")
881		elif fileSet in self._fileSets:
882			raise Exception("Sub-fileset already contains this fileset.")
883		elif fileSet.Name in self._fileSets.keys():
884			raise Exception(f"Fileset already contains a sub-fileset named '{fileSet.Name}'.")
885
886		self._fileSets[fileSet.Name] = fileSet
887		fileSet._parent = self
888
889	def AddFileSets(self, fileSets: Iterable["FileSet"]) -> None:
890		"""
891		Method to add a multiple sub-filesets to this fileset.
892
893		:arg fileSets: An iterable of filesets to add each to the fileset.
894		"""
895		for fileSet in fileSets:
896			self.AddFileSet(fileSet)
897
898	@property
899	def FileSetCount(self) -> int:
900		"""Returns number of file sets excl. sub-filesets."""
901		return len(self._fileSets)
902
903	@property
904	def TotalFileSetCount(self) -> int:
905		"""Returns number of file sets incl. sub-filesets."""
906		fileSetCount = len(self._fileSets)
907		for fileSet in self._fileSets.values():
908			fileSetCount += fileSet.TotalFileSetCount
909
910		return fileSetCount
911
912	def AddFile(self, file: File) -> None:
913		"""
914		Method to add a single file to this fileset.
915
916		:arg file: A file to add to this fileset.
917		"""
918		if not isinstance(file, File):
919			raise TypeError("Parameter 'file' is not of type ProjectModel.File.")
920		elif file._fileSet is not None:
921			ex = ValueError(f"File '{file.Path!s}' is already part of fileset '{file.FileSet.Name}'.")
922			ex.add_note("A file can't be assigned to another fileset.")
923			raise ex
924		elif file in self._set:
925			ex = ValueError(f"File '{file.Path!s}' is already part of this fileset.")
926			ex.add_note("A file can't be added twice to a fileset.")
927			raise ex
928
929		self._files.append(file)
930		self._set.add(file)
931		file._fileSet = self
932
933	def AddFiles(self, files: Iterable[File]) -> None:
934		"""
935		Method to add a multiple files to this fileset.
936
937		:arg files: An iterable of files to add each to the fileset.
938		"""
939		for file in files:
940			self.AddFile(file)
941
942	@property
943	def FileCount(self) -> int:
944		"""Returns number of files excl. sub-filesets."""
945		return len(self._files)
946
947	@property
948	def TotalFileCount(self) -> int:
949		"""Returns number of files incl. the files in sub-filesets."""
950		fileCount = len(self._files)
951		for fileSet in self._fileSets.values():
952			fileCount += fileSet.FileCount
953
954		return fileCount
955
956	def Validate(self) -> None:
957		"""Validate this fileset."""
958		if self._name is None or self._name == "":
959			raise Exception("Validation: FileSet has no name.")
960
961		if self._directory is None:
962			raise Exception(f"Validation: FileSet '{self._name}' has no directory.")
963		try:
964			path = self.ResolvedPath
965		except Exception as ex:
966			raise Exception(f"Validation: FileSet '{self._name}' could not compute resolved path.") from ex
967		if not path.exists():
968			raise Exception(f"Validation: FileSet '{self._name}'s directory '{path}' does not exist.")
969		if not path.is_dir():
970			raise Exception(f"Validation: FileSet '{self._name}'s directory '{path}' is not a directory.")
971
972		if self._design is None:
973			raise Exception(f"Validation: FileSet '{self._directory}' has no design.")
974		if self._project is None:
975			raise Exception(f"Validation: FileSet '{self._directory}' has no project.")
976
977		for fileSet in self._fileSets.values():
978			fileSet.Validate()
979		for file in self._files:
980			file.Validate()
981
982	def GetOrCreateVHDLLibrary(self, name) -> 'VHDLLibrary':
983		if name in self._vhdlLibraries:
984			return self._vhdlLibraries[name]
985		elif name in self._design._vhdlLibraries:
986			library = self._design._vhdlLibraries[name]
987			self._vhdlLibraries[name] = library
988			return library
989		else:
990			library = VHDLLibrary(name, design=self._design, vhdlVersion=self._vhdlVersion)
991			self._vhdlLibraries[name] = library
992			return library
993
994	@property
995	def VHDLLibrary(self) -> 'VHDLLibrary':
996		"""Property setting or returning the VHDL library of this fileset."""
997		if self._vhdlLibrary is not None:
998			return self._vhdlLibrary
999		elif self._parent is not None:
1000			return self._parent.VHDLLibrary
1001		elif self._design is not None:
1002			return self._design.VHDLLibrary   # FIXME: no library property. add a DefaultVHDLLibrary property
1003		else:
1004			raise Exception("VHDLLibrary was neither set locally nor globally.")
1005
1006	@VHDLLibrary.setter
1007	def VHDLLibrary(self, value: 'VHDLLibrary') -> None:
1008		self._vhdlLibrary = value
1009
1010	@property
1011	def VHDLVersion(self) -> VHDLVersion:
1012		"""Property setting or returning the VHDL version of this fileset."""
1013		if self._vhdlVersion is not None:
1014			return self._vhdlVersion
1015		elif self._parent is not None:
1016			return self._parent.VHDLVersion
1017		elif self._design is not None:
1018			return self._design.VHDLVersion
1019		else:
1020			raise Exception("VHDLVersion was neither set locally nor globally.")
1021
1022	@VHDLVersion.setter
1023	def VHDLVersion(self, value: VHDLVersion) -> None:
1024		self._vhdlVersion = value
1025
1026	@property
1027	def VerilogVersion(self) -> SystemVerilogVersion:
1028		"""Property setting or returning the Verilog version of this fileset."""
1029		if self._verilogVersion is not None:
1030			return self._verilogVersion
1031		elif self._parent is not None:
1032			return self._parent.VerilogVersion
1033		elif self._design is not None:
1034			return self._design.VerilogVersion
1035		else:
1036			raise Exception("VerilogVersion was neither set locally nor globally.")
1037
1038	@VerilogVersion.setter
1039	def VerilogVersion(self, value: SystemVerilogVersion) -> None:
1040		self._verilogVersion = value
1041
1042	@property
1043	def SVVersion(self) -> SystemVerilogVersion:
1044		"""Property setting or returning the SystemVerilog version of this fileset."""
1045		if self._svVersion is not None:
1046			return self._svVersion
1047		elif self._parent is not None:
1048			return self._parent.SVVersion
1049		elif self._design is not None:
1050			return self._design.SVVersion
1051		else:
1052			raise Exception("SVVersion was neither set locally nor globally.")
1053
1054	@SVVersion.setter
1055	def SVVersion(self, value: SystemVerilogVersion) -> None:
1056		self._svVersion = value
1057
1058	@property
1059	def SRDLVersion(self) -> SystemRDLVersion:
1060		if self._srdlVersion is not None:
1061			return self._srdlVersion
1062		elif self._parent is not None:
1063			return self._parent.SRDLVersion
1064		elif self._design is not None:
1065			return self._design.SRDLVersion
1066		else:
1067			raise Exception("SRDLVersion was neither set locally nor globally.")
1068
1069	@SRDLVersion.setter
1070	def SRDLVersion(self, value: SystemRDLVersion) -> None:
1071		self._srdlVersion = value
1072
1073	def __len__(self) -> int:
1074		"""
1075		Returns number of attributes set on this fileset.
1076
1077		:returns: The number of attributes set on this fileset.
1078		"""
1079		return len(self._attributes)
1080
1081	def __getitem__(self, key: Type[Attribute]) -> typing_Any:
1082		"""Index access for returning attributes on this fileset.
1083
1084		:param key:        The attribute type.
1085		:returns:          The attribute's value.
1086		:raises TypeError: When parameter 'key' is not a subclass of Attribute.
1087		"""
1088		if not issubclass(key, Attribute):
1089			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1090
1091		try:
1092			return self._attributes[key]
1093		except KeyError:
1094			return key.resolve(self, key)
1095
1096	def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1097		"""
1098		Index access for adding or setting attributes on this fileset.
1099
1100		:param key:        The attribute type.
1101		:param value:      The attributes value.
1102		:raises TypeError: When parameter 'key' is not a subclass of Attribute.
1103		"""
1104		if not issubclass(key, Attribute):
1105			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1106
1107		self._attributes[key] = value
1108
1109	def __delitem__(self, key: Type[Attribute]) -> None:
1110		"""
1111		Index access for deleting attributes on this fileset.
1112
1113		:param key: The attribute type.
1114		"""
1115		if not issubclass(key, Attribute):
1116			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1117
1118		del self._attributes[key]
1119
1120	def __str__(self) -> str:
1121		"""Returns the fileset's name."""
1122		return self._name
1123
1124
1125@export
1126class VHDLLibrary(metaclass=ExtendedType, slots=True):
1127	"""
1128	A :term:`VHDLLibrary` represents a group of VHDL source files compiled into the same VHDL library.
1129
1130	:arg name:        The VHDL libraries' name.
1131	:arg project:     Project the VHDL library is associated with.
1132	:arg design:      Design the VHDL library is associated with.
1133	:arg vhdlVersion: Default VHDL version for files in this VHDL library, if not specified for the file itself.
1134	"""
1135
1136	_name:        str
1137	_project:     Nullable['Project']
1138	_design:      Nullable['Design']
1139	_files:       List[File]
1140	_vhdlVersion: VHDLVersion
1141
1142	_attributes:     Dict[Attribute, typing_Any]
1143	_dependencyNode: Vertex
1144
1145	def __init__(
1146		self,
1147		name: str,
1148		project:     Nullable["Project"] =   None,
1149		design:      Nullable["Design"] =    None,
1150		vhdlVersion: Nullable[VHDLVersion] = None
1151	) -> None:
1152		self._name =       name
1153		self._attributes = {}
1154
1155		if project is not None:
1156			self._project = project
1157			self._design = project._defaultDesign if design is None else design
1158			self._dependencyNode = Vertex(value=self, graph=self._design._vhdlLibraryDependencyGraph)
1159
1160			if name in self._design._vhdlLibraries:
1161				raise Exception(f"Library '{name}' already in design '{self._design.Name}'.")
1162			else:
1163				self._design._vhdlLibraries[name] = self
1164
1165		elif design is not None:
1166			self._project = design._project
1167			self._design = design
1168			self._dependencyNode = Vertex(value=self, graph=design._vhdlLibraryDependencyGraph)
1169
1170			if name in design._vhdlLibraries:
1171				raise Exception(f"Library '{name}' already in design '{design.Name}'.")
1172			else:
1173				design._vhdlLibraries[name] = self
1174
1175		else:
1176			self._project = None
1177			self._design =  None
1178			self._dependencyNode = None
1179
1180		self._files =     []
1181		self._vhdlVersion = vhdlVersion
1182
1183	@property
1184	def Name(self) -> str:
1185		return self._name
1186
1187	@property
1188	def Project(self) -> Nullable['Project']:
1189		"""Property setting or returning the project this VHDL library is used in."""
1190		return self._project
1191
1192	@Project.setter
1193	def Project(self, value: 'Project') -> None:
1194		if not isinstance(value, Project):
1195			raise TypeError("Parameter 'value' is not of type 'Project'.")
1196
1197		if value is None:
1198			# TODO: unlink VHDLLibrary from project
1199			self._project = None
1200		else:
1201			self._project = value
1202			if self._design is None:
1203				self._design = value._defaultDesign
1204
1205	@property
1206	def Design(self) -> Nullable['Design']:
1207		"""Property setting or returning the design this VHDL library is used in."""
1208		return self._design
1209
1210	@Design.setter
1211	def Design(self, value: 'Design') -> None:
1212		if not isinstance(value, Design):
1213			raise TypeError("Parameter 'value' is not of type 'Design'.")
1214
1215		if value is None:
1216			# TODO: unlink VHDLLibrary from design
1217			self._design = None
1218		else:
1219			if self._design is None:
1220				self._design = value
1221				self._dependencyNode = Vertex(value=self, graph=self._design._vhdlLibraryDependencyGraph)
1222			elif self._design is not value:
1223				# TODO: move VHDLLibrary to other design
1224				# TODO: create new vertex in dependency graph and remove vertex from old graph
1225				self._design = value
1226			else:
1227				pass
1228
1229			if self._project is None:
1230				self._project = value._project
1231			elif self._project is not value._project:
1232				raise Exception("The design's project is not identical to the already assigned project.")
1233
1234	@property
1235	def Files(self) -> Generator[File, None, None]:
1236		"""Read-only property to return all files in this VHDL library."""
1237		for file in self._files:
1238			yield file
1239
1240	@property
1241	def VHDLVersion(self) -> VHDLVersion:
1242		"""Property setting or returning the VHDL version of this VHDL library."""
1243		if self._vhdlVersion is not None:
1244			return self._vhdlVersion
1245		elif self._design is not None:
1246			return self._design.VHDLVersion
1247		else:
1248			raise Exception("VHDLVersion is not set on VHDLLibrary nor parent object.")
1249
1250	@VHDLVersion.setter
1251	def VHDLVersion(self, value: VHDLVersion) -> None:
1252		self._vhdlVersion = value
1253
1254	def AddDependency(self, library: 'VHDLLibrary') -> None:
1255		library.parent = self
1256
1257	def AddFile(self, vhdlFile: VHDLSourceFile) -> None:
1258		if not isinstance(vhdlFile, VHDLSourceFile):
1259			ex = TypeError("Parameter 'vhdlFile' is not a 'VHDLSourceFile'.")
1260			ex.add_note(f"Got type '{getFullyQualifiedName(vhdlFile)}'.")
1261			raise ex
1262
1263		self._files.append(vhdlFile)
1264
1265	def AddFiles(self, vhdlFiles: Iterable[VHDLSourceFile]) -> None:
1266		for vhdlFile in vhdlFiles:
1267			if not isinstance(vhdlFile, VHDLSourceFile):
1268				raise TypeError(f"Item '{vhdlFile}' in parameter 'vhdlFiles' is not a 'VHDLSourceFile'.")
1269
1270			self._files.append(vhdlFile)
1271
1272	@property
1273	def FileCount(self) -> int:
1274		"""Returns number of files."""
1275		return len(self._files)
1276
1277	def __len__(self) -> int:
1278		"""
1279		Returns number of attributes set on this VHDL library.
1280
1281		:returns: The number of attributes set on this VHDL library.
1282		"""
1283		return len(self._attributes)
1284
1285	def __getitem__(self, key: Type[Attribute]) -> typing_Any:
1286		"""Index access for returning attributes on this VHDL library.
1287
1288		:param key:        The attribute type.
1289		:returns:          The attribute's value.
1290		:raises TypeError: When parameter 'key' is not a subclass of Attribute.
1291		"""
1292		if not issubclass(key, Attribute):
1293			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1294
1295		try:
1296			return self._attributes[key]
1297		except KeyError:
1298			return key.resolve(self, key)
1299
1300	def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1301		"""
1302		Index access for adding or setting attributes on this VHDL library.
1303
1304		:param key:        The attribute type.
1305		:param value:      The attributes value.
1306		:raises TypeError: When parameter 'key' is not a subclass of Attribute.
1307		"""
1308		if not issubclass(key, Attribute):
1309			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1310
1311		self._attributes[key] = value
1312
1313	def __delitem__(self, key: Type[Attribute]) -> None:
1314		"""
1315		Index access for deleting attributes on this VHDL library.
1316
1317		:param key: The attribute type.
1318		"""
1319		if not issubclass(key, Attribute):
1320			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1321
1322		del self._attributes[key]
1323
1324	def __str__(self) -> str:
1325		"""Returns the VHDL library's name."""
1326		return self._name
1327
1328
1329@export
1330class Design(metaclass=ExtendedType, slots=True):
1331	"""
1332	A :term:`Design` represents a group of filesets and the source files therein.
1333
1334	Each design contains at least one fileset - the :term:`default fileset`. For
1335	designs with VHDL source files, a independent `VHDLLibraries` overlay structure
1336	exists.
1337
1338	:arg name:            The design's name.
1339	:arg topLevel:        Name of the design's toplevel.
1340	:arg directory:       Path of this design (absolute or relative to the project).
1341	:arg project:         Project the design is associated with.
1342	:arg vhdlVersion:     Default VHDL version for files in this design, if not specified for the file itself.
1343	:arg verilogVersion:  Default Verilog version for files in this design, if not specified for the file itself.
1344	:arg svVersion:       Default SystemVerilog version for files in this design, if not specified for the file itself.
1345	:arg srdlVersion:     Default SystemRDL version for files in this fileset, if not specified for the file itself.
1346	"""
1347
1348	_name:                  str
1349	_topLevel:              Nullable[str]
1350	_project:               Nullable['Project']
1351	_directory:             pathlib_Path
1352	_fileSets:              Dict[str, FileSet]
1353	_defaultFileSet:        Nullable[FileSet]
1354	_attributes:            Dict[Type[Attribute], typing_Any]
1355
1356	_vhdlLibraries:         Dict[str, VHDLLibrary]
1357	_vhdlVersion:           VHDLVersion
1358	_verilogVersion:        SystemVerilogVersion
1359	_svVersion:             SystemVerilogVersion
1360	_srdlVersion:           SystemRDLVersion
1361	_externalVHDLLibraries: List
1362
1363	_vhdlLibraryDependencyGraph: Graph
1364	_fileDependencyGraph:        Graph
1365
1366	def __init__(
1367		self,
1368		name: str,
1369		topLevel:       Nullable[str] =                  None,
1370		directory:      pathlib_Path =                   pathlib_Path("."),
1371		project:        Nullable["Project"] =            None,
1372		vhdlVersion:    Nullable[VHDLVersion] =          None,
1373		verilogVersion: Nullable[SystemVerilogVersion] = None,
1374		svVersion:      Nullable[SystemVerilogVersion] = None,
1375		srdlVersion:    Nullable[SystemRDLVersion] =     None
1376	) -> None:
1377		self._name =                  name
1378		self._topLevel =              topLevel
1379		self._project =               project
1380		if project is not None:
1381			project._designs[name] = self
1382		self._directory =             directory
1383		self._fileSets =              {}
1384		self._defaultFileSet =        FileSet("default", project=project, design=self)
1385		self._attributes =            {}
1386		self._vhdlLibraries =         {}
1387		self._vhdlVersion =           vhdlVersion
1388		self._verilogVersion =        verilogVersion
1389		self._svVersion =             svVersion
1390		self._srdlVersion =           srdlVersion
1391		self._externalVHDLLibraries = []
1392
1393		self._vhdlLibraryDependencyGraph = Graph()
1394		self._fileDependencyGraph = Graph()
1395
1396	@property
1397	def Name(self) -> str:
1398		"""Property setting or returning the design's name."""
1399		return self._name
1400
1401	@Name.setter
1402	def Name(self, value: str) -> None:
1403		self._name = value
1404
1405	@property
1406	def TopLevel(self) -> str:
1407		"""Property setting or returning the fileset's toplevel."""
1408		return self._topLevel
1409
1410	@TopLevel.setter
1411	def TopLevel(self, value: str) -> None:
1412		self._topLevel = value
1413
1414	@property
1415	def Project(self) -> Nullable['Project']:
1416		"""Property setting or returning the project this design is used in."""
1417		return self._project
1418
1419	@Project.setter
1420	def Project(self, value: 'Project') -> None:
1421		self._project = value
1422
1423	@property
1424	def Directory(self) -> pathlib_Path:
1425		"""Property setting or returning the directory this design is located in."""
1426		return self._directory
1427
1428	@Directory.setter
1429	def Directory(self, value: pathlib_Path) -> None:
1430		self._directory = value
1431
1432	@property
1433	def ResolvedPath(self) -> pathlib_Path:
1434		"""Read-only property returning the resolved path of this fileset."""
1435		if self._directory.is_absolute():
1436			return self._directory.resolve()
1437		elif self._project is not None:
1438			path = (self._project.ResolvedPath / self._directory).resolve()
1439
1440			if path.is_absolute():
1441				return path
1442			else:
1443				# WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
1444				return pathlib_Path(path_relpath(path, pathlib_Path.cwd()))
1445		else:
1446			# TODO: message and exception type
1447			raise Exception("")
1448
1449	@property
1450	def DefaultFileSet(self) -> FileSet:
1451		"""Property setting or returning the default fileset of this design."""
1452		return self._defaultFileSet
1453
1454	@DefaultFileSet.setter
1455	def DefaultFileSet(self, value: Union[str, FileSet]) -> None:
1456		if isinstance(value, str):
1457			if value not in self._fileSets.keys():
1458				raise Exception(f"Fileset '{value}' is not in this design.")
1459
1460			self._defaultFileSet = self._fileSets[value]
1461		elif isinstance(value, FileSet):
1462			if value not in self.FileSets:
1463				raise Exception(f"Fileset '{value}' is not associated to this design.")
1464
1465			self._defaultFileSet = value
1466		else:
1467			raise ValueError("Unsupported parameter type for 'value'.")
1468
1469	# TODO: return generator with another method
1470	@property
1471	def FileSets(self) -> Dict[str, FileSet]:
1472		"""Read-only property returning the dictionary of filesets."""
1473		return self._fileSets
1474
1475	def Files(self, fileType: FileType = FileTypes.Any, fileSet: Union[str, FileSet] = None) -> Generator[File, None, None]:
1476		"""
1477		Method returning the files of this design.
1478
1479		:arg fileType: A filter for file types. Default: ``Any``.
1480		:arg fileSet:  Specifies if all files from all filesets (``fileSet=None``) are files from a single fileset are returned.
1481		"""
1482		if fileSet is None:
1483			for fileSet in self._fileSets.values():
1484				for file in fileSet.Files(fileType):
1485					yield file
1486		else:
1487			if isinstance(fileSet, str):
1488				try:
1489					fileSet = self._fileSets[fileSet]
1490				except KeyError as ex:
1491					raise Exception(f"Fileset {fileSet.Name} not bound to design {self.Name}.") from ex
1492			elif not isinstance(fileSet, FileSet):
1493				raise TypeError("Parameter 'fileSet' is not of type 'str' or 'FileSet' nor value 'None'.")
1494
1495			for file in fileSet.Files(fileType):
1496				yield file
1497
1498	def Validate(self) -> None:
1499		"""Validate this design."""
1500		if self._name is None or self._name == "":
1501			raise Exception("Validation: Design has no name.")
1502
1503		if self._directory is None:
1504			raise Exception(f"Validation: Design '{self._name}' has no directory.")
1505		try:
1506			path = self.ResolvedPath
1507		except Exception as ex:
1508			raise Exception(f"Validation: Design '{self._name}' could not compute resolved path.") from ex
1509		if not path.exists():
1510			raise Exception(f"Validation: Design '{self._name}'s directory '{path}' does not exist.")
1511		if not path.is_dir():
1512			raise Exception(f"Validation: Design '{self._name}'s directory '{path}' is not a directory.")
1513
1514		if len(self._fileSets) == 0:
1515			raise Exception(f"Validation: Design '{self._name}' has no fileset.")
1516		try:
1517			if self._defaultFileSet is not self._fileSets[self._defaultFileSet.Name]:
1518				raise Exception(f"Validation: Design '{self._name}'s default fileset is the same as listed in filesets.")
1519		except KeyError as ex:
1520			raise Exception(f"Validation: Design '{self._name}'s default fileset is not in list of filesets.") from ex
1521		if self._project is None:
1522			raise Exception(f"Validation: Design '{self._path}' has no project.")
1523
1524		for fileSet in self._fileSets.values():
1525			fileSet.Validate()
1526
1527	@property
1528	def VHDLLibraries(self) -> Dict[str, VHDLLibrary]:
1529		return self._vhdlLibraries
1530
1531	@property
1532	def VHDLVersion(self) -> VHDLVersion:
1533		if self._vhdlVersion is not None:
1534			return self._vhdlVersion
1535		elif self._project is not None:
1536			return self._project.VHDLVersion
1537		else:
1538			raise Exception("VHDLVersion was neither set locally nor globally.")
1539
1540	@VHDLVersion.setter
1541	def VHDLVersion(self, value: VHDLVersion) -> None:
1542		self._vhdlVersion = value
1543
1544	@property
1545	def VerilogVersion(self) -> SystemVerilogVersion:
1546		if self._verilogVersion is not None:
1547			return self._verilogVersion
1548		elif self._project is not None:
1549			return self._project.VerilogVersion
1550		else:
1551			raise Exception("VerilogVersion was neither set locally nor globally.")
1552
1553	@VerilogVersion.setter
1554	def VerilogVersion(self, value: SystemVerilogVersion) -> None:
1555		self._verilogVersion = value
1556
1557	@property
1558	def SVVersion(self) -> SystemVerilogVersion:
1559		if self._svVersion is not None:
1560			return self._svVersion
1561		elif self._project is not None:
1562			return self._project.SVVersion
1563		else:
1564			raise Exception("SVVersion was neither set locally nor globally.")
1565
1566	@SVVersion.setter
1567	def SVVersion(self, value: SystemVerilogVersion) -> None:
1568		self._svVersion = value
1569
1570	@property
1571	def SRDLVersion(self) -> SystemRDLVersion:
1572		if self._srdlVersion is not None:
1573			return self._srdlVersion
1574		elif self._project is not None:
1575			return self._project.SRDLVersion
1576		else:
1577			raise Exception("SRDLVersion was neither set locally nor globally.")
1578
1579	@SRDLVersion.setter
1580	def SRDLVersion(self, value: SystemRDLVersion) -> None:
1581		self._srdlVersion = value
1582
1583	@property
1584	def ExternalVHDLLibraries(self) -> List:
1585		return self._externalVHDLLibraries
1586
1587	def AddFileSet(self, fileSet: FileSet) -> None:
1588		if not isinstance(fileSet, FileSet):
1589			raise ValueError("Parameter 'fileSet' is not of type ProjectModel.FileSet.")
1590		elif fileSet in self._fileSets:
1591			raise Exception("Design already contains this fileset.")
1592		elif fileSet.Name in self._fileSets.keys():
1593			raise Exception(f"Design already contains a fileset named '{fileSet.Name}'.")
1594
1595		self._fileSets[fileSet.Name] = fileSet
1596		fileSet.Design = self
1597		fileSet._parent = self
1598
1599	def AddFileSets(self, fileSets: Iterable[FileSet]) -> None:
1600		for fileSet in fileSets:
1601			self.AddFileSet(fileSet)
1602
1603	@property
1604	def FileSetCount(self) -> int:
1605		"""Returns number of file sets excl. sub-filesets."""
1606		return len(self._fileSets)
1607
1608	@property
1609	def TotalFileSetCount(self) -> int:
1610		"""Returns number of file sets incl. sub-filesets."""
1611		fileSetCount = len(self._fileSets)
1612		for fileSet in self._fileSets.values():
1613			fileSetCount += fileSet.TotalFileSetCount
1614
1615		return fileSetCount
1616
1617	def AddFile(self, file: File) -> None:
1618		if file.FileSet is None:
1619			self._defaultFileSet.AddFile(file)
1620		else:
1621			raise ValueError(f"File '{file.Path!s}' is already part of fileset '{file.FileSet.Name}' and can't be assigned via Design to a default fileset.")
1622
1623	def AddFiles(self, files: Iterable[File]) -> None:
1624		for file in files:
1625			self.AddFile(file)
1626
1627	def AddVHDLLibrary(self, vhdlLibrary: VHDLLibrary) -> None:
1628		if vhdlLibrary.Name in self._vhdlLibraries:
1629			if self._vhdlLibraries[vhdlLibrary.Name] is vhdlLibrary:
1630				raise Exception(f"The VHDLLibrary '{vhdlLibrary.Name}' was already added to the design.")
1631			else:
1632				raise Exception(f"A VHDLLibrary with same name ('{vhdlLibrary.Name}') already exists for this design.")
1633
1634
1635	def __len__(self) -> int:
1636		"""
1637		Returns number of attributes set on this design.
1638
1639		:returns: The number of attributes set on this design.
1640		"""
1641		return len(self._attributes)
1642
1643	def __getitem__(self, key: Type[Attribute]) -> typing_Any:
1644		"""Index access for returning attributes on this design.
1645
1646		:param key:        The attribute type.
1647		:returns:          The attribute's value.
1648		:raises TypeError: When parameter 'key' is not a subclass of Attribute.
1649		"""
1650		if not issubclass(key, Attribute):
1651			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1652
1653		try:
1654			return self._attributes[key]
1655		except KeyError:
1656			return key.resolve(self, key)
1657
1658	def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1659		"""
1660		Index access for adding or setting attributes on this design.
1661
1662		:param key:        The attribute type.
1663		:param value:      The attributes value.
1664		:raises TypeError: When parameter 'key' is not a subclass of Attribute.
1665		"""
1666		if not issubclass(key, Attribute):
1667			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1668
1669		self._attributes[key] = value
1670
1671	def __delitem__(self, key: Type[Attribute]) -> None:
1672		"""
1673		Index access for deleting attributes on this design.
1674
1675		:param key: The attribute type.
1676		"""
1677		if not issubclass(key, Attribute):
1678			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1679
1680		del self._attributes[key]
1681
1682	def __str__(self) -> str:
1683		return self._name
1684
1685
1686@export
1687class Project(metaclass=ExtendedType, slots=True):
1688	"""
1689	A :term:`Project` represents a group of designs and the source files therein.
1690
1691	:arg name:            The project's name.
1692	:arg rootDirectory:   Base-path to the project.
1693	:arg vhdlVersion:     Default VHDL version for files in this project, if not specified for the file itself.
1694	:arg verilogVersion:  Default Verilog version for files in this project, if not specified for the file itself.
1695	:arg svVersion:       Default SystemVerilog version for files in this project, if not specified for the file itself.
1696	"""
1697
1698	_name:            str
1699	_rootDirectory:   pathlib_Path
1700	_designs:         Dict[str, Design]
1701	_defaultDesign:   Design
1702	_attributes:      Dict[Type[Attribute], typing_Any]
1703
1704	_vhdlVersion:     VHDLVersion
1705	_verilogVersion:  SystemVerilogVersion
1706	_svVersion:       SystemVerilogVersion
1707	_srdlVersion:     SystemRDLVersion
1708
1709	def __init__(
1710		self,
1711		name: str,
1712		rootDirectory:  pathlib_Path =                   pathlib_Path("."),
1713		vhdlVersion:    Nullable[VHDLVersion] =          None,
1714		verilogVersion: Nullable[SystemVerilogVersion] = None,
1715		svVersion:      Nullable[SystemVerilogVersion] = None
1716	) -> None:
1717		self._name =            name
1718		self._rootDirectory =   rootDirectory
1719		self._designs =         {}
1720		self._defaultDesign =   Design("default", project=self)
1721		self._attributes =      {}
1722		self._vhdlVersion =     vhdlVersion
1723		self._verilogVersion =  verilogVersion
1724		self._svVersion =       svVersion
1725
1726	@property
1727	def Name(self) -> str:
1728		"""Property setting or returning the project's name."""
1729		return self._name
1730
1731	@property
1732	def RootDirectory(self) -> pathlib_Path:
1733		"""Property setting or returning the root directory this project is located in."""
1734		return self._rootDirectory
1735
1736	@RootDirectory.setter
1737	def RootDirectory(self, value: pathlib_Path) -> None:
1738		self._rootDirectory = value
1739
1740	@property
1741	def ResolvedPath(self) -> pathlib_Path:
1742		"""Read-only property returning the resolved path of this fileset."""
1743		path = self._rootDirectory.resolve()
1744		if self._rootDirectory.is_absolute():
1745			return path
1746		else:
1747			# WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
1748			return pathlib_Path(path_relpath(path, pathlib_Path.cwd()))
1749
1750	# TODO: return generator with another method
1751	@property
1752	def Designs(self) -> Dict[str, Design]:
1753		return self._designs
1754
1755	@property
1756	def DefaultDesign(self) -> Design:
1757		return self._defaultDesign
1758
1759	def Validate(self) -> None:
1760		"""Validate this project."""
1761		if self._name is None or self._name == "":
1762			raise Exception("Validation: Project has no name.")
1763
1764		if self._rootDirectory is None:
1765			raise Exception(f"Validation: Project '{self._name}' has no root directory.")
1766		try:
1767			path = self.ResolvedPath
1768		except Exception as ex:
1769			raise Exception(f"Validation: Project '{self._name}' could not compute resolved path.") from ex
1770		if not path.exists():
1771			raise Exception(f"Validation: Project '{self._name}'s directory '{path}' does not exist.")
1772		if not path.is_dir():
1773			raise Exception(f"Validation: Project '{self._name}'s directory '{path}' is not a directory.")
1774
1775		if len(self._designs) == 0:
1776			raise Exception(f"Validation: Project '{self._name}' has no design.")
1777		try:
1778			if self._defaultDesign is not self._designs[self._defaultDesign.Name]:
1779				raise Exception(f"Validation: Project '{self._name}'s default design is the same as listed in designs.")
1780		except KeyError as ex:
1781			raise Exception(f"Validation: Project '{self._name}'s default design is not in list of designs.") from ex
1782
1783		for design in self._designs.values():
1784			design.Validate()
1785
1786	@property
1787	def DesignCount(self) -> int:
1788		"""Returns number of designs."""
1789		return len(self._designs)
1790
1791	@property
1792	def VHDLVersion(self) -> VHDLVersion:
1793		# TODO: check for None and return exception
1794		return self._vhdlVersion
1795
1796	@VHDLVersion.setter
1797	def VHDLVersion(self, value: VHDLVersion) -> None:
1798		self._vhdlVersion = value
1799
1800	@property
1801	def VerilogVersion(self) -> SystemVerilogVersion:
1802		# TODO: check for None and return exception
1803		return self._verilogVersion
1804
1805	@VerilogVersion.setter
1806	def VerilogVersion(self, value: SystemVerilogVersion) -> None:
1807		self._verilogVersion = value
1808
1809	@property
1810	def SVVersion(self) -> SystemVerilogVersion:
1811		# TODO: check for None and return exception
1812		return self._svVersion
1813
1814	@SVVersion.setter
1815	def SVVersion(self, value: SystemVerilogVersion) -> None:
1816		self._svVersion = value
1817
1818	@property
1819	def SRDLVersion(self) -> SystemRDLVersion:
1820		# TODO: check for None and return exception
1821		return self._srdlVersion
1822
1823	@SRDLVersion.setter
1824	def SRDLVersion(self, value: SystemRDLVersion) -> None:
1825		self._srdlVersion = value
1826
1827	def __len__(self) -> int:
1828		"""
1829		Returns number of attributes set on this project.
1830
1831		:returns: The number of attributes set on this project.
1832		"""
1833		return len(self._attributes)
1834
1835	def __getitem__(self, key: Type[Attribute]) -> typing_Any:
1836		"""Index access for returning attributes on this project.
1837
1838		:param key:        The attribute type.
1839		:returns:          The attribute's value.
1840		:raises TypeError: When parameter 'key' is not a subclass of Attribute.
1841		"""
1842		if not issubclass(key, Attribute):
1843			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1844
1845		try:
1846			return self._attributes[key]
1847		except KeyError:
1848			return key.resolve(self, key)
1849
1850	def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1851		"""
1852		Index access for adding or setting attributes on this project.
1853
1854		:param key:        The attribute type.
1855		:param value:      The attributes value.
1856		:raises TypeError: When parameter 'key' is not a subclass of Attribute.
1857		"""
1858		if not issubclass(key, Attribute):
1859			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1860
1861		self._attributes[key] = value
1862
1863	def __delitem__(self, key: Type[Attribute]) -> None:
1864		"""
1865		Index access for deleting attributes on this project.
1866
1867		:param key: The attribute type.
1868		"""
1869		if not issubclass(key, Attribute):
1870			raise TypeError("Parameter 'key' is not an 'Attribute'.")
1871
1872		del self._attributes[key]
1873
1874	def __str__(self) -> str:
1875		return self._name