Port to libyang v3

...along with a bunch of incompatible API/ABI changes.

I had to make a few tweaks in the build process as well, and these
couldn't be made (and tested!) with separate commits. The test suite was
failing on OS X 13, so I decided to upgrade, because "why not".
This commit is contained in:
Jan Kundrát
2025-04-02 16:19:10 -07:00
parent ee67ef49c5
commit 32b9ff8813
8 changed files with 59 additions and 43 deletions

View File

@@ -78,19 +78,19 @@ jobs:
wheel: win_amd64
python-version: "3.12"
- name: "macOS 13 x86_64 Python 3.12"
os: macos-13
- name: "macOS 15 x86_64 x86_64 Python 3.13"
os: macos-15-large
build_type: Release
generators: Ninja
wheel: macosx_13_0_x86_64
python-version: "3.12"
wheel: macosx_15_0_x86_64
python-version: "3.13"
- name: "macOS 13 arm64 Python 3.12"
os: macos-13-xlarge
- name: "macOS 15 arm64 Python 3.13"
os: macos-15-xlarge
build_type: Release
generators: Ninja
wheel: macosx_13_0_arm64
python-version: "3.12"
wheel: macosx_15_0_arm64
python-version: "3.13"
steps:
- name: Unix line endings in git
@@ -177,19 +177,18 @@ jobs:
echo CC=gcc-10 >> $GITHUB_ENV
echo CXX=g++-10 >> $GITHUB_ENV
# FIXME: this cannot be GCC 13 because of https://github.com/actions/runner-images/issues/9997
- name: GCC 12 on Mac OS 13
- name: GCC 14 on Mac OS 15
if: startsWith(matrix.os, 'macos-13')
shell: bash
run: |
echo CC=gcc-12 >> $GITHUB_ENV
echo CXX=g++-12 >> $GITHUB_ENV
echo CC=gcc-14 >> $GITHUB_ENV
echo CXX=g++-14 >> $GITHUB_ENV
- name: Mac OS 13 deployment target
if: startsWith(matrix.os, 'macos-13')
- name: Mac OS 15 deployment target
if: startsWith(matrix.os, 'macos-15')
shell: bash
run: |
echo MACOSX_DEPLOYMENT_TARGET=13.0 >> $GITHUB_ENV
echo MACOSX_DEPLOYMENT_TARGET=15.0 >> $GITHUB_ENV
- name: Mac OS ARCHFLAGS x86_64
if: startsWith(matrix.os, 'macos') && endsWith(matrix.wheel, '_x86_64')
@@ -234,14 +233,6 @@ jobs:
echo REPAIR_LIBRARY_PATH=${{ github.workspace }}/../target/lib >> $GITHUB_ENV
echo PKG_CONFIG_PATH=${{ github.workspace }}/../target/lib/pkgconfig >> $GITHUB_ENV
# FIXME: linker is FUBAR on this platform and/or version of XCode
# https://forums.developer.apple.com/forums/thread/737707
# https://github.com/actions/runner-images/issues/9273
# https://github.com/Homebrew/homebrew-core/issues/145991
- name: Unbreak XCode linker
if: startswith(matrix.os, 'macos-13')
run: sudo xcode-select -switch /Applications/Xcode_14.3.1.app
# FIXME: CMake 4.0 is backwards-incompatible, https://github.com/actions/runner-images/issues/11926
- name: pin CMake to the latest 3.x series
uses: jwlawson/actions-setup-cmake@09fd9b0fb3b239b4b68d9256cd65adf8d6b91da0
@@ -501,15 +492,15 @@ jobs:
wheel: win_amd64
python-version: "3.12"
- name: "macOS 13 x86_64 Python 3.12"
os: macos-13
wheel: macosx_13_0_x86_64
python-version: "3.12"
- name: "macOS 15 x86_64 Python 3.13"
os: macos-15-large
wheel: macosx_15_0_x86_64
python-version: "3.13"
- name: "macOS 13 arm64 Python 3.12"
os: macos-13-xlarge
wheel: macosx_13_0_arm64
python-version: "3.12"
- name: "macOS 13 arm64 Python 3.13"
os: macos-15-xlarge
wheel: macosx_15_0_arm64
python-version: "3.13"
steps:
- uses: actions/checkout@v3

View File

@@ -17,7 +17,7 @@ if(NOT MSVC)
endif()
find_package(PkgConfig)
pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=1.0.0 libyang)
pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=4 libyang)
set(PYBIND11_FINDPYTHON ON)
find_package(Python 3.8 REQUIRED COMPONENTS Interpreter Development.Module)

View File

@@ -68,7 +68,7 @@ for x in data.siblings():
for xx in x.children_dfs():
print(f' {"term " if xx.is_term else "child"}: {xx.path}')
if xx.is_term:
print(f' {xx.as_term()} {" (default)" if xx.as_term().is_default_value else ""}')
print(f' {xx.as_term()} {" (default)" if xx.as_term().has_default_value else ""}')
```
Data can be accessed via their known paths, of course. Either as a full, multi-level XPath:
@@ -119,7 +119,9 @@ try:
assert False
except ly.Error:
for error in c.errors():
assert error.path == "Schema location \"/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/address/prefix-length\", data location \"/ietf-ip:address[ip='::1']\", line number 1."
assert error.schema_path is None
assert error.data_path == "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6/address[ip='::1']/prefix-length"
assert error.line > 0
assert error.message == 'Value "666" is out of type uint8 min/max bounds.'
```

Submodule libyang updated: fbcee29952...ce38b35612

View File

@@ -167,7 +167,9 @@ PYBIND11_MODULE(oopt_gnpy_libyang, m) {
.def_readonly("level", &ErrorInfo::level)
.def_readonly("message", &ErrorInfo::message)
.def_readonly("code", &ErrorInfo::code)
.def_readonly("path", &ErrorInfo::path)
.def_readonly("data_path", &ErrorInfo::dataPath)
.def_readonly("schema_path", &ErrorInfo::schemaPath)
.def_readonly("line", &ErrorInfo::line)
.def_readonly("validation_code", &ErrorInfo::validationCode)
.def("__repr__", [](const ErrorInfo& e) {
std::ostringstream ss;
@@ -176,7 +178,9 @@ PYBIND11_MODULE(oopt_gnpy_libyang, m) {
ss << ", code = " << e.code;
ss << ", validation_code = " << e.validationCode;
ss << ", message = " << repr_optional_string(e.message);
ss << ", path = " << repr_optional_string(e.path);
ss << ", data_path = " << repr_optional_string(e.dataPath);
ss << ", schema_path = " << repr_optional_string(e.schemaPath);
ss << ", line = " << e.line;
ss << ", app_tag = " << repr_optional_string(e.appTag);
ss << ")";
return ss.str();
@@ -219,7 +223,8 @@ PYBIND11_MODULE(oopt_gnpy_libyang, m) {
;
py::class_<DataNodeTerm, DataNode>(m, "DataNodeTerm")
.def_property("is_default_value", &DataNodeTerm::isDefaultValue, nullptr)
.def_property("has_default_value", &DataNodeTerm::hasDefaultValue, nullptr)
.def_property("is_implicit_default", &DataNodeTerm::isImplicitDefault, nullptr)
.def_property("value", &DataNodeTerm::value, nullptr)
.def("__str__", &DataNodeTerm::valueStr)
;

View File

@@ -27,10 +27,13 @@ def test_features(context_no_libyang):
m = context_no_libyang.load_module('ietf-interfaces')
with pytest.raises(ly.Error, match="Couldn't set module 'ietf-interfaces' to implemented: LY_EINVAL"):
m.set_implemented_with_features(['arbotrary-names'])
errors = [(e.level, e.code, e.message, e.path, e.validation_code, e.app_tag) for e in context_no_libyang.errors()]
errors = [
(e.level, e.code, e.message, e.data_path, e.schema_path, e.line, e.validation_code, e.app_tag)
for e in context_no_libyang.errors()
]
assert errors == [
(ly.LogLevel.Error, ly.ErrorCode.InvalidValue, 'Feature "arbotrary-names" not found in module "ietf-interfaces".',
None, ly.ValidationErrorCode.Success, None)
None, None, 0, ly.ValidationErrorCode.Success, None)
]
m.set_implemented_with_features(['arbitrary-names'])
for feature in m.features:
@@ -54,5 +57,5 @@ def test_explicit_loading(context_no_libyang):
def test_version():
assert len(ly.libyang_version_info()) == 3
assert ly.libyang_version_info()[0] == 2
assert ly.libyang_version()[0:2] == '2.'
assert ly.libyang_version_info()[0] == 3
assert ly.libyang_version()[0:2] == '3.'

View File

@@ -1,3 +1,4 @@
import json
import pytest
import oopt_gnpy_libyang as ly
@@ -192,6 +193,20 @@ def test_ietf_interfaces(context_with_modules):
assert "/ietf-interfaces:interfaces/interface[name='42']" in separate_node
context_with_modules.create("/ietf-interfaces:interfaces/interface[name='666']")
wrong = json.loads(blob)
wrong["ietf-interfaces:interfaces"]["interface"][0]["ietf-ip:ipv6"]["address"][0]["prefix-length"] = 666
try:
data = context_with_modules.parse_data(json.dumps(wrong, indent=2),
ly.DataFormat.JSON, ly.ParseOptions.Strict | ly.ParseOptions.Ordered,
ly.ValidationOptions.Present | ly.ValidationOptions.NoState)
assert False
except ly.Error:
for error in context_with_modules.errors():
assert error.schema_path is None
assert error.data_path == "/ietf-interfaces:interfaces/interface[name='lo']/ietf-ip:ipv6/address[ip='::1']/prefix-length"
assert error.line == 20
assert error.message == 'Value "666" is out of type uint8 min/max bounds.'
def test_types(context_no_libyang):
context_no_libyang.parse_module('''
module dummy {