Skip to content

Commit 042bf81

Browse files
Merge branch 'master' into claude/add-stl-container-tests-01SghEieKPbunGiJjFeExriA
2 parents 16c55ea + 53d4844 commit 042bf81

File tree

9 files changed

+327
-18
lines changed

9 files changed

+327
-18
lines changed

.github/workflows/release-autowrap.yaml

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
name: release autowrap
22

3-
###########################################################################
4-
# please make sure that autowrap version numbers have been properly updated
5-
###########################################################################
6-
73
on:
84
workflow_dispatch: # manual trigger
95
inputs:
6+
version:
7+
description: 'Version to release (empty = use version from autowrap/version.py)'
8+
default: ''
109
next_version:
11-
description: 'Next version (empty = minor bump)'
10+
description: 'Next development version (empty = minor bump from release version)'
1211
default: ''
1312

1413
jobs:
@@ -22,6 +21,29 @@ jobs:
2221
with:
2322
python-version: "3.11"
2423

24+
- name: Determine release version
25+
id: version
26+
run: |
27+
INPUT_VER="${{ github.event.inputs.version }}"
28+
if [ -z "$INPUT_VER" ]; then
29+
# Deduce version from autowrap/version.py
30+
RELEASE_VER=$(python3 -c 'from autowrap.version import __version__; print(__version__)')
31+
echo "Using version from autowrap/version.py: $RELEASE_VER"
32+
else
33+
# Validate version format (only allow digits and dots)
34+
if ! echo "$INPUT_VER" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
35+
echo "Error: Invalid version format. Expected format: X.Y.Z (e.g., 1.2.3)"
36+
exit 1
37+
fi
38+
RELEASE_VER="$INPUT_VER"
39+
echo "Using input version: $RELEASE_VER"
40+
# Update version.py with the specified version
41+
sed -i -e "s/^__version__ = \".*\"/__version__ = \"$RELEASE_VER\"/g" autowrap/version.py
42+
TUPLE_VER=$(echo $RELEASE_VER | sed 's/\./, /g')
43+
sed -i -e "s/^__version_tuple__ = (.*)/__version_tuple__ = ($TUPLE_VER)/g" autowrap/version.py
44+
fi
45+
echo "version=$RELEASE_VER" >> $GITHUB_OUTPUT
46+
2547
- name: Install build dependencies
2648
run: |
2749
python -m pip install -U pip build
@@ -36,10 +58,6 @@ jobs:
3658
user: __token__
3759
password: ${{ secrets.PYPI_RELEASE_AUTOWRAP }}
3860
packages_dir: ${{ github.workspace }}/dist
39-
40-
- name: Parse version
41-
run: echo "version=$(python3 -c 'from autowrap.version import __version__; print(__version__)')" >> $GITHUB_OUTPUT
42-
id: version
4361

4462
- name: Create github release
4563
uses: softprops/action-gh-release@v2
@@ -56,9 +74,17 @@ jobs:
5674
- name: Setup things for new cycle
5775
id: setup_new
5876
run: |
59-
NEXT_VER=${{ github.event.inputs.next_version }}
60-
OLD_VER=${{ steps.version.outputs.version }}
61-
[ -z "$NEXT_VER" ] && NEXT_VER=$(echo $OLD_VER | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g') || true
77+
NEXT_VER="${{ github.event.inputs.next_version }}"
78+
RELEASE_VER="${{ steps.version.outputs.version }}"
79+
if [ -z "$NEXT_VER" ]; then
80+
NEXT_VER=$(echo $RELEASE_VER | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g')
81+
else
82+
# Validate version format (only allow digits and dots)
83+
if ! echo "$NEXT_VER" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
84+
echo "Error: Invalid next version format. Expected format: X.Y.Z (e.g., 1.2.3)"
85+
exit 1
86+
fi
87+
fi
6288
echo >> HISTORY.md
6389
cat CHANGELOG.md >> HISTORY.md
6490
echo >> HISTORY.md

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
autowrap 0.24.0 (unreleased)
1+
autowrap 0.24.0
22

33

44
New STL Container Support (C++17):

autowrap/CodeGenerator.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1984,12 +1984,18 @@ def create_foreign_cimports(self):
19841984
# usually in the same pxd file and should not be
19851985
# globally exported.
19861986
pass
1987+
elif resolved.wrap_ignore:
1988+
# Skip wrap-ignored enums as they won't have pxd files
1989+
L.info("Skip pxd import for wrap-ignored enum %s" % name)
19871990
else:
19881991
code.add("from $mname cimport $name", locals())
19891992
if resolved.__class__ in (ResolvedClass,):
1990-
# Skip classes that explicitely should not have a pxd
1993+
# Skip classes that explicitly should not have a pxd
19911994
# import statement (abstract base classes and the like)
1992-
if not resolved.no_pxd_import:
1995+
# Also skip wrap-ignored classes as they won't have pxd files
1996+
if resolved.wrap_ignore:
1997+
L.info("Skip pxd import for wrap-ignored class %s" % name)
1998+
elif not resolved.no_pxd_import:
19931999
if resolved.cpp_decl.annotations.get("wrap-attach"):
19942000
code.add("from $mname cimport __$name", locals())
19952001
else:

autowrap/version.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3131
"""
3232

33-
__version__ = "0.23.0"
33+
__version__ = "0.24.0"
3434

3535
# For backward compatibility with older code expecting tuple format
36-
__version_tuple__ = (0, 23, 0)
36+
__version_tuple__ = (0, 24, 0)
3737

3838
# for compatibility with older version:
3939
version = __version_tuple__

tests/test_code_generator.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,3 +383,89 @@ def test_automatic_output_string_conversion():
383383
msg = h.get(input_unicode)
384384
assert isinstance(msg, expected_type)
385385
assert msg == expected
386+
387+
388+
def test_wrap_ignore_foreign_cimports():
389+
"""
390+
Test that wrap-ignored classes are not included in foreign cimports.
391+
392+
This test verifies the fix for GitHub issue #194:
393+
When a class has the wrap-ignore annotation, other modules should not
394+
generate cimport statements for it, as wrap-ignored classes don't have
395+
corresponding pxd files.
396+
397+
The test creates a multi-module scenario where one module has a
398+
wrap-ignored class, and verifies that the generated code in the other
399+
module does not attempt to cimport the wrap-ignored class.
400+
"""
401+
import tempfile
402+
import shutil
403+
from autowrap.CodeGenerator import CodeGenerator
404+
from autowrap.DeclResolver import ResolvedClass
405+
406+
# Create a temporary directory for generated files
407+
test_dir = tempfile.mkdtemp()
408+
try:
409+
# Parse the libcpp_test.pxd which has AbstractBaseClass (wrap-ignore)
410+
# and ABS_Impl1, ABS_Impl2 which inherit from it
411+
pxd_files = ["libcpp_test.pxd"]
412+
full_pxd_files = [os.path.join(test_files, f) for f in pxd_files]
413+
decls, instance_map = autowrap.parse(full_pxd_files, test_files)
414+
415+
# Find the wrap-ignored class (AbstractBaseClass)
416+
wrap_ignored_classes = [d for d in decls if isinstance(d, ResolvedClass) and d.wrap_ignore]
417+
assert len(wrap_ignored_classes) > 0, "Expected at least one wrap-ignored class"
418+
419+
# Set up a multi-module scenario
420+
# Module "module1" contains all the classes
421+
# Module "module2" is our target module that will generate foreign cimports
422+
module1_decls = decls
423+
module2_decls = [] # Empty module that needs to import from module1
424+
425+
master_dict = {
426+
"module1": {"decls": module1_decls, "addons": [], "files": full_pxd_files},
427+
"module2": {"decls": module2_decls, "addons": [], "files": []},
428+
}
429+
430+
# Generate code for module2 which would need foreign cimports from module1
431+
target = os.path.join(test_dir, "module2.pyx")
432+
cg = CodeGenerator(
433+
module2_decls,
434+
instance_map,
435+
pyx_target_path=target,
436+
all_decl=master_dict,
437+
)
438+
439+
# Call create_foreign_cimports
440+
cg.create_foreign_cimports()
441+
442+
# Check the generated code for foreign cimports
443+
generated_code = ""
444+
for code_block in cg.top_level_code:
445+
generated_code += code_block.render()
446+
447+
# Verify that wrap-ignored classes are NOT in the cimports
448+
for ignored_class in wrap_ignored_classes:
449+
# The cimport line would look like: "from .module1 cimport ClassName"
450+
cimport_pattern = f"cimport {ignored_class.name}"
451+
assert cimport_pattern not in generated_code, (
452+
f"Wrap-ignored class '{ignored_class.name}' should not be in foreign cimports. "
453+
f"Generated code:\n{generated_code}"
454+
)
455+
456+
# Verify that non-ignored classes ARE in the cimports
457+
non_ignored_classes = [d for d in decls if isinstance(d, ResolvedClass) and not d.wrap_ignore]
458+
for normal_class in non_ignored_classes:
459+
# Skip classes with no_pxd_import or wrap-attach
460+
if normal_class.no_pxd_import:
461+
continue
462+
if normal_class.cpp_decl.annotations.get("wrap-attach"):
463+
continue
464+
cimport_pattern = f"cimport {normal_class.name}"
465+
assert cimport_pattern in generated_code, (
466+
f"Non-ignored class '{normal_class.name}' should be in foreign cimports. "
467+
f"Generated code:\n{generated_code}"
468+
)
469+
470+
finally:
471+
shutil.rmtree(test_dir, ignore_errors=True)

tests/test_files/inherited.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#Generated with autowrap 0.23.0 and Cython (Parser) 3.2.1
1+
#Generated with autowrap 0.24.0 and Cython (Parser) 3.2.1
22
#cython: c_string_encoding=ascii
33
#cython: embedsignature=False
44
from enum import Enum as _PyEnum

tests/test_files/wrapped_container_test.hpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,20 @@ class WrappedContainerTest {
129129
return result;
130130
}
131131

132+
// Lookup Item by key - returns Item value_ or -1 if not found
133+
int lookupMapIntToItem(const std::map<int, Item>& m, int key) {
134+
auto it = m.find(key);
135+
if (it != m.end()) {
136+
return it->second.value_;
137+
}
138+
return -1;
139+
}
140+
141+
// Check if key exists in map
142+
bool hasKeyMapIntToItem(const std::map<int, Item>& m, int key) {
143+
return m.count(key) > 0;
144+
}
145+
132146
// ========================================
133147
// MAP WITH WRAPPED CLASS AS KEY
134148
// ========================================
@@ -244,6 +258,20 @@ class WrappedContainerTest {
244258
return result;
245259
}
246260

261+
// Lookup Item by key in unordered_map - returns Item value_ or -1 if not found
262+
int lookupUnorderedMapIntToItem(const std::unordered_map<int, Item>& m, int key) {
263+
auto it = m.find(key);
264+
if (it != m.end()) {
265+
return it->second.value_;
266+
}
267+
return -1;
268+
}
269+
270+
// Check if key exists in unordered_map
271+
bool hasKeyUnorderedMapIntToItem(const std::unordered_map<int, Item>& m, int key) {
272+
return m.count(key) > 0;
273+
}
274+
247275
// ========================================
248276
// UNORDERED_MAP WITH WRAPPED CLASS AS BOTH KEY AND VALUE
249277
// ========================================
@@ -339,6 +367,20 @@ class WrappedContainerTest {
339367
return result;
340368
}
341369

370+
// Check if Item exists in unordered_set (membership test using hash)
371+
bool hasItemUnorderedSet(const std::unordered_set<Item>& items, const Item& item) {
372+
return items.count(item) > 0;
373+
}
374+
375+
// Find Item and return its value_ or -1 if not found
376+
int findItemUnorderedSet(const std::unordered_set<Item>& items, const Item& item) {
377+
auto it = items.find(item);
378+
if (it != items.end()) {
379+
return it->value_;
380+
}
381+
return -1;
382+
}
383+
342384
// ========================================
343385
// NESTED CONTAINERS: list<vector<int>>
344386
// ========================================

tests/test_files/wrapped_container_test.pxd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ cdef extern from "wrapped_container_test.hpp":
5959
# ========================================
6060
int sumMapValues(libcpp_map[int, Item]& m)
6161
libcpp_map[int, Item] createMapIntToItem(int count)
62+
int lookupMapIntToItem(libcpp_map[int, Item]& m, int key)
63+
bool hasKeyMapIntToItem(libcpp_map[int, Item]& m, int key)
6264

6365
# ========================================
6466
# MAP WITH WRAPPED CLASS AS KEY
@@ -97,6 +99,8 @@ cdef extern from "wrapped_container_test.hpp":
9799
# ========================================
98100
int sumUnorderedMapValues(libcpp_unordered_map[int, Item]& m)
99101
libcpp_unordered_map[int, Item] createUnorderedMapIntToItem(int count)
102+
int lookupUnorderedMapIntToItem(libcpp_unordered_map[int, Item]& m, int key)
103+
bool hasKeyUnorderedMapIntToItem(libcpp_unordered_map[int, Item]& m, int key)
100104

101105
# ========================================
102106
# UNORDERED_MAP WITH WRAPPED CLASS AS BOTH KEY AND VALUE
@@ -126,6 +130,8 @@ cdef extern from "wrapped_container_test.hpp":
126130
# ========================================
127131
int sumUnorderedSetItems(libcpp_unordered_set[Item]& items)
128132
libcpp_unordered_set[Item] createUnorderedSetItems(int count)
133+
bool hasItemUnorderedSet(libcpp_unordered_set[Item]& items, Item& item)
134+
int findItemUnorderedSet(libcpp_unordered_set[Item]& items, Item& item)
129135

130136
# ========================================
131137
# NESTED CONTAINERS: list<vector<int>>

0 commit comments

Comments
 (0)