multi-lang-build 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multi_lang_build/__init__.py +57 -0
- multi_lang_build/cli.py +256 -0
- multi_lang_build/compiler/__init__.py +22 -0
- multi_lang_build/compiler/base.py +196 -0
- multi_lang_build/compiler/go.py +452 -0
- multi_lang_build/compiler/pnpm.py +431 -0
- multi_lang_build/compiler/python.py +530 -0
- multi_lang_build/mirror/__init__.py +21 -0
- multi_lang_build/mirror/config.py +251 -0
- multi_lang_build/py.typed +2 -0
- multi_lang_build/register.py +383 -0
- multi_lang_build/types.py +43 -0
- multi_lang_build-0.2.0.dist-info/METADATA +383 -0
- multi_lang_build-0.2.0.dist-info/RECORD +17 -0
- multi_lang_build-0.2.0.dist-info/WHEEL +4 -0
- multi_lang_build-0.2.0.dist-info/entry_points.txt +6 -0
- multi_lang_build-0.2.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Multi-Lang Build - Multi-language automated build tool with domestic mirror acceleration."""
|
|
2
|
+
|
|
3
|
+
from multi_lang_build.compiler.base import BuildConfig, BuildResult, CompilerBase
|
|
4
|
+
from multi_lang_build.compiler.pnpm import PnpmCompiler
|
|
5
|
+
from multi_lang_build.compiler.go import GoCompiler
|
|
6
|
+
from multi_lang_build.compiler.python import PythonCompiler
|
|
7
|
+
from multi_lang_build.mirror.config import MirrorConfig, get_mirror_config
|
|
8
|
+
from multi_lang_build.register import register_skill
|
|
9
|
+
|
|
10
|
+
__version__ = "0.2.0"
|
|
11
|
+
__all__ = [
|
|
12
|
+
"BuildConfig",
|
|
13
|
+
"BuildResult",
|
|
14
|
+
"CompilerBase",
|
|
15
|
+
"PnpmCompiler",
|
|
16
|
+
"GoCompiler",
|
|
17
|
+
"PythonCompiler",
|
|
18
|
+
"MirrorConfig",
|
|
19
|
+
"get_mirror_config",
|
|
20
|
+
"register_skill",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main() -> None:
|
|
25
|
+
"""Main entry point for the multi-lang-build CLI tool."""
|
|
26
|
+
import sys
|
|
27
|
+
from multi_lang_build.cli import run_cli
|
|
28
|
+
|
|
29
|
+
run_cli(sys.argv[1:])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main_register() -> None:
|
|
33
|
+
"""Entry point for the register CLI tool."""
|
|
34
|
+
import sys
|
|
35
|
+
import argparse
|
|
36
|
+
from multi_lang_build.register import register_skill
|
|
37
|
+
|
|
38
|
+
parser = argparse.ArgumentParser(
|
|
39
|
+
description="Register multi-lang-build as an IDE skill",
|
|
40
|
+
prog="multi-lang-register",
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"ide",
|
|
44
|
+
nargs="?",
|
|
45
|
+
default="claude",
|
|
46
|
+
choices=["claude", "opencode", "trae", "codebuddy", "all"],
|
|
47
|
+
help="IDE to register with (default: claude)",
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"--version",
|
|
51
|
+
action="version",
|
|
52
|
+
version=f"%(prog)s {__version__}",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
args = parser.parse_args()
|
|
56
|
+
success = register_skill(args.ide)
|
|
57
|
+
sys.exit(0 if success else 1)
|
multi_lang_build/cli.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""CLI interface for auto-build tool."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Sequence, TypedDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BuildResult(TypedDict):
|
|
8
|
+
"""Result of a build operation."""
|
|
9
|
+
success: bool
|
|
10
|
+
return_code: int
|
|
11
|
+
stdout: str
|
|
12
|
+
stderr: str
|
|
13
|
+
output_path: Path | None
|
|
14
|
+
duration_seconds: float
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run_cli(args: Sequence[str]) -> None:
|
|
18
|
+
"""Run the CLI with the given arguments.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
args: Command line arguments (excluding program name)
|
|
22
|
+
"""
|
|
23
|
+
import argparse
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
parser = argparse.ArgumentParser(
|
|
27
|
+
description="AutoBuild - Multi-language automated build tool",
|
|
28
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
29
|
+
epilog="""
|
|
30
|
+
Examples:
|
|
31
|
+
%(prog)s pnpm ./src --output ./dist --mirror
|
|
32
|
+
%(prog)s go ./src --output ./bin/app --mirror
|
|
33
|
+
%(prog)s python ./src --output ./dist --mirror --poetry
|
|
34
|
+
|
|
35
|
+
Supported languages:
|
|
36
|
+
pnpm - Frontend JavaScript/TypeScript projects
|
|
37
|
+
go - Go projects with module support
|
|
38
|
+
python - Python projects with pip/poetry/pdm
|
|
39
|
+
""",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--version",
|
|
44
|
+
action="version",
|
|
45
|
+
version=f"%(prog)s {__import__('multi_lang_build').__version__}",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
subparsers = parser.add_subparsers(
|
|
49
|
+
dest="language",
|
|
50
|
+
title="language",
|
|
51
|
+
description="Build tool for specific language",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
pnpm_parser = subparsers.add_parser(
|
|
55
|
+
"pnpm",
|
|
56
|
+
help="Build pnpm-based frontend projects",
|
|
57
|
+
description="Build pnpm-based frontend projects with mirror acceleration",
|
|
58
|
+
)
|
|
59
|
+
pnpm_parser.add_argument("source_dir", type=Path, help="Source directory")
|
|
60
|
+
pnpm_parser.add_argument("-o", "--output", type=Path, required=True, help="Output directory")
|
|
61
|
+
pnpm_parser.add_argument("--mirror/--no-mirror", default=True, help="Enable/disable mirror acceleration")
|
|
62
|
+
pnpm_parser.add_argument("--script", type=str, help="Run specific npm script")
|
|
63
|
+
pnpm_parser.add_argument("--install", action="store_true", help="Install dependencies only")
|
|
64
|
+
|
|
65
|
+
go_parser = subparsers.add_parser(
|
|
66
|
+
"go",
|
|
67
|
+
help="Build Go projects",
|
|
68
|
+
description="Build Go projects with module support and mirror acceleration",
|
|
69
|
+
)
|
|
70
|
+
go_parser.add_argument("source_dir", type=Path, help="Source directory")
|
|
71
|
+
go_parser.add_argument("-o", "--output", type=Path, required=True, help="Output file or directory")
|
|
72
|
+
go_parser.add_argument("--mirror/--no-mirror", default=True, help="Enable/disable Go proxy mirror")
|
|
73
|
+
go_parser.add_argument("--ldflags", type=str, help="Linker flags")
|
|
74
|
+
go_parser.add_argument("--tags", type=str, help="Build tags (comma-separated)")
|
|
75
|
+
go_parser.add_argument("--test", action="store_true", help="Run tests instead of building")
|
|
76
|
+
go_parser.add_argument("--tidy", action="store_true", help="Run go mod tidy")
|
|
77
|
+
go_parser.add_argument("--all", action="store_true", help="Build all packages")
|
|
78
|
+
|
|
79
|
+
python_parser = subparsers.add_parser(
|
|
80
|
+
"python",
|
|
81
|
+
help="Build Python projects",
|
|
82
|
+
description="Build Python projects with pip/poetry/pdm and mirror acceleration",
|
|
83
|
+
)
|
|
84
|
+
python_parser.add_argument("source_dir", type=Path, help="Source directory")
|
|
85
|
+
python_parser.add_argument("-o", "--output", type=Path, required=True, help="Output directory")
|
|
86
|
+
python_parser.add_argument("--mirror/--no-mirror", default=True, help="Enable/disable PyPI mirror")
|
|
87
|
+
python_parser.add_argument("--install", action="store_true", help="Install dependencies only")
|
|
88
|
+
python_parser.add_argument("--dev", action="store_true", help="Include development dependencies")
|
|
89
|
+
python_parser.add_argument("--poetry", action="store_true", help="Force using poetry")
|
|
90
|
+
python_parser.add_argument("--create-venv", type=Path, help="Create virtual environment at path")
|
|
91
|
+
|
|
92
|
+
mirror_parser = subparsers.add_parser(
|
|
93
|
+
"mirror",
|
|
94
|
+
help="Manage mirror configurations",
|
|
95
|
+
description="List or configure mirror settings",
|
|
96
|
+
)
|
|
97
|
+
mirror_subparsers = mirror_parser.add_subparsers(dest="mirror_action")
|
|
98
|
+
|
|
99
|
+
list_parser = mirror_subparsers.add_parser("list", help="List available mirrors")
|
|
100
|
+
list_parser.add_argument("--language", type=str, help="Filter by language")
|
|
101
|
+
|
|
102
|
+
# Register subcommand for IDE integration
|
|
103
|
+
register_parser = subparsers.add_parser(
|
|
104
|
+
"register",
|
|
105
|
+
help="Register as IDE skill (Claude Code, OpenCode, Trae, CodeBuddy)",
|
|
106
|
+
description="Register multi-lang-build as a skill for various AI coding assistants and IDEs",
|
|
107
|
+
epilog="""
|
|
108
|
+
Examples:
|
|
109
|
+
%(prog)s register # Register with Claude Code (default)
|
|
110
|
+
%(prog)s register claude # Register with Claude Code
|
|
111
|
+
%(prog)s register opencode # Register with OpenCode
|
|
112
|
+
%(prog)s register trae # Register with Trae
|
|
113
|
+
%(prog)s register codebuddy # Register with CodeBuddy
|
|
114
|
+
%(prog)s register all # Register with all supported IDEs
|
|
115
|
+
""",
|
|
116
|
+
)
|
|
117
|
+
register_parser.add_argument(
|
|
118
|
+
"ide",
|
|
119
|
+
nargs="?",
|
|
120
|
+
default="claude",
|
|
121
|
+
choices=["claude", "opencode", "trae", "codebuddy", "all"],
|
|
122
|
+
help="IDE to register with (default: claude)",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
parsed_args = parser.parse_args(args)
|
|
126
|
+
|
|
127
|
+
if parsed_args.language is None:
|
|
128
|
+
parser.print_help()
|
|
129
|
+
sys.exit(1)
|
|
130
|
+
|
|
131
|
+
result: BuildResult | None = None
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
if parsed_args.language == "pnpm":
|
|
135
|
+
from multi_lang_build.compiler.pnpm import PnpmCompiler
|
|
136
|
+
|
|
137
|
+
compiler = PnpmCompiler()
|
|
138
|
+
|
|
139
|
+
if parsed_args.script:
|
|
140
|
+
result = compiler.run_script(
|
|
141
|
+
parsed_args.source_dir,
|
|
142
|
+
parsed_args.script,
|
|
143
|
+
mirror_enabled=parsed_args.mirror,
|
|
144
|
+
)
|
|
145
|
+
elif parsed_args.install:
|
|
146
|
+
result = compiler.install_dependencies(
|
|
147
|
+
parsed_args.source_dir,
|
|
148
|
+
mirror_enabled=parsed_args.mirror,
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
result = compiler.build(
|
|
152
|
+
parsed_args.source_dir,
|
|
153
|
+
parsed_args.output,
|
|
154
|
+
mirror_enabled=parsed_args.mirror,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
elif parsed_args.language == "go":
|
|
158
|
+
from multi_lang_build.compiler.go import GoCompiler
|
|
159
|
+
|
|
160
|
+
compiler = GoCompiler()
|
|
161
|
+
|
|
162
|
+
if parsed_args.test:
|
|
163
|
+
result = compiler.run_tests(
|
|
164
|
+
parsed_args.source_dir,
|
|
165
|
+
mirror_enabled=parsed_args.mirror,
|
|
166
|
+
)
|
|
167
|
+
elif parsed_args.tidy:
|
|
168
|
+
result = compiler.tidy_modules(
|
|
169
|
+
parsed_args.source_dir,
|
|
170
|
+
mirror_enabled=parsed_args.mirror,
|
|
171
|
+
)
|
|
172
|
+
elif parsed_args.all:
|
|
173
|
+
result = compiler.build_all(
|
|
174
|
+
parsed_args.source_dir,
|
|
175
|
+
parsed_args.output,
|
|
176
|
+
mirror_enabled=parsed_args.mirror,
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
tags = parsed_args.tags.split(",") if parsed_args.tags else None
|
|
180
|
+
result = compiler.build_binary(
|
|
181
|
+
parsed_args.source_dir,
|
|
182
|
+
parsed_args.output,
|
|
183
|
+
mirror_enabled=parsed_args.mirror,
|
|
184
|
+
ldflags=parsed_args.ldflags,
|
|
185
|
+
tags=tags,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
elif parsed_args.language == "python":
|
|
189
|
+
from multi_lang_build.compiler.python import PythonCompiler
|
|
190
|
+
|
|
191
|
+
compiler = PythonCompiler()
|
|
192
|
+
|
|
193
|
+
if parsed_args.create_venv:
|
|
194
|
+
result = compiler.create_venv(
|
|
195
|
+
parsed_args.create_venv,
|
|
196
|
+
mirror_enabled=parsed_args.mirror,
|
|
197
|
+
)
|
|
198
|
+
elif parsed_args.install:
|
|
199
|
+
result = compiler.install_dependencies(
|
|
200
|
+
parsed_args.source_dir,
|
|
201
|
+
mirror_enabled=parsed_args.mirror,
|
|
202
|
+
dev=parsed_args.dev,
|
|
203
|
+
poetry=parsed_args.poetry,
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
result = compiler.build(
|
|
207
|
+
parsed_args.source_dir,
|
|
208
|
+
parsed_args.output,
|
|
209
|
+
mirror_enabled=parsed_args.mirror,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
elif parsed_args.language == "mirror":
|
|
213
|
+
if parsed_args.mirror_action == "list":
|
|
214
|
+
from multi_lang_build.mirror.config import get_all_mirror_names, get_mirror_config
|
|
215
|
+
|
|
216
|
+
mirrors = get_all_mirror_names()
|
|
217
|
+
if parsed_args.language:
|
|
218
|
+
mirrors = [m for m in mirrors if m in parsed_args.language.split(",")]
|
|
219
|
+
|
|
220
|
+
print("Available mirrors:")
|
|
221
|
+
for mirror_name in sorted(mirrors):
|
|
222
|
+
config = get_mirror_config(mirror_name)
|
|
223
|
+
if config:
|
|
224
|
+
print(f" {mirror_name}: {config['url']}")
|
|
225
|
+
else:
|
|
226
|
+
mirror_parser.print_help()
|
|
227
|
+
|
|
228
|
+
elif parsed_args.language == "register":
|
|
229
|
+
from multi_lang_build.register import register_skill
|
|
230
|
+
|
|
231
|
+
success = register_skill(parsed_args.ide)
|
|
232
|
+
if success:
|
|
233
|
+
print("\n✅ Registration completed successfully!")
|
|
234
|
+
print(f"\nYou can now use 'multi-lang-build' commands in {parsed_args.ide}")
|
|
235
|
+
else:
|
|
236
|
+
print("\n❌ Registration failed for some IDEs.")
|
|
237
|
+
sys.exit(0 if success else 1)
|
|
238
|
+
|
|
239
|
+
else:
|
|
240
|
+
parser.print_help()
|
|
241
|
+
sys.exit(1)
|
|
242
|
+
|
|
243
|
+
if result is not None and result["success"]:
|
|
244
|
+
print(f"Build completed successfully in {result['duration_seconds']:.2f}s")
|
|
245
|
+
if result["output_path"]:
|
|
246
|
+
print(f"Output: {result['output_path']}")
|
|
247
|
+
sys.exit(0)
|
|
248
|
+
elif result is not None:
|
|
249
|
+
print(f"Build failed with code {result['return_code']}")
|
|
250
|
+
if result["stderr"]:
|
|
251
|
+
print(f"Error: {result['stderr']}")
|
|
252
|
+
sys.exit(1)
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
256
|
+
sys.exit(2)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Compiler module for auto-build tool."""
|
|
2
|
+
|
|
3
|
+
from multi_lang_build.compiler.base import (
|
|
4
|
+
BuildConfig,
|
|
5
|
+
BuildResult,
|
|
6
|
+
CompilerInfo,
|
|
7
|
+
CompilerBase,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from multi_lang_build.compiler.pnpm import PnpmCompiler
|
|
11
|
+
from multi_lang_build.compiler.go import GoCompiler
|
|
12
|
+
from multi_lang_build.compiler.python import PythonCompiler
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"BuildConfig",
|
|
16
|
+
"BuildResult",
|
|
17
|
+
"CompilerInfo",
|
|
18
|
+
"CompilerBase",
|
|
19
|
+
"PnpmCompiler",
|
|
20
|
+
"GoCompiler",
|
|
21
|
+
"PythonCompiler",
|
|
22
|
+
]
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Base compiler interface and common types."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TypeAlias, TypedDict, NotRequired
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BuildConfig(TypedDict, total=False):
|
|
10
|
+
"""Build configuration for compiler."""
|
|
11
|
+
source_dir: Path
|
|
12
|
+
output_dir: Path
|
|
13
|
+
environment: dict[str, str]
|
|
14
|
+
mirror_enabled: bool
|
|
15
|
+
mirror_region: str
|
|
16
|
+
extra_args: list[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BuildResult(TypedDict):
|
|
20
|
+
"""Result of a build operation."""
|
|
21
|
+
success: bool
|
|
22
|
+
return_code: int
|
|
23
|
+
stdout: str
|
|
24
|
+
stderr: str
|
|
25
|
+
output_path: Path | None
|
|
26
|
+
duration_seconds: float
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CompilerInfo(TypedDict):
|
|
30
|
+
"""Information about a compiler."""
|
|
31
|
+
name: str
|
|
32
|
+
version: str
|
|
33
|
+
supported_mirrors: list[str]
|
|
34
|
+
default_mirror: str
|
|
35
|
+
executable: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CompilerBase(ABC):
|
|
39
|
+
"""Abstract base class for all language compilers."""
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def name(self) -> str:
|
|
44
|
+
"""Get the compiler name."""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def version(self) -> str:
|
|
50
|
+
"""Get the compiler version."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def supported_mirrors(self) -> list[str]:
|
|
56
|
+
"""Get list of supported mirror configurations."""
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def get_info(self) -> CompilerInfo:
|
|
61
|
+
"""Get compiler information."""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def build(
|
|
66
|
+
self,
|
|
67
|
+
source_dir: Path,
|
|
68
|
+
output_dir: Path,
|
|
69
|
+
*,
|
|
70
|
+
environment: dict[str, str] | None = None,
|
|
71
|
+
mirror_enabled: bool = True,
|
|
72
|
+
extra_args: list[str] | None = None,
|
|
73
|
+
) -> BuildResult:
|
|
74
|
+
"""Execute the build process.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
source_dir: Source code directory
|
|
78
|
+
output_dir: Build output directory
|
|
79
|
+
environment: Additional environment variables
|
|
80
|
+
mirror_enabled: Whether to use mirror acceleration
|
|
81
|
+
extra_args: Additional arguments to pass to the build command
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
BuildResult containing success status and output information.
|
|
85
|
+
"""
|
|
86
|
+
...
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def clean(self, directory: Path) -> bool:
|
|
90
|
+
"""Clean build artifacts in the specified directory.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
directory: Directory to clean
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
True if successful, False otherwise.
|
|
97
|
+
"""
|
|
98
|
+
...
|
|
99
|
+
|
|
100
|
+
def _run_build(
|
|
101
|
+
self,
|
|
102
|
+
command: list[str],
|
|
103
|
+
source_dir: Path,
|
|
104
|
+
output_dir: Path,
|
|
105
|
+
*,
|
|
106
|
+
environment: dict[str, str] | None = None,
|
|
107
|
+
extra_args: list[str] | None = None,
|
|
108
|
+
) -> BuildResult:
|
|
109
|
+
"""Execute a build command with timing and error handling.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
command: Build command to execute
|
|
113
|
+
source_dir: Source directory for the build
|
|
114
|
+
output_dir: Output directory for build artifacts
|
|
115
|
+
environment: Additional environment variables
|
|
116
|
+
extra_args: Additional arguments to append to command
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
BuildResult with success status and output information.
|
|
120
|
+
"""
|
|
121
|
+
import subprocess
|
|
122
|
+
|
|
123
|
+
full_command = command.copy()
|
|
124
|
+
if extra_args:
|
|
125
|
+
full_command.extend(extra_args)
|
|
126
|
+
|
|
127
|
+
env = environment.copy() if environment else {}
|
|
128
|
+
env.setdefault("PATH", "")
|
|
129
|
+
|
|
130
|
+
start_time = time.perf_counter()
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
result = subprocess.run(
|
|
134
|
+
full_command,
|
|
135
|
+
cwd=source_dir,
|
|
136
|
+
capture_output=True,
|
|
137
|
+
text=True,
|
|
138
|
+
env=env,
|
|
139
|
+
timeout=3600, # 1 hour timeout
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
duration = time.perf_counter() - start_time
|
|
143
|
+
|
|
144
|
+
return BuildResult(
|
|
145
|
+
success=result.returncode == 0,
|
|
146
|
+
return_code=result.returncode,
|
|
147
|
+
stdout=result.stdout,
|
|
148
|
+
stderr=result.stderr,
|
|
149
|
+
output_path=output_dir if result.returncode == 0 else None,
|
|
150
|
+
duration_seconds=duration,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
except subprocess.TimeoutExpired:
|
|
154
|
+
duration = time.perf_counter() - start_time
|
|
155
|
+
return BuildResult(
|
|
156
|
+
success=False,
|
|
157
|
+
return_code=-1,
|
|
158
|
+
stdout="",
|
|
159
|
+
stderr="Build timed out after 1 hour",
|
|
160
|
+
output_path=None,
|
|
161
|
+
duration_seconds=duration,
|
|
162
|
+
)
|
|
163
|
+
except Exception as e:
|
|
164
|
+
duration = time.perf_counter() - start_time
|
|
165
|
+
return BuildResult(
|
|
166
|
+
success=False,
|
|
167
|
+
return_code=-2,
|
|
168
|
+
stdout="",
|
|
169
|
+
stderr=f"Build error: {str(e)}",
|
|
170
|
+
output_path=None,
|
|
171
|
+
duration_seconds=duration,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def _validate_directory(self, directory: Path, create_if_not_exists: bool = True) -> Path:
|
|
175
|
+
"""Validate and optionally create a directory.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
directory: Directory path to validate
|
|
179
|
+
create_if_not_exists: Create directory if it doesn't exist
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Resolved absolute path
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
ValueError: If directory cannot be created or is not a directory
|
|
186
|
+
"""
|
|
187
|
+
if not directory.exists():
|
|
188
|
+
if create_if_not_exists:
|
|
189
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
190
|
+
else:
|
|
191
|
+
raise ValueError(f"Directory does not exist: {directory}")
|
|
192
|
+
|
|
193
|
+
if not directory.is_dir():
|
|
194
|
+
raise ValueError(f"Path is not a directory: {directory}")
|
|
195
|
+
|
|
196
|
+
return directory.resolve()
|