Compare commits

...

15 Commits

Author SHA1 Message Date
3cf3fc1437 Refactors build script with CLI support
Introduces argparse-based CLI for build management, adding clear, build, and synth commands. Consolidates and simplifies build process logic while improving usability through structured command handling.

Improves maintainability and user experience.
2025-04-26 16:18:44 +00:00
12d9f4b6c9 Refactors file management and enhances task tracking
Replaces duplicate file copy logic with a unified `copy_file` function to improve code reuse and maintainability. Updates step tracking across the toolchain to reflect a consistent 12-step workflow. Adds a new entry point (`xilinx_ise_all`) to streamline execution of the entire toolchain.

Improves logging by integrating enhanced console utilities for better step visualization and progress tracking.
2025-04-26 16:18:38 +00:00
90859715c4 Enhances directory management with improved messaging
Replaces print statements with a ConsoleUtils helper for consistent, customizable output.
Adds a new `clear_build_directories` function to exclude specific directories from deletion.
Changes default behavior of `silent` parameter to `False` for better visibility.

Improves code readability and user feedback.
2025-04-26 16:18:29 +00:00
cc82d883c0 Enhances console task rendering with rich library
Refactors spinner and log rendering to use the Rich library for better formatting and improved user experience.
Adds support for task prefixes, step tracking, and transient live updates.
Replaces manual console manipulation with structured rendering for clarity and maintainability.
2025-04-26 16:18:19 +00:00
72699ed32f Adds Rich library dependency
Introduces Rich library (v14.0.0) to enhance text formatting and terminal output capabilities.
2025-04-26 16:18:09 +00:00
690decb33b Adds silent mode to directory management functions
Introduces a `silent` parameter to suppress console output in
`ensure_directories_exist` and `clear_directories` functions.
Enhances flexibility by allowing optional logging control.

No issue reference provided.
2025-04-26 15:18:00 +00:00
2e2d86cfc2 Adds console task utility with spinner and log management
Introduces a utility class for managing console tasks with a
spinner and log output. Includes features for logging messages,
running commands, and displaying real-time status updates.

Enhances user experience with clear task status visualization.
2025-04-26 15:17:54 +00:00
decc18ac83 Adds progress tracking and new trace tool integration
Introduces step tracking for Xilinx toolchain stages with step number and total steps added to tool execution calls. Enhances user feedback during execution.

Refactors `run_tool` to improve progress display and integrates `ConsoleTask` for better command handling. Simplifies code comments and improves readability.

Implements the `trace` tool support, including execution and report generation for timing analysis.

Adds minor enhancements like file copy feedback with icons.
2025-04-26 14:41:43 +00:00
5c52c0cf59 Refactors Xilinx ISE tool execution and report handling
Replaces repetitive tool execution logic with a reusable `run_tool` function to streamline code and reduce duplication.

Introduces a shared `copy_report_file` utility for consistent report copying and error handling.

Simplifies individual tool scripts (XST, NGDBuild, MAP, PAR, BitGen) by delegating core logic to common utilities.

Improves maintainability and consistency across the codebase.
2025-04-26 13:59:40 +00:00
91f4f03d97 Introduces support for Xilinx ISE build flow
Adds scripts and utilities for synthesis, mapping, placement, and bitstream generation using Xilinx ISE tools. Refactors configuration management into a dedicated module. Updates project model to support tool-specific options. Adjusts `.gitignore` and Python version compatibility.

Simplifies directory handling and ensures modularity by reorganizing configuration and tool logic.
2025-04-26 13:47:50 +00:00
7a03040926 Adds hdlbuild script for project configuration and source expansion
Testfile
2025-04-26 13:54:24 +02:00
f0100234c9 Adds utility to expand source file paths with wildcards
Introduces a function to resolve wildcard patterns in source file paths, normalizing and returning them as tuples of library and file path. Improves flexibility in handling source files.
2025-04-26 13:52:58 +02:00
cfca986f72 Adds utility functions for directory management
Implements functions to ensure directories exist and to clear them based on configuration. Provides user feedback for created, existing, or removed directories.

Helps manage project-specific directory structure efficiently.
2025-04-26 13:52:52 +02:00
defd2345b1 Adds YAML-based project configuration loader
Introduces a function to load and parse project configurations from a YAML file, returning a typed object for improved usability and type safety.
2025-04-26 13:52:33 +02:00
8b735c0d97 Adds project-specific configurations and dependencies
Updates .gitignore to exclude build artifacts and project-specific files.
Introduces pyproject.toml with project metadata, dependencies, and build system configuration using Poetry.

Facilitates project reproducibility and dependency management.
2025-04-26 13:51:47 +02:00
18 changed files with 757 additions and 0 deletions

11
.gitignore vendored
View File

@@ -174,3 +174,14 @@ cython_debug/
# PyPI configuration file
.pypirc
# hdlbuild specific
.hdlbuild_deps/
.working/
reports/
output/
vhdl/
poetry.lock
project.yml
.project/
.devcontainer/

41
project.example.yml Normal file
View File

@@ -0,0 +1,41 @@
name: VGA_Test
topmodule: VGA_Test_Top
target_device: xc3s1200e-4-fg320
xilinx_path: /opt/Xilinx/14.7/ISE_DS/ISE
sources:
vhdl:
- path: src/vga/*.vhd
library: work
- path: src/common/*.vhd
library: work
- path: src/VGA_test_top.vhd
library: work
verilog:
- path: src/old_modules/*.v
library: work
dependencies:
- name: AsyncFIFO
git: "https://github.com/0xMax32/Asynchronous-FIFO-AXI-Handshake.git"
rev: "main"
library: asyncfifo
- name: GrayCounter
git: "https://github.com/0xMax32/Gray-Counter.git"
rev: "v1.0.0"
library: graycounter
testbenches:
vhdl:
- path: src/tests/*.vhd
library: work
verilog: []
constraints: constraints/VGA_Test.ucf
build:
build_dir: working
report_dir: reports
copy_target_dir: output

19
pyproject.toml Normal file
View File

@@ -0,0 +1,19 @@
[tool.poetry]
name = "hdlbuild"
version = "0.1.0"
description = "Flexible FPGA Build System"
authors = ["0xMax42 <Mail@0xMax42.io>"]
license = "MIT"
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
pyyaml = "^6.0.2"
doit = "^0.36.0"
pydantic = "^2.11.3"
rich = "^14.0.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

70
src/hdlbuild.py Normal file
View File

@@ -0,0 +1,70 @@
import argparse
import sys
from tools.xilinx_ise.main import xilinx_ise_all, xilinx_ise_synth
from utils.console_utils import ConsoleUtils
from utils.directory_manager import clear_build_directories, clear_directories, ensure_directories_exist
from utils.project_loader import load_project_config
project = load_project_config()
console_utils = ConsoleUtils("hdlbuild")
def clear(args):
"""Clears the build artifacts."""
if args.target == "all":
console_utils.print("Starting clear all process...")
clear_directories()
console_utils.print("All cleared.")
else:
console_utils.print("Clearing build artifacts...")
clear_build_directories()
console_utils.print("Build artifacts cleared.")
def build(args):
"""Starts the build process."""
console_utils.print("Starting build process...")
ensure_directories_exist(True)
xilinx_ise_all(project)
def synth(args):
"""Starts the build process."""
console_utils.print("Starting build process...")
ensure_directories_exist()
xilinx_ise_synth(project)
def main():
parser = argparse.ArgumentParser(
description="hdlbuild - Build management tool for FPGA projects",
formatter_class=argparse.RawTextHelpFormatter
)
subparsers = parser.add_subparsers(
title="Commands",
description="Available commands",
dest="command",
required=True
)
# Clear command
parser_clear = subparsers.add_parser("clear", help="Clear build artifacts")
parser_clear.add_argument(
"target",
nargs="?",
choices=["all"],
help="Specify 'all' to clear everything (optional)"
)
parser_clear.set_defaults(func=clear)
# Build command
parser_build = subparsers.add_parser("build", help="Start the build process")
parser_build.set_defaults(func=build)
# Synth command
parser_build = subparsers.add_parser("synth", help="Start the synth process")
parser_build.set_defaults(func=synth)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()

26
src/models/config.py Normal file
View File

@@ -0,0 +1,26 @@
import os
from pydantic import BaseModel
class DirectoryConfig(BaseModel):
dependency: str = ".hdlbuild_deps"
build: str = ".working"
report: str = "reports"
copy_target: str = "output"
def get_relative_prefix(self) -> str:
"""
Gibt den relativen Pfad von build-Verzeichnis zurück zum Hauptverzeichnis.
Beispiel:
".working" -> "../"
".build/deep" -> "../../"
"""
depth = len(os.path.normpath(self.build).split(os.sep))
return "../" * depth
DIRECTORIES = DirectoryConfig()
class GitConfig(BaseModel):
timeout: int = 10
GIT = GitConfig()

47
src/models/project.py Normal file
View File

@@ -0,0 +1,47 @@
from pydantic import BaseModel, Field
from typing import List, Optional
class SourceFile(BaseModel):
path: str
library: str = "work" # Default auf 'work'
class ToolOptions(BaseModel):
common: List[str] = Field(default_factory=list)
xst: List[str] = Field(default_factory=list)
ngdbuild: List[str] = Field(default_factory=list)
map: List[str] = Field(default_factory=list)
par: List[str] = Field(default_factory=list)
bitgen: List[str] = Field(default_factory=list)
trace: List[str] = Field(default_factory=list)
fuse: List[str] = Field(default_factory=list)
class Dependency(BaseModel):
name: str
git: str
rev: str
library: str = "work" # Default auf 'work'
class Sources(BaseModel):
vhdl: List[SourceFile] = Field(default_factory=list)
verilog: List[SourceFile] = Field(default_factory=list)
class Testbenches(BaseModel):
vhdl: List[SourceFile] = Field(default_factory=list)
verilog: List[SourceFile] = Field(default_factory=list)
class BuildOptions(BaseModel):
build_dir: Optional[str] = "working"
report_dir: Optional[str] = "reports"
copy_target_dir: Optional[str] = "output"
class ProjectConfig(BaseModel):
name: str
topmodule: Optional[str]
target_device: str
xilinx_path: str
sources: Sources
testbenches: Optional[Testbenches] = None
constraints: Optional[str] = None
build: Optional[BuildOptions] = None
dependencies: Optional[List[Dependency]] = Field(default_factory=list)
tool_options: Optional[ToolOptions] = ToolOptions()

View File

@@ -0,0 +1,29 @@
import subprocess
import os
import shutil
from typing import Optional
from models.project import ProjectConfig
from models.config import DIRECTORIES
from tools.xilinx_ise.common import copy_file, run_tool
def run_bitgen(project: ProjectConfig):
run_tool(
project=project,
tool_executable_name="bitgen",
tool_option_attr="bitgen",
mandatory_arguments=[
"-w",
f"{project.name}.ncd",
f"{project.name}.bit"
], step_number=9, total_steps=12
)
def copy_bitstream_file(project: ProjectConfig):
copy_file(
project=project,
source_filename=f"{project.name}.bit",
destination_filename=f"{project.name}.Bitstream",
description="Bitstream File",
step_number=10, total_steps=12
)

View File

@@ -0,0 +1,72 @@
import shutil
import os
from typing import Optional, List
from models.project import ProjectConfig
from models.config import DIRECTORIES
from utils.console_utils import ConsoleTask, ConsoleUtils
from rich.console import Console
def run_tool(
project: ProjectConfig,
tool_executable_name: str,
mandatory_arguments: List[str],
tool_option_attr: Optional[str] = None,
working_dir: Optional[str] = None,
silent: bool = False,
step_number: Optional[int] = None,
total_steps: Optional[int] = None
):
if working_dir is None:
working_dir = DIRECTORIES.build
xilinx_bin_dir = os.path.join(project.xilinx_path, "bin", "lin64")
tool_executable = os.path.join(xilinx_bin_dir, tool_executable_name)
if not os.path.exists(tool_executable):
raise FileNotFoundError(f"Executable nicht gefunden: {tool_executable}")
cmd = [tool_executable]
if project.tool_options and project.tool_options.common:
cmd.extend(project.tool_options.common)
if tool_option_attr and project.tool_options:
tool_opts = getattr(project.tool_options, tool_option_attr, [])
if tool_opts:
cmd.extend(tool_opts)
cmd.extend(mandatory_arguments)
task = ConsoleTask("hdlbuild", tool_executable_name.upper(), step_number, total_steps)
task.run_command(cmd, cwd=working_dir, silent=silent)
def copy_file(
project: ProjectConfig,
source_filename: str,
destination_filename: str,
description: str = "Report",
step_number: Optional[int] = None,
total_steps: Optional[int] = None
):
"""
Kopiert eine beliebige Report-Datei vom Build- in das Report-Verzeichnis.
Args:
project (ProjectConfig): Geladene Projektkonfiguration
source_filename (str): Name der Quelldatei im Build-Ordner
destination_filename (str): Neuer Name der Zieldatei im Report-Ordner
description (str): Optionale Beschreibung für die Ausgabe (z.B. "Synthesis Report")
"""
src_path = os.path.join(DIRECTORIES.build, source_filename)
dst_path = os.path.join(DIRECTORIES.report, destination_filename)
if not os.path.exists(src_path):
raise FileNotFoundError(f"{description} nicht gefunden: {src_path}")
os.makedirs(DIRECTORIES.report, exist_ok=True)
shutil.copyfile(src_path, dst_path)
util = ConsoleUtils("hdlbuild", step_number, total_steps)
util.print(f"{description} kopiert nach {dst_path}")

View File

@@ -0,0 +1,34 @@
from models.config import DIRECTORIES
from models.project import ProjectConfig
from tools.xilinx_ise.bitgen import copy_bitstream_file, run_bitgen
from tools.xilinx_ise.map import copy_map_report, run_map
from tools.xilinx_ise.ngdbuild import run_ngdbuild
from tools.xilinx_ise.par import copy_par_report, copy_pinout_report, run_par
from tools.xilinx_ise.trace import copy_trace_report, run_trace
from tools.xilinx_ise.xst import copy_synthesis_report, generate_xst_project_file, generate_xst_script_file, run_xst
def xilinx_ise_synth(project: ProjectConfig):
generate_xst_project_file(project, f"{DIRECTORIES.build}/{project.name}.prj")
generate_xst_script_file(project, f"{DIRECTORIES.build}/{project.name}.scr")
run_xst(project)
copy_synthesis_report(project)
def xilinx_ise_all(project: ProjectConfig):
xilinx_ise_synth(project)
run_ngdbuild(project)
run_map(project)
copy_map_report(project)
run_par(project)
copy_par_report(project)
copy_pinout_report(project)
run_bitgen(project)
copy_bitstream_file(project)
run_trace(project)
copy_trace_report(project)

View File

@@ -0,0 +1,30 @@
import subprocess
import os
import shutil
from typing import Optional
from models.project import ProjectConfig
from models.config import DIRECTORIES
from tools.xilinx_ise.common import copy_file, run_tool
def run_map(project: ProjectConfig):
run_tool(
project=project,
tool_executable_name="map",
tool_option_attr="map",
mandatory_arguments=[
"-p", project.target_device,
"-w",
f"{project.name}.ngd",
"-o", f"{project.name}.map.ncd",
f"{project.name}.pcf"
], step_number=4, total_steps=12
)
def copy_map_report(project: ProjectConfig):
copy_file(
project=project,
source_filename=f"{project.name}.map.mrp",
destination_filename=f"{project.name}.MapReport",
description="Map Report",
step_number=5, total_steps=12
)

View File

@@ -0,0 +1,19 @@
import subprocess
import os
from typing import Optional
from models.project import ProjectConfig
from models.config import DIRECTORIES
from tools.xilinx_ise.common import run_tool
def run_ngdbuild(project: ProjectConfig):
run_tool(
project=project,
tool_executable_name="ngdbuild",
tool_option_attr="ngdbuild",
mandatory_arguments=[
"-p", project.target_device,
"-uc", f"{DIRECTORIES.get_relative_prefix()}{project.constraints}",
f"{project.name}.ngc",
f"{project.name}.ngd"
], step_number=3, total_steps=12
)

View File

@@ -0,0 +1,38 @@
import subprocess
import shutil
import os
from typing import Optional
from models.project import ProjectConfig
from models.config import DIRECTORIES
from tools.xilinx_ise.common import copy_file, run_tool
def run_par(project: ProjectConfig):
run_tool(
project=project,
tool_executable_name="par",
tool_option_attr="par",
mandatory_arguments=[
"-w",
f"{project.name}.map.ncd",
f"{project.name}.ncd",
f"{project.name}.pcf"
], step_number=6, total_steps=12
)
def copy_par_report(project: ProjectConfig):
copy_file(
project=project,
source_filename=f"{project.name}.par",
destination_filename=f"{project.name}.PlaceRouteReport",
description="Place & Route Report",
step_number=7, total_steps=12
)
def copy_pinout_report(project: ProjectConfig):
copy_file(
project=project,
source_filename=f"{project.name}_pad.txt",
destination_filename=f"{project.name}.PinoutReport",
description="Pinout Report",
step_number=8, total_steps=12
)

View File

@@ -0,0 +1,27 @@
import subprocess
import os
import shutil
from typing import Optional
from models.project import ProjectConfig
from models.config import DIRECTORIES
from tools.xilinx_ise.common import copy_file, run_tool
def run_trace(project: ProjectConfig):
run_tool(
project=project,
tool_executable_name="trce",
tool_option_attr="trace",
mandatory_arguments=[
f"{project.name}.ncd",
f"{project.name}.pcf",
], step_number=11, total_steps=12
)
def copy_trace_report(project: ProjectConfig):
copy_file(
project=project,
source_filename=f"{project.name}.twr",
destination_filename=f"{project.name}.TimingReport",
description="Timing Report",
step_number=12, total_steps=12
)

View File

@@ -0,0 +1,63 @@
from typing import Optional
from models.config import DIRECTORIES
from tools.xilinx_ise.common import copy_file, run_tool
from utils.source_resolver import expand_sources
from models.project import ProjectConfig
import subprocess
import os
import shutil
def generate_xst_project_file(project: ProjectConfig, output_path: str):
"""
Generiert die XST .prj-Datei mit allen Quellcodes.
"""
with open(output_path, "w") as f:
# VHDL-Sources
for lib, src in expand_sources(project.sources.vhdl):
f.write(f"vhdl {lib} \"{DIRECTORIES.get_relative_prefix()}/{src}\"\n")
# Verilog-Sources
for lib, src in expand_sources(project.sources.verilog):
f.write(f"verilog {lib} \"{DIRECTORIES.get_relative_prefix()}/{src}\"\n")
# Optionale Dependencies
if project.dependencies:
for dep in project.dependencies:
# Hier könnte man noch spezielle Sources aus dep.path expandieren
pass
def generate_xst_script_file(project: ProjectConfig, output_path: str):
"""
Generiert die XST .scr-Datei mit den Synthese-Optionen.
"""
with open(output_path, "w") as f:
f.write(f"run ")
f.write(f"-ifn {project.name}.prj ")
f.write(f"-ofn {project.name}.ngc ")
f.write(f"-ifmt mixed ")
if project.tool_options and project.tool_options.xst:
for opt in project.tool_options.xst:
f.write(f"{opt} ")
f.write(f"-top {project.topmodule} ")
f.write(f"-ofmt NGC ")
f.write(f"-p {project.target_device} ")
def run_xst(project: ProjectConfig):
run_tool(
project=project,
tool_executable_name="xst",
mandatory_arguments=["-ifn", f"{project.name}.scr",
], step_number=1, total_steps=12
)
def copy_synthesis_report(project: ProjectConfig):
copy_file(
project=project,
source_filename=f"{project.name}.srp",
destination_filename=f"{project.name}.SynthesisReport",
description="Synthesebericht",
step_number=2, total_steps=12
)

139
src/utils/console_utils.py Normal file
View File

@@ -0,0 +1,139 @@
import threading
import time
import subprocess
from typing import List, Optional
from rich.console import Console
from rich.live import Live
from rich.text import Text
class ConsoleTask:
def __init__(self, prefix:str, title: str, step_number: Optional[int] = None, total_steps: Optional[int] = None, max_log_lines: int = 10):
self.prefix = prefix
self.title = title
self.step_number = step_number
self.total_steps = total_steps
self.max_log_lines = max_log_lines
self.spinner_cycle = ['|', '/', '-', '\\']
self.stop_event = threading.Event()
self.spinner_thread: Optional[threading.Thread] = None
self.output_lines: List[str] = []
self._stdout_lock = threading.Lock()
self.console = Console()
self.live: Optional[Live] = None
self.spinner_idx = 0
def start_spinner(self):
self.live = Live(console=self.console, refresh_per_second=30, transient=True)
self.live.start()
self.spinner_thread = threading.Thread(target=self._spinner_task, daemon=True)
self.spinner_thread.start()
def _spinner_task(self):
while not self.stop_event.is_set():
with self._stdout_lock:
self._redraw_spinner()
self.spinner_idx += 1
time.sleep(0.1)
def _render_content(self) -> Text:
visible_lines = self.output_lines[-self.max_log_lines:]
prefix_text = f"[grey50]\[{self.prefix}][/grey50]" if self.prefix else ""
step_text = f"[bold blue]Step {self.step_number}/{self.total_steps}[/bold blue]" if self.step_number and self.total_steps else ""
title_text = f"[bold]{self.title}[/bold]" if self.title else ""
spinner_markup = f"{prefix_text} {step_text} {title_text} {self.spinner_cycle[self.spinner_idx % len(self.spinner_cycle)]}"
spinner_text = Text.from_markup(spinner_markup)
log_text = Text("\n".join(visible_lines))
full_text = spinner_text + Text("\n") + log_text
return full_text
def _redraw_spinner(self):
if self.live:
self.live.update(self._render_content())
def log(self, message: str):
with self._stdout_lock:
self.output_lines.append(message)
if len(self.output_lines) > self.max_log_lines:
self.output_lines = self.output_lines[-self.max_log_lines:]
if self.live:
self.live.update(self._render_content())
def run_command(self, cmd: List[str], cwd: Optional[str] = None, silent: bool = False) -> int:
success = False
start_time = time.time()
self.start_spinner()
try:
if silent:
subprocess.run(cmd, cwd=cwd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
success = True
else:
process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
if process.stdout is None:
raise ValueError("Failed to capture stdout")
while True:
line = process.stdout.readline()
if not line and process.poll() is not None:
break
if line:
self.log(line.rstrip())
success = (process.returncode == 0)
if not success:
raise subprocess.CalledProcessError(process.returncode, cmd)
except subprocess.CalledProcessError:
success = False
raise
finally:
self.stop_event.set()
if self.spinner_thread:
self.spinner_thread.join()
duration = time.time() - start_time
# Finalize output
with self._stdout_lock:
self._finalize_output(success, duration)
return 0 if success else 1
def _finalize_output(self, success: bool, duration: float):
if self.live:
self.live.stop()
prefix_text = f"[grey50]\[{self.prefix}][/grey50]" if self.prefix else ""
status_symbol = "[green]✅[/green]" if success else "[red]❌[/red]"
step_text = f"[bold blue]Step {self.step_number}/{self.total_steps}[/bold blue]" if self.step_number and self.total_steps else ""
status_title = f"[bold green]{self.title}[/bold green]" if success else f"[bold red]{self.title}[/bold red]"
final_line = f"{prefix_text} {step_text} {status_title} {status_symbol} [bold green]({duration:.1f}s[/bold green])"
# Final full output
self.console.print(final_line)
class ConsoleUtils:
def __init__(self,
prefix: str = "",
step_number: Optional[int] = None,
total_steps: Optional[int] = None
):
self.prefix = prefix
self.step_number = step_number
self.total_steps = total_steps
self.console = Console()
def print(self, message: str):
prefix = f"[grey50]\[{self.prefix}][/grey50]" if self.prefix else ""
step_info = f"[bold blue]Step {self.step_number}/{self.total_steps}[/bold blue]" if self.step_number and self.total_steps else ""
message_text = f"{prefix} {step_info} {message}"
self.console.print(message_text)

View File

@@ -0,0 +1,57 @@
import os
import shutil
from models.config import DIRECTORIES
from utils.console_utils import ConsoleUtils
def ensure_directories_exist(silent: bool = False):
"""
Erstellt alle in der Konfiguration definierten Verzeichnisse, falls sie nicht existieren.
"""
console_utils = None
if not silent:
console_utils = ConsoleUtils("hdlbuild")
for name, path in DIRECTORIES.dict().items():
if not os.path.exists(path):
os.makedirs(path, exist_ok=True)
if not silent and console_utils:
console_utils.print(f"Verzeichnis erstellt: {path}")
else:
if not silent and console_utils:
console_utils.print(f"[hdlbuild] Verzeichnis vorhanden: {path}")
def clear_directories(silent: bool = False):
"""
Löscht alle in der Konfiguration definierten Verzeichnisse, falls sie existieren.
"""
console_utils = None
if not silent:
console_utils = ConsoleUtils("hdlbuild")
for name, path in DIRECTORIES.dict().items():
if os.path.exists(path):
if not silent and console_utils:
console_utils.print(f"Lösche Verzeichnis: {path}")
shutil.rmtree(path)
else:
if not silent and console_utils:
console_utils.print(f"Verzeichnis nicht vorhanden, übersprungen: {path}")
def clear_build_directories(silent: bool = False):
"""
Löscht alle in der Konfiguration definierten Verzeichnisse, falls sie existieren.
"""
console_utils = None
if not silent:
console_utils = ConsoleUtils("hdlbuild")
for name, path in DIRECTORIES.dict().items():
if name == "dependency":
continue
if os.path.exists(path):
if not silent and console_utils:
console_utils.print(f"Lösche Verzeichnis: {path}")
shutil.rmtree(path)
else:
if not silent and console_utils:
console_utils.print(f"Verzeichnis nicht vorhanden, übersprungen: {path}")

View File

@@ -0,0 +1,16 @@
import yaml
from models.project import ProjectConfig
def load_project_config(path: str = "project.yml") -> ProjectConfig:
"""
Lädt die Projektkonfiguration aus einer YAML-Datei und gibt ein typisiertes ProjectConfig-Objekt zurück.
Args:
path (str): Pfad zur project.yml Datei (Default: "project.yml")
Returns:
ProjectConfig: Geparstes und typisiertes Projektkonfigurationsobjekt
"""
with open(path, "r") as file:
raw_data = yaml.safe_load(file)
return ProjectConfig(**raw_data)

View File

@@ -0,0 +1,19 @@
import glob
import os
from typing import List, Tuple
from models.project import SourceFile
def expand_sources(sources: List[SourceFile]) -> List[Tuple[str, str]]:
"""
Expandiert eine Liste von SourceFile-Objekten mit Wildcards in echte Pfade.
Returns:
List of (library, filepath)
"""
expanded = []
for source in sources:
matched_files = glob.glob(source.path, recursive=True)
for file in matched_files:
normalized_path = os.path.normpath(file)
expanded.append((source.library, normalized_path))
return expanded