mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-02-27 12:22:43 -08:00
311 lines
10 KiB
Python
Executable File
311 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Build and Test Automation for Xcode Projects
|
|
|
|
Ultra token-efficient build automation with progressive disclosure via xcresult bundles.
|
|
|
|
Features:
|
|
- Minimal default output (5-10 tokens)
|
|
- Progressive disclosure for error/warning/log details
|
|
- Native xcresult bundle support
|
|
- Clean modular architecture
|
|
|
|
Usage Examples:
|
|
# Build (minimal output)
|
|
python scripts/build_and_test.py --project MyApp.xcodeproj
|
|
# Output: Build: SUCCESS (0 errors, 3 warnings) [xcresult-20251018-143052]
|
|
|
|
# Get error details
|
|
python scripts/build_and_test.py --get-errors xcresult-20251018-143052
|
|
|
|
# Get warnings
|
|
python scripts/build_and_test.py --get-warnings xcresult-20251018-143052
|
|
|
|
# Get build log
|
|
python scripts/build_and_test.py --get-log xcresult-20251018-143052
|
|
|
|
# Get everything as JSON
|
|
python scripts/build_and_test.py --get-all xcresult-20251018-143052 --json
|
|
|
|
# List recent builds
|
|
python scripts/build_and_test.py --list-xcresults
|
|
|
|
# Verbose mode (for debugging)
|
|
python scripts/build_and_test.py --project MyApp.xcodeproj --verbose
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Import our modular components
|
|
from xcode import BuildRunner, OutputFormatter, XCResultCache, XCResultParser
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
parser = argparse.ArgumentParser(
|
|
description="Build and test Xcode projects with progressive disclosure",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
# Build project (minimal output)
|
|
python scripts/build_and_test.py --project MyApp.xcodeproj
|
|
|
|
# Run tests
|
|
python scripts/build_and_test.py --project MyApp.xcodeproj --test
|
|
|
|
# Get error details from previous build
|
|
python scripts/build_and_test.py --get-errors xcresult-20251018-143052
|
|
|
|
# Get all details as JSON
|
|
python scripts/build_and_test.py --get-all xcresult-20251018-143052 --json
|
|
|
|
# List recent builds
|
|
python scripts/build_and_test.py --list-xcresults
|
|
""",
|
|
)
|
|
|
|
# Build/test mode arguments
|
|
build_group = parser.add_argument_group("Build/Test Options")
|
|
project_group = build_group.add_mutually_exclusive_group()
|
|
project_group.add_argument("--project", help="Path to .xcodeproj file")
|
|
project_group.add_argument("--workspace", help="Path to .xcworkspace file")
|
|
|
|
build_group.add_argument("--scheme", help="Build scheme (auto-detected if not specified)")
|
|
build_group.add_argument(
|
|
"--configuration",
|
|
default="Debug",
|
|
choices=["Debug", "Release"],
|
|
help="Build configuration (default: Debug)",
|
|
)
|
|
build_group.add_argument("--simulator", help="Simulator name (default: iPhone 15)")
|
|
build_group.add_argument("--clean", action="store_true", help="Clean before building")
|
|
build_group.add_argument("--test", action="store_true", help="Run tests")
|
|
build_group.add_argument("--suite", help="Specific test suite to run")
|
|
|
|
# Progressive disclosure arguments
|
|
disclosure_group = parser.add_argument_group("Progressive Disclosure Options")
|
|
disclosure_group.add_argument(
|
|
"--get-errors", metavar="XCRESULT_ID", help="Get error details from xcresult"
|
|
)
|
|
disclosure_group.add_argument(
|
|
"--get-warnings", metavar="XCRESULT_ID", help="Get warning details from xcresult"
|
|
)
|
|
disclosure_group.add_argument(
|
|
"--get-log", metavar="XCRESULT_ID", help="Get build log from xcresult"
|
|
)
|
|
disclosure_group.add_argument(
|
|
"--get-all", metavar="XCRESULT_ID", help="Get all details from xcresult"
|
|
)
|
|
disclosure_group.add_argument(
|
|
"--list-xcresults", action="store_true", help="List recent xcresult bundles"
|
|
)
|
|
|
|
# Output options
|
|
output_group = parser.add_argument_group("Output Options")
|
|
output_group.add_argument("--verbose", action="store_true", help="Show detailed output")
|
|
output_group.add_argument("--json", action="store_true", help="Output as JSON")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Initialize cache
|
|
cache = XCResultCache()
|
|
|
|
# Handle list mode
|
|
if args.list_xcresults:
|
|
xcresults = cache.list()
|
|
if args.json:
|
|
import json
|
|
|
|
print(json.dumps(xcresults, indent=2))
|
|
elif not xcresults:
|
|
print("No xcresult bundles found")
|
|
else:
|
|
print(f"Recent XCResult bundles ({len(xcresults)}):")
|
|
print()
|
|
for xc in xcresults:
|
|
print(f" {xc['id']}")
|
|
print(f" Created: {xc['created']}")
|
|
print(f" Size: {xc['size_mb']} MB")
|
|
print()
|
|
return 0
|
|
|
|
# Handle retrieval modes
|
|
xcresult_id = args.get_errors or args.get_warnings or args.get_log or args.get_all
|
|
|
|
if xcresult_id:
|
|
xcresult_path = cache.get_path(xcresult_id)
|
|
|
|
if not xcresult_path or not xcresult_path.exists():
|
|
print(f"Error: XCResult bundle not found: {xcresult_id}", file=sys.stderr)
|
|
print("Use --list-xcresults to see available bundles", file=sys.stderr)
|
|
return 1
|
|
|
|
# Load cached stderr for progressive disclosure
|
|
cached_stderr = cache.get_stderr(xcresult_id)
|
|
parser = XCResultParser(xcresult_path, stderr=cached_stderr)
|
|
|
|
# Get errors
|
|
if args.get_errors:
|
|
errors = parser.get_errors()
|
|
if args.json:
|
|
import json
|
|
|
|
print(json.dumps(errors, indent=2))
|
|
else:
|
|
print(OutputFormatter.format_errors(errors))
|
|
return 0
|
|
|
|
# Get warnings
|
|
if args.get_warnings:
|
|
warnings = parser.get_warnings()
|
|
if args.json:
|
|
import json
|
|
|
|
print(json.dumps(warnings, indent=2))
|
|
else:
|
|
print(OutputFormatter.format_warnings(warnings))
|
|
return 0
|
|
|
|
# Get log
|
|
if args.get_log:
|
|
log = parser.get_build_log()
|
|
if log:
|
|
print(OutputFormatter.format_log(log))
|
|
else:
|
|
print("No build log available", file=sys.stderr)
|
|
return 1
|
|
return 0
|
|
|
|
# Get all
|
|
if args.get_all:
|
|
error_count, warning_count = parser.count_issues()
|
|
errors = parser.get_errors()
|
|
warnings = parser.get_warnings()
|
|
build_log = parser.get_build_log()
|
|
|
|
if args.json:
|
|
import json
|
|
|
|
data = {
|
|
"xcresult_id": xcresult_id,
|
|
"error_count": error_count,
|
|
"warning_count": warning_count,
|
|
"errors": errors,
|
|
"warnings": warnings,
|
|
"log_preview": build_log[:1000] if build_log else None,
|
|
}
|
|
print(json.dumps(data, indent=2))
|
|
else:
|
|
print(f"XCResult: {xcresult_id}")
|
|
print(f"Errors: {error_count}, Warnings: {warning_count}")
|
|
print()
|
|
if errors:
|
|
print(OutputFormatter.format_errors(errors, limit=10))
|
|
print()
|
|
if warnings:
|
|
print(OutputFormatter.format_warnings(warnings, limit=10))
|
|
print()
|
|
if build_log:
|
|
print("Build Log (last 30 lines):")
|
|
print(OutputFormatter.format_log(build_log, lines=30))
|
|
return 0
|
|
|
|
# Build/test mode
|
|
if not args.project and not args.workspace:
|
|
# Try to auto-detect in current directory
|
|
cwd = Path.cwd()
|
|
projects = list(cwd.glob("*.xcodeproj"))
|
|
workspaces = list(cwd.glob("*.xcworkspace"))
|
|
|
|
if workspaces:
|
|
args.workspace = str(workspaces[0])
|
|
elif projects:
|
|
args.project = str(projects[0])
|
|
else:
|
|
parser.error("No project or workspace specified and none found in current directory")
|
|
|
|
# Initialize builder
|
|
builder = BuildRunner(
|
|
project_path=args.project,
|
|
workspace_path=args.workspace,
|
|
scheme=args.scheme,
|
|
configuration=args.configuration,
|
|
simulator=args.simulator,
|
|
cache=cache,
|
|
)
|
|
|
|
# Execute build or test
|
|
if args.test:
|
|
success, xcresult_id, stderr = builder.test(test_suite=args.suite)
|
|
else:
|
|
success, xcresult_id, stderr = builder.build(clean=args.clean)
|
|
|
|
if not xcresult_id and not stderr:
|
|
print("Error: Build/test failed without creating xcresult or error output", file=sys.stderr)
|
|
return 1
|
|
|
|
# Save stderr to cache for progressive disclosure
|
|
if xcresult_id and stderr:
|
|
cache.save_stderr(xcresult_id, stderr)
|
|
|
|
# Parse results
|
|
xcresult_path = cache.get_path(xcresult_id) if xcresult_id else None
|
|
parser = XCResultParser(xcresult_path, stderr=stderr)
|
|
error_count, warning_count = parser.count_issues()
|
|
|
|
# Format output
|
|
status = "SUCCESS" if success else "FAILED"
|
|
|
|
# Generate hints for failed builds
|
|
hints = None
|
|
if not success:
|
|
errors = parser.get_errors()
|
|
hints = OutputFormatter.generate_hints(errors)
|
|
|
|
if args.verbose:
|
|
# Verbose mode with error/warning details
|
|
errors = parser.get_errors() if error_count > 0 else None
|
|
warnings = parser.get_warnings() if warning_count > 0 else None
|
|
|
|
output = OutputFormatter.format_verbose(
|
|
status=status,
|
|
error_count=error_count,
|
|
warning_count=warning_count,
|
|
xcresult_id=xcresult_id or "N/A",
|
|
errors=errors,
|
|
warnings=warnings,
|
|
)
|
|
print(output)
|
|
elif args.json:
|
|
# JSON mode
|
|
data = {
|
|
"success": success,
|
|
"xcresult_id": xcresult_id or None,
|
|
"error_count": error_count,
|
|
"warning_count": warning_count,
|
|
}
|
|
if hints:
|
|
data["hints"] = hints
|
|
import json
|
|
|
|
print(json.dumps(data, indent=2))
|
|
else:
|
|
# Minimal mode (default)
|
|
output = OutputFormatter.format_minimal(
|
|
status=status,
|
|
error_count=error_count,
|
|
warning_count=warning_count,
|
|
xcresult_id=xcresult_id or "N/A",
|
|
hints=hints,
|
|
)
|
|
print(output)
|
|
|
|
# Exit with appropriate code
|
|
return 0 if success else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|