diff --git a/.audit/oberstet_vendored-flatbuffers-improvements.md b/.audit/oberstet_vendored-flatbuffers-improvements.md new file mode 100644 index 000000000..660d998a9 --- /dev/null +++ b/.audit/oberstet_vendored-flatbuffers-improvements.md @@ -0,0 +1,8 @@ +- [ ] I did **not** use any AI-assistance tools to help create this pull request. +- [x] I **did** use AI-assistance tools to *help* create this pull request. +- [x] I have read, understood and followed the project's AI_POLICY.md when creating code, documentation etc. for this pull request. + +Submitted by: @oberstet +Date: 2025-12-11 +Related issue(s): #1810 +Branch: oberstet:vendored-flatbuffers-improvements diff --git a/deps/flatbuffers b/deps/flatbuffers index 95053e6a4..187240970 160000 --- a/deps/flatbuffers +++ b/deps/flatbuffers @@ -1 +1 @@ -Subproject commit 95053e6a479d22ad0817dca3d992fb16c7adf5e8 +Subproject commit 187240970746d00bbd26b0f5873ed54d2477f9f3 diff --git a/hatch_build.py b/hatch_build.py index 9995e9da3..2b6302e97 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -4,10 +4,13 @@ This builds the NVX (Native Vector Extensions) for WebSocket frame masking and UTF-8 validation using CFFI. +Also captures the git version of deps/flatbuffers submodule at build time. + See: https://hatch.pypa.io/latest/plugins/build-hook/custom/ """ import os +import subprocess import sys import sysconfig import importlib.util @@ -28,6 +31,9 @@ def initialize(self, version, build_data): For wheel builds, compile the CFFI modules. For sdist builds, just ensure source files are included. """ + # Always capture flatbuffers git version (for both wheel and sdist) + self._update_flatbuffers_git_version() + if self.target_name != "wheel": # Only compile for wheel builds, sdist just includes source return @@ -128,3 +134,127 @@ def _build_cffi_modules(self, build_data): traceback.print_exc() return built_any + + def _update_flatbuffers_git_version(self): + """ + Capture the git describe version of deps/flatbuffers submodule. + + This writes the version to flatbuffers/_git_version.py and patches + __init__.py with the version() function so that + autobahn.flatbuffers.version() returns the exact git version at runtime. + """ + print("=" * 70) + print("Capturing FlatBuffers git version from deps/flatbuffers") + print("=" * 70) + + flatbuffers_dir = Path(self.root) / "deps" / "flatbuffers" + flatbuffers_dst = Path(self.root) / "src" / "autobahn" / "flatbuffers" + git_version_file = flatbuffers_dst / "_git_version.py" + init_file = flatbuffers_dst / "__init__.py" + + # Check if flatbuffers directory exists (vendored) + if not flatbuffers_dst.exists(): + print(" -> src/autobahn/flatbuffers not found, skipping") + print(" -> Run 'just vendor-flatbuffers' first") + return + + # Default version if git is not available or submodule not initialized + git_version = "unknown" + + if flatbuffers_dir.exists() and (flatbuffers_dir / ".git").exists(): + try: + result = subprocess.run( + ["git", "describe", "--tags", "--always"], + cwd=flatbuffers_dir, + capture_output=True, + text=True, + timeout=10, + ) + if result.returncode == 0: + git_version = result.stdout.strip() + print(f" -> Git version: {git_version}") + else: + print(f" -> git describe failed: {result.stderr}") + except FileNotFoundError: + print(" -> git command not found, using existing version") + return + except subprocess.TimeoutExpired: + print(" -> git describe timed out, using existing version") + return + except Exception as e: + print(f" -> Error getting git version: {e}") + return + else: + print(" -> deps/flatbuffers not found or not a git repo") + if git_version_file.exists(): + print(f" -> Using existing version in {git_version_file.name}") + return + + # Write the _git_version.py file + git_version_content = '''\ +# Copyright 2014 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Git version from deps/flatbuffers submodule. +# This file is regenerated at build time by hatch_build.py. +# The version is captured via `git describe --tags` in the submodule. +# +# Format: "v25.9.23" (tagged release) or "v25.9.23-2-g95053e6a" (post-tag) +# +# If building from sdist without git, this will retain the version +# from when the sdist was created. + +__git_version__ = "{version}" +'''.format(version=git_version) + + git_version_file.write_text(git_version_content) + print(f" -> Updated {git_version_file.name}") + + # Check if __init__.py already has version() function + init_content = init_file.read_text() + if "def version()" not in init_content: + # Patch __init__.py to add version() function + version_func = ''' +import re + +from ._git_version import __git_version__ + + +def version() -> tuple[int, int, int, int | None, str | None]: + """ + Return the exact git version of the vendored FlatBuffers runtime. + + Handles: + + 1. "v25.9.23" -> (25, 9, 23, None, None) # Release (Named Tag, CalVer Year.Month.Day) + 2. "v25.9.23-71" -> (25, 9, 23, 71, None) # 71 commits ahead of the Release v25.9.23 + 3. "v25.9.23-71-g19b2300f" -> (25, 9, 23, 71, "19b2300f") # dito, with Git commit hash + """ + + pattern = r"^v(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\d+))?(?:-g([0-9a-f]+))?$" + + match = re.match(pattern, __git_version__) + + if match: + major, minor, patch, commits, commit_hash = match.groups() + commits_int = int(commits) if commits else None + return (int(major), int(minor), int(patch), commits_int, commit_hash) + + # Fallback if regex fails entirely + return (0, 0, 0, None, None) +''' + init_file.write_text(init_content + version_func) + print(f" -> Patched {init_file.name} with version() function") + else: + print(f" -> {init_file.name} already has version() function") diff --git a/justfile b/justfile index 223186b28..e1f2135b6 100644 --- a/justfile +++ b/justfile @@ -1271,21 +1271,97 @@ docs-spelling venv="": (install-docs venv) # This avoids conflicts with the standalone 'flatbuffers' PyPI package. # The vendored copy is gitignored and must be regenerated before build. vendor-flatbuffers: - #!/usr/bin/env bash - set -e - SRC_DIR="deps/flatbuffers/python/flatbuffers" - DST_DIR="src/autobahn/flatbuffers" + #!/usr/bin/env python3 + import os + import shutil + import subprocess + from pathlib import Path + + src_dir = Path("deps/flatbuffers/python/flatbuffers") + dst_dir = Path("src/autobahn/flatbuffers") + + if not src_dir.exists(): + print("ERROR: Flatbuffers submodule not found at", src_dir) + print("Run: git submodule update --init --recursive") + raise SystemExit(1) + + print(f"==> Vendoring flatbuffers from {src_dir} to {dst_dir}...") + if dst_dir.exists(): + shutil.rmtree(dst_dir) + shutil.copytree(src_dir, dst_dir) + + # Capture git version from submodule + try: + result = subprocess.run( + ["git", "describe", "--tags", "--always"], + cwd="deps/flatbuffers", + capture_output=True, + text=True, + timeout=10 + ) + git_version = result.stdout.strip() if result.returncode == 0 else "unknown" + except Exception: + git_version = "unknown" + + print(f"==> FlatBuffers git version: {git_version}") + + # Generate _git_version.py + git_version_content = f'''\ +# Copyright 2014 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Git version from deps/flatbuffers submodule. +# This file is generated at build time by justfile vendor-flatbuffers recipe. +# The version is captured via `git describe --tags` in the submodule. +# +# Format: "v25.9.23" (tagged release) or "v25.9.23-2-g95053e6a" (post-tag) - if [ ! -d "${SRC_DIR}" ]; then - echo "ERROR: Flatbuffers submodule not found at ${SRC_DIR}" - echo "Run: git submodule update --init --recursive" - exit 1 - fi +__git_version__ = "{git_version}" +''' + (dst_dir / "_git_version.py").write_text(git_version_content) + + # Append version() function to __init__.py + init_file = dst_dir / "__init__.py" + init_content = init_file.read_text() + + version_func = ''' +import re + +from ._git_version import __git_version__ + + +def version() -> tuple[int, int, int, int | None, str | None]: + """ + Return the exact git version of the vendored FlatBuffers runtime. + + Handles: + + 1. "v25.9.23" -> (25, 9, 23, None, None) # Release + 2. "v25.9.23-71" -> (25, 9, 23, 71, None) # 71 commits ahead + 3. "v25.9.23-71-g19b2300f" -> (25, 9, 23, 71, "19b2300f") # with hash + """ + pattern = r"^v(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\d+))?(?:-g([0-9a-f]+))?$" + match = re.match(pattern, __git_version__) + if match: + major, minor, patch, commits, commit_hash = match.groups() + commits_int = int(commits) if commits else None + return (int(major), int(minor), int(patch), commits_int, commit_hash) + return (0, 0, 0, None, None) +''' + init_file.write_text(init_content + version_func) - echo "==> Vendoring flatbuffers from ${SRC_DIR} to ${DST_DIR}..." - rm -rf "${DST_DIR}" - cp -r "${SRC_DIR}" "${DST_DIR}" - echo "==> Flatbuffers vendored successfully." + print("==> Flatbuffers vendored successfully with version() function.") # Build wheel only (usage: `just build cpy314`) build venv="": vendor-flatbuffers (install-build-tools venv) diff --git a/pyproject.toml b/pyproject.toml index 8c8a9c85f..377e758a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "autobahn" -version = "25.12.1" +version = "25.12.2" description = "WebSocket client & server library, WAMP real-time framework" readme = "README.md" requires-python = ">=3.11" diff --git a/src/autobahn/_version.py b/src/autobahn/_version.py index 4972d7baa..627ae6bd9 100644 --- a/src/autobahn/_version.py +++ b/src/autobahn/_version.py @@ -24,6 +24,6 @@ # ############################################################################### -__version__ = "25.12.1" +__version__ = "25.12.2" __build__ = "00000000-0000000"