-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor to allow Buildsystem integration
Parsing, creation of bridge and building of C sources is moved out of TestSetup into BuildDescription.
- Loading branch information
Showing
20 changed files
with
892 additions
and
901 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import abc | ||
import hashlib | ||
import os | ||
from pathlib import Path | ||
from typing import List, Dict | ||
|
||
|
||
class BuildError(Exception): | ||
|
||
def __init__(self, msg, path=None): | ||
super().__init__(msg) | ||
self.path = path | ||
|
||
def __str__(self): | ||
return (f'building {self.path} failed: ' if self.path | ||
else '') \ | ||
+ super().__str__() | ||
|
||
|
||
class BuildDescription: | ||
""" | ||
This is an abstract base class for all classes that allow to specify | ||
C projects | ||
""" | ||
|
||
def __init__(self, name:str, build_dir:Path, unique_name=True): | ||
""" | ||
Abstract Base Class for Descriptions how to build a bunch of C files | ||
:param name: Descriptive Name of BuildDescription | ||
:param build_dir: Directory where all generated files shall be stored | ||
:param unique_name: If True, a BuildDescription with the same name | ||
cannot exist | ||
""" | ||
self.name = name | ||
self.__build_dir = build_dir | ||
self.unique_name = unique_name | ||
|
||
@property | ||
def build_dir(self): | ||
if self.unique_name: | ||
return self.__build_dir | ||
else: | ||
hash = hashlib.md5() | ||
incl_dirs = self.incl_dirs() | ||
predef_macros = self.predef_macros() | ||
for c_source in self.c_sources(): | ||
hash.update(os.fsencode(c_source)) | ||
for incl_dir in incl_dirs[c_source]: | ||
hash.update(os.fsencode(incl_dir)) | ||
for mname, mvalue in predef_macros[c_source].items(): | ||
hash.update((mname + '=' + mvalue).encode('ascii')) | ||
return self.__build_dir.parent \ | ||
/ (self.__build_dir.name + '_' + hash.hexdigest()[:8]) | ||
|
||
@abc.abstractmethod | ||
def clang_target(self) -> str: | ||
""" | ||
returns a string that represents the "target" parameter of clang | ||
""" | ||
raise NotImplementedError() | ||
|
||
@abc.abstractmethod | ||
def sys_incl_dirs(self) -> List[Path]: | ||
""" | ||
retrieves a list of all system include directories | ||
""" | ||
|
||
def sys_predef_macros(self) -> Dict[str, str]: | ||
""" | ||
A dictionary of toolchain-inhernt macros, that shall be always | ||
predefined additionally to the predefined macros provided by clang | ||
""" | ||
return {} | ||
|
||
def c_sources(self) -> List[Path]: | ||
""" | ||
returns all C source files | ||
""" | ||
|
||
@abc.abstractmethod | ||
def incl_dirs(self) -> Dict[Path, List[Path]]: | ||
""" | ||
returns a list of all source code files. | ||
:return: | ||
""" | ||
|
||
@abc.abstractmethod | ||
def predef_macros(self) -> Dict[Path, Dict[str, str]]: | ||
""" | ||
returns all predefined macros per source file a list of all source code files. | ||
:return: | ||
""" | ||
|
||
@abc.abstractmethod | ||
def exe_path(self) -> Path: | ||
""" | ||
returns name of executable image/shared object library/dll | ||
""" | ||
|
||
@abc.abstractmethod | ||
def build(self, additonal_c_sources:List[Path]=None): | ||
""" | ||
builds executable image | ||
""" | ||
|
||
def is_header_file(self, src_path:Path) -> bool: | ||
""" | ||
Returns True, if this is a header file | ||
""" | ||
return src_path.suffix.lower() == '.h' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import subprocess | ||
import os | ||
from pathlib import Path | ||
|
||
from typing import Dict, List, Any | ||
from . import BuildDescription, BuildError | ||
|
||
|
||
BUILD_CACHE = set() | ||
|
||
|
||
class GccBuildDescription(BuildDescription): | ||
|
||
SYS_INCL_DIR_CACHE:Dict[Path, List[Path]] = {} | ||
|
||
ADDITIONAL_COMPILE_OPTIONS = [] | ||
ADDITIONAL_LINK_OPTIONS = [] | ||
|
||
def __init__(self, name:str, build_dir:Path, unique_name=True, *, | ||
c_sources:List[Path]=None, predef_macros:Dict[str, Any]=None, | ||
incl_dirs:List[Path]=None, lib_dirs:List[Path]=None, | ||
req_libs:List[str]=None, gcc_executable='gcc'): | ||
super().__init__(name, build_dir, unique_name) | ||
self.gcc_executable = gcc_executable | ||
self.__c_sources = c_sources or [] | ||
self.__incl_dirs = incl_dirs or [] | ||
self.__req_libs = req_libs or [] | ||
self.__lib_dirs = lib_dirs or [] | ||
self.__predef_macros = {} | ||
self.add_predef_macros(predef_macros or {}) | ||
|
||
def add_c_source(self, c_filename:Path): | ||
if c_filename in self.__c_sources: | ||
raise ValueError(f'Translation Unit {c_filename!r} is already part ' | ||
f'of BuildDescription') | ||
self.__c_sources.append(c_filename) | ||
|
||
def add_predef_macros(self, predef_macros:Dict[str, Any]): | ||
for name, val in predef_macros.items(): | ||
self.__predef_macros[name] = str(val) if val is not None else None | ||
|
||
def add_incl_dir(self, incl_dir:Path): | ||
self.__incl_dirs.append(incl_dir) | ||
|
||
def add_lib_dir(self, lib_dir:Path): | ||
self.__lib_dirs.append(lib_dir) | ||
|
||
def add_req_lib(self, lib_name:str): | ||
self.__req_libs.append(lib_name) | ||
|
||
def sys_incl_dirs(self): | ||
if self.gcc_executable not in self.SYS_INCL_DIR_CACHE: | ||
try: | ||
gcc_info = subprocess.check_output( | ||
[self.gcc_executable, | ||
'-v', '-xc', '-c', '/dev/null', '-o', '/dev/null'], | ||
stderr=subprocess.STDOUT, | ||
encoding='utf8') | ||
except (subprocess.CalledProcessError, FileNotFoundError) as e: | ||
raise BuildError('failed to retrieve SYS include path from gcc') | ||
else: | ||
incl_dirs = self.SYS_INCL_DIR_CACHE[self.gcc_executable] = [] | ||
print("gcc_info") | ||
collecting = False | ||
for line in gcc_info.splitlines(): | ||
if line.startswith('#include <...> search starts here'): | ||
collecting = True | ||
elif line.startswith('End of search list.'): | ||
collecting = False | ||
elif collecting: | ||
incl_dirs.append(Path(line.strip())) | ||
return self.SYS_INCL_DIR_CACHE[self.gcc_executable] | ||
|
||
def c_sources(self): | ||
return self.__c_sources | ||
|
||
def predef_macros(self): | ||
return dict.fromkeys(self.__c_sources, self.__predef_macros) | ||
|
||
def incl_dirs(self): | ||
return dict.fromkeys(self.__c_sources, self.__incl_dirs) | ||
|
||
def _run_gcc(self, call_params, dest_file): | ||
try: | ||
completed_proc = subprocess.run( | ||
[self.gcc_executable] + call_params, | ||
encoding='utf8', | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE) | ||
except (subprocess.CalledProcessError, FileNotFoundError) as e: | ||
raise BuildError(f'failed to call gcc: {e}', dest_file) | ||
else: | ||
if completed_proc.returncode != 0: | ||
raise BuildError(completed_proc.stderr, dest_file) | ||
|
||
def exe_path(self): | ||
return self.build_dir / '__headlock__.dll' | ||
|
||
def build(self, additonal_c_sources=None): | ||
transunits = (tuple(self.__c_sources), | ||
tuple(self.__predef_macros.items()), | ||
tuple(self.__incl_dirs), | ||
tuple(self.__req_libs), | ||
tuple(self.__lib_dirs)) | ||
if (tuple(transunits), self.build_dir) in BUILD_CACHE: | ||
return | ||
total_sources = self.__c_sources + (additonal_c_sources or []) | ||
for c_src in total_sources: | ||
if self.is_header_file(c_src): | ||
continue | ||
obj_file_path = self.build_dir / (c_src.stem + '.o') | ||
self._run_gcc(['-c', os.fspath(c_src)] | ||
+ ['-o', os.fspath(obj_file_path)] | ||
+ ['-I' + os.fspath(incl_dir) | ||
for incl_dir in self.__incl_dirs] | ||
+ [f'-D{mname}={mval or ""}' | ||
for mname, mval in self.__predef_macros.items()] | ||
+ ['-Werror'] | ||
+ self.ADDITIONAL_COMPILE_OPTIONS, | ||
obj_file_path) | ||
exe_file_path = self.exe_path() | ||
self._run_gcc([str(self.build_dir / (c_src.stem + '.o')) | ||
for c_src in total_sources | ||
if not self.is_header_file(c_src)] | ||
+ ['-shared', '-o', os.fspath(exe_file_path)] | ||
+ ['-l' + str(req_lib) for req_lib in self.__req_libs] | ||
+ ['-L' + str(lib_dir) for lib_dir in self.__lib_dirs] | ||
+ ['-Werror'] | ||
+ self.ADDITIONAL_LINK_OPTIONS, | ||
exe_file_path) | ||
BUILD_CACHE.add((tuple(transunits), self.build_dir)) | ||
|
||
|
||
class Gcc32BuildDescription(GccBuildDescription): | ||
def clang_target(self): | ||
return 'i386-pc-linux-gnu' | ||
|
||
|
||
class Gcc64BuildDescription(GccBuildDescription): | ||
ADDITIONAL_COMPILE_OPTIONS = ['-fPIC'] | ||
def clang_target(self): | ||
return 'x86_64-pc-linux-gnu' |
Oops, something went wrong.