Skip to content

Commit 55a9602

Browse files
committed
All tests pass
1 parent b5d653e commit 55a9602

File tree

16 files changed

+205
-52
lines changed

16 files changed

+205
-52
lines changed

src/semiwrap/cmd_gen_pkgconf.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""
2+
Generates a pkg-config file for this extension
3+
"""
4+
5+
import argparse
6+
import inspect
7+
import pathlib
8+
9+
from .pkgconf_cache import INITPY_VARNAME
10+
from .pyproject import PyProject
11+
12+
13+
def main():
14+
parser = argparse.ArgumentParser(usage=inspect.cleandoc(__doc__ or ""))
15+
parser.add_argument("package_name")
16+
parser.add_argument("name")
17+
parser.add_argument("pyproject_toml", type=pathlib.Path)
18+
parser.add_argument("pcfile", type=pathlib.Path)
19+
parser.add_argument("--libinit-py")
20+
args = parser.parse_args()
21+
22+
package_name = args.package_name
23+
project = PyProject(args.pyproject_toml)
24+
25+
module = project.get_extension(package_name)
26+
depends = project.get_extension_deps(module)
27+
28+
pc_install_path = project.package_root / pathlib.Path(*package_name.split("."))
29+
30+
pc_content = [
31+
"# automatically generated by semiwrap",
32+
"prefix=${pcfiledir}",
33+
]
34+
35+
if args.libinit_py:
36+
pc_content.append(f"{INITPY_VARNAME}={args.libinit_py}")
37+
38+
cflags = ["-I${prefix}"]
39+
40+
for i, inc in enumerate(module.includes):
41+
includedir = project.root / pathlib.PurePosixPath(inc)
42+
rel = includedir.relative_to(pc_install_path.parent)
43+
pc_content.append(f"inc{i}=${{prefix}}/{rel.as_posix()}")
44+
cflags.append(f"-I${{inc{i}}}")
45+
46+
pc_content += [
47+
"",
48+
f"Name: {package_name}",
49+
f"Description: semiwrap pybind11 module",
50+
"Version:", # TODO put in correct version
51+
"Cflags: " + " ".join(cflags),
52+
]
53+
54+
if depends:
55+
requires = " ".join(depends)
56+
pc_content.append(f"Requires: {requires}")
57+
58+
pc_content.append("")
59+
60+
with open(args.pcfile, "w") as fp:
61+
fp.write("\n".join(pc_content))
62+
63+
64+
if __name__ == "__main__":
65+
main()

src/semiwrap/cmd_genmeson.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ def gen_meson(
270270
271271
# internal commands for the autowrap machinery
272272
_sw_cmd_gen_libinit_py = [sw_py, '-m', 'semiwrap.cmd_gen_libinit']
273+
_sw_cmd_gen_pkgconf = [sw_py, '-m', 'semiwrap.cmd_gen_pkgconf']
273274
_sw_cmd_publish_casters = [sw_py, '-m', 'semiwrap.cmd_publish_casters']
274275
_sw_cmd_resolve_casters = [sw_py, '-m', 'semiwrap.cmd_resolve_casters']
275276
_sw_cmd_header2dat = [sw_py, '-m', 'semiwrap.cmd_header2dat']

src/semiwrap/cmd_publish_casters.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,11 @@ def main():
3636
pc_install_path = project.package_root / pathlib.Path(*cfg.pypackage.split("."))
3737
rel_includedir = includedir.relative_to(pc_install_path)
3838

39-
incstr = ""
40-
if rel_includedir.parts:
41-
incstr = "/" + "/".join(rel_includedir.parts)
42-
4339
# Write the pc file
4440
pc_content = [
4541
"# automatically generated by semiwrap",
4642
"prefix=${pcfiledir}",
47-
f"includedir=${{pcfiledir}}{incstr}",
43+
f"includedir=${{prefix}}/{rel_includedir.as_posix()}",
4844
"",
4945
f"Name: {caster_name}",
5046
"Description: pybind11 type casters",
@@ -56,6 +52,8 @@ def main():
5652
requires = " ".join(cfg.requires)
5753
pc_content.append(f"Requires: {requires}")
5854

55+
pc_content.append("")
56+
5957
with open(output_pc, "w") as fp:
6058
fp.write("\n".join(pc_content))
6159

src/semiwrap/makeplan.py

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import collections
34
import dataclasses
45
import pathlib
56
import pprint
@@ -219,7 +220,9 @@ def _process_extension_module(
219220
self._prepare_dependency_paths(depends, extension)
220221
)
221222

222-
includes = [self.project_root / inc for inc in extension.includes]
223+
includes = [
224+
self.project_root / pathlib.PurePosixPath(inc) for inc in extension.includes
225+
]
223226
search_path.extend(includes)
224227

225228
# Search the package path last
@@ -240,11 +243,42 @@ def _process_extension_module(
240243
# Generate init.py for loading dependencies
241244
#
242245

243-
libinit_target = self._generate_libinit_file(
244-
extension, module_name, package_path, libinit_modules
246+
libinit_py = None
247+
if libinit_modules:
248+
libinit_py = extension.libinit or f"_init_{module_name}.py"
249+
libinit_tgt = BuildTarget(
250+
command="gen-libinit-py",
251+
args=(OutputFile(libinit_py), *libinit_modules),
252+
install_path=package_path,
253+
)
254+
255+
self.pyi_args += [libinit_py, libinit_tgt]
256+
yield libinit_tgt
257+
258+
#
259+
# Publish a .pc file for this module
260+
#
261+
262+
pc_args = [
263+
package_name,
264+
varname,
265+
self.pyproject_input,
266+
OutputFile(f"{varname}.pc"),
267+
]
268+
if libinit_py:
269+
pc_args += ["--libinit-py", libinit_py]
270+
271+
yield BuildTarget(
272+
command="gen-pkgconf",
273+
args=tuple(pc_args),
274+
install_path=package_path,
245275
)
246-
if libinit_target:
247-
yield libinit_target
276+
277+
yield Entrypoint(group="pkg_config", name=varname, package=parent_package)
278+
279+
#
280+
# Process the headers
281+
#
248282

249283
# Find and load the yaml
250284
if extension.yaml_path is None:
@@ -326,6 +360,31 @@ def _process_extension_module(
326360
)
327361
)
328362

363+
def _locate_type_caster_json(
364+
self,
365+
depname: str,
366+
caster_json_file: T.List[T.Union[BuildTargetOutput, pathlib.Path]],
367+
):
368+
checked = set()
369+
to_check = collections.deque([depname])
370+
while to_check:
371+
name = to_check.popleft()
372+
checked.add(name)
373+
print("checking", name)
374+
375+
entry = self.pkgcache.get(name)
376+
377+
if name in self.local_caster_targets:
378+
caster_json_file.append(self.local_caster_targets[name])
379+
else:
380+
tc = entry.type_casters_path
381+
if tc and tc not in caster_json_file:
382+
caster_json_file.append(tc)
383+
384+
for req in entry.requires:
385+
if req not in checked:
386+
to_check.append(req)
387+
329388
def _prepare_dependency_paths(
330389
self, depends: T.List[str], extension: ExtensionModuleConfig
331390
):
@@ -347,34 +406,13 @@ def _prepare_dependency_paths(
347406
if dep in extension.wraps:
348407
search_path.extend(entry.include_path)
349408

350-
if dep in self.local_caster_targets:
351-
caster_json_file.append(self.local_caster_targets[dep])
352-
elif entry.type_casters_path is not None:
353-
caster_json_file.append(entry.type_casters_path)
409+
self._locate_type_caster_json(dep, caster_json_file)
354410

355411
if entry.libinit_py:
356412
libinit_modules.append(entry.libinit_py)
357413

358414
return search_path, include_directories_uniq, caster_json_file, libinit_modules
359415

360-
def _generate_libinit_file(
361-
self,
362-
extension: ExtensionModuleConfig,
363-
module_name: str,
364-
package_path: pathlib.Path,
365-
libinit_modules: T.List[str],
366-
):
367-
if libinit_modules:
368-
libinit_py = extension.libinit or f"_init_{module_name}.py"
369-
tgt = BuildTarget(
370-
command="gen-libinit-py",
371-
args=(OutputFile(libinit_py), *libinit_modules),
372-
install_path=package_path,
373-
)
374-
375-
self.pyi_args += [libinit_py, tgt]
376-
return tgt
377-
378416
def _process_headers(
379417
self,
380418
extension: ExtensionModuleConfig,
@@ -395,7 +433,8 @@ def _process_headers(
395433
ayml = AutowrapConfigYaml.from_file(self.project_root / yml_input.path)
396434
except FileNotFoundError:
397435
if not self.missing_yaml_ok:
398-
raise
436+
msg = f"{self.project_root / yml_input.path}: use `python3 -m semiwrap.cmd_creategen` to generate"
437+
raise FileNotFoundError(msg) from None
399438
ayml = AutowrapConfigYaml()
400439

401440
# find the source header
@@ -491,12 +530,13 @@ def _process_headers(
491530
return datfiles, module_sources, subpackages
492531

493532
def _locate_header(self, hdr: str, search_path: T.List[pathlib.Path]):
533+
phdr = pathlib.PurePosixPath(hdr)
494534
for p in search_path:
495-
h_path = p / hdr
535+
h_path = p / phdr
496536
if h_path.exists():
497537
return InputFile(h_path.relative_to(self.project_root)), p
498538
raise FileNotFoundError(
499-
f"cannot locate {hdr} in {', '.join(map(str, search_path))}"
539+
f"cannot locate {phdr} in {', '.join(map(str, search_path))}"
500540
)
501541

502542

src/semiwrap/pkgconf_cache.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ def _get_pkgconf_data(self, *args) -> str:
2121

2222
return r.stdout.decode("utf-8").strip()
2323

24+
@property
25+
def requires(self) -> T.List[str]:
26+
if not hasattr(self, "_requires"):
27+
raw = self._get_pkgconf_data("--print-requires")
28+
self._requires = [r for r in raw.split("\n") if r]
29+
30+
return self._requires
31+
2432
@property
2533
def include_path(self) -> T.List[pathlib.Path]:
2634
"""Only the include path for this package"""
@@ -92,6 +100,7 @@ def add_local(
92100
entry._include_path = [inc.absolute() for inc in includes]
93101
entry._full_include_path = entry._include_path[:]
94102
entry._libinit_py = None
103+
entry._requires = requires[:]
95104
for req in requires:
96105
dep = self.get(req)
97106
entry._full_include_path.extend(dep.full_include_path)

tests/cpp/sw-caster-consumer/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ packages = ['src/sw_caster_consumer']
2020

2121
[tool.semiwrap]
2222
[tool.semiwrap.extension_modules."sw_caster_consumer._module"]
23-
depends = ["swtest-base", "sw-test-base-pybind11"]
23+
depends = ["swtest_base__module"]
2424

2525
[tool.semiwrap.extension_modules."sw_caster_consumer._module".headers]
26-
fn = "cpp/more.h"
26+
more = "cpp/more.h"

tests/cpp/sw-test-base/pyproject.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ description = "Test program"
77
name = "sw-test-base"
88
version = "0.0.1"
99

10-
[project.entry-points.pkg_config]
11-
sw-test-base = 'swtest_base'
12-
1310
[tool.hatch.build.targets.wheel]
1411
packages = ['src/swtest_base']
1512

@@ -24,10 +21,11 @@ packages = ['src/swtest_base']
2421
[tool.semiwrap]
2522
[tool.semiwrap.extension_modules."swtest_base._module"]
2623
depends = ["sw-test-base-pybind11"]
24+
includes = ["src/swtest_base/cpp"]
2725

2826
[tool.semiwrap.extension_modules."swtest_base._module".headers]
2927
fn = "cpp/fn.h"
30-
28+
base_class = "cpp/baseclass.h"
3129

3230

3331
[tool.semiwrap.export_type_casters.sw-test-base-pybind11]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
// tests trampoline across packages
6+
class abaseclass {
7+
public:
8+
virtual ~abaseclass() = default;
9+
10+
inline virtual std::string fn() {
11+
return "abaseclass";
12+
}
13+
14+
};

tests/cpp/sw-test-base/src/swtest_base/swtest-base.pc

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)