diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..6bd39ba7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,22 @@ +--- +name: Bug report +about: Report an issue with this project +title: '' +labels: bug +assignees: '' + +--- + +**Steps to reproduce** +1. +2. +3. + +**Sample code or sample project upload** +Please illustrate the steps to reproduce above with sample code or a sample project and include it here. + +**Expected behavior** +What did you exect to happen? + +**Actual behavior** +What actually happened, and how did that differ from the expected behavior? diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..eeeb5036 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Goals** +What do you want this feature to accomplish? What are the effects of your change? + +**Non-Goals** +What aren’t you trying to accomplish? What are the boundaries of the proposed work? + +**Investigations** +What other solutions (if any) did you investigate? Why didn’t you choose them? + +**Design** +What are you proposing? What are the details of your chosen design? Include an API overview, technical details, and (potentially) some example headers, along with anything else you think will be useful. This is where you sell the design to yourself and project maintainers. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..c0dc0d00 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +jobs: + spm-15: + name: Build Xcode 15 + runs-on: macos-13 + strategy: + matrix: + platforms: [ + 'iOS_17,watchOS_10', + 'macOS_14,tvOS_17', + ] + fail-fast: false + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Bundle Install + run: bundle install + - name: Select Xcode Version + run: sudo xcode-select --switch /Applications/Xcode_15.0.1.app/Contents/Developer + - name: Build and Test Framework + run: Scripts/build.swift ${{ matrix.platforms }} + - name: Prepare Coverage Reports + run: ./Scripts/prepare-coverage-reports.sh + - name: Upload Coverage Reports + if: success() + uses: codecov/codecov-action@v3 + spm-15-swift: + name: Swift Build Xcode 15 + runs-on: macos-13 + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Bundle Install + run: bundle install + - name: Select Xcode Version + run: sudo xcode-select --switch /Applications/Xcode_15.0.1.app/Contents/Developer + - name: Build and Test Framework + run: xcrun swift test -c release -Xswiftc -enable-testing diff --git a/Package.swift b/Package.swift index 983173f9..0e41ac08 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,13 @@ import CompilerPluginSupport let package = Package( name: "SafeDI", - platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)], + platforms: [ + .macOS(.v10_15), + .iOS(.v13), + .tvOS(.v13), + .watchOS(.v6), + .macCatalyst(.v13) + ], products: [ .library( name: "SafeDI", diff --git a/Scripts/build.swift b/Scripts/build.swift new file mode 100755 index 00000000..5912fcfc --- /dev/null +++ b/Scripts/build.swift @@ -0,0 +1,201 @@ +#!/usr/bin/env swift + +import Foundation + +// Usage: build.swift platforms + +func execute(commandPath: String, arguments: [String]) throws { + let task = Process() + task.launchPath = commandPath + task.arguments = arguments + print("Launching command: \(commandPath) \(arguments.joined(separator: " "))") + task.launch() + task.waitUntilExit() + guard task.terminationStatus == 0 else { + throw TaskError.code(task.terminationStatus) + } +} + +enum TaskError: Error { + case code(Int32) +} + +enum Platform: String, CaseIterable, CustomStringConvertible { + case iOS_13 + case iOS_14 + case iOS_15 + case iOS_16 + case iOS_17 + case tvOS_13 + case tvOS_14 + case tvOS_15 + case tvOS_16 + case tvOS_17 + case macOS_10_15 + case macOS_11 + case macOS_12 + case macOS_13 + case macOS_14 + case watchOS_6 + case watchOS_7 + case watchOS_8 + case watchOS_9 + case watchOS_10 + + var destination: String { + switch self { + case .iOS_13: + return "platform=iOS Simulator,OS=13.7,name=iPad Pro (12.9-inch) (4th generation)" + case .iOS_14: + return "platform=iOS Simulator,OS=14.4,name=iPad Pro (12.9-inch) (4th generation)" + case .iOS_15: + return "platform=iOS Simulator,OS=15.5,name=iPad Pro (12.9-inch) (5th generation)" + case .iOS_16: + return "platform=iOS Simulator,OS=16.4,name=iPad Pro (12.9-inch) (6th generation)" + case .iOS_17: + return "platform=iOS Simulator,OS=17.0,name=iPad Pro (12.9-inch) (6th generation)" + + case .tvOS_13: + return "platform=tvOS Simulator,OS=13.4,name=Apple TV" + case .tvOS_14: + return "platform=tvOS Simulator,OS=14.3,name=Apple TV" + case .tvOS_15: + return "platform=tvOS Simulator,OS=15.4,name=Apple TV" + case .tvOS_16: + return "platform=tvOS Simulator,OS=16.4,name=Apple TV" + case .tvOS_17: + return "platform=tvOS Simulator,OS=17.0,name=Apple TV" + + case .macOS_10_15, + .macOS_11, + .macOS_12, + .macOS_13, + .macOS_14: + return "platform=OS X" + + case .watchOS_6: + return "OS=6.2.1,name=Apple Watch Series 4 - 44mm" + case .watchOS_7: + return "OS=7.2,name=Apple Watch Series 6 - 44mm" + case .watchOS_8: + return "OS=8.5,name=Apple Watch Series 6 - 44mm" + case .watchOS_9: + return "OS=9.4,name=Apple Watch Series 7 (45mm)" + case .watchOS_10: + return "OS=10.0,name=Apple Watch Series 7 (45mm)" + } + } + + var sdk: String { + switch self { + case .iOS_13, + .iOS_14, + .iOS_15, + .iOS_16, + .iOS_17: + return "iphonesimulator" + + case .tvOS_13, + .tvOS_14, + .tvOS_15, + .tvOS_16, + .tvOS_17: + return "appletvsimulator" + + case .macOS_10_15: + return "macosx10.15" + case .macOS_11: + return "macosx11.1" + case .macOS_12: + return "macosx12.3" + case .macOS_13: + return "macosx13.3" + case .macOS_14: + return "macosx14.0" + + case .watchOS_6, + .watchOS_7, + .watchOS_8, + .watchOS_9, + .watchOS_10: + return "watchsimulator" + } + } + + var shouldTest: Bool { + switch self { + case .iOS_13, + .iOS_14, + .iOS_15, + .iOS_16, + .iOS_17, + .tvOS_13, + .tvOS_14, + .tvOS_15, + .tvOS_16, + .tvOS_17, + .macOS_10_15, + .macOS_11, + .macOS_12, + .macOS_13, + .macOS_14: + return true + + case .watchOS_6, + .watchOS_7, + .watchOS_8, + .watchOS_9, + .watchOS_10: + // watchOS does not support unit testing (yet?). + return false + } + } + + var derivedDataPath: String { + ".build/derivedData/" + description + } + + var description: String { + rawValue + } +} + +guard CommandLine.arguments.count > 1 else { + print("Usage: build.swift platforms") + throw TaskError.code(1) +} + +let rawPlatforms = CommandLine.arguments[1].components(separatedBy: ",") + +var isFirstRun = true +for rawPlatform in rawPlatforms { + guard let platform = Platform(rawValue: rawPlatform) else { + print("Received unknown platform type \(rawPlatform)") + print("Possible platform types are: \(Platform.allCases)") + throw TaskError.code(1) + } + + var xcodeBuildArguments = [ + "-scheme", "SafeDI", + "-sdk", platform.sdk, + "-derivedDataPath", platform.derivedDataPath, + "-PBXBuildsContinueAfterErrors=0", + "OTHER_SWIFT_FLAGS=-warnings-as-errors", + ] + + if !platform.destination.isEmpty { + xcodeBuildArguments.append("-destination") + xcodeBuildArguments.append(platform.destination) + } + if platform.shouldTest { + xcodeBuildArguments.append("-enableCodeCoverage") + xcodeBuildArguments.append("YES") + } + xcodeBuildArguments.append("build") + if platform.shouldTest { + xcodeBuildArguments.append("test") + } + + try execute(commandPath: "/usr/bin/xcodebuild", arguments: xcodeBuildArguments) + isFirstRun = false +} diff --git a/Scripts/prepare-coverage-reports.sh b/Scripts/prepare-coverage-reports.sh new file mode 100755 index 00000000..ef0d2b90 --- /dev/null +++ b/Scripts/prepare-coverage-reports.sh @@ -0,0 +1,34 @@ +#!/bin/zsh -l +set -e + +function exportlcov() { + build_type=$1 + executable_name=$2 + + executable=$(find "${directory}" -type f -name $executable_name) + profile=$(find "${directory}" -type f -name 'Coverage.profdata') + output_file_name="$executable_name.lcov" + + can_proceed=true + if [[ $build_type == watchOS* ]]; then + echo "\tAborting creation of $output_file_name – watchOS not supported." + elif [[ -z $profile ]]; then + echo "\tAborting creation of $output_file_name – no profile found." + elif [[ -z $executable ]]; then + echo "\tAborting creation of $output_file_name – no executable found." + else + output_dir=".build/artifacts/$build_type" + mkdir -p $output_dir + + output_file="$output_dir/$output_file_name" + echo "\tExporting $output_file" + xcrun llvm-cov export -format="lcov" $executable -instr-profile $profile > $output_file + fi +} + +for directory in $(git rev-parse --show-toplevel)/.build/derivedData/*/; do + build_type=$(basename $directory) + echo "Finding coverage information for $build_type" + + exportlcov $build_type 'SafeDITests' +done