from __future__ import annotations import importlib import shutil import subprocess from pathlib import Path from typing import Optional import semantic_version import typer from tomlkit import dump, parse from typing_extensions import Annotated import gradio from gradio.analytics import custom_component_analytics from gradio.cli.commands.components._docs_utils import ( get_deep, ) from gradio.cli.commands.components.docs import run_command from gradio.cli.commands.components.install_component import _get_executable_path from gradio.cli.commands.display import LivePanelDisplay gradio_template_path = Path(gradio.__file__).parent / "templates" / "frontend" def _build( path: Annotated[ Path, typer.Argument(help="The directory of the custom component.") ] = Path("."), build_frontend: Annotated[ bool, typer.Option(help="Whether to build the frontend as well.") ] = True, bump_version: Annotated[ bool, typer.Option(help="Whether to bump the version number automatically.") ] = False, generate_docs: Annotated[ bool, typer.Option(help="Whether to generate the documentation as well.") ] = True, python_path: Annotated[ Optional[str], typer.Option( help="Path to python executable. If None, will use the default path found by `which python3`. If python3 is not found, `which python` will be tried. If both fail an error will be raised." ), ] = None, ): custom_component_analytics( "build", None, None, None, None, generate_docs=generate_docs, bump_version=bump_version, ) name = Path(path).resolve() if not (name / "pyproject.toml").exists(): raise ValueError(f"Cannot find pyproject.toml file in {name}") with LivePanelDisplay() as live: live.update( f":package: Building package in [orange3]{str(name.name)}[/]", add_sleep=0.2 ) pyproject_toml = parse((path / "pyproject.toml").read_text()) package_name = get_deep(pyproject_toml, ["project", "name"]) python_path = _get_executable_path( "python", None, "--python-path", check_3=True ) if not isinstance(package_name, str): raise ValueError( "Your pyproject.toml file does not have a [project] name field!" ) try: importlib.import_module(package_name) # type: ignore except ModuleNotFoundError as e: raise ValueError( f"Your custom component package ({package_name}) is not installed! " "Please install it with the gradio cc install command before buillding it." ) from e if bump_version: pyproject_toml = parse((path / "pyproject.toml").read_text()) version = semantic_version.Version( pyproject_toml["project"]["version"] # type: ignore ).next_patch() live.update( f":1234: Using version [bold][magenta]{version}[/][/]. " "Set [bold][magenta]--no-bump-version[/][/] to use the version in pyproject.toml file." ) pyproject_toml["project"]["version"] = str(version) # type: ignore with open(path / "pyproject.toml", "w") as f: dump(pyproject_toml, f) else: version = pyproject_toml["project"]["version"] # type: ignore live.update( f":1234: Package will use version [bold][magenta]{version}[/][/] defined in pyproject.toml file. " "Set [bold][magenta]--bump-version[/][/] to automatically bump the version number." ) if generate_docs: _demo_dir = Path("demo").resolve() _demo_name = "app.py" _demo_path = _demo_dir / _demo_name _readme_path = name / "README.md" run_command( live=live, name=package_name, suppress_demo_check=False, pyproject_toml=pyproject_toml, generate_space=True, generate_readme=True, type_mode="simple", _demo_path=_demo_path, _demo_dir=_demo_dir, _readme_path=_readme_path, space_url=None, _component_dir=name, simple=True, ) if build_frontend: live.update(":art: Building frontend") component_directory = path.resolve() node = shutil.which("node") if not node: raise ValueError( "node must be installed in order to run build command." ) gradio_node_path = subprocess.run( [node, "-e", "console.log(require.resolve('@gradio/preview'))"], cwd=Path(component_directory / "frontend"), check=False, capture_output=True, ) if gradio_node_path.returncode != 0: raise ValueError( "Could not find `@gradio/preview`. Run `npm i -D @gradio/preview` in your frontend folder." ) gradio_node_path = gradio_node_path.stdout.decode("utf-8").strip() node_cmds = [ node, gradio_node_path, "--component-directory", component_directory, "--root", gradio_template_path, "--mode", "build", "--python-path", python_path, ] pipe = subprocess.run( node_cmds, capture_output=True, text=True, check=False ) if pipe.returncode != 0: live.update(":red_square: Build failed!") live.update(pipe.stderr) live.update(pipe.stdout) return else: live.update(":white_check_mark: Build succeeded!") cmds = [python_path, "-m", "build", str(name)] live.update(f":construction_worker: Building... [grey37]({' '.join(cmds)})[/]") pipe = subprocess.run(cmds, capture_output=True, text=True, check=False) if pipe.returncode != 0: live.update(":red_square: Build failed!") live.update(pipe.stderr) else: live.update(":white_check_mark: Build succeeded!") live.update( f":ferris_wheel: Wheel located in [orange3]{str(name / 'dist')}[/]" )