Skip to content

Commit 53f30e5

Browse files
committed
Fix xmlapi arg parsing and build/test paths
Accept legacy xmlapi() call shapes and make Makefile/unit tests resilient to modern Python build directories.
1 parent 29d0081 commit 53f30e5

4 files changed

Lines changed: 100 additions & 23 deletions

File tree

Makefile

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,21 @@
3838
#. $AutoHeaderSerial::20100225 $
3939
#. ******* AUTOHEADER END v1.2 *******
4040

41-
PY_BIN := python3
41+
PY_BIN ?= python3
4242
VERSION := $(shell cd src;$(PY_BIN) -c "from setup_common import *; print(get_version());")
4343
PACKAGE := python-dmidecode
4444
PY_VER := $(shell $(PY_BIN) -c 'import sys; print("%d.%d"%sys.version_info[0:2])')
45-
PY_MV := $(shell echo $(PY_VER) | cut -b 1)
46-
PY := python$(PY_VER)
47-
SO_PATH := build/lib.linux-$(shell uname -m)-$(PY_VER)
45+
PY_MV := $(shell $(PY_BIN) -c 'import sys; print(sys.version_info[0])')
46+
PY_TAG := $(shell $(PY_BIN) -c 'import sys; print("python%d.%d"%sys.version_info[0:2])')
47+
4848
ifeq ($(PY_MV),2)
49-
SO := $(SO_PATH)/dmidecodemod.so
49+
PLATFORM := $(shell $(PY_BIN) -c 'from distutils.util import get_platform; print(get_platform())')
50+
BUILD_LIB := build/lib.$(PLATFORM)-$(PY_VER)
51+
SO := $(BUILD_LIB)/dmidecodemod.so
5052
else
51-
SOABI := $(shell $(PY_BIN) -c 'import sysconfig; print(sysconfig.get_config_var("SOABI"))')
52-
SO := $(SO_PATH)/dmidecodemod.$(SOABI).so
53+
SOABI := $(shell $(PY_BIN) -c 'import sysconfig; print(sysconfig.get_config_var("SOABI") or "")')
54+
BUILD_LIB := $(shell $(PY_BIN) -c 'import sys, sysconfig; print("build/lib.%s-%s" % (sysconfig.get_platform(), sys.implementation.cache_tag))')
55+
SO := $(BUILD_LIB)/dmidecodemod.$(SOABI).so
5356
endif
5457
SHELL := /bin/bash
5558

@@ -58,23 +61,23 @@ SHELL := /bin/bash
5861

5962
all : build dmidump
6063

61-
build: $(PY)-dmidecodemod.so
62-
$(PY)-dmidecodemod.so: $(SO)
64+
build: $(PY_TAG)-dmidecodemod.so
65+
$(PY_TAG)-dmidecodemod.so: $(SO)
6366
cp $< $@
6467
$(SO):
65-
$(PY) src/setup.py build
68+
$(PY_BIN) src/setup.py build
6669

6770
dmidump : src/util.o src/efi.o src/dmilog.o
6871
$(CC) -o $@ src/dmidump.c $^ -g -Wall -D_DMIDUMP_MAIN_
6972

7073
install:
71-
$(PY) src/setup.py install
74+
$(PY_BIN) src/setup.py install
7275

7376
uninstall:
74-
$(PY) src/setup.py uninstall
77+
$(PY_BIN) src/setup.py uninstall
7578

7679
clean:
77-
-$(PY) src/setup.py clean --all
80+
-$(PY_BIN) src/setup.py clean --all
7881
-rm -f *.so lib/*.o core dmidump src/*.o
7982
-rm -rf build
8083
-rm -rf rpm
@@ -111,4 +114,3 @@ conflicts:
111114
@comm -12 \
112115
<(dpkg-deb -c ../../DPKGS/python-dmidecode_$(VERSION)-1_amd64.deb | awk '$$NF!~/\/$$/{print$$NF}'|sort) \
113116
<(dpkg-deb -c ../../DPKGS/python-dmidecode-dbg_$(VERSION)-1_amd64.deb | awk '$$NF!~/\/$$/{print$$NF}'|sort)
114-

PORTING_PLAN.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,15 @@ Status: 🚧 In progress (core API switched; compatibility surface not complete)
201201
```python
202202
# Replace
203203
import libxml2
204-
204+
205205
# With
206206
import xml.etree.ElementTree as ET
207-
207+
208208
# Create wrapper classes
209209
class XmlNode:
210210
def __init__(self, element):
211211
self.element = element
212-
212+
213213
class XmlDoc:
214214
def __init__(self, element_tree):
215215
self.element_tree = element_tree
@@ -241,7 +241,7 @@ Status: 🚧 In progress (XML serialization bridge implemented; build/packaging
241241
char* serialize_libxml2_to_string(xmlNode* node) {
242242
// Implement XML serialization
243243
}
244-
244+
245245
PyObject* create_elementtree_object(xmlNode* node) {
246246
char* xml_string = serialize_libxml2_to_string(node);
247247
// Call Python ElementTree.parse() on the string
@@ -264,7 +264,7 @@ Status: 🚧 In progress (unit test type checks updated; broader behavioral cove
264264
# Replace
265265
import libxml2
266266
test(isinstance(output_node, libxml2.xmlNode))
267-
267+
268268
# With
269269
from xml.etree.ElementTree import Element
270270
test(isinstance(output_node.element, Element))

src/dmidecodemodule.c

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -695,25 +695,64 @@ static PyObject *dmidecode_xmlapi(PyObject *self, PyObject *args)
695695
xmlDoc *temp_doc = NULL;
696696
xmlNode *dmixml_n = NULL;
697697
xmlChar *xml_buffer = NULL;
698-
char *sect_query = NULL, *qtype = NULL, *rtype = NULL;
698+
const char *sect_query = NULL, *qtype = NULL, *rtype = NULL;
699+
PyObject *third_arg = NULL;
699700
int type_query = -1;
700701
int buffer_size = 0;
701702

702-
// Parse arguments - we use a simpler interface for compatibility
703-
if( !PyArg_ParseTuple(args, "ss|si", &qtype, &rtype, &sect_query, &type_query) ) {
703+
// Parse arguments.
704+
// We support both of these call shapes:
705+
// xmlapi('s', rtype, section)
706+
// xmlapi('t', rtype, typeid)
707+
// And the legacy 4-arg variant:
708+
// xmlapi('t', rtype, section_placeholder, typeid)
709+
if( !PyArg_ParseTuple(args, "ss|Oi", &qtype, &rtype, &third_arg, &type_query) ) {
704710
return NULL;
705711
}
706712

713+
if( third_arg == Py_None ) {
714+
third_arg = NULL;
715+
}
716+
707717
// Check for sensible arguments and retrieve the xmlNode with DMI data
708718
switch( *qtype ) {
709719
case 's': // Section / GroupName
720+
if( third_arg == NULL ) {
721+
PyReturnError(PyExc_TypeError, "section argument cannot be NULL")
722+
}
723+
if( PyUnicode_Check(third_arg) ) {
724+
sect_query = PyUnicode_AsUTF8(third_arg);
725+
} else if( PyBytes_Check(third_arg) ) {
726+
sect_query = PyBytes_AsString(third_arg);
727+
} else {
728+
PyReturnError(PyExc_TypeError, "section argument must be str or bytes")
729+
}
710730
if( sect_query == NULL ) {
711-
PyReturnError(PyExc_TypeError, "section keyword cannot be NULL")
731+
// Exception already set by PyUnicode_AsUTF8() or PyBytes_AsString()
732+
return NULL;
712733
}
713734
dmixml_n = __dmidecode_xml_getsection(global_options, sect_query);
714735
break;
715736

716737
case 't': // TypeID / direct TypeMap
738+
// Prefer a positional typeid in the third slot.
739+
if( third_arg != NULL ) {
740+
if( PyLong_Check(third_arg) ) {
741+
long v = PyLong_AsLong(third_arg);
742+
if( PyErr_Occurred() ) {
743+
return NULL;
744+
}
745+
type_query = (int) v;
746+
} else if( type_query < 0 && (PyUnicode_Check(third_arg) || PyBytes_Check(third_arg)) ) {
747+
// Backwards compatibility: allow typeid passed as string.
748+
const char *s = PyUnicode_Check(third_arg) ? PyUnicode_AsUTF8(third_arg)
749+
: PyBytes_AsString(third_arg);
750+
if( s == NULL ) {
751+
return NULL;
752+
}
753+
type_query = atoi(s);
754+
}
755+
}
717756
if( type_query < 0 ) {
718757
PyReturnError(PyExc_TypeError,
719758
"typeid keyword must be set and must be a positive integer");

unit-tests/unit

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#.awk '$0 ~ /case [0-9]+: .. 3/ { sys.stdout.write($2 }' src/dmidecode.c|tr ':\n' ', '
33

44
from pprint import pprint
5+
import glob
56
import os, sys, subprocess, random, tempfile, time
67
if sys.version_info[0] < 3:
78
import commands as subprocess
@@ -138,6 +139,41 @@ def vwrite(msg, vLevel=0):
138139

139140
################################################################################
140141

142+
# Setup temporary sys.path() with our build dir
143+
144+
def _add_build_paths():
145+
"""Make unit tests resilient across Python versions/build layouts.
146+
147+
Historically this test suite assumed a distutils build directory like:
148+
../build/lib.<sys>-<arch>-<pyver>
149+
150+
On newer Pythons the build directory often looks like:
151+
../build/lib.<platform>-<cache_tag>
152+
(e.g. lib.linux-x86_64-cpython-313)
153+
"""
154+
155+
here = os.path.dirname(os.path.abspath(__file__))
156+
repo_root = os.path.abspath(os.path.join(here, '..'))
157+
build_root = os.path.join(repo_root, 'build')
158+
159+
# Always allow importing from repo root (useful for in-place builds)
160+
if repo_root not in sys.path:
161+
sys.path.insert(0, repo_root)
162+
163+
# Prefer any build/lib.* directory that contains dmidecode.py
164+
candidates = sorted(glob.glob(os.path.join(build_root, 'lib.*')))
165+
for p in candidates:
166+
if os.path.exists(os.path.join(p, 'dmidecode.py')) and p not in sys.path:
167+
sys.path.insert(0, p)
168+
169+
# Fallback: add all build/lib.* candidates (best-effort)
170+
for p in candidates:
171+
if p not in sys.path:
172+
sys.path.insert(0, p)
173+
174+
175+
_add_build_paths()
176+
141177
#. Let's ignore warnings from the module for the test units...
142178
err = open('/dev/null', 'a+', 1)
143179
os.dup2(err.fileno(), sys.stderr.fileno())

0 commit comments

Comments
 (0)