diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ace2f22..e43184c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,6 +99,51 @@ jobs: with: name: BuildConfigDemo.xcresult path: test_output/BuildConfigDemo.xcresult + test_demo_with_package: + name: Test package based demo app + needs: generate-matrix + runs-on: macOS-14 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + env: + DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode_version }}.app/Contents/Developer + steps: + - uses: actions/checkout@v4 + - name: Cache SPM build directory + uses: actions/cache@v4 + env: + cache-name: swiftpm + with: + path: | + DemoWithPackage/.build + DemoWithPackage/Tools/.build + key: ${{ runner.os }}-${{ github.job }}-${{ env.cache-name }}-${{ hashFiles('**/Package.swift') }} + restore-keys: | + ${{ runner.os }}-${{ github.job }}-${{ env.cache-name }}- + ${{ runner.os }}-${{ github.job }}- + ${{ runner.os }}- + - name: Cache DerivedData + uses: actions/cache@v4 + env: + cache-name: derived-data + with: + path: DemoWithPackage/DerivedData + key: ${{ runner.os }}-${{ github.job }}-${{ env.cache-name }}-${{ hashFiles('**/Package.swift') }} + restore-keys: | + ${{ runner.os }}-${{ github.job }}-${{ env.cache-name }}- + ${{ runner.os }}-${{ github.job }}- + ${{ runner.os }}- + - name: Disable SwiftLint Plugin + run: sed -i -e 's/.*SwiftLint.*//g' Package.swift + - name: test + run: ./scripts/test_demo_package.sh + - name: upload test results + if: ${{ matrix.xcode_version == fromJson(needs.generate-matrix.outputs.matrix).xcode_version[0] && (success() || failure()) }} + uses: actions/upload-artifact@v4 + with: + name: BuildConfigDemoPackage.xcresult + path: test_output/BuildConfigDemoPackage.xcresult report: name: Report needs: [test, test_demo] diff --git a/.swiftlint.yml b/.swiftlint.yml index 5a4553a..6a8f37a 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,7 +1,9 @@ included: - Sources + - Plugins excluded: - Demo + - DemoWithPackage - Tests - Package.swift - Dangerfile.swift @@ -22,6 +24,7 @@ disabled_rules: - indentation_width # https://github.com/realm/SwiftLint/issues/3046 - let_var_whitespace # https://github.com/realm/SwiftLint/issues/2980 - missing_docs + - multiline_arguments_brackets - no_extension_access_modifier - no_grouping_extension - one_declaration_per_file diff --git a/DemoWithPackage/.gitignore b/DemoWithPackage/.gitignore new file mode 100644 index 0000000..b700d21 --- /dev/null +++ b/DemoWithPackage/.gitignore @@ -0,0 +1,107 @@ +# Created by https://www.gitignore.io/api/xcode,swift + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +.swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +*.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Xcode ### + +## Xcode 8 and earlier + +### Xcode Patch ### +*.xcodeproj/* +# !*.xcodeproj/project.pbxproj +# !*.xcodeproj/xcshareddata/ +# !*.xcworkspace/contents.xcworkspacedata +# /*.gcno +# **/xcshareddata/WorkspaceSettings.xcsettings + +# End of https://www.gitignore.io/api/xcode,swift + +*.lock +*.generated.swift +*.plist +!**/Settings.bundle/Root.plist +!**/com.mono0926.LicensePlist.plist +!**/com.mono0926.LicensePlist/*.plist +.swiftlint.yml diff --git a/DemoWithPackage/App/Base.lproj/LaunchScreen.storyboard b/DemoWithPackage/App/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..ce83f19 --- /dev/null +++ b/DemoWithPackage/App/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DemoWithPackage/App/Info.plist b/DemoWithPackage/App/Info.plist new file mode 100644 index 0000000..51f16b5 --- /dev/null +++ b/DemoWithPackage/App/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/DemoWithPackage/App/main.swift b/DemoWithPackage/App/main.swift new file mode 100644 index 0000000..52bda87 --- /dev/null +++ b/DemoWithPackage/App/main.swift @@ -0,0 +1,11 @@ +// +// main.swift +// App +// +// Created by 417.72KI on 2023/10/09. +// Copyright © 2023 417.72KI. All rights reserved. +// + +import BuildConfigSwiftDemo + +DemoApp.main() diff --git a/DemoWithPackage/App/project.yml b/DemoWithPackage/App/project.yml new file mode 100644 index 0000000..78218cd --- /dev/null +++ b/DemoWithPackage/App/project.yml @@ -0,0 +1,137 @@ +name: BuildConfigSwiftDemoApp +attributes: + ORGANIZATIONNAME: 417.72KI +configs: + Debug: debug + Release: release +settings: + base: + IPHONEOS_DEPLOYMENT_TARGET: 15.0 + SWIFT_VERSION: 5.0 + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: ${inherited} + ALWAYS_SEARCH_USER_PATHS: false + CLANG_ENABLE_OBJC_ARC: true + CODE_SIGN_STYLE: Manual + TARGETED_DEVICE_FAMILY: 1 + DEVELOPMENT_TEAM: "" + LIBRARY_SEARCH_PATHS: $(inherited) + CURRENT_PROJECT_VERSION: 1 + MARKETING_VERSION: 1.0.0 +options: + xcodeVersion: 10.2 + bundleIdPrefix: io.github.417-72KI +packages: + BuildConfigSwiftDemo: + path: ../ +targets: + Development: + type: application + platform: iOS + sources: + - main.swift + - Base.lproj + info: + path: Info.plist + properties: + CFBundleDevelopmentRegion: $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable: $(EXECUTABLE_NAME) + CFBundleIdentifier: $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion: '6.0' + CFBundleName: $(PRODUCT_NAME) + CFBundlePackageType: APPL + CFBundleShortVersionString: $(MARKETING_VERSION) + CFBundleVersion: $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS: true + UILaunchStoryboardName: LaunchScreen + UIRequiredDeviceCapabilities: + - arm64 + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + settings: + base: + PRODUCT_NAME: BuildConfigSwiftDemoDev + PRODUCT_BUNDLE_IDENTIFIER: io.github.417-72KI.BuildConfigSwiftDemo.dev + configs: + debug: + CODE_SIGN_IDENTITY: iPhone Developer + PROVISIONING_PROFILE_SPECIFIER: "" + release: + CODE_SIGN_IDENTITY: iPhone Distribution + PROVISIONING_PROFILE_SPECIFIER: "" + EXCLUDED_SOURCE_FILE_NAMES: Mock* + dependencies: + - package: BuildConfigSwiftDemo + Staging: + type: application + platform: iOS + sources: + - main.swift + - Base.lproj + info: + path: Info.plist + properties: + CFBundleDevelopmentRegion: $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable: $(EXECUTABLE_NAME) + CFBundleIdentifier: $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion: '6.0' + CFBundleName: $(PRODUCT_NAME) + CFBundlePackageType: APPL + CFBundleShortVersionString: $(MARKETING_VERSION) + CFBundleVersion: $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS: true + UILaunchStoryboardName: LaunchScreen + UIRequiredDeviceCapabilities: + - arm64 + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + settings: + base: + PRODUCT_NAME: BuildConfigSwiftDemoStg + PRODUCT_BUNDLE_IDENTIFIER: io.github.417-72KI.BuildConfigSwiftDemo.stg + configs: + debug: + CODE_SIGN_IDENTITY: iPhone Developer + PROVISIONING_PROFILE_SPECIFIER: "" + release: + CODE_SIGN_IDENTITY: iPhone Distribution + PROVISIONING_PROFILE_SPECIFIER: "" + EXCLUDED_SOURCE_FILE_NAMES: Mock* + dependencies: + - package: BuildConfigSwiftDemo + Production: + type: application + platform: iOS + sources: + - main.swift + - Base.lproj + info: + path: Info.plist + properties: + CFBundleDevelopmentRegion: $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable: $(EXECUTABLE_NAME) + CFBundleIdentifier: $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion: '6.0' + CFBundleName: $(PRODUCT_NAME) + CFBundlePackageType: APPL + CFBundleShortVersionString: $(MARKETING_VERSION) + CFBundleVersion: $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS: true + UILaunchStoryboardName: LaunchScreen + UIRequiredDeviceCapabilities: + - arm64 + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + settings: + base: + PRODUCT_NAME: BuildConfigSwiftDemoProduction + PRODUCT_BUNDLE_IDENTIFIER: io.github.417-72KI.BuildConfigSwiftDemo + configs: + debug: + CODE_SIGN_IDENTITY: iPhone Developer + PROVISIONING_PROFILE_SPECIFIER: "" + release: + CODE_SIGN_IDENTITY: iPhone Distribution + PROVISIONING_PROFILE_SPECIFIER: "" + EXCLUDED_SOURCE_FILE_NAMES: Mock* + dependencies: + - package: BuildConfigSwiftDemo diff --git a/DemoWithPackage/BuildConfig/api.yml b/DemoWithPackage/BuildConfig/api.yml new file mode 100644 index 0000000..fea282a --- /dev/null +++ b/DemoWithPackage/BuildConfig/api.yml @@ -0,0 +1,13 @@ +api: + version: 1 + host: localhost + endpoint: + login: + method: POST + path: /login + profile: + method: GET + path: /profile + search: + method: GET + path: /search diff --git a/DemoWithPackage/BuildConfig/config.yml b/DemoWithPackage/BuildConfig/config.yml new file mode 100644 index 0000000..0824541 --- /dev/null +++ b/DemoWithPackage/BuildConfig/config.yml @@ -0,0 +1,3 @@ +is_debug: true +environment: debug +pi: 3.14 diff --git a/DemoWithPackage/BuildConfig/debug/api.yml b/DemoWithPackage/BuildConfig/debug/api.yml new file mode 100644 index 0000000..f6b147b --- /dev/null +++ b/DemoWithPackage/BuildConfig/debug/api.yml @@ -0,0 +1,2 @@ +api: + host: api-dev.example.com diff --git a/DemoWithPackage/BuildConfig/debug/config.yml b/DemoWithPackage/BuildConfig/debug/config.yml new file mode 100644 index 0000000..3e0d81f --- /dev/null +++ b/DemoWithPackage/BuildConfig/debug/config.yml @@ -0,0 +1,2 @@ +is_debug: true +environment: debug diff --git a/DemoWithPackage/BuildConfig/integration_test/api.yml b/DemoWithPackage/BuildConfig/integration_test/api.yml new file mode 100644 index 0000000..ff729de --- /dev/null +++ b/DemoWithPackage/BuildConfig/integration_test/api.yml @@ -0,0 +1,2 @@ +api: + host: api-staging.example.com diff --git a/DemoWithPackage/BuildConfig/integration_test/config.yml b/DemoWithPackage/BuildConfig/integration_test/config.yml new file mode 100644 index 0000000..aedcbb3 --- /dev/null +++ b/DemoWithPackage/BuildConfig/integration_test/config.yml @@ -0,0 +1,2 @@ +is_debug: false +environment: staging diff --git a/DemoWithPackage/BuildConfig/release/api.yml b/DemoWithPackage/BuildConfig/release/api.yml new file mode 100644 index 0000000..44a1411 --- /dev/null +++ b/DemoWithPackage/BuildConfig/release/api.yml @@ -0,0 +1,2 @@ +api: + host: api.example.com diff --git a/DemoWithPackage/BuildConfig/release/config.yml b/DemoWithPackage/BuildConfig/release/config.yml new file mode 100644 index 0000000..64e10e7 --- /dev/null +++ b/DemoWithPackage/BuildConfig/release/config.yml @@ -0,0 +1,2 @@ +is_debug: false +environment: production diff --git a/DemoWithPackage/Package.swift b/DemoWithPackage/Package.swift new file mode 100644 index 0000000..458fee8 --- /dev/null +++ b/DemoWithPackage/Package.swift @@ -0,0 +1,37 @@ +// swift-tools-version: 5.9 + +import PackageDescription +import CompilerPluginSupport + +let package = Package( + name: "BuildConfigSwiftDemo", + platforms: [.macOS(.v14), .iOS(.v15)], + products: [ + .library( + name: "BuildConfigSwiftDemo", + targets: ["BuildConfigSwiftDemo"] + ), + ], + dependencies: [ + .package(path: "../"), + .package(url: "https://github.com/DaveWoodCom/XCGLogger", from: "7.0.1"), + .package(url: "https://github.com/417-72KI/StubNetworkKit", from: "0.3.0"), + ], + targets: [ + .target( + name: "BuildConfigSwiftDemo", + dependencies: [ + "XCGLogger", + "StubNetworkKit", + ], + plugins: [ + .plugin(name: "BuildConfigSwiftGenerate", package: "BuildConfig.swift"), + ] + ), + .testTarget( + name: "BuildConfigSwiftDemoTests", + dependencies: ["BuildConfigSwiftDemo"], + resources: [.copy("test_config.json")] + ), + ] +) diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/Assets.xcassets/AccentColor.colorset/Contents.json b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/Assets.xcassets/Contents.json b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/BuildConfigSwiftDemo.swift b/DemoWithPackage/Sources/BuildConfigSwiftDemo/BuildConfigSwiftDemo.swift new file mode 100644 index 0000000..08b22b8 --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/BuildConfigSwiftDemo.swift @@ -0,0 +1,2 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/BaseEntities.swift b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/BaseEntities.swift new file mode 100644 index 0000000..fbdb9ef --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/BaseEntities.swift @@ -0,0 +1,15 @@ +// +// BaseEntities.swift +// BuildConfigSwiftDemo +// +// Created by 417.72KI on 2023/10/05. +// Copyright © 2023 417.72KI. All rights reserved. +// + +import Foundation + +protocol Request: Encodable, Hashable { +} + +protocol Response: Decodable, Hashable { +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/LoginRequest.swift b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/LoginRequest.swift new file mode 100644 index 0000000..c5cb40a --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/LoginRequest.swift @@ -0,0 +1,14 @@ +// +// LoginRequest.swift +// BuildConfigSwiftDemo +// +// Created by 417.72KI on 2023/10/05. +// Copyright © 2023 417.72KI. All rights reserved. +// + +import Foundation + +struct LoginRequest: Request { + var id: String + var password: String +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/LoginResponse.swift b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/LoginResponse.swift new file mode 100644 index 0000000..e675750 --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/LoginResponse.swift @@ -0,0 +1,14 @@ +// +// LoginResponse.swift +// BuildConfigSwiftDemo +// +// Created by 417.72KI on 2023/10/05. +// Copyright © 2023 417.72KI. All rights reserved. +// + +import Foundation + +struct LoginResponse: Response { + var accessToken: String + var refreshToken: String +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/SearchResponse.swift b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/SearchResponse.swift new file mode 100644 index 0000000..6a3ccd7 --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Data/SearchResponse.swift @@ -0,0 +1,19 @@ +// +// SearchResponse.swift +// BuildConfigSwiftDemo +// +// Created by 417.72KI on 2023/10/05. +// Copyright © 2023 417.72KI. All rights reserved. +// + +import Foundation + +struct SearchResponse: Response { + var items: [Item] +} + +extension SearchResponse { + struct Item: Response { + var name: String + } +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/Preview Content/Preview Assets.xcassets/Contents.json b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/Repository/APIClient.swift b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Repository/APIClient.swift new file mode 100644 index 0000000..f776370 --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Repository/APIClient.swift @@ -0,0 +1,61 @@ +// +// APIClient.swift +// BuildConfigSwiftDemo +// +// Created by 417.72KI on 2023/10/05. +// Copyright © 2023 417.72KI. All rights reserved. +// + +import Foundation + +struct APIClient { + var config: BuildConfig.Api + var session: URLSession +} + +extension APIClient { + var host: URL { URL(string: "https://\(config.host)")! } + + var decoder: JSONDecoder { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + return decoder + } + + var encoder: JSONEncoder { + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + return encoder + } +} + +extension APIClient { + func endpoint(_ keyPath: KeyPath) -> E { + config.endpoint[keyPath: keyPath] + } +} + +extension APIClient { + func login(id: String, password: String) async throws -> LoginResponse { + let endpoint = endpoint(\.login) + let url = host.appendingPathComponent(endpoint.path) + var request = URLRequest(url: url) + request.httpMethod = endpoint.method + request.httpBody = try encoder.encode(LoginRequest(id: id, password: password)) + let (data, _) = try await session.data(for: request) + return try decoder.decode(LoginResponse.self, from: data) + } + + @available(iOS 16.0, *) + func search(_ text: String) async throws -> SearchResponse { + let endpoint = endpoint(\.search) + let url = host.appendingPathComponent(endpoint.path) + .appending(queryItems: [ + URLQueryItem(name: "text", value: text) + ]) + var request = URLRequest(url: url) + request.httpMethod = endpoint.method + let (data, _) = try await session.data(for: request) + return try decoder.decode(SearchResponse.self, from: data) + } +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/UI/ContentView.swift b/DemoWithPackage/Sources/BuildConfigSwiftDemo/UI/ContentView.swift new file mode 100644 index 0000000..46424a7 --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/UI/ContentView.swift @@ -0,0 +1,28 @@ +// +// ContentView.swift +// BuildConfigSwiftDemo +// +// Created by 417.72KI on 2022/04/11. +// Copyright © 2019 417.72KI. All rights reserved. +// + +import SwiftUI + +struct ContentView: View { + @Environment(\.buildConfig) var config: BuildConfig + + var body: some View { + VStack { + Text("isDebug: \(String(config.isDebug))") + Text("Environment: \(config.environment)") + Text("API version: \(config.api.version, format: .number)") + Text("PI: \(config.pi, format: .number)") + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/UI/DemoApp.swift b/DemoWithPackage/Sources/BuildConfigSwiftDemo/UI/DemoApp.swift new file mode 100644 index 0000000..75fc9e3 --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/UI/DemoApp.swift @@ -0,0 +1,23 @@ +// +// DemoApp.swift +// BuildConfigSwiftDemo +// +// Created by 417.72KI on 2022/04/11. +// Copyright © 2019 417.72KI. All rights reserved. +// + +import SwiftUI + +public struct DemoApp: App { + static var buildConfig: BuildConfig = .default + + public init() { + } + + public var body: some Scene { + WindowGroup { + ContentView() + .environment(\.buildConfig, Self.buildConfig) + } + } +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/Util/BuildConfig+Environment.swift b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Util/BuildConfig+Environment.swift new file mode 100644 index 0000000..fa36ac2 --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Util/BuildConfig+Environment.swift @@ -0,0 +1,20 @@ +// +// BuildConfig+Environment.swift +// BuildConfigSwiftDemo +// +// Created by 417.72KI on 2023/10/09. +// Copyright © 2023 417.72KI. All rights reserved. +// + +import SwiftUI + +struct BuildConfigKey: EnvironmentKey { + static let defaultValue = BuildConfig.default +} + +extension EnvironmentValues { + var buildConfig: BuildConfig { + get { self[BuildConfigKey.self] } + set { self[BuildConfigKey.self] = newValue } + } +} diff --git a/DemoWithPackage/Sources/BuildConfigSwiftDemo/Util/Logger.swift b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Util/Logger.swift new file mode 100644 index 0000000..bcc658f --- /dev/null +++ b/DemoWithPackage/Sources/BuildConfigSwiftDemo/Util/Logger.swift @@ -0,0 +1,42 @@ +// +// Logger.swift +// BuildConfigSwiftDemo +// +// Created by 417.72KI on 2019/05/09. +// Copyright © 2019 417.72KI. All rights reserved. +// + +import Foundation +import XCGLogger + +let log = XCGLogger() + +func setupLog(isDebugMode: Bool) { + if isDebugMode { + log.setup( + level: .debug, + showLogIdentifier: true, + showFunctionName: true, + showThreadName: true, + showLevel: true, + showFileNames: true, + showLineNumbers: true, + showDate: true, + writeToFile: nil, + fileLevel: nil + ) + } else { + log.setup( + level: .info, + showLogIdentifier: false, + showFunctionName: false, + showThreadName: false, + showLevel: true, + showFileNames: false, + showLineNumbers: false, + showDate: true, + writeToFile: nil, + fileLevel: nil + ) + } +} diff --git a/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/APIClientTests.swift b/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/APIClientTests.swift new file mode 100644 index 0000000..f9940bd --- /dev/null +++ b/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/APIClientTests.swift @@ -0,0 +1,64 @@ +// +// APIClientTests.swift +// BuildConfigSwiftDemoTests +// +// Created by 417.72KI on 2023/10/05. +// Copyright © 2023 417.72KI. All rights reserved. +// + +import XCTest +import StubNetworkKit + +@testable import BuildConfigSwiftDemo + +final class APIClientTests: XCTestCase { + var config = BuildConfig.fake.api + + var apiClient: APIClient! + + override func setUpWithError() throws { + apiClient = APIClient(config: config, + session: defaultStubSession) + } + + override func tearDownWithError() throws { + clearStubs() + } + + func testLogin() async throws { + stub { + Scheme.is("https") + Host.is("localhost") + Path.is("/login") + Method.isPost() + Body.isJson(["id": "john_doe", "password": "password"]) + }.responseJson(["access_token": "foo", "refresh_token": "bar"]) + + let response = try await apiClient.login(id: "john_doe", + password: "password") + XCTAssertEqual("foo", response.accessToken) + XCTAssertEqual("bar", response.refreshToken) + } + + @available(iOS 16.0, *) + func testSearch() async throws { + stub { + Scheme.is("https") + Host.is("localhost") + Path.is("/search") + Method.isGet() + QueryParams.contains(["text": "寿限無"]) + }.responseJson([ + "items": [ + ["name": "foo"], + ["name": "bar"], + ["name": "baz"], + ] + ]) + + let response = try await apiClient.search("寿限無") + XCTAssertEqual(3, response.items.count) + XCTAssertEqual("foo", response.items.first?.name) + XCTAssertEqual("baz", response.items.last?.name) + } +} diff --git a/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/BuildConfigSwiftDemoTests.swift b/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/BuildConfigSwiftDemoTests.swift new file mode 100644 index 0000000..5e2c5f0 --- /dev/null +++ b/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/BuildConfigSwiftDemoTests.swift @@ -0,0 +1,30 @@ +// +// BuildConfigSwiftDemoTests.swift +// BuildConfigSwiftDemoTests +// +// Created by 417.72KI on 2019/05/09. +// Copyright © 2019 417.72KI. All rights reserved. +// + +import XCTest +@testable import BuildConfigSwiftDemo + +final class BuildConfigTests: XCTestCase { + func testDefault() { + let buildConfig = BuildConfig.default + XCTAssertEqual(1, buildConfig.api.version) + XCTAssertEqual("api-dev.example.com", buildConfig.api.host) + XCTAssertEqual("debug", buildConfig.environment) + XCTAssertTrue(buildConfig.isDebug) + XCTAssertEqual(3.14, buildConfig.pi, accuracy: 0.01) + } + + func testLoad() throws { + let buildConfig = BuildConfig.fake + XCTAssertEqual(100, buildConfig.api.version) + XCTAssertEqual("localhost", buildConfig.api.host) + XCTAssertEqual("staging", buildConfig.environment) + XCTAssertFalse(buildConfig.isDebug) + XCTAssertEqual(3.14, buildConfig.pi, accuracy: 0.01) + } +} diff --git a/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/FakeApp.swift b/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/FakeApp.swift new file mode 100644 index 0000000..bdd4428 --- /dev/null +++ b/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/FakeApp.swift @@ -0,0 +1,25 @@ +// +// FakeApp.swift +// BuildConfigSwiftDemoTests +// +// Created by 417.72KI on 2023/10/09. +// Copyright © 2023 417.72KI. All rights reserved. +// + +import SwiftUI + +@objc(FakeApp) +final class FakeApp: NSObject, App { + override init() { super.init() } + + var body: some Scene { + WindowGroup { + Text("This is a fake app.") + .foregroundStyle(Color.white) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background { + Color.black + } + } + } +} diff --git a/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/TestHelper.swift b/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/TestHelper.swift new file mode 100644 index 0000000..86ae87e --- /dev/null +++ b/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/TestHelper.swift @@ -0,0 +1,30 @@ +// +// TestHelper.swift +// BuildConfigSwiftDemoTests +// +// Created by 417.72KI on 2023/10/05. +// Copyright © 2023 417.72KI. All rights reserved. +// + +import Foundation +@testable import BuildConfigSwiftDemo + +final class TestHelper { + private init() {} +} + +extension TestHelper { + static var bundle: Bundle { .module } +} + +extension TestHelper { + static func path(forResource name: String, ofType ext: String) -> String? { + bundle.path(forResource: name, ofType: ext) + } +} + +extension BuildConfig { + static var fake: Self { + .load(from: TestHelper.path(forResource: "test_config", ofType: "json")!) + } +} diff --git a/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/test_config.json b/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/test_config.json new file mode 100644 index 0000000..209b487 --- /dev/null +++ b/DemoWithPackage/Tests/BuildConfigSwiftDemoTests/test_config.json @@ -0,0 +1,23 @@ +{ + "api": { + "version": 100, + "host": "localhost", + "endpoint": { + "login": { + "path": "/login", + "method": "POST" + }, + "profile": { + "path": "/profile", + "method": "GET" + }, + "search": { + "path": "/search", + "method": "GET" + } + } + }, + "environment": "staging", + "is_debug": false, + "pi": 3.14 +} diff --git a/Makefile b/Makefile index 0f93b6d..8748862 100644 --- a/Makefile +++ b/Makefile @@ -30,11 +30,14 @@ format: demo_app_init: scripts/copy-lint-config.sh && \ - cd Demo && \ - xcrun --sdk macosx swift run -c release --package-path Tools xcodegen + xcrun --sdk macosx swift run -c release --package-path Tools xcodegen --spec Demo/project.yml && \ + xcrun --sdk macosx swift run -c release --package-path Tools xcodegen --spec DemoWithPackage/App/project.yml demo: demo_app_init xed Demo/BuildConfigSwiftDemo.xcodeproj +demo_package: demo_app_init + xed DemoWithPackage/App/BuildConfigSwiftDemoApp.xcodeproj + demo_test: ./scripts/test_demo.sh diff --git a/Plugins/BuildConfigSwiftGenerate/BuildConfigSwiftGenerate.swift b/Plugins/BuildConfigSwiftGenerate/BuildConfigSwiftGenerate.swift index 704e194..7e9de3f 100644 --- a/Plugins/BuildConfigSwiftGenerate/BuildConfigSwiftGenerate.swift +++ b/Plugins/BuildConfigSwiftGenerate/BuildConfigSwiftGenerate.swift @@ -7,8 +7,38 @@ struct BuildConfigSwiftGenerate: BuildToolPlugin { context: PackagePlugin.PluginContext, target: PackagePlugin.Target ) async throws -> [PackagePlugin.Command] { - Diagnostics.warning("Command only supported as Xcode build tool plugin") - return [] + guard let target = target as? SourceModuleTarget else { return [] } + + guard let buildConfigDirectoryPath = try find( + resourceDirectoryName, + in: target.directory + .removingLastComponent() + .removingLastComponent() + ) else { + throw Error.directoryNotFound(in: target.directory) + } + let generatedFileContainerPath = context.pluginWorkDirectory + .appending(subpath: target.name) + .appending(subpath: resourceDirectoryName) + + try FileManager.default.createDirectory( + atPath: generatedFileContainerPath.string, + withIntermediateDirectories: true + ) + + let description = "\(target.kind) \(target.name)" + return [ + .buildCommand( + displayName: "BuildConfig.swift generate for \(description)", + executable: try context.tool(named: "buildconfigswift").path, + arguments: [ + "-o", + generatedFileContainerPath.string, + buildConfigDirectoryPath.string, + ], + outputFiles: [generatedFileContainerPath.appending(generatedFileName)] + ), + ] } } @@ -46,10 +76,10 @@ extension BuildConfigSwiftGenerate: XcodeBuildToolPlugin { arguments: [ "-o", generatedFileContainerPath.string, - buildConfigDirectoryPath.string + buildConfigDirectoryPath.string, ], outputFiles: [generatedFileContainerPath.appending(generatedFileName)] - ) + ), ] } } diff --git a/Demo/Tools/.gitignore b/Tools/.gitignore similarity index 100% rename from Demo/Tools/.gitignore rename to Tools/.gitignore diff --git a/Demo/Tools/Package.swift b/Tools/Package.swift similarity index 100% rename from Demo/Tools/Package.swift rename to Tools/Package.swift diff --git a/Demo/Tools/README.md b/Tools/README.md similarity index 100% rename from Demo/Tools/README.md rename to Tools/README.md diff --git a/scripts/copy-lint-config.sh b/scripts/copy-lint-config.sh index 3050016..e1e1958 100755 --- a/scripts/copy-lint-config.sh +++ b/scripts/copy-lint-config.sh @@ -25,3 +25,4 @@ cat "${SRCROOT}/.swiftlint.yml" \ | yq '. | .included = ["BuildConfigSwiftDemo"] | .excluded = ["Tools", "Package.swift"]' \ > "${SRCROOT}/Demo/.swiftlint.yml" +cp "${SRCROOT}/Demo/.swiftlint.yml" "${SRCROOT}/DemoWithPackage/.swiftlint.yml" diff --git a/scripts/test_demo.sh b/scripts/test_demo.sh index b843e4e..0d3b1a2 100755 --- a/scripts/test_demo.sh +++ b/scripts/test_demo.sh @@ -11,7 +11,7 @@ fi if [[ "$(find Demo -depth 1 -name '*.xcodeproj')" == '' ]]; then echo '\e[33m`xcodeproj` in `Demo` not found, generating...\e[0m' $(dirname $0)/copy-lint-config.sh - xcrun --sdk macosx swift run --package-path Demo/Tools xcodegen --spec Demo/project.yml + xcrun --sdk macosx swift run --package-path Tools xcodegen --spec Demo/project.yml fi PROJECT_PATH=$(find Demo -depth 1 -name '*.xcodeproj' | head -n 1) diff --git a/scripts/test_demo_package.sh b/scripts/test_demo_package.sh new file mode 100755 index 0000000..c231161 --- /dev/null +++ b/scripts/test_demo_package.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +set -o pipefail + +XCRESULT_PATH="$(git rev-parse --show-toplevel)/test_output/BuildConfigDemoPackage.xcresult" + +if [[ -e "$XCRESULT_PATH" ]]; then + rm -rf "$XCRESULT_PATH" +fi + +$(dirname $0)/copy-lint-config.sh + +cd DemoWithPackage +SCHEME="$(xcrun --sdk macosx xcodebuild -list -json | jq -rc '.workspace.schemes[] | select(. | contains("Demo"))')" + +echo "\e[32mScheme: $SCHEME\e[0m" + +xcrun --sdk macosx xcodebuild \ + -skipPackagePluginValidation \ + -enableCodeCoverage YES \ + -scheme "${SCHEME}" \ + -destination 'platform=iOS Simulator,name=iPhone 14 Pro' \ + -derivedDataPath 'DerivedData' \ + -clonedSourcePackagesDirPath '.build/SourcePackages' \ + -resultBundlePath "$XCRESULT_PATH" \ + clean test | xcpretty