diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 0000000..49c2383 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,53 @@ +name: Integration Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + integration-test: + name: Integration Tests + runs-on: macos-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build CLI + run: swift build --product ackgen + + - name: Run Integration Tests + run: swift test --filter IntegrationTests + + - name: Test Example Project Setup + run: | + cd Example + + # Simulate a real Xcode project environment + export SRCROOT="${PWD}" + export PROJECT_TEMP_DIR="${PWD}/Build/Intermediates.noindex/AckGenExample.build" + + # Create mock directory structure + mkdir -p "${PROJECT_TEMP_DIR}" + mkdir -p "Build/DerivedData/AckGenExample-test/SourcePackages/checkouts/AckGen" + + # The path calculation should work (using the improved logic) + BASE_DIR="${PROJECT_TEMP_DIR%/Build/*}" + CALCULATED_PATH="${BASE_DIR}/SourcePackages/checkouts" + echo "Calculated package path: ${CALCULATED_PATH}" + + # Verify the calculation matches expected structure + if [[ "${CALCULATED_PATH}" == *"/SourcePackages/checkouts" ]]; then + echo "✅ Path calculation works correctly" + else + echo "❌ Path calculation failed" + exit 1 + fi diff --git a/Example/ackgen.sh b/Example/ackgen.sh index 2530de5..43535ee 100644 --- a/Example/ackgen.sh +++ b/Example/ackgen.sh @@ -1,10 +1,14 @@ -# DIR=$PROJECT_TEMP_DIR/../../../SourcePackages/checkouts/AckGen -# Different path, because the sample uses AckGen as a local package: +# For the Example app, AckGen is used as a local package, so we use a simple relative path +# In your own project with SPM, use the dynamic path calculation from the README: +# BASE_DIR="${PROJECT_TEMP_DIR%/Build/*}" +# DIR="$BASE_DIR/SourcePackages/checkouts/AckGen" DIR=.. + if [ -d "$DIR" ]; then - cd $DIR - SDKROOT=(xcrun --sdk macosx --show-sdk-path) - swift run ackgen $SRCROOT/PackageLicenses.plist + cd "$DIR" + SDKROOT=$(xcrun --sdk macosx --show-sdk-path) + swift run ackgen "$SRCROOT/PackageLicenses.plist" else echo "warning: AckGen not found. Please install the package via SPM (https://github.com/MartinP7r/AckGen#installation)" fi + diff --git a/README.md b/README.md index e865481..89b5f7e 100644 --- a/README.md +++ b/README.md @@ -25,16 +25,23 @@ This can be used to feed a SwiftUI List or UITableView dataSource in your app. 2. Add the following as a Run Script for your target in Xcode ```sh -DIR=$PROJECT_TEMP_DIR/../../../SourcePackages/checkouts/AckGen +# Calculate the package path dynamically (works with various Xcode configurations) +# Use parameter expansion to remove everything from /Build/ onwards +BASE_DIR="${PROJECT_TEMP_DIR%/Build/*}" +DIR="$BASE_DIR/SourcePackages/checkouts/AckGen" + if [ -d "$DIR" ]; then - cd $DIR - SDKROOT=(xcrun --sdk macosx --show-sdk-path) + cd "$DIR" + SDKROOT=$(xcrun --sdk macosx --show-sdk-path) swift run ackgen else echo "warning: AckGen not found. Please install the package via SPM (https://github.com/MartinP7r/AckGen#installation)" fi ``` +> **Note** +> The script dynamically calculates the package path from `PROJECT_TEMP_DIR` to support various Xcode project configurations. If you encounter issues, run `./diagnose_path.sh` from this repository to debug your setup. + Make sure to set `ENABLE_USER_SCRIPT_SANDBOXING` to `NO` in your build settings so the build phase above can write to the desired destination. If you want the plist file to be saved somewhere other than `Acknowledgements.plist` at the root of your project (`$SRCROOT/Acknowledgements.plist`), you can provide a custom path as the first command line argument to `ackgen` above. @@ -76,6 +83,39 @@ struct ContentView: View { Until 1.0 is reached, minor versions will be breaking. +## Troubleshooting + +### "AckGen not found" Error + +If you see the warning `AckGen not found. Please install the package via SPM`, try the following: + +1. **Run the diagnostic script**: + - Swift version: `swift diagnose_path.swift` (set `PROJECT_TEMP_DIR` environment variable first) + - Shell version: `./diagnose_path.sh` (set `PROJECT_TEMP_DIR` environment variable first) +2. **Verify SPM installation**: Make sure AckGen is added as a Swift Package dependency in your Xcode project +3. **Build your project**: SPM dependencies are only downloaded after you build your project at least once +4. **Check your path**: The script in the README dynamically calculates the package path. If your project uses a non-standard structure, you may need to adjust the path calculation + +#### Understanding Path Detection + +Xcode places SPM packages in different locations depending on your project setup. The recommended script uses: + +```sh +BASE_DIR="${PROJECT_TEMP_DIR%/Build/*}" +DIR="$BASE_DIR/SourcePackages/checkouts/AckGen" +``` + +This uses bash parameter expansion to remove everything from `/Build/` onwards (including `/Build/` itself) and appends the standard SPM checkout path. This approach: +- Works across different Xcode versions +- Handles Debug/Release configurations +- Supports iOS/macOS/watchOS/tvOS targets +- Works with Simulator/Device builds +- Handles edge cases like usernames or project names containing "Build" + +The calculation finds the **last** occurrence of `/Build/` in the path, ensuring correct behavior even when "Build" appears elsewhere in the path (e.g., `/Users/Build/Projects/MyApp/Build/Intermediates`). + +If you need a different path for your setup (e.g., local package development), you can modify the `DIR` variable accordingly. See `Example/ackgen.sh` for an example of using a local package. + ## Contribution This is my first stab at building a Swift package and was mainly intended to be an exercise. diff --git a/Sources/AckGenCLI/AckGen.swift b/Sources/AckGenCLI/AckGen.swift index ad75fd6..57c1eb5 100644 --- a/Sources/AckGenCLI/AckGen.swift +++ b/Sources/AckGenCLI/AckGen.swift @@ -32,7 +32,17 @@ struct AckGenCLI { let settingsTitle: String = arguments.count > 2 ? arguments[2] : "Acknowledgements" - let packageCachePath = tempDirPath.components(separatedBy: "/Build/")[0] + "/SourcePackages/checkouts" + // Calculate package cache path using improved logic + // Find the last occurrence of "/Build/" to handle edge cases like "Build" in username + let packageCachePath: String + if let range = tempDirPath.range(of: "/Build/", options: .backwards) { + let basePath = String(tempDirPath[.. String { + if let range = projectTempDir.range(of: "/Build/", options: .backwards) { + return String(projectTempDir[../dev/null | grep -v "^\." || echo "") + if [ -n "$PACKAGES" ]; then + echo " ✓ Found packages:" + echo "$PACKAGES" | sed 's/^/ - /' + else + echo " ⚠ No packages found in $CALCULATED_PACKAGE_PATH" + fi +else + echo " ✗ Calculated path does not exist: $CALCULATED_PACKAGE_PATH" +fi + +if [ -d "$RELATIVE_PACKAGE_PATH" ]; then + echo " ✓ Relative path exists: $RELATIVE_PACKAGE_PATH" +else + echo " ✗ Relative path does not exist: $RELATIVE_PACKAGE_PATH" +fi + +# Check for AckGen specifically +ACKGEN_CALCULATED="${CALCULATED_PACKAGE_PATH}/AckGen" +ACKGEN_RELATIVE="${RELATIVE_PACKAGE_PATH}/AckGen" + +echo "" +echo "🔎 AckGen Package Location:" +if [ -d "$ACKGEN_CALCULATED" ]; then + echo " ✓ Found via calculated path: $ACKGEN_CALCULATED" +elif [ -d "$ACKGEN_RELATIVE" ]; then + echo " ✓ Found via relative path: $ACKGEN_RELATIVE" +else + echo " ✗ AckGen package not found in either location" + echo "" + echo "💡 Troubleshooting:" + echo " 1. Make sure AckGen is added as a Swift Package dependency in Xcode" + echo " 2. Build your project at least once so SPM downloads dependencies" + echo " 3. Check if packages are in a different location:" + echo " find ~/Library/Developer/Xcode/DerivedData -name AckGen -type d 2>/dev/null" +fi + +echo "" +echo "📋 Summary:" +if [ -d "$ACKGEN_CALCULATED" ] || [ -d "$ACKGEN_RELATIVE" ]; then + echo " ✅ AckGen package is accessible" + echo "" + echo "Use this in your Run Script:" + echo "┌─────────────────────────────────────────────────────────────" + if [ -d "$ACKGEN_CALCULATED" ]; then + echo "│ DIR=${CALCULATED_PACKAGE_PATH}/AckGen" + else + echo "│ DIR=${RELATIVE_PACKAGE_PATH}/AckGen" + fi + echo "│ if [ -d \"\$DIR\" ]; then" + echo "│ cd \$DIR" + echo "│ SDKROOT=\$(xcrun --sdk macosx --show-sdk-path)" + echo "│ swift run ackgen" + echo "│ else" + echo "│ echo \"warning: AckGen not found at \$DIR\"" + echo "│ fi" + echo "└─────────────────────────────────────────────────────────────" +else + echo " ❌ AckGen package could not be found" + echo " Please verify that AckGen is installed via Swift Package Manager" +fi + +echo "" diff --git a/diagnose_path.swift b/diagnose_path.swift new file mode 100755 index 0000000..afe9cba --- /dev/null +++ b/diagnose_path.swift @@ -0,0 +1,152 @@ +#!/usr/bin/env swift + +import Foundation + +/// Diagnostic tool for AckGen path detection issues +struct PathDiagnostics { + + static func main() { + print("🔍 AckGen Path Diagnostics") + print("==========================") + print("") + + // Check if we're running in an Xcode environment + guard let projectTempDir = ProcessInfo.processInfo.environment["PROJECT_TEMP_DIR"] else { + print("❌ ERROR: PROJECT_TEMP_DIR is not set") + print("This script must be run from an Xcode Run Script phase or with PROJECT_TEMP_DIR set") + print("") + print("To test manually, set PROJECT_TEMP_DIR to your Xcode build directory, e.g.:") + print("export PROJECT_TEMP_DIR=/Users/$USER/Library/Developer/Xcode/DerivedData/YourApp-xyz/Build/Intermediates.noindex/YourApp.build") + exit(1) + } + + let srcRoot = ProcessInfo.processInfo.environment["SRCROOT"] + + print("📂 Environment Variables:") + print(" PROJECT_TEMP_DIR: \(projectTempDir)") + print(" SRCROOT: \(srcRoot ?? "not set")") + print("") + + // Calculate package path using improved logic (handles edge cases like "Build" in username) + // Find the last occurrence of "/Build/" and take everything before it + let calculatedBase: String + if let range = projectTempDir.range(of: "/Build/", options: .backwards) { + calculatedBase = String(projectTempDir[../dev/null") + } + + print("") + print("📋 Summary:") + + if ackgenFound { + print(" ✅ AckGen package is accessible") + print("") + print("Use this in your Run Script:") + print("┌─────────────────────────────────────────────────────────────") + print("│ # Calculate the package path dynamically") + print("│ BASE_DIR=\"${PROJECT_TEMP_DIR%/Build/*}\"") + print("│ DIR=\"$BASE_DIR/SourcePackages/checkouts/AckGen\"") + print("│ ") + print("│ if [ -d \"$DIR\" ]; then") + print("│ cd \"$DIR\"") + print("│ SDKROOT=$(xcrun --sdk macosx --show-sdk-path)") + print("│ swift run ackgen") + print("│ else") + print("│ echo \"warning: AckGen not found at $DIR\"") + print("│ fi") + print("└─────────────────────────────────────────────────────────────") + } else { + print(" ❌ AckGen package could not be found") + print(" Please verify that AckGen is installed via Swift Package Manager") + } + + print("") + + // Additional diagnostics + if calculatedPathExists && !foundPackages.contains("AckGen") { + print("⚠️ Note: Package directory exists but AckGen is not found.") + print("Found packages: \(foundPackages.joined(separator: ", "))") + print("Make sure AckGen is listed in your Xcode project's Swift Packages.") + } + } +} + +PathDiagnostics.main() diff --git a/test_path_calculation.sh b/test_path_calculation.sh new file mode 100755 index 0000000..2d66ac0 --- /dev/null +++ b/test_path_calculation.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# Test script to verify path calculation works correctly +# This mimics what the Run Script phase does + +echo "Testing AckGen Path Calculation" +echo "================================" +echo "" + +# Test cases with different PROJECT_TEMP_DIR structures +test_cases=( + "/Users/username/Library/Developer/Xcode/DerivedData/AppName-xyz/Build/Intermediates.noindex/AppName.build" + "/Users/dev/Library/Developer/Xcode/DerivedData/App-xyz/Build/Intermediates.noindex/App.build/Debug-iphonesimulator/App.build" + "/Users/Build/Projects/AppName-xyz/Build/Intermediates.noindex/AppName.build" +) + +expected_bases=( + "/Users/username/Library/Developer/Xcode/DerivedData/AppName-xyz" + "/Users/dev/Library/Developer/Xcode/DerivedData/App-xyz" + "/Users/Build/Projects/AppName-xyz" +) + +passed=0 +failed=0 + +for i in "${!test_cases[@]}"; do + PROJECT_TEMP_DIR="${test_cases[$i]}" + EXPECTED_BASE="${expected_bases[$i]}" + + # Calculate base directory (same logic as in README - improved version) + BASE_DIR="${PROJECT_TEMP_DIR%/Build/*}" + PACKAGE_PATH="$BASE_DIR/SourcePackages/checkouts" + + EXPECTED_PACKAGE_PATH="$EXPECTED_BASE/SourcePackages/checkouts" + + if [ "$PACKAGE_PATH" = "$EXPECTED_PACKAGE_PATH" ]; then + echo "✅ Test $((i+1)): PASSED" + echo " Input: $PROJECT_TEMP_DIR" + echo " Output: $PACKAGE_PATH" + passed=$((passed + 1)) + else + echo "❌ Test $((i+1)): FAILED" + echo " Input: $PROJECT_TEMP_DIR" + echo " Expected: $EXPECTED_PACKAGE_PATH" + echo " Got: $PACKAGE_PATH" + failed=$((failed + 1)) + fi + echo "" +done + +# Test edge case: multiple "Build" in path +echo "Testing edge case: Multiple 'Build' in path" +PROJECT_TEMP_DIR="/Users/Build/Projects/AppName-xyz/Build/Intermediates.noindex/AppName.build" +BASE_DIR="${PROJECT_TEMP_DIR%/Build/*}" +EXPECTED="/Users/Build/Projects/AppName-xyz" + +if [ "$BASE_DIR" = "$EXPECTED" ]; then + echo "✅ Edge case: PASSED (correctly uses last /Build/)" + echo " Input: $PROJECT_TEMP_DIR" + echo " Output: $BASE_DIR" + passed=$((passed + 1)) +else + echo "❌ Edge case: FAILED" + echo " Expected: $EXPECTED" + echo " Got: $BASE_DIR" + failed=$((failed + 1)) +fi +echo "" + +# Summary +echo "================================" +echo "Test Summary:" +echo " Passed: $passed" +echo " Failed: $failed" +echo "" + +if [ $failed -eq 0 ]; then + echo "✅ All tests passed!" + exit 0 +else + echo "❌ Some tests failed" + exit 1 +fi