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.
@@ -0,0 +1,452 @@
1
+ """Go compiler with mirror acceleration support."""
2
+
3
+ from pathlib import Path
4
+ from typing import Final, Literal
5
+ import shutil
6
+ import subprocess
7
+ import os
8
+ import tempfile
9
+
10
+ from multi_lang_build.compiler.base import CompilerBase, BuildResult, CompilerInfo
11
+ from multi_lang_build.mirror.config import (
12
+ get_mirror_config,
13
+ apply_mirror_environment,
14
+ GO_PROXY_GOPROXY_CN,
15
+ GO_PROXY_GOPROXY_IO,
16
+ GO_PROXY_GOVIP_CN,
17
+ DEFAULT_GO_MIRROR,
18
+ )
19
+
20
+
21
+ class GoCompiler(CompilerBase):
22
+ """Compiler for Go projects with module support."""
23
+
24
+ NAME: Final[str] = "go"
25
+ DEFAULT_MIRROR: Final[str] = DEFAULT_GO_MIRROR
26
+
27
+ def __init__(
28
+ self,
29
+ go_path: str | None = None,
30
+ mirror: str | None = None,
31
+ ) -> None:
32
+ """Initialize the Go compiler.
33
+
34
+ Args:
35
+ go_path: Optional path to go executable. If None, uses system PATH.
36
+ mirror: Mirror configuration name (e.g., "go", "go_qiniu", "go_vip").
37
+ Defaults to DEFAULT_GO_MIRROR (goproxy.cn).
38
+ """
39
+ self._go_path = go_path
40
+ self._mirror = mirror
41
+ self._version_cache: str | None = None
42
+
43
+ @property
44
+ def name(self) -> str:
45
+ """Get the compiler name."""
46
+ return self.NAME
47
+
48
+ @property
49
+ def version(self) -> str:
50
+ """Get the Go version."""
51
+ if self._version_cache:
52
+ return self._version_cache
53
+
54
+ go_executable = self._get_executable_path()
55
+
56
+ try:
57
+ result = subprocess.run(
58
+ [go_executable, "version"],
59
+ capture_output=True,
60
+ text=True,
61
+ timeout=10,
62
+ )
63
+ output = result.stdout.strip()
64
+ if output:
65
+ parts = output.split()
66
+ if len(parts) >= 3:
67
+ self._version_cache = parts[2]
68
+ else:
69
+ self._version_cache = output
70
+ else:
71
+ self._version_cache = "unknown"
72
+ except Exception:
73
+ self._version_cache = "unknown"
74
+
75
+ return self._version_cache
76
+
77
+ @property
78
+ def supported_mirrors(self) -> list[str]:
79
+ """Get list of supported mirror configurations."""
80
+ return ["go", "go_qiniu", "go_vip"]
81
+
82
+ @property
83
+ def current_mirror(self) -> str:
84
+ """Get the current mirror configuration name."""
85
+ return self._mirror or "go"
86
+
87
+ def get_info(self) -> CompilerInfo:
88
+ """Get compiler information."""
89
+ return {
90
+ "name": self.name,
91
+ "version": self.version,
92
+ "supported_mirrors": self.supported_mirrors,
93
+ "default_mirror": self.DEFAULT_MIRROR,
94
+ "executable": self._get_executable_path(),
95
+ }
96
+
97
+ def set_mirror(self, mirror: str) -> None:
98
+ """Set the mirror configuration.
99
+
100
+ Args:
101
+ mirror: Mirror configuration name (e.g., "go", "go_qiniu", "go_vip")
102
+ """
103
+ self._mirror = mirror
104
+
105
+ def build(
106
+ self,
107
+ source_dir: Path,
108
+ output_dir: Path,
109
+ *,
110
+ environment: dict[str, str] | None = None,
111
+ mirror_enabled: bool = True,
112
+ extra_args: list[str] | None = None,
113
+ ) -> BuildResult:
114
+ """Execute the Go build process.
115
+
116
+ Args:
117
+ source_dir: Source code directory
118
+ output_dir: Build output directory (for binary)
119
+ environment: Additional environment variables
120
+ mirror_enabled: Whether to use Go proxy mirror
121
+ extra_args: Additional arguments to pass to go build
122
+
123
+ Returns:
124
+ BuildResult containing success status and output information.
125
+ """
126
+ go_executable = self._get_executable_path()
127
+
128
+ # Validate directories
129
+ source_dir = self._validate_directory(source_dir, create_if_not_exists=False)
130
+ output_dir = self._validate_directory(output_dir, create_if_not_exists=True)
131
+
132
+ # Prepare environment
133
+ env = environment.copy() if environment else {}
134
+
135
+ if mirror_enabled:
136
+ mirror_key = self._mirror or "go"
137
+ env = apply_mirror_environment(mirror_key, env)
138
+
139
+ # Check for go.mod
140
+ go_mod = source_dir / "go.mod"
141
+ has_go_mod = go_mod.exists()
142
+
143
+ # Download dependencies if go.mod exists
144
+ if has_go_mod:
145
+ deps_result = self._run_build(
146
+ [go_executable, "mod", "download"],
147
+ source_dir,
148
+ output_dir,
149
+ environment=env,
150
+ )
151
+
152
+ if not deps_result["success"]:
153
+ return deps_result
154
+
155
+ # Build the project
156
+ build_args = [go_executable, "build", "-o", str(output_dir)]
157
+
158
+ if extra_args:
159
+ build_args.extend(extra_args)
160
+
161
+ return self._run_build(
162
+ build_args,
163
+ source_dir,
164
+ output_dir,
165
+ environment=env,
166
+ )
167
+
168
+ def build_binary(
169
+ self,
170
+ source_dir: Path,
171
+ output_path: Path,
172
+ *,
173
+ target: str | Path | None = None,
174
+ environment: dict[str, str] | None = None,
175
+ mirror_enabled: bool = True,
176
+ ldflags: str | None = None,
177
+ tags: list[str] | None = None,
178
+ ) -> BuildResult:
179
+ """Build a specific Go binary.
180
+
181
+ Args:
182
+ source_dir: Source code directory (working directory for build)
183
+ output_path: Output path for the binary
184
+ target: Build target - can be a .go file (e.g., "server.go", "cmd/server/main.go")
185
+ or a directory (e.g., "./cmd/server"). If None, builds current directory.
186
+ environment: Additional environment variables
187
+ mirror_enabled: Whether to use Go proxy mirror
188
+ ldflags: Linker flags to pass to the compiler
189
+ tags: Build tags to pass to the compiler
190
+
191
+ Returns:
192
+ BuildResult containing success status and output information.
193
+ """
194
+ go_executable = self._get_executable_path()
195
+
196
+ source_dir = self._validate_directory(source_dir, create_if_not_exists=False)
197
+
198
+ # Ensure output directory exists
199
+ output_path.parent.mkdir(parents=True, exist_ok=True)
200
+
201
+ env = environment.copy() if environment else {}
202
+
203
+ if mirror_enabled:
204
+ mirror_key = self._mirror or "go"
205
+ env = apply_mirror_environment(mirror_key, env)
206
+
207
+ build_args = [go_executable, "build", "-o", str(output_path)]
208
+
209
+ if ldflags:
210
+ build_args.extend(["-ldflags", ldflags])
211
+
212
+ if tags:
213
+ build_args.extend(["-tags", ",".join(tags)])
214
+
215
+ # Add target if specified (target is relative to source_dir)
216
+ if target:
217
+ build_args.append(str(target))
218
+
219
+ return self._run_build(
220
+ build_args,
221
+ source_dir,
222
+ output_path.parent,
223
+ environment=env,
224
+ )
225
+
226
+ def build_all(
227
+ self,
228
+ source_dir: Path,
229
+ output_dir: Path,
230
+ *,
231
+ environment: dict[str, str] | None = None,
232
+ mirror_enabled: bool = True,
233
+ platform: str | None = None,
234
+ ) -> BuildResult:
235
+ """Build all binaries defined in a Makefile or build script.
236
+
237
+ Args:
238
+ source_dir: Source code directory
239
+ output_dir: Output directory for all binaries
240
+ environment: Additional environment variables
241
+ mirror_enabled: Whether to use Go proxy mirror
242
+ platform: Target platform (e.g., "linux/amd64", "windows/amd64")
243
+
244
+ Returns:
245
+ BuildResult containing success status and output information.
246
+ """
247
+ go_executable = self._get_executable_path()
248
+
249
+ source_dir = self._validate_directory(source_dir, create_if_not_exists=False)
250
+ output_dir = self._validate_directory(output_dir, create_if_not_exists=True)
251
+
252
+ env = environment.copy() if environment else {}
253
+
254
+ if mirror_enabled:
255
+ mirror_key = self._mirror or "go"
256
+ env = apply_mirror_environment(mirror_key, env)
257
+
258
+ build_args = [go_executable, "build", "-o", str(output_dir), "./..."]
259
+
260
+ if platform:
261
+ env["GOOS"], env["GOARCH"] = platform.split("/")
262
+
263
+ return self._run_build(
264
+ build_args,
265
+ source_dir,
266
+ output_dir,
267
+ environment=env,
268
+ )
269
+
270
+ def run_tests(
271
+ self,
272
+ source_dir: Path,
273
+ *,
274
+ environment: dict[str, str] | None = None,
275
+ mirror_enabled: bool = True,
276
+ verbose: bool = True,
277
+ race: bool = False,
278
+ ) -> BuildResult:
279
+ """Run Go tests.
280
+
281
+ Args:
282
+ source_dir: Source code directory
283
+ environment: Additional environment variables
284
+ mirror_enabled: Whether to use Go proxy mirror
285
+ verbose: Enable verbose test output
286
+ race: Enable race detector
287
+
288
+ Returns:
289
+ BuildResult containing success status and output information.
290
+ """
291
+ go_executable = self._get_executable_path()
292
+
293
+ source_dir = self._validate_directory(source_dir, create_if_not_exists=False)
294
+
295
+ env = environment.copy() if environment else {}
296
+
297
+ if mirror_enabled:
298
+ mirror_key = self._mirror or "go"
299
+ env = apply_mirror_environment(mirror_key, env)
300
+
301
+ test_args = [go_executable, "test"]
302
+
303
+ if verbose:
304
+ test_args.append("-v")
305
+
306
+ if race:
307
+ test_args.append("-race")
308
+
309
+ return self._run_build(
310
+ test_args,
311
+ source_dir,
312
+ source_dir,
313
+ environment=env,
314
+ )
315
+
316
+ def tidy_modules(
317
+ self,
318
+ source_dir: Path,
319
+ *,
320
+ environment: dict[str, str] | None = None,
321
+ mirror_enabled: bool = True,
322
+ ) -> BuildResult:
323
+ """Run go mod tidy to clean up dependencies.
324
+
325
+ Args:
326
+ source_dir: Source code directory
327
+ environment: Additional environment variables
328
+ mirror_enabled: Whether to use Go proxy mirror
329
+
330
+ Returns:
331
+ BuildResult containing success status and output information.
332
+ """
333
+ go_executable = self._get_executable_path()
334
+
335
+ source_dir = self._validate_directory(source_dir, create_if_not_exists=False)
336
+
337
+ env = environment.copy() if environment else {}
338
+
339
+ if mirror_enabled:
340
+ mirror_key = self._mirror or "go"
341
+ env = apply_mirror_environment(mirror_key, env)
342
+
343
+ return self._run_build(
344
+ [go_executable, "mod", "tidy"],
345
+ source_dir,
346
+ source_dir,
347
+ environment=env,
348
+ )
349
+
350
+ def clean(self, directory: Path) -> bool:
351
+ """Clean Go build artifacts in the specified directory.
352
+
353
+ Args:
354
+ directory: Directory to clean
355
+
356
+ Returns:
357
+ True if successful, False otherwise.
358
+ """
359
+ import shutil
360
+
361
+ try:
362
+ directory = self._validate_directory(directory, create_if_not_exists=False)
363
+
364
+ # Remove go.sum
365
+ go_sum = directory / "go.sum"
366
+ if go_sum.exists():
367
+ go_sum.unlink()
368
+
369
+ # Remove vendor directory
370
+ vendor = directory / "vendor"
371
+ if vendor.exists():
372
+ shutil.rmtree(vendor)
373
+
374
+ # Remove bin directory
375
+ bin_dir = directory / "bin"
376
+ if bin_dir.exists():
377
+ shutil.rmtree(bin_dir)
378
+
379
+ return True
380
+
381
+ except Exception:
382
+ return False
383
+
384
+ def _get_executable_path(self) -> str:
385
+ """Get the go executable path."""
386
+ if self._go_path:
387
+ return self._go_path
388
+
389
+ go_path = shutil.which("go")
390
+ if go_path is None:
391
+ raise RuntimeError("go not found in PATH. Please install Go or provide go_path.")
392
+
393
+ return go_path
394
+
395
+ @staticmethod
396
+ def create(source_dir: Path, *, mirror_enabled: bool = True) -> "GoCompiler":
397
+ """Factory method to create a GoCompiler instance.
398
+
399
+ Args:
400
+ source_dir: Source directory for the project
401
+ mirror_enabled: Whether to enable mirror acceleration by default
402
+
403
+ Returns:
404
+ Configured GoCompiler instance
405
+ """
406
+ compiler = GoCompiler()
407
+ return compiler
408
+
409
+
410
+ def main() -> None:
411
+ """Go compiler CLI entry point."""
412
+ import argparse
413
+ import sys
414
+
415
+ parser = argparse.ArgumentParser(description="Go Build Compiler")
416
+ parser.add_argument("source_dir", type=Path, help="Source directory (working directory)")
417
+ parser.add_argument("-o", "--output", type=Path, required=True, help="Output binary path")
418
+ parser.add_argument("-t", "--target", type=str, help="Build target: .go file (e.g., server.go, cmd/main.go) or directory (e.g., ./cmd/server)")
419
+ parser.add_argument("--mirror", action="store_true", default=True, help="Enable Go proxy mirror")
420
+ parser.add_argument("--no-mirror", dest="mirror", action="store_false", help="Disable Go proxy mirror")
421
+ parser.add_argument("--ldflags", type=str, help="Linker flags")
422
+ parser.add_argument("--tags", type=str, help="Build tags (comma-separated)")
423
+ parser.add_argument("--test", action="store_true", help="Run tests instead of building")
424
+ parser.add_argument("--tidy", action="store_true", help="Run go mod tidy")
425
+ parser.add_argument("--all", action="store_true", help="Build all packages")
426
+
427
+ args = parser.parse_args()
428
+
429
+ compiler = GoCompiler()
430
+
431
+ if args.test:
432
+ result = compiler.run_tests(args.source_dir, mirror_enabled=args.mirror)
433
+ elif args.tidy:
434
+ result = compiler.tidy_modules(args.source_dir, mirror_enabled=args.mirror)
435
+ elif args.all:
436
+ result = compiler.build_all(
437
+ args.source_dir,
438
+ args.output,
439
+ mirror_enabled=args.mirror,
440
+ )
441
+ else:
442
+ tags = args.tags.split(",") if args.tags else None
443
+ result = compiler.build_binary(
444
+ args.source_dir,
445
+ args.output,
446
+ target=args.target,
447
+ mirror_enabled=args.mirror,
448
+ ldflags=args.ldflags,
449
+ tags=tags,
450
+ )
451
+
452
+ sys.exit(0 if result["success"] else 1)