Skip to content

Commit 7cf5c58

Browse files
committed
Clean up
Signed-off-by: Alireza Poodineh <[email protected]>
1 parent 729cc86 commit 7cf5c58

File tree

2 files changed

+87
-71
lines changed

2 files changed

+87
-71
lines changed

config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
#pybite.Host.SOLUTION_PATH = 'ProjectName.sln'
77

88
# Change bite modules folder
9-
#pybite.Host.MODULES_DIR = 'tools/bite'
9+
#pybite.Host.MODULES_DIR = 'tools/bite'

tools/pybite/host.py

Lines changed: 86 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import os
55
import platform
66
import subprocess
7-
from typing import Optional, Dict, Any, List, Callable
87
import urllib.request
8+
from typing import Optional, Dict, Any, List, Callable
99

1010
from .global_json import GlobalJson
1111

@@ -17,19 +17,19 @@ class Host:
1717
"""
1818

1919
BASE_DIR: str = os.getcwd()
20-
"""Base directory for the project (current working directory)."""
20+
"""Absolute path to the project root."""
2121

2222
MODULES_DIR: str = os.path.join('build', 'modules')
23-
"""Directory where the bite modules are located."""
23+
"""Relative or absolute path to the bite modules directory."""
2424

2525
DOTNET_DIR: str = '.dotnet'
26-
"""Directory where the .NET SDK is or will be installed."""
26+
"""Relative or absolute path to the .NET SDK directory."""
2727

2828
SOLUTION_PATH: Optional[str] = None
29-
"""Optional override for the solution path."""
30-
29+
"""Optional override for the solution (.sln) file."""
30+
3131
BITE_PROJ_PATH: str = 'bite.proj'
32-
"""Path to the bite.core file."""
32+
"""Relative or absolute path to the bite.proj file."""
3333

3434
DEFAULT_ARGS: List[str] = ['--nologo']
3535
"""Default arguments to pass to dotnet CLI commands."""
@@ -68,7 +68,7 @@ def __init__(
6868
self.argparser_usage = f'{self.name} command [options]'
6969
self.argparser_epilog = "Any unrecognized options will be passed to the command handler."
7070

71-
# Only join with BASE_DIR if the path is relative
71+
# Normalize paths: only join with BASE_DIR if relative
7272
if not os.path.isabs(self.MODULES_DIR):
7373
self.MODULES_DIR = os.path.join(self.BASE_DIR, self.MODULES_DIR)
7474
if not os.path.isabs(self.DOTNET_DIR):
@@ -93,15 +93,17 @@ def __init__(
9393
self.requested_sdk: Optional[str] = self._resolve_requested_sdk()
9494
self.solution: str = self.SOLUTION_PATH or self.detect_solution()
9595
self.argparser: Optional[argparse.ArgumentParser] = None
96-
self.handlers: Dict[str, Callable[[argparse.Namespace], None]] = {}
96+
self.handlers: Dict[str, Callable[[argparse.Namespace, List[str]], None]] = {}
9797
self._subparsers_action: Optional[argparse._SubParsersAction] = None
9898

99+
# --- CLI and Command Registration ---
100+
99101
def get_argparser(self) -> argparse.ArgumentParser:
100102
"""
101103
Get or create the argument parser for the CLI, using subparsers for each command.
102104
103105
Returns:
104-
An argparse.ArgumentParser instance for parsing command-line arguments.
106+
argparse.ArgumentParser: The argument parser instance.
105107
"""
106108
if self.argparser is not None:
107109
return self.argparser
@@ -113,14 +115,12 @@ def get_argparser(self) -> argparse.ArgumentParser:
113115
usage=self.argparser_usage,
114116
add_help=True
115117
)
116-
# Hide the metavar for the subparser argument, but keep help for each command
117118
subparsers = parser.add_subparsers(
118119
dest='command',
119-
required=False, # Allow default
120-
metavar='', # This hides the {restore,clean,...} line in help
120+
required=False,
121+
metavar='',
121122
)
122-
123-
subparsers.required = False # Explicitly allow no subcommand
123+
subparsers.required = False
124124
parser.set_defaults(command='build', func=self._handle_dotnet_command)
125125
self._subparsers_action = subparsers
126126

@@ -135,6 +135,7 @@ def get_argparser(self) -> argparse.ArgumentParser:
135135
sub.set_defaults(func=self._handle_dotnet_command)
136136
self.handlers[cmd['name']] = self._handle_dotnet_command
137137

138+
# Register bite command only if bite.proj exists
138139
if os.path.isfile(self.BITE_PROJ_PATH):
139140
bite_parser = subparsers.add_parser(
140141
'bite',
@@ -152,7 +153,7 @@ def get_argparser(self) -> argparse.ArgumentParser:
152153
def add_command(
153154
self,
154155
name: str,
155-
handler: Callable[[argparse.Namespace], None],
156+
handler: Callable[[argparse.Namespace, List[str]], None],
156157
description: Optional[str] = None,
157158
help: str = "",
158159
arguments: Optional[List[Dict[str, Any]]] = None,
@@ -175,50 +176,83 @@ def add_command(
175176
if arguments:
176177
for arg in arguments:
177178
sub.add_argument(*arg.get('args', ()), **arg.get('kwargs', {}))
178-
# Always allow extra arguments for custom commands
179-
sub.add_argument('extras', nargs=argparse.REMAINDER, help='Extra arguments')
180179
sub.set_defaults(func=handler)
181180
self.handlers[name] = handler
182181

183-
def _handle_dotnet_command(self, args: argparse.Namespace, *extras: str) -> None:
182+
def register_handler(self, command: str, handler: Callable[[argparse.Namespace, List[str]], None]) -> None:
183+
"""
184+
Register a custom handler for a command.
185+
186+
Args:
187+
command: The command name.
188+
handler: The function to handle the command.
189+
"""
190+
self.handlers[command] = handler
191+
192+
def dispatch(self, args: argparse.Namespace, unknown_args: Optional[List[str]] = None) -> None:
193+
"""
194+
Dispatch the parsed arguments to the appropriate handler.
195+
196+
Args:
197+
args: The parsed argparse.Namespace.
198+
unknown_args: List of unknown arguments, if any.
199+
"""
200+
extras = unknown_args or []
201+
if hasattr(args, 'func'):
202+
args.func(args, extras)
203+
else:
204+
self.get_argparser().print_help()
205+
206+
# --- Dotnet/MSBuild Command Handlers ---
207+
208+
def _handle_dotnet_command(self, args: argparse.Namespace, extras: List[str]) -> None:
184209
"""
185-
Handle standard dotnet commands: restore, clean, build, test.
210+
Handle standard dotnet commands: restore, clean, build, test, pack.
186211
Passes any extra arguments to the dotnet CLI.
187212
"""
188213
self.run(args.command, *extras)
189214

190-
def _handle_bite(self, args: argparse.Namespace, *extras: str) -> None:
215+
def _handle_bite(self, args: argparse.Namespace, extras: List[str]) -> None:
191216
"""
192217
Handle the 'bite' command, running a custom msbuild target.
193218
Passes any extra arguments to msbuild.
194219
"""
195220
target = getattr(args, 'target', 'help')
196221
self.run_bite(target, *extras)
197222

198-
def register_handler(self, command: str, handler: Callable[[argparse.Namespace], None]) -> None:
223+
# --- Dotnet/MSBuild Execution ---
224+
225+
def run(self, command: str, *args: str) -> None:
199226
"""
200-
Register a custom handler for a command.
227+
Run a dotnet command with the solution file as an argument.
201228
202229
Args:
203-
command: The command name.
204-
handler: The function to handle the command.
230+
command: The dotnet CLI command to run (e.g., 'build', 'restore').
231+
*args: Additional arguments to pass to the command.
205232
"""
206-
self.handlers[command] = handler
233+
cmd = ['dotnet', command] + [self.solution] + self.DEFAULT_ARGS + list(args)
234+
try:
235+
subprocess.check_call(cmd)
236+
except subprocess.CalledProcessError as e:
237+
print(f"Error: dotnet {command} failed with exit code {e.returncode}")
238+
raise
207239

208-
def dispatch(self, args: argparse.Namespace, unknown_args: Optional[List[str]] = None) -> None:
240+
def run_bite(self, target: str, *args: str) -> None:
209241
"""
210-
Dispatch the parsed arguments to the appropriate handler.
242+
Run bite.core with the specified target.
211243
212244
Args:
213-
args: The parsed argparse.Namespace.
214-
unknown_args: List of unknown arguments, if any.
245+
target: The bite.core target to run.
246+
*args: Additional arguments to pass to msbuild.
215247
"""
216-
extras = unknown_args or []
248+
cmd = ['dotnet', 'msbuild'] + self.DEFAULT_ARGS + [f'-t:{target}', self.BITE_PROJ_PATH] + list(args)
249+
try:
250+
subprocess.check_call(cmd)
251+
except subprocess.CalledProcessError as e:
252+
print(f"Error: msbuild target '{target}' failed with exit code {e.returncode}")
253+
raise
217254

218-
if hasattr(args, 'func'):
219-
args.func(args, *extras)
220-
else:
221-
self.get_argparser().print_help()
255+
# --- SDK Installation ---
222256

223257
def _set_environment_variables(self) -> None:
224258
"""
@@ -249,15 +283,13 @@ def _install_sdk_windows(self) -> None:
249283
"""
250284
if self.requested_sdk is None:
251285
raise RuntimeError("No .NET SDK install required")
252-
286+
253287
installer = os.path.join(self.DOTNET_DIR, 'dotnet-install.ps1')
254288
url = 'https://dot.net/v1/dotnet-install.ps1'
255289

256-
# Download the PowerShell script using urllib
257290
with urllib.request.urlopen(url) as response, open(installer, 'wb') as out_file:
258291
out_file.write(response.read())
259292

260-
# Run the installer script
261293
subprocess.check_call([
262294
'powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass',
263295
installer,
@@ -271,30 +303,29 @@ def _install_sdk_unix(self) -> None:
271303
"""
272304
if self.requested_sdk is None:
273305
raise RuntimeError("No .NET SDK install required")
274-
306+
275307
installer = os.path.join(self.DOTNET_DIR, 'dotnet-install.sh')
276308
url = 'https://dot.net/v1/dotnet-install.sh'
277309

278-
# Download the Bash script using urllib
279310
with urllib.request.urlopen(url) as response, open(installer, 'wb') as out_file:
280311
out_file.write(response.read())
281312

282-
# Make the script executable
283313
os.chmod(installer, 0o755)
284314

285-
# Run the installer script
286315
subprocess.check_call([
287316
'bash', installer,
288317
'--version', self.requested_sdk,
289318
'--install-dir', self.DOTNET_DIR
290319
])
291320

321+
# --- Solution/SDK Detection ---
322+
292323
def detect_solution(self) -> str:
293324
"""
294325
Find the single .sln file in BASE_DIR and return its full path.
295326
296327
Returns:
297-
The full path to the solution file.
328+
str: The full path to the solution file.
298329
299330
Raises:
300331
RuntimeError: If zero or multiple .sln files are found.
@@ -311,7 +342,7 @@ def _resolve_requested_sdk(self) -> Optional[str]:
311342
Determine if the required SDK is installed.
312343
313344
Returns:
314-
The required version if installation is needed, otherwise None.
345+
Optional[str]: The required version if installation is needed, otherwise None.
315346
"""
316347
required = (
317348
self.global_json.version
@@ -329,27 +360,7 @@ def _resolve_requested_sdk(self) -> Optional[str]:
329360
except (subprocess.CalledProcessError, FileNotFoundError):
330361
return required
331362

332-
def run(self, command: str, *args: str) -> None:
333-
"""
334-
Run a dotnet command with the solution file as an argument.
335-
336-
Args:
337-
command: The dotnet CLI command to run (e.g., 'build', 'restore').
338-
*args: Additional arguments to pass to the command.
339-
"""
340-
cmd = ['dotnet', command] + [self.solution] + self.DEFAULT_ARGS + list(args)
341-
subprocess.call(cmd)
342-
343-
def run_bite(self, target: str, *args: str) -> None:
344-
"""
345-
Run bite.core with the specified target.
346-
347-
Args:
348-
target: The bite.core target to run.
349-
*args: Additional arguments to pass to msbuild.
350-
"""
351-
cmd = ['dotnet', 'msbuild'] + self.DEFAULT_ARGS + [f'-t:{target}', self.BITE_PROJ_PATH] + list(args)
352-
subprocess.call(cmd)
363+
# --- Utility ---
353364

354365
@staticmethod
355366
def msbuild_path(path: str) -> str:
@@ -370,7 +381,7 @@ def load_modules(self) -> Dict[str, Any]:
370381
Load all .bite.py modules from the modules directory.
371382
372383
Returns:
373-
A dictionary mapping plugin names to loaded module objects.
384+
Dict[str, Any]: Mapping of plugin names to loaded module objects.
374385
"""
375386
mods: Dict[str, Any] = {}
376387
pattern = os.path.join(self.MODULES_DIR, '**', '*.bite.py')
@@ -379,10 +390,15 @@ def load_modules(self) -> Dict[str, Any]:
379390
spec = importlib.util.spec_from_file_location(name, path)
380391
if spec is None or spec.loader is None:
381392
continue
382-
383393
mod = importlib.util.module_from_spec(spec)
384-
spec.loader.exec_module(mod)
385-
394+
try:
395+
spec.loader.exec_module(mod)
396+
except Exception as e:
397+
print(f"Failed to load module {name} from {path}: {e}")
398+
continue
386399
if hasattr(mod, 'load'):
387-
mods[name] = mod.load(self)
400+
try:
401+
mods[name] = mod.load(self)
402+
except Exception as e:
403+
print(f"Module '{name}' failed to initialize: {e}")
388404
return mods

0 commit comments

Comments
 (0)