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.
@@ -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()