Skip to content

Commit 0981dca

Browse files
authored
Support conanfile-path for recipe in a subdirectory (#26)
* can configure conanfile-path * wip * wip * wip * Empty commit Made-with: Cursor * wip * wip * wip * wip * wip
1 parent 9d9bba8 commit 0981dca

7 files changed

Lines changed: 76 additions & 15 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ on:
77
branches: [main]
88
workflow_dispatch:
99

10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
13+
1014
jobs:
1115
test:
1216
strategy:

README.md

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,16 @@ Pass configuration options via `--config-settings`:
7272
| `build-profile` | Conan profile for build context | `default` |
7373
| `build-dir` | Persistent build directory | temp dir |
7474

75-
### Conan home and profile
75+
Configure in `pyproject.toml` under `[tool.conan-py-build]`:
7676

77-
The backend **always uses Conan’s default home** (e.g. `~/.conan2`), or the one set via
78-
`CONAN_HOME` or the `.conanrc` file.
79-
80-
By default the backend uses Conan's **default** profile. To use an autodetected
81-
profile, set
82-
**`CONAN_PY_BUILD_PROFILE_AUTODETECT=1`** (or `true` / `yes`).
77+
| Option | Description | Default |
78+
|--------|-------------|---------|
79+
| `version-file` | Path to a Python file from which to read `__version__` when `[project].version` is dynamic | (none) |
80+
| `conanfile-path` | Path to the Conan recipe (directory containing `conanfile.py` or path to the file), relative to project root | `"."` (project root) |
81+
| `wheel.packages` | List of paths (relative to project root) of Python packages to include in the wheel; each must be a directory with `__init__.py` | `["src/<normalized_project_name>"]` |
82+
| `sdist.include` | List of paths or patterns to add to the sdist | `[]` |
83+
| `sdist.exclude` | List of paths or patterns to exclude from the sdist | `[]` |
84+
| `extra-profile` | Path (relative to project root) to a Conan profile file | (none) |
8385

8486
### Dynamic version
8587

@@ -105,6 +107,15 @@ If `wheel.packages` is not set, the backend includes a single package at
105107
`src/<normalized_project_name>` (e.g. `src/mypackage` for a project named
106108
`mypackage`).
107109

110+
**Conan recipe path.** If your recipe lives outside the project root (e.g.
111+
`subfolder/conanfile.py`), set `conanfile-path` so the backend runs
112+
`conan source`, `conan build` and `conan export-pkg` on that path:
113+
114+
```toml
115+
[tool.conan-py-build]
116+
conanfile-path = "subfolder"
117+
```
118+
108119
```toml
109120
[tool.conan-py-build.wheel]
110121
packages = ["src/mypackage", "src/other_package"]
@@ -162,6 +173,15 @@ Keys: `extra-profile`, `extra-profile-host`, `extra-profile-build`,
162173
extra-profile = "cpp17.profile"
163174
```
164175

176+
### Conan home and default profile
177+
178+
The backend **always uses Conan’s default home** (e.g. `~/.conan2`), or the one set via
179+
`CONAN_HOME` or the `.conanrc` file.
180+
181+
By default the backend uses Conan's **default** profile. To use an autodetected
182+
profile, set
183+
**`CONAN_PY_BUILD_PROFILE_AUTODETECT=1`** (or `true` / `yes`).
184+
165185
## Examples
166186

167187
See the [examples/](examples/) directory for complete working examples:

examples/basic/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
This example demonstrates a Python package with C++ code that uses the `fmt`
44
library (managed by Conan) for formatted output with colors.
55

6+
It also shows **`conanfile-path`** (recipe in `conan/` subdirectory).
7+
68
## Build and Install
79

810
```bash
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ class MyadderConan(ConanFile):
99
generators = "CMakeToolchain", "CMakeDeps"
1010

1111
def layout(self):
12-
cmake_layout(self)
12+
# Recipe in conan/, sources at project root (see pyproject conanfile-path)
13+
cmake_layout(self, src_folder="..")
1314

1415
def requirements(self):
1516
self.requires("fmt/12.1.0")

examples/basic/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ name = "myadder"
77
version = "0.1.0"
88
description = "A simple Python package with C++ code that adds two numbers"
99
requires-python = ">=3.8"
10+
11+
[tool.conan-py-build]
12+
conanfile-path = "conan"

src/conan_py_build/build.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ def _get_sdist_config(project_dir: Path) -> dict:
111111
}
112112

113113

114+
def _resolve_conanfile_path(conanfile_path: str, source_dir: Path) -> Path:
115+
# Using py=True will raise an exception if the path is not a .py file.
116+
conan_api = ConanAPI()
117+
full_path = conan_api.local.get_conanfile_path(conanfile_path, source_dir, py=True)
118+
return Path(full_path)
119+
120+
114121
def _get_version_from_config(source_dir: Path) -> Optional[str]:
115122
"""Read version from [tool.conan-py-build] version-file if set. Reads pyproject from source_dir."""
116123
tool = _get_tool_config(source_dir)
@@ -411,11 +418,10 @@ def _do_build_wheel(
411418
arg = key[len("extra-"):].replace("-", ":", 1) # profile-host -> profile:host
412419
profile_args.extend([f"--{arg}", str(p)])
413420

421+
conanfile_path = tool.get("conanfile-path") or "."
422+
resolved_conanfile = str(_resolve_conanfile_path(conanfile_path, source_dir))
414423

415-
source_cmd = [
416-
"source",
417-
"."
418-
]
424+
source_cmd = ["source", resolved_conanfile]
419425
print("Running conan source...", flush=True)
420426
try:
421427
conan_api.command.run(source_cmd)
@@ -424,7 +430,7 @@ def _do_build_wheel(
424430

425431
build_cmd = [
426432
"build",
427-
".",
433+
resolved_conanfile,
428434
"-of",
429435
str(staging_dir),
430436
"-c",
@@ -451,7 +457,7 @@ def _do_build_wheel(
451457

452458
export_pkg_cmd = [
453459
"export-pkg",
454-
str(source_dir),
460+
resolved_conanfile,
455461
"-of",
456462
str(staging_dir),
457463
"-tf",
@@ -527,14 +533,19 @@ def build_sdist(sdist_directory: str, config_settings: Optional[dict] = None) ->
527533
default_include = [
528534
"pyproject.toml",
529535
"CMakeLists.txt",
530-
"conanfile.py",
531536
"cmake",
532537
"src",
533538
"include",
534539
"README.md",
535540
"README.rst",
536541
"LICENSE",
537542
]
543+
544+
tool = _get_tool_config(source_dir)
545+
conanfile_path = tool.get("conanfile-path") or "."
546+
resolved_conanfile = _resolve_conanfile_path(conanfile_path, source_dir)
547+
default_include.append(resolved_conanfile.relative_to(source_dir).as_posix())
548+
538549
default_exclude = [
539550
"__pycache__",
540551
"*.pyc",

tests/test_unit.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33

44
import pytest
55

6+
from conan.errors import ConanException
7+
68
from conan_py_build.build import (
79
_parse_config,
810
_read_version_from_file,
911
_resolve_version,
1012
_get_sdist_config,
13+
_resolve_conanfile_path,
1114
_get_wheel_tags,
1215
_check_wheel_package_path,
1316
_get_wheel_packages,
@@ -124,6 +127,23 @@ def test_get_sdist_config_tool_include_exclude(tmp_path):
124127
assert _get_sdist_config(tmp_path) == {"include": ["docs"], "exclude": ["README.md"]}
125128

126129

130+
def test_resolve_conanfile_path(tmp_path):
131+
"""Resolved path is the conanfile.py (py=True: only .py allowed)."""
132+
(tmp_path / "conanfile.py").write_text("")
133+
assert _resolve_conanfile_path(".", tmp_path) == tmp_path / "conanfile.py"
134+
135+
(tmp_path / "conan").mkdir()
136+
(tmp_path / "conan" / "conanfile.py").write_text("")
137+
assert _resolve_conanfile_path("conan", tmp_path) == tmp_path / "conan" / "conanfile.py"
138+
139+
140+
def test_resolve_conanfile_path_rejects_txt(tmp_path):
141+
"""Raises when only conanfile.txt exists (py=True allows only .py)."""
142+
(tmp_path / "conanfile.txt").write_text("")
143+
with pytest.raises(ConanException, match="Conanfile not found"):
144+
_resolve_conanfile_path(".", tmp_path)
145+
146+
127147
def test_get_wheel_tags_from_env(monkeypatch):
128148
monkeypatch.setenv("WHEEL_ARCH", "manylinux_2_28_x86_64")
129149
monkeypatch.setenv("WHEEL_PYVER", "cp312")

0 commit comments

Comments
 (0)