Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .audit/oberstet_vendored-flatbuffers-improvements.md
Original file line number Diff line number Diff line change
@@ -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
130 changes: 130 additions & 0 deletions hatch_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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")
102 changes: 89 additions & 13 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/autobahn/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@
#
###############################################################################

__version__ = "25.12.1"
__version__ = "25.12.2"

__build__ = "00000000-0000000"
Loading