multi-lang-build 0.2.5__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 +64 -0
- multi_lang_build/cli.py +320 -0
- multi_lang_build/compiler/__init__.py +22 -0
- multi_lang_build/compiler/base.py +309 -0
- multi_lang_build/compiler/go.py +468 -0
- multi_lang_build/compiler/pnpm.py +481 -0
- multi_lang_build/compiler/python.py +539 -0
- multi_lang_build/mirror/__init__.py +21 -0
- multi_lang_build/mirror/cli.py +340 -0
- multi_lang_build/mirror/config.py +251 -0
- multi_lang_build/py.typed +2 -0
- multi_lang_build/register.py +412 -0
- multi_lang_build/types.py +43 -0
- multi_lang_build-0.2.5.dist-info/METADATA +402 -0
- multi_lang_build-0.2.5.dist-info/RECORD +18 -0
- multi_lang_build-0.2.5.dist-info/WHEEL +4 -0
- multi_lang_build-0.2.5.dist-info/entry_points.txt +7 -0
- multi_lang_build-0.2.5.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,309 @@
|
|
|
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
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BuildConfig(TypedDict, total=False):
|
|
12
|
+
"""Build configuration for compiler."""
|
|
13
|
+
source_dir: Path
|
|
14
|
+
output_dir: Path
|
|
15
|
+
environment: dict[str, str]
|
|
16
|
+
mirror_enabled: bool
|
|
17
|
+
mirror_region: str
|
|
18
|
+
extra_args: list[str]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BuildResult(TypedDict):
|
|
22
|
+
"""Result of a build operation."""
|
|
23
|
+
success: bool
|
|
24
|
+
return_code: int
|
|
25
|
+
stdout: str
|
|
26
|
+
stderr: str
|
|
27
|
+
output_path: Path | None
|
|
28
|
+
duration_seconds: float
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CompilerInfo(TypedDict):
|
|
32
|
+
"""Information about a compiler."""
|
|
33
|
+
name: str
|
|
34
|
+
version: str
|
|
35
|
+
supported_mirrors: list[str]
|
|
36
|
+
default_mirror: str
|
|
37
|
+
executable: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CompilerBase(ABC):
|
|
41
|
+
"""Abstract base class for all language compilers."""
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def name(self) -> str:
|
|
46
|
+
"""Get the compiler name."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def version(self) -> str:
|
|
52
|
+
"""Get the compiler version."""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def supported_mirrors(self) -> list[str]:
|
|
58
|
+
"""Get list of supported mirror configurations."""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def get_info(self) -> CompilerInfo:
|
|
63
|
+
"""Get compiler information."""
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def build(
|
|
68
|
+
self,
|
|
69
|
+
source_dir: Path,
|
|
70
|
+
output_dir: Path,
|
|
71
|
+
*,
|
|
72
|
+
environment: dict[str, str] | None = None,
|
|
73
|
+
mirror_enabled: bool = True,
|
|
74
|
+
extra_args: list[str] | None = None,
|
|
75
|
+
stream_output: bool = True,
|
|
76
|
+
) -> BuildResult:
|
|
77
|
+
"""Execute the build process.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
source_dir: Source code directory
|
|
81
|
+
output_dir: Build output directory
|
|
82
|
+
environment: Additional environment variables
|
|
83
|
+
mirror_enabled: Whether to use mirror acceleration
|
|
84
|
+
extra_args: Additional arguments to pass to the build command
|
|
85
|
+
stream_output: Whether to stream output in real-time (default: True)
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
BuildResult containing success status and output information.
|
|
89
|
+
"""
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def clean(self, directory: Path) -> bool:
|
|
94
|
+
"""Clean build artifacts in the specified directory.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
directory: Directory to clean
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if successful, False otherwise.
|
|
101
|
+
"""
|
|
102
|
+
...
|
|
103
|
+
|
|
104
|
+
def _run_build(
|
|
105
|
+
self,
|
|
106
|
+
command: list[str],
|
|
107
|
+
source_dir: Path,
|
|
108
|
+
output_dir: Path,
|
|
109
|
+
*,
|
|
110
|
+
environment: dict[str, str] | None = None,
|
|
111
|
+
extra_args: list[str] | None = None,
|
|
112
|
+
stream_output: bool = True,
|
|
113
|
+
) -> BuildResult:
|
|
114
|
+
"""Execute a build command with timing and error handling.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
command: Build command to execute
|
|
118
|
+
source_dir: Source directory for the build
|
|
119
|
+
output_dir: Output directory for build artifacts
|
|
120
|
+
environment: Additional environment variables
|
|
121
|
+
extra_args: Additional arguments to append to command
|
|
122
|
+
stream_output: Whether to stream output in real-time (default: True)
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
BuildResult with success status and output information.
|
|
126
|
+
"""
|
|
127
|
+
full_command = command.copy()
|
|
128
|
+
if extra_args:
|
|
129
|
+
full_command.extend(extra_args)
|
|
130
|
+
|
|
131
|
+
env = environment.copy() if environment else {}
|
|
132
|
+
env.setdefault("PATH", "")
|
|
133
|
+
|
|
134
|
+
start_time = time.perf_counter()
|
|
135
|
+
|
|
136
|
+
if stream_output:
|
|
137
|
+
return self._run_build_stream(
|
|
138
|
+
full_command, source_dir, output_dir, env, start_time
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
return self._run_build_capture(
|
|
142
|
+
full_command, source_dir, output_dir, env, start_time
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def _run_build_stream(
|
|
146
|
+
self,
|
|
147
|
+
command: list[str],
|
|
148
|
+
source_dir: Path,
|
|
149
|
+
output_dir: Path,
|
|
150
|
+
env: dict[str, str],
|
|
151
|
+
start_time: float,
|
|
152
|
+
) -> BuildResult:
|
|
153
|
+
"""Execute build command with real-time output streaming.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
command: Build command to execute
|
|
157
|
+
source_dir: Source directory for the build
|
|
158
|
+
output_dir: Output directory for build artifacts
|
|
159
|
+
env: Environment variables
|
|
160
|
+
start_time: Start time for duration calculation
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
BuildResult with success status and output information.
|
|
164
|
+
"""
|
|
165
|
+
stdout_buffer = []
|
|
166
|
+
stderr_buffer = []
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
process = subprocess.Popen(
|
|
170
|
+
command,
|
|
171
|
+
cwd=source_dir,
|
|
172
|
+
stdout=subprocess.PIPE,
|
|
173
|
+
stderr=subprocess.PIPE,
|
|
174
|
+
text=True,
|
|
175
|
+
env=env,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Read stdout in real-time
|
|
179
|
+
for line in process.stdout:
|
|
180
|
+
line = line.rstrip('\n\r')
|
|
181
|
+
stdout_buffer.append(line)
|
|
182
|
+
print(line)
|
|
183
|
+
sys.stdout.flush()
|
|
184
|
+
|
|
185
|
+
# Read stderr in real-time
|
|
186
|
+
for line in process.stderr:
|
|
187
|
+
line = line.rstrip('\n\r')
|
|
188
|
+
stderr_buffer.append(line)
|
|
189
|
+
print(line, file=sys.stderr)
|
|
190
|
+
sys.stderr.flush()
|
|
191
|
+
|
|
192
|
+
return_code = process.wait()
|
|
193
|
+
duration = time.perf_counter() - start_time
|
|
194
|
+
|
|
195
|
+
return BuildResult(
|
|
196
|
+
success=return_code == 0,
|
|
197
|
+
return_code=return_code,
|
|
198
|
+
stdout='\n'.join(stdout_buffer),
|
|
199
|
+
stderr='\n'.join(stderr_buffer),
|
|
200
|
+
output_path=output_dir if return_code == 0 else None,
|
|
201
|
+
duration_seconds=duration,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
except subprocess.TimeoutExpired:
|
|
205
|
+
duration = time.perf_counter() - start_time
|
|
206
|
+
return BuildResult(
|
|
207
|
+
success=False,
|
|
208
|
+
return_code=-1,
|
|
209
|
+
stdout='\n'.join(stdout_buffer),
|
|
210
|
+
stderr="Build timed out after 1 hour",
|
|
211
|
+
output_path=None,
|
|
212
|
+
duration_seconds=duration,
|
|
213
|
+
)
|
|
214
|
+
except Exception as e:
|
|
215
|
+
duration = time.perf_counter() - start_time
|
|
216
|
+
return BuildResult(
|
|
217
|
+
success=False,
|
|
218
|
+
return_code=-2,
|
|
219
|
+
stdout='\n'.join(stdout_buffer),
|
|
220
|
+
stderr=f"Build error: {str(e)}",
|
|
221
|
+
output_path=None,
|
|
222
|
+
duration_seconds=duration,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def _run_build_capture(
|
|
226
|
+
self,
|
|
227
|
+
command: list[str],
|
|
228
|
+
source_dir: Path,
|
|
229
|
+
output_dir: Path,
|
|
230
|
+
env: dict[str, str],
|
|
231
|
+
start_time: float,
|
|
232
|
+
) -> BuildResult:
|
|
233
|
+
"""Execute build command with captured output (no real-time display).
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
command: Build command to execute
|
|
237
|
+
source_dir: Source directory for the build
|
|
238
|
+
output_dir: Output directory for build artifacts
|
|
239
|
+
env: Environment variables
|
|
240
|
+
start_time: Start time for duration calculation
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
BuildResult with success status and output information.
|
|
244
|
+
"""
|
|
245
|
+
try:
|
|
246
|
+
result = subprocess.run(
|
|
247
|
+
command,
|
|
248
|
+
cwd=source_dir,
|
|
249
|
+
capture_output=True,
|
|
250
|
+
text=True,
|
|
251
|
+
env=env,
|
|
252
|
+
timeout=3600, # 1 hour timeout
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
duration = time.perf_counter() - start_time
|
|
256
|
+
|
|
257
|
+
return BuildResult(
|
|
258
|
+
success=result.returncode == 0,
|
|
259
|
+
return_code=result.returncode,
|
|
260
|
+
stdout=result.stdout,
|
|
261
|
+
stderr=result.stderr,
|
|
262
|
+
output_path=output_dir if result.returncode == 0 else None,
|
|
263
|
+
duration_seconds=duration,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
except subprocess.TimeoutExpired:
|
|
267
|
+
duration = time.perf_counter() - start_time
|
|
268
|
+
return BuildResult(
|
|
269
|
+
success=False,
|
|
270
|
+
return_code=-1,
|
|
271
|
+
stdout="",
|
|
272
|
+
stderr="Build timed out after 1 hour",
|
|
273
|
+
output_path=None,
|
|
274
|
+
duration_seconds=duration,
|
|
275
|
+
)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
duration = time.perf_counter() - start_time
|
|
278
|
+
return BuildResult(
|
|
279
|
+
success=False,
|
|
280
|
+
return_code=-2,
|
|
281
|
+
stdout="",
|
|
282
|
+
stderr=f"Build error: {str(e)}",
|
|
283
|
+
output_path=None,
|
|
284
|
+
duration_seconds=duration,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def _validate_directory(self, directory: Path, create_if_not_exists: bool = True) -> Path:
|
|
288
|
+
"""Validate and optionally create a directory.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
directory: Directory path to validate
|
|
292
|
+
create_if_not_exists: Create directory if it doesn't exist
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Resolved absolute path
|
|
296
|
+
|
|
297
|
+
Raises:
|
|
298
|
+
ValueError: If directory cannot be created or is not a directory
|
|
299
|
+
"""
|
|
300
|
+
if not directory.exists():
|
|
301
|
+
if create_if_not_exists:
|
|
302
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
303
|
+
else:
|
|
304
|
+
raise ValueError(f"Directory does not exist: {directory}")
|
|
305
|
+
|
|
306
|
+
if not directory.is_dir():
|
|
307
|
+
raise ValueError(f"Path is not a directory: {directory}")
|
|
308
|
+
|
|
309
|
+
return directory.resolve()
|