multi-lang-build 0.2.1__tar.gz → 0.2.3__tar.gz

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.
Files changed (22) hide show
  1. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/PKG-INFO +1 -1
  2. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/pyproject.toml +2 -1
  3. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/__init__.py +8 -1
  4. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/cli.py +87 -23
  5. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/compiler/base.py +135 -22
  6. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/compiler/go.py +54 -38
  7. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/compiler/pnpm.py +83 -33
  8. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/compiler/python.py +24 -15
  9. multi_lang_build-0.2.3/src/multi_lang_build/mirror/cli.py +340 -0
  10. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/.gitignore +0 -0
  11. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/LICENSE +0 -0
  12. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/README.md +0 -0
  13. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/compiler/__init__.py +0 -0
  14. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/mirror/__init__.py +0 -0
  15. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/mirror/config.py +0 -0
  16. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/py.typed +0 -0
  17. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/register.py +0 -0
  18. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/src/multi_lang_build/types.py +0 -0
  19. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/tests/__init__.py +0 -0
  20. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/tests/conftest.py +0 -0
  21. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/tests/test_compiler.py +0 -0
  22. {multi_lang_build-0.2.1 → multi_lang_build-0.2.3}/tests/test_mirror.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multi-lang-build
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Multi-language automated build tool with domestic mirror acceleration support
5
5
  Project-URL: Homepage, https://github.com/example/multi-lang-build
6
6
  Project-URL: Repository, https://github.com/example/multi-lang-build
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "multi-lang-build"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  description = "Multi-language automated build tool with domestic mirror acceleration support"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -43,6 +43,7 @@ pnpm-build = "multi_lang_build.compiler.pnpm:main"
43
43
  go-build = "multi_lang_build.compiler.go:main"
44
44
  python-build = "multi_lang_build.compiler.python:main"
45
45
  multi-lang-register = "multi_lang_build:main_register"
46
+ multi-lang-mirror = "multi_lang_build.mirror.cli:mirror_main"
46
47
 
47
48
  [project.urls]
48
49
  Homepage = "https://github.com/example/multi-lang-build"
@@ -7,7 +7,7 @@ from multi_lang_build.compiler.python import PythonCompiler
7
7
  from multi_lang_build.mirror.config import MirrorConfig, get_mirror_config
8
8
  from multi_lang_build.register import register_skill
9
9
 
10
- __version__ = "0.2.0"
10
+ __version__ = "0.2.3"
11
11
  __all__ = [
12
12
  "BuildConfig",
13
13
  "BuildResult",
@@ -55,3 +55,10 @@ def main_register() -> None:
55
55
  args = parser.parse_args()
56
56
  success = register_skill(args.ide)
57
57
  sys.exit(0 if success else 1)
58
+
59
+
60
+ def main_mirror() -> None:
61
+ """Entry point for the mirror CLI tool."""
62
+ from multi_lang_build.mirror.cli import mirror_main
63
+
64
+ mirror_main()
@@ -61,7 +61,8 @@ Supported languages:
61
61
  pnpm_parser.add_argument("--mirror/--no-mirror", default=True, help="Enable/disable mirror acceleration")
62
62
  pnpm_parser.add_argument("--script", type=str, help="Run specific npm script")
63
63
  pnpm_parser.add_argument("--install", action="store_true", help="Install dependencies only")
64
-
64
+ pnpm_parser.add_argument("--stream/--no-stream", dest="stream_output", default=True, help="Enable/disable real-time output streaming (default: enabled)")
65
+
65
66
  go_parser = subparsers.add_parser(
66
67
  "go",
67
68
  help="Build Go projects",
@@ -75,7 +76,8 @@ Supported languages:
75
76
  go_parser.add_argument("--test", action="store_true", help="Run tests instead of building")
76
77
  go_parser.add_argument("--tidy", action="store_true", help="Run go mod tidy")
77
78
  go_parser.add_argument("--all", action="store_true", help="Build all packages")
78
-
79
+ go_parser.add_argument("--stream/--no-stream", dest="stream_output", default=True, help="Enable/disable real-time output streaming (default: enabled)")
80
+
79
81
  python_parser = subparsers.add_parser(
80
82
  "python",
81
83
  help="Build Python projects",
@@ -88,16 +90,59 @@ Supported languages:
88
90
  python_parser.add_argument("--dev", action="store_true", help="Include development dependencies")
89
91
  python_parser.add_argument("--poetry", action="store_true", help="Force using poetry")
90
92
  python_parser.add_argument("--create-venv", type=Path, help="Create virtual environment at path")
93
+ python_parser.add_argument("--stream/--no-stream", dest="stream_output", default=True, help="Enable/disable real-time output streaming (default: enabled)")
91
94
 
92
95
  mirror_parser = subparsers.add_parser(
93
96
  "mirror",
94
97
  help="Manage mirror configurations",
95
- description="List or configure mirror settings",
98
+ description="List or configure domestic mirror acceleration settings",
99
+ epilog="""
100
+ Examples:
101
+ %(prog)s mirror list List all available mirrors
102
+ %(prog)s mirror set pip Configure pip mirror (default)
103
+ %(prog)s mirror set go Configure go proxy mirror
104
+ %(prog)s mirror set pnpm Configure pnpm registry mirror
105
+ %(prog)s mirror show Show current configuration
106
+ %(prog)s mirror reset Reset configuration
107
+ """,
96
108
  )
97
109
  mirror_subparsers = mirror_parser.add_subparsers(dest="mirror_action")
98
110
 
99
111
  list_parser = mirror_subparsers.add_parser("list", help="List available mirrors")
100
- list_parser.add_argument("--language", type=str, help="Filter by language")
112
+ list_parser.add_argument("--json", action="store_true", help="Output as JSON")
113
+
114
+ set_parser = mirror_subparsers.add_parser("set", help="Set mirror configuration")
115
+ set_parser.add_argument(
116
+ "type",
117
+ type=str,
118
+ default="pip",
119
+ nargs="?",
120
+ choices=["pip", "go", "npm", "pnpm"],
121
+ help="Package manager type (default: pip)",
122
+ )
123
+ set_parser.add_argument(
124
+ "mirror",
125
+ type=str,
126
+ nargs="?",
127
+ default="pip",
128
+ help="Mirror to use",
129
+ )
130
+
131
+ show_parser = mirror_subparsers.add_parser("show", help="Show current configuration")
132
+ show_parser.add_argument(
133
+ "--global",
134
+ dest="global_level",
135
+ action="store_true",
136
+ help="Show global config instead of project-level",
137
+ )
138
+
139
+ reset_parser = mirror_subparsers.add_parser("reset", help="Reset configuration")
140
+ reset_parser.add_argument(
141
+ "--global",
142
+ dest="global_level",
143
+ action="store_true",
144
+ help="Reset global config instead of project-level",
145
+ )
101
146
 
102
147
  # Register subcommand for IDE integration
103
148
  register_parser = subparsers.add_parser(
@@ -133,9 +178,9 @@ Examples:
133
178
  try:
134
179
  if parsed_args.language == "pnpm":
135
180
  from multi_lang_build.compiler.pnpm import PnpmCompiler
136
-
181
+
137
182
  compiler = PnpmCompiler()
138
-
183
+
139
184
  if parsed_args.script:
140
185
  result = compiler.run_script(
141
186
  parsed_args.source_dir,
@@ -152,13 +197,14 @@ Examples:
152
197
  parsed_args.source_dir,
153
198
  parsed_args.output,
154
199
  mirror_enabled=parsed_args.mirror,
200
+ stream_output=parsed_args.stream_output,
155
201
  )
156
-
202
+
157
203
  elif parsed_args.language == "go":
158
204
  from multi_lang_build.compiler.go import GoCompiler
159
-
205
+
160
206
  compiler = GoCompiler()
161
-
207
+
162
208
  if parsed_args.test:
163
209
  result = compiler.run_tests(
164
210
  parsed_args.source_dir,
@@ -174,6 +220,7 @@ Examples:
174
220
  parsed_args.source_dir,
175
221
  parsed_args.output,
176
222
  mirror_enabled=parsed_args.mirror,
223
+ stream_output=parsed_args.stream_output,
177
224
  )
178
225
  else:
179
226
  tags = parsed_args.tags.split(",") if parsed_args.tags else None
@@ -183,13 +230,14 @@ Examples:
183
230
  mirror_enabled=parsed_args.mirror,
184
231
  ldflags=parsed_args.ldflags,
185
232
  tags=tags,
233
+ stream_output=parsed_args.stream_output,
186
234
  )
187
-
235
+
188
236
  elif parsed_args.language == "python":
189
237
  from multi_lang_build.compiler.python import PythonCompiler
190
-
238
+
191
239
  compiler = PythonCompiler()
192
-
240
+
193
241
  if parsed_args.create_venv:
194
242
  result = compiler.create_venv(
195
243
  parsed_args.create_venv,
@@ -207,21 +255,37 @@ Examples:
207
255
  parsed_args.source_dir,
208
256
  parsed_args.output,
209
257
  mirror_enabled=parsed_args.mirror,
258
+ stream_output=parsed_args.stream_output,
210
259
  )
211
260
 
212
261
  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(",")]
262
+ from multi_lang_build.mirror.cli import configure_mirror, print_mirrors, get_current_config
219
263
 
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']}")
264
+ if parsed_args.mirror_action == "list":
265
+ if getattr(parsed_args, "json", False):
266
+ from multi_lang_build.mirror.cli import get_all_mirrors
267
+ import json
268
+ print(json.dumps(get_all_mirrors(), indent=2, ensure_ascii=False))
269
+ else:
270
+ print_mirrors()
271
+ elif parsed_args.mirror_action == "set":
272
+ mirror_type = getattr(parsed_args, "type", "pip")
273
+ mirror_key = getattr(parsed_args, "mirror", "pip")
274
+ success = configure_mirror(mirror_type, mirror_key)
275
+ sys.exit(0 if success else 1)
276
+ elif parsed_args.mirror_action == "show":
277
+ config = get_current_config()
278
+ if config:
279
+ print("\n📦 Current Mirror Configuration:")
280
+ print("-" * 40)
281
+ for key, value in config.items():
282
+ print(f" • {key}: {value}")
283
+ print()
284
+ else:
285
+ print("\n📦 No mirror configured")
286
+ print(" Run 'multi-lang-build mirror set <type> <mirror>' to configure")
287
+ print()
288
+ print_mirrors()
225
289
  else:
226
290
  mirror_parser.print_help()
227
291
 
@@ -4,6 +4,8 @@ from abc import ABC, abstractmethod
4
4
  from typing import TypeAlias, TypedDict, NotRequired
5
5
  from pathlib import Path
6
6
  import time
7
+ import subprocess
8
+ import sys
7
9
 
8
10
 
9
11
  class BuildConfig(TypedDict, total=False):
@@ -37,30 +39,30 @@ class CompilerInfo(TypedDict):
37
39
 
38
40
  class CompilerBase(ABC):
39
41
  """Abstract base class for all language compilers."""
40
-
42
+
41
43
  @property
42
44
  @abstractmethod
43
45
  def name(self) -> str:
44
46
  """Get the compiler name."""
45
47
  ...
46
-
48
+
47
49
  @property
48
50
  @abstractmethod
49
51
  def version(self) -> str:
50
52
  """Get the compiler version."""
51
53
  ...
52
-
54
+
53
55
  @property
54
56
  @abstractmethod
55
57
  def supported_mirrors(self) -> list[str]:
56
58
  """Get list of supported mirror configurations."""
57
59
  ...
58
-
60
+
59
61
  @abstractmethod
60
62
  def get_info(self) -> CompilerInfo:
61
63
  """Get compiler information."""
62
64
  ...
63
-
65
+
64
66
  @abstractmethod
65
67
  def build(
66
68
  self,
@@ -70,33 +72,35 @@ class CompilerBase(ABC):
70
72
  environment: dict[str, str] | None = None,
71
73
  mirror_enabled: bool = True,
72
74
  extra_args: list[str] | None = None,
75
+ stream_output: bool = True,
73
76
  ) -> BuildResult:
74
77
  """Execute the build process.
75
-
78
+
76
79
  Args:
77
80
  source_dir: Source code directory
78
81
  output_dir: Build output directory
79
82
  environment: Additional environment variables
80
83
  mirror_enabled: Whether to use mirror acceleration
81
84
  extra_args: Additional arguments to pass to the build command
82
-
85
+ stream_output: Whether to stream output in real-time (default: True)
86
+
83
87
  Returns:
84
88
  BuildResult containing success status and output information.
85
89
  """
86
90
  ...
87
-
91
+
88
92
  @abstractmethod
89
93
  def clean(self, directory: Path) -> bool:
90
94
  """Clean build artifacts in the specified directory.
91
-
95
+
92
96
  Args:
93
97
  directory: Directory to clean
94
-
98
+
95
99
  Returns:
96
100
  True if successful, False otherwise.
97
101
  """
98
102
  ...
99
-
103
+
100
104
  def _run_build(
101
105
  self,
102
106
  command: list[str],
@@ -105,42 +109,151 @@ class CompilerBase(ABC):
105
109
  *,
106
110
  environment: dict[str, str] | None = None,
107
111
  extra_args: list[str] | None = None,
112
+ stream_output: bool = True,
108
113
  ) -> BuildResult:
109
114
  """Execute a build command with timing and error handling.
110
-
115
+
111
116
  Args:
112
117
  command: Build command to execute
113
118
  source_dir: Source directory for the build
114
119
  output_dir: Output directory for build artifacts
115
120
  environment: Additional environment variables
116
121
  extra_args: Additional arguments to append to command
117
-
122
+ stream_output: Whether to stream output in real-time (default: True)
123
+
118
124
  Returns:
119
125
  BuildResult with success status and output information.
120
126
  """
121
- import subprocess
122
-
123
127
  full_command = command.copy()
124
128
  if extra_args:
125
129
  full_command.extend(extra_args)
126
-
130
+
127
131
  env = environment.copy() if environment else {}
128
132
  env.setdefault("PATH", "")
129
-
133
+
130
134
  start_time = time.perf_counter()
131
-
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
+ """
132
245
  try:
133
246
  result = subprocess.run(
134
- full_command,
247
+ command,
135
248
  cwd=source_dir,
136
249
  capture_output=True,
137
250
  text=True,
138
251
  env=env,
139
252
  timeout=3600, # 1 hour timeout
140
253
  )
141
-
254
+
142
255
  duration = time.perf_counter() - start_time
143
-
256
+
144
257
  return BuildResult(
145
258
  success=result.returncode == 0,
146
259
  return_code=result.returncode,
@@ -149,7 +262,7 @@ class CompilerBase(ABC):
149
262
  output_path=output_dir if result.returncode == 0 else None,
150
263
  duration_seconds=duration,
151
264
  )
152
-
265
+
153
266
  except subprocess.TimeoutExpired:
154
267
  duration = time.perf_counter() - start_time
155
268
  return BuildResult(