Skip to content

Commit 9116005

Browse files
cvicentiuRazvanLiviuVarzaru
authored andcommitted
Introduce a cmake generator class
This commit introduces a utility class to allow for uniform construction of the MariaDB (& other projects) cmake step. Usage is documented in the unit test. * compilers.py -> Standard type-safe way of specifying the compiler command * options.py -> Standard type-safe way of specifying CMAKE args. This file should be extended over time, as MariaDB's cmake flags change. * generator.py -> Holds the CMake Generator class used to build the command argument. This commit also introduces an additional workflow step, to make sure we run unit tests on the code.
1 parent 71010f2 commit 9116005

File tree

8 files changed

+438
-0
lines changed

8 files changed

+438
-0
lines changed

.github/workflows/unittests.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
name: unittests
3+
4+
on:
5+
pull_request:
6+
push:
7+
8+
jobs:
9+
pre-commit:
10+
runs-on: ubuntu-24.04
11+
steps:
12+
- uses: actions/checkout@v4
13+
#//TEMP we need to check only modified files WRT main
14+
- name: Install requirements (apt)
15+
run: |
16+
sudo apt-get update
17+
sudo apt-get install -y libvirt-dev
18+
- name: Install requirements (uv pip)
19+
run: |
20+
curl -LsSf https://astral.sh/uv/install.sh | sh
21+
make venv
22+
make install
23+
- name: Run unit tests
24+
run: |
25+
source .venv/bin/activate
26+
make test

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ checkconfig: ## Validate master.cfg files
5656
$(info --> validate master.cfg files with docker)
5757
./validate_master_cfg.sh
5858

59+
test: ## Run unittests
60+
$(info --> run unittests)
61+
python -m unittest discover -s tests -p "test*.py"
62+
5963
clean: ## Clean venv
6064
[[ ! -d $(VENV_DIR) ]] || rm -rf $(VENV_DIR)
6165
[[ ! -d $(VENDOR_DIR) ]] || rm -rf $(VENDOR_DIR)

steps/__init__.py

Whitespace-only changes.

steps/cmake/__init__.py

Whitespace-only changes.

steps/cmake/compilers.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class CompilerCommand:
2+
def __init__(self, cc: str, cxx: str):
3+
assert isinstance(cc, str)
4+
assert isinstance(cxx, str)
5+
self.cc_ = cc
6+
self.cxx_ = cxx
7+
8+
@property
9+
def cc(self):
10+
return self.cc_
11+
12+
@property
13+
def cxx(self):
14+
return self.cxx_
15+
16+
17+
class GCCCompiler(CompilerCommand):
18+
def __init__(self, version: str = None):
19+
if version:
20+
super().__init__(f"gcc-{version}", f"g++-{version}")
21+
else:
22+
super().__init__("gcc", "g++")
23+
24+
25+
class ClangCompiler(CompilerCommand):
26+
def __init__(self, version: str = None):
27+
if version:
28+
super().__init__(cc=f"clang-{version}", cxx=f"clang++-{version}")
29+
else:
30+
super().__init__(cc="clang", cxx="clang++")
31+
32+
33+
class MicrosoftCompiler(CompilerCommand):
34+
def __init__(self):
35+
super().__init__(cc="cl", cxx="cl++")
36+
37+
38+
# TODO(cvicentiu) aocc, intel compiler.

steps/cmake/generator.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from typing import Iterable
2+
3+
from .compilers import CompilerCommand
4+
from .options import CMAKE, OTHER, BuildConfig, CMakeOption
5+
6+
7+
class DuplicateFlagException(Exception):
8+
def __init__(self, flag_name: str, existing_value: str, new_value: str):
9+
super().__init__(
10+
f"Duplicate flag detected: {flag_name}"
11+
f"(existing: {existing_value}, new: {new_value})"
12+
)
13+
super().__init__(f"Duplicate flag detected: {flag_name}")
14+
15+
16+
class CMakeGenerator:
17+
"""
18+
Generates a CMake command with specified flags.
19+
"""
20+
21+
def __init__(self, flags: Iterable[CMakeOption], source_path: str = "."):
22+
"""
23+
Initializes the CMakeGenerator with an optional list of flags.
24+
25+
Args:
26+
flags: An iterable of CMakeFlag objects.
27+
source_path: The source path to the base CMakeLists.txt file.
28+
Default path is "in source build".
29+
"""
30+
self.flags: dict[str, CMakeOption] = {}
31+
self.source_path = source_path
32+
self.append_flags(flags)
33+
34+
def set_compiler(self, compiler: CompilerCommand):
35+
"""
36+
Sets the compiler options for C and C++ compilers.
37+
38+
Args:
39+
compiler: An instance of CompilerCommand.
40+
"""
41+
assert isinstance(compiler, CompilerCommand)
42+
self.append_flags(
43+
[
44+
CMakeOption(CMAKE.C_COMPILER, compiler.cc),
45+
CMakeOption(CMAKE.CXX_COMPILER, compiler.cxx),
46+
]
47+
)
48+
49+
def use_ccache(self):
50+
"""
51+
Configures CMake to use ccache for faster builds.
52+
"""
53+
self.append_flags(
54+
[
55+
CMakeOption(CMAKE.C_COMPILER_LAUNCHER, "ccache"),
56+
CMakeOption(CMAKE.CXX_COMPILER_LAUNCHER, "ccache"),
57+
]
58+
)
59+
60+
# TODO(cvicentiu) write unit test.
61+
def set_build_config(self, config: BuildConfig):
62+
"""
63+
Set the build config flag. This is separate because of it being a
64+
"one-off" special flag.
65+
"""
66+
self.append_flags([CMakeOption(OTHER.BUILD_CONFIG, config)])
67+
68+
def append_flags(self, flags: Iterable[CMakeOption]):
69+
"""
70+
Appends new flags to the generator.
71+
72+
Raises:
73+
DuplicateFlagException: If a flag with the same name already
74+
exists.
75+
"""
76+
for flag in flags:
77+
# Do not allow duplicate flags being set.
78+
# Flags should only be set once to avoid confusion about them
79+
# being overwritten.
80+
if flag.name in self.flags:
81+
existing_flag = self.flags[flag.name]
82+
raise DuplicateFlagException(flag.name, existing_flag.value, flag.value)
83+
self.flags[flag.name] = flag
84+
85+
def generate(self) -> list[str]:
86+
"""
87+
Generates the CMake command as a list of strings.
88+
"""
89+
result = ["cmake", self.source_path]
90+
for flag in sorted(list(self.flags.values()), key=lambda x: x.name):
91+
result.append(flag.as_cmd_arg())
92+
return result

steps/cmake/options.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from enum import StrEnum
2+
3+
4+
# Flag names use UPPER_CASE
5+
class CMAKE(StrEnum):
6+
"""
7+
Explicitly enumerates valid CMake flags to enforce type safety
8+
and avoid typos in flag names.
9+
"""
10+
11+
AR = "AR"
12+
BUILD_TYPE = "BUILD_TYPE"
13+
CXX_COMPILER = "CXX_COMPILER"
14+
CXX_FLAGS = "CXX_FLAGS"
15+
C_COMPILER = "C_COMPILER"
16+
C_FLAGS = "C_FLAGS"
17+
C_COMPILER_LAUNCHER = "C_COMPILER_LAUNCHER"
18+
CXX_COMPILER_LAUNCHER = "CXX_COMPILER_LAUNCHER"
19+
INSTALL_PREFIX = "INSTALL_PREFIX"
20+
LIBRARY_PATH = "LIBRARY_PATH"
21+
22+
def __str__(self):
23+
return f"CMAKE_{self.value}"
24+
25+
26+
class PLUGIN(StrEnum):
27+
"""
28+
Enumerates valid plugin options for MariaDB's CMake configuration.
29+
"""
30+
31+
ARCHIVE_STORAGE_ENGINE = "ARCHIVE"
32+
CONNECT_STORAGE_ENGINE = "CONNECT"
33+
ROCKSDB_STORAGE_ENGINE = "ROCKSDB"
34+
TOKUDB_STORAGE_ENGINE = "TOKUDB"
35+
36+
def __str__(self):
37+
return f"PLUGIN_{self.value}"
38+
39+
40+
class WITH(StrEnum):
41+
"""
42+
Enumerates valid options for MariaDB's CMake configuration. Each
43+
option starts with WITH_.
44+
"""
45+
46+
ASAN = "ASAN"
47+
DBUG_TRACE = "DBUG_TRACE"
48+
EMBEDDED_SERVER = "EMBEDDED_SERVER"
49+
JEMALLOC = "JEMALLOC"
50+
SAFEMALLOC = "SAFEMALLOC"
51+
UBSAN = "UBSAN"
52+
UNIT_TESTS = "UNIT_TESTS"
53+
VALGRIND = "VALGRIND"
54+
55+
def __str__(self):
56+
return f"WITH_{self.value}"
57+
58+
59+
class OTHER(StrEnum):
60+
"""
61+
Enumerates other valid options for MariaDB's
62+
"""
63+
64+
BUILD_CONFIG = "BUILD_CONFIG"
65+
66+
67+
# Flag values use CapitalCase
68+
class BuildType(StrEnum):
69+
"""
70+
Enumerates build types for CMake.
71+
"""
72+
73+
RELEASE = "Release"
74+
DEBUG = "Debug"
75+
RELWITHDEBUG = "RelWithDebInfo"
76+
77+
78+
class BuildConfig(StrEnum):
79+
"""
80+
Used for -DBUILD_CONFIG=<value> of cmake.
81+
Enumerates build configurations for MariaDB's CMake.
82+
"""
83+
84+
MYSQL_RELEASE = "mysql_release"
85+
86+
87+
class CMakeOption:
88+
"""
89+
Represents a CMake option in the form `-D<name>=<value>`.
90+
"""
91+
92+
@staticmethod
93+
def _quote_value(value: str):
94+
"""
95+
Quote the value if it contains spaces or special characters.
96+
"""
97+
if " " in value or '"' in value:
98+
return f'"{value.replace('"', '\\\"')}"'
99+
return value
100+
101+
def __init__(self, name: StrEnum, value: str | bool):
102+
assert isinstance(name, StrEnum)
103+
assert isinstance(value, str) or isinstance(value, bool)
104+
self.name = str(name)
105+
if isinstance(value, bool):
106+
self.value = "ON" if value else "OFF"
107+
elif isinstance(value, str):
108+
self.value = value
109+
# Quote if necessary.
110+
self.value = self._quote_value(self.value)
111+
112+
def as_cmd_arg(self) -> str:
113+
return f"-D{self.name}={self.value}"
114+
115+
def __str__(self) -> str:
116+
return self.as_cmd_arg()
117+
118+
def __repr__(self) -> str:
119+
return f"CMakeOption({self.name}, {self.value})"

0 commit comments

Comments
 (0)