Make pip buildable
This package would be very useful if able to be built with pip. This
commit makes libcper usable from python with
git clone git@github.com:openbmc/libcper.git
pip install libcper
The API at the moment is primitive, and only supports the parse api. An
example of its usage is included.
Change-Id: I944565c71f616735a738bcc4f815d25251ed27bb
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/meson.build b/meson.build
index 4240cd3..ede0bf1 100644
--- a/meson.build
+++ b/meson.build
@@ -2,10 +2,9 @@
'libcper',
'c',
version: '0.1',
- meson_version: '>=1.1.1',
+ meson_version: '>=1.2.0',
default_options: [
'c_std=c18',
- 'cpp_std=c++23',
'tests=' + (meson.is_subproject() ? 'disabled' : 'enabled'),
'warning_level=2',
'werror=true',
@@ -74,7 +73,11 @@
cc = meson.get_compiler('c')
-json_c_dep = dependency('json-c')
+json_c = dependency('json-c', default_options: {'warning_level': '0'})
+json_c_dep = declare_dependency(
+ include_directories: include_directories('subprojects'),
+ dependencies: json_c,
+)
libcper_include = ['include']
libcper_include_dir = include_directories(libcper_include, is_system: true)
@@ -89,6 +92,8 @@
subdir('include')
+install = get_option('install').allowed()
+
libcper_parse = library(
'cper-parse',
libcper_parse_sources,
@@ -97,8 +102,7 @@
include_directories: libcper_include_dir,
c_args: '-Wno-address-of-packed-member',
dependencies: [json_c_dep],
- install: true,
- install_dir: get_option('libdir'),
+ install: install,
)
libcper_parse_dep = declare_dependency(
include_directories: libcper_include_dir,
@@ -117,20 +121,21 @@
include_directories: libcper_include_dir,
dependencies: [json_c_dep],
link_with: [libcper_parse],
- install: true,
- install_dir: get_option('libdir'),
+ install: install,
)
libcper_generate_dep = declare_dependency(
include_directories: libcper_include_dir,
link_with: libcper_generate,
)
-import('pkgconfig').generate(
- libcper_parse,
- name: meson.project_name(),
- version: meson.project_version(),
- description: 'C bindings for parsing CPER',
-)
+if get_option('pkgconfig').allowed()
+ import('pkgconfig').generate(
+ libcper_parse,
+ name: meson.project_name(),
+ version: meson.project_version(),
+ description: 'C bindings for parsing CPER',
+ )
+endif
if get_option('utility').allowed()
executable(
@@ -139,7 +144,7 @@
include_directories: libcper_include_dir,
dependencies: [json_c_dep],
link_with: [libcper_parse, libcper_generate],
- install: true,
+ install: install,
install_dir: get_option('bindir'),
)
@@ -149,7 +154,7 @@
edk_sources,
include_directories: libcper_include_dir,
link_with: [libcper_parse, libcper_generate],
- install: true,
+ install: install,
install_dir: get_option('bindir'),
)
endif
@@ -161,3 +166,14 @@
subdir('tests')
endif
endif
+
+if get_option('python').allowed()
+ py = import('python').find_installation(pure: false)
+ py.extension_module(
+ 'cper',
+ 'pycper.c',
+ c_args: ['-DLIBCPER_PYTHON'],
+ dependencies: [libcper_parse_dep, json_c_dep],
+ install: true,
+ )
+endif
diff --git a/meson.options b/meson.options
index 9cda0c4..ffd7c74 100644
--- a/meson.options
+++ b/meson.options
@@ -5,7 +5,25 @@
description: 'Build fuzz targets',
)
option('tests', type: 'feature', value: 'enabled', description: 'Build tests')
-option('utility', type: 'feature', value: 'enabled', description: 'Utility')
+option(
+ 'utility',
+ type: 'feature',
+ value: 'enabled',
+ description: 'Compile CLI utilities',
+)
+option(
+ 'python',
+ type: 'feature',
+ value: 'disabled',
+ description: 'Build python extensions',
+)
+option('install', type: 'feature', value: 'enabled', description: 'Install')
+option(
+ 'pkgconfig',
+ type: 'feature',
+ value: 'enabled',
+ description: 'Install pkgconfig',
+)
option(
'output-all-properties',
type: 'feature',
diff --git a/parse_example.py b/parse_example.py
new file mode 100755
index 0000000..3760b35
--- /dev/null
+++ b/parse_example.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+
+import json
+
+import cper
+
+
+def main():
+ cper_raw = (
+ b"\x43\x50\x45\x52\x01\x01\xff\xff\xff\xff\x01\x00\x03"
+ b"\x00\x00\x00\x00\x00\x00\x00\x28\x01\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x78"
+ b"\xe4\x01\xa9\x73\x11\xef\x11\x96\xa8\x5f\xa6\xbe\xf5"
+ b"\xee\xa4\xac\xd5\xa9\x09\x04\x52\x14\x42\x96\xe5\x94"
+ b"\x99\x2e\x75\x2b\xcd\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc8\x00"
+ b"\x00\x00\x60\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00"
+ b"\x00\xf2\x44\x52\x6d\x12\x27\xec\x11\xbe\xa7\xcb\x3f"
+ b"\xdb\x95\xc7\x86\x4a\x33\x4f\xcc\x63\xc5\xeb\x11\x8f"
+ b"\x88\x9f\x7a\xc7\x6c\x6f\x0c\x03\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x44\x52\x41\x4d\x2d\x43\x48\x41"
+ b"\x4e\x4e\x45\x4c\x53\x00\x00\x00\x00\x20\x00\x00\x03"
+ b"\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\xbe"
+ b"\x02\x04\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00"
+ b"\x00\x04\xbe\x02\x04\x00\x10\x00\x00\xff\xff\xff\xff"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ )
+
+ result = cper.parse(cper_raw)
+ print(json.dumps(result, indent=4))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pycper.c b/pycper.c
new file mode 100644
index 0000000..5c5ec22
--- /dev/null
+++ b/pycper.c
@@ -0,0 +1,125 @@
+#if LIBCPER_PYTHON
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <libcper/cper-parse-str.h>
+#include <libcper/cper-parse.h>
+
+PyObject *convert_to_pydict(json_object *jso)
+{
+ PyObject *ret = Py_None;
+ enum json_type type = json_object_get_type(jso);
+ //printf("type: %d ", type);
+ switch (type) {
+ case json_type_null:
+ //printf("json_type_null\n");
+ ret = Py_None;
+ break;
+ case json_type_boolean: {
+ //printf("json_type_boolean\n");
+ int b = json_object_get_boolean(jso);
+ ret = PyBool_FromLong(b);
+ } break;
+ case json_type_double: {
+ //printf("json_type_double\n");
+ double d = json_object_get_double(jso);
+ ret = PyFloat_FromDouble(d);
+ } break;
+
+ case json_type_int: {
+ //printf("json_type_int\n");
+ int64_t i = json_object_get_int64(jso);
+ ret = PyLong_FromLong(i);
+ } break;
+ case json_type_object: {
+ //printf("json_type_object\n");
+ ret = PyDict_New();
+
+ json_object_object_foreach(jso, key1, val)
+ {
+ PyObject *pyobj = convert_to_pydict(val);
+ if (key1 != NULL) {
+ //printf("Parsing %s\n", key1);
+ if (pyobj == NULL) {
+ pyobj = Py_None;
+ }
+ PyDict_SetItemString(ret, key1, pyobj);
+ }
+ }
+ } break;
+ case json_type_array: {
+ //printf("json_type_array\n");
+ ret = PyList_New(0);
+ int arraylen = json_object_array_length(jso);
+
+ for (int i = 0; i < arraylen; i++) {
+ //printf("Parsing %d\n", i);
+ json_object *val = json_object_array_get_idx(jso, i);
+ PyObject *pyobj = convert_to_pydict(val);
+ if (pyobj == NULL) {
+ pyobj = Py_None;
+ }
+ PyList_Append(ret, pyobj);
+ }
+ } break;
+ case json_type_string: {
+ //printf("json_type_string\n");
+ const char *strval = json_object_get_string(jso);
+ ret = PyUnicode_FromString(strval);
+ } break;
+ }
+ return ret;
+}
+
+static PyObject *parse(PyObject *self, PyObject *args)
+{
+ (void)self;
+ PyObject *ret;
+ const unsigned char *data;
+ Py_ssize_t count;
+
+ if (!PyArg_ParseTuple(args, "y#", &data, &count)) {
+ return NULL;
+ }
+
+ char *jstrout = cperbuf_to_str_ir(data, count);
+ if (jstrout == NULL) {
+ free(jstrout);
+ return NULL;
+ }
+ json_object *jout = cper_buf_to_ir(data, count);
+ if (jout == NULL) {
+ free(jstrout);
+ free(jout);
+ return NULL;
+ }
+
+ ret = convert_to_pydict(jout);
+
+ //ret = PyUnicode_FromString(jstrout);
+ free(jout);
+ free(jstrout);
+ return ret;
+}
+
+static PyMethodDef methods[] = {
+ { "parse", (PyCFunction)parse, METH_VARARGS, NULL },
+ { NULL, NULL, 0, NULL },
+};
+
+static struct PyModuleDef module = {
+ PyModuleDef_HEAD_INIT,
+ "cper",
+ NULL,
+ -1,
+ methods,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+PyMODINIT_FUNC PyInit_cper(void)
+{
+ return PyModule_Create(&module);
+}
+#endif
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..2116d15
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,35 @@
+[build-system]
+build-backend = 'mesonpy'
+requires = ['meson-python']
+
+[project]
+name = 'cper'
+version = '0.0.4'
+description = 'Package for decoding CPER values'
+license = {file = 'LICENSE'}
+authors = [
+ {name = 'Ed Tanous', email = 'ed@tanous.net'},
+]
+requires-python = ">3.10"
+
+[tool.meson-python.args]
+setup = [
+ '-Ddefault_library=static',
+ '-Dinstall=disabled',
+ '-Dpkgconfig=disabled',
+ '-Dpython=enabled',
+ '-Dtests=disabled',
+ '-Dtests=disabled',
+ '-Dutility=disabled',
+]
+install = ['--skip-subprojects']
+
+[tool.cibuildwheel]
+archs = "auto"
+skip = "*pp*"
+manylinux-x86_64-image = "musllinux_1_2"
+manylinux-i686-image = "musllinux_1_2"
+manylinux-aarch64-image = "musllinux_1_2"
+manylinux-ppc64le-image = "musllinux_1_2"
+manylinux-s390x-image = "musllinux_1_2"
+manylinux-armv7l-image = "musllinux_1_2"
diff --git a/subprojects/json-c.wrap b/subprojects/json-c.wrap
index e68e6ef..a633336 100644
--- a/subprojects/json-c.wrap
+++ b/subprojects/json-c.wrap
@@ -1,7 +1,8 @@
[wrap-git]
-revision = json-c-0.18-20240915
+revision = HEAD
url = https://github.com/json-c/json-c.git
patch_directory = json-c
[provide]
json-c = json_c_dep
+
diff --git a/subprojects/packagefiles/json-c/config/meson.build b/subprojects/packagefiles/json-c/config/meson.build
new file mode 100644
index 0000000..0c57785
--- /dev/null
+++ b/subprojects/packagefiles/json-c/config/meson.build
@@ -0,0 +1,110 @@
+conf = configuration_data()
+check_headers = [
+ 'dlfcn.h',
+ 'endian.h',
+ 'fcntl.h',
+ 'float.h',
+ 'inttypes.h',
+ 'limits.h',
+ 'memory.h',
+ 'stdarg.h',
+ 'stdint.h',
+ 'stdlib.h',
+ 'strings.h',
+ 'string.h',
+ 'syslog.h',
+ 'sys/cdefs.h',
+ 'sys/param.h',
+ 'sys/stat.h',
+ 'sys/types.h',
+ 'unistd.h',
+ 'xlocale.h',
+]
+
+foreach header : check_headers
+ if cc.has_header(header)
+ conf.set('HAVE_@0@'.format(header.underscorify().to_upper()), 1)
+ endif
+endforeach
+
+have_stdc = true
+foreach header : ['stdlib.h', 'stdarg.h', 'string.h', 'float.h']
+ if not conf.has('HAVE_@0@'.format(header.underscorify().to_upper()))
+ have_stdc = false
+ endif
+endforeach
+conf.set10('STDC_HEADERS', have_stdc)
+
+foreach symbol : ['_isnan', '_finite']
+ if cc.has_header_symbol('float.h', symbol)
+ conf.set('HAVE_DECL_@0@'.format(symbol.to_upper()), 1)
+ endif
+endforeach
+
+foreach symbol : ['INFINITY', 'isinf', 'isnan', 'nan']
+ if cc.has_header_symbol('math.h', symbol)
+ conf.set('HAVE_DECL_@0@'.format(symbol.to_upper()), 1)
+ endif
+endforeach
+
+check_function = [
+ 'vasprintf',
+ 'realloc',
+ 'strcasecmp',
+ 'strdup',
+ 'strerror',
+ 'vsyslog',
+ 'open',
+ 'strtoll',
+]
+
+if conf.has('HAVE_STRINGS_H')
+ check_function += 'strncasecmp'
+endif
+
+foreach function : check_function
+ if cc.has_function(function)
+ conf.set('HAVE_@0@'.format(function.to_upper()), 1)
+ endif
+endforeach
+conf.set10('HAVE_DOPRNT', cc.has_function('_doprnt'))
+
+foreach f : ['snprintf', 'vsnprintf', 'vprintf']
+ if cc.has_header_symbol('stdio.h', f)
+ conf.set('HAVE_@0@'.format(f.to_upper()), 1)
+ endif
+endforeach
+
+size = cc.sizeof('size_t', prefix: '#include <stddef.h>')
+conf.set('SIZEOF_SIZE_T', size)
+
+if cc.get_argument_syntax() == 'msvc'
+ size = cc.sizeof('SSIZE_T', prefix: '#include <BaseTsd.h>')
+else
+ size = cc.sizeof('ssize_t', prefix: '#include <sys/types.h>')
+endif
+conf.set('SIZEOF_SSIZE_T', size)
+
+foreach type : ['int', 'int64_t', 'long', 'long long']
+ size = cc.sizeof(type, prefix: '#include <stdint.h>')
+ conf.set('SIZEOF_@0@'.format(type.underscorify().to_upper()), size)
+endforeach
+
+if cc.links('int main(){__sync_synchronize();}', name: 'atomic builtins')
+ conf.set('HAVE_ATOMIC_BUILTINS', 1)
+endif
+
+if cc.compiles('static __thread int x = 0;', name: '__thread')
+ conf.set('HAVE___THREAD', 1)
+endif
+
+if conf.has('HAVE___THREAD')
+ conf.set('SPEC___THREAD', '__thread')
+elif cc.get_argument_syntax() == 'msvc'
+ conf.set('SPEC___THREAD', '__declspec(thread)')
+endif
+
+conf.set_quoted('VERSION', meson.project_version())
+
+config_h = configure_file(configuration: conf, output: 'config.h')
+
diff --git a/subprojects/packagefiles/json-c/meson.build b/subprojects/packagefiles/json-c/meson.build
new file mode 100644
index 0000000..c2d1c03
--- /dev/null
+++ b/subprojects/packagefiles/json-c/meson.build
@@ -0,0 +1,111 @@
+project(
+ 'json-c',
+ 'c',
+ version: '0.18',
+ license: 'MIT',
+ meson_version: '>=0.49',
+ default_options: [
+ meson.version().version_compare('>=1.3.0') ? 'c_std=gnu99,c99' : 'c_std=gnu99',
+ ],
+)
+
+cc = meson.get_compiler('c')
+
+if cc.get_argument_syntax() == 'msvc'
+ add_project_arguments('-D_CRT_SECURE_NO_WARNINGS', language: 'c')
+else
+ add_project_arguments('-D_GNU_SOURCE', language: 'c')
+endif
+
+if get_option('default_library') != 'static'
+ add_project_arguments('-DJSON_C_DLL', language: 'c')
+endif
+
+add_project_arguments(
+ cc.get_supported_arguments('-Wno-deprecated-declarations'),
+ language: 'c',
+)
+
+threads = dependency('threads', required: false)
+math = cc.find_library('m', required: false)
+
+subdir('config')
+inc = include_directories('config')
+
+json_config = configuration_data()
+if conf.has('HAVE_INTTYPES_H')
+ json_config.set('JSON_C_HAVE_INTTYPES_H', 1)
+endif
+json_config_h = configure_file(
+ configuration: json_config,
+ output: 'json_config.h',
+)
+
+jconf = configuration_data()
+jconf.set('JSON_H_JSON_POINTER', '#include "json_pointer.h"')
+jconf.set('JSON_H_JSON_PATCH', '#include "json_patch.h"')
+
+json_h = configure_file(
+ input: 'json.h.cmakein',
+ output: 'json.h',
+ format: 'cmake@',
+ configuration: jconf,
+)
+
+public_headers = [
+ json_config_h,
+ json_h,
+ 'arraylist.h',
+ 'debug.h',
+ 'json_c_version.h',
+ 'json_inttypes.h',
+ 'json_object.h',
+ 'json_pointer.h',
+ 'json_tokener.h',
+ 'json_util.h',
+ 'linkhash.h',
+ 'printbuf.h',
+]
+
+sources = files(
+ 'arraylist.c',
+ 'debug.c',
+ 'json_c_version.c',
+ 'json_object.c',
+ 'json_object_iterator.c',
+ 'json_pointer.c',
+ 'json_tokener.c',
+ 'json_util.c',
+ 'json_visit.c',
+ 'linkhash.c',
+ 'printbuf.c',
+ 'random_seed.c',
+ 'strerror_override.c',
+)
+
+libjson_c = library(
+ 'json-c',
+ sources,
+ include_directories: inc,
+ install: true,
+ version: '5.4.0',
+)
+
+json_c_dep = declare_dependency(
+ link_with: libjson_c,
+ include_directories: include_directories('.', '..'),
+ dependencies: [math, threads],
+ sources: json_config_h,
+)
+
+install_headers(public_headers, subdir: 'json-c')
+
+pkgconfig = import('pkgconfig')
+pkgconfig.generate(
+ libjson_c,
+ description: 'A JSON implementation in C',
+ version: meson.project_version(),
+ filebase: meson.project_name(),
+ name: meson.project_name(),
+ subdirs: 'json-c',
+)
diff --git a/subprojects/packagefiles/jsoncdac/meson.build b/subprojects/packagefiles/jsoncdac/meson.build
index f7af58f..b47b676 100644
--- a/subprojects/packagefiles/jsoncdac/meson.build
+++ b/subprojects/packagefiles/jsoncdac/meson.build
@@ -23,7 +23,7 @@
add_project_arguments('-Wno-unused-parameter', language: 'c')
add_project_arguments('-Wformat=0', language: 'c')
-jsonc = dependency('json-c')
+jsonc = dependency('json-c', static: true)
deps += jsonc
jsoncdac_sources = files(
@@ -50,10 +50,15 @@
deps += m_dep
endif
-jsoncdac = library('jsoncdac', jsoncdac_sources, dependencies: deps)
+jsoncdac = library(
+ 'jsoncdac',
+ jsoncdac_sources,
+ dependencies: deps,
+ include_directories: include_directories('..'),
+)
jsoncdac_dep = declare_dependency(
link_with: jsoncdac,
dependencies: deps,
- include_directories: include_directories('include'),
+ include_directories: include_directories('include', '..'),
)