Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
build/
dist/
*.egg-info/
.venv/
Empty file added cbi.log
Empty file.
3 changes: 2 additions & 1 deletion codebasin/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ def _main() -> None:
p = analysis_toml["platform"][name]["commands"]
db = config.load_database(p, rootdir)
args.platforms.append(name)
configuration.update({name: db})
configuration[name] = db


# Construct a codebase object associated with the root directory.
codebase = CodeBase(rootdir, exclude_patterns=args.excludes)
Expand Down
103 changes: 82 additions & 21 deletions codebasin/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,36 +258,105 @@ def _load_compilers() -> None:


@dataclass


class PreprocessorConfiguration:


"""


Represents the configuration for a specific file, including:


- Macro definitions


- Include paths


- Include files


- A meaningful pass name


"""





file: str


defines: list[str]


include_paths: list[str]


include_files: list[str]


pass_name: str = "default"





def _update(self, pass_or_mode: _CompilerPass | _CompilerMode) -> None:


"""


Update this PreprocessorConfiguration by extending the


defines, include paths and include files using the values


contained in the provided _CompilerPass or _CompilerMode.


Parameters


----------


pass_or_mode: _CompilerPass | _CompilerMode


The pass or mode to enable.


"""


self.defines.extend(pass_or_mode.defines)


self.include_paths.extend(pass_or_mode.include_paths)


self.include_files.extend(pass_or_mode.include_files)





@dataclass
class Configuration:
"""
Represents the configuration for a platform, including a list of
configurations for each file processed by the preprocessor.
"""

files: list[PreprocessorConfiguration]


class ArgumentParser:
"""
Represents the behavior of a specific compiler.
Expand Down Expand Up @@ -331,13 +400,14 @@ def __init__(self, path: str) -> None:
f"Compiler '{self.name}' recognized; aliases '{alias}'.",
)

def parse_args(self, argv: list[str]) -> list[PreprocessorConfiguration]:
def parse_args(self, filename: str, argv: list[str]) -> list[PreprocessorConfiguration]:
"""
Parameters
----------
filename: str
The name of the file being compiled.
argv: list[str]
The list of arguments passed to the compiler.

Returns
-------
list[PreprocessorConfiguration]
Expand Down Expand Up @@ -420,6 +490,7 @@ def parse_args(self, argv: list[str]) -> list[PreprocessorConfiguration]:
configurations = []
for pass_name in args.passes:
config = PreprocessorConfiguration(
filename,
args.defines.copy(),
args.include_paths.copy(),
args.include_files.copy(),
Expand Down Expand Up @@ -449,15 +520,15 @@ def parse_args(self, argv: list[str]) -> list[PreprocessorConfiguration]:
def load_database(
dbpath: str | os.PathLike[str],
rootdir: str | os.PathLike[str],
) -> list[dict[str, Any]]:
) -> Configuration:
"""
Load a compilation database.
Return a list of compilation commands, where each command is
represented as a compilation database entry.
"""
db = CompilationDatabase.from_file(dbpath)

configuration = []
files = []
for command in db:
# Skip commands that invoke unsupported tools.
if not command.is_supported():
Expand Down Expand Up @@ -490,36 +561,26 @@ def load_database(
# Parse command-line arguments, emulating compiler-specific behavior.
compiler_name = os.path.basename(command.arguments[0])
parser = ArgumentParser(compiler_name)
preprocessor_configs = parser.parse_args(command.arguments[1:])
preprocessor_configs = parser.parse_args(path, command.arguments[1:])

# Create a configuration entry for each compiler pass.
# Each compiler pass may set different defines, etc.
for preprocessor_config in preprocessor_configs:
entry = asdict(preprocessor_config)

entry["file"] = path

# Include paths may be specified relative to root
entry["include_paths"] = [
os.path.abspath(os.path.join(rootdir, f))
for f in entry["include_paths"]
]

configuration += [entry]
files.append(preprocessor_config)

# Print variables for debugging purposes.
if not log.isEnabledFor(logging.DEBUG):
continue
pass_name = entry["pass_name"]
pass_name = preprocessor_config.pass_name
for v in ["defines", "include_paths", "include_files"]:
if entry[v]:
value = " ".join(entry[v])
if getattr(preprocessor_config, v):
value = " ".join(getattr(preprocessor_config, v))
log.debug(f"{v} for {path} in pass '{pass_name}': {value}")

if len(configuration) == 0:
if len(files) == 0:
log.warning(
f"No files found in compilation database at '{dbpath}'.\n"
+ "Ensure that 'directory' and 'file' are in the root directory.",
)

return configuration
return Configuration(files)
35 changes: 16 additions & 19 deletions codebasin/finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from tqdm import tqdm

from codebasin import CodeBase, file_parser, preprocessor
from codebasin.config import Configuration, PreprocessorConfiguration
from codebasin.language import FileLanguage
from codebasin.preprocessor import CodeNode, Node, Platform, SourceTree, Visit

Expand Down Expand Up @@ -150,11 +151,10 @@ def associator(node: Node) -> Visit:
tree.visit(associator)


# FIXME: configuration should be refactored to avoid such a complex type.
def find(
rootdir: str,
codebase: CodeBase,
configuration: dict[Any, list[dict[str, Any]]],
configuration: dict[str, Configuration],
*,
summarize_only: bool = True,
show_progress: bool = False,
Expand Down Expand Up @@ -197,9 +197,9 @@ def _potential_file_generator(
):
if f in codebase:
filenames.add(f)
for p in configuration:
for e in configuration[p]:
filenames.add(e["file"])
for platform_name, platform_config in configuration.items():
for file_config in platform_config.files:
filenames.add(file_config.file)

# Build a tree for each unique file for all platforms.
state = ParserState(summarize_only)
Expand All @@ -214,42 +214,39 @@ def _potential_file_generator(
state.insert_file(str(f))

# Process each tree, by associating nodes with platforms
for p in tqdm(
configuration,
for platform_name, platform_config in tqdm(
configuration.items(),
desc="Preprocessing",
unit=" platform",
leave=False,
disable=not show_progress,
):
for e in tqdm(
configuration[p],
desc=p,
for file_config in tqdm(
platform_config.files,
desc=platform_name,
unit=" file",
leave=False,
disable=not show_progress,
):
file_platform = Platform(p, rootdir)
file_platform = Platform(platform_name, rootdir)

for path in e["include_paths"]:
for path in file_config.include_paths:
file_platform.add_include_path(path)

for definition in e["defines"]:
for definition in file_config.defines:
macro = preprocessor.macro_from_definition_string(definition)
file_platform.define(macro.name, macro)

# Process include files.
# These modify the file_platform instance, but we throw away
# the active nodes after processing is complete.
for include in e["include_files"]:
for include in file_config.include_files:
include_file = file_platform.find_include_file(
include,
os.path.dirname(e["file"]),
os.path.dirname(file_config.file),
)
if include_file:
state.insert_file(include_file)
state.associate(include_file, file_platform)

# Process the file, to build a list of associate nodes
state.associate(e["file"], file_platform)
state.associate(file_config.file, file_platform)

return state
4 changes: 2 additions & 2 deletions codebasin/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sys

from codebasin import CodeBase, __version__, config, finder, report, util

from codebasin.config import Configuration
# TODO: Refactor to avoid imports from __main__
from codebasin.__main__ import Formatter, _help_string

Expand Down Expand Up @@ -137,7 +137,7 @@ def _tree(args: argparse.Namespace) -> None:
if "commands" not in analysis_toml["platform"][name]:
raise ValueError(f"Missing 'commands' for platform {name}")
p = analysis_toml["platform"][name]["commands"]
db = config.load_database(p, rootdir)
db: Configuration = config.load_database(p, rootdir)
args.platforms.append(name)
configuration.update({name: db})

Expand Down
36 changes: 21 additions & 15 deletions tests/basic_asm/test_basic_asm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pathlib import Path

from codebasin import CodeBase, finder
from codebasin.config import Configuration, PreprocessorConfiguration


class TestBasicAsm(unittest.TestCase):
Expand All @@ -24,15 +25,17 @@ def test_yaml(self):
codebase = CodeBase(self.rootdir)
entries = []
for f in codebase:
entries.append(
{
"file": f,
"defines": [],
"include_paths": [],
"include_files": [],
},
preprocessor_config = PreprocessorConfiguration(
file=f,
defines=[],
include_paths=[],
include_files=[]
)
configuration = {"CPU": entries}
entries.append(preprocessor_config)

configuration_obj = Configuration(files=entries)

configuration = {"CPU": configuration_obj}
state = finder.find(self.rootdir, codebase, configuration)
setmap = state.get_setmap(codebase)
self.assertDictEqual(
Expand All @@ -44,13 +47,15 @@ def test_yaml(self):
def test_ptx(self):
"""basic_asm/basic_asm_ptx.yaml"""
codebase = CodeBase(self.rootdir)
entry = {
"file": str(self.rootdir / "test.ptx"),
"defines": [],
"include_paths": [],
"include_files": [],
}
configuration = {"GPU": [entry]}
preprocessor_config = PreprocessorConfiguration(
file=str(self.rootdir / "test.ptx"),
defines=[],
include_paths=[],
include_files=[],
)
configuration_obj = Configuration(files=[preprocessor_config])

configuration = {"GPU": configuration_obj}
self.assertRaises(
RuntimeError,
finder.find,
Expand All @@ -60,5 +65,6 @@ def test_ptx(self):
)



if __name__ == "__main__":
unittest.main()
Loading