Skip to content
Merged
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
32 changes: 32 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Repository Guidelines

## Project Structure & Module Organization
- Core Python modules live at the repo root (e.g., `parser.py`, `ninjabuild.py`, `build.py`, `visitor.py`).
- Shared helpers are in `helpers/` and `helpers.py`.
- Tests live in `test/`, with fixtures under `test/data/`.
- `README.md` documents usage and example workflows.

## Build, Test, and Development Commands
- `python parser.py -p "." path/to/build.ninja path/to/src` runs the main CLI to translate a Ninja build; see `README.md` for full examples and flags.
- `python -m unittest discover -s test` runs the unit test suite using the standard library.
- `pytest` runs the same tests if you prefer pytest (needed for `test/test_integration_build_files.py`).

## Coding Style & Naming Conventions
- Use 4-space indentation and PEP 8 style.
- Follow the line-length limit of 110 characters (see `tox.ini`).
- Prefer `snake_case` for functions/variables and `CamelCase` for classes.
- Test files follow `test_*.py` naming and use `unittest.TestCase`.

## Testing Guidelines
- Add tests for new parsing behaviors and edge cases in `test/`.
- Keep tests deterministic and local; avoid network or system-specific dependencies.
- When adding fixtures, place them under `test/data/` with descriptive names.

## Commit & Pull Request Guidelines
- Commit messages in this repo use short, imperative sentences without prefixes (e.g., "Add documentation on generated stuff").
- Keep commits focused on one change set.
- PRs should include a clear summary, testing notes (commands run), and links to relevant issues when applicable.

## Security & Configuration Tips
- The tool reads local build files and source trees; avoid committing or referencing sensitive paths in fixtures or examples.
- Prefer relative paths in docs and tests so examples work across machines.
67 changes: 59 additions & 8 deletions bazel.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@
from copy import deepcopy
from functools import cache, cmp_to_key, total_ordering
from itertools import combinations
from typing import (Any, Callable, Dict, List, Optional, Set, Type, TypeVar,
Union)
from typing import (
Any,
Callable,
Dict,
Iterable,
List,
Optional,
Set,
Tuple,
Type,
TypeVar,
Union,
)

PREGENERATED_LOCATION = "<pregenerated>"

Expand All @@ -14,6 +25,29 @@
T = TypeVar("T")

CompilationFlags = Dict[str, Union[str, Set[str]]]
BASIC_C_STD_RE = re.compile(r"^-std=(?:c|gnu)[0-9A-Za-z]+$")
BASIC_CXX_STD_RE = re.compile(r"^-std=(?:c\+\+|gnu\+\+)[0-9A-Za-z]+$")


def _normalize_flag(flag: str) -> str:
return flag.strip('"')


def _split_language_opts(
opts: Iterable[str],
) -> Tuple[Set[str], Set[str], Set[str]]:
conlyopts: Set[str] = set()
cxxopts: Set[str] = set()
copts: Set[str] = set()
for opt in opts:
normalized = _normalize_flag(opt)
if BASIC_C_STD_RE.match(normalized):
conlyopts.add(opt)
elif BASIC_CXX_STD_RE.match(normalized) or normalized.startswith("-stdlib="):
cxxopts.add(opt)
else:
copts.add(opt)
return conlyopts, cxxopts, copts


def _getPrefix(
Expand Down Expand Up @@ -416,19 +450,23 @@ def genAdditionalDeps(self):
if not (f.name.endswith(".c") or f.name.endswith(".C"))
]
if len(c_sources) and len(nonc_sources):
conlyopts, cxxopts, copts = _split_language_opts(t.copts)
t.copts = copts
t.conlyopts.update(conlyopts)
t.cxxopts.update(cxxopts)
sublib = BazelTarget(t.type, f"_{t.name}_c", t.location)
sublib.includeDirs = deepcopy(t.includeDirs)
sublib.srcs = c_sources
sublib.addPrefixIfRequired = t.addPrefixIfRequired
sublib.copts = [
o for o in t.copts if not re.match(r'^"-std=(?:c|gnu)\+\+', o)
]
sublib.copts = set(t.copts)
sublib.conlyopts = set(t.conlyopts)
sublib.cxxopts = set()
sublib.defines = t.defines
sublib.deps = t.deps
sublib.hdrs = t.hdrs
self.bazelTargets.add(sublib)

t.copts = [o for o in t.copts if not re.match(r'^"-std=(?:c|gnu)\d', o)]
t.conlyopts = set()
t.srcs = nonc_sources
t.hdrs = set()
t.deps = set()
Expand Down Expand Up @@ -510,7 +548,7 @@ def genBazelBuildContent(self) -> Dict[str, str]:
# Add some scaffolding for common options that could be easily tweaked
vals = []
flags_n_opts = self.commonFlags.get(k, {})
for c in ["copts", "defines", "linkopts"]:
for c in ["copts", "conlyopts", "cxxopts", "defines", "linkopts"]:
flags = flags_n_opts.get(c, set())
if isinstance(flags, str):
vals.append(f"common_{c} = {flags}\n")
Expand Down Expand Up @@ -629,11 +667,16 @@ def __init__(self, type: str, name: str, location: str):
self.includeDirs: set[IncludeDir] = set()
self.addPrefixIfRequired: bool = True
self.copts: set[str] = set()
self.conlyopts: set[str] = set()
self.cxxopts: set[str] = set()
self.defines: set[str] = set()
self.data: set[BaseBazelTarget] = set()

def addCopt(self, opt: str):
self.copts.add(opt)
conlyopts, cxxopts, copts = _split_language_opts([opt])
self.conlyopts.update(conlyopts)
self.cxxopts.update(cxxopts)
self.copts.update(copts)

def addDefine(self, define: str):
self.defines.add(define)
Expand Down Expand Up @@ -738,7 +781,13 @@ def asBazel(
sources = [f for f in self.srcs]
includes = set()
copts = set()
conlyopts, cxxopts, copts = _split_language_opts(self.copts)
self.copts = copts
self.conlyopts.update(conlyopts)
self.cxxopts.update(cxxopts)
copts.update(self.copts)
conlyopts = set(self.conlyopts)
cxxopts = set(self.cxxopts)
for dir in list(self.includeDirs):
includes.add(f'"{dir[0]}"')
# FIXME for the moment move defines to copts so that they are not propagated to
Expand All @@ -757,6 +806,8 @@ def asBazel(
"srcs": sources,
"hdrs": headers,
"copts": list(copts),
"conlyopts": list(conlyopts),
"cxxopts": list(cxxopts),
"defines": list(self.defines),
"data": data,
"includes": list(includes),
Expand Down
4 changes: 3 additions & 1 deletion test/test_bazel.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,10 @@ def test_gen_additional_deps_splits_c_sources(self) -> None:
self.assertIn(sublib, target.deps)
self.assertNotIn(c_src, target.srcs)
self.assertIn(cpp_src, target.srcs)
self.assertIn('"-std=c11"', sublib.copts)
self.assertIn('"-std=c11"', sublib.conlyopts)
self.assertNotIn('"-std=c11"', target.copts)
self.assertNotIn('"-std=c11"', target.conlyopts)
self.assertIn('"-O2"', sublib.copts)

def test_gen_bazel_build_content_includes_various_targets(self) -> None:
build = BazelBuild("src/")
Expand Down
Loading