diff --git a/scripts/build_env.py b/scripts/build_env.py index e73d523..de63805 100644 --- a/scripts/build_env.py +++ b/scripts/build_env.py @@ -1,6 +1,9 @@ import os from re import split import subprocess +from typing import List + +from project_man import addLibraryInProject, removeLibraryInProject def getCfFileId(std: str): return "08" if std == "08" else "93" ## Weird behaviour from GHDL, but all vhdl versions besides 08 have [...]93.cf @@ -26,45 +29,78 @@ def ghdlEnvExists(std, lib): ## Nothing bad, continue return True -def createBuildEnv(std: str, lib: str): +def addVHDLFiles(fileNames: List[str], std: str, lib: str): + os.makedirs("work", exist_ok=True) + vhdlFiles = [] if ghdlEnvExists(std=std, lib=lib): - print("Build environment already exists, exiting...") - return -1 - ## Create build env - print("Initializing GHDL project in current directory...") - os.makedirs("work",exist_ok=True) - addAllVHDLFiles(std=std, lib=lib, init=True) + cfFileId = getCfFileId(std) + cfFileName = list(filter(lambda x: ".cf" in x and lib in x and cfFileId in x, os.listdir("work")))[0] + cfFilePath = os.path.join("work",cfFileName) + currentlyAdded = getCurrentlyAddedFiles(cfFilePath) + for fileName in fileNames: + if fileName not in currentlyAdded: + vhdlFiles.append(fileName) + else: + addLibraryInProject(lib, ".", std) + vhdlFiles = fileNames + vhdlFiles = list(filter(lambda x: ".vhd" in x, vhdlFiles)) + if len(vhdlFiles) == 0: + print("no files to add.") + return 0 + print(f"adding {vhdlFiles} to library {lib}") + command = ["ghdl", "-i", "--workdir=work", f"--work={lib}", f"--std={std}"] + vhdlFiles + subprocess.run(command) return 0 -def addAllVHDLFiles(std: str, lib: str, init=False): - ## Ensure everything is ready for adding files - ## (init exception to avoid one if-case in ghdlEnvExists) - if not ghdlEnvExists(std=std, lib=lib) and not init: +def removeVHDLFiles(fileNames: List[str], std: str, lib: str): + if not ghdlEnvExists(std=std, lib=lib): return -1 - vhdlFiles = [] - currentlyAdded = [] - absWorkDir = "work" cfFileId = getCfFileId(std) - ## Find already present files - if not init: - cfFileName = list(filter(lambda x: ".cf" in x and lib in x and cfFileId in x, os.listdir(absWorkDir)))[0] - cfFilePath = os.path.join(absWorkDir,cfFileName) - currentlyAdded = getCurrentlyAddedFiles(cfFilePath) - print(currentlyAdded) - ## Add files not added - for file in os.listdir(): - if ".vhd" in file and file not in currentlyAdded: - 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 - subprocess.run(command) - return 0 + cfFileName = list(filter(lambda x: ".cf" in x and lib in x and cfFileId in x, os.listdir("work")))[0] + cfFilePath = os.path.join("work",cfFileName) + currentlyAdded = getCurrentlyAddedFiles(cfFilePath) + + for fileName in fileNames: + if fileName not in currentlyAdded: + print(f"file {fileName} is not present in {cfFileName}.") + return 0 + currentlyAdded.remove(fileName) + removeCurrentlyAddedFile(fileName, cfFilePath) + if len(currentlyAdded) == 0: + print("Project is empty, removing GHDL project file and library reference in project") + removeLibraryInProject(lib) + os.remove(cfFilePath) + def getCurrentlyAddedFiles(cfFilePath:str): f = open(cfFilePath,"r") lines = f.readlines() f.close() fileLines = filter(lambda x: "file" in x, lines) - files = map(lambda x: split("\"",x)[1], fileLines) + files = map(lambda x: split("\" \"",x)[1], fileLines) return list(files) + +def removeCurrentlyAddedFile(fileName: str, cfFilePath: str): + f = open(cfFilePath,"r") + lines = f.readlines() + f.close() + try: + mappedLines = list(map(lambda x: fileName in x, lines)) + index = mappedLines.index(True) + endIndex = len(lines)-1 + for x in range(index+1,len(lines)): + if "file" in lines[x]: + endIndex = x + break + newLines = [] + for x in range(len(lines)): + if x < index or x >= endIndex: + newLines.append(lines[x]) + f = open(cfFilePath, "w") + f.writelines(newLines) + f.close() + except: + print(f"Something went wrong when trying to remove {fileName} from {cfFilePath}. Restoring to original configuration") + f = open(cfFilePath, "w") + f.writelines(lines) + f.close() diff --git a/scripts/elab.py b/scripts/elab.py index 793a89e..d5be319 100644 --- a/scripts/elab.py +++ b/scripts/elab.py @@ -18,20 +18,16 @@ def generateIncludesForGHDL(includes: List[str]): return cmd def elabDesign(topDef: str, arch: str, lib: str, std: str, includes: List[str]): - ## Add all source files present in pwd - if build_env.addAllVHDLFiles(std, lib) == -1: - print("Adding files failed. GHDL Build environment may be broken...") - return -1 + if not build_env.ghdlEnvExists(std, lib): + print("No GHDL environment present. Add all needed files before elaborating") incs = generateIncludesForGHDL(includes) command = [ "ghdl", "-m", "--workdir=work", f"--work={lib}", f"--std={std}"] + incs + ["-o", f"work/{topDef}-{arch}", f"work.{topDef}", f"{arch}"] subprocess.run(command) def runDesign(topDef: str, arch: str, lib: str, std: str, includes): - ## elaborate first, then run - if elabDesign(topDef, arch, lib, std, includes) == -1: - print("Elaboration failed...") - return -1 + if not build_env.ghdlEnvExists(std, lib): + print("No GHDL environment present. Add all needed files before elaborating") os.makedirs("wave",exist_ok=True) wavePath = os.path.join(os.getcwd(), "wave") incs = generateIncludesForGHDL(includes) diff --git a/scripts/gantry.py b/scripts/gantry.py index 24c4921..13362aa 100644 --- a/scripts/gantry.py +++ b/scripts/gantry.py @@ -1,6 +1,6 @@ import typer import os -from project_man import findProjectFile, initProjectFile, addLibraryInProject, projectFileExists, removeLibraryInProject, getLibrariesPresent +from project_man import findProjectFile, getLibraryInProject, initProjectFile, addLibraryInProject, removeLibraryInProject, getLibrariesPresent import elab as elaborate import build_env from typing import List, Optional @@ -19,6 +19,40 @@ app.add_typer(project, name="project", help="Project management to ease working app.add_typer(software, name="sim", help="GHDL simulator command group") app.add_typer(hardware, name="syn", help="Synthesis and deployment command group") +def autoDetectLibrary(lib: str) -> str: + if lib == "": + [exists, presentLibs] = getLibrariesPresent() + if not exists: + print("No libs found.") + return "work" + if len(presentLibs.keys()) == 1: + return presentLibs.popitem()[0] + else: + libs = list(map(lambda x: x[0], presentLibs.items())) + libsText = list(map(lambda i: f"{i}" + ": \"" + libs[i] + "\"", [x for x in range(len(libs))])) + selectionIndex = 0 + while True: + try: + selectionIndex = int(input("More than one library present, please choose: \n" + "\n".join(libsText) + "\n")) + if selectionIndex < 0 or selectionIndex >=len(libs): + raise ValueError + else: + break + except ValueError: + print("Invalid input!"), + return libs[selectionIndex] + else: + return lib + +def autoDetectStd(library: str, std: str) -> str: + if std == "" or std not in complete_vhdl_ver(): + (exists, libDict) = getLibraryInProject(library) + if not exists: + return "93" + else: + return libDict["vhdl-version"] + else: + return std def complete_vhdl_ver(): return ["87", "93", "93c", "00", "02", "08"] @@ -35,7 +69,7 @@ def create( 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" + std: Annotated[str, typer.Option(help="Which VHDL standard to use. 87, 93, 93c, 00, 02 or 08", autocompletion=complete_vhdl_ver)] = "93" ): print(f"Adding library {libname} to gantry.toml") addLibraryInProject(libname, libpath, std) @@ -47,16 +81,28 @@ def removeLib( 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 to. Defaults to \"work\"")] = "work", +@software.command(help="Adds files to a library. Automatically updates gantry project file and creates GHDL project files") +def add( + filenames: Annotated[List[str], typer.Argument(help="Which files to add to the library. May be more than one.")], + std: Annotated[str, typer.Option(help="Which VHDL standard to use. 87, 93, 93c, 00, 02 or 08", autocompletion=complete_vhdl_ver)] = "93", + library: Annotated[str, typer.Option("--library", "-l", help="Library to compile to.")] = "" ): + library = autoDetectLibrary(library) + std = autoDetectStd(library, std) (exists, _) = findProjectFile() if exists: addLibraryInProject(library, ".", std) - - return build_env.createBuildEnv(std, library) + return build_env.addVHDLFiles(filenames, std, library) + +@software.command(help="Removes files from a library. Automatically updates gantry project file and creates GHDL project files") +def remove( + filenames: Annotated[List[str], typer.Argument(help="Which files to add to the library. May be more than one.")], + std: Annotated[str, typer.Option(help="Which VHDL standard to use. 87, 93, 93c, 00, 02 or 08", autocompletion=complete_vhdl_ver)] = "93", + library: Annotated[str, typer.Option("--library", "-l", help="Library to compile to.")] = "" + ): + library = autoDetectLibrary(library) + std = autoDetectStd(library, std) + return build_env.removeVHDLFiles(filenames, std, library) @software.command(help="Runs analysis and elaboration on the provided top definition and architecture using GHDL. Automatically adds new files not present in the project") @@ -65,19 +111,10 @@ def elab( 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 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" + std: Annotated[str, typer.Option(help="Which VHDL standard to use. 87, 93, 93c, 00, 02 or 08", autocompletion=complete_vhdl_ver)] = "93" ): - 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 + library = autoDetectLibrary(library) + std = autoDetectStd(library, std) print(f"Elaborating {topdef} with arch {arch} in library {library}. VHDL {std}.") if includes is not None: print(f"Including libraries: {includes}") @@ -91,8 +128,10 @@ def run( 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 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" + std: Annotated[str, typer.Option(help="Which VHDL standard to use. 87, 93, 93c, 00, 02 or 08", autocompletion=complete_vhdl_ver)] = "93" ): + library = autoDetectLibrary(library) + std = autoDetectStd(library, std) print(f"Running (and synthesizing if needed) {topdef} with arch {arch} in library {library}. VHDL {std}") if includes is not None: print(f"Including libraries: {includes}") diff --git a/scripts/project_man.py b/scripts/project_man.py index c1890e5..55f062d 100644 --- a/scripts/project_man.py +++ b/scripts/project_man.py @@ -60,8 +60,6 @@ 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]": [exists, path] = findProjectFile() if not exists: @@ -114,7 +112,18 @@ def addLibraryInProject(lib: str, relPath: str, std: str) -> "tuple[bool, str]": return (wentWell, "") return (False, "Library with this name is already declared") - +def getLibraryInProject(lib: str) -> "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 (False, {}) + (_, projectRoot) = findProjectRoot() + if lib in projectDict["libraries"].keys(): + return (True, projectDict["libraries"][lib]) + return (False, {}) def getLibrariesInProject() -> "tuple[bool, dict[str, Any]]": (exists, projectDict) = getProjectDict() if not exists: