Skip to content

Commit 108cf5a

Browse files
committed
fix(build): recover python wheel build with proper versioning
1 parent 9f2c2a8 commit 108cf5a

File tree

4 files changed

+131
-116
lines changed

4 files changed

+131
-116
lines changed

BUILD.bazel

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ load("@aspect_rules_js//js:defs.bzl", "js_library")
22
load("@aspect_rules_ts//ts:defs.bzl", "ts_config")
33
load("@npm//:defs.bzl", "npm_link_all_packages")
44
load("@aspect_rules_js//npm:defs.bzl", "npm_link_package")
5+
load("@bazel_skylib//rules:write_file.bzl", "write_file")
6+
load("@aspect_bazel_lib//lib:expand_template.bzl", "expand_template")
57

68
package(default_visibility = ["//visibility:public"])
79

@@ -35,3 +37,17 @@ js_library(
3537
name = "package_json",
3638
srcs = ["package.json"],
3739
)
40+
41+
# Global version computation that can be reused across all targets
42+
write_file(
43+
name = "version_tmpl",
44+
out = "version.txt.tmpl",
45+
content = ["{{RTBOT_VERSION}}"],
46+
)
47+
48+
expand_template(
49+
name = "version",
50+
out = "version.txt",
51+
stamp_substitutions = {"{{RTBOT_VERSION}}": "{{RTBOT_VERSION}}"},
52+
template = ":version_tmpl",
53+
)

libs/wrappers/python/BUILD.bazel

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")
22
load("@rules_python//python:packaging.bzl", "py_package", "py_wheel")
33
load("//tools/generator:generator.bzl", "rtbot_generate")
44
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
5+
load("@aspect_bazel_lib//lib:expand_template.bzl", "expand_template")
6+
load("@bazel_skylib//rules:write_file.bzl", "write_file")
57
load("@py_deps//:requirements.bzl", "requirement")
68

79
package(default_visibility = ["//visibility:public"])
@@ -68,25 +70,28 @@ copy_to_directory(
6870
}),
6971
)
7072

71-
genrule(
72-
name = "version",
73-
outs = ["version.txt"],
74-
cmd = "cat bazel-out/stable-status.txt | grep '^VERSION ' | cut -d' ' -f2 > $@",
75-
stamp = 1,
76-
)
73+
# Use global version from root BUILD.bazel
7774

7875
py_wheel(
79-
name = "rtbot_wheel",
76+
name = "rtbot_wheel_base",
8077
distribution = "rtbot",
8178
python_tag = "py3",
8279
stamp = 1,
8380
strip_path_prefixes = [
8481
"libs/wrappers/python",
8582
],
86-
version = "0.1.0", # We'll need to read from version file in setup.py
83+
version = "{RTBOT_VERSION}", # Dynamic version from workspace status
8784
deps = [":copy"],
8885
)
8986

87+
genrule(
88+
name = "rtbot_wheel",
89+
srcs = [":rtbot_wheel_base", "//:version"],
90+
outs = ["rtbot.whl"],
91+
cmd = "cp $(location :rtbot_wheel_base) $(location rtbot.whl)",
92+
stamp = 1,
93+
)
94+
9095
py_test(
9196
name = "rtbot_test",
9297
srcs = ["rtbot_test.py"],

libs/wrappers/python/setup.py

Lines changed: 72 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,99 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Minimal setup.py for RtBot Python wrapper.
4+
5+
This setup.py is designed to work with pre-built artifacts from Bazel.
6+
For development builds, use: bazel build //libs/wrappers/python:rtbot_wheel
7+
"""
8+
19
import os
2-
import platform
3-
import subprocess
410
import sys
5-
import tempfile
6-
import shutil
7-
from setuptools import setup, Extension
8-
from setuptools.command.build_ext import build_ext
9-
from setuptools.command.egg_info import egg_info
10-
11-
RTBOT_REPO = "https://github.com/rtbot-dev/rtbot.git"
11+
from setuptools import setup, find_packages
1212

1313
def get_version():
14+
"""Get version from git tags, with dev suffix if ahead of latest tag."""
15+
import subprocess
16+
1417
try:
15-
output = subprocess.check_output(
16-
['bash', '-c', "grep '^VERSION ' dist/out/stable-status.txt | cut -d' ' -f2"],
18+
# Get the latest git tag
19+
latest_tag = subprocess.check_output(
20+
['git', 'describe', '--tags', '--abbrev=0'],
1721
stderr=subprocess.PIPE
1822
).decode().strip()
19-
return output if output else "0.1.0"
20-
except:
21-
return "0.1.0"
22-
23-
class BazelExtension(Extension):
24-
def __init__(self, name):
25-
super().__init__(name, sources=[])
2623

27-
class CustomEggInfo(egg_info):
28-
def run(self):
29-
os.makedirs('rtbot', exist_ok=True)
30-
super().run()
24+
# Remove 'v' prefix if present
25+
if latest_tag.startswith('v'):
26+
latest_tag = latest_tag[1:]
3127

32-
class BazelBuildExt(build_ext):
33-
def run(self):
34-
self._install_bazelisk()
35-
repo_dir = self._get_repo_dir()
36-
self._build_rtbot(repo_dir)
37-
38-
def _get_repo_dir(self):
39-
current_dir = os.path.abspath(os.getcwd())
40-
while current_dir != '/':
41-
if os.path.exists(os.path.join(current_dir, 'WORKSPACE')):
42-
return current_dir
43-
current_dir = os.path.dirname(current_dir)
44-
45-
tmp_dir = tempfile.mkdtemp()
46-
self._clone_repo(tmp_dir)
47-
return tmp_dir
28+
# Get current commit description
29+
git_describe = subprocess.check_output(
30+
['git', 'describe', '--tags', '--always'],
31+
stderr=subprocess.PIPE
32+
).decode().strip()
4833

49-
def _install_bazelisk(self):
50-
try:
51-
subprocess.check_call(['bazelisk', '--version'])
52-
return
53-
except (subprocess.CalledProcessError, FileNotFoundError):
54-
pass
34+
# If we're exactly on a tag, use that version
35+
if git_describe == f'v{latest_tag}' or git_describe == latest_tag:
36+
return latest_tag
5537

56-
print("Installing bazelisk...")
57-
system = platform.system().lower()
58-
arch = platform.machine().lower()
59-
60-
if system == 'windows':
61-
url = f"https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-windows-{arch}.exe"
62-
out = "bazelisk.exe"
63-
else:
64-
url = f"https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-{system}-{arch}"
65-
out = "bazelisk"
66-
67-
subprocess.check_call(['curl', '-L', url, '-o', out])
68-
if system != 'windows':
69-
os.chmod(out, 0o755)
70-
71-
os.environ['PATH'] = os.getcwd() + os.pathsep + os.environ['PATH']
38+
# If we're ahead of the tag, create a dev version
39+
# Format: v0.3.8-135-gc6ee14a -> 0.3.8.dev135+gc6ee14a
40+
parts = git_describe.split('-')
41+
if len(parts) >= 3:
42+
tag_part = parts[0]
43+
if tag_part.startswith('v'):
44+
tag_part = tag_part[1:]
45+
commits_ahead = parts[1]
46+
short_sha = parts[2]
47+
return f"{tag_part}.dev{commits_ahead}+{short_sha}"
7248

73-
def _clone_repo(self, tmp_dir):
74-
print("Cloning RTBot repository...")
75-
subprocess.check_call(['git', 'clone', '--depth', '1', RTBOT_REPO, tmp_dir])
49+
# Fallback: just use the tag
50+
return latest_tag
7651

77-
def _get_bazel_bin(self, repo_dir):
78-
bazel_cmd = 'bazelisk' if platform.system().lower() != 'windows' else 'bazelisk.exe'
52+
except (subprocess.CalledProcessError, FileNotFoundError, IndexError):
53+
# Fallback to reading from Bazel build if git fails
7954
try:
80-
bazel_bin = subprocess.check_output(
81-
[bazel_cmd, 'info', 'bazel-bin'],
82-
cwd=repo_dir,
83-
text=True
84-
).strip()
85-
return bazel_bin
86-
except subprocess.CalledProcessError:
87-
return os.path.join(repo_dir, 'dist') # Fallback to --symlink_prefix value
88-
89-
def _build_rtbot(self, repo_dir):
90-
print("Building RTBot...")
91-
bazel_cmd = 'bazelisk' if platform.system().lower() != 'windows' else 'bazelisk.exe'
92-
93-
subprocess.check_call(
94-
[bazel_cmd, 'build', '//libs/wrappers/python:rtbotapi.so', '//libs/wrappers/python:copy'],
95-
cwd=repo_dir
96-
)
97-
98-
bazel_bin = self._get_bazel_bin(repo_dir)
99-
package_dir = os.path.join(self.build_lib, 'rtbot')
100-
os.makedirs(package_dir, exist_ok=True)
55+
version_file = "dist/bin/libs/wrappers/python/version.txt"
56+
if os.path.exists(version_file):
57+
with open(version_file, 'r') as f:
58+
return f.read().strip()
59+
except:
60+
pass
61+
return "0.1.0"
10162

102-
# Copy files from bazel-bin
103-
copy_dir = os.path.join(bazel_bin, 'libs/wrappers/python/rtbot')
104-
for item in ['MANIFEST.in', 'README.md', 'operators.py', 'setup.py', '__init__.py']:
105-
src = os.path.join(copy_dir, item)
106-
if os.path.exists(src):
107-
shutil.copy2(src, package_dir)
63+
# Check if we're in development mode (rtbotapi.so exists locally)
64+
has_extension = os.path.exists("rtbotapi.so") or os.path.exists("rtbotapi.pyd")
10865

109-
# Copy the extension
110-
ext_path = os.path.join(bazel_bin, 'libs/wrappers/python/rtbotapi.so')
111-
if platform.system().lower() == 'windows':
112-
ext_path = ext_path.replace('.so', '.pyd')
113-
114-
if os.path.exists(ext_path):
115-
shutil.copy2(ext_path, package_dir)
116-
else:
117-
raise RuntimeError(f"Built extension not found at {ext_path}")
66+
if not has_extension:
67+
print("Error: rtbot extension not found.")
68+
print("This package requires pre-built artifacts from Bazel.")
69+
print("Run: bazel build //libs/wrappers/python:copy")
70+
print("Then copy the artifacts to this directory before running setup.py")
71+
sys.exit(1)
11872

11973
setup(
12074
name='rtbot',
12175
version=get_version(),
122-
description='Python bindings for RTBot framework',
123-
author='RTBot Developers',
76+
description='Python bindings for RtBot framework',
77+
long_description='RtBot is a real-time data processing framework with Python bindings.',
78+
long_description_content_type='text/plain',
79+
author='RtBot Developers',
12480
url='https://github.com/rtbot-dev/rtbot',
125-
ext_modules=[BazelExtension('rtbot')],
126-
cmdclass={
127-
'build_ext': BazelBuildExt,
128-
'egg_info': CustomEggInfo,
81+
packages=find_packages(),
82+
package_data={
83+
'rtbot': ['*.so', '*.pyd', '*.py'],
12984
},
130-
packages=['rtbot'],
13185
install_requires=[
13286
"pandas>=1.0.0"
13387
],
13488
python_requires='>=3.10',
89+
classifiers=[
90+
'Development Status :: 3 - Alpha',
91+
'Intended Audience :: Developers',
92+
'License :: OSI Approved :: Apache Software License',
93+
'Programming Language :: Python :: 3',
94+
'Programming Language :: Python :: 3.10',
95+
'Programming Language :: Python :: 3.11',
96+
'Programming Language :: Python :: 3.12',
97+
'Programming Language :: Python :: 3.13',
98+
],
13599
)

tools/bazel_workspace_status.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,33 @@ echo "CURRENT_TIME $(date +%s)"
2727
echo "STABLE_GIT_COMMIT $(git rev-parse HEAD)"
2828
echo "STABLE_SHORT_GIT_COMMIT $(git rev-parse HEAD | cut -c 1-8)"
2929
echo "STABLE_USER_NAME $USER"
30+
31+
# Generate dynamic version for RtBot Python wheel
32+
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
33+
LATEST_TAG=$(git describe --tags --abbrev=0)
34+
GIT_DESCRIBE=$(git describe --tags --always)
35+
36+
# Remove 'v' prefix if present
37+
LATEST_TAG=${LATEST_TAG#v}
38+
39+
# If we're exactly on a tag, use that version
40+
if [ "$GIT_DESCRIBE" = "v$LATEST_TAG" ] || [ "$GIT_DESCRIBE" = "$LATEST_TAG" ]; then
41+
RTBOT_VERSION="$LATEST_TAG"
42+
else
43+
# Parse format: v0.3.8-135-gc6ee14a -> 0.3.8.dev135+gc6ee14a
44+
TAG_PART=$(echo "$GIT_DESCRIBE" | cut -d'-' -f1)
45+
TAG_PART=${TAG_PART#v}
46+
COMMITS_AHEAD=$(echo "$GIT_DESCRIBE" | cut -d'-' -f2)
47+
SHORT_SHA=$(echo "$GIT_DESCRIBE" | cut -d'-' -f3)
48+
49+
if [ -n "$TAG_PART" ] && [ -n "$COMMITS_AHEAD" ] && [ -n "$SHORT_SHA" ]; then
50+
RTBOT_VERSION="${TAG_PART}.dev${COMMITS_AHEAD}+${SHORT_SHA}"
51+
else
52+
RTBOT_VERSION="$LATEST_TAG"
53+
fi
54+
fi
55+
else
56+
RTBOT_VERSION="0.1.0"
57+
fi
58+
59+
echo "RTBOT_VERSION $RTBOT_VERSION"

0 commit comments

Comments
 (0)