Files
2026-07-13 13:22:34 +08:00

334 lines
10 KiB
Python
Executable File

import platform
import subprocess
import tempfile
import textwrap
import urllib.request
import zipfile
from pathlib import Path
from typing import Literal
SYSTEM = platform.system()
MACHINE = platform.machine()
CACHE_DIR = Path(".cache/protobuf_cache")
MLFLOW_PROTOS_DIR = Path("mlflow/protos")
TEST_PROTOS_DIR = Path("tests/protos")
OTEL_PROTOS_DIR = Path("mlflow/protos/opentelemetry")
def gen_protos(
proto_dir: Path,
proto_files: list[Path],
lang: Literal["python", "java"],
protoc_bin: Path,
protoc_include_paths: list[Path],
out_dir: Path,
) -> None:
assert lang in ["python", "java"]
out_dir.mkdir(parents=True, exist_ok=True)
subprocess.check_call([
protoc_bin,
"--fatal_warnings",
*(f"-I={p}" for p in protoc_include_paths),
f"-I={proto_dir}",
f"--{lang}_out={out_dir}",
*[proto_dir / pf for pf in proto_files],
])
def gen_stub_files(
proto_dir: Path,
proto_files: list[Path],
protoc_bin: Path,
protoc_include_paths: list[Path],
out_dir: Path,
) -> None:
out_dir.mkdir(parents=True, exist_ok=True)
subprocess.check_call([
protoc_bin,
"--fatal_warnings",
*(f"-I={p}" for p in protoc_include_paths),
f"-I={proto_dir}",
f"--pyi_out={out_dir}",
*[proto_dir / pf for pf in proto_files],
])
def gen_proto_docs(
proto_dir: Path,
proto_files: list[Path],
protoc_bin: Path,
protoc_include_path: Path,
out_dir: Path,
) -> None:
plugin_path = Path("dev/proto-plugin.sh").resolve()
subprocess.check_call([
protoc_bin,
f"-I={protoc_include_path}",
f"-I={proto_dir}",
f"--plugin=protoc-gen-doc={plugin_path}",
f"--doc_out={out_dir}",
*[proto_dir / pf for pf in proto_files],
])
def apply_python_gencode_replacement(file_path: Path) -> None:
content = file_path.read_text()
for old, new in python_gencode_replacements:
content = content.replace(old, new)
file_path.write_text(content, encoding="UTF-8")
def _get_python_output_path(proto_file_path: Path) -> Path:
return proto_file_path.parent / (proto_file_path.stem + "_pb2.py")
def to_paths(*args: str) -> list[Path]:
return list(map(Path, args))
basic_proto_files = to_paths(
"databricks.proto",
"service.proto",
"model_registry.proto",
"databricks_artifacts.proto",
"mlflow_artifacts.proto",
"internal.proto",
"scalapb/scalapb.proto",
"assessments.proto",
"datasets.proto",
"issues.proto",
"label_schemas.proto",
"review_queues.proto",
"webhooks.proto",
"jobs.proto",
"prompt_optimization.proto",
)
uc_proto_files = to_paths(
"databricks_managed_catalog_messages.proto",
"databricks_managed_catalog_service.proto",
"databricks_uc_registry_messages.proto",
"databricks_uc_registry_service.proto",
"databricks_filesystem_service.proto",
"unity_catalog_oss_messages.proto",
"unity_catalog_oss_service.proto",
"unity_catalog_prompt_messages.proto",
"unity_catalog_prompt_service.proto",
)
tracing_proto_files = to_paths(
"databricks_exception_with_details.proto",
"databricks_tracing.proto",
)
facet_proto_files = to_paths("facet_feature_statistics.proto")
python_proto_files = basic_proto_files + uc_proto_files + facet_proto_files + tracing_proto_files
test_proto_files = to_paths("test_message.proto")
python_gencode_replacements = [
(
"from scalapb import scalapb_pb2 as scalapb_dot_scalapb__pb2",
"from .scalapb import scalapb_pb2 as scalapb_dot_scalapb__pb2",
),
(
"import databricks_pb2 as databricks__pb2",
"from . import databricks_pb2 as databricks__pb2",
),
(
"import databricks_uc_registry_messages_pb2 as databricks__uc__registry__messages__pb2",
"from . import databricks_uc_registry_messages_pb2 as databricks_uc_registry_messages_pb2",
),
(
"import databricks_managed_catalog_messages_pb2 as databricks__managed__catalog__"
"messages__pb2",
"from . import databricks_managed_catalog_messages_pb2 as databricks_managed_"
"catalog_messages_pb2",
),
(
"import unity_catalog_oss_messages_pb2 as unity__catalog__oss__messages__pb2",
"from . import unity_catalog_oss_messages_pb2 as unity_catalog_oss_messages_pb2",
),
(
"import unity_catalog_prompt_messages_pb2 as unity__catalog__prompt__messages__pb2",
"from . import unity_catalog_prompt_messages_pb2 as unity_catalog_prompt_messages_pb2",
),
(
"import service_pb2 as service__pb2",
"from . import service_pb2 as service__pb2",
),
(
"import assessments_pb2 as assessments__pb2",
"from . import assessments_pb2 as assessments__pb2",
),
(
"import datasets_pb2 as datasets__pb2",
"from . import datasets_pb2 as datasets__pb2",
),
(
"import issues_pb2 as issues__pb2",
"from . import issues_pb2 as issues__pb2",
),
(
"import label_schemas_pb2 as label__schemas__pb2",
"from . import label_schemas_pb2 as label__schemas__pb2",
),
(
"import review_queues_pb2 as review__queues__pb2",
"from . import review_queues_pb2 as review__queues__pb2",
),
(
"import webhooks_pb2 as webhooks__pb2",
"from . import webhooks_pb2 as webhooks__pb2",
),
(
"import jobs_pb2 as jobs__pb2",
"from . import jobs_pb2 as jobs__pb2",
),
(
"import prompt_optimization_pb2 as prompt__optimization__pb2",
"from . import prompt_optimization_pb2 as prompt__optimization__pb2",
),
(
"import databricks_exception_with_details_pb2 as databricks__exception__with__details__pb2",
"from . import databricks_exception_with_details_pb2 as databricks_exception_"
"with_details_pb2",
),
]
def gen_python_protos(protoc_bin: Path, protoc_include_paths: list[Path], out_dir: Path) -> None:
gen_protos(
MLFLOW_PROTOS_DIR,
python_proto_files,
"python",
protoc_bin,
protoc_include_paths,
out_dir,
)
gen_protos(
TEST_PROTOS_DIR,
test_proto_files,
"python",
protoc_bin,
protoc_include_paths,
out_dir,
)
for proto_file in python_proto_files:
apply_python_gencode_replacement(out_dir / _get_python_output_path(proto_file))
def download_file(url: str, output_path: Path) -> None:
urllib.request.urlretrieve(url, output_path)
def download_and_extract_protoc(version: Literal["3.19.4", "26.0"]) -> tuple[Path, Path]:
"""
Download and extract specific version protoc tool for Linux systems,
return extracted protoc executable file path and include path.
"""
assert SYSTEM == "Linux", "This script only supports Linux systems."
assert MACHINE in ["x86_64", "aarch64"], (
"This script only supports x86_64 or aarch64 CPU architectures."
)
cpu_type = "x86_64" if MACHINE == "x86_64" else "aarch_64"
protoc_zip_filename = f"protoc-{version}-linux-{cpu_type}.zip"
downloaded_protoc_bin = CACHE_DIR / f"protoc-{version}" / "bin" / "protoc"
downloaded_protoc_include_path = CACHE_DIR / f"protoc-{version}" / "include"
if not (downloaded_protoc_bin.is_file() and downloaded_protoc_include_path.is_dir()):
with tempfile.TemporaryDirectory() as t:
zip_path = Path(t) / protoc_zip_filename
download_file(
f"https://github.com/protocolbuffers/protobuf/releases/download/v{version}/{protoc_zip_filename}",
zip_path,
)
with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(CACHE_DIR / f"protoc-{version}")
# Make protoc executable
downloaded_protoc_bin.chmod(0o755)
return downloaded_protoc_bin, downloaded_protoc_include_path
def generate_final_python_gencode(
gencode3194_path: Path, gencode5260_path: Path, out_path: Path
) -> None:
gencode3194 = gencode3194_path.read_text()
gencode5260 = gencode5260_path.read_text()
merged_code = f"""
import google.protobuf
from packaging.version import Version
if Version(google.protobuf.__version__).major >= 5:
{textwrap.indent(gencode5260, " ")}
else:
{textwrap.indent(gencode3194, " ")}
"""
out_path.write_text(merged_code, encoding="UTF-8")
def main() -> None:
CACHE_DIR.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory() as temp_gencode_dir:
temp_gencode_path = Path(temp_gencode_dir)
proto3194_out = temp_gencode_path / "3.19.4"
proto5260_out = temp_gencode_path / "26.0"
proto3194_out.mkdir(exist_ok=True)
proto5260_out.mkdir(exist_ok=True)
protoc3194, protoc3194_include = download_and_extract_protoc("3.19.4")
protoc5260, protoc5260_include = download_and_extract_protoc("26.0")
# Build include paths list
protoc3194_includes = [protoc3194_include, OTEL_PROTOS_DIR]
protoc5260_includes = [protoc5260_include, OTEL_PROTOS_DIR]
gen_python_protos(protoc3194, protoc3194_includes, proto3194_out)
gen_python_protos(protoc5260, protoc5260_includes, proto5260_out)
for proto_files, protos_dir in [
(python_proto_files, MLFLOW_PROTOS_DIR),
(test_proto_files, TEST_PROTOS_DIR),
]:
for proto_file in proto_files:
gencode_path = _get_python_output_path(proto_file)
generate_final_python_gencode(
proto3194_out / gencode_path,
proto5260_out / gencode_path,
protos_dir / gencode_path,
)
# generate java gencode using pinned protoc 3.19.4 version.
gen_protos(
MLFLOW_PROTOS_DIR,
basic_proto_files,
"java",
protoc3194,
protoc3194_includes,
Path("mlflow/java/client/src/main/java"),
)
gen_stub_files(
MLFLOW_PROTOS_DIR,
python_proto_files,
protoc5260,
protoc5260_includes,
Path("mlflow/protos/"),
)
gen_proto_docs(
MLFLOW_PROTOS_DIR,
basic_proto_files,
protoc5260,
protoc5260_include,
Path("mlflow/protos"),
)
if __name__ == "__main__":
main()