Skip to content

Commit 6c3d8b3

Browse files
committed
CLI Configuration
1 parent 106b512 commit 6c3d8b3

File tree

10 files changed

+208
-70
lines changed

10 files changed

+208
-70
lines changed

cppython/console/entry.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,46 +209,61 @@ def publish(
209209
@app.command()
210210
def build(
211211
context: typer.Context,
212+
configuration: Annotated[
213+
str | None,
214+
typer.Option(help='Named build configuration to use (e.g. CMake preset name, Meson build directory)'),
215+
] = None,
212216
) -> None:
213217
"""Build the project
214218
215219
Assumes dependencies have been installed via `install`.
216220
217221
Args:
218222
context: The CLI configuration object
223+
configuration: Optional named configuration
219224
"""
220225
project = get_enabled_project(context)
221-
project.build()
226+
project.build(configuration=configuration)
222227

223228

224229
@app.command()
225230
def test(
226231
context: typer.Context,
232+
configuration: Annotated[
233+
str | None,
234+
typer.Option(help='Named build configuration to use (e.g. CMake preset name, Meson build directory)'),
235+
] = None,
227236
) -> None:
228237
"""Run project tests
229238
230239
Assumes dependencies have been installed via `install`.
231240
232241
Args:
233242
context: The CLI configuration object
243+
configuration: Optional named configuration
234244
"""
235245
project = get_enabled_project(context)
236-
project.test()
246+
project.test(configuration=configuration)
237247

238248

239249
@app.command()
240250
def bench(
241251
context: typer.Context,
252+
configuration: Annotated[
253+
str | None,
254+
typer.Option(help='Named build configuration to use (e.g. CMake preset name, Meson build directory)'),
255+
] = None,
242256
) -> None:
243257
"""Run project benchmarks
244258
245259
Assumes dependencies have been installed via `install`.
246260
247261
Args:
248262
context: The CLI configuration object
263+
configuration: Optional named configuration
249264
"""
250265
project = get_enabled_project(context)
251-
project.bench()
266+
project.bench(configuration=configuration)
252267

253268

254269
@app.command()
@@ -258,6 +273,10 @@ def run(
258273
str,
259274
typer.Argument(help='The name of the build target/executable to run'),
260275
],
276+
configuration: Annotated[
277+
str | None,
278+
typer.Option(help='Named build configuration to use (e.g. CMake preset name, Meson build directory)'),
279+
] = None,
261280
) -> None:
262281
"""Run a built executable
263282
@@ -266,6 +285,7 @@ def run(
266285
Args:
267286
context: The CLI configuration object
268287
target: The name of the build target to run
288+
configuration: Optional named configuration
269289
"""
270290
project = get_enabled_project(context)
271-
project.run(target)
291+
project.run(target, configuration=configuration)

cppython/core/plugin_schema/generator.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,34 +71,41 @@ def features(directory: DirectoryPath) -> SupportedFeatures:
7171
raise NotImplementedError
7272

7373
@abstractmethod
74-
def build(self) -> None:
74+
def build(self, configuration: str | None = None) -> None:
7575
"""Builds the project using the generator's build system.
7676
77-
Executes the build step (e.g. cmake --build --preset).
77+
Executes the build step. The interpretation of ``configuration`` is
78+
generator-specific (e.g. CMake preset name, Meson build directory).
79+
80+
Args:
81+
configuration: Optional named configuration override.
7882
"""
7983
raise NotImplementedError
8084

8185
@abstractmethod
82-
def test(self) -> None:
86+
def test(self, configuration: str | None = None) -> None:
8387
"""Runs tests using the generator's build system.
8488
85-
Executes the test step (e.g. ctest --preset).
89+
Args:
90+
configuration: Optional named configuration override.
8691
"""
8792
raise NotImplementedError
8893

8994
@abstractmethod
90-
def bench(self) -> None:
95+
def bench(self, configuration: str | None = None) -> None:
9196
"""Runs benchmarks using the generator's build system.
9297
93-
Executes benchmarks, typically via test presets with a label filter.
98+
Args:
99+
configuration: Optional named configuration override.
94100
"""
95101
raise NotImplementedError
96102

97103
@abstractmethod
98-
def run(self, target: str) -> None:
104+
def run(self, target: str, configuration: str | None = None) -> None:
99105
"""Runs a built executable by target name.
100106
101107
Args:
102108
target: The name of the build target/executable to run.
109+
configuration: Optional named configuration override.
103110
"""
104111
raise NotImplementedError

cppython/data.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,22 +104,35 @@ async def download_provider_tools(self) -> None:
104104
self.logger.warning('Downloading the %s requirements to %s', self.plugins.provider.name(), path)
105105
await self.plugins.provider.download_tooling(path)
106106

107-
def build(self) -> None:
108-
"""Builds the project via the generator"""
109-
self.plugins.generator.build()
107+
def build(self, configuration: str | None = None) -> None:
108+
"""Builds the project via the generator
110109
111-
def test(self) -> None:
112-
"""Runs tests via the generator"""
113-
self.plugins.generator.test()
110+
Args:
111+
configuration: Optional named configuration to use
112+
"""
113+
self.plugins.generator.build(configuration=configuration)
114+
115+
def test(self, configuration: str | None = None) -> None:
116+
"""Runs tests via the generator
114117
115-
def bench(self) -> None:
116-
"""Runs benchmarks via the generator"""
117-
self.plugins.generator.bench()
118+
Args:
119+
configuration: Optional named configuration to use
120+
"""
121+
self.plugins.generator.test(configuration=configuration)
122+
123+
def bench(self, configuration: str | None = None) -> None:
124+
"""Runs benchmarks via the generator
125+
126+
Args:
127+
configuration: Optional named configuration to use
128+
"""
129+
self.plugins.generator.bench(configuration=configuration)
118130

119-
def run(self, target: str) -> None:
131+
def run(self, target: str, configuration: str | None = None) -> None:
120132
"""Runs a built executable via the generator
121133
122134
Args:
123135
target: The name of the build target to run
136+
configuration: Optional named configuration to use
124137
"""
125-
self.plugins.generator.run(target)
138+
self.plugins.generator.run(target, configuration=configuration)

cppython/plugins/cmake/plugin.py

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,25 +108,57 @@ def _ctest_command(self) -> str:
108108
return str(ctest_exe)
109109
return 'ctest'
110110

111-
def build(self) -> None:
112-
"""Builds the project using cmake --build with the configured preset."""
113-
release_preset = self.data.configuration_name + '-release'
114-
cmd = [self._cmake_command(), '--build', '--preset', release_preset]
111+
def _resolve_configuration(self, configuration: str | None) -> str:
112+
"""Resolves the effective CMake preset from CLI argument or default config.
113+
114+
Args:
115+
configuration: The configuration value passed from the CLI, or None
116+
117+
Returns:
118+
The resolved CMake preset name
119+
120+
Raises:
121+
ValueError: If no configuration is available from either CLI or default-configuration config
122+
"""
123+
effective = configuration or self.data.default_configuration
124+
if effective is None:
125+
raise ValueError(
126+
'CMake generator requires a configuration. '
127+
"Provide --configuration on the CLI or set 'default-configuration' in [tool.cppython.generators.cmake]."
128+
)
129+
return effective
130+
131+
def build(self, configuration: str | None = None) -> None:
132+
"""Builds the project using cmake --build with the resolved preset.
133+
134+
Args:
135+
configuration: Optional CMake preset name. Overrides default-configuration from config.
136+
"""
137+
preset = self._resolve_configuration(configuration)
138+
cmd = [self._cmake_command(), '--build', '--preset', preset]
115139
subprocess.run(cmd, check=True, cwd=self.data.preset_file.parent)
116140

117-
def test(self) -> None:
118-
"""Runs tests using ctest with the configured preset."""
119-
release_preset = self.data.configuration_name + '-release'
120-
cmd = [self._ctest_command(), '--preset', release_preset]
141+
def test(self, configuration: str | None = None) -> None:
142+
"""Runs tests using ctest with the resolved preset.
143+
144+
Args:
145+
configuration: Optional CMake preset name. Overrides default-configuration from config.
146+
"""
147+
preset = self._resolve_configuration(configuration)
148+
cmd = [self._ctest_command(), '--preset', preset]
121149
subprocess.run(cmd, check=True, cwd=self.data.preset_file.parent)
122150

123-
def bench(self) -> None:
124-
"""Runs benchmarks using ctest with the configured benchmark preset."""
125-
bench_preset = self.data.configuration_name + '-bench-release'
126-
cmd = [self._ctest_command(), '--preset', bench_preset]
151+
def bench(self, configuration: str | None = None) -> None:
152+
"""Runs benchmarks using ctest with the resolved preset.
153+
154+
Args:
155+
configuration: Optional CMake preset name. Overrides default-configuration from config.
156+
"""
157+
preset = self._resolve_configuration(configuration)
158+
cmd = [self._ctest_command(), '--preset', preset]
127159
subprocess.run(cmd, check=True, cwd=self.data.preset_file.parent)
128160

129-
def run(self, target: str) -> None:
161+
def run(self, target: str, configuration: str | None = None) -> None:
130162
"""Runs a built executable by target name.
131163
132164
Searches the build directory for the executable matching the target name.

cppython/plugins/cmake/resolution.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,8 @@ def resolve_cmake_data(data: dict[str, Any], core_data: CorePluginData) -> CMake
7777
cmake_binary = _resolve_cmake_binary(parsed_data.cmake_binary)
7878

7979
return CMakeData(
80-
preset_file=modified_preset_file, configuration_name=parsed_data.configuration_name, cmake_binary=cmake_binary
80+
preset_file=modified_preset_file,
81+
configuration_name=parsed_data.configuration_name,
82+
cmake_binary=cmake_binary,
83+
default_configuration=parsed_data.default_configuration,
8184
)

cppython/plugins/cmake/schema.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ class CMakeData(CPPythonModel):
140140
preset_file: Path
141141
configuration_name: str
142142
cmake_binary: Path | None
143+
default_configuration: str | None = None
143144

144145

145146
class CMakeConfiguration(CPPythonModel):
@@ -167,3 +168,12 @@ class CMakeConfiguration(CPPythonModel):
167168
'Can be overridden via CMAKE_BINARY environment variable.'
168169
),
169170
] = None
171+
default_configuration: Annotated[
172+
str | None,
173+
Field(
174+
alias='default-configuration',
175+
description='Default CMake preset name to use for build/test/bench commands. '
176+
'When set, the --configuration CLI option is no longer required. '
177+
'The CLI --configuration value takes precedence over this default.',
178+
),
179+
] = None

cppython/plugins/meson/plugin.py

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,34 +127,63 @@ def _ensure_setup(self) -> None:
127127

128128
subprocess.run(cmd, check=True, cwd=source_dir)
129129

130-
def build(self) -> None:
131-
"""Builds the project using meson compile."""
130+
def _effective_build_dir(self, configuration: str | None) -> Path:
131+
"""Returns the build directory, optionally overridden by a configuration name.
132+
133+
Args:
134+
configuration: If provided, used as the build directory name instead of the
135+
configured ``build_directory``.
136+
137+
Returns:
138+
The absolute path to the build directory
139+
"""
140+
directory = configuration if configuration else self.data.build_directory
141+
return self.data.build_file.parent / directory
142+
143+
def build(self, configuration: str | None = None) -> None:
144+
"""Builds the project using meson compile.
145+
146+
Args:
147+
configuration: Optional build directory name override.
148+
"""
132149
self._ensure_setup()
133-
cmd = [self._meson_command(), 'compile', '-C', str(self._build_dir())]
150+
build_dir = self._effective_build_dir(configuration)
151+
cmd = [self._meson_command(), 'compile', '-C', str(build_dir)]
134152
subprocess.run(cmd, check=True, cwd=self.data.build_file.parent)
135153

136-
def test(self) -> None:
137-
"""Runs tests using meson test."""
138-
cmd = [self._meson_command(), 'test', '-C', str(self._build_dir())]
154+
def test(self, configuration: str | None = None) -> None:
155+
"""Runs tests using meson test.
156+
157+
Args:
158+
configuration: Optional build directory name override.
159+
"""
160+
build_dir = self._effective_build_dir(configuration)
161+
cmd = [self._meson_command(), 'test', '-C', str(build_dir)]
139162
subprocess.run(cmd, check=True, cwd=self.data.build_file.parent)
140163

141-
def bench(self) -> None:
142-
"""Runs benchmarks using meson test --benchmark."""
143-
cmd = [self._meson_command(), 'test', '--benchmark', '-C', str(self._build_dir())]
164+
def bench(self, configuration: str | None = None) -> None:
165+
"""Runs benchmarks using meson test --benchmark.
166+
167+
Args:
168+
configuration: Optional build directory name override.
169+
"""
170+
build_dir = self._effective_build_dir(configuration)
171+
cmd = [self._meson_command(), 'test', '--benchmark', '-C', str(build_dir)]
144172
subprocess.run(cmd, check=True, cwd=self.data.build_file.parent)
145173

146-
def run(self, target: str) -> None:
174+
def run(self, target: str, configuration: str | None = None) -> None:
147175
"""Runs a built executable by target name.
148176
149177
Searches the build directory for the executable matching the target name.
150178
151179
Args:
152180
target: The name of the build target/executable to run
181+
configuration: Optional build directory name override.
153182
154183
Raises:
155184
FileNotFoundError: If the target executable cannot be found
156185
"""
157-
build_dir = self._build_dir()
186+
build_dir = self._effective_build_dir(configuration)
158187

159188
# Search for the executable in the build directory
160189
candidates = list(build_dir.rglob(target)) + list(build_dir.rglob(f'{target}.exe'))

0 commit comments

Comments
 (0)