From 6d0ff8e9325281d1acd55396281f5c32c082112a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20=C3=96rtenberg?= Date: Thu, 13 Mar 2025 15:14:27 +0100 Subject: [PATCH] Added project files to automate library imports --- scripts/build_env.py | 6 +-- scripts/elab.py | 11 +++++- scripts/gantry.py | 57 ++++++++++++++++++++++++---- scripts/project_man.py | 86 ++++++++++++++++++++++++++++-------------- 4 files changed, 119 insertions(+), 41 deletions(-) diff --git a/scripts/build_env.py b/scripts/build_env.py index b6fceff..e73d523 100644 --- a/scripts/build_env.py +++ b/scripts/build_env.py @@ -43,7 +43,7 @@ def addAllVHDLFiles(std: str, lib: str, init=False): return -1 vhdlFiles = [] currentlyAdded = [] - absWorkDir = os.path.join(os.getcwd(), "work") + absWorkDir = "work" cfFileId = getCfFileId(std) ## Find already present files if not init: @@ -53,10 +53,8 @@ def addAllVHDLFiles(std: str, lib: str, init=False): print(currentlyAdded) ## Add files not added for file in os.listdir(): - print(file) - print(currentlyAdded, file, file not in currentlyAdded) if ".vhd" in file and file not in currentlyAdded: - vhdlFiles.append(os.path.join(os.getcwd(), file)) + vhdlFiles.append(file) if len(vhdlFiles) > 0: print(f"Detected new files. Adding {vhdlFiles}") command = ["ghdl", "-i", "--workdir=work", f"--work={lib}", f"--std={std}"] + vhdlFiles diff --git a/scripts/elab.py b/scripts/elab.py index 1d5a040..793a89e 100644 --- a/scripts/elab.py +++ b/scripts/elab.py @@ -2,12 +2,19 @@ import os import subprocess import build_env from typing import List - +from project_man import getLibrariesPresent, getLibrariesInProject def generateIncludesForGHDL(includes: List[str]): cmd = [] + [exists, projectLibs] = getLibrariesInProject() + if not exists: + return [] + for lib in projectLibs.keys(): + includeString = f"{projectLibs[lib]['path']}/work" + cmd.append(f"-P{includeString}") for inc in includes: - cmd.append(f"-P{os.path.join(os.getcwd(), f"{inc}/work")}") + includeString = f"{inc}/work" + cmd.append(f"-P{includeString}") return cmd def elabDesign(topDef: str, arch: str, lib: str, std: str, includes: List[str]): diff --git a/scripts/gantry.py b/scripts/gantry.py index 55f64fb..24c4921 100644 --- a/scripts/gantry.py +++ b/scripts/gantry.py @@ -1,29 +1,61 @@ import typer +import os +from project_man import findProjectFile, initProjectFile, addLibraryInProject, projectFileExists, removeLibraryInProject, getLibrariesPresent import elab as elaborate import build_env from typing import List, Optional from typing_extensions import Annotated import subprocess -gantry_install_path = "/home/thesis2/exjobb-public/scripts" +gantry_install_path = "/home/thesis1/exjobb-public/scripts" app = typer.Typer() +project = typer.Typer() software = typer.Typer() hardware = typer.Typer() +app.add_typer(project, name="project", help="Project management to ease working with the tool") app.add_typer(software, name="sim", help="GHDL simulator command group") app.add_typer(hardware, name="syn", help="Synthesis and deployment command group") + def complete_vhdl_ver(): return ["87", "93", "93c", "00", "02", "08"] +@project.command(help="Creates a project file which allows for further project configuration") +def create( + name: Annotated[str, typer.Argument(help="Name of the project. Has no functional impact")] + ): + print(f"Creating a project at {os.getcwd()}/gantry.toml") + initProjectFile(name) + +@project.command(name="add-lib", help="Initializes a library in the project (non destructive)") +def addLib( + libname: Annotated[str, typer.Argument(help="Name of the library. This is what is used for imports in VHDL.")], + libpath: Annotated[str, typer.Argument(help="Relative path to the library. This tells simulators where to import form.")], + std: Annotated[str, typer.Option(help="Which VHDL standard to use. 87, 93, 93c, 00, 02 or 08", autocompletion=complete_vhdl_ver)] = "93c" + ): + print(f"Adding library {libname} to gantry.toml") + addLibraryInProject(libname, libpath, std) + +@project.command(name="remove-lib", help="Removes a library in the project (non destructive)") +def removeLib( + libname: Annotated[str, typer.Argument(help="Name of the library.")] + ): + print(f"Removing library {libname} from gantry.toml") + removeLibraryInProject(libname) + @software.command(help="Initializes the GHDL build environment in a library named \"work\". Adds all files ending in \".vhd\" to the project") def init( std: Annotated[str, typer.Option(help="Which VHDL standard to use. 87, 93, 93c, 00, 02 or 08", autocompletion=complete_vhdl_ver)] = "93c", - library: Annotated[str, typer.Option("--library", "-l", help="Library to compile from")] = "defaultLib", + library: Annotated[str, typer.Option("--library", "-l", help="Library to compile to. Defaults to \"work\"")] = "work", ): + (exists, _) = findProjectFile() + if exists: + addLibraryInProject(library, ".", std) + return build_env.createBuildEnv(std, library) @@ -31,10 +63,21 @@ def init( def elab( topdef: Annotated[str, typer.Argument(help="Top Definition entity to synthesize")] = "", arch: Annotated[str, typer.Argument(help="Architecture to synthesize within the top definition provided")] = "", - library: Annotated[str, typer.Option("--library", "-l", help="Library to compile from")] = "defaultLib", + library: Annotated[str, typer.Option("--library", "-l", help="Library to compile to")] = "", includes: Annotated[Optional[List[str]], typer.Option("--include", "-i", help="Which libraries to include in compile")] = None, std: Annotated[str, typer.Option(help="Which VHDL standard to use. 87, 93, 93c, 00, 02 or 08", autocompletion=complete_vhdl_ver)] = "93c" ): + if library == "": + [exists, presentLibs] = getLibrariesPresent() + if not exists: + print("No libs found.") + return + if len(presentLibs.keys()) == 1: + library = presentLibs.popitem()[0] + else: + libs = list(map(lambda x: "\"" + x[0] + "\"", presentLibs.items())) + print("More than one library present, please specify one by adding the flag -l with one of: " + " ".join(libs)) + return print(f"Elaborating {topdef} with arch {arch} in library {library}. VHDL {std}.") if includes is not None: print(f"Including libraries: {includes}") @@ -46,7 +89,7 @@ def elab( def run( topdef: Annotated[str, typer.Argument(help="Top Definition entity to synthesize")] = "", arch: Annotated[str, typer.Argument(help="Architecture to synthesize within the top definition provided")] = "", - library: Annotated[str, typer.Option("--library", "-l", help="Library to compile from")] = "defaultLib", + library: Annotated[str, typer.Option("--library", "-l", help="Library to compile to")] = "", includes: Annotated[Optional[List[str]], typer.Option("--include", "-i", help="Which libraries to include in compile")] = None, std: Annotated[str, typer.Option(help="Which VHDL standard to use. 87, 93, 93c, 00, 02 or 08", autocompletion=complete_vhdl_ver)] = "93c" ): @@ -60,14 +103,14 @@ def run( @hardware.command(help="Synthesizes the provided top level design using NXPython. Make sure you run this with NXPython.") def synth( topdef: Annotated[str, typer.Argument(help="Top Definition entity to synthesize")] = "", - library: Annotated[str, typer.Option("--library", "-l", help="Library to compile from, defaults to \"work\"")] = "defaultLib" + library: Annotated[str, typer.Option("--library", "-l", help="Library to compile todefaults to \"\"")] = "" ): proc = subprocess.run(["source_and_run.sh", f"{gantry_install_path}/nxp_script.py", "synthDesign", library, topdef]) @hardware.command(help="Places the provided top level design using NXPython. Make sure you run this with NXPython.") def place( topdef: Annotated[str, typer.Argument(help="Top Definition entity to synthesize")] = "", - library: Annotated[str, typer.Option("--library", "-l", help="Library to compile from, defaults to \"work\"")] = "defaultLib" + library: Annotated[str, typer.Option("--library", "-l", help="Library to compile to. If current directory contains libraries, it will be automatically detected")] = "" ): proc = subprocess.run(["source_and_run.sh", f"{gantry_install_path}/nxp_script.py", "placeDesign", library, topdef]) @@ -75,7 +118,7 @@ def place( @hardware.command(help="Routes the provided top level design using NXPython. Make sure you run this with NXPython.") def route( topdef: Annotated[str, typer.Argument(help="Top Definition entity to synthesize")] = "", - library: Annotated[str, typer.Option("--library", "-l", help="Library to compile from, defaults to \"work\"")] = "defaultLib" + library: Annotated[str, typer.Option("--library", "-l", help="Library to compile to. If current directory contains libraries, it will be automatically detected")] = "" ): proc = subprocess.run(["source_and_run.sh", f"{gantry_install_path}/nxp_script.py", "routeDesign", library, topdef]) diff --git a/scripts/project_man.py b/scripts/project_man.py index 425ad20..c1890e5 100644 --- a/scripts/project_man.py +++ b/scripts/project_man.py @@ -4,13 +4,19 @@ import toml import datetime -def findProjectRoot() -> tuple[bool, str]: +def findProjectRoot() -> "tuple[bool, str]": [exists, projectFile] = findProjectFile() if not exists: return (False, "") return (True, "/".join(projectFile.split("/")[0:-1])) -def findProjectFile() -> tuple[bool, str]: +def getRelativePathToRoot(path: str) -> str: + (exists, projectRoot) = findProjectRoot() + if not exists: + return "" + return os.path.relpath(path, projectRoot) + +def findProjectFile() -> "tuple[bool, str]": cwd = os.getcwd().split("/") for i in range(len(cwd) - 2): searchPath = "/".join(cwd[0:len(cwd)-i]) @@ -19,14 +25,14 @@ def findProjectFile() -> tuple[bool, str]: return (True, pathToProjectFile) return (False, "") -def projectFileExists(path: str) -> tuple[bool, str]: +def projectFileExists(path: str) -> "tuple[bool, str]": files = os.listdir(path) for file in files: if "gantry.toml" in file: return (True, os.path.join(path, file)) return (False,"") -def initProjectFile(projectName: str) -> tuple[bool, str]: +def initProjectFile(projectName: str) -> "tuple[bool, str]": cwd = os.getcwd() projectPath = os.path.join(cwd, "gantry.toml") [exists, existingProjectPath] = projectFileExists(cwd) @@ -42,7 +48,7 @@ def initProjectFile(projectName: str) -> tuple[bool, str]: return (True, projectPath) -def loadProjectFile() -> tuple[bool, str | dict[str, Any]] : +def loadProjectFile() -> "tuple[bool, str | dict[str, Any]]" : [exists, path] = findProjectFile() if not exists: return (False, "") @@ -54,7 +60,9 @@ def loadProjectFile() -> tuple[bool, str | dict[str, Any]] : except: return (False, "Reading Project file failed, permissions may be set wrong") -def writeProjectFile(projectDict: dict[str, Any]) -> tuple[bool, str]: + + +def writeProjectFile(projectDict: "dict[str, Any]") -> "tuple[bool, str]": [exists, path] = findProjectFile() if not exists: return (False, "") @@ -65,15 +73,21 @@ def writeProjectFile(projectDict: dict[str, Any]) -> tuple[bool, str]: except: return (False, "Reading Project file failed, permissions may be set wrong") -def removeLibraryInProject(lib: str) -> tuple[bool, str]: +def getProjectDict() -> "tuple[bool, dict[str, Any]]": [exists, output] = loadProjectFile() if not exists: - return (False, "Project doesn't exist.") - projectDict = {} + print(output) + return (False, {}) if isinstance(output, dict): - projectDict = output + return (True, output) else: - return (False, "Output wasn't a dictionary") + print(output) + return (False, {}) + +def removeLibraryInProject(lib: str) -> "tuple[bool, str]": + [exists, projectDict] = getProjectDict() + if not exists: + return (False, "Found no project dictionary") if "libraries" not in projectDict.keys(): return (False, "No libraries are declared in this project.") if lib in projectDict["libraries"].keys(): @@ -83,25 +97,50 @@ def removeLibraryInProject(lib: str) -> tuple[bool, str]: return (False, "Library with this name is not declared") -def addLibraryInProject(lib: str, std: str) -> tuple[bool, str]: - [exists, output] = loadProjectFile() +def addLibraryInProject(lib: str, relPath: str, std: str) -> "tuple[bool, str]": + (exists, projectDict) = getProjectDict() if not exists: return (False, "Project doesn't exist.") - projectDict = {} - if isinstance(output, dict): - projectDict = output - else: - return (False, "Output wasn't a dictionary") if "libraries" not in projectDict.keys(): projectDict["libraries"] = {} if lib not in projectDict["libraries"].keys(): + libDir = getRelativePathToRoot(os.path.join(os.getcwd(), relPath)) projectDict["libraries"][lib] = {} projectDict["libraries"][lib]["vhdl-version"] = std - projectDict["libraries"][lib]["path"] = os.path.join(os.getcwd(), lib) + projectDict["libraries"][lib]["path"] = libDir + os.makedirs(name=relPath,exist_ok=True) + print(libDir) [wentWell, _] = writeProjectFile(projectDict) return (wentWell, "") return (False, "Library with this name is already declared") + +def getLibrariesInProject() -> "tuple[bool, dict[str, Any]]": + (exists, projectDict) = getProjectDict() + if not exists: + return (False, {}) + libs = {} + if "libraries" not in projectDict.keys(): + ## Successful read, no libs found -> empty return + return (True, {}) + (_, projectRoot) = findProjectRoot() + for lib in projectDict["libraries"].keys(): + libs[lib] = projectDict["libraries"][lib] + absPath = os.path.join(projectRoot, libs[lib]["path"]) + libs[lib]["path"] = os.path.relpath(absPath, os.getcwd()) + return (True, libs) + +def getLibrariesPresent() -> "tuple[bool, dict[str, Any]]": + cwd = os.getcwd() + (exists, libs) = getLibrariesInProject() + if not exists: + return (False, {}) + libsInCwd = {} + for lib in libs.keys(): + if os.path.samefile(libs[lib]["path"], cwd): + libsInCwd[lib] = libs[lib] + return (True, libsInCwd) + def createProjectFileTemplate(projectName: str) -> str: return f""" title = "{projectName}" @@ -111,12 +150,3 @@ email = "" version = "0.0.1" """ - - -if __name__ == "__main__": - print(initProjectFile("test")) - print(loadProjectFile()) - print(findProjectFile()) - print(findProjectRoot()) - print(addLibraryInProject("ganimede", "93")) - print(removeLibraryInProject("ganimede"))