diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..9f95854b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,87 @@ +name: ๐Ÿ› Bug Report +description: File a bug report to help us improve +title: "[Bug]: " +labels: ["bug", "needs-triage"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + - type: input + id: contact + attributes: + label: Contact Details + description: How can we get in touch with you if we need more info? + placeholder: ex. email@example.com + validations: + required: false + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to Reproduce + description: Please provide detailed steps to reproduce the issue + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + + - type: input + id: version + attributes: + label: Version + description: What version of XcodeBuildServer are you running? + placeholder: ex. v1.0.0 + validations: + required: true + + - type: dropdown + id: os + attributes: + label: Operating System + description: What operating system are you using? + options: + - macOS 14 (Sonoma) + - macOS 13 (Ventura) + - macOS 12 (Monterey) + - Other (please specify in additional context) + validations: + required: true + + - type: input + id: xcode-version + attributes: + label: Xcode Version + description: What version of Xcode are you using? + placeholder: ex. 15.4 + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Add any other context about the problem here. + placeholder: Any additional information that might be helpful... + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..4757c7d1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,71 @@ +name: โœจ Feature Request +description: Suggest an idea for this project +title: "[Feature]: " +labels: ["enhancement", "needs-triage"] +body: + - type: markdown + attributes: + value: | + Thanks for suggesting a new feature! Please provide as much detail as possible. + + - type: textarea + id: problem-statement + attributes: + label: Problem Statement + description: Is your feature request related to a problem? Please describe. + placeholder: I'm always frustrated when... + validations: + required: true + + - type: textarea + id: proposed-solution + attributes: + label: Proposed Solution + description: Describe the solution you'd like + placeholder: I would like to see... + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: Describe any alternative solutions or features you've considered + placeholder: Alternative approaches could be... + validations: + required: false + + - type: dropdown + id: priority + attributes: + label: Priority + description: How important is this feature to you? + options: + - Low - Nice to have + - Medium - Would be helpful + - High - Need this feature + - Critical - Blocking my work + validations: + required: true + + - type: checkboxes + id: implementation + attributes: + label: Implementation + description: Are you willing to help implement this feature? + options: + - label: I'm willing to submit a PR for this feature + required: false + - label: I would need guidance on how to implement this + required: false + - label: I can help with testing + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Add any other context, screenshots, or examples about the feature request. + placeholder: Any additional information that might be helpful... + validations: + required: false \ No newline at end of file diff --git a/.github/TROUBLESHOOTING.md b/.github/TROUBLESHOOTING.md new file mode 100644 index 00000000..7c286c47 --- /dev/null +++ b/.github/TROUBLESHOOTING.md @@ -0,0 +1,325 @@ +# GitHub Actions Troubleshooting Guide + +This guide helps you resolve common issues with the GitHub Actions workflows in XcodeBuildServer. + +## Common Issues and Solutions + +### 1. SwiftLint Container Action Error + +**Error:** `Container action is only supported on Linux` + +**Cause:** The `norio-nomura/action-swiftlint` action uses Docker containers which don't work on macOS runners. + +**Solution:** We've updated the workflows to install SwiftLint via Homebrew instead: + +```yaml +- name: Install SwiftLint + run: brew install swiftlint + +- name: Run SwiftLint + run: swiftlint --strict --reporter github-actions-logging +``` + +### 2. Build Failures + +**Error:** Swift build fails with compilation errors + +**Solutions:** + +1. **Check Swift Version Compatibility:** + ```bash + # Locally test with the same Swift version as CI + swift --version + swift build + ``` + +2. **Clean Build:** + ```bash + swift package clean + swift build + ``` + +3. **Check Dependencies:** + ```bash + swift package resolve + swift package show-dependencies + ``` + +### 3. Missing Secrets/Tokens + +**Error:** Workflows fail due to missing environment variables + +**Solutions:** + +1. **Use Basic CI:** Switch to `basic-ci.yml` which doesn't require external tokens +2. **Configure Required Secrets:** + - Go to repository Settings โ†’ Secrets and variables โ†’ Actions + - Add required secrets: + - `CODECOV_TOKEN` (optional, for code coverage) + - `SEMGREP_APP_TOKEN` (optional, for security scanning) + +### 4. Test Failures + +**Error:** `swift test` command fails + +**Solutions:** + +1. **Run Tests Locally:** + ```bash + swift test --enable-code-coverage + ``` + +2. **Check Test Dependencies:** + ```bash + swift package resolve + swift test --list-tests + ``` + +3. **Isolate Failing Tests:** + ```bash + swift test --filter TestClassName.testMethodName + ``` + +### 5. Artifact Upload Issues + +**Error:** Artifacts not uploading or wrong paths + +**Solutions:** + +1. **Verify Paths:** + ```bash + # Check if the binary exists + ls -la .build/release/ + ``` + +2. **Update Artifact Paths:** + ```yaml + - name: Upload Build Artifact + uses: actions/upload-artifact@v4 + with: + name: xcode-build-server + path: .build/release/XcodeBuildServerCLI + ``` + +### 6. Cache Issues + +**Error:** Builds are slow or cache not working + +**Solutions:** + +1. **Clear Cache:** + - Go to Actions tab โ†’ Caches + - Delete old or corrupted caches + +2. **Update Cache Keys:** + ```yaml + - name: Cache Swift Package Manager + uses: actions/cache@v4 + with: + path: | + .build + ~/.cache/org.swift.swiftpm + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + ``` + +### 7. Release Workflow Issues + +**Error:** Release workflow not triggering or failing + +**Solutions:** + +1. **Check Tag Format:** + ```bash + # Correct format + git tag v1.0.0 + git push origin v1.0.0 + + # Incorrect format + git tag 1.0.0 # Missing 'v' prefix + ``` + +2. **Verify Permissions:** + - Ensure the repository has write permissions for releases + - Check that `GITHUB_TOKEN` has appropriate permissions + +### 8. SwiftLint Configuration Issues + +**Error:** SwiftLint fails with configuration errors + +**Solutions:** + +1. **Test Configuration Locally:** + ```bash + swiftlint lint --config .swiftlint.yml + ``` + +2. **Validate YAML:** + ```bash + # Use any YAML validator + yamllint .swiftlint.yml + ``` + +3. **Simplify Configuration:** + - Start with a minimal `.swiftlint.yml` + - Add rules incrementally + +## Debugging Steps + +### 1. Enable Debug Logging + +Add this to your workflow for more verbose output: + +```yaml +- name: Debug Information + run: | + echo "Runner OS: ${{ runner.os }}" + echo "GitHub SHA: ${{ github.sha }}" + echo "GitHub Ref: ${{ github.ref }}" + swift --version + xcodebuild -version + ls -la .build/ || echo "No .build directory" +``` + +### 2. Matrix Debugging + +Test specific combinations: + +```yaml +strategy: + matrix: + swift-version: ['5.10'] + # Remove other versions temporarily to isolate issues +``` + +### 3. Step-by-Step Isolation + +Comment out failing steps and re-enable them one by one: + +```yaml +# - name: Problematic Step +# run: some-command + +- name: Debug Step + run: | + echo "Debugging the issue..." + # Add debugging commands here +``` + +## Performance Optimization + +### 1. Reduce Matrix Size + +```yaml +strategy: + matrix: + swift-version: ['5.10'] # Test with one version first +``` + +### 2. Use Caching Effectively + +```yaml +- name: Cache Swift Package Manager + uses: actions/cache@v4 + with: + path: | + .build + ~/.cache/org.swift.swiftpm + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm- +``` + +### 3. Parallel Jobs + +```yaml +jobs: + build: + # Fast job + test: + needs: build # Only run after build succeeds + # Test job +``` + +## Workflow Selection Guide + +### Use `basic-ci.yml` when: +- โœ… Getting started with CI/CD +- โœ… No external service tokens available +- โœ… Want simple, reliable builds +- โœ… Focus on core functionality + +### Use `ci.yml` when: +- โœ… Need code coverage reporting +- โœ… Have external service tokens configured +- โœ… Want comprehensive testing +- โœ… Ready for advanced features + +### Use `code-quality.yml` when: +- โœ… Need strict code quality enforcement +- โœ… Want security scanning +- โœ… Have development team collaboration +- โœ… Ready to maintain quality standards + +## Getting Help + +### 1. Check Logs +- Go to Actions tab in your repository +- Click on the failed workflow run +- Expand the failing step to see detailed logs + +### 2. Local Testing +Always test equivalent commands locally: + +```bash +# Test build +swift build -c release + +# Test linting +swiftlint --strict + +# Test format +swift-format lint --recursive Sources Tests +``` + +### 3. Community Resources +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [Swift Package Manager](https://swift.org/package-manager/) +- [SwiftLint Documentation](https://github.com/realm/SwiftLint) + +### 4. Repository Specific Help +- Create an issue with the `ci/cd` label +- Include workflow logs and error messages +- Mention your environment (macOS version, Xcode version, etc.) + +## Minimal Working Example + +If all else fails, start with this minimal workflow: + +```yaml +name: Minimal CI +on: + push: + branches: [ main ] + +jobs: + build: + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - name: Build + run: swift build + - name: Test + run: swift test +``` + +Once this works, gradually add more features. + +## Prevention Tips + +1. **Test Locally First:** Always test changes locally before pushing +2. **Small Incremental Changes:** Add one feature at a time to workflows +3. **Monitor Workflow Health:** Regularly check that workflows are passing +4. **Keep Dependencies Updated:** Update action versions quarterly +5. **Document Changes:** Update this guide when you solve new issues + +Remember: CI/CD should make development easier, not harder. Start simple and build complexity gradually! \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..dc0a4efe --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,41 @@ +## Pull Request Description + +### Summary + + +### Type of Change +- [ ] ๐Ÿ› Bug fix (non-breaking change which fixes an issue) +- [ ] โœจ New feature (non-breaking change which adds functionality) +- [ ] ๐Ÿ’ฅ Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] ๐Ÿ“š Documentation update +- [ ] ๐Ÿงน Code cleanup/refactoring +- [ ] ๐Ÿ”ง Build/CI changes + +### Changes Made + +- +- +- + +### Related Issues + +- + +### Testing +- [ ] New tests added for new functionality +- [ ] All existing tests pass +- [ ] Manual testing completed + +### Screenshots/Recordings + + +### Checklist +- [ ] Code follows the project's style guidelines +- [ ] Self-review of the code has been performed +- [ ] Code has been commented, particularly in hard-to-understand areas +- [ ] Corresponding documentation has been updated +- [ ] Changes generate no new warnings +- [ ] Any dependent changes have been merged and published + +### Additional Notes + \ No newline at end of file diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000..5dcf2b29 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,212 @@ +# GitHub Actions Workflows + +This directory contains all the GitHub Actions workflows for XcodeBuildServer. Each workflow serves a specific purpose in our CI/CD pipeline. + +## Workflows Overview + +### ๐Ÿ—๏ธ CI Pipeline (`ci.yml` & `basic-ci.yml`) + +**Main CI (`ci.yml`):** +- Advanced CI with full feature set +- Requires external service tokens (Codecov, etc.) + +**Basic CI (`basic-ci.yml`):** +- Simplified CI that works without external dependencies +- Recommended for getting started + +**Triggers:** +- Push to `main` and `develop` branches +- Pull requests to `main` branch + +**Jobs:** +- **Build and Test**: Builds the project and runs tests +- **SwiftLint**: Enforces Swift coding standards (via Homebrew) +- **Basic Security**: Simple security pattern scanning +- **Build Release**: Creates release binaries (main branch only) + +**Key Features:** +- Native macOS tools (no container dependencies) +- SPM dependency caching +- Artifact uploads for release binaries +- Works out of the box without tokens + +### ๐Ÿ” Code Quality (`code-quality.yml`) + +**Triggers:** +- Push to `main` and `develop` branches +- Pull requests to `main` branch + +**Jobs:** +- **SwiftLint**: Comprehensive linting with strict mode +- **Swift Format**: Code formatting validation +- **Dependency Review**: Analyzes dependency changes in PRs +- **Security Scan**: Semgrep security analysis +- **Documentation**: Validates and generates documentation + +**Security Features:** +- Hardcoded secrets detection +- Dependency vulnerability scanning +- Code pattern security analysis + +### ๐Ÿš€ Release (`release.yml`) + +**Triggers:** +- Tag pushes matching `v*.*.*` pattern + +**Features:** +- Universal binary builds (ARM64 + x86_64) +- Multiple archive formats (tar.gz, zip) +- SHA256 checksums generation +- Automatic release notes +- Pre-release detection + +### ๐Ÿ›ก๏ธ Security (`security.yml`) + +**Triggers:** +- Push to `main` branch +- Pull requests to `main` branch +- Daily scheduled runs at 2 AM UTC + +**Jobs:** +- **Dependency Security**: Swift package vulnerability scanning +- **Code Security**: GitHub CodeQL analysis +- **Secret Scan**: TruffleHog secret detection +- **License Compliance**: FOSSA license scanning + +## Environment Variables + +### Required Secrets + +| Secret | Purpose | Required For | +|--------|---------|-------------| +| `CODECOV_TOKEN` | Code coverage reporting | CI | +| `SEMGREP_APP_TOKEN` | Security scanning | Code Quality | +| `FOSSA_API_KEY` | License compliance | Security | + +### Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `DEVELOPER_DIR` | `/Applications/Xcode_15.4.app/Contents/Developer` | Xcode developer directory | + +## Badges + +The following badges are available for the README: + +```markdown +[![CI](https://github.com/wang.lun/XcodeBuildServer/workflows/CI/badge.svg)](https://github.com/wang.lun/XcodeBuildServer/actions/workflows/ci.yml) +[![Release](https://github.com/wang.lun/XcodeBuildServer/workflows/Release/badge.svg)](https://github.com/wang.lun/XcodeBuildServer/actions/workflows/release.yml) +[![Code Quality](https://github.com/wang.lun/XcodeBuildServer/workflows/Code%20Quality/badge.svg)](https://github.com/wang.lun/XcodeBuildServer/actions/workflows/code-quality.yml) +[![Security](https://github.com/wang.lun/XcodeBuildServer/workflows/Security/badge.svg)](https://github.com/wang.lun/XcodeBuildServer/actions/workflows/security.yml) +``` + +## Workflow Dependencies + +```mermaid +graph TD + A[Push/PR] --> B[CI Workflow] + A --> C[Code Quality] + A --> D[Security Scan] + + B --> E{Tests Pass?} + C --> F{Quality OK?} + D --> G{Security OK?} + + E -->|Yes| H[Build Artifacts] + F -->|Yes| H + G -->|Yes| H + + I[Tag Push] --> J[Release Workflow] + J --> K[Build Universal Binary] + K --> L[Create GitHub Release] +``` + +## Configuration Files + +### SwiftLint (`.swiftlint.yml`) + +Comprehensive linting configuration with: +- 120 character line limit +- Function body length limits +- Naming conventions +- Custom rules for access control + +### Swift Format + +Automatic code formatting validation ensuring consistent style across the codebase. + +## Optimization Features + +### Caching +- Swift Package Manager dependencies +- Build artifacts between runs +- Documentation generation + +### Matrix Builds +- Multiple Swift versions (5.9, 5.10) +- Platform-specific builds +- Configuration variations + +### Artifact Management +- Build artifacts retention (30 days) +- Release binaries with checksums +- Documentation archives (7 days) + +## Monitoring and Notifications + +### Status Checks +- All workflows must pass for PR merges +- Release builds only trigger on successful CI +- Security scans run on schedule + +### Notifications +- Failed builds notify maintainers +- Security alerts for vulnerabilities +- Release notifications to subscribers + +## Maintenance + +### Regular Updates +- Bump action versions quarterly +- Update Swift/Xcode versions as needed +- Review and update security policies + +### Performance Monitoring +- Track workflow execution times +- Monitor artifact sizes +- Optimize caching strategies + +## Troubleshooting + +### Common Issues + +1. **Build Failures**: Check Xcode/Swift version compatibility +2. **Security Scans**: Review and acknowledge false positives +3. **Release Failures**: Verify tag format matches `v*.*.*` +4. **Cache Issues**: Clear workflow caches if needed + +### Debug Steps + +1. Check workflow logs in Actions tab +2. Verify environment variables are set +3. Test locally with same Swift version +4. Review recent changes for breaking modifications + +## Local Testing + +Run equivalent commands locally: + +```bash +# Build and test +swift build +swift test --enable-code-coverage + +# Linting +swiftlint --strict +swift-format lint --recursive Sources Tests + +# Security scan (basic) +grep -r "api_key\|token" Sources/ --include="*.swift" +``` + +This ensures your changes will pass CI checks before pushing. \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..46ed99ce --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,168 @@ +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + build-and-test: + name: Build and Test + runs-on: macos-latest + + strategy: + matrix: + swift-version: ['6.1'] + xcode-version: ['16.3'] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Swift + uses: swift-actions/setup-swift@v2 + with: + swift-version: ${{ matrix.swift-version }} + + - name: Cache Swift Package Manager + uses: actions/cache@v4 + with: + path: | + .build + ~/.cache/org.swift.swiftpm + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm- + + - name: Build + run: swift build -v + + - name: Run Tests + run: swift test --enable-code-coverage + + - name: Install LLVM + run: brew install llvm + + - name: Generate coverage report + run: | + # Use Homebrew LLVM tools for better version compatibility + LLVM_COV="/opt/homebrew/opt/llvm/bin/llvm-cov" + LLVM_PROFDATA="/opt/homebrew/opt/llvm/bin/llvm-profdata" + + TEST_BINARY=$(find .build -path "*/Contents/MacOS/*PackageTests" -type f -perm +111 | head -1) + + # Try merging profraw files with newer LLVM tools + if find .build -name "*.profraw" | head -1 >/dev/null 2>&1; then + echo "Merging profraw files with Homebrew LLVM..." + if $LLVM_PROFDATA merge -sparse .build/*/debug/codecov/*.profraw -o coverage.profdata 2>/dev/null; then + $LLVM_COV export "$TEST_BINARY" -instr-profile=coverage.profdata -format=lcov > coverage.info + echo "โœ… Generated coverage using Homebrew LLVM tools" + else + echo "โš ๏ธ Failed to merge profraw files" + echo "# No coverage data available" > coverage.info + fi + else + echo "โš ๏ธ No profraw files found" + echo "# No coverage data available" > coverage.info + fi + + - name: Upload to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: coverage.info + name: codecov-umbrella + fail_ci_if_error: false + verbose: true + + lint: + name: Code Quality + runs-on: macos-14 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install SwiftLint + run: | + brew install swiftlint + + - name: Run SwiftLint + run: | + swiftlint --strict --reporter github-actions-logging + + - name: Install swift-format + run: | + brew install swift-format + + - name: Check Swift Format + run: | + swift-format lint --recursive Sources Tests + + security-scan: + name: Security Scan + runs-on: macos-14 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Semgrep + run: | + brew install semgrep + + - name: Run Semgrep Security Scan + run: | + semgrep --config=p/swift --config=p/security-audit --config=p/secrets . || echo "Semgrep scan completed with findings" + + - name: Check for hardcoded secrets + run: | + echo "Checking for potential secrets..." + + SECRETS_FOUND=false + + if grep -ri "api[_-]\?key\s*[:=]\s*['\"][^'\"]*['\"]" Sources/ Tests/ --include="*.swift" 2>/dev/null; then + echo "::warning::Potential API key pattern found" + SECRETS_FOUND=true + fi + + if grep -ri "token\s*[:=]\s*['\"][^'\"]*['\"]" Sources/ Tests/ --include="*.swift" 2>/dev/null; then + echo "::warning::Potential token pattern found" + SECRETS_FOUND=true + fi + + if grep -ri "password\s*[:=]\s*['\"][^'\"]*['\"]" Sources/ Tests/ --include="*.swift" 2>/dev/null; then + echo "::warning::Potential password pattern found" + SECRETS_FOUND=true + fi + + if [ "$SECRETS_FOUND" = false ]; then + echo "โœ… No obvious secrets found" + fi + + build-release: + name: Build Release + runs-on: macos-14 + if: github.ref == 'refs/heads/main' + needs: [build-and-test, lint, security-scan] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build Release + run: swift build -c release --arch arm64 --arch x86_64 + + - name: Archive Binary + run: | + mkdir -p artifacts + cp .build/apple/Products/Release/XcodeBuildServerCLI artifacts/ + tar -czf artifacts/xcode-build-server-macos.tar.gz -C artifacts XcodeBuildServerCLI + + - name: Upload Build Artifact + uses: actions/upload-artifact@v4 + with: + name: xcode-build-server-macos + path: artifacts/xcode-build-server-macos.tar.gz + retention-days: 30 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..98935fb6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,80 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + +env: + DEVELOPER_DIR: /Applications/Xcode_16.2.app/Contents/Developer + +jobs: + build-release: + name: Build Release Binaries + runs-on: macos-14 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Swift + uses: swift-actions/setup-swift@v2 + with: + swift-version: '6.1' + + - name: Build for macOS (Universal) + run: | + swift build -c release --arch arm64 --arch x86_64 + + - name: Create Release Archive + run: | + mkdir -p release + cp .build/apple/Products/Release/XcodeBuildServerCLI release/xcode-build-server + chmod +x release/xcode-build-server + + # Create tar.gz + tar -czf xcode-build-server-macos-universal.tar.gz -C release xcode-build-server + + # Create zip + cd release && zip ../xcode-build-server-macos-universal.zip xcode-build-server && cd .. + + - name: Generate Checksums + run: | + shasum -a 256 xcode-build-server-macos-universal.tar.gz > checksums.txt + shasum -a 256 xcode-build-server-macos-universal.zip >> checksums.txt + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: | + xcode-build-server-macos-universal.tar.gz + xcode-build-server-macos-universal.zip + checksums.txt + generate_release_notes: true + prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }} + body: | + ## Changes in this Release + + ### Installation + + #### Homebrew (Recommended) + ```bash + # Coming soon + brew install xcode-build-server + ``` + + #### Manual Installation + 1. Download the appropriate archive for your platform + 2. Extract the binary: `tar -xzf xcode-build-server-macos-universal.tar.gz` + 3. Move to PATH: `mv xcode-build-server /usr/local/bin/` + 4. Make executable: `chmod +x /usr/local/bin/xcode-build-server` + + ### Usage + ```bash + xcode-build-server --help + ``` + + ### Checksums + Verify your download with the checksums provided in `checksums.txt`. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 00000000..5efe950e --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,122 @@ +name: Security + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + # Run security scans daily at 2 AM UTC + - cron: '0 2 * * *' + +jobs: + dependency-security: + name: Dependency Security Scan + runs-on: macos-14 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Swift + uses: swift-actions/setup-swift@v2 + with: + swift-version: '6.1' + + - name: Audit Swift Package Dependencies + run: | + # Generate dependency list + swift package show-dependencies --format json > dependencies.json + + # Check for any known vulnerabilities in Swift packages + # This is a basic check - in a real scenario you'd want to use + # a more comprehensive tool + echo "Checking Swift package dependencies..." + + if [ -f dependencies.json ]; then + # Parse dependencies and check for known issues + jq -r '.dependencies[] | .name + " " + .version' dependencies.json || true + echo "Dependencies audit completed successfully" + fi + + - name: Upload Dependency Information + uses: actions/upload-artifact@v4 + with: + name: dependencies-${{ github.sha }} + path: dependencies.json + retention-days: 30 + + code-security: + name: Code Security Scan + runs-on: macos-14 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: CodeQL Initialize + uses: github/codeql-action/init@v3 + with: + languages: swift + + - name: Setup Swift + uses: swift-actions/setup-swift@v2 + with: + swift-version: '6.1' + + - name: Build for Analysis + run: | + swift build -c release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + + secret-scan: + name: Secret Scan + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: TruffleHog OSS + uses: trufflesecurity/trufflehog@main + with: + path: ./ + base: main + head: HEAD + extra_args: --debug --only-verified + + license-compliance: + name: License Compliance + runs-on: macos-14 + if: false # Disabled until FOSSA_API_KEY is configured + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Swift + uses: swift-actions/setup-swift@v2 + with: + swift-version: '6.1' + + - name: Basic License Check + run: | + # Check for license files + echo "Checking for license information..." + + # Look for license files + find . -name "LICENSE*" -o -name "COPYING*" -o -name "COPYRIGHT*" | head -10 + + # Check Package.swift for license info + if [ -f Package.swift ]; then + echo "Package.swift found" + # Could add license parsing logic here + fi + + # List dependencies + swift package show-dependencies --format json > dependencies.json + echo "Dependencies check completed" \ No newline at end of file diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 00000000..8b0f3e02 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,124 @@ +# SwiftLint configuration file + +# Include/Exclude paths +included: + - Sources + - Tests + +excluded: + - .build + - .swiftpm + - Package.swift + +# Rules configuration +opt_in_rules: + - array_init + - closure_spacing + - collection_alignment + - contains_over_filter_count + - empty_count + - empty_string + - fatal_error_message + - first_where + - force_unwrapping + - implicitly_unwrapped_optional + - last_where + - legacy_random + - literal_expression_end_indentation + - multiline_arguments + - multiline_function_chains + - multiline_literal_brackets + - multiline_parameters + - operator_usage_whitespace + - overridden_super_call + - pattern_matching_keywords + - prefer_self_type_over_type_of_self + - redundant_nil_coalescing + - sorted_first_last + - toggle_bool + - trailing_closure + - unneeded_parentheses_in_closure_argument + - unused_import + - vertical_parameter_alignment_on_call + - yoda_condition + +disabled_rules: + - todo + - trailing_comma + - explicit_acl # Too many violations, will be addressed gradually + - force_unwrapping # Some cases are necessary for reliability + +# Rule-specific configuration +line_length: + warning: 120 + error: 150 + ignores_urls: true + ignores_function_declarations: true + ignores_comments: true + +function_body_length: + warning: 60 + error: 120 + +function_parameter_count: + warning: 5 + error: 8 + +type_body_length: + warning: 200 + error: 300 + +file_length: + warning: 400 + error: 1000 + +cyclomatic_complexity: + warning: 10 + error: 20 + +nesting: + type_level: + warning: 2 + error: 3 + function_level: + warning: 5 + error: 10 + +identifier_name: + min_length: + warning: 1 + error: 1 + max_length: + warning: 40 + error: 60 + excluded: + - id + - x + - y + - z + - git_commit + - git_rebase + - objective_c + - objective_cpp + +type_name: + min_length: + warning: 3 + error: 2 + max_length: + warning: 50 + error: 60 + excluded: + - WorkspaceDidChangeWatchedFilesNotification + - WorkspaceWaitForBuildSystemUpdatesRequest + +# Custom rules (examples) +custom_rules: + # Force developers to use explicit access control + explicit_acl: + name: "Explicit ACL" + regex: "^\\s*(class|struct|enum|protocol|extension|func|var|let)\\s+" + match_kinds: + - keyword + message: "Consider using explicit access control" + severity: warning \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index d227f06b..d89aa0eb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,7 +16,7 @@ "program": "${workspaceFolder:XcodeBuildServer}/.build/debug/XcodeBuildServerCLI" }, { - "type": "lldb", + "type": "swift", "request": "launch", "args": [], "cwd": "${workspaceFolder:XcodeBuildServer}", @@ -25,7 +25,7 @@ "preLaunchTask": "swift: Build Release XcodeBuildServerCLI" }, { - "type": "lldb", + "type": "swift", "request": "launch", "args": [], "cwd": "${workspaceFolder:XcodeBuildServer}", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..38fea19d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,260 @@ +# Contributing to XcodeBuildServer + +Thank you for your interest in contributing to XcodeBuildServer! This document provides guidelines and information for contributors. + +## Code of Conduct + +This project adheres to a [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. + +## How to Contribute + +### Reporting Bugs + +1. **Check existing issues** first to avoid duplicates +2. **Use the bug report template** when creating new issues +3. **Provide detailed information** including: + - Steps to reproduce the issue + - Expected vs actual behavior + - Environment details (macOS version, Xcode version, etc.) + - Relevant log output + +### Suggesting Features + +1. **Check existing feature requests** to avoid duplicates +2. **Use the feature request template** +3. **Describe the problem** you're trying to solve +4. **Propose a solution** with implementation details if possible + +### Contributing Code + +#### Prerequisites + +- macOS 12.0 or later +- Xcode 14.0 or later +- Swift 6.1 or later +- Familiarity with Build Server Protocol (BSP) + +#### Development Setup + +1. **Fork and clone** the repository: + ```bash + git clone https://github.com/yourusername/XcodeBuildServer.git + cd XcodeBuildServer + ``` + +2. **Install dependencies**: + ```bash + swift package resolve + ``` + +3. **Build the project**: + ```bash + swift build + ``` + +4. **Run tests**: + ```bash + swift test + ``` + +#### Making Changes + +1. **Create a feature branch**: + ```bash + git checkout -b feature/your-feature-name + ``` + +2. **Make your changes** following the coding standards below + +3. **Add tests** for new functionality + +4. **Ensure all tests pass**: + ```bash + swift test + ``` + +5. **Run code quality checks**: + ```bash + swiftlint + swift-format lint --recursive Sources Tests + ``` + +6. **Commit your changes** with clear, descriptive messages: + ```bash + git commit -m "Add feature: brief description of changes" + ``` + +7. **Push to your fork**: + ```bash + git push origin feature/your-feature-name + ``` + +8. **Create a pull request** using the PR template + +## Coding Standards + +### Swift Style Guide + +We follow the [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/) and use SwiftLint for enforcement. + +#### Key Points: + +- **Naming**: Use clear, descriptive names +- **Access Control**: Use the most restrictive access level possible +- **Documentation**: Document all public APIs with triple-slash comments +- **Error Handling**: Use proper Swift error handling, avoid force unwrapping +- **Concurrency**: Use Swift's modern concurrency features (async/await, actors) + +#### Example: + +```swift +/// Manages the build server context for an Xcode project +public actor BuildServerContext { + private let projectURL: URL + private var buildSettings: [BuildSettings]? + + /// Initializes a new build server context + /// - Parameter projectURL: The URL of the Xcode project or workspace + public init(projectURL: URL) { + self.projectURL = projectURL + } + + /// Loads the project configuration and build settings + /// - Throws: `BuildServerError` if the project cannot be loaded + public func loadProject() async throws { + // Implementation... + } +} +``` + +### Architecture Guidelines + +1. **Separation of Concerns**: Keep BSP protocol, JSON-RPC, and Xcode integration separate +2. **Thread Safety**: Use actors for shared mutable state +3. **Error Handling**: Define specific error types, avoid generic errors +4. **Testing**: Write unit tests for all public APIs +5. **Documentation**: Document complex algorithms and protocols + +### Git Commit Messages + +Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: + +``` +type(scope): description + +[optional body] + +[optional footer] +``` + +Types: +- `feat`: New feature +- `fix`: Bug fix +- `docs`: Documentation changes +- `style`: Code style changes (formatting, etc.) +- `refactor`: Code refactoring +- `test`: Adding or updating tests +- `chore`: Maintenance tasks + +Examples: +``` +feat(bsp): add support for build target dependencies + +fix(jsonrpc): handle malformed requests gracefully + +docs(readme): update installation instructions +``` + +## Testing + +### Unit Tests + +- Write tests for all new functionality +- Maintain test coverage above 80% +- Use descriptive test names that explain what is being tested +- Follow the Arrange-Act-Assert pattern + +### Integration Tests + +- Test BSP protocol compliance +- Test Xcode integration with real projects +- Test error scenarios and edge cases + +### Running Tests + +```bash +# Run all tests +swift test + +# Run specific test +swift test --filter TestClassName.testMethodName + +# Run with coverage +swift test --enable-code-coverage +``` + +## Documentation + +### Code Documentation + +- Document all public APIs with triple-slash comments +- Include parameter descriptions and return value information +- Add usage examples for complex APIs +- Document thrown errors + +### Architecture Documentation + +- Update architecture diagrams for significant changes +- Document design decisions in ADRs (Architecture Decision Records) +- Keep the README up to date with new features + +## Review Process + +### Before Submitting + +- [ ] Code follows style guidelines +- [ ] All tests pass +- [ ] Documentation is updated +- [ ] Commit messages follow conventions +- [ ] PR description explains the changes + +### Review Criteria + +Pull requests are reviewed for: + +1. **Correctness**: Does the code work as intended? +2. **Design**: Is the code well-designed and fits the architecture? +3. **Functionality**: Does it fulfill the requirements? +4. **Complexity**: Is the code as simple as possible? +5. **Tests**: Are there appropriate tests? +6. **Naming**: Are names clear and descriptive? +7. **Comments**: Are comments clear and useful? +8. **Documentation**: Is documentation updated? + +### Review Timeline + +- Small changes: 1-2 days +- Medium changes: 3-5 days +- Large changes: 1-2 weeks + +## Release Process + +1. Version bumps follow [Semantic Versioning](https://semver.org/) +2. Releases are created from the `main` branch +3. Release notes are auto-generated from commit messages +4. Binaries are automatically built and uploaded via GitHub Actions + +## Getting Help + +- **Discussions**: Use GitHub Discussions for questions +- **Issues**: Create issues for bugs and feature requests +- **Discord**: Join our community Discord (link in README) + +## Recognition + +Contributors are recognized in: +- README acknowledgments +- Release notes +- GitHub contributors page + +Thank you for contributing to XcodeBuildServer! \ No newline at end of file diff --git a/Package.swift b/Package.swift index f708dced..f4a46264 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.10 +// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/README.md b/README.md new file mode 100644 index 00000000..8086938c --- /dev/null +++ b/README.md @@ -0,0 +1,173 @@ +# XcodeBuildServer + +[![Swift](https://img.shields.io/badge/swift-6.1+-orange.svg)](https://swift.org) +[![Platform](https://img.shields.io/badge/platform-macOS-lightgrey.svg)](https://developer.apple.com/macos/) +[![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](https://github.com/wang.lun/XcodeBuildServer/actions) +[![codecov](https://codecov.io/github/aelam/XcodeBuildServer/graph/badge.svg?token=SUL2UI5FQD)](https://codecov.io/github/aelam/XcodeBuildServer) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + +A Build Server Protocol (BSP) implementation for Xcode projects, enabling better IDE integration with Swift and Objective-C codebases. + +## Features + +- ๐Ÿ”ง **BSP 2.0 Support**: Full compatibility with Build Server Protocol 2.0 +- ๐Ÿ—๏ธ **Xcode Integration**: Seamless integration with Xcode build system +- โšก **Fast Indexing**: Efficient source code indexing and navigation +- ๐Ÿ“ **Multi-target Support**: Support for complex Xcode project structures +- ๐Ÿ” **SourceKit Integration**: Native Swift language server capabilities +- ๐Ÿ›ก๏ธ **Thread-safe**: Robust concurrent operations with Swift actors + +## Installation + +### Homebrew (Recommended) +```bash +# Coming soon +brew install xcode-build-server +``` + +### Manual Installation +1. Download the latest release from [GitHub Releases](https://github.com/wang.lun/XcodeBuildServer/releases) +2. Extract and move to your PATH: + ```bash + tar -xzf xcode-build-server-macos-universal.tar.gz + sudo mv xcode-build-server /usr/local/bin/ + chmod +x /usr/local/bin/xcode-build-server + ``` + +### Build from Source +```bash +git clone https://github.com/wang.lun/XcodeBuildServer.git +cd XcodeBuildServer +swift build -c release +cp .build/release/XcodeBuildServerCLI /usr/local/bin/xcode-build-server +``` + +## Quick Start + +1. **Configure your project**: Create a `.bsp/xcode.json` configuration file in your project root: + ```json + { + "workspace": "YourProject.xcworkspace", + "scheme": "YourScheme", + "configuration": "Debug" + } + ``` + +2. **Start the server**: + ```bash + xcode-build-server + ``` + +3. **Connect from your IDE**: Configure your IDE to connect to the BSP server (typically on stdio). + +## Configuration + +The build server looks for configuration in the following order: +1. `.bsp/*.json` files (BSP standard) +2. `buildServer.json` in project root (legacy support) + +### Configuration Options + +| Option | Description | Required | +|--------|-------------|----------| +| `workspace` | Path to .xcworkspace file | Yes* | +| `project` | Path to .xcodeproj file | Yes* | +| `scheme` | Xcode scheme to use | Yes | +| `configuration` | Build configuration (Debug/Release) | No (defaults to Debug) | + +*Either `workspace` or `project` is required. + +## IDE Integration + +### VS Code with SourceKit-LSP +```json +{ + "sourcekit-lsp.serverPath": "/path/to/sourcekit-lsp", + "sourcekit-lsp.toolchainPath": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain" +} +``` + +### Vim/Neovim +Use with [vim-lsp](https://github.com/prabirshrestha/vim-lsp) or [coc.nvim](https://github.com/neoclide/coc.nvim). + +## Development + +### Prerequisites +- macOS 12.0+ +- Xcode 14.0+ +- Swift 6.1+ + +### Building +```bash +swift build +``` + +### Testing +```bash +swift test +``` + +### Contributing + +We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for details. + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Add tests +5. Submit a pull request + +## Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” JSON-RPC โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ IDE โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ XcodeBuildServer โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Xcode Build โ”‚ + โ”‚ System โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Key Components + +- **BSPServer**: Core BSP protocol implementation +- **BuildServerContext**: Manages project state and configuration +- **JSONRPCServer**: JSON-RPC transport layer +- **XcodeBuild Integration**: Interface with xcodebuild tool + +## Troubleshooting + +### Common Issues + +1. **Server not starting**: Check that your configuration file is valid JSON +2. **Build failures**: Ensure your Xcode project builds successfully first +3. **Index not updating**: Verify that the scheme and configuration are correct + +### Logging + +Enable debug logging: +```bash +export XCODE_BUILD_SERVER_LOG_LEVEL=debug +xcode-build-server +``` + +## References + +- [Build Server Protocol Specification](https://build-server-protocol.github.io/) +- [SourceKit-LSP](https://github.com/apple/sourcekit-lsp) +- [Swift Package Manager BSP](https://github.com/apple/swift-package-manager/blob/main/Documentation/BuildServerProtocol.md) + +Inspired by [sourcekit-bazel-bsp](https://github.com/spotify/sourcekit-bazel-bsp) + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +- Apple for SourceKit and the Swift toolchain +- The Build Server Protocol community +- Contributors to the Swift ecosystem \ No newline at end of file diff --git a/Sources/XcodeBuildServer/BSPServer/BSP/BuildTarget.swift b/Sources/XcodeBuildServer/BSPServer/BSP/BuildTarget.swift index 892cf31f..ccd1bf73 100644 --- a/Sources/XcodeBuildServer/BSPServer/BSP/BuildTarget.swift +++ b/Sources/XcodeBuildServer/BSPServer/BSP/BuildTarget.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // @@ -8,7 +8,7 @@ // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// /// Build target contains metadata about an artifact (for example library, test, or binary artifact). /// Using vocabulary of other build tools: diff --git a/Sources/XcodeBuildServer/BSPServer/BSP/DocumentURI.swift b/Sources/XcodeBuildServer/BSPServer/BSP/DocumentURI.swift index 44115b05..00d0d03f 100644 --- a/Sources/XcodeBuildServer/BSPServer/BSP/DocumentURI.swift +++ b/Sources/XcodeBuildServer/BSPServer/BSP/DocumentURI.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // @@ -8,12 +8,12 @@ // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// #if compiler(>=6) - public import Foundation +public import Foundation #else - import Foundation +import Foundation #endif struct FailedToConstructDocumentURIFromStringError: Error, CustomStringConvertible { diff --git a/Sources/XcodeBuildServer/BSPServer/BSP/LSPAny.swift b/Sources/XcodeBuildServer/BSPServer/BSP/LSPAny.swift index 19090698..f447a451 100644 --- a/Sources/XcodeBuildServer/BSPServer/BSP/LSPAny.swift +++ b/Sources/XcodeBuildServer/BSPServer/BSP/LSPAny.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // @@ -8,7 +8,7 @@ // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// /// Representation of 'any' in the Language Server Protocol, which is equivalent /// to an arbitrary JSON value. @@ -112,7 +112,7 @@ extension LSPAny: ExpressibleByArrayLiteral { extension LSPAny: ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (String, LSPAny)...) { - let dict = [String: LSPAny](elements, uniquingKeysWith: { first, _ in first }) + let dict = [String: LSPAny](elements) { first, _ in first } self = .dictionary(dict) } } @@ -155,50 +155,36 @@ extension Array: LSPAnyCodable where Element: LSPAnyCodable { var result = [Element]() for element in array { - switch element { - case let .dictionary(dict): - if let value = Element(fromLSPDictionary: dict) { - result.append(value) - } else { - return nil - } - case let .array(value): - if let value = value as? [Element] { - result.append(contentsOf: value) - } else { - return nil - } - case let .string(value): - if let value = value as? Element { - result.append(value) - } else { - return nil - } - case let .int(value): - if let value = value as? Element { - result.append(value) - } else { - return nil - } - case let .double(value): - if let value = value as? Element { - result.append(value) - } else { - return nil - } - case let .bool(value): - if let value = value as? Element { - result.append(value) - } else { - return nil - } - case .null: - // null is not expected for non-optional Element + guard let converted = Self.convertElement(element) else { return nil } + result.append(contentsOf: converted) } self = result } + private static func convertElement(_ element: LSPAny) -> [Element]? { + switch element { + case let .dictionary(dict): + return Element(fromLSPDictionary: dict).map { [$0] } + case let .array(value): + return value as? [Element] + case .string, .int, .double, .bool: + return convertPrimitiveValue(element) + case .null: + return nil + } + } + private static func convertPrimitiveValue(_ element: LSPAny) -> [Element]? { + let value: Any + switch element { + case let .string(stringValue): value = stringValue + case let .int(intValue): value = intValue + case let .double(doubleValue): value = doubleValue + case let .bool(boolValue): value = boolValue + default: return nil + } + return (value as? Element).map { [$0] } + } public init?(fromLSPDictionary _: [String: LSPAny]) { nil diff --git a/Sources/XcodeBuildServer/BSPServer/BSP/ProgressToken.swift b/Sources/XcodeBuildServer/BSPServer/BSP/ProgressToken.swift index e45c1288..4db45f65 100644 --- a/Sources/XcodeBuildServer/BSPServer/BSP/ProgressToken.swift +++ b/Sources/XcodeBuildServer/BSPServer/BSP/ProgressToken.swift @@ -6,26 +6,29 @@ // public enum ProgressToken: Codable, Hashable, Sendable { - case integer(Int) - case string(String) + case integer(Int) + case string(String) - public init(from decoder: Decoder) throws { - if let integer = try? Int(from: decoder) { - self = .integer(integer) - } else if let string = try? String(from: decoder) { - self = .string(string) - } else { - let context = DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Expected Int or String") - throw DecodingError.dataCorrupted(context) + public init(from decoder: Decoder) throws { + if let integer = try? Int(from: decoder) { + self = .integer(integer) + } else if let string = try? String(from: decoder) { + self = .string(string) + } else { + let context = DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Expected Int or String" + ) + throw DecodingError.dataCorrupted(context) + } } - } - public func encode(to encoder: Encoder) throws { - switch self { - case .integer(let integer): - try integer.encode(to: encoder) - case .string(let string): - try string.encode(to: encoder) + public func encode(to encoder: Encoder) throws { + switch self { + case .integer(let integer): + try integer.encode(to: encoder) + case .string(let string): + try string.encode(to: encoder) + } } - } } diff --git a/Sources/XcodeBuildServer/BSPServer/BSP/TextDocumentIdentifier.swift b/Sources/XcodeBuildServer/BSPServer/BSP/TextDocumentIdentifier.swift index b520a718..db82ff1d 100644 --- a/Sources/XcodeBuildServer/BSPServer/BSP/TextDocumentIdentifier.swift +++ b/Sources/XcodeBuildServer/BSPServer/BSP/TextDocumentIdentifier.swift @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // @@ -8,7 +8,7 @@ // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// public struct TextDocumentIdentifier: Codable, Sendable, Hashable { /// The text document's URI. diff --git a/Sources/XcodeBuildServer/BSPServer/BuildServerContext.swift b/Sources/XcodeBuildServer/BSPServer/BuildServerContext.swift index b633b341..ff61cf33 100644 --- a/Sources/XcodeBuildServer/BSPServer/BuildServerContext.swift +++ b/Sources/XcodeBuildServer/BSPServer/BuildServerContext.swift @@ -6,11 +6,36 @@ import Foundation -enum BuildServerError: Error { +enum BuildServerError: Error, CustomStringConvertible { case missingConfigFile case missingWorkspace + case missingProject case buildSettingsLoadFailed case buildSettingsForIndexLoadFailed + case invalidConfiguration(String) + case xcodebuildExecutionFailed(String) + case indexingPathsLoadFailed + + var description: String { + switch self { + case .missingConfigFile: + return "BSP configuration file not found" + case .missingWorkspace: + return "No workspace specified in configuration" + case .missingProject: + return "No project or workspace found" + case .buildSettingsLoadFailed: + return "Failed to load Xcode build settings" + case .buildSettingsForIndexLoadFailed: + return "Failed to load Xcode build settings for index" + case .invalidConfiguration(let message): + return "Invalid configuration: \(message)" + case .xcodebuildExecutionFailed(let output): + return "xcodebuild execution failed: \(output)" + case .indexingPathsLoadFailed: + return "Failed to load indexing paths" + } + } } struct BuildServerConfig: Codable { @@ -30,7 +55,7 @@ struct XcodeProject { var configuration: String? } -final class BuildServerContext: Sendable { +actor BuildServerContext { private(set) var rootURL: URL? private(set) var config: BuildServerConfig? private(set) var xcodeProject: XcodeProject? @@ -71,7 +96,7 @@ final class BuildServerContext: Sendable { private func loadXcodeBuildSettings() async throws { // xcodebuild -showBuildSettings -json // Load the index store - var arguments = getXcodeBuildBasicArguments() + var arguments = try getXcodeBuildBasicArguments() arguments.append(contentsOf: ["-destination", "generic/platform=iOS Simulator"]) arguments.append(contentsOf: ["-showBuildSettings", "-json"]) guard let json = try await xcodebuild(arguments: arguments), !json.isEmpty else { @@ -79,31 +104,49 @@ final class BuildServerContext: Sendable { } let data = Data(json.utf8) logger.debug("Build settings JSON: \(String(data: data, encoding: .utf8) ?? "nil", privacy: .public)") - buildSettings = try jsonDecoder.decode([BuildSettings].self, from: data) - logger.debug("Build settings: \(String(describing: self.buildSettings), privacy: .public)") + do { + buildSettings = try jsonDecoder.decode([BuildSettings].self, from: data) + logger.debug("Build settings: \(String(describing: self.buildSettings), privacy: .public)") + } catch { + logger.error("Failed to decode build settings: \(error)") + throw BuildServerError.buildSettingsLoadFailed + } } private func loadXcodeBuildSettingsForIndex() async throws { // xcodebuild -showBuildSettingsForIndex -json - var arguments = getXcodeBuildBasicArguments() + var arguments = try getXcodeBuildBasicArguments() // arguments.append(contentsOf: ["-destination", "generic/platform=iOS Simulator"]) arguments.append(contentsOf: ["-showBuildSettingsForIndex", "-json"]) guard let json = try await xcodebuild(arguments: arguments), !json.isEmpty else { - throw BuildServerError.buildSettingsLoadFailed + throw BuildServerError.buildSettingsForIndexLoadFailed } logger.debug("Build settings for index JSON: \(json, privacy: .public)") let data = Data(json.utf8) - buildSettingsForIndex = try jsonDecoder.decode(BuildSettingsForIndex.self, from: data) - logger.debug("Build settings for index: \(String(describing: self.buildSettingsForIndex), privacy: .public)") + do { + buildSettingsForIndex = try jsonDecoder.decode(BuildSettingsForIndex.self, from: data) + logger.debug( + "Build settings for index: \(String(describing: self.buildSettingsForIndex), privacy: .public)" + ) + } catch { + logger.error("Failed to decode build settings for index: \(error)") + throw BuildServerError.buildSettingsForIndexLoadFailed + } } private func loadIndexingPaths() async throws { - guard - let scheme = xcodeProject?.scheme, - let buildSettings = buildSettings?.first(where: { $0.target == scheme && $0.action == "build" })?.buildSettings, - let buildFolderPath = buildSettings["BUILD_DIR"] - else { - throw BuildServerError.buildSettingsLoadFailed + guard let scheme = xcodeProject?.scheme else { + throw BuildServerError.invalidConfiguration("No scheme available for indexing paths") + } + + guard let buildSettings = buildSettings?.first(where: { + $0.target == scheme && $0.action == "build" + })?.buildSettings else { + throw BuildServerError.invalidConfiguration("No build settings found for scheme: \(scheme)") + } + + guard let buildFolderPath = buildSettings["BUILD_DIR"] else { + throw BuildServerError.invalidConfiguration("BUILD_DIR not found in build settings") } let outputFolder = URL(fileURLWithPath: buildFolderPath) @@ -113,17 +156,22 @@ final class BuildServerContext: Sendable { indexStoreURL = outputFolder.appendingPathComponent("Index.noIndex/DataStore") let indexDatabaseURL = outputFolder.appendingPathComponent("IndexDatabase.noIndex") - if !FileManager.default.fileExists(atPath: indexDatabaseURL.path) { - try FileManager.default.createDirectory(at: indexDatabaseURL, withIntermediateDirectories: true) + do { + if !FileManager.default.fileExists(atPath: indexDatabaseURL.path) { + try FileManager.default.createDirectory(at: indexDatabaseURL, withIntermediateDirectories: true) + } + } catch { + logger.error("Failed to create index database directory: \(error)") + throw BuildServerError.indexingPathsLoadFailed } self.indexDatabaseURL = indexDatabaseURL logger.debug("Index store: \(String(describing: self.indexStoreURL), privacy: .public)") } - private func getXcodeBuildBasicArguments() -> [String] { + private func getXcodeBuildBasicArguments() throws -> [String] { guard let xcodeProject else { - fatalError("Xcode project not loaded") + throw BuildServerError.invalidConfiguration("Xcode project not loaded") } var arguments: [String] = [] @@ -132,7 +180,7 @@ final class BuildServerContext: Sendable { } else if let project = xcodeProject.project { arguments.append(contentsOf: ["-project", project]) } else { - fatalError("No workspace or project found") + throw BuildServerError.missingProject } if let scheme = xcodeProject.scheme { @@ -152,33 +200,60 @@ final class BuildServerContext: Sendable { return nil } - let buildServerConfigLocation: URL = workspaceFolder.appending(component: ".bsp") + let configSearchPaths = [ + // Standard BSP config location + workspaceFolder.appendingPathComponent(".bsp"), + // Legacy location for compatibility + workspaceFolder.appendingPathComponent("buildServer.json", isDirectory: false) + ] - let jsonFiles = - try? FileManager.default.contentsOfDirectory(at: buildServerConfigLocation, includingPropertiesForKeys: nil) - .filter { $0.pathExtension == "json" } + // First try standard BSP .bsp directory + if let bspDir = configSearchPaths.first, + FileManager.default.fileExists(atPath: bspDir.path) { + do { + let jsonFiles = try FileManager.default + .contentsOfDirectory(at: bspDir, includingPropertiesForKeys: nil) + .filter { $0.pathExtension == "json" } + .sorted { $0.lastPathComponent < $1.lastPathComponent } - if let configFileURL = jsonFiles?.sorted(by: { $0.lastPathComponent < $1.lastPathComponent }).first, - FileManager.default.fileExists(atPath: configFileURL.path) - { - return configFileURL + if let firstConfig = jsonFiles.first { + logger.debug("Found BSP config at: \(firstConfig.path)") + return firstConfig + } + } catch { + logger.debug("Failed to read .bsp directory: \(error)") + } } - // Pre Swift 6.1 SourceKit-LSP looked for `buildServer.json` in the project root. Maintain this search location for - // compatibility even though it's not a standard BSP search location. - let rootBuildServerJSONFile = workspaceFolder.appending(component: "buildServer.json") - if FileManager.default.fileExists(atPath: rootBuildServerJSONFile.path) { - return rootBuildServerJSONFile + // Fallback to legacy location + if let legacyConfig = configSearchPaths.last, + FileManager.default.fileExists(atPath: legacyConfig.path) { + logger.debug("Found legacy config at: \(legacyConfig.path)") + return legacyConfig } + logger.debug("No BSP configuration file found in workspace") return nil } private func loadConfig(configFileURL: URL) throws -> BuildServerConfig? { - let data = try Data(contentsOf: configFileURL) - let config = try JSONDecoder().decode(BuildServerConfig.self, from: data) - return config + logger.debug("Loading config from: \(configFileURL.path)") + + do { + let data = try Data(contentsOf: configFileURL) + var config = try JSONDecoder().decode(BuildServerConfig.self, from: data) + + // Validate and provide defaults + config = validateAndNormalizeConfig(config, rootURL: rootURL) + + logger.debug("Config loaded successfully: \(String(describing: config))") + return config + } catch { + logger.error("Failed to load config from \(configFileURL.path): \(error)") + throw BuildServerError.invalidConfiguration("Failed to parse config file: \(error.localizedDescription)") + } } + } extension BuildServerContext { @@ -195,3 +270,70 @@ extension BuildServerContext { return fileBuildSettings?.swiftASTCommandArguments ?? [] } } + +// MARK: - Private Configuration Helpers +private extension BuildServerContext { + func validateAndNormalizeConfig(_ config: BuildServerConfig, rootURL: URL?) -> BuildServerConfig { + var normalizedConfig = config + + // Ensure we have either workspace or project + if normalizedConfig.workspace == nil && normalizedConfig.project == nil { + logger.debug("No workspace or project specified, attempting to find one") + normalizedConfig = findWorkspaceOrProject(in: normalizedConfig, rootURL: rootURL) + } + + // Provide default configuration if none specified + if normalizedConfig.configuration == nil { + normalizedConfig = BuildServerConfig( + rootURL: normalizedConfig.rootURL, + workspace: normalizedConfig.workspace, + project: normalizedConfig.project, + scheme: normalizedConfig.scheme, + configuration: BuildServerConfig.defaultConfiguration + ) + logger.debug("Using default configuration: \(BuildServerConfig.defaultConfiguration)") + } + + return normalizedConfig + } + + func findWorkspaceOrProject(in config: BuildServerConfig, rootURL: URL?) -> BuildServerConfig { + guard let rootURL = rootURL else { return config } + + let fileManager = FileManager.default + + do { + let contents = try fileManager.contentsOfDirectory(at: rootURL, includingPropertiesForKeys: nil) + + // Look for .xcworkspace first + if let workspace = contents.first(where: { $0.pathExtension == "xcworkspace" }) { + let workspaceName = workspace.lastPathComponent + logger.debug("Found workspace: \(workspaceName)") + return BuildServerConfig( + rootURL: config.rootURL, + workspace: workspaceName, + project: config.project, + scheme: config.scheme, + configuration: config.configuration + ) + } + + // Fallback to .xcodeproj + if let project = contents.first(where: { $0.pathExtension == "xcodeproj" }) { + let projectName = project.lastPathComponent + logger.debug("Found project: \(projectName)") + return BuildServerConfig( + rootURL: config.rootURL, + workspace: config.workspace, + project: projectName, + scheme: config.scheme, + configuration: config.configuration + ) + } + } catch { + logger.debug("Failed to scan directory for Xcode projects: \(error)") + } + + return config + } +} diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/ProcessNotification.swift b/Sources/XcodeBuildServer/BSPServer/Messages/ProcessNotification.swift index 533088b4..fd600983 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/ProcessNotification.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/ProcessNotification.swift @@ -11,209 +11,208 @@ struct ProcessNotification: NotificationType, Sendable { public let token: ProgressToken public let value: WorkDoneProgressKind } - + static var method: String { "$/progress" } func handle(_: MessageHandler) async throws { - fatalError() + fatalError("ProcessNotification not implemented") } } public enum WorkDoneProgressKind: Codable, Hashable, Sendable { - case begin(WorkDoneProgressBegin) - case report(WorkDoneProgressReport) - case end(WorkDoneProgressEnd) - - public init(from decoder: Decoder) throws { - if let begin = try? WorkDoneProgressBegin(from: decoder) { - self = .begin(begin) - } else if let report = try? WorkDoneProgressReport(from: decoder) { - self = .report(report) - } else if let end = try? WorkDoneProgressEnd(from: decoder) { - self = .end(end) - } else { - let context = DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Expected WorkDoneProgressBegin, WorkDoneProgressReport, or WorkDoneProgressEnd" - ) - throw DecodingError.dataCorrupted(context) - } - } - - public func encode(to encoder: Encoder) throws { - switch self { - case .begin(let begin): - try begin.encode(to: encoder) - case .report(let report): - try report.encode(to: encoder) - case .end(let end): - try end.encode(to: encoder) - } - } + case begin(WorkDoneProgressBegin) + case report(WorkDoneProgressReport) + case end(WorkDoneProgressEnd) + + public init(from decoder: Decoder) throws { + if let begin = try? WorkDoneProgressBegin(from: decoder) { + self = .begin(begin) + } else if let report = try? WorkDoneProgressReport(from: decoder) { + self = .report(report) + } else if let end = try? WorkDoneProgressEnd(from: decoder) { + self = .end(end) + } else { + let context = DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Expected WorkDoneProgressBegin, WorkDoneProgressReport, or WorkDoneProgressEnd" + ) + throw DecodingError.dataCorrupted(context) + } + } + + public func encode(to encoder: Encoder) throws { + switch self { + case .begin(let begin): + try begin.encode(to: encoder) + case .report(let report): + try report.encode(to: encoder) + case .end(let end): + try end.encode(to: encoder) + } + } } public struct WorkDoneProgressBegin: Codable, Hashable, Sendable { - /// Mandatory title of the progress operation. Used to briefly inform about - /// the kind of operation being performed. - /// - /// Examples: "Indexing" or "Linking dependencies". - public var title: String - - /// Controls if a cancel button should show to allow the user to cancel the - /// long running operation. Clients that don't support cancellation are - /// allowed to ignore the setting. - public var cancellable: Bool? - - /// Optional, more detailed associated progress message. Contains - /// complementary information to the `title`. - /// - /// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". - /// If unset, the previous progress message (if any) is still valid. - public var message: String? - - /// Optional progress percentage to display (value 100 is considered 100%). - /// If not provided infinite progress is assumed and clients are allowed - /// to ignore the `percentage` value in subsequent in report notifications. - /// - /// The value should be steadily rising. Clients are free to ignore values - /// that are not following this rule. The value range is [0, 100] - public var percentage: Int? - - public init(title: String, cancellable: Bool? = nil, message: String? = nil, percentage: Int? = nil) { - self.title = title - self.cancellable = cancellable - self.message = message - self.percentage = percentage - } - - enum CodingKeys: CodingKey { - case kind - case title - case cancellable - case message - case percentage - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let kind = try container.decode(String.self, forKey: .kind) - guard kind == "begin" else { - throw DecodingError.dataCorruptedError( - forKey: .kind, - in: container, - debugDescription: "Kind of WorkDoneProgressBegin is not 'begin'" - ) - } - - self.title = try container.decode(String.self, forKey: .title) - self.cancellable = try container.decodeIfPresent(Bool.self, forKey: .cancellable) - self.message = try container.decodeIfPresent(String.self, forKey: .message) - self.percentage = try container.decodeIfPresent(Int.self, forKey: .percentage) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode("begin", forKey: .kind) - try container.encode(self.title, forKey: .title) - try container.encodeIfPresent(self.cancellable, forKey: .cancellable) - try container.encodeIfPresent(self.message, forKey: .message) - try container.encodeIfPresent(self.percentage, forKey: .percentage) - } + /// Mandatory title of the progress operation. Used to briefly inform about + /// the kind of operation being performed. + /// + /// Examples: "Indexing" or "Linking dependencies". + public var title: String + + /// Controls if a cancel button should show to allow the user to cancel the + /// long running operation. Clients that don't support cancellation are + /// allowed to ignore the setting. + public var cancellable: Bool? + + /// Optional, more detailed associated progress message. Contains + /// complementary information to the `title`. + /// + /// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". + /// If unset, the previous progress message (if any) is still valid. + public var message: String? + + /// Optional progress percentage to display (value 100 is considered 100%). + /// If not provided infinite progress is assumed and clients are allowed + /// to ignore the `percentage` value in subsequent in report notifications. + /// + /// The value should be steadily rising. Clients are free to ignore values + /// that are not following this rule. The value range is [0, 100] + public var percentage: Int? + + public init(title: String, cancellable: Bool? = nil, message: String? = nil, percentage: Int? = nil) { + self.title = title + self.cancellable = cancellable + self.message = message + self.percentage = percentage + } + + enum CodingKeys: CodingKey { + case kind + case title + case cancellable + case message + case percentage + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let kind = try container.decode(String.self, forKey: .kind) + guard kind == "begin" else { + throw DecodingError.dataCorruptedError( + forKey: .kind, + in: container, + debugDescription: "Kind of WorkDoneProgressBegin is not 'begin'" + ) + } + + self.title = try container.decode(String.self, forKey: .title) + self.cancellable = try container.decodeIfPresent(Bool.self, forKey: .cancellable) + self.message = try container.decodeIfPresent(String.self, forKey: .message) + self.percentage = try container.decodeIfPresent(Int.self, forKey: .percentage) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode("begin", forKey: .kind) + try container.encode(self.title, forKey: .title) + try container.encodeIfPresent(self.cancellable, forKey: .cancellable) + try container.encodeIfPresent(self.message, forKey: .message) + try container.encodeIfPresent(self.percentage, forKey: .percentage) + } } public struct WorkDoneProgressReport: Codable, Hashable, Sendable { - /// Controls enablement state of a cancel button. This property is only valid - /// if a cancel button got requested in the `WorkDoneProgressBegin` payload. - /// - /// Clients that don't support cancellation or don't support control the - /// button's enablement state are allowed to ignore the setting. - public var cancellable: Bool? - - /// Optional, more detailed associated progress message. Contains - /// complementary information to the `title`. - /// - /// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". - /// If unset, the previous progress message (if any) is still valid. - public var message: String? - - /// Optional progress percentage to display (value 100 is considered 100%). - /// If not provided infinite progress is assumed and clients are allowed - /// to ignore the `percentage` value in subsequent in report notifications. - /// - /// The value should be steadily rising. Clients are free to ignore values - /// that are not following this rule. The value range is [0, 100] - public var percentage: Int? - - public init(cancellable: Bool? = nil, message: String? = nil, percentage: Int? = nil) { - self.cancellable = cancellable - self.message = message - self.percentage = percentage - } - - enum CodingKeys: CodingKey { - case kind - case cancellable - case message - case percentage - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let kind = try container.decode(String.self, forKey: .kind) - guard kind == "report" else { - throw DecodingError.dataCorruptedError( - forKey: .kind, - in: container, - debugDescription: "Kind of WorkDoneProgressReport is not 'report'" - ) - } - - self.cancellable = try container.decodeIfPresent(Bool.self, forKey: .cancellable) - self.message = try container.decodeIfPresent(String.self, forKey: .message) - self.percentage = try container.decodeIfPresent(Int.self, forKey: .percentage) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode("report", forKey: .kind) - try container.encodeIfPresent(self.cancellable, forKey: .cancellable) - try container.encodeIfPresent(self.message, forKey: .message) - try container.encodeIfPresent(self.percentage, forKey: .percentage) - } + /// Controls enablement state of a cancel button. This property is only valid + /// if a cancel button got requested in the `WorkDoneProgressBegin` payload. + /// + /// Clients that don't support cancellation or don't support control the + /// button's enablement state are allowed to ignore the setting. + public var cancellable: Bool? + + /// Optional, more detailed associated progress message. Contains + /// complementary information to the `title`. + /// + /// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". + /// If unset, the previous progress message (if any) is still valid. + public var message: String? + + /// Optional progress percentage to display (value 100 is considered 100%). + /// If not provided infinite progress is assumed and clients are allowed + /// to ignore the `percentage` value in subsequent in report notifications. + /// + /// The value should be steadily rising. Clients are free to ignore values + /// that are not following this rule. The value range is [0, 100] + public var percentage: Int? + + public init(cancellable: Bool? = nil, message: String? = nil, percentage: Int? = nil) { + self.cancellable = cancellable + self.message = message + self.percentage = percentage + } + + enum CodingKeys: CodingKey { + case kind + case cancellable + case message + case percentage + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let kind = try container.decode(String.self, forKey: .kind) + guard kind == "report" else { + throw DecodingError.dataCorruptedError( + forKey: .kind, + in: container, + debugDescription: "Kind of WorkDoneProgressReport is not 'report'" + ) + } + + self.cancellable = try container.decodeIfPresent(Bool.self, forKey: .cancellable) + self.message = try container.decodeIfPresent(String.self, forKey: .message) + self.percentage = try container.decodeIfPresent(Int.self, forKey: .percentage) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode("report", forKey: .kind) + try container.encodeIfPresent(self.cancellable, forKey: .cancellable) + try container.encodeIfPresent(self.message, forKey: .message) + try container.encodeIfPresent(self.percentage, forKey: .percentage) + } } public struct WorkDoneProgressEnd: Codable, Hashable, Sendable { - /// Optional, a final message indicating to for example indicate the outcome - /// of the operation. - public var message: String? - - public init(message: String? = nil) { - self.message = message - } - - enum CodingKeys: CodingKey { - case kind - case message - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let kind = try container.decode(String.self, forKey: .kind) - guard kind == "end" else { - throw DecodingError.dataCorruptedError( - forKey: .kind, - in: container, - debugDescription: "Kind of WorkDoneProgressReport is not 'end'" - ) - } - - self.message = try container.decodeIfPresent(String.self, forKey: .message) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode("end", forKey: .kind) - try container.encodeIfPresent(self.message, forKey: .message) - } -} + /// Optional, a final message indicating to for example indicate the outcome + /// of the operation. + public var message: String? + + public init(message: String? = nil) { + self.message = message + } + enum CodingKeys: CodingKey { + case kind + case message + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let kind = try container.decode(String.self, forKey: .kind) + guard kind == "end" else { + throw DecodingError.dataCorruptedError( + forKey: .kind, + in: container, + debugDescription: "Kind of WorkDoneProgressReport is not 'end'" + ) + } + + self.message = try container.decodeIfPresent(String.self, forKey: .message) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode("end", forKey: .kind) + try container.encodeIfPresent(self.message, forKey: .message) + } +} diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildInitializeRequest.swift b/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildInitializeRequest.swift index c96bbe2b..762a0688 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildInitializeRequest.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildInitializeRequest.swift @@ -9,26 +9,26 @@ import Foundation /** { - "params": { - "rootUri": "file:///Users/ST22956/work-vscode/Hello/", - "capabilities": { - "languageIds": [ - "c", - "cpp", - "objective-c", - "objective-cpp", - "swift" - ] - }, - "version": "1.0", - "displayName": "SourceKit-LSP", - "bspVersion": "2.0" - }, - "method": "build/initialize", - "jsonrpc": "2.0", - "id": 1 + "params": { + "rootUri": "file:///Users/ST22956/work-vscode/Hello/", + "capabilities": { + "languageIds": [ + "c", + "cpp", + "objective-c", + "objective-cpp", + "swift" + ] + }, + "version": "1.0", + "displayName": "SourceKit-LSP", + "bspVersion": "2.0" + }, + "method": "build/initialize", + "jsonrpc": "2.0", + "id": 1 } - */ + */ public struct BuildInitializeRequest: RequestType, @unchecked Sendable { struct Params: Codable { @@ -58,8 +58,8 @@ public struct BuildInitializeRequest: RequestType, @unchecked Sendable { try await handler.initialize(rootURL: URL(filePath: params.rootUri)) guard let rootURL = URL(string: params.rootUri), // without file:// - let indexDataStoreURL = handler.buildServerContext.indexStoreURL, - let indexDatabaseURL = handler.buildServerContext.indexDatabaseURL + let indexDataStoreURL = await handler.getIndexStoreURL(), + let indexDatabaseURL = await handler.getIndexDatabaseURL() else { return nil } @@ -109,53 +109,53 @@ public struct BuildInitializeRequest: RequestType, @unchecked Sendable { /** { - "id": 1, - "result": { - "capabilities": { - "languageIds": [ - "c", - "cpp", - "objective-c", - "objective-cpp", - "swift" - ] - }, - "data": { - "indexDatabasePath": "/Users/ST22956/Library/Caches/xcode-build-server/Users-ST22956-work-vscode-Hello/indexDatabasePath", - "indexStorePath": "/Users/ST22956/Library/Developer/Xcode/DerivedData/Hello-fcuisfeafkcytvbjerdcxvnpmzxn/Index.noindex/DataStore" - }, - "version": "0.1", - "displayName": "xcode build server", - "bspVersion": "2.0", - "rootUri": "/Users/ST22956/work-vscode/Hello" - }, - "jsonrpc": "2.0" + "id": 1, + "result": { + "capabilities": { + "languageIds": [ + "c", + "cpp", + "objective-c", + "objective-cpp", + "swift" + ] + }, + "data": { + "indexDatabasePath": "/Users/ST22956/Library/Caches/xcode-build-server/Users-ST22956-work-vscode-Hello/indexDatabasePath", + "indexStorePath": "/Users/ST22956/Library/Developer/Xcode/DerivedData/Hello-fcuisfeafkcytvbjerdcxvnpmzxn/Index.noindex/DataStore" + }, + "version": "0.1", + "displayName": "xcode build server", + "bspVersion": "2.0", + "rootUri": "/Users/ST22956/work-vscode/Hello" + }, + "jsonrpc": "2.0" } */ /** { - "result": { - "data": { - "indexStorePath": "/Users/ST22956/Library/Developer/Xcode/DerivedData/Hello-fcuisfeafkcytvbjerdcxvnpmzxn/Index.noIndex/DataStore", - "indexDatabasePath": "/Users/ST22956/Library/Developer/Xcode/DerivedData/Hello-fcuisfeafkcytvbjerdcxvnpmzxn/IndexDatabase.noIndex" - }, - "bspVersion": "2.0", - "rootUri": "file:///Users/ST22956/work-vscode/Hello/", - "displayName": "xcode build server", - "capabilities": { - "languageIds": [ - "c", - "cpp", - "objective-c", - "objective-cpp", - "swift" - ] - }, - "version": "0.1" - }, - "jsonrpc": "2.0", - "id": 1 + "result": { + "data": { + "indexStorePath": "/Users/ST22956/Library/Developer/Xcode/DerivedData/Hello-fcuisfeafkcytvbjerdcxvnpmzxn/Index.noIndex/DataStore", + "indexDatabasePath": "/Users/ST22956/Library/Developer/Xcode/DerivedData/Hello-fcuisfeafkcytvbjerdcxvnpmzxn/IndexDatabase.noIndex" + }, + "bspVersion": "2.0", + "rootUri": "file:///Users/ST22956/work-vscode/Hello/", + "displayName": "xcode build server", + "capabilities": { + "languageIds": [ + "c", + "cpp", + "objective-c", + "objective-cpp", + "swift" + ] + }, + "version": "0.1" + }, + "jsonrpc": "2.0", + "id": 1 } */ diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildShutdownRequest.swift b/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildShutdownRequest.swift index 16204701..19ed7930 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildShutdownRequest.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildShutdownRequest.swift @@ -6,12 +6,12 @@ // public final class BuildShutdownRequest: Request, @unchecked Sendable { - override public class var method: String { "build/shutdown" } + override public static var method: String { "build/shutdown" } override public func handle( _: MessageHandler, id _: RequestID ) async -> ResponseType { - fatalError() + fatalError("BuildShutdownRequest not implemented") } } diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildSourceKitOptionsChangedNotification.swift b/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildSourceKitOptionsChangedNotification.swift index 3e937910..0f296495 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildSourceKitOptionsChangedNotification.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/build/BuildSourceKitOptionsChangedNotification.swift @@ -6,121 +6,121 @@ /** { - "jsonrpc": "2.0", - "method": "build\/sourceKitOptionsChanged", - "params": { - "updatedOptions": { - "options": [ - "-module-name", - "Hello", - "-Onone", - "-enforce-exclusivity=checked", - "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/Hello.swift", - "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/World.swift", - "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/AppDelegate.swift", - "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/SceneDelegate.swift", - "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/ViewController.swift", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources\/GeneratedAssetSymbols.swift", - "-DDEBUG", - "-enable-bare-slash-regex", - "-enable-experimental-feature", - "DebugDescriptionMacro", - "-sdk", - "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/iPhoneOS.platform\/Developer\/SDKs\/iPhoneOS18.1.sdk", - "-target", - "arm64-apple-ios18.0", - "-g", - "-module-cache-path", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/ModuleCache.noindex", - "-Xfrontend", - "-serialize-debugging-options", - "-profile-coverage-mapping", - "-profile-generate", - "-enable-testing", - "-index-store-path", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Index.noindex\/DataStore", - "-Xcc", - "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG", - "-swift-version", - "5", - "-Xcc", - "-I", - "-Xcc", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", - "-I", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", - "-Xcc", - "-F", - "-Xcc", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", - "-F", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", - "-emit-localized-strings", - "-emit-localized-strings-path", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Objects-normal\/arm64", - "-c", - "-j10", - "-enable-batch-mode", - "-Xcc", - "-ivfsstatcache", - "-Xcc", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/SDKStatCaches.noindex\/iphoneos18.1-22B74-456b5073a84ca8a40bffd5133c40ea2b.sdkstatcache", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/swift-overrides.hmap", - "-emit-const-values", - "-Xfrontend", - "-const-gather-protocols-file", - "-Xfrontend", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Objects-normal\/arm64\/Hello_const_extract_protocols.json", - "-Xcc", - "-iquote", - "-Xcc", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-generated-files.hmap", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-own-target-headers.hmap", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-all-target-headers.hmap", - "-Xcc", - "-iquote", - "-Xcc", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-project-headers.hmap", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos\/include", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources-normal\/arm64", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources\/arm64", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources", - "-Xcc", - "-DDEBUG=1", - "-working-directory", - "\/Users\/ST22956\/work-vscode\/Hello", - "-Xcc", - "-fretain-comments-from-system-headers", - "-Xcc", - "-Xclang", - "-Xcc", - "-detailed-preprocessing-record", - "-Xcc", - "-Xclang", - "-Xcc", - "-fmodule-format=raw", - "-Xcc", - "-Xclang", - "-Xcc", - "-fallow-pch-with-compiler-errors", - "-Xcc", - "-Wno-non-modular-include-in-framework-module", - "-Xcc", - "-Wno-incomplete-umbrella", - "-Xcc", - "-fmodules-validate-system-headers" - ], - "workingDirectory": "\/Users\/ST22956\/work-vscode\/Hello\/" - }, - "uri": "file:\/\/\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/World.swift" - } + "jsonrpc": "2.0", + "method": "build\/sourceKitOptionsChanged", + "params": { + "updatedOptions": { + "options": [ + "-module-name", + "Hello", + "-Onone", + "-enforce-exclusivity=checked", + "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/Hello.swift", + "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/World.swift", + "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/AppDelegate.swift", + "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/SceneDelegate.swift", + "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/ViewController.swift", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources\/GeneratedAssetSymbols.swift", + "-DDEBUG", + "-enable-bare-slash-regex", + "-enable-experimental-feature", + "DebugDescriptionMacro", + "-sdk", + "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/iPhoneOS.platform\/Developer\/SDKs\/iPhoneOS18.1.sdk", + "-target", + "arm64-apple-ios18.0", + "-g", + "-module-cache-path", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/ModuleCache.noindex", + "-Xfrontend", + "-serialize-debugging-options", + "-profile-coverage-mapping", + "-profile-generate", + "-enable-testing", + "-index-store-path", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Index.noindex\/DataStore", + "-Xcc", + "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG", + "-swift-version", + "5", + "-Xcc", + "-I", + "-Xcc", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", + "-I", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", + "-Xcc", + "-F", + "-Xcc", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", + "-F", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", + "-emit-localized-strings", + "-emit-localized-strings-path", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Objects-normal\/arm64", + "-c", + "-j10", + "-enable-batch-mode", + "-Xcc", + "-ivfsstatcache", + "-Xcc", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/SDKStatCaches.noindex\/iphoneos18.1-22B74-456b5073a84ca8a40bffd5133c40ea2b.sdkstatcache", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/swift-overrides.hmap", + "-emit-const-values", + "-Xfrontend", + "-const-gather-protocols-file", + "-Xfrontend", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Objects-normal\/arm64\/Hello_const_extract_protocols.json", + "-Xcc", + "-iquote", + "-Xcc", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-generated-files.hmap", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-own-target-headers.hmap", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-all-target-headers.hmap", + "-Xcc", + "-iquote", + "-Xcc", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-project-headers.hmap", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos\/include", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources-normal\/arm64", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources\/arm64", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources", + "-Xcc", + "-DDEBUG=1", + "-working-directory", + "\/Users\/ST22956\/work-vscode\/Hello", + "-Xcc", + "-fretain-comments-from-system-headers", + "-Xcc", + "-Xclang", + "-Xcc", + "-detailed-preprocessing-record", + "-Xcc", + "-Xclang", + "-Xcc", + "-fmodule-format=raw", + "-Xcc", + "-Xclang", + "-Xcc", + "-fallow-pch-with-compiler-errors", + "-Xcc", + "-Wno-non-modular-include-in-framework-module", + "-Xcc", + "-Wno-incomplete-umbrella", + "-Xcc", + "-fmodules-validate-system-headers" + ], + "workingDirectory": "\/Users\/ST22956\/work-vscode\/Hello\/" + }, + "uri": "file:\/\/\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/World.swift" + } } */ diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/build/OnBuildInitializedNotification.swift b/Sources/XcodeBuildServer/BSPServer/Messages/build/OnBuildInitializedNotification.swift index 6e8fd559..5a8841ed 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/build/OnBuildInitializedNotification.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/build/OnBuildInitializedNotification.swift @@ -7,11 +7,11 @@ /** { - "method": "build\/initialized", - "jsonrpc": "2.0", - "params": { + "method": "build\/initialized", + "jsonrpc": "2.0", + "params": { - } + } } */ diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/build/buildLogMessageRequest.swift b/Sources/XcodeBuildServer/BSPServer/Messages/build/buildLogMessageRequest.swift index 9d62c43a..dcaac92b 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/build/buildLogMessageRequest.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/build/buildLogMessageRequest.swift @@ -6,11 +6,11 @@ // public struct BuildLogMessageRequest: RequestType, @unchecked Sendable { - + public static var method: String { "build/logMessage" } - + let id: JSONRPCID - + public func handle( _ handler: any MessageHandler, id: RequestID diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/buildTarget/BuildTargetDidChangeNotification.swift b/Sources/XcodeBuildServer/BSPServer/Messages/buildTarget/BuildTargetDidChangeNotification.swift index b1e4fada..f35dd633 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/buildTarget/BuildTargetDidChangeNotification.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/buildTarget/BuildTargetDidChangeNotification.swift @@ -9,13 +9,13 @@ struct BuildTargetDidChangeNotification: NotificationType, Sendable { struct Params: Codable, Sendable { let changes: [BuildTargetEvent]? } - + static let method: String = "build/targetDidChange" - + let id: JSONRPCID let jsonrpc: String let params: Params - + func handle(_ handler: MessageHandler) async throws { } } @@ -23,13 +23,13 @@ struct BuildTargetDidChangeNotification: NotificationType, Sendable { public struct BuildTargetEvent: Codable, Hashable, Sendable { /// The identifier for the changed build target. public let target: BuildTargetIdentifier - + /// The kind of change for this build target. public let kind: BuildTargetEventKind? - + /// Kind of data to expect in the `data` field. If this field is not set, the kind of data is not specified. public let dataKind: BuildTargetEventDataKind? - + /// Any additional metadata about what information changed. public let data: LSPAny? } @@ -37,17 +37,17 @@ public struct BuildTargetEvent: Codable, Hashable, Sendable { public enum BuildTargetEventKind: Int, Codable, Hashable, Sendable { /// The build target is new. case created = 1 - + /// The build target has changed. case changed = 2 - + /// The build target has been deleted. case deleted = 3 } public struct BuildTargetEventDataKind: RawRepresentable, Codable, Hashable, Sendable { public var rawValue: String - + public init(rawValue: String) { self.rawValue = rawValue } diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/buildTarget/BuiltTargetSourcesRequest.swift b/Sources/XcodeBuildServer/BSPServer/Messages/buildTarget/BuiltTargetSourcesRequest.swift index 55053029..ea28da3f 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/buildTarget/BuiltTargetSourcesRequest.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/buildTarget/BuiltTargetSourcesRequest.swift @@ -21,7 +21,7 @@ struct BuiltTargetSourcesRequest: RequestType, Sendable { public struct BuildTargetSourcesResponse: ResponseType, Hashable { public var items: [SourcesItem] - + public init(items: [SourcesItem]) { self.items = items } @@ -29,14 +29,14 @@ public struct BuildTargetSourcesResponse: ResponseType, Hashable { public struct SourcesItem: Codable, Hashable, Sendable { public var target: BuildTargetIdentifier - + /// The text documents and directories that belong to this build target. public var sources: [SourceItem] - + /// The root directories from where source files should be relativized. /// Example: ["file://Users/name/dev/metals/src/main/scala"] public var roots: [URI]? - + public init(target: BuildTargetIdentifier, sources: [SourceItem], roots: [URI]? = nil) { self.target = target self.sources = sources @@ -49,20 +49,20 @@ public struct SourceItem: Codable, Hashable, Sendable { /// forward slash "/" and a directory entry implies that every nested text /// document within the directory belongs to this source item. public var uri: URI - + /// Type of file of the source item, such as whether it is file or directory. public var kind: SourceItemKind - + /// Indicates if this source is automatically generated by the build and is /// not intended to be manually edited by the user. public var generated: Bool - + /// Kind of data to expect in the `data` field. If this field is not set, the kind of data is not specified. public var dataKind: SourceItemDataKind? - + /// Language-specific metadata about this source item. public var data: LSPAny? - + public init( uri: URI, kind: SourceItemKind, @@ -81,21 +81,21 @@ public struct SourceItem: Codable, Hashable, Sendable { public enum SourceItemKind: Int, Codable, Hashable, Sendable { /// The source item references a normal file. case file = 1 - + /// The source item references a directory. case directory = 2 } public struct SourceItemDataKind: RawRepresentable, Codable, Hashable, Sendable { public var rawValue: String - + public init(rawValue: String) { self.rawValue = rawValue } - + /// `data` field must contain a JvmSourceItemData object. public static let jvm = SourceItemDataKind(rawValue: "jvm") - + /// `data` field must contain a `SourceKitSourceItemData` object. /// /// **(BSP Extension)** @@ -106,7 +106,7 @@ public struct SourceItemDataKind: RawRepresentable, Codable, Hashable, Sendable public struct SourceKitSourceItemData: LSPAnyCodable, Codable { /// The language of the source file. If `nil`, the language is inferred from the file extension. public var language: Language? - + /// Whether the file is a header file that is clearly associated with one target. /// /// For example header files in SwiftPM projects are always associated to one target and SwiftPM can provide build @@ -117,12 +117,12 @@ public struct SourceKitSourceItemData: LSPAnyCodable, Codable { /// inferring build settings from it. Listing header files in `buildTarget/sources` allows SourceKit-LSP to provide /// semantic functionality for header files if they haven't been included by any main file. public var isHeader: Bool? - + public init(language: Language? = nil, isHeader: Bool? = nil) { self.language = language self.isHeader = isHeader } - + public init?(fromLSPDictionary dictionary: [String: LSPAny]) { if case .string(let language) = dictionary[CodingKeys.language.stringValue] { self.language = Language(rawValue: language) @@ -131,7 +131,7 @@ public struct SourceKitSourceItemData: LSPAnyCodable, Codable { self.isHeader = isHeader } } - + public func encodeToLSPAny() -> LSPAny { var result: [String: LSPAny] = [:] if let language { @@ -143,4 +143,3 @@ public struct SourceKitSourceItemData: LSPAnyCodable, Codable { return .dictionary(result) } } - diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/textDocument/TextDocumentRegisterForChangeRequest.swift b/Sources/XcodeBuildServer/BSPServer/Messages/textDocument/TextDocumentRegisterForChangeRequest.swift index 490a2dbd..748c0f1a 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/textDocument/TextDocumentRegisterForChangeRequest.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/textDocument/TextDocumentRegisterForChangeRequest.swift @@ -7,14 +7,14 @@ import Foundation /** - { - "params": { - "uri" : "file:///Users/ST22956/work-vscode/Hello/Hello/World/World.swift", - "action" : "register" - }, - "method":"textDocument/registerForChanges", - "id":3, - "jsonrpc":"2.0" + { + "params": { + "uri" : "file:///Users/ST22956/work-vscode/Hello/Hello/World/World.swift", + "action" : "register" + }, + "method":"textDocument/registerForChanges", + "id":3, + "jsonrpc":"2.0" } */ @@ -48,16 +48,16 @@ public struct TextDocumentRegisterForChangeRequest: RequestType, @unchecked Send } if params.action == .register { - let arguments = handler.buildServerContext.getCompileArguments(fileURI: fileURL.path) - let workingDirectory = handler.buildServerContext.rootURL?.path + let arguments = await handler.getCompileArguments(fileURI: fileURL.path) + let workingDirectory = await handler.getRootURL()?.path return TextDocumentRegisterForChangeResponse( jsonrpc: jsonrpc, id: id, result: - TextDocumentRegisterForChangeResponse.Result( - compilerArguments: arguments, - workingDirectory: workingDirectory - ) + TextDocumentRegisterForChangeResponse.Result( + compilerArguments: arguments, + workingDirectory: workingDirectory + ) ) } else {} return nil diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/textDocument/TextDocumentSourceKitOptionsRequest.swift b/Sources/XcodeBuildServer/BSPServer/Messages/textDocument/TextDocumentSourceKitOptionsRequest.swift index 6eeec8fc..aa5d4f69 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/textDocument/TextDocumentSourceKitOptionsRequest.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/textDocument/TextDocumentSourceKitOptionsRequest.swift @@ -6,145 +6,145 @@ /** { - "jsonrpc": "2.0", - "method": "build\/sourceKitOptionsChanged", - "params": { - "updatedOptions": { - "options": [ - "-module-name", - "Hello", - "-Onone", - "-enforce-exclusivity=checked", - "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/Hello.swift", - "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/World.swift", - "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/AppDelegate.swift", - "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/SceneDelegate.swift", - "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/ViewController.swift", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources\/GeneratedAssetSymbols.swift", - "-DDEBUG", - "-enable-bare-slash-regex", - "-enable-experimental-feature", - "DebugDescriptionMacro", - "-sdk", - "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/iPhoneOS.platform\/Developer\/SDKs\/iPhoneOS18.1.sdk", - "-target", - "arm64-apple-ios18.0", - "-g", - "-module-cache-path", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/ModuleCache.noindex", - "-Xfrontend", - "-serialize-debugging-options", - "-profile-coverage-mapping", - "-profile-generate", - "-enable-testing", - "-index-store-path", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Index.noindex\/DataStore", - "-Xcc", - "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG", - "-swift-version", - "5", - "-Xcc", - "-I", - "-Xcc", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", - "-I", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", - "-Xcc", - "-F", - "-Xcc", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", - "-F", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", - "-emit-localized-strings", - "-emit-localized-strings-path", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Objects-normal\/arm64", - "-c", - "-j10", - "-enable-batch-mode", - "-Xcc", - "-ivfsstatcache", - "-Xcc", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/SDKStatCaches.noindex\/iphoneos18.1-22B74-456b5073a84ca8a40bffd5133c40ea2b.sdkstatcache", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/swift-overrides.hmap", - "-emit-const-values", - "-Xfrontend", - "-const-gather-protocols-file", - "-Xfrontend", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Objects-normal\/arm64\/Hello_const_extract_protocols.json", - "-Xcc", - "-iquote", - "-Xcc", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-generated-files.hmap", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-own-target-headers.hmap", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-all-target-headers.hmap", - "-Xcc", - "-iquote", - "-Xcc", - "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-project-headers.hmap", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos\/include", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources-normal\/arm64", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources\/arm64", - "-Xcc", - "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources", - "-Xcc", - "-DDEBUG=1", - "-working-directory", - "\/Users\/ST22956\/work-vscode\/Hello", - "-Xcc", - "-fretain-comments-from-system-headers", - "-Xcc", - "-Xclang", - "-Xcc", - "-detailed-preprocessing-record", - "-Xcc", - "-Xclang", - "-Xcc", - "-fmodule-format=raw", - "-Xcc", - "-Xclang", - "-Xcc", - "-fallow-pch-with-compiler-errors", - "-Xcc", - "-Wno-non-modular-include-in-framework-module", - "-Xcc", - "-Wno-incomplete-umbrella", - "-Xcc", - "-fmodules-validate-system-headers" - ], - "workingDirectory": "\/Users\/ST22956\/work-vscode\/Hello\/" - }, - "uri": "file:\/\/\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/World.swift" - } + "jsonrpc": "2.0", + "method": "build\/sourceKitOptionsChanged", + "params": { + "updatedOptions": { + "options": [ + "-module-name", + "Hello", + "-Onone", + "-enforce-exclusivity=checked", + "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/Hello.swift", + "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/World.swift", + "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/AppDelegate.swift", + "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/SceneDelegate.swift", + "\/Users\/ST22956\/work-vscode\/Hello\/Hello\/ViewController.swift", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources\/GeneratedAssetSymbols.swift", + "-DDEBUG", + "-enable-bare-slash-regex", + "-enable-experimental-feature", + "DebugDescriptionMacro", + "-sdk", + "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/iPhoneOS.platform\/Developer\/SDKs\/iPhoneOS18.1.sdk", + "-target", + "arm64-apple-ios18.0", + "-g", + "-module-cache-path", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/ModuleCache.noindex", + "-Xfrontend", + "-serialize-debugging-options", + "-profile-coverage-mapping", + "-profile-generate", + "-enable-testing", + "-index-store-path", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Index.noindex\/DataStore", + "-Xcc", + "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG", + "-swift-version", + "5", + "-Xcc", + "-I", + "-Xcc", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", + "-I", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", + "-Xcc", + "-F", + "-Xcc", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", + "-F", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos", + "-emit-localized-strings", + "-emit-localized-strings-path", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Objects-normal\/arm64", + "-c", + "-j10", + "-enable-batch-mode", + "-Xcc", + "-ivfsstatcache", + "-Xcc", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/SDKStatCaches.noindex\/iphoneos18.1-22B74-456b5073a84ca8a40bffd5133c40ea2b.sdkstatcache", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/swift-overrides.hmap", + "-emit-const-values", + "-Xfrontend", + "-const-gather-protocols-file", + "-Xfrontend", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Objects-normal\/arm64\/Hello_const_extract_protocols.json", + "-Xcc", + "-iquote", + "-Xcc", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-generated-files.hmap", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-own-target-headers.hmap", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-all-target-headers.hmap", + "-Xcc", + "-iquote", + "-Xcc", + "\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/Hello-project-headers.hmap", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Products\/Debug-iphoneos\/include", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources-normal\/arm64", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources\/arm64", + "-Xcc", + "-I\/Users\/ST22956\/Library\/Developer\/Xcode\/DerivedData\/Hello-fcuisfeafkcytvbjerdcxvnpmzxn\/Build\/Intermediates.noindex\/Hello.build\/Debug-iphoneos\/Hello.build\/DerivedSources", + "-Xcc", + "-DDEBUG=1", + "-working-directory", + "\/Users\/ST22956\/work-vscode\/Hello", + "-Xcc", + "-fretain-comments-from-system-headers", + "-Xcc", + "-Xclang", + "-Xcc", + "-detailed-preprocessing-record", + "-Xcc", + "-Xclang", + "-Xcc", + "-fmodule-format=raw", + "-Xcc", + "-Xclang", + "-Xcc", + "-fallow-pch-with-compiler-errors", + "-Xcc", + "-Wno-non-modular-include-in-framework-module", + "-Xcc", + "-Wno-incomplete-umbrella", + "-Xcc", + "-fmodules-validate-system-headers" + ], + "workingDirectory": "\/Users\/ST22956\/work-vscode\/Hello\/" + }, + "uri": "file:\/\/\/Users\/ST22956\/work-vscode\/Hello\/Hello\/World\/World.swift" + } } */ /** export interface TextDocumentSourceKitOptionsRequest { - /** The URI of the document to get options for */ - textDocument: TextDocumentIdentifier; + /** The URI of the document to get options for */ + textDocument: TextDocumentIdentifier; - /** The target for which the build setting should be returned. - * - * A source file might be part of multiple targets and might have different compiler arguments in those two targets, - * thus the target is necessary in this request. **/ - target: BuildTargetIdentifier; + /** The target for which the build setting should be returned. + * + * A source file might be part of multiple targets and might have different compiler arguments in those two targets, + * thus the target is necessary in this request. **/ + target: BuildTargetIdentifier; - /** The language with which the document was opened in the editor. */ - language: LanguageId; + /** The language with which the document was opened in the editor. */ + language: LanguageId; } export interface TextDocumentSourceKitOptionsResult { - /** The compiler options required for the requested file. */ - compilerArguments: string[]; + /** The compiler options required for the requested file. */ + compilerArguments: string[]; - /** The working directory for the compile command. */ - workingDirectory?: string; + /** The working directory for the compile command. */ + workingDirectory?: string; } */ diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/window/WindowWorkDoneProgressCreate.swift b/Sources/XcodeBuildServer/BSPServer/Messages/window/WindowWorkDoneProgressCreate.swift index 96614dfc..bbbeefa4 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/window/WindowWorkDoneProgressCreate.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/window/WindowWorkDoneProgressCreate.swift @@ -9,10 +9,10 @@ public struct CreateWorkDoneProgressRequest: RequestType { struct Params: Codable { let token: ProgressToken } - + public static let method: String = "window/workDoneProgress/create" - + public func handle(_ handler: any MessageHandler, id: RequestID) async -> (any ResponseType)? { - fatalError() + fatalError("WindowWorkDoneProgressCreate not implemented") } } diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceBuildTargetsRequest.swift b/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceBuildTargetsRequest.swift index d99470fc..6f7e7825 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceBuildTargetsRequest.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceBuildTargetsRequest.swift @@ -6,15 +6,15 @@ /** export interface BuildTargetTag { - // ... + // ... - /** This is a target of a dependency from the project the user opened, eg. a target that builds a SwiftPM dependency. */ - export const Dependency = "dependency"; + /** This is a target of a dependency from the project the user opened, eg. a target that builds a SwiftPM dependency. */ + export const Dependency = "dependency"; - /** This target only exists to provide compiler arguments for SourceKit-LSP can't be built standalone. - * - * For example, a SwiftPM package manifest is in a non-buildable target. **/ - export const NotBuildable = "not-buildable"; + /** This target only exists to provide compiler arguments for SourceKit-LSP can't be built standalone. + * + * For example, a SwiftPM package manifest is in a non-buildable target. **/ + export const NotBuildable = "not-buildable"; } */ @@ -30,6 +30,6 @@ public struct WorkspaceBuildTargetsRequest: RequestType, @unchecked Sendable { _: MessageHandler, id _: RequestID ) async -> ResponseType? { - fatalError() + fatalError("WorkspaceBuildTargetsRequest not implemented") } } diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceDidChangeWatchedFilesNotification.swift b/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceDidChangeWatchedFilesNotification.swift index 329c963f..e33c00f0 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceDidChangeWatchedFilesNotification.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceDidChangeWatchedFilesNotification.swift @@ -14,6 +14,6 @@ public struct WorkspaceDidChangeWatchedFilesNotification: NotificationType, @unc public func handle( _: MessageHandler ) async { - fatalError() + fatalError("WorkspaceDidChangeWatchedFilesNotification not implemented") } } diff --git a/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceWaitForBuildSystemUpdates.swift b/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceWaitForBuildSystemUpdates.swift index 5a6253c2..24bc4d4b 100644 --- a/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceWaitForBuildSystemUpdates.swift +++ b/Sources/XcodeBuildServer/BSPServer/Messages/workspace/WorkspaceWaitForBuildSystemUpdates.swift @@ -18,6 +18,6 @@ public struct WorkspaceWaitForBuildSystemUpdatesRequest: RequestType, @unchecked _: MessageHandler, id _: RequestID ) async -> ResponseType? { - fatalError() + fatalError("WorkspaceWaitForBuildSystemUpdatesRequest not implemented") } } diff --git a/Sources/XcodeBuildServer/BSPServer/Transportation/Imp/StdioJSONRPCServerTransport.swift b/Sources/XcodeBuildServer/BSPServer/Transportation/Imp/StdioJSONRPCServerTransport.swift index 97ad8582..5fb7562e 100644 --- a/Sources/XcodeBuildServer/BSPServer/Transportation/Imp/StdioJSONRPCServerTransport.swift +++ b/Sources/XcodeBuildServer/BSPServer/Transportation/Imp/StdioJSONRPCServerTransport.swift @@ -12,7 +12,21 @@ public final class StdioJSONRPCServerTransport: JSONRPCServerTransport { private let output: FileHandle private let jsonDecoder = JSONDecoder() private let jsonEncoder = JSONEncoder() - public var requestHandler: RequestHandler? + private let requestHandlerLock = NSLock() + nonisolated(unsafe) private var _requestHandler: RequestHandler? + + public var requestHandler: RequestHandler? { + get { + requestHandlerLock.lock() + defer { requestHandlerLock.unlock() } + return _requestHandler + } + set { + requestHandlerLock.lock() + defer { requestHandlerLock.unlock() } + _requestHandler = newValue + } + } public init() { input = .standardInput @@ -30,6 +44,13 @@ public final class StdioJSONRPCServerTransport: JSONRPCServerTransport { RunLoop.current.run() } + public func close() { + logger.debug("Closing stdio transport") + input.readabilityHandler = nil + // Note: We don't close stdio handles as they are managed by the system + logger.debug("Stdio transport closed") + } + private func handleData(fileHandle: FileHandle) throws { let data = fileHandle.availableData guard diff --git a/Sources/XcodeBuildServer/BSPServer/XcodeBSPMessageHandler.swift b/Sources/XcodeBuildServer/BSPServer/XcodeBSPMessageHandler.swift index 8caeb2e7..02af4060 100644 --- a/Sources/XcodeBuildServer/BSPServer/XcodeBSPMessageHandler.swift +++ b/Sources/XcodeBuildServer/BSPServer/XcodeBSPMessageHandler.swift @@ -14,4 +14,24 @@ public final class XcodeBSPMessageHandler: MessageHandler, Sendable { public func initialize(rootURL: URL) async throws { try await buildServerContext.loadProject(rootURL: rootURL) } + + func getBuildSettings() async -> [BuildSettings]? { + return await buildServerContext.buildSettings + } + + func getIndexStoreURL() async -> URL? { + return await buildServerContext.indexStoreURL + } + + func getIndexDatabaseURL() async -> URL? { + return await buildServerContext.indexDatabaseURL + } + + func getCompileArguments(fileURI: String) async -> [String] { + return await buildServerContext.getCompileArguments(fileURI: fileURI) + } + + func getRootURL() async -> URL? { + return await buildServerContext.rootURL + } } diff --git a/Sources/XcodeBuildServer/BSPServer/XcodeBuild/BuildSettings.swift b/Sources/XcodeBuildServer/BSPServer/XcodeBuild/BuildSettings.swift index f8097aa0..5b8cef64 100644 --- a/Sources/XcodeBuildServer/BSPServer/XcodeBuild/BuildSettings.swift +++ b/Sources/XcodeBuildServer/BSPServer/XcodeBuild/BuildSettings.swift @@ -21,7 +21,7 @@ typealias BuildSettingsForIndex = [String: [String: FileBuildSettingInfoForIndex struct FileBuildSettingInfoForIndex: Decodable { var assetSymbolIndexPath: String? - var LanguageDialect: LanguageDialect + var languageDialect: LanguageDialect var outputFilePath: String? var swiftASTBuiltProductsDir: String? var swiftASTCommandArguments: [String]? diff --git a/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCMessage.swift b/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCMessage.swift index 722d2931..ee5d15c5 100644 --- a/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCMessage.swift +++ b/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCMessage.swift @@ -27,7 +27,13 @@ public enum JSONRPCID: Codable, Equatable, Hashable, Sendable { } else if let stringValue = try? container.decode(String.self) { self = .string(stringValue) } else { - throw DecodingError.typeMismatch(JSONRPCID.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "ID is not a valid type")) + throw DecodingError.typeMismatch( + JSONRPCID.self, + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "ID is not a valid type" + ) + ) } } } @@ -43,6 +49,33 @@ public struct JSONRPCError: Codable, Sendable { let code: Int let message: String let data: JSONValue? + + public init(code: Int, message: String, data: JSONValue? = nil) { + self.code = code + self.message = message + self.data = data + } + + // Standard JSON-RPC error codes + static func parseError(_ message: String) -> JSONRPCError { + JSONRPCError(code: -32700, message: "Parse error: \(message)") + } + + static func invalidRequest(_ message: String) -> JSONRPCError { + JSONRPCError(code: -32600, message: "Invalid request: \(message)") + } + + static func methodNotFound(_ message: String) -> JSONRPCError { + JSONRPCError(code: -32601, message: "Method not found: \(message)") + } + + static func invalidParams(_ message: String) -> JSONRPCError { + JSONRPCError(code: -32602, message: "Invalid params: \(message)") + } + + static func internalError(_ message: String) -> JSONRPCError { + JSONRPCError(code: -32603, message: "Internal error: \(message)") + } } public enum JSONRPCResult: Codable, Sendable { @@ -57,7 +90,11 @@ public enum JSONRPCResult: Codable, Sendable { } else if let error = try? container.decode(JSONRPCError.self, forKey: .error) { self = .error(error) } else { - throw DecodingError.dataCorruptedError(forKey: .result, in: container, debugDescription: "Response must have either result or error") + throw DecodingError.dataCorruptedError( + forKey: .result, + in: container, + debugDescription: "Response must have either result or error" + ) } } @@ -83,6 +120,18 @@ public struct JSONRPCResponse: ResponseType { let response: JSONRPCResult } +public struct JSONRPCErrorResponse: ResponseType { + public let jsonrpc: String + public let id: JSONRPCID? + public let error: JSONRPCError + + public init(jsonrpc: String = "2.0", id: JSONRPCID?, error: JSONRPCError) { + self.jsonrpc = jsonrpc + self.id = id + self.error = error + } +} + public enum JSONValue: Codable, Sendable { case int(Int) case double(Double) diff --git a/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCServer.swift b/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCServer.swift index 6beec6da..91c7d7b5 100644 --- a/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCServer.swift +++ b/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCServer.swift @@ -33,22 +33,73 @@ public final actor JSONRPCServer { transport.listen() } - func close() {} + func close() async { + logger.debug("Closing JSON-RPC server") + transport.close() + logger.debug("JSON-RPC server closed") + } private func onReceivedMesssage(request: JSONRPCRequest, requestData: Data) async { logger.debug("Received method: \(request.method, privacy: .public)") + if let requestType = messageRegistry.requestType(for: request.method) { + await handleRequest(request: request, requestData: requestData, requestType: requestType) + } else if let notificationType = messageRegistry.notificationType(for: request.method) { + await handleNotification(requestData: requestData, notificationType: notificationType) + } else { + logger.error("Unknown method: \(request.method)") if let requestID = request.id { - let typedRequest = try! jsonDecoder.decode(requestType, from: requestData) - guard let response = await typedRequest.handle(messageHandler, id: requestID) else { - return - } - try? send(response: response) + await sendErrorResponse(id: requestID, error: .methodNotFound("Method not found: \(request.method)")) } - } else if let notificationType = messageRegistry.notificationType(for: request.method) { - if let typedNotification = try? jsonDecoder.decode(notificationType, from: requestData) { - try? await typedNotification.handle(messageHandler) + } + } + + private func handleRequest(request: JSONRPCRequest, requestData: Data, requestType: any RequestType.Type) async { + guard let requestID = request.id else { + logger.error("Request missing ID for method: \(request.method)") + return + } + + do { + let typedRequest = try jsonDecoder.decode(requestType, from: requestData) + + if let response = await typedRequest.handle(messageHandler, id: requestID) { + do { + try send(response: response) + } catch { + logger.error("Failed to send response: \(error)") + } + } else { + logger.error("Handler returned nil response for method: \(request.method)") + await sendErrorResponse(id: requestID, error: .internalError("Handler failed to process request")) } + } catch { + logger.error("Failed to decode request for method \(request.method): \(error)") + await sendErrorResponse(id: requestID, error: .parseError("Invalid request format")) + } + } + + private func handleNotification(requestData: Data, notificationType: NotificationType.Type) async { + do { + let typedNotification = try jsonDecoder.decode(notificationType, from: requestData) + try await typedNotification.handle(messageHandler) + } catch { + logger.error("Failed to handle notification: \(error)") + // Notifications don't have responses, so we can only log the error + } + } + + private func sendErrorResponse(id: RequestID, error: JSONRPCError) async { + let errorResponse = JSONRPCErrorResponse( + jsonrpc: "2.0", + id: id, + error: error + ) + + do { + try send(response: errorResponse) + } catch { + logger.error("Failed to send error response: \(error)") } } diff --git a/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCTransport.swift b/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCTransport.swift index 76edf8ab..7699f322 100644 --- a/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCTransport.swift +++ b/Sources/XcodeBuildServer/JSONRPC/JSONRPCServer/JSONRPCTransport.swift @@ -20,6 +20,7 @@ public typealias RequestHandler = @Sendable (_ request: JSONRPCRequest, _ reques public protocol JSONRPCServerTransport: AnyObject, Sendable { func listen() + func close() var requestHandler: RequestHandler? { get set } func send(response: ResponseType) throws } diff --git a/Sources/XcodeBuildServer/Logger.swift b/Sources/XcodeBuildServer/Logger.swift index df237c3e..5d170748 100644 --- a/Sources/XcodeBuildServer/Logger.swift +++ b/Sources/XcodeBuildServer/Logger.swift @@ -4,9 +4,9 @@ // Copyright ยฉ 2024 Wang Lun. // -import OSLog +@preconcurrency import OSLog -let privacy: OSLogPrivacy = .public +nonisolated(unsafe) let privacy: OSLogPrivacy = .public let logger = Logger( subsystem: "XocdeBuildServer", category: "main" diff --git a/Tests/XcodeBuildServerTests/BSPMessageTests.swift b/Tests/XcodeBuildServerTests/BSPMessageTests.swift index 6b36bfc8..bf1f5c39 100644 --- a/Tests/XcodeBuildServerTests/BSPMessageTests.swift +++ b/Tests/XcodeBuildServerTests/BSPMessageTests.swift @@ -13,14 +13,20 @@ final class BSPMessageTests: XCTestCase { { "jsonrpc": "2.0", "method": "build/initialize", - "capabilities": { - "languageIds": [ - "c", - "cpp", - "objective-c", - "objective-cpp", - "swift" - ] + "params": { + "rootUri": "file:///Users/test/project", + "capabilities": { + "languageIds": [ + "c", + "cpp", + "objective-c", + "objective-cpp", + "swift" + ] + }, + "displayName": "Test Client", + "bspVersion": "2.0", + "version": "1.0" }, "id": 1 } @@ -28,11 +34,24 @@ final class BSPMessageTests: XCTestCase { let data = message.data(using: .utf8)! let request = try JSONDecoder().decode(JSONRPCRequest.self, from: data) + XCTAssertEqual(request.method, "build/initialize") + XCTAssertEqual(request.jsonrpc, "2.0") + XCTAssertNotNil(request.id) + + // Test that we can decode the specific request type if let requestType: RequestType.Type = bspRegistry.requestType(for: request.method) { let typedRequest = try JSONDecoder().decode(requestType.self, from: data) - print(typedRequest) + // Verify it's the correct type + XCTAssertTrue(typedRequest is BuildInitializeRequest) + if let buildRequest = typedRequest as? BuildInitializeRequest { + XCTAssertEqual(buildRequest.params.rootUri, "file:///Users/test/project") + XCTAssertEqual(buildRequest.params.displayName, "Test Client") + XCTAssertEqual(buildRequest.params.capabilities.languageIds.count, 5) + XCTAssertTrue(buildRequest.params.capabilities.languageIds.contains(.swift)) + XCTAssertTrue(buildRequest.params.capabilities.languageIds.contains(.c)) + } + } else { + XCTFail("Should find request type for build/initialize") } - - XCTAssertEqual(request.method, "build/initialize") } }