mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-05 01:02:08 +00:00
fix(codegen): drop grpcio-tools, stop committing generated code
The codegen only ever needed a protoc binary, but every entry point installed
grpcio-tools to get one. That drags in the grpcio C extension, which has no
Windows/ARM64 wheel and falls back to building from source there, so the ARM64
job died with "Failed building wheel for grpcio" -> "protoc not found".
tools/codegen_toolchain.py resolves the toolchain instead: protoc from $PROTOC,
PATH, a cache, grpc_tools when already installed, or a pinned checksum-verified
protoc release unpacked into .codegen-tools/; protobuf and pyyaml from the
calling interpreter or a cached virtualenv it re-execs into (distro Pythons
refuse `pip install` under PEP 668). All four build scripts and all three CI
jobs are now just `python tools/run_codegen.py`, with no pip lines around it.
tools/config_metadata_pb2.py was the one generated file checked into git. The
orca.* option extensions are now read out of the descriptor set, which already
carries config_metadata.proto via --include_imports, so nothing is generated
into the tree -- and the protobuf>=6.33.5,<7 CI pin goes away with it, since it
only existed to satisfy gencode's hard ValidateProtobufRuntimeVersion check.
Generated C++ verified byte-identical under upb and the pure-Python protobuf
runtime (what win/arm64 installs), and under both grpc_tools' and standalone
protoc.
Also fixed:
- build_release_macos.sh still passed -DPython3_EXECUTABLE=<codegen venv>,
pointing the bundled *embed* interpreter at the codegen environment -- the
same confusion fae4b124 fixed on the CMake side.
- Tab.cpp #includes TabLayout_generated.cpp but had no dependency on
codegen_config, so an incremental build after a .proto edit could compile it
while the file was being rewritten. libslic3r already had this guard.
- ConfigCodegen.cmake now probes with `codegen_toolchain.py --check` (which
never downloads or installs), prefers a host interpreter over the embed one,
and lets a fresh clone generate at configure time instead of erroring out.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
260
tools/codegen_toolchain.py
Normal file
260
tools/codegen_toolchain.py
Normal file
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Toolchain resolution for the config codegen.
|
||||
|
||||
The codegen needs exactly three things: a protoc binary, the protobuf Python
|
||||
runtime and pyyaml. Every entry point used to install `grpcio-tools` to get
|
||||
protoc, which drags in the grpcio C extension: it has no Windows/ARM64 wheel and
|
||||
falls back to building from source there, which is what broke the ARM64 build
|
||||
("Failed building wheel for grpcio" -> "protoc not found"). Nothing in the
|
||||
codegen uses gRPC.
|
||||
|
||||
Resolution order, so callers only ever run `python tools/run_codegen.py`:
|
||||
|
||||
protoc $PROTOC -> PATH -> cached download -> grpc_tools (if installed) ->
|
||||
pinned, checksum-verified protoc release downloaded into
|
||||
.codegen-tools/ (gitignored)
|
||||
runtime the current interpreter, else a cached virtualenv under
|
||||
.codegen-tools/venv that the entry point re-execs into
|
||||
|
||||
Set PROTOC=/path/to/protoc (or put protoc on PATH) to build offline.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
CACHE_DIR = ROOT / ".codegen-tools"
|
||||
|
||||
# Pinned so every machine generates with the same compiler, and checksummed
|
||||
# because we execute what we download. To bump: change the version and refresh
|
||||
# every hash from https://github.com/protocolbuffers/protobuf/releases/tag/v<ver>
|
||||
PROTOC_VERSION = "28.3"
|
||||
PROTOC_ARCHIVE_SHA256 = {
|
||||
"win64": "ce64f49bdeddef49ce4bd313a8f59bcf92fcf67b5831efbf66170386d2e66948",
|
||||
"linux-x86_64": "0ad949f04a6a174da83cdcbdb36dee0a4925272a5b6d83f79a6bf9852076d53f",
|
||||
"linux-aarch_64": "1de522032a8b194002fe35cab86d747848238b5e4de4f99648372079f5b46f9a",
|
||||
"osx-universal_binary": "52df502b263da20f3311b23b5c6553d10cc25c6ebb85df381d80a2806b6a698b",
|
||||
}
|
||||
|
||||
# pip name -> import name
|
||||
PYTHON_PACKAGES = {"protobuf": "google.protobuf", "pyyaml": "yaml"}
|
||||
|
||||
# Guards against an endless re-exec loop if the virtualenv still can't import.
|
||||
_BOOTSTRAP_ENV = "ORCA_CODEGEN_BOOTSTRAPPED"
|
||||
|
||||
_PROTOC_EXE = "protoc.exe" if os.name == "nt" else "protoc"
|
||||
|
||||
|
||||
def _protoc_dir():
|
||||
return CACHE_DIR / f"protoc-{PROTOC_VERSION}"
|
||||
|
||||
|
||||
def _archive_key():
|
||||
"""Release asset for this host, or None if protobuf ships no build for it."""
|
||||
if sys.platform == "win32":
|
||||
# There is no win/arm64 release; the x64 build runs under Windows'
|
||||
# emulation, which is how the ARM64 CI job gets a protoc.
|
||||
return "win64"
|
||||
if sys.platform == "darwin":
|
||||
return "osx-universal_binary"
|
||||
if sys.platform.startswith("linux"):
|
||||
machine = platform.machine().lower()
|
||||
if machine in ("x86_64", "amd64"):
|
||||
return "linux-x86_64"
|
||||
if machine in ("aarch64", "arm64"):
|
||||
return "linux-aarch_64"
|
||||
return None
|
||||
|
||||
|
||||
def _download_protoc():
|
||||
"""Fetch and unpack the pinned protoc. Returns the binary path, or None."""
|
||||
key = _archive_key()
|
||||
if key is None:
|
||||
print(f" ERROR: no pinned protoc release for {sys.platform}/{platform.machine()}.")
|
||||
print(" Install protoc from your package manager and re-run, or set PROTOC=<path>.")
|
||||
return None
|
||||
|
||||
url = (f"https://github.com/protocolbuffers/protobuf/releases/download/"
|
||||
f"v{PROTOC_VERSION}/protoc-{PROTOC_VERSION}-{key}.zip")
|
||||
print(f" Downloading protoc {PROTOC_VERSION} ({key})...")
|
||||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(dir=CACHE_DIR) as tmp:
|
||||
archive = Path(tmp) / "protoc.zip"
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=120) as response:
|
||||
archive.write_bytes(response.read())
|
||||
except OSError as exc:
|
||||
print(f" ERROR: download failed: {exc}")
|
||||
print(" Install protoc manually and re-run, or set PROTOC=<path>.")
|
||||
return None
|
||||
|
||||
actual = hashlib.sha256(archive.read_bytes()).hexdigest()
|
||||
expected = PROTOC_ARCHIVE_SHA256[key]
|
||||
if actual != expected:
|
||||
print(f" ERROR: protoc archive checksum mismatch for {key} {PROTOC_VERSION}:")
|
||||
print(f" expected {expected}")
|
||||
print(f" actual {actual}")
|
||||
return None
|
||||
|
||||
# Unpack the whole archive, not just bin/: protoc resolves the well-known
|
||||
# imports (google/protobuf/descriptor.proto) from ../include next to it.
|
||||
staging = Path(tmp) / "unpacked"
|
||||
with zipfile.ZipFile(archive) as zf:
|
||||
zf.extractall(staging)
|
||||
|
||||
target = _protoc_dir()
|
||||
if target.exists():
|
||||
shutil.rmtree(target)
|
||||
# Move into place only once complete, so an interrupted run never leaves
|
||||
# a half-extracted toolchain that later runs would happily use.
|
||||
shutil.move(str(staging), str(target))
|
||||
|
||||
binary = target / "bin" / _PROTOC_EXE
|
||||
if not binary.exists():
|
||||
print(f" ERROR: protoc archive did not contain bin/{_PROTOC_EXE}")
|
||||
return None
|
||||
binary.chmod(binary.stat().st_mode | 0o755)
|
||||
return binary
|
||||
|
||||
|
||||
def find_protoc(allow_download=True):
|
||||
"""Return the protoc command as a list, or None if it can't be resolved."""
|
||||
override = os.environ.get("PROTOC")
|
||||
if override:
|
||||
return [override]
|
||||
|
||||
on_path = shutil.which("protoc")
|
||||
if on_path:
|
||||
return [on_path]
|
||||
|
||||
cached = _protoc_dir() / "bin" / _PROTOC_EXE
|
||||
if cached.exists():
|
||||
return [str(cached)]
|
||||
|
||||
# Honour a pre-existing grpcio-tools install rather than downloading.
|
||||
if importlib.util.find_spec("grpc_tools") is not None:
|
||||
return [sys.executable, "-m", "grpc_tools.protoc"]
|
||||
|
||||
if allow_download:
|
||||
binary = _download_protoc()
|
||||
if binary is not None:
|
||||
return [str(binary)]
|
||||
return None
|
||||
|
||||
|
||||
def missing_packages():
|
||||
"""pip names of the required Python packages this interpreter can't import."""
|
||||
missing = []
|
||||
for pip_name, module in PYTHON_PACKAGES.items():
|
||||
try:
|
||||
found = importlib.util.find_spec(module) is not None
|
||||
except (ImportError, ValueError):
|
||||
found = False
|
||||
if not found:
|
||||
missing.append(pip_name)
|
||||
return missing
|
||||
|
||||
|
||||
def _venv_python():
|
||||
venv_dir = CACHE_DIR / "venv"
|
||||
if os.name == "nt":
|
||||
return venv_dir / "Scripts" / "python.exe"
|
||||
return venv_dir / "bin" / "python"
|
||||
|
||||
|
||||
def bootstrap_python():
|
||||
"""The cached virtualenv interpreter, if it exists and has the packages."""
|
||||
python = _venv_python()
|
||||
if not python.exists():
|
||||
return None
|
||||
probe = subprocess.run([str(python), "-c", "import google.protobuf, yaml"],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
return python if probe.returncode == 0 else None
|
||||
|
||||
|
||||
def _ensure_venv(missing):
|
||||
"""Create/reuse .codegen-tools/venv with the required packages installed."""
|
||||
python = _venv_python()
|
||||
|
||||
if not python.exists():
|
||||
print(f" Creating codegen virtualenv in {python.parent.parent}...")
|
||||
if subprocess.run([sys.executable, "-m", "venv", str(python.parent.parent)]).returncode != 0 \
|
||||
or not python.exists():
|
||||
return None
|
||||
|
||||
print(f" Installing {', '.join(missing)} into the codegen virtualenv...")
|
||||
result = subprocess.run([str(python), "-m", "pip", "install", "--quiet",
|
||||
"--disable-pip-version-check", *missing])
|
||||
return python if result.returncode == 0 else None
|
||||
|
||||
|
||||
def ensure_python_runtime():
|
||||
"""
|
||||
Guarantee protobuf + pyyaml are importable.
|
||||
|
||||
If they aren't, re-run the calling script in a cached virtualenv that has
|
||||
them and exit with its status. Keeping the packages out of the caller's
|
||||
interpreter is what lets the build scripts work on distros that refuse
|
||||
`pip install` into a system Python (PEP 668).
|
||||
"""
|
||||
missing = missing_packages()
|
||||
if not missing:
|
||||
return
|
||||
|
||||
if os.environ.get(_BOOTSTRAP_ENV):
|
||||
# We are already inside the bootstrapped environment: installing again
|
||||
# would just fail the same way.
|
||||
print(f" ERROR: {', '.join(missing)} still missing after bootstrap.")
|
||||
sys.exit(1)
|
||||
|
||||
# Reuse a complete virtualenv as-is: builds call this every time, and pip
|
||||
# would otherwise hit the network on each one.
|
||||
python = bootstrap_python() or _ensure_venv(missing)
|
||||
if python is None:
|
||||
print(f" ERROR: could not provide {', '.join(missing)}.")
|
||||
print(f" Install them for this interpreter: {sys.executable} -m pip install "
|
||||
f"{' '.join(missing)}")
|
||||
sys.exit(1)
|
||||
|
||||
env = dict(os.environ, **{_BOOTSTRAP_ENV: "1"})
|
||||
sys.exit(subprocess.run([str(python), *sys.argv], env=env).returncode)
|
||||
|
||||
|
||||
def toolchain_ready():
|
||||
"""
|
||||
True if the codegen can run without downloading or installing anything --
|
||||
either from this interpreter or by re-execing into an existing bootstrap
|
||||
virtualenv. This is what decides whether a build regenerates on proto edits.
|
||||
"""
|
||||
if missing_packages() and bootstrap_python() is None:
|
||||
return False
|
||||
return find_protoc(allow_download=False) is not None
|
||||
|
||||
|
||||
def main():
|
||||
# --check is what ConfigCodegen.cmake probes with: it answers whether this
|
||||
# interpreter can regenerate during a build, and must not download, install
|
||||
# or create anything while doing so.
|
||||
if "--check" in sys.argv[1:]:
|
||||
return 0 if toolchain_ready() else 1
|
||||
|
||||
missing = missing_packages()
|
||||
print(f"python: {sys.executable}")
|
||||
print(f"packages: {'all present' if not missing else 'missing ' + ', '.join(missing)}")
|
||||
protoc = find_protoc()
|
||||
print(f"protoc: {' '.join(protoc) if protoc else 'NOT FOUND'}")
|
||||
return 0 if protoc else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -10,11 +10,10 @@ Usage:
|
||||
protoc --proto_path=src/PrintConfigs --descriptor_set_out=config.desc \
|
||||
--include_imports src/PrintConfigs/*.proto
|
||||
|
||||
# Step 2: Generate Python bindings (one-time, or when config_metadata.proto changes)
|
||||
protoc --proto_path=src/PrintConfigs --python_out=tools/ config_metadata.proto
|
||||
# Step 2: Run codegen
|
||||
python tools/config_codegen.py config.desc src/slic3r/GUI/generated/
|
||||
|
||||
# Step 3: Run codegen
|
||||
python tools/config_codegen.py config.desc codegen/generated/
|
||||
(tools/run_codegen.py does both, and resolves protoc for you)
|
||||
|
||||
Outputs:
|
||||
- PrintConfigDef_generated.cpp (init_fff_params body)
|
||||
@@ -29,20 +28,22 @@ import re
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
# Add tools/ to path so we can import generated config_metadata_pb2
|
||||
# Add tools/ to path so we can import the sibling helper modules
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
try:
|
||||
from google.protobuf import descriptor_pb2
|
||||
# Import the generated bindings - this registers extensions globally
|
||||
import config_metadata_pb2 as meta_pb2
|
||||
from config_metadata import load_descriptor_set
|
||||
except ImportError as e:
|
||||
print(f"ERROR: {e}")
|
||||
print("Ensure google-protobuf is installed: pip install protobuf")
|
||||
print("And that config_metadata_pb2.py exists in tools/")
|
||||
print("Generate it with: protoc --proto_path=src/PrintConfigs --python_out=tools/ config_metadata.proto")
|
||||
print("Ensure protobuf is installed: pip install protobuf")
|
||||
print("Or just run the pipeline, which bootstraps it: python tools/run_codegen.py")
|
||||
sys.exit(1)
|
||||
|
||||
# The orca.* option extensions and enum constants, bound by main() from the
|
||||
# descriptor set (see config_metadata.py for why they aren't imported).
|
||||
meta_pb2 = None
|
||||
|
||||
|
||||
# Proto FieldDescriptorProto.Type enum values
|
||||
TYPE_DOUBLE = 1
|
||||
@@ -179,9 +180,8 @@ def parse_field_options(field_desc_proto):
|
||||
Re-parse FieldOptions from a FieldDescriptorProto with extensions registered.
|
||||
This is needed because the FileDescriptorSet parser doesn't know about our
|
||||
custom extensions, so they end up as unknown fields. Re-parsing with the
|
||||
extensions registered (via config_metadata_pb2 import) resolves them.
|
||||
extensions registered (see config_metadata.load_descriptor_set) resolves them.
|
||||
"""
|
||||
from google.protobuf import descriptor_pb2
|
||||
opts = field_desc_proto.options
|
||||
if not opts.ByteSize():
|
||||
return descriptor_pb2.FieldOptions()
|
||||
@@ -981,11 +981,8 @@ def main():
|
||||
print(f"ERROR: Descriptor file not found: {desc_path}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(desc_path, 'rb') as f:
|
||||
raw = f.read()
|
||||
|
||||
file_descriptor_set = descriptor_pb2.FileDescriptorSet()
|
||||
file_descriptor_set.ParseFromString(raw)
|
||||
global meta_pb2
|
||||
file_descriptor_set, meta_pb2 = load_descriptor_set(desc_path)
|
||||
|
||||
print(f"Loaded {len(file_descriptor_set.file)} proto files")
|
||||
for fd in file_descriptor_set.file:
|
||||
|
||||
67
tools/config_metadata.py
Normal file
67
tools/config_metadata.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Access to the orca.* option extensions declared in config_metadata.proto.
|
||||
|
||||
The extensions are read straight out of the compiled descriptor set: protoc runs
|
||||
with --include_imports, so config_metadata.proto travels inside the .desc file
|
||||
that the codegen already consumes. Registering it in the default descriptor pool
|
||||
before the descriptor set is parsed is what makes the custom options resolve
|
||||
instead of landing in unknown fields.
|
||||
|
||||
Doing it this way keeps generated code out of git. The alternative -- a checked-in
|
||||
config_metadata_pb2.py -- also pinned the protobuf runtime to whichever protoc
|
||||
produced it, because gencode embeds a hard ValidateProtobufRuntimeVersion() check.
|
||||
"""
|
||||
|
||||
from google.protobuf import descriptor_pb2, descriptor_pool
|
||||
|
||||
METADATA_PROTO = "config_metadata.proto"
|
||||
|
||||
|
||||
class Metadata:
|
||||
"""
|
||||
Stand-in for the generated config_metadata_pb2 module.
|
||||
|
||||
Exposes each orca extension as an attribute holding its FieldDescriptor
|
||||
(usable as `options.Extensions[meta.label]`) and each enum value as an int
|
||||
constant (`meta.MODE_SIMPLE`, `meta.STEP_SLICE`, ...), matching how the
|
||||
generated module was used.
|
||||
"""
|
||||
|
||||
def __init__(self, file_descriptor):
|
||||
for name, extension in file_descriptor.extensions_by_name.items():
|
||||
setattr(self, name, extension)
|
||||
for enum in file_descriptor.enum_types_by_name.values():
|
||||
for value in enum.values:
|
||||
setattr(self, value.name, value.number)
|
||||
|
||||
|
||||
def load_descriptor_set(path):
|
||||
"""
|
||||
Read a protoc descriptor set and return (FileDescriptorSet, Metadata).
|
||||
|
||||
The file is parsed twice on purpose: the first pass only locates the embedded
|
||||
config_metadata.proto so its extensions can be registered, the second one
|
||||
parses with those extensions known.
|
||||
"""
|
||||
with open(path, 'rb') as f:
|
||||
raw = f.read()
|
||||
|
||||
probe = descriptor_pb2.FileDescriptorSet()
|
||||
probe.ParseFromString(raw)
|
||||
metadata_file = next((f for f in probe.file if f.name == METADATA_PROTO), None)
|
||||
if metadata_file is None:
|
||||
raise RuntimeError(
|
||||
f"{path} does not contain {METADATA_PROTO} -- protoc must be run with "
|
||||
"--include_imports")
|
||||
|
||||
pool = descriptor_pool.Default()
|
||||
try:
|
||||
file_descriptor = pool.FindFileByName(METADATA_PROTO)
|
||||
except KeyError:
|
||||
pool.Add(metadata_file)
|
||||
file_descriptor = pool.FindFileByName(METADATA_PROTO)
|
||||
|
||||
descriptor_set = descriptor_pb2.FileDescriptorSet()
|
||||
descriptor_set.ParseFromString(raw)
|
||||
return descriptor_set, Metadata(file_descriptor)
|
||||
@@ -1,51 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: config_metadata.proto
|
||||
# Protobuf Python Version: 6.33.5
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
33,
|
||||
5,
|
||||
'',
|
||||
'config_metadata.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x63onfig_metadata.proto\x12\x04orca\x1a google/protobuf/descriptor.proto\"0\n\x0e\x46loatOrPercent\x12\r\n\x05value\x18\x01 \x01(\x01\x12\x0f\n\x07percent\x18\x02 \x01(\x08\"\x1f\n\x07Point2D\x12\t\n\x01x\x18\x01 \x01(\x01\x12\t\n\x01y\x18\x02 \x01(\x01*S\n\nConfigMode\x12\x0f\n\x0bMODE_SIMPLE\x10\x00\x12\x11\n\rMODE_ADVANCED\x10\x01\x12\x10\n\x0cMODE_DEVELOP\x10\x02\x12\x0f\n\x0bMODE_EXPERT\x10\x03*G\n\nPresetType\x12\x10\n\x0cPRESET_PRINT\x10\x00\x12\x13\n\x0fPRESET_FILAMENT\x10\x01\x12\x12\n\x0ePRESET_PRINTER\x10\x02*\xaa\x01\n\x10InvalidationStep\x12\x15\n\x11STEP_GCODE_EXPORT\x10\x00\x12\x13\n\x0fSTEP_SKIRT_BRIM\x10\x01\x12\x13\n\x0fSTEP_WIPE_TOWER\x10\x02\x12\x0e\n\nSTEP_SLICE\x10\x03\x12\x13\n\x0fSTEP_PERIMETERS\x10\x04\x12\x0f\n\x0bSTEP_INFILL\x10\x05\x12\x10\n\x0cSTEP_SUPPORT\x10\x06\x12\r\n\tSTEP_NONE\x10\x07*\x81\x01\n\x14OptionListMembership\x12\r\n\tLIST_NONE\x10\x00\x12\x1d\n\x19LIST_EXTRUDER_OPTION_KEYS\x10\x01\x12\x1d\n\x19LIST_FILAMENT_OPTION_KEYS\x10\x02\x12\x1c\n\x18LIST_VARIANT_OPTION_KEYS\x10\x03*\\\n\nCoTypeHint\x12\x16\n\x12\x43O_TYPE_HINT_UNSET\x10\x00\x12\r\n\tcoPercent\x10\x01\x12\x0e\n\ncoPercents\x10\x02\x12\n\n\x06\x63oEnum\x10\x03\x12\x0b\n\x07\x63oEnums\x10\x04*\x96\x01\n\x07GuiType\x12\x12\n\x0eGUI_TYPE_UNSET\x10\x00\x12\x0f\n\x0bi_enum_open\x10\x01\x12\x0f\n\x0b\x66_enum_open\x10\x02\x12\t\n\x05\x63olor\x10\x03\x12\x0f\n\x0bselect_open\x10\x04\x12\n\n\x06slider\x10\x05\x12\n\n\x06legend\x10\x06\x12\x0e\n\none_string\x10\x07\x12\x11\n\rplugin_picker\x10\x08:.\n\x05label\x12\x1d.google.protobuf.FieldOptions\x18\xd1\x86\x03 \x01(\t:3\n\nfull_label\x12\x1d.google.protobuf.FieldOptions\x18\xd2\x86\x03 \x01(\t:0\n\x07tooltip\x12\x1d.google.protobuf.FieldOptions\x18\xd3\x86\x03 \x01(\t:1\n\x08\x63\x61tegory\x12\x1d.google.protobuf.FieldOptions\x18\xd4\x86\x03 \x01(\t:1\n\x08sidetext\x12\x1d.google.protobuf.FieldOptions\x18\xd5\x86\x03 \x01(\t:2\n\tmin_value\x12\x1d.google.protobuf.FieldOptions\x18\xd6\x86\x03 \x01(\x01:2\n\tmax_value\x12\x1d.google.protobuf.FieldOptions\x18\xd7\x86\x03 \x01(\x01:4\n\x0bmax_literal\x12\x1d.google.protobuf.FieldOptions\x18\xd8\x86\x03 \x01(\x01:?\n\x04mode\x12\x1d.google.protobuf.FieldOptions\x18\xd9\x86\x03 \x01(\x0e\x32\x10.orca.ConfigMode:3\n\nratio_over\x12\x1d.google.protobuf.FieldOptions\x18\xda\x86\x03 \x01(\t:2\n\tmultiline\x12\x1d.google.protobuf.FieldOptions\x18\xdd\x86\x03 \x01(\x08:3\n\nfull_width\x12\x1d.google.protobuf.FieldOptions\x18\xde\x86\x03 \x01(\x08:/\n\x06height\x12\x1d.google.protobuf.FieldOptions\x18\xdf\x86\x03 \x01(\x05:A\n\x06preset\x12\x1d.google.protobuf.FieldOptions\x18\xdb\x86\x03 \x01(\x0e\x32\x10.orca.PresetType:L\n\x0binvalidates\x12\x1d.google.protobuf.FieldOptions\x18\xdc\x86\x03 \x03(\x0e\x32\x16.orca.InvalidationStep:T\n\x0flist_membership\x12\x1d.google.protobuf.FieldOptions\x18\xe2\x86\x03 \x03(\x0e\x32\x1a.orca.OptionListMembership:4\n\x0blegacy_name\x12\x1d.google.protobuf.FieldOptions\x18\xe0\x86\x03 \x01(\t:4\n\x0bis_nullable\x12\x1d.google.protobuf.FieldOptions\x18\xe1\x86\x03 \x01(\x08:@\n\x08gui_type\x12\x1d.google.protobuf.FieldOptions\x18\xe3\x86\x03 \x01(\x0e\x32\r.orca.GuiType:2\n\tgui_flags\x12\x1d.google.protobuf.FieldOptions\x18\xe4\x86\x03 \x01(\t::\n\x11\x65num_keys_map_ref\x12\x1d.google.protobuf.FieldOptions\x18\xe5\x86\x03 \x01(\t:/\n\x06no_cli\x12\x1d.google.protobuf.FieldOptions\x18\xe6\x86\x03 \x01(\x08:1\n\x08readonly\x12\x1d.google.protobuf.FieldOptions\x18\xe7\x86\x03 \x01(\x08:G\n\x0c\x63o_type_hint\x12\x1d.google.protobuf.FieldOptions\x18\xe8\x86\x03 \x01(\x0e\x32\x10.orca.CoTypeHint:6\n\rdefault_value\x12\x1d.google.protobuf.FieldOptions\x18\xe9\x86\x03 \x01(\t:4\n\x0bhas_default\x12\x1d.google.protobuf.FieldOptions\x18\xec\x86\x03 \x01(\x08:;\n\x12\x65num_value_entries\x12\x1d.google.protobuf.FieldOptions\x18\xea\x86\x03 \x03(\t:;\n\x12\x65num_label_entries\x12\x1d.google.protobuf.FieldOptions\x18\xeb\x86\x03 \x03(\t:1\n\x08tab_type\x12\x1d.google.protobuf.FieldOptions\x18\xed\x86\x03 \x01(\t:1\n\x08tab_page\x12\x1d.google.protobuf.FieldOptions\x18\xee\x86\x03 \x01(\t:5\n\x0ctab_optgroup\x12\x1d.google.protobuf.FieldOptions\x18\xef\x86\x03 \x01(\t:>\n\x13virtual_preset_keys\x12\x1f.google.protobuf.MessageOptions\x18\xe1\xd4\x03 \x03(\tb\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'config_metadata_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_CONFIGMODE']._serialized_start=148
|
||||
_globals['_CONFIGMODE']._serialized_end=231
|
||||
_globals['_PRESETTYPE']._serialized_start=233
|
||||
_globals['_PRESETTYPE']._serialized_end=304
|
||||
_globals['_INVALIDATIONSTEP']._serialized_start=307
|
||||
_globals['_INVALIDATIONSTEP']._serialized_end=477
|
||||
_globals['_OPTIONLISTMEMBERSHIP']._serialized_start=480
|
||||
_globals['_OPTIONLISTMEMBERSHIP']._serialized_end=609
|
||||
_globals['_COTYPEHINT']._serialized_start=611
|
||||
_globals['_COTYPEHINT']._serialized_end=703
|
||||
_globals['_GUITYPE']._serialized_start=706
|
||||
_globals['_GUITYPE']._serialized_end=856
|
||||
_globals['_FLOATORPERCENT']._serialized_start=65
|
||||
_globals['_FLOATORPERCENT']._serialized_end=113
|
||||
_globals['_POINT2D']._serialized_start=115
|
||||
_globals['_POINT2D']._serialized_end=146
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -6,17 +6,23 @@ Convenience script: runs the codegen pipeline.
|
||||
2. Generate C++ from descriptors (config_codegen.py)
|
||||
3. Validate output against original
|
||||
|
||||
The toolchain (protoc, protobuf, pyyaml) is resolved by codegen_toolchain.py, so
|
||||
this script is the single entry point every build script and CI job calls -- no
|
||||
`pip install` lines needed around it.
|
||||
|
||||
Usage:
|
||||
python tools/run_codegen.py # full pipeline
|
||||
python tools/run_codegen.py --validate-only # just validate
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import codegen_toolchain # noqa: E402
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
PROTO_DIR = ROOT / "src" / "PrintConfigs"
|
||||
CODEGEN_OUT = ROOT / "src" / "slic3r" / "GUI" / "generated"
|
||||
@@ -24,22 +30,6 @@ DESC_FILE = ROOT / "config.desc"
|
||||
LAYOUT_YAML = PROTO_DIR / "layout.yaml"
|
||||
|
||||
|
||||
def _ensure_pyyaml():
|
||||
"""Install pyyaml if not present — needed for tab layout generation."""
|
||||
try:
|
||||
import yaml # noqa: F401
|
||||
return True
|
||||
except ImportError:
|
||||
print(" Installing pyyaml (required for tab layout generation)...")
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "pyyaml", "-q"],
|
||||
capture_output=True)
|
||||
if result.returncode != 0:
|
||||
print(" ERROR: failed to install pyyaml")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def run(cmd, **kwargs):
|
||||
print(f" $ {' '.join(str(c) for c in cmd)}")
|
||||
result = subprocess.run(cmd, **kwargs)
|
||||
@@ -49,19 +39,6 @@ def run(cmd, **kwargs):
|
||||
return True
|
||||
|
||||
|
||||
def _protoc_cmd():
|
||||
"""Return the protoc command list. Prefers standalone protoc, falls back to grpc_tools."""
|
||||
if shutil.which("protoc"):
|
||||
return ["protoc"]
|
||||
try:
|
||||
import grpc_tools.protoc # noqa: F401
|
||||
return [sys.executable, "-m", "grpc_tools.protoc"]
|
||||
except ImportError:
|
||||
pass
|
||||
print(" ERROR: protoc not found. Install protoc or run: pip install grpcio-tools")
|
||||
return None
|
||||
|
||||
|
||||
def step_compile():
|
||||
print("\n=== Step 1: Compile .proto -> descriptor set ===")
|
||||
proto_files = [f for f in PROTO_DIR.glob("*.proto") if not f.name.endswith("_gen.proto") and f.name != "config_metadata.proto"]
|
||||
@@ -69,7 +46,7 @@ def step_compile():
|
||||
print(" ERROR: No .proto files found")
|
||||
return False
|
||||
|
||||
protoc = _protoc_cmd()
|
||||
protoc = codegen_toolchain.find_protoc()
|
||||
if protoc is None:
|
||||
return False
|
||||
|
||||
@@ -82,7 +59,6 @@ def step_compile():
|
||||
|
||||
def step_generate():
|
||||
print("\n=== Step 2: Generate C++ from descriptors + layout.yaml ===")
|
||||
_ensure_pyyaml() # tab layout generation requires pyyaml
|
||||
return run([sys.executable, str(ROOT / "tools" / "config_codegen.py"),
|
||||
str(DESC_FILE), str(CODEGEN_OUT)])
|
||||
|
||||
@@ -106,8 +82,11 @@ def main():
|
||||
help="Skip validation step (used by cmake build)")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Re-execs into a virtualenv with protobuf/pyyaml if this interpreter lacks them.
|
||||
codegen_toolchain.ensure_python_runtime()
|
||||
|
||||
if args.validate_only:
|
||||
# Compile + lint the protos, then check the committed generated files are current.
|
||||
# Compile + lint the protos, then check the generated files against PrintConfig.cpp.
|
||||
sys.exit(0 if (step_compile() and step_lint() and step_validate()) else 1)
|
||||
|
||||
for name, fn in [("Compile", step_compile), ("Generate", step_generate)]:
|
||||
|
||||
Reference in New Issue
Block a user