File size: 6,714 Bytes
0ad74ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
from __future__ import annotations

import importlib
import shutil
import subprocess
from pathlib import Path
from typing import Annotated, Optional

import semantic_version
import typer
from tomlkit import dump, parse

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", python_path, "--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 building 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", encoding="utf-8") 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)
                raise SystemExit("Frontend build failed")
            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)
            raise SystemExit("Python build failed")
        else:
            live.update(":white_check_mark: Build succeeded!")
            live.update(
                f":ferris_wheel: Wheel located in [orange3]{str(name / 'dist')}[/]"
            )