Skip to content

Commit e47a0b9

Browse files
authored
Merge pull request #45 from boschglobal/fix/proto_gen
Fix proto generation from setup.py
2 parents cdf8f82 + 36d2ea3 commit e47a0b9

7 files changed

Lines changed: 180 additions & 16 deletions

File tree

.github/workflows/kuksa-client.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ jobs:
141141
run: |
142142
cd kuksa-client
143143
pip install -r requirements.txt -r test-requirements.txt
144-
python3 -m proto
144+
python3 -m prototagandcopy
145+
python3 -m protobuild
145146
pip install -e .
146147
- name: Run tests
147148
run: |

docs/building.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,20 @@ python3 -m venv ~/.venv/kuksa-client
2121
source ~/.venv/kuksa-client/bin/activate # Run this every time you want to activate kuksa-client's virtual environment
2222
```
2323

24-
To use the right api interfaces of databroker run the following:
24+
To use the right api interfaces of databroker run the following
25+
2526
```console
26-
python3 -m proto
27+
python3 -m prototagandcopy
2728
```
29+
2830
This should copy the corresponding proto files to the kuksa-client directory.
2931

32+
You still might need to compile the proto files, to do so do
33+
34+
```console
35+
python3 -m protobuild
36+
```
37+
3038
Your prompt should change to somehting indicating you are in the virutal environment now, e.g.
3139

3240
```console

kuksa-client/Dockerfile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# Note: This dockerfile needs to be executed one level above in the root folder
1010

1111

12-
FROM python:3.10-slim-bookworm as build
12+
FROM python:3.10-slim-bookworm AS build
1313
# binutils is required by pyinstaller to strip any .so libs that are collected
1414
# git is used to determine & embed version information during build time
1515
RUN apt update && apt -yy install binutils git
@@ -19,8 +19,6 @@ RUN pip install --upgrade pip build pyinstaller
1919
COPY . /kuksa-python-sdk/
2020
WORKDIR /kuksa-python-sdk/kuksa-client
2121
RUN git submodule update --recursive --remote --init
22-
# install files from submodules to kuksa-client repo to generate protos out of it
23-
RUN python3 -m proto
2422

2523
RUN python3 -m build
2624
# We install globally on build container, so pyinstaller can easily gather all files
@@ -29,7 +27,7 @@ RUN pip install --no-cache-dir dist/*.whl
2927
WORKDIR /
3028
RUN rm -rf dist
3129
# Letting pyinstaller collect everything that is required
32-
RUN pyinstaller --collect-data kuksa_client --clean -s /usr/local/bin/kuksa-client
30+
RUN pyinstaller --collect-data kuksa_client --add-data=/kuksa-python-sdk/kuksa-client/kuksa:kuksa --add-data=/kuksa-python-sdk/kuksa-client/sdv:sdv --clean -s /usr/local/bin/kuksa-client
3331

3432

3533
# Debian 12 is bookworm, so the glibc version matches. Distroless is a lot smaller than

kuksa-client/MANIFEST.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
include *.py
2+
recursive-include kuksa *.py
3+
recursive-include sdv *.py
24
global-exclude *.pyc
5+
global-exclude protobuild.py
6+
global-exclude prototagandcopy.py

kuksa-client/protobuild.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# /********************************************************************************
2+
# * Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
# *
4+
# * See the NOTICE file(s) distributed with this work for additional
5+
# * information regarding copyright ownership.
6+
# *
7+
# * This program and the accompanying materials are made available under the
8+
# * terms of the Apache License 2.0 which is available at
9+
# * http://www.apache.org/licenses/LICENSE-2.0
10+
# *
11+
# * SPDX-License-Identifier: Apache-2.0
12+
# ********************************************************************************/
13+
14+
from grpc_tools import command
15+
16+
# Helper to compile all protos in cwd
17+
18+
19+
def main():
20+
print("Compiling protobuf files...")
21+
command.build_package_protos(".", strict_mode=True)
22+
23+
24+
if __name__ == "__main__":
25+
main()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919

2020

2121
def main():
22+
'''
23+
This will tag all proto folders as Python packages by creating an __init__.py file
24+
in each subdirectory and then copy the proto files to the current working directory
25+
'''
2226
for root, dirs, files in os.walk(PROTO_PATH):
2327
for directory in dirs:
2428
# Create an __init__.py file in each subdirectory

kuksa-client/setup.py

Lines changed: 133 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,52 @@
2121
from setuptools.command.develop import develop as _develop
2222

2323

24+
class BuildGenerateProtos(setuptools.Command):
25+
def run(self):
26+
self.run_command('generate_proto')
27+
return super().run()
28+
29+
30+
class GenerateProtosCommand(setuptools.Command):
31+
"""Command to run prototagandcopy.py script."""
32+
user_options = []
33+
34+
def initialize_options(self):
35+
pass
36+
37+
def finalize_options(self):
38+
pass
39+
40+
def run(self):
41+
import subprocess # pylint: disable=import-outside-toplevel
42+
import os
43+
from pathlib import Path
44+
45+
# Check if we're in an sdist build (proto files should already exist)
46+
proto_files_exist = any(Path('.').glob('**/*_pb2.py'))
47+
proto_dir_exists = Path("../submodules/kuksa-proto/proto/").exists()
48+
49+
if proto_files_exist:
50+
print("Proto files already exist, skipping package tagging")
51+
# List files in the current directory
52+
print("Existing proto files:")
53+
for file in Path('.').glob('**/*_pb2.py'):
54+
print(f" - {file}")
55+
return
56+
57+
if not proto_dir_exists:
58+
print("Warning: Proto directory not found, skipping package tagging")
59+
return
60+
61+
print(f"Generating package from proto: {os.getcwd()}")
62+
result = subprocess.call(['python', 'prototagandcopy.py'])
63+
if result != 0:
64+
print(f"Warning: prototagandcopy.py failed with exit code {result}")
65+
# print("This is how it looks like:")
66+
# for file in Path('./kuksa').glob('**/*'):
67+
# print(f" - {file}")
68+
69+
2470
class BuildPackageProtos(setuptools.Command):
2571
def run(self):
2672
self.run_command('build_pb2')
@@ -37,32 +83,110 @@ def finalize_options(self):
3783
pass
3884

3985
def run(self):
40-
from grpc_tools import command # pylint: disable=import-outside-toplevel
86+
import subprocess
87+
import sys
88+
from pathlib import Path
89+
90+
# Check if proto files already exist (generated by previous step)
91+
proto_files_exist = any(Path('.').glob('**/*_pb2.py'))
92+
if proto_files_exist:
93+
print("Proto files already exist, skipping protobuf compilation")
94+
# Recursively list every file in folder "kuksa"
95+
for file in Path('./kuksa').glob('**/*'):
96+
print(f" - {file}")
97+
return
98+
99+
# Check if we have .proto files to compile
100+
proto_source_files = list(Path('.').glob('**/*.proto'))
101+
if not proto_source_files:
102+
print("No .proto files found, skipping protobuf compilation")
103+
return
104+
105+
print(f"Found {len(proto_source_files)} .proto files to compile")
106+
107+
try:
108+
from grpc_tools import command
109+
print("Compiling protobuf files...")
110+
command.build_package_protos(".", strict_mode=True)
111+
except ImportError:
112+
# Fallback to direct protoc call
113+
print("grpc_tools not found, using protoc directly.")
114+
if proto_source_files:
115+
subprocess.check_call([
116+
sys.executable, "-m", "grpc_tools.protoc",
117+
"--proto_path=.",
118+
"--python_out=.",
119+
"--grpc_python_out=.",
120+
*[str(f) for f in proto_source_files]
121+
])
122+
123+
124+
class BuildCommand(BuildGenerateProtos, BuildPackageProtos, build.build):
125+
...
41126

42-
command.build_package_protos(".", strict_mode=True)
43127

128+
class BuildPyCommand(BuildGenerateProtos, BuildPackageProtos, build_py.build_py): # pylint: disable=too-many-ancestors
129+
def finalize_options(self):
130+
# First run the parent finalize_options which includes proto generation
131+
super().finalize_options()
44132

45-
class BuildCommand(BuildPackageProtos, build.build):
46-
...
133+
# After proto generation, ensure packages are properly set BEFORE build_py runs
134+
self.ensure_proto_packages()
135+
136+
def run(self):
137+
# Just run the normal build_py
138+
build_py.build_py.run(self)
47139

140+
def ensure_proto_packages(self):
141+
"""Ensure generated proto packages are included in the distribution."""
142+
import os
48143

49-
class BuildPyCommand(BuildPackageProtos, build_py.build_py): # pylint: disable=too-many-ancestors
50-
...
144+
# Find all proto packages
145+
print("Searching and adding dynamically built proto packages...")
146+
proto_packages = []
147+
148+
for root, dirs, files in os.walk('.'):
149+
if '__init__.py' in files:
150+
package = root.replace('./', '').replace('/', '.').lstrip('.')
151+
print(f"Found package: {package}")
152+
if package and any(proto_name in package for proto_name in ['kuksa', 'sdv']):
153+
# Skip build directories
154+
if not package.startswith('build.'):
155+
proto_packages.append(package)
156+
print(f"Adding proto package: {package}")
157+
158+
# Get current packages from distribution or setup.cfg
159+
current_packages = []
160+
if hasattr(self.distribution, 'packages') and self.distribution.packages:
161+
current_packages = list(self.distribution.packages)
162+
163+
# Add proto packages
164+
all_packages = current_packages + [pkg for pkg in proto_packages if pkg not in current_packages]
165+
166+
# Update the distribution packages
167+
self.distribution.packages = all_packages
168+
print(f"Final package list: {all_packages}")
51169

52170

53-
class SDistCommand(BuildPackageProtos, sdist.sdist):
171+
class SDistCommand(BuildGenerateProtos, BuildPackageProtos, sdist.sdist):
54172
...
55173

56174

57-
class DevelopCommand(BuildPackageProtos, _develop):
175+
class DevelopCommand(BuildGenerateProtos, BuildPackageProtos, _develop):
58176

59177
def run(self):
60-
self.run_command("build_pb2")
178+
try:
179+
self.run_command("generate_proto")
180+
self.run_command("build_pb2")
181+
except Exception as e:
182+
print(f"Warning: Proto generation failed: {e}")
183+
print("Continuing with development install...")
61184
super().run()
62185

63186

64187
setuptools.setup(
65188
cmdclass={
189+
"generate_proto": GenerateProtosCommand,
66190
"build": BuildCommand,
67191
"build_pb2": BuildPackageProtosCommand,
68192
"build_py": BuildPyCommand, # Used for editable installs but also for building wheels

0 commit comments

Comments
 (0)