Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25ff5a790b | |||
| f446f6061d | |||
| b46d1e2ca1 | |||
| 88d0666da9 | |||
| 08bf5c21bf | |||
| be97a6a247 | |||
| dff71f8070 | |||
| cda5fc1188 | |||
| 15586755d2 | |||
| e978eb182c | |||
| 7e98cebdd9 | |||
| fbc2982aa3 | |||
| cbcc028cad | |||
| 2acf97eca1 | |||
| 40803cf747 | |||
| 97496ed7b8 | |||
| 45222f8e33 | |||
| 91b5b5e590 | |||
| f2a7880c24 | |||
| 00cb8cc23d | |||
| a3cd6bea07 | |||
| 86c762b070 | |||
| bd21156695 | |||
| 3a82ad91b2 | |||
| 5a5bf35c4a | |||
| 1f766ad4a4 | |||
| 3c3cd84d81 | |||
| 7728733aef | |||
| 50580bf9fd | |||
| ceaff318d6 | |||
| 4cc932a592 | |||
| 75bef0baf6 | |||
| 03671e71b7 | |||
| 91505a59d2 | |||
| 59c1d999b1 | |||
| ba58c1c21e | |||
| 96ce18bc31 | |||
| 30e49ef7bc | |||
| d46f09c6dd | |||
| 2fdf6c39e2 | |||
| 064b22a2d1 | |||
| ddeffe75d6 | |||
| 181997e3d2 | |||
| 1d6ac2c171 | |||
| 3dfd6e296f | |||
| c39b286222 | |||
| a55a4547ac | |||
| 51c2007c5b | |||
| fc092fc4e3 | |||
| 3f9596f6e6 | |||
| 15f4ee7eab | |||
| a6325db0a5 | |||
| cfa4fbd070 | |||
| c573ced4f4 | |||
| 0c2afc15fc | |||
| 586e8df56e | |||
| ef3a88c6ec | |||
| 95f95be29e | |||
| 4d12800096 | |||
| fbb456b0e0 | |||
| a321ea3362 | |||
| 963e6858ee | |||
| f9999a402f | |||
| dbfe1dd8d4 | |||
| def12fab6f | |||
| 6fee69f081 | |||
| b74bafa5d7 | |||
| 33de361317 | |||
| 0aeb5aee36 | |||
| 8682731f30 | |||
| 62b637bdaa | |||
| 0bca5e5bc4 | |||
| eb0d4b17b7 | |||
| d9ea5bfd49 | |||
| ee8bf69814 | |||
| 2c0f78056f | |||
| b6b17bdc8a | |||
| a387359930 | |||
| 456233b90e | |||
| d6f6e2e9bc | |||
| a35bd0b394 | |||
| def131f2a0 | |||
| c052ed8ed6 | |||
| d4f9486b92 | |||
| 8241914543 | |||
| b5ff16484f | |||
| 2b9dde9aec | |||
| 136e7a99ff | |||
| c626d51f97 | |||
| e8ddc9297d | |||
| 3b614c6172 | |||
| 87a214104e | |||
| 599e1e229d | |||
| 423da7cc4a | |||
| 4b082e9dd2 | |||
| 36803d6b5d | |||
| 5809bc963c | |||
| cb6626cfbc | |||
| 3e18711e09 | |||
| ccda424791 | |||
| 90d784cc8d | |||
| 6715824195 | |||
| c4e08d4288 | |||
| a2067f8dfe | |||
| 253e9597bd | |||
| 4a93f944fd | |||
| f839b4064b | |||
| e71837b8b2 | |||
| b45792646b | |||
| 1966562eef | |||
| 883b207c5b | |||
| ca137d0ce4 | |||
| b9a633c86f | |||
| 55a87eb4e9 | |||
| e6b56024b9 | |||
| 8c34a31110 | |||
| f7c32d6e80 | |||
| b3a16ae5d0 | |||
| d013fe4c81 | |||
| 4aefee078e | |||
| 9363e68d51 | |||
| 4af8156da5 |
@@ -9,11 +9,13 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
- name: SwiftLint
|
||||
uses: norio-nomura/action-swiftlint@3.1.0
|
||||
with:
|
||||
args: --strict
|
||||
|
||||
macOS:
|
||||
runs-on: macOS-latest
|
||||
runs-on: macos-12
|
||||
env:
|
||||
XCODE_VERSION: ${{ '13.1' }}
|
||||
XCODE_VERSION: ${{ '13.3.1' }}
|
||||
steps:
|
||||
- name: Select Xcode
|
||||
run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
runs-on: macos-12
|
||||
env:
|
||||
XCODE_VERSION: ${{ '13.3.1' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Select Xcode
|
||||
run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app"
|
||||
- name: "Generate documentation"
|
||||
run: "swift package --allow-writing-to-directory ./docs generate-documentation --target XCRemoteCache --disable-indexing --transform-for-static-hosting --output-path ./docs --hosting-base-path XCRemoteCache/"
|
||||
- name: Deploy GH-pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./docs
|
||||
keep_files: false
|
||||
@@ -6,9 +6,9 @@ on:
|
||||
jobs:
|
||||
macOS:
|
||||
name: Add macOS binaries to release
|
||||
runs-on: macOS-latest
|
||||
runs-on: macos-12
|
||||
env:
|
||||
XCODE_VERSION: ${{ '13.1' }}
|
||||
XCODE_VERSION: ${{ '13.3.1' }}
|
||||
steps:
|
||||
- name: Select Xcode
|
||||
run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app"
|
||||
|
||||
+22
-2
@@ -5,7 +5,6 @@ disabled_rules:
|
||||
- superfluous_disable_command # Disabled since we disable some rules pre-emptively to avoid issues in the future
|
||||
- todo # Temporarily disabled. We have too many right now hiding real issues :(
|
||||
- nesting # Does not make sense anymore since Swift 4 uses nested `CodingKeys` enums for example
|
||||
- trailing_dot_in_comments # Triggers warnings for generated file headers
|
||||
|
||||
opt_in_rules:
|
||||
- anyobject_protocol
|
||||
@@ -64,6 +63,8 @@ excluded:
|
||||
- docs/
|
||||
- fastlane/
|
||||
- DerivedData/
|
||||
- e2eTests/XCRemoteCacheSample/Pods
|
||||
- e2eTests/StandaloneSampleApp
|
||||
|
||||
attributes:
|
||||
always_on_same_line:
|
||||
@@ -88,6 +89,25 @@ file_header:
|
||||
\/\/ Created by .*? on .*\.
|
||||
\/\/ Copyright © \d{4} .*\. All rights reserved\.
|
||||
\/\/
|
||||
required_pattern: |
|
||||
\/\/ Copyright \(c\) \d{4} Spotify AB\.
|
||||
\/\/
|
||||
\/\/ Licensed to the Apache Software Foundation \(ASF\) under one
|
||||
\/\/ or more contributor license agreements\. See the NOTICE file
|
||||
\/\/ distributed with this work for additional information
|
||||
\/\/ regarding copyright ownership\. The ASF licenses this file
|
||||
\/\/ to you under the Apache License, Version 2.0 \(the
|
||||
\/\/ "License"\); you may not use this file except in compliance
|
||||
\/\/ with the License\. You may obtain a copy of the License at
|
||||
\/\/
|
||||
\/\/ http:\/\/www.apache.org\/licenses\/LICENSE-2\.0
|
||||
\/\/
|
||||
\/\/ Unless required by applicable law or agreed to in writing,
|
||||
\/\/ software distributed under the License is distributed on an
|
||||
\/\/ \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
\/\/ KIND, either express or implied\. See the License for the
|
||||
\/\/ specific language governing permissions and limitations
|
||||
\/\/ under the License\.
|
||||
force_cast: warning
|
||||
force_try: warning
|
||||
implicit_getter: warning
|
||||
@@ -124,6 +144,6 @@ custom_rules:
|
||||
severity: warning
|
||||
trailing_dot_in_comments:
|
||||
name: "Trailing dot in comments"
|
||||
regex: '^[ ]*///?[^\n]*\.\n'
|
||||
regex: '^(?!\/\/\ Copyright\ \(c\)\ \d{4}\ Spotify AB\.|\/\/\ under\ the\ License\.)[ ]*///?[^\n]*\.\n'
|
||||
message: "There shouldn't be trailing dot in comments"
|
||||
severity: warning
|
||||
|
||||
+19
-10
@@ -3,16 +3,16 @@
|
||||
"pins": [
|
||||
{
|
||||
"package": "AEXML",
|
||||
"repositoryURL": "https://github.com/tadija/AEXML",
|
||||
"repositoryURL": "https://github.com/tadija/AEXML.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "8623e73b193386909566a9ca20203e33a09af142",
|
||||
"version": "4.5.0"
|
||||
"revision": "38f7d00b23ecd891e1ee656fa6aeebd6ba04ecc3",
|
||||
"version": "4.6.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "PathKit",
|
||||
"repositoryURL": "https://github.com/kylef/PathKit",
|
||||
"repositoryURL": "https://github.com/kylef/PathKit.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "73f8e9dca9b7a3078cb79128217dc8f2e585a511",
|
||||
@@ -37,13 +37,22 @@
|
||||
"version": "0.0.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftDocCPlugin",
|
||||
"repositoryURL": "https://github.com/apple/swift-docc-plugin",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "XcodeProj",
|
||||
"repositoryURL": "https://github.com/tuist/XcodeProj.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "0b18c3e7a10c241323397a80cb445051f4494971",
|
||||
"version": "8.0.0"
|
||||
"revision": "c75c3acc25460195cfd203a04dde165395bf00e0",
|
||||
"version": "8.7.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -51,8 +60,8 @@
|
||||
"repositoryURL": "https://github.com/jpsim/Yams.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "53741ba55ecca5c7149d8c9f810913ec80845c69",
|
||||
"version": "3.0.0"
|
||||
"revision": "00c403debcd0a007b854bb35e598466207a2d58c",
|
||||
"version": "5.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -60,8 +69,8 @@
|
||||
"repositoryURL": "https://github.com/marmelroy/Zip.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "80b1c3005ee25b4c7ce46c4029ac3347e8d5e37e",
|
||||
"version": "2.0.0"
|
||||
"revision": "67fa55813b9e7b3b9acee9c0ae501def28746d76",
|
||||
"version": "2.1.2"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+10
-4
@@ -1,4 +1,5 @@
|
||||
// swift-tools-version:5.3
|
||||
// swiftlint:disable:previous file_header
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package
|
||||
|
||||
import PackageDescription
|
||||
@@ -12,10 +13,11 @@ let package = Package(
|
||||
.executable(name: "xcprebuild", targets: ["xcprebuild"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/marmelroy/Zip.git", from: "2.0.0"),
|
||||
.package(url: "https://github.com/jpsim/Yams.git", from: "3.0.0"),
|
||||
.package(url: "https://github.com/marmelroy/Zip.git", from: "2.1.2"),
|
||||
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.0"),
|
||||
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1"),
|
||||
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.0.0"),
|
||||
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.7.1"),
|
||||
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
@@ -49,10 +51,14 @@ let package = Package(
|
||||
name: "xcld",
|
||||
dependencies: ["XCRemoteCache"]
|
||||
),
|
||||
.target(
|
||||
name: "xcldplusplus",
|
||||
dependencies: ["XCRemoteCache"]
|
||||
),
|
||||
.target(
|
||||
// Wrapper target that builds all binaries but does nothing in runtime
|
||||
name: "Aggregator",
|
||||
dependencies: ["xcprebuild", "xcswiftc", "xclibtool", "xcpostbuild", "xcprepare", "xcld"]
|
||||
dependencies: ["xcprebuild", "xcswiftc", "xclibtool", "xcpostbuild", "xcprepare", "xcld", "xcldplusplus"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "XCRemoteCacheTests",
|
||||
|
||||
@@ -8,6 +8,7 @@ _XCRemoteCache is a remote cache tool for Xcode projects. It reuses target artif
|
||||
[](https://github.com/spotify/XCRemoteCache/workflows/CI/badge.svg)
|
||||
[](LICENSE)
|
||||
[](https://slackin.spotify.com)
|
||||
[](https://github.com/spotify/XCRemoteCache/workflows/Docs/badge.svg)
|
||||
|
||||
- [How and Why?](#how-and-why)
|
||||
* [Accurate target input files](#accurate-target-input-files)
|
||||
@@ -195,7 +196,9 @@ Configure Xcode targets that **should use** XCRemoteCache:
|
||||
* `SWIFT_EXEC` - location of `xcprepare` (e.g. `xcremotecache/xcswiftc`)
|
||||
* `LIBTOOL` - location of `xclibtool` (e.g. `xcremotecache/xclibtool`)
|
||||
* `LD` - location of `xcld` (e.g. `xcremotecache/xcld`)
|
||||
* `LDPLUSPLUS` - location of `xcldplusplus` (e.g. `xcremotecache/xcldplusplus`)
|
||||
* `XCRC_PLATFORM_PREFERRED_ARCH` - `$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)`
|
||||
* `SWIFT_USE_INTEGRATED_DRIVER` - `NO` (required in Xcode 14.0+)
|
||||
|
||||
<details>
|
||||
<summary>Screenshot</summary>
|
||||
@@ -264,7 +267,7 @@ $ xcremotecache/xcprepare mark --configuration Debug --platform iphonesimulator
|
||||
|
||||
That command creates an empty file on a remote server which informs that for given sha, configuration, platform, Xcode versions etc. all artifacts are available.
|
||||
|
||||
_Note that for the `producer` mode, the prebuild build phase and `xccc`, `xcld`, `xclibtool` wrappers become no-op, so it is recommended to not add them for the `producer` mode._
|
||||
_Note that for the `producer` mode, the prebuild build phase and `xccc`, `xcld`, `xcldplusplus`, `xclibtool` wrappers become no-op, so it is recommended to not add them for the `producer` mode._
|
||||
|
||||
## A full list of configuration parameters:
|
||||
|
||||
@@ -288,13 +291,14 @@ _Note that for the `producer` mode, the prebuild build phase and `xccc`, `xcld`,
|
||||
| `cache_commit_history` | Number of historical git commits to look for cache artifacts | `10` | ⬜️ |
|
||||
| `source_root` | Source root of the Xcode project | `""` | ⬜️ |
|
||||
| `fingerprint_override_extension` | Fingerprint override extension (sample override `Module.swiftmodule/x86_64.swiftmodule.md5`) | `md5` | ⬜️ |
|
||||
| `extra_configuration_file` | Configuration file that overrides project configuration | `user.rcinfo` | ⬜️ |
|
||||
| `extra_configuration_file` | Configuration file that overrides project configuration (this property can be overriden multiple times in different files to chain extra configuration files) | `user.rcinfo` | ⬜️ |
|
||||
| `publishing_sha` | Custom commit sha to publish artifact (producer only) | `nil` | ⬜️ |
|
||||
| `artifact_maximum_age` | Maximum age in days HTTP response should be locally cached before being evicted | `30` | ⬜️ |
|
||||
| `custom_fingerprint_envs` | Extra ENV keys that should be convoluted into the environment fingerprint | `[]` | ⬜️ |
|
||||
| `stats_dir` | Directory where all XCRemoteCache statistics (e.g. counters) are stored | `~/.xccache` | ⬜️ |
|
||||
| `download_retries` | Number of retries for download requests | `0` | ⬜️ |
|
||||
| `upload_retries` | Number of retries for upload requests | `3` | ⬜️ |
|
||||
| `retry_delay` | Delay between retries in seconds | `10` | ⬜️ |
|
||||
| `request_custom_headers` | Dictionary of extra HTTP headers for all remote server requests | `[]` | ⬜️ |
|
||||
| `thin_target_mock_filename` | Filename (without an extension) of the compilation input file that is used as a fake compilation for the forced-cached target (aka thin target) | `standin` | ⬜️ |
|
||||
| `focused_targets` | A list of all targets that are not thinned. If empty, all targets are meant to be non-thin | `[]` | ⬜️ |
|
||||
@@ -308,11 +312,14 @@ _Note that for the `producer` mode, the prebuild build phase and `xccc`, `xcld`,
|
||||
| `prettify_meta_files` | A Boolean value that opts-in pretty JSON formatting for meta files | `false` | ⬜️ |
|
||||
| `aws_secret_key` | Secret key for AWS V4 Signature Authorization. If this is set to a non-empty String - an Authentication Header will be added based on this and the other `aws_*` parameters.| `""` | ⬜️ |
|
||||
| `aws_access_key` | Access key for AWS V4 Signature Authorization. | `""` | ⬜️ |
|
||||
| `aws_security_token` | Temporary security token provided by the AWS Security Token Service. | `nil` | ⬜️ |
|
||||
| `aws_region` | Region for AWS V4 Signature Authorization. E.g. `eu`. | `""` | ⬜️ |
|
||||
| `aws_service` | Service for AWS V4 Signature Authorization. E.g. `storage`. | `""` | ⬜️ |
|
||||
| `out_of_band_mappings` | A dictionary of files path remapping that should be applied to make it absolute path agnostic on a list of dependencies. Useful if a project refers files out of repo root, either compilation files or precompiled dependencies. Keys represent generic replacement and values are substrings that should be replaced. Example: for mapping `["COOL_LIBRARY": "/CoolLibrary"]` `/CoolLibrary/main.swift`will be represented as `$(COOL_LIBRARY)/main.swift`). Warning: remapping order is not-deterministic so avoid remappings with multiple matchings. | `[:]` | ⬜️ |
|
||||
| `disable_certificate_verification` | A Boolean value that opts-in SSL certificate validation is disabled | `false` | ⬜️ |
|
||||
| `disable_vfs_overlay` | A feature flag to disable virtual file system overlay support (temporary) | `false` | ⬜️ |
|
||||
| `custom_rewrite_envs` | A list of extra ENVs that should be used as placeholders in the dependency list. ENV rewrite process is optimistic - does nothing if an ENV is not defined in the pre/postbuild process. | `[]` | ⬜️ |
|
||||
| `irrelevant_dependencies_paths` | Regexes of files that should not be included in a list of dependencies. Warning! Add entries here with caution - excluding dependencies that are relevant might lead to a target overcaching. The regex can match either partially or fully the filepath, e.g. `\\.modulemap$` will exclude all `.modulemap` files. | `[]` | ⬜️ |
|
||||
|
||||
## Backend cache server
|
||||
|
||||
@@ -350,6 +357,8 @@ XCRemoteCache supports Amazon S3 and Google Cloud Storage buckets to be used as
|
||||
|
||||
To set it up use the configuration parameters `aws_secret_key`, `aws_access_key`, `aws_region`, and `aws_service` in the `.rcinfo` file. Specify the URL to the bucket in cache-addresses field in the same file.
|
||||
|
||||
XCRemoteCache also supports [AWS Temporary Access Keys](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#temporary-access-keys). Use additional `aws_security_token` parameter combined with `aws_secret_key`, `aws_access_key` to set it up. [This page](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html) describes how to receive a security token.
|
||||
|
||||
Example
|
||||
```yaml
|
||||
...
|
||||
@@ -403,12 +412,14 @@ Note: This setup is not recommended and may not be supported in future XCRemoteC
|
||||
* Recommended: multi-targets Xcode project
|
||||
* Recommended: do not use fast-forward PR strategy (use merge or squash instead)
|
||||
* Recommended: avoid `DWARF with dSYM File` "Debug Information Format" build setting. Use `DWARF` instead
|
||||
* Recommended: avoid having a symbolic link in the source root (e.g. placing a project in `/tmp`)
|
||||
|
||||
## Limitations
|
||||
|
||||
* Swift Package Manager (SPM) dependencies are not supported. _Because SPM does not allow customizing Build Settings, XCRemoteCache cannot specify `clang` and `swiftc` wrappers that control if the local compilation should be skipped (cache hit) or not (cache miss)_
|
||||
* Filenames with `_vers.c` suffix are reserved and cannot be used as a source file
|
||||
* All compilation files should be referenced via the git repo root. Referencing `/AbsolutePath/someOther.swift` or `../../someOther.swift` that resolve to the location outside of the git repo root is prohibited.
|
||||
* The new Swift driver (introduced by default in Xcode 14.0) is not supported and has to be disabled when using XCRemoteCache
|
||||
|
||||
## FAQ
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ DERIVED_DATA_DIR = File.join('.build').freeze
|
||||
RELEASES_ROOT_DIR = File.join('releases').freeze
|
||||
|
||||
EXECUTABLE_NAME = 'XCRemoteCache'
|
||||
EXECUTABLE_NAMES = ['xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc', 'xcld']
|
||||
EXECUTABLE_NAMES = ['xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc', 'xcld', 'xcldplusplus']
|
||||
PROJECT_NAME = 'XCRemoteCache'
|
||||
|
||||
SWIFTLINT_ENABLED = true
|
||||
|
||||
@@ -52,7 +52,7 @@ class XCLibtoolCreateUniversalBinary: XCLibtoolLogic {
|
||||
let config: XCRemoteCacheConfig
|
||||
do {
|
||||
let srcRoot: URL = URL(fileURLWithPath: fileManager.currentDirectoryPath)
|
||||
config = try XCRemoteCacheConfigReader(srcRootPath: srcRoot.path, fileManager: fileManager)
|
||||
config = try XCRemoteCacheConfigReader(srcRootPath: srcRoot.path, fileReader: fileManager)
|
||||
.readConfiguration()
|
||||
} catch {
|
||||
errorLog("Libtool initialization failed with error: \(error). Fallbacking to libtool")
|
||||
|
||||
@@ -38,7 +38,7 @@ class ThinningCreatorPlugin: ArtifactCreatorPlugin {
|
||||
|
||||
/// Default Initializer
|
||||
/// - Parameter targetTempDir: Location of current target-specific temp dir (TARGET_TEMP_DIR)
|
||||
/// - Parameter modeMarkerPath: path of maker file that informs if a given target can reuse remote artifacts.
|
||||
/// - Parameter modeMarkerPath: path of maker file that informs if a given target can reuse remote artifacts
|
||||
/// - Parameter dirScanner: scanner to access disk and read files and directories hierarchy
|
||||
init(targetTempDir: URL, modeMarkerPath: String, dirScanner: DirScanner) {
|
||||
self.targetTempDir = targetTempDir
|
||||
@@ -94,7 +94,7 @@ class ThinningCreatorPlugin: ArtifactCreatorPlugin {
|
||||
// ProducerFast mode:
|
||||
// If a target reused already existing artifact, it still has `$(TARGET_TEMP_DIR)/rc.enabled` marker file
|
||||
// and the reused zip is placed in:
|
||||
// `$(TARGET_TEMP_DIR)/xccache/{{fileKey}}.zip` location.
|
||||
// `$(TARGET_TEMP_DIR)/xccache/{{fileKey}}.zip` location
|
||||
|
||||
let targetEnabledMarker = tempDir.appendingPathComponent(modeMarkerPath)
|
||||
let targetReusedArtifactRootDir = tempDir.appendingPathComponent("xccache")
|
||||
|
||||
@@ -25,7 +25,7 @@ class ThinningConsumerPlugin {
|
||||
|
||||
deinit {
|
||||
// initialised but never run plugin suggests that standard target fallbacks to the local development
|
||||
// and DerivedData still misses build artifacts.
|
||||
// and DerivedData still misses build artifacts
|
||||
guard wasRun else {
|
||||
let errorMessage = """
|
||||
\(type(of: self)) plugin has never been run, thinning cannot be supported. Verify you \
|
||||
|
||||
@@ -40,6 +40,7 @@ public struct PostbuildContext {
|
||||
var mode: Mode
|
||||
var targetName: String
|
||||
var targetTempDir: URL
|
||||
var derivedFilesDir: URL
|
||||
/// Location where all compilation outputs (.o) are placed
|
||||
var compilationTempDir: URL
|
||||
var configuration: String
|
||||
@@ -76,19 +77,24 @@ public struct PostbuildContext {
|
||||
let derivedSourcesDir: URL
|
||||
/// List of all targets to downloaded from the thinning aggregation target
|
||||
var thinnedTargets: [String]
|
||||
/// Action type: build, indexbuild etc.
|
||||
/// Action type: build, indexbuild etc
|
||||
var action: BuildActionType
|
||||
let modeMarkerPath: String
|
||||
/// location of the json file that define virtual files system overlay (mappings of the virtual location file -> local file path)
|
||||
/// location of the json file that define virtual files system overlay
|
||||
/// (mappings of the virtual location file -> local file path)
|
||||
let overlayHeadersPath: URL
|
||||
/// Regexes of files that should not be included in the dependency list
|
||||
let irrelevantDependenciesPaths: [String]
|
||||
}
|
||||
|
||||
extension PostbuildContext {
|
||||
// swiftlint:disable:next function_body_length
|
||||
init(_ config: XCRemoteCacheConfig, env: [String: String]) throws {
|
||||
mode = config.mode
|
||||
let targetNameValue: String = try env.readEnv(key: "TARGET_NAME")
|
||||
targetName = targetNameValue
|
||||
targetTempDir = try env.readEnv(key: "TARGET_TEMP_DIR")
|
||||
derivedFilesDir = try env.readEnv(key: "DERIVED_FILE_DIR")
|
||||
let archs: [String] = try env.readEnv(key: "ARCHS").split(separator: " ").map(String.init)
|
||||
guard let firstArch = archs.first, !firstArch.isEmpty else {
|
||||
throw PostbuildContextError.missingArchitecture
|
||||
@@ -119,7 +125,7 @@ extension PostbuildContext {
|
||||
dSYMPath = try env.readEnv(key: "DWARF_DSYM_FOLDER_PATH")
|
||||
.appendingPathComponent(env.readEnv(key: "DWARF_DSYM_FILE_NAME"))
|
||||
builtProductsDir = try env.readEnv(key: "BUILT_PRODUCTS_DIR")
|
||||
if let contentsFolderPath = env.readEnv(key: "CONTENTS_FOLDER_PATH") {
|
||||
if let contentsFolderPath: String = env.readEnv(key: "CONTENTS_FOLDER_PATH") {
|
||||
bundleDir = productsDir.appendingPathComponent(contentsFolderPath)
|
||||
} else {
|
||||
bundleDir = nil
|
||||
@@ -131,5 +137,6 @@ extension PostbuildContext {
|
||||
modeMarkerPath = config.modeMarkerPath
|
||||
/// Note: The file has yaml extension, even it is in the json format
|
||||
overlayHeadersPath = targetTempDir.appendingPathComponent("all-product-headers.yaml")
|
||||
irrelevantDependenciesPaths = config.irrelevantDependenciesPaths
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable type_body_length
|
||||
/// Checks current mode from a configuration and based on that:
|
||||
/// * triggers build completion
|
||||
/// * triggers uploading artifacts to the server for a 'producer' mode
|
||||
@@ -33,8 +34,9 @@ public class XCPostbuild {
|
||||
let context: PostbuildContext
|
||||
let cacheHitLogger: CacheHitLogger
|
||||
do {
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileManager: fileManager).readConfiguration()
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileReader: fileManager).readConfiguration()
|
||||
context = try PostbuildContext(config, env: env)
|
||||
updateProcessTag(context.targetName)
|
||||
let counterFactory: FileStatsCoordinator.CountersFactory = { file, count in
|
||||
ExclusiveFileCounter(ExclusiveFile(file, mode: .override), countersCount: count)
|
||||
}
|
||||
@@ -66,8 +68,8 @@ public class XCPostbuild {
|
||||
// Initialize dependencies
|
||||
let primaryGitBranch = GitBranch(repoLocation: config.primaryRepo, branch: config.primaryBranch)
|
||||
let gitClient = GitClientImpl(repoRoot: config.repoRoot, primary: primaryGitBranch, shell: shellGetStdout)
|
||||
let envsRemapper = try StringDependenciesRemapperFactory().build(
|
||||
orderKeys: DependenciesMapping.rewrittenEnvs,
|
||||
let envsRemapper = try PathDependenciesRemapperFactory().build(
|
||||
orderKeys: DependenciesMapping.rewrittenEnvs + config.customRewriteEnvs,
|
||||
envs: env,
|
||||
customMappings: config.outOfBandMappings
|
||||
)
|
||||
@@ -112,6 +114,7 @@ public class XCPostbuild {
|
||||
awsV4Signature = AWSV4Signature(
|
||||
secretKey: config.AWSSecretKey,
|
||||
accessKey: config.AWSAccessKey,
|
||||
securityToken: config.AWSSecurityToken,
|
||||
region: config.AWSRegion,
|
||||
service: config.AWSService,
|
||||
date: Date(timeIntervalSinceNow: 0)
|
||||
@@ -120,6 +123,7 @@ public class XCPostbuild {
|
||||
let networkClient = NetworkClientImpl(
|
||||
session: sessionFactory.build(),
|
||||
retries: config.uploadRetries,
|
||||
retryDelay: config.retryDelay,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: awsV4Signature
|
||||
)
|
||||
@@ -149,7 +153,8 @@ public class XCPostbuild {
|
||||
if !config.disableVFSOverlay {
|
||||
// As the PostbuildContext assumes file location and filename (`all-product-headers.yaml`)
|
||||
// do not fail in case of a missing headers overlay file. In the future, all overlay files could be
|
||||
// captured from the swiftc invocation similarly is stored in the `history.compile` for the consumer mode.
|
||||
// captured from the swiftc invocation similarly is stored in the `history.compile`
|
||||
// for the consumer mode
|
||||
let overlayReader = JsonOverlayReader(
|
||||
context.overlayHeadersPath,
|
||||
mode: .bestEffort,
|
||||
@@ -167,7 +172,9 @@ public class XCPostbuild {
|
||||
product: context.productsDir,
|
||||
source: context.srcRoot,
|
||||
intermediate: context.targetTempDir,
|
||||
bundle: context.bundleDir
|
||||
derivedFiles: context.derivedFilesDir,
|
||||
bundle: context.bundleDir,
|
||||
skippedRegexes: context.irrelevantDependenciesPaths
|
||||
)
|
||||
// Override fingerprints for all produced '.swiftmodule' files
|
||||
let fingerprintOverrideManager = FingerprintOverrideManagerImpl(
|
||||
@@ -318,3 +325,4 @@ public class XCPostbuild {
|
||||
}
|
||||
}
|
||||
}
|
||||
// swiftlint:enable type_body_length
|
||||
|
||||
@@ -63,7 +63,9 @@ class Prebuild {
|
||||
do {
|
||||
let metaData = try networkClient.fetch(.meta(commit: commit))
|
||||
let meta = try metaReader.read(data: metaData)
|
||||
let localDependencies = try remapper.replace(genericPaths: meta.dependencies).map(URL.init(fileURLWithPath:))
|
||||
let localDependencies = try remapper.replace(
|
||||
genericPaths: meta.dependencies
|
||||
).map(URL.init(fileURLWithPath:))
|
||||
let localFingerprint = try generateFingerprint(for: localDependencies)
|
||||
if localFingerprint.raw != meta.rawFingerprint {
|
||||
if context.forceCached {
|
||||
|
||||
@@ -43,7 +43,8 @@ public struct PrebuildContext {
|
||||
let targetName: String
|
||||
/// List of all targets to downloaded from the thinning aggregation target
|
||||
var thinnedTargets: [String]?
|
||||
/// location of the json file that define virtual files system overlay (mappings of the virtual location file -> local file path)
|
||||
/// location of the json file that define virtual files system overlay
|
||||
/// (mappings of the virtual location file -> local file path)
|
||||
let overlayHeadersPath: URL
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,9 @@ public class XCPrebuild {
|
||||
let config: XCRemoteCacheConfig
|
||||
let context: PrebuildContext
|
||||
do {
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileManager: fileManager).readConfiguration()
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileReader: fileManager).readConfiguration()
|
||||
context = try PrebuildContext(config, env: env)
|
||||
updateProcessTag(context.targetName)
|
||||
} catch {
|
||||
// Fatal error:
|
||||
exit(1, "FATAL: Prebuild initialization failed with error: \(error)")
|
||||
@@ -77,6 +78,10 @@ public class XCPrebuild {
|
||||
exit(0)
|
||||
}
|
||||
|
||||
let compilationHistoryOrganizer = CompilationHistoryFileOrganizer(
|
||||
context.compilationHistoryFile,
|
||||
fileManager: fileManager
|
||||
)
|
||||
do {
|
||||
let envFingerprint = try EnvironmentFingerprintGenerator(
|
||||
configuration: config,
|
||||
@@ -95,6 +100,7 @@ public class XCPrebuild {
|
||||
awsV4Signature = AWSV4Signature(
|
||||
secretKey: config.AWSSecretKey,
|
||||
accessKey: config.AWSAccessKey,
|
||||
securityToken: config.AWSSecurityToken,
|
||||
region: config.AWSRegion,
|
||||
service: config.AWSService,
|
||||
date: Date(timeIntervalSinceNow: 0)
|
||||
@@ -103,6 +109,7 @@ public class XCPrebuild {
|
||||
let networkClient = NetworkClientImpl(
|
||||
session: sessionFactory.build(),
|
||||
retries: config.downloadRetries,
|
||||
retryDelay: config.retryDelay,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: awsV4Signature
|
||||
)
|
||||
@@ -115,15 +122,15 @@ public class XCPrebuild {
|
||||
)
|
||||
let client: NetworkClient = config.disableHttpCache ? networkClient : cacheNetworkClient
|
||||
let remoteNetworkClient = RemoteNetworkClientImpl(client, urlBuilder)
|
||||
let envsRemapper = try StringDependenciesRemapperFactory().build(
|
||||
orderKeys: DependenciesMapping.rewrittenEnvs,
|
||||
let envsRemapper = try PathDependenciesRemapperFactory().build(
|
||||
orderKeys: DependenciesMapping.rewrittenEnvs + config.customRewriteEnvs,
|
||||
envs: env,
|
||||
customMappings: config.outOfBandMappings
|
||||
)
|
||||
var remappers: [DependenciesRemapper] = []
|
||||
if !config.disableVFSOverlay {
|
||||
// As PrebuildContext assumes file location and its filename (`all-product-headers.yaml`)
|
||||
// do not fail in case of a missing headers overlay file.
|
||||
// do not fail in case of a missing headers overlay file
|
||||
let overlayReader = JsonOverlayReader(
|
||||
context.overlayHeadersPath,
|
||||
mode: .bestEffort,
|
||||
@@ -146,10 +153,6 @@ public class XCPrebuild {
|
||||
algorithm: MD5Algorithm()
|
||||
)
|
||||
let organizer = ZipArtifactOrganizer(targetTempDir: context.targetTempDir, fileManager: fileManager)
|
||||
let compilationHistoryOrganizer = CompilationHistoryFileOrganizer(
|
||||
context.compilationHistoryFile,
|
||||
fileManager: fileManager
|
||||
)
|
||||
let metaReader = JsonMetaReader(fileAccessor: fileManager)
|
||||
var consumerPlugins: [ArtifactConsumerPrebuildPlugin] = []
|
||||
|
||||
@@ -187,7 +190,6 @@ public class XCPrebuild {
|
||||
case .compatible(localDependencies: let dependencies):
|
||||
// TODO: pass `allowedInputFiles` observed in the build time
|
||||
try modeController.enable(allowedInputFiles: dependencies, dependencies: dependencies)
|
||||
compilationHistoryOrganizer.reset()
|
||||
}
|
||||
} catch {
|
||||
disableRemoteCache(
|
||||
@@ -195,6 +197,7 @@ public class XCPrebuild {
|
||||
errorMessage: "Prebuild step failed with error: \(error)"
|
||||
)
|
||||
}
|
||||
compilationHistoryOrganizer.reset()
|
||||
}
|
||||
|
||||
private func disableRemoteCache(modeController: PhaseCacheModeController, errorMessage: String?) {
|
||||
|
||||
@@ -72,7 +72,17 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
)
|
||||
infoLog("ClangWrapperBuilder compiles file at \(compilationFile).")
|
||||
// -O3: optimize for faster execution
|
||||
let args = [clangCommand, "-arch", "arm64", "-arch", "x86_64", "-O3", compilationFile.path, "-o", destination.path]
|
||||
let args = [
|
||||
clangCommand,
|
||||
"-arch",
|
||||
"arm64",
|
||||
"-arch",
|
||||
"x86_64",
|
||||
"-O3",
|
||||
compilationFile.path,
|
||||
"-o",
|
||||
destination.path,
|
||||
]
|
||||
let compilationOutput = try shell("xcrun", args, URL(fileURLWithPath: "").path, nil)
|
||||
infoLog("Clang compilation output: \(compilationOutput)")
|
||||
}
|
||||
@@ -385,6 +395,8 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
const char *dependency_arg_name = "-MF";
|
||||
const char *output_arg_name = "-o";
|
||||
const char *serialize_diagnostics_arg_name = "--serialize-diagnostics";
|
||||
const char *language_mode_arg_name = "-x";
|
||||
const char *precompile_header_arg_value = "objective-c-header";
|
||||
const char *clang_cmd = "\(clangCommand)";
|
||||
const char *markerFile = "\(markerFilename)";
|
||||
const char *compilationHistoryFile = "\(compilationHistoryFilename)";
|
||||
@@ -399,6 +411,7 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
const char *output_file= NULL;
|
||||
const char *input_file = NULL;
|
||||
const char *diagnostics_file = NULL;
|
||||
const char *language_mode = NULL;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], dependency_arg_name) == 0 && i < (argc - 1) ) {
|
||||
@@ -419,6 +432,12 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
i += 1;
|
||||
clang_args[i] = argv[i];
|
||||
diagnostics_file = argv[i];
|
||||
} if (strcmp(argv[i], language_mode_arg_name) == 0 && i < (argc - 1) ) {
|
||||
// called with "-x path" pattern and not the last argument
|
||||
clang_args[i] = argv[i];
|
||||
i += 1;
|
||||
clang_args[i] = argv[i];
|
||||
language_mode = argv[i];
|
||||
} else if (
|
||||
isSuffixed(argv[i],".m") ||
|
||||
isSuffixed(argv[i],".mm") ||
|
||||
@@ -426,10 +445,13 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
isSuffixed(argv[i],".cc") ||
|
||||
isSuffixed(argv[i],".cpp") ||
|
||||
isSuffixed(argv[i],".c++") ||
|
||||
isSuffixed(argv[i],".cxx")
|
||||
isSuffixed(argv[i],".cxx") ||
|
||||
isSuffixed(argv[i],".S") ||
|
||||
isSuffixed(argv[i],".s")
|
||||
) {
|
||||
// a full list of extensions is taken from https://clang.llvm.org/docs/ClangFormatStyleOptions.html
|
||||
// support for .m,.mm,.c,.cc,.cpp,.c++,.cxx input files
|
||||
// .s and .S are assembly files
|
||||
clang_args[i] = argv[i];
|
||||
input_file = argv[i];
|
||||
} else {
|
||||
@@ -438,6 +460,14 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// null-terminating the args array needed for local compilation fallback
|
||||
clang_args[argc] = NULL;
|
||||
|
||||
// Verify mode. Even a target is cached, pch mode is not supported. Fallback to the local compilation
|
||||
if (language_mode != NULL && strcmp(language_mode, precompile_header_arg_value) == 0) {
|
||||
return execvp(clang_cmd, (char *const*) clang_args);
|
||||
}
|
||||
|
||||
// Verify all input arguments
|
||||
if (dependency_file == NULL) {
|
||||
fprintf(stderr, "error: missing %s input\\n", dependency_arg_name);
|
||||
@@ -508,8 +538,6 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// null-terminating the args array
|
||||
clang_args[argc] = NULL;
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
|
||||
/// execvp takes $PATH to consideration
|
||||
|
||||
+9
-3
@@ -33,15 +33,18 @@ protocol BuildSettingsIntegrateAppender {
|
||||
class XcodeProjBuildSettingsIntegrateAppender: BuildSettingsIntegrateAppender {
|
||||
private let mode: Mode
|
||||
private let repoRoot: URL
|
||||
private let fakeSrcRoot: URL
|
||||
|
||||
init(mode: Mode, repoRoot: URL) {
|
||||
init(mode: Mode, repoRoot: URL, fakeSrcRoot: URL) {
|
||||
self.mode = mode
|
||||
self.repoRoot = repoRoot
|
||||
self.fakeSrcRoot = fakeSrcRoot
|
||||
}
|
||||
|
||||
func appendToBuildSettings(buildSettings: BuildSettings, wrappers: XCRCBinariesPaths) -> BuildSettings {
|
||||
var result = buildSettings
|
||||
result["SWIFT_EXEC"] = wrappers.swiftc.path
|
||||
result["SWIFT_USE_INTEGRATED_DRIVER"] = "NO"
|
||||
// When generating artifacts, no need to shell-out all compilation commands to our wrappers
|
||||
if case .consumer = mode {
|
||||
result["CC"] = wrappers.cc.path
|
||||
@@ -61,8 +64,11 @@ class XcodeProjBuildSettingsIntegrateAppender: BuildSettingsIntegrateAppender {
|
||||
result["OTHER_SWIFT_FLAGS"] = swiftFlags.settingValue
|
||||
result["OTHER_CFLAGS"] = clangFlags.settingValue
|
||||
|
||||
result["XCRC_FAKE_SRCROOT"] = "/\(String(repeating: "x", count: 10))"
|
||||
result["XCRC_PLATFORM_PREFERRED_ARCH"] = "$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)"
|
||||
result["XCRC_FAKE_SRCROOT"] = "\(fakeSrcRoot.path)"
|
||||
result["XCRC_PLATFORM_PREFERRED_ARCH"] =
|
||||
"""
|
||||
$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)
|
||||
"""
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ struct IncludeExcludeOracle: IncludeOracle {
|
||||
|
||||
|
||||
func shouldInclude(identifier: OracleIdentifierType) -> Bool {
|
||||
// exclude array has precedence.
|
||||
// exclude array has precedence
|
||||
if excludes.contains(identifier) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ extension IntegrateContext {
|
||||
swiftc: binariesDir.appendingPathComponent("xcswiftc"),
|
||||
libtool: binariesDir.appendingPathComponent("xclibtool"),
|
||||
ld: binariesDir.appendingPathComponent("xcld"),
|
||||
ldplusplus: binariesDir.appendingPathComponent("xcldplusplus"),
|
||||
prebuild: binariesDir.appendingPathComponent("xcprebuild"),
|
||||
postbuild: binariesDir.appendingPathComponent("xcpostbuild")
|
||||
)
|
||||
|
||||
@@ -74,7 +74,7 @@ public class XCIntegrate {
|
||||
let binariesDir = commandURL.deletingLastPathComponent()
|
||||
|
||||
let srcRoot: URL = URL(fileURLWithPath: projectPath).deletingLastPathComponent()
|
||||
let config = try XCRemoteCacheConfigReader(srcRootPath: srcRoot.path, fileManager: fileManager)
|
||||
let config = try XCRemoteCacheConfigReader(srcRootPath: srcRoot.path, fileReader: fileManager)
|
||||
.readConfiguration()
|
||||
|
||||
let context = try IntegrateContext(
|
||||
@@ -97,7 +97,8 @@ public class XCIntegrate {
|
||||
)
|
||||
let buildSettingsAppender = XcodeProjBuildSettingsIntegrateAppender(
|
||||
mode: context.mode,
|
||||
repoRoot: context.repoRoot
|
||||
repoRoot: context.repoRoot,
|
||||
fakeSrcRoot: context.fakeSrcRoot
|
||||
)
|
||||
let lldbPatcher: LLDBInitPatcher
|
||||
switch lldbMode {
|
||||
|
||||
@@ -26,6 +26,7 @@ struct XCRCBinariesPaths {
|
||||
let swiftc: URL
|
||||
let libtool: URL
|
||||
let ld: URL
|
||||
let ldplusplus: URL
|
||||
let prebuild: URL
|
||||
let postbuild: URL
|
||||
}
|
||||
|
||||
@@ -230,10 +230,12 @@ struct XcodeProjIntegrate: Integrate {
|
||||
if let sourceIndex = target.buildPhases.map(\.buildPhase).firstIndex(of: .sources) {
|
||||
// add (pre|post)build phases only when a target has some compilation steps
|
||||
// otherwise they make no sense (nothing to store in an artifact)
|
||||
// add postbuild right after compilation as custom build steps may depend on files generated by
|
||||
// the xcpostbuild (e.g. to copy -Swift.h.md5)
|
||||
pbxproj.add(object: postbuildPhase)
|
||||
target.buildPhases.insert(postbuildPhase, at: sourceIndex + 1)
|
||||
pbxproj.add(object: prebuildPhase)
|
||||
target.buildPhases.insert(prebuildPhase, at: sourceIndex)
|
||||
pbxproj.add(object: postbuildPhase)
|
||||
target.buildPhases.append(postbuildPhase)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public class XCConfig {
|
||||
let fileManager = FileManager.default
|
||||
let config: XCRemoteCacheConfig
|
||||
do {
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileManager: fileManager).readConfiguration()
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileReader: fileManager).readConfiguration()
|
||||
} catch {
|
||||
exit(1, "FATAL: Prepare initialization failed with error: \(error)")
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class XCPrepare {
|
||||
var context: PrepareContext
|
||||
let xcodeVersion: String
|
||||
do {
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileManager: fileManager).readConfiguration()
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileReader: fileManager).readConfiguration()
|
||||
context = try PrepareContext(config, offline: offline)
|
||||
xcodeVersion = try customXcodeBuildNumber ?? XcodeProbeImpl(shell: shellGetStdout).read().buildVersion
|
||||
} catch {
|
||||
@@ -78,6 +78,7 @@ public class XCPrepare {
|
||||
awsV4Signature = AWSV4Signature(
|
||||
secretKey: config.AWSSecretKey,
|
||||
accessKey: config.AWSAccessKey,
|
||||
securityToken: config.AWSSecurityToken,
|
||||
region: config.AWSRegion,
|
||||
service: config.AWSService,
|
||||
date: Date(timeIntervalSinceNow: 0)
|
||||
@@ -86,6 +87,7 @@ public class XCPrepare {
|
||||
let networkClient = NetworkClientImpl(
|
||||
session: sessionFactory.build(),
|
||||
retries: config.downloadRetries,
|
||||
retryDelay: config.retryDelay,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: awsV4Signature
|
||||
)
|
||||
|
||||
@@ -46,7 +46,7 @@ public class XCPrepareMark {
|
||||
let context: PrepareMarkContext
|
||||
let xcodeVersion: String
|
||||
do {
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileManager: fileManager).readConfiguration()
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileReader: fileManager).readConfiguration()
|
||||
context = try PrepareMarkContext(config)
|
||||
xcodeVersion = try xcode ?? XcodeProbeImpl(shell: shellGetStdout).read().buildVersion
|
||||
} catch {
|
||||
@@ -60,6 +60,7 @@ public class XCPrepareMark {
|
||||
awsV4Signature = AWSV4Signature(
|
||||
secretKey: config.AWSSecretKey,
|
||||
accessKey: config.AWSAccessKey,
|
||||
securityToken: config.AWSSecurityToken,
|
||||
region: config.AWSRegion,
|
||||
service: config.AWSService,
|
||||
date: Date(timeIntervalSinceNow: 0)
|
||||
@@ -68,6 +69,7 @@ public class XCPrepareMark {
|
||||
let networkClient = NetworkClientImpl(
|
||||
session: sessionFactory.build(),
|
||||
retries: config.uploadRetries,
|
||||
retryDelay: config.retryDelay,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: awsV4Signature
|
||||
)
|
||||
|
||||
@@ -36,7 +36,7 @@ public class XCStats {
|
||||
let config: XCRemoteCacheConfig
|
||||
let context: XCStatsContext
|
||||
do {
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileManager: fileManager).readConfiguration()
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileReader: fileManager).readConfiguration()
|
||||
try context = XCStatsContext(config, fileManager: fileManager)
|
||||
} catch {
|
||||
exit(1, "FATAL: Prepare initialization failed with error: \(error)")
|
||||
|
||||
@@ -70,7 +70,7 @@ public class XCCreateBinary {
|
||||
let config: XCRemoteCacheConfig
|
||||
do {
|
||||
let srcRoot: URL = URL(fileURLWithPath: fileManager.currentDirectoryPath)
|
||||
config = try XCRemoteCacheConfigReader(srcRootPath: srcRoot.path, fileManager: fileManager)
|
||||
config = try XCRemoteCacheConfigReader(srcRootPath: srcRoot.path, fileReader: fileManager)
|
||||
.readConfiguration()
|
||||
} catch {
|
||||
errorLog("\(stepDescription) initialization failed with error: \(error). Fallbacking to \(fallbackCommand)")
|
||||
|
||||
@@ -110,8 +110,8 @@ class SwiftcOrchestrator {
|
||||
try invocationStorage.store(args: invocationArgs)
|
||||
}
|
||||
} catch {
|
||||
// The critical section is protected by a lock. Some other process already called compilation history.
|
||||
// We only need to call our current step then.
|
||||
// The critical section is protected by a lock. Some other process already called compilation history
|
||||
// We only need to call our current step then
|
||||
fallbackToDefault(command: swiftcCommand)
|
||||
}
|
||||
case .consumer:
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import Foundation
|
||||
|
||||
enum DiskSwiftcProductsGeneratorError: Error {
|
||||
/// When a generator was asked to generate unknown swiftmodule extension file.
|
||||
/// When a generator was asked to generate unknown swiftmodule extension file
|
||||
/// Probably a programmer error: asking to generate excessive extensions, not listed in
|
||||
/// `SwiftmoduleFileExtension.SwiftmoduleExtensions`
|
||||
case unknownSwiftmoduleFile
|
||||
|
||||
@@ -70,7 +70,7 @@ public class XCSwiftc {
|
||||
let context: SwiftcContext
|
||||
do {
|
||||
let srcRoot: URL = URL(fileURLWithPath: fileManager.currentDirectoryPath)
|
||||
config = try XCRemoteCacheConfigReader(srcRootPath: srcRoot.path, fileManager: fileManager)
|
||||
config = try XCRemoteCacheConfigReader(srcRootPath: srcRoot.path, fileReader: fileManager)
|
||||
.readConfiguration()
|
||||
context = try SwiftcContext(config: config, input: inputArgs)
|
||||
} catch {
|
||||
|
||||
@@ -83,6 +83,8 @@ public struct XCRemoteCacheConfig: Encodable {
|
||||
var downloadRetries: Int = 0
|
||||
/// Number of retries for upload requests
|
||||
var uploadRetries: Int = 3
|
||||
/// Delay between retries in seconds
|
||||
var retryDelay: Double = 10.0
|
||||
/// Extra headers appended to all remote HTTP(S) requests
|
||||
var requestCustomHeaders: [String: String] = [:]
|
||||
/// Filename (without an extension) of the compilation input file that is used
|
||||
@@ -95,10 +97,10 @@ public struct XCRemoteCacheConfig: Encodable {
|
||||
/// Disable cache for http requests to fecth metadata and download artifacts
|
||||
var disableHttpCache: Bool = false
|
||||
/// Path, relative to $TARGET_TEMP_DIR which gathers all compilation commands that should be e
|
||||
/// xecuted if a target switches to local compilation.
|
||||
/// xecuted if a target switches to local compilation
|
||||
/// Example: A new `.swift` file invalidates remote arXcodeProjIntegrate.swifttifact and triggers local compilation
|
||||
/// When that happens, all previously skipped clang build steps
|
||||
/// need to be eventually called locally - this file lists all these commands.
|
||||
/// need to be eventually called locally - this file lists all these commands
|
||||
var compilationHistoryFile: String = "history.compile"
|
||||
/// Timeout for remote response data interval (in seconds). If an interval between data chunks is
|
||||
/// longer than a timeout, a request fails
|
||||
@@ -118,21 +120,31 @@ public struct XCRemoteCacheConfig: Encodable {
|
||||
var AWSSecretKey: String = ""
|
||||
/// Access key for AWS V4 Signature
|
||||
var AWSAccessKey: String = ""
|
||||
/// Temporary security token provided by the AWS Security Token Service
|
||||
var AWSSecurityToken: String?
|
||||
/// Region for AWS V4 Signature (e.g. `eu`)
|
||||
var AWSRegion: String = ""
|
||||
/// Service for AWS V4 Signature (e.g. `storage`)
|
||||
var AWSService: String = ""
|
||||
/// A dictionary of files path remapping that should be applied to make it absolute path agnostic on a list of dependencies.
|
||||
/// Useful if a project refers files out of repo root, either compilation files or precompiled dependencies.
|
||||
/// Keys represent generic replacement and values are substrings that should be replaced.
|
||||
/// A dictionary of files path remapping that should be applied to make it absolute path agnostic on a list of
|
||||
/// dependencies. Useful if a project refers files out of repo root, either compilation files or precompiled
|
||||
/// dependencies. Keys represent generic replacement and values are substrings that should be replaced
|
||||
/// Example: for mapping `["COOL_LIBRARY": "/CoolLibrary"]`
|
||||
/// `/CoolLibrary/main.swift`will be represented as `$(COOL_LIBRARY)/main.swift`).
|
||||
/// Warning: remapping order is not-deterministic so avoid remappings with multiple matchings.
|
||||
/// `/CoolLibrary/main.swift`will be represented as `$(COOL_LIBRARY)/main.swift`)
|
||||
/// Warning: remapping order is not-deterministic so avoid remappings with multiple matchings
|
||||
var outOfBandMappings: [String: String] = [:]
|
||||
/// If true, SSL certificate validation is disabled
|
||||
var disableCertificateVerification: Bool = false
|
||||
/// A feature flag to disable virtual file system overlay support (temporary)
|
||||
var disableVFSOverlay: Bool = false
|
||||
/// A list of extra ENVs that should be used as placeholders in the dependency list
|
||||
/// ENV rewrite process is optimistic - does nothing if an ENV is not defined in the pre/postbuild process
|
||||
var customRewriteEnvs: [String] = []
|
||||
/// Regexes of files that should not be included in a list of dependencies. Warning! Add entries here
|
||||
/// with caution - excluding dependencies that are relevant might lead to a target overcaching
|
||||
/// Note: The regex can match either partially or fully the filepath, e.g. `\\.modulemap$` will exclude
|
||||
/// all `.modulemap` files
|
||||
var irrelevantDependenciesPaths: [String] = []
|
||||
}
|
||||
|
||||
extension XCRemoteCacheConfig {
|
||||
@@ -165,6 +177,7 @@ extension XCRemoteCacheConfig {
|
||||
merge.statsDir = scheme.statsDir ?? statsDir
|
||||
merge.downloadRetries = scheme.downloadRetries ?? downloadRetries
|
||||
merge.uploadRetries = scheme.uploadRetries ?? uploadRetries
|
||||
merge.retryDelay = scheme.retryDelay ?? retryDelay
|
||||
merge.requestCustomHeaders = scheme.requestCustomHeaders ?? requestCustomHeaders
|
||||
merge.thinTargetMockFilename = scheme.thinTargetMockFilename ?? thinTargetMockFilename
|
||||
merge.focusedTargets = scheme.focusedTargets ?? focusedTargets
|
||||
@@ -181,11 +194,14 @@ extension XCRemoteCacheConfig {
|
||||
merge.prettifyMetaFiles = scheme.prettifyMetaFiles ?? prettifyMetaFiles
|
||||
merge.AWSAccessKey = scheme.AWSAccessKey ?? AWSAccessKey
|
||||
merge.AWSSecretKey = scheme.AWSSecretKey ?? AWSSecretKey
|
||||
merge.AWSSecurityToken = scheme.AWSSecurityToken ?? AWSSecurityToken
|
||||
merge.AWSRegion = scheme.AWSRegion ?? AWSRegion
|
||||
merge.AWSService = scheme.AWSService ?? AWSService
|
||||
merge.outOfBandMappings = scheme.outOfBandMappings ?? outOfBandMappings
|
||||
merge.disableCertificateVerification = scheme.disableCertificateVerification ?? disableCertificateVerification
|
||||
merge.disableVFSOverlay = scheme.disableVFSOverlay ?? disableVFSOverlay
|
||||
merge.customRewriteEnvs = scheme.customRewriteEnvs ?? customRewriteEnvs
|
||||
merge.irrelevantDependenciesPaths = scheme.irrelevantDependenciesPaths ?? irrelevantDependenciesPaths
|
||||
return merge
|
||||
}
|
||||
|
||||
@@ -230,6 +246,7 @@ struct ConfigFileScheme: Decodable {
|
||||
let statsDir: String?
|
||||
let downloadRetries: Int?
|
||||
let uploadRetries: Int?
|
||||
let retryDelay: Double?
|
||||
let requestCustomHeaders: [String: String]?
|
||||
let thinTargetMockFilename: String?
|
||||
let focusedTargets: [String]?
|
||||
@@ -243,11 +260,14 @@ struct ConfigFileScheme: Decodable {
|
||||
let prettifyMetaFiles: Bool?
|
||||
let AWSSecretKey: String?
|
||||
let AWSAccessKey: String?
|
||||
let AWSSecurityToken: String?
|
||||
let AWSRegion: String?
|
||||
let AWSService: String?
|
||||
let outOfBandMappings: [String: String]?
|
||||
let disableCertificateVerification: Bool?
|
||||
let disableVFSOverlay: Bool?
|
||||
let customRewriteEnvs: [String]?
|
||||
let irrelevantDependenciesPaths: [String]?
|
||||
|
||||
// Yams library doesn't support encoding strategy, see https://github.com/jpsim/Yams/issues/84
|
||||
enum CodingKeys: String, CodingKey {
|
||||
@@ -275,6 +295,7 @@ struct ConfigFileScheme: Decodable {
|
||||
case statsDir = "stats_dir"
|
||||
case downloadRetries = "download_retries"
|
||||
case uploadRetries = "upload_retries"
|
||||
case retryDelay = "retry_delay"
|
||||
case requestCustomHeaders = "request_custom_headers"
|
||||
case thinTargetMockFilename = "thin_target_mock_filename"
|
||||
case focusedTargets = "focused_targets"
|
||||
@@ -288,11 +309,14 @@ struct ConfigFileScheme: Decodable {
|
||||
case prettifyMetaFiles = "prettify_meta_files"
|
||||
case AWSSecretKey = "aws_secret_key"
|
||||
case AWSAccessKey = "aws_access_key"
|
||||
case AWSSecurityToken = "aws_security_token"
|
||||
case AWSRegion = "aws_region"
|
||||
case AWSService = "aws_service"
|
||||
case outOfBandMappings = "out_of_band_mappings"
|
||||
case disableCertificateVerification = "disable_certificate_verification"
|
||||
case disableVFSOverlay = "disable_vfs_overlay"
|
||||
case customRewriteEnvs = "custom_rewrite_envs"
|
||||
case irrelevantDependenciesPaths = "irrelevant_dependencies_paths"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,31 +329,42 @@ class XCRemoteCacheConfigReader {
|
||||
/// Name of the configuration file, required in $(SRCROOT) location
|
||||
private static let configurationFile = ".rcinfo"
|
||||
private let srcRoot: String
|
||||
private let fileManager: FileManager
|
||||
private let fileReader: FileReader
|
||||
private lazy var yamlDecorer = YAMLDecoder(encoding: .utf8)
|
||||
|
||||
init(env: [String: String], fileManager: FileManager) throws {
|
||||
init(env: [String: String], fileReader: FileReader) throws {
|
||||
let explicitSrcRoot: String? = env.readEnv(key: "SRCROOT")
|
||||
srcRoot = explicitSrcRoot ?? fileManager.currentDirectoryPath
|
||||
self.fileManager = fileManager
|
||||
srcRoot = explicitSrcRoot ?? FileManager.default.currentDirectoryPath
|
||||
self.fileReader = fileReader
|
||||
}
|
||||
|
||||
init(srcRootPath srcRoot: String, fileManager: FileManager) {
|
||||
init(srcRootPath srcRoot: String, fileReader: FileReader) {
|
||||
self.srcRoot = srcRoot
|
||||
self.fileManager = fileManager
|
||||
self.fileReader = fileReader
|
||||
}
|
||||
|
||||
// Reads the final configuration by loading all extra configs
|
||||
// until reaching a config that doesn't override `extraConfigurationFile`
|
||||
func readConfiguration() throws -> XCRemoteCacheConfig {
|
||||
let rootURL = URL(fileURLWithPath: srcRoot)
|
||||
let configURL = URL(fileURLWithPath: Self.configurationFile, relativeTo: rootURL)
|
||||
let userConfigs = try readUserConfig(configURL)
|
||||
var config = XCRemoteCacheConfig(sourceRoot: srcRoot).merged(with: userConfigs)
|
||||
let extraConfURL = URL(fileURLWithPath: config.extraConfigurationFile, relativeTo: rootURL)
|
||||
do {
|
||||
let extraConfig = try readUserConfig(extraConfURL)
|
||||
config = config.merged(with: extraConfig)
|
||||
} catch {
|
||||
infoLog("Extra config override failed with \(error). Skipping extra configuration")
|
||||
var extraConfURL = URL(fileURLWithPath: config.extraConfigurationFile, relativeTo: rootURL)
|
||||
var visitedFiles = Set([configURL])
|
||||
while !visitedFiles.contains(extraConfURL) {
|
||||
do {
|
||||
let extraConfig = try readUserConfig(extraConfURL)
|
||||
debugLog("Reading extra configuration from \(extraConfURL)")
|
||||
config = config.merged(with: extraConfig)
|
||||
visitedFiles.insert(extraConfURL)
|
||||
// Advance extra configuration
|
||||
extraConfURL = URL(fileURLWithPath: config.extraConfigurationFile, relativeTo: rootURL)
|
||||
} catch {
|
||||
infoLog("Extra config override failed with \(error). Skipping extra configuration")
|
||||
// swiftlint:disable:next unneeded_break_in_switch
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return try config.verifyAndApplyDefaults()
|
||||
@@ -337,7 +372,7 @@ class XCRemoteCacheConfigReader {
|
||||
|
||||
/// Reads user configuration from a file
|
||||
private func readUserConfig(_ file: URL) throws -> ConfigFileScheme {
|
||||
let configurationContent = fileManager.contents(atPath: file.path)
|
||||
let configurationContent = try fileReader.contents(atPath: file.path)
|
||||
guard let configurationData = configurationContent else {
|
||||
throw XCRemoteCacheConfigReaderError.missingConfigurationFile(file)
|
||||
}
|
||||
|
||||
@@ -51,34 +51,22 @@ public class FileDependenciesReader: DependenciesReader {
|
||||
public func findDependencies() throws -> [String] {
|
||||
let yaml = try readRaw()
|
||||
|
||||
struct ParseState {
|
||||
var buffer: String = ""
|
||||
var prevChar: Character?
|
||||
var result: [String] = []
|
||||
func with(buffer: String? = nil, prevChar: Character? = nil, result: [String]? = nil) -> ParseState {
|
||||
var new = self
|
||||
new.buffer = buffer ?? new.buffer
|
||||
new.prevChar = prevChar ?? new.prevChar
|
||||
new.result = result ?? new.result
|
||||
return new
|
||||
}
|
||||
}
|
||||
|
||||
let dependencies = yaml.reduce(Set<String>()) { prev, arg1 -> Set<String> in
|
||||
let (key, value) = arg1
|
||||
switch key {
|
||||
case "dependencies":
|
||||
// 'clang' output formatting
|
||||
return Set(splitDependencyFileList(value))
|
||||
return Set(parseDependencyFileList(value))
|
||||
case let s where s.hasSuffix(".o") || s.hasSuffix(".bc"):
|
||||
// 'swiftc' output formatting
|
||||
// take dependencies from any .o or .bc file.
|
||||
// take dependencies from any .o or .bc file
|
||||
// Note: For WMO, all .{o|bc} files have the same dependencies
|
||||
return Set(splitDependencyFileList(value))
|
||||
return Set(parseDependencyFileList(value))
|
||||
default:
|
||||
return prev
|
||||
}
|
||||
}
|
||||
|
||||
return Array(dependencies)
|
||||
}
|
||||
|
||||
@@ -92,56 +80,92 @@ public class FileDependenciesReader: DependenciesReader {
|
||||
return yaml.mapValues { $0.components(separatedBy: .whitespaces) }
|
||||
}
|
||||
|
||||
private func readRaw() throws -> [String: String] {
|
||||
func readRaw() throws -> [String: String] {
|
||||
let fileData = try getFileData()
|
||||
let fileString = try getFileStringFromData(fileData: fileData)
|
||||
let yaml = try getYaml(fileString: fileString)
|
||||
return yaml
|
||||
}
|
||||
|
||||
func getFileData() throws -> Data {
|
||||
guard let fileData = fileManager.contents(atPath: file.path) else {
|
||||
throw DependenciesReaderError.readingError
|
||||
}
|
||||
return fileData
|
||||
}
|
||||
|
||||
func getFileStringFromData(fileData: Data) throws -> String {
|
||||
guard let fileString = String(data: fileData, encoding: .utf8) else {
|
||||
throw DependenciesReaderError.invalidFile
|
||||
}
|
||||
// .d matches the .yaml format
|
||||
return fileString
|
||||
}
|
||||
|
||||
func getYaml(fileString: String) throws -> [String: String] {
|
||||
guard let yaml = try Yams.load(yaml: fileString) as? [String: String] else {
|
||||
throw DependenciesReaderError.invalidFile
|
||||
}
|
||||
return yaml
|
||||
}
|
||||
|
||||
/// Splits space or new line separated files into a set of files
|
||||
/// Parses the String to get the list of files
|
||||
/// It iterates over the String using its UTF8View since it is more performant (String type operates
|
||||
/// in a higher abstraction level and supports features that have a negative impact in the performance)
|
||||
/// It supports escaping whitespace charaters, prefixed with "\\"
|
||||
/// - Parameter string: string of whitespace charaters separated file paths
|
||||
/// - Returns: Array of all file paths
|
||||
private func splitDependencyFileList(_ string: String) -> [String] {
|
||||
struct ParseState {
|
||||
var buffer: String = ""
|
||||
var prevChar: Character?
|
||||
var result: [String] = []
|
||||
func with(buffer: String? = nil, prevChar: Character? = nil, result: [String]? = nil) -> ParseState {
|
||||
var new = self
|
||||
new.buffer = buffer ?? new.buffer
|
||||
new.prevChar = prevChar ?? new.prevChar
|
||||
new.result = result ?? new.result
|
||||
return new
|
||||
}
|
||||
func parseDependencyFileList(_ string: String) -> [String] {
|
||||
var result: [String] = []
|
||||
var prevChar: UTF8.CodeUnit?
|
||||
|
||||
// These index are used to move over the UTF8View of the string
|
||||
// The goal is to optimize the memory used, since UTF8View uses
|
||||
// the same memory as the original String without copying it
|
||||
var startIndex = string.utf8.startIndex
|
||||
var endIndex = startIndex
|
||||
|
||||
// This buffer is only used to save the part of the path that has been already parsed when finding a backslash
|
||||
var buffer: String = ""
|
||||
|
||||
for c in string.utf8 {
|
||||
switch c {
|
||||
case UTF8.CodeUnit(ascii: "\n") where prevChar == UTF8.CodeUnit(ascii: "\\"):
|
||||
startIndex = string.utf8.index(after: startIndex)
|
||||
endIndex = startIndex
|
||||
case UTF8.CodeUnit(ascii: " ") where startIndex == endIndex && buffer.isEmpty:
|
||||
startIndex = string.utf8.index(after: startIndex)
|
||||
endIndex = startIndex
|
||||
case UTF8.CodeUnit(ascii: " ") where prevChar != UTF8.CodeUnit(ascii: "\\"):
|
||||
// If a space is found and it is not escaped, then that's the end of the file path
|
||||
buffer += String(Substring(string.utf8[startIndex ..< endIndex]))
|
||||
result.append(buffer)
|
||||
buffer = ""
|
||||
prevChar = nil
|
||||
startIndex = string.utf8.index(after: endIndex)
|
||||
endIndex = startIndex
|
||||
case UTF8.CodeUnit(ascii: "\\"):
|
||||
// If a backslash is found it is not included in the file path
|
||||
// The current parsed range of the UTF8View is saved in the buffer as a String
|
||||
buffer += String(Substring(string.utf8[startIndex ..< endIndex]))
|
||||
// The backslash is assigned as the previous char
|
||||
prevChar = c
|
||||
// The indexes are moved to the next char so we continue parsing the String
|
||||
startIndex = string.utf8.index(after: endIndex)
|
||||
endIndex = startIndex
|
||||
default:
|
||||
// As long as it is possible the indexes are used to track the range of the string that
|
||||
// will be included in the file path (until it ends or until a backslash is found)
|
||||
endIndex = string.utf8.index(after: endIndex)
|
||||
// The char is assigned as the previous char
|
||||
prevChar = c
|
||||
}
|
||||
}
|
||||
let parseResult = string.reduce(ParseState()) { total, char in
|
||||
switch char {
|
||||
case "\n" where total.prevChar == "\\":
|
||||
return total
|
||||
case " " where total.buffer.isEmpty:
|
||||
return total
|
||||
case " " where total.prevChar == "\\":
|
||||
return total.with(buffer: "\(total.buffer) ")
|
||||
case " ":
|
||||
return total.with(buffer: "", prevChar: nil, result: total.result + [total.buffer])
|
||||
case "\\":
|
||||
return total.with(prevChar: "\\")
|
||||
default:
|
||||
return total.with(buffer: "\(total.buffer)\(char)", prevChar: char, result: total.result)
|
||||
}
|
||||
|
||||
if startIndex != endIndex {
|
||||
buffer += String(Substring(string.utf8[startIndex ..< endIndex]))
|
||||
result.append(buffer)
|
||||
}
|
||||
if !parseResult.buffer.isEmpty {
|
||||
return parseResult.result + [parseResult.buffer]
|
||||
}
|
||||
return parseResult.result
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class FileDependenciesWriter: DependenciesWriter {
|
||||
var content = ""
|
||||
for (file, deps) in dependencies {
|
||||
content.append(file + ": ")
|
||||
content.append(deps.joined(separator: " "))
|
||||
content.append(deps.map { $0.replacingOccurrences(of: " ", with: "\\ ") }.joined(separator: " "))
|
||||
content.append("\n")
|
||||
}
|
||||
try content.write(to: file, atomically: true, encoding: .utf8)
|
||||
|
||||
@@ -27,8 +27,11 @@ public struct Dependency: Equatable {
|
||||
case source
|
||||
case fingerprint
|
||||
case intermediate
|
||||
case derivedFile
|
||||
// Product of the target itself
|
||||
case ownProduct
|
||||
// User-excluded path
|
||||
case userExcluded
|
||||
case unknown
|
||||
}
|
||||
|
||||
@@ -55,14 +58,18 @@ class DependencyProcessorImpl: DependencyProcessor {
|
||||
private let productPath: String
|
||||
private let sourcePath: String
|
||||
private let intermediatePath: String
|
||||
private let derivedFilesPath: String
|
||||
private let bundlePath: String?
|
||||
private let skippedRegexes: [String]
|
||||
|
||||
init(xcode: URL, product: URL, source: URL, intermediate: URL, bundle: URL?) {
|
||||
init(xcode: URL, product: URL, source: URL, intermediate: URL, derivedFiles: URL, bundle: URL?, skippedRegexes: [String]) {
|
||||
xcodePath = xcode.path.dirPath()
|
||||
productPath = product.path.dirPath()
|
||||
sourcePath = source.path.dirPath()
|
||||
intermediatePath = intermediate.path.dirPath()
|
||||
derivedFilesPath = derivedFiles.path.dirPath()
|
||||
bundlePath = bundle?.path.dirPath()
|
||||
self.skippedRegexes = skippedRegexes
|
||||
}
|
||||
|
||||
func process(_ files: [URL]) -> [Dependency] {
|
||||
@@ -73,10 +80,14 @@ class DependencyProcessorImpl: DependencyProcessor {
|
||||
private func classify(_ files: [URL]) -> [Dependency] {
|
||||
return files.map { file -> Dependency in
|
||||
let filePath = file.resolvingSymlinksInPath().path
|
||||
if filePath.hasPrefix(xcodePath) {
|
||||
if skippedRegexes.contains(where: { filePath.range(of: $0, options: .regularExpression) != nil }) {
|
||||
return Dependency(url: file, type: .userExcluded)
|
||||
} else if filePath.hasPrefix(xcodePath) {
|
||||
return Dependency(url: file, type: .xcode)
|
||||
} else if filePath.hasPrefix(intermediatePath) {
|
||||
return Dependency(url: file, type: .intermediate)
|
||||
} else if filePath.hasPrefix(derivedFilesPath) {
|
||||
return Dependency(url: file, type: .derivedFile)
|
||||
} else if let bundle = bundlePath, filePath.hasPrefix(bundle) {
|
||||
// If a target produces a bundle, explicitly classify all
|
||||
// of products to distinguish from other targets products
|
||||
@@ -107,7 +118,12 @@ class DependencyProcessorImpl: DependencyProcessor {
|
||||
// - All files in `*/Interemediates/*` - this file are created on-fly for a given target
|
||||
// - Some files may depend on its own product (e.g. .m may #include *-Swift.h) - we know products will match
|
||||
// because in case of a hit, these will be taken from the artifact
|
||||
let irrelevantDependenciesType: [Dependency.Kind] = [.xcode, .intermediate, .ownProduct]
|
||||
// - Customized DERIVED_FILE_DIR may change a directory of
|
||||
// derived files, which by default is under `*/Interemediates`
|
||||
// - User-specified (in .rcinfo) files to exclude
|
||||
let irrelevantDependenciesType: [Dependency.Kind] = [
|
||||
.xcode, .intermediate, .ownProduct, .derivedFile, .userExcluded,
|
||||
]
|
||||
return !irrelevantDependenciesType.contains(dependency.type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ class OverlayDependenciesRemapper: DependenciesRemapper {
|
||||
|
||||
private func mapPath(
|
||||
_ path: String,
|
||||
source: KeyPath<OverlayMapping,URL>,
|
||||
destination: KeyPath<OverlayMapping,URL>
|
||||
source: KeyPath<OverlayMapping, URL>,
|
||||
destination: KeyPath<OverlayMapping, URL>
|
||||
) throws -> String {
|
||||
guard let mapping = try getMappings().first(where: { $0[keyPath: source].path == path }) else {
|
||||
// TODO: support partial mappings, where a directory path can be replaced with some other directory
|
||||
@@ -54,14 +54,14 @@ class OverlayDependenciesRemapper: DependenciesRemapper {
|
||||
}
|
||||
|
||||
func replace(genericPaths: [String]) throws -> [String] {
|
||||
try Set(genericPaths.map {
|
||||
try genericPaths.map {
|
||||
try mapPath($0, source: \.virtual, destination: \.local)
|
||||
}).sorted()
|
||||
}
|
||||
}
|
||||
|
||||
func replace(localPaths: [String]) throws -> [String] {
|
||||
try Set(localPaths.map {
|
||||
try localPaths.map {
|
||||
try mapPath($0, source: \.local, destination: \.virtual)
|
||||
}).sorted()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ class JsonOverlayReader: OverlayReader {
|
||||
case .strict:
|
||||
throw JsonOverlayReaderError.missingSourceFile(json)
|
||||
case .bestEffort:
|
||||
printWarning("overlay mapping file \(json) doesn't exist. Skipping overlay for the best-effort mode.")
|
||||
debugLog("overlay mapping file \(json) doesn't exist. Skipping overlay for the best-effort mode.")
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ class JsonOverlayReader: OverlayReader {
|
||||
let mappings: [OverlayMapping] = try overlay.roots.reduce([]) { prev, root in
|
||||
switch root.type {
|
||||
case .directory:
|
||||
//iterate all contents
|
||||
// iterate all contents
|
||||
let dir = URL(fileURLWithPath: root.name)
|
||||
let mappings: [OverlayMapping] = try root.contents.map { content in
|
||||
switch content.type {
|
||||
@@ -124,7 +124,7 @@ class JsonOverlayReader: OverlayReader {
|
||||
case .strict:
|
||||
throw error
|
||||
case .bestEffort:
|
||||
printWarning("Overlay reader has failed with an error \(error). Best-effort mode - skipping an overlay.")
|
||||
errorLog("Overlay reader has failed with an error \(error). Best-effort mode - skipping an overlay.")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
+12
-8
@@ -19,24 +19,28 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum StringDependenciesRemapperFactoryError: Error {
|
||||
enum PathDependenciesRemapperFactoryError: Error {
|
||||
/// Remapping keys are duplicated and can lead to undetermined results
|
||||
case mappingKeyDuplication
|
||||
}
|
||||
|
||||
class StringDependenciesRemapperFactory {
|
||||
class PathDependenciesRemapperFactory {
|
||||
func build(
|
||||
orderKeys: [String],
|
||||
envs: [String: String],
|
||||
customMappings: [String: String]
|
||||
) throws -> StringDependenciesRemapper {
|
||||
let mappingMap = try envs.merging(customMappings) { envValue, outOfBandMapping in
|
||||
throw StringDependenciesRemapperFactoryError.mappingKeyDuplication
|
||||
let mappingMap = try envs.merging(customMappings) { _, _ in
|
||||
throw PathDependenciesRemapperFactoryError.mappingKeyDuplication
|
||||
}
|
||||
let mappingOrderKeys = orderKeys + customMappings.keys
|
||||
let mappings: [StringDependenciesRemapper.Mapping] = try mappingOrderKeys.map { key in
|
||||
let localValue: String = try mappingMap.readEnv(key: key)
|
||||
return StringDependenciesRemapper.Mapping(generic: "$(\(key))", local: localValue)
|
||||
let mappingOrderKeys = orderKeys + customMappings.keys
|
||||
let mappings: [StringDependenciesRemapper.Mapping] = mappingOrderKeys.compactMap { key in
|
||||
guard let localURL: URL = mappingMap.readEnv(key: key) else {
|
||||
debugLog("\(key) ENV to map a dependency is not defined")
|
||||
return nil
|
||||
}
|
||||
infoLog("Found url to remapp: \(localURL). Remapping: \(localURL.standardized.path)")
|
||||
return StringDependenciesRemapper.Mapping(generic: "$(\(key))", local: localURL.standardized.path)
|
||||
}
|
||||
return StringDependenciesRemapper(mappings: mappings)
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class TargetDependenciesReader: DependenciesReader {
|
||||
let allURLs = try dirScanner.items(at: directory)
|
||||
let mergedDependencies = try allURLs.reduce(Set<String>()) { (prev: Set<String>, file) in
|
||||
// include only these .d files that either have corresponding .o file (incremental) or end
|
||||
// with '-master' (whole-module).
|
||||
// with '-master' (whole-module)
|
||||
// Otherwise .d is probably just a leftover from previous builds
|
||||
let correspondingOutputURL = file.deletingPathExtension().appendingPathExtension("o")
|
||||
let isDependencyFile = file.pathExtension == "d"
|
||||
|
||||
@@ -31,7 +31,7 @@ class EnvironmentFingerprintGenerator {
|
||||
"DYLIB_COMPATIBILITY_VERSION",
|
||||
"DYLIB_CURRENT_VERSION",
|
||||
"PRODUCT_MODULE_NAME",
|
||||
"ARCHS"
|
||||
"ARCHS",
|
||||
]
|
||||
private let version: String
|
||||
private let customFingerprintEnvs: [String]
|
||||
@@ -52,7 +52,9 @@ class EnvironmentFingerprintGenerator {
|
||||
}
|
||||
try fill(envKeys: Self.defaultEnvFingerprintKeys + customFingerprintEnvs)
|
||||
try generator.append(version)
|
||||
return try generator.generate()
|
||||
let result = try generator.generate()
|
||||
generatedFingerprint = result
|
||||
return result
|
||||
}
|
||||
|
||||
/// Creates a fingerprint of the environemtn, by hashing all ENVs specified in keys
|
||||
|
||||
@@ -22,34 +22,36 @@ import Foundation
|
||||
import os.log
|
||||
|
||||
|
||||
private var processTag: String = ""
|
||||
|
||||
public func exit(_ exitCode: Int32, _ message: String) -> Never {
|
||||
os_log("%{public}@", log: OSLog.default, type: .error, message)
|
||||
os_log("%{public}@%{public}@", log: OSLog.default, type: .error, processTag, message)
|
||||
printError(errorMessage: message)
|
||||
exit(exitCode)
|
||||
}
|
||||
|
||||
func defaultLog(_ message: String) {
|
||||
os_log("%{public}@", log: OSLog.default, type: .default, message)
|
||||
os_log("%{public}@%{public}@", log: OSLog.default, type: .default, processTag, message)
|
||||
}
|
||||
|
||||
func errorLog(_ message: String) {
|
||||
os_log("%{public}@", log: OSLog.default, type: .error, message)
|
||||
os_log("%{public}@%{public}@", log: OSLog.default, type: .error, processTag, message)
|
||||
}
|
||||
|
||||
func infoLog(_ message: String) {
|
||||
os_log("%{public}@", log: OSLog.default, type: .info, message)
|
||||
os_log("%{public}@%{public}@", log: OSLog.default, type: .info, processTag, message)
|
||||
}
|
||||
|
||||
func debugLog(_ message: String) {
|
||||
os_log("%{public}@", log: OSLog.default, type: .debug, message)
|
||||
os_log("%{public}@%{public}@", log: OSLog.default, type: .debug, processTag, message)
|
||||
}
|
||||
|
||||
func printError(errorMessage: String) {
|
||||
fputs("error: \(errorMessage)\n", stderr)
|
||||
fputs("error: \(processTag)\(errorMessage)\n", stderr)
|
||||
}
|
||||
|
||||
func printWarning(_ message: String) {
|
||||
print("warning: \(message)")
|
||||
print("warning: \(processTag)\(message)")
|
||||
}
|
||||
|
||||
/// Prints a message to the user. It shows in Xcode (if applies) or console output
|
||||
@@ -57,3 +59,7 @@ func printWarning(_ message: String) {
|
||||
func printToUser(_ message: String) {
|
||||
print("[RC] \(message)")
|
||||
}
|
||||
|
||||
func updateProcessTag(_ tag: String) {
|
||||
processTag = "(\(tag)) "
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import Foundation
|
||||
|
||||
protocol MetaWriter {
|
||||
func write<T>(_ meta: T, locationDir : URL) throws -> URL where T : Meta
|
||||
func write<T>(_ meta: T, locationDir: URL) throws -> URL where T: Meta
|
||||
}
|
||||
|
||||
class JsonMetaWriter: MetaWriter {
|
||||
@@ -36,7 +36,7 @@ class JsonMetaWriter: MetaWriter {
|
||||
self.metaEncoder = encoder
|
||||
}
|
||||
|
||||
func write<T>(_ meta: T, locationDir : URL) throws -> URL where T : Meta {
|
||||
func write<T>(_ meta: T, locationDir: URL) throws -> URL where T: Meta {
|
||||
let metaURL = locationDir.appendingPathComponent(meta.fileKey).appendingPathExtension("json")
|
||||
let metaData = try metaEncoder.encode(meta)
|
||||
try fileWriter.write(toPath: metaURL.path, contents: metaData)
|
||||
|
||||
@@ -23,17 +23,21 @@ struct AWSV4Signature {
|
||||
|
||||
let secretKey: String
|
||||
let accessKey: String
|
||||
let securityToken: String?
|
||||
let region: String
|
||||
let service: String
|
||||
let date: Date
|
||||
|
||||
|
||||
func addSignatureHeaderTo(request: inout URLRequest) {
|
||||
|
||||
request.setValue(request.url?.host, forHTTPHeaderField: "host")
|
||||
request.setValue(StringToSign.ISO8601BasicFormatter.string(from: date), forHTTPHeaderField: "x-amz-date")
|
||||
request.setValue((request.httpBody ?? Data()).sha256(), forHTTPHeaderField: "x-amz-content-sha256")
|
||||
|
||||
if let securityToken = securityToken {
|
||||
request.setValue(securityToken, forHTTPHeaderField: "x-amz-security-token")
|
||||
}
|
||||
|
||||
let canonicalRequest = CanonicalRequest(request: request)
|
||||
let stringToSign = StringToSign(
|
||||
region: region,
|
||||
|
||||
@@ -25,7 +25,7 @@ final class IgnoringCertificatesTrustManager: NSObject, URLSessionDelegate {
|
||||
completionHandler(.performDefaultHandling, nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let urlCredential = URLCredential(trust: serverTrust)
|
||||
completionHandler(.useCredential, urlCredential)
|
||||
}
|
||||
|
||||
@@ -29,12 +29,14 @@ class NetworkClientImpl: NetworkClient {
|
||||
private let session: URLSession
|
||||
private let fileManager: FileManager
|
||||
private let maxRetries: Int
|
||||
private let retryDelay: TimeInterval
|
||||
private let awsV4Signature: AWSV4Signature?
|
||||
|
||||
init(session: URLSession, retries: Int, fileManager: FileManager, awsV4Signature: AWSV4Signature?) {
|
||||
init(session: URLSession, retries: Int, retryDelay: TimeInterval, fileManager: FileManager, awsV4Signature: AWSV4Signature?) {
|
||||
self.session = session
|
||||
self.fileManager = fileManager
|
||||
maxRetries = retries
|
||||
self.maxRetries = retries
|
||||
self.retryDelay = retryDelay
|
||||
self.awsV4Signature = awsV4Signature
|
||||
}
|
||||
|
||||
@@ -173,7 +175,13 @@ class NetworkClientImpl: NetworkClient {
|
||||
if let error = responseError {
|
||||
if retries > 0 {
|
||||
infoLog("Upload request failed with \(error). Left retries: \(retries).")
|
||||
self.makeUploadRequest(request, input: input, retries: retries - 1, completion: completion)
|
||||
self.retryUpload(
|
||||
request,
|
||||
input: input,
|
||||
retries: retries,
|
||||
completion: completion,
|
||||
after: self.retryDelay
|
||||
)
|
||||
return
|
||||
}
|
||||
errorLog("Upload request failed: \(error)")
|
||||
@@ -184,6 +192,13 @@ class NetworkClientImpl: NetworkClient {
|
||||
}
|
||||
dataTask.resume()
|
||||
}
|
||||
|
||||
private func retryUpload(_ request: URLRequest, input: URL, retries: Int, completion: @escaping (Result<Void, NetworkClientError>) -> Void, after: TimeInterval) {
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + after) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.makeUploadRequest(request, input: input, retries: retries - 1, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension NetworkClientError {
|
||||
|
||||
@@ -36,6 +36,8 @@ class DefaultURLSessionFactory: URLSessionFactory {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
configuration.httpAdditionalHeaders = config.requestCustomHeaders
|
||||
configuration.timeoutIntervalForRequest = config.timeoutResponseDataChunksInterval
|
||||
configuration.urlCache?.memoryCapacity = 0
|
||||
configuration.urlCache?.diskCapacity = 0
|
||||
switch config.disableCertificateVerification {
|
||||
case true:
|
||||
return URLSession(
|
||||
|
||||
@@ -76,8 +76,8 @@ class ExclusiveFile: ExclusiveFileAccessor {
|
||||
guard flock(fd, LOCK_EX) == 0 else {
|
||||
throw FileAccessorError.lockingFailure
|
||||
}
|
||||
// While having a lock, make sure the file still exists.
|
||||
// It might delete it while we were waiting for a lock.
|
||||
// While having a lock, make sure the file still exists
|
||||
// It might delete it while we were waiting for a lock
|
||||
guard access(fileURL.path, F_OK) == 0 else {
|
||||
throw FileAccessorError.lockingFailure
|
||||
}
|
||||
|
||||
@@ -24,8 +24,15 @@ enum EnvironmentError: Error {
|
||||
}
|
||||
|
||||
extension Dictionary where Key == String, Value == String {
|
||||
func readEnv(key: String) throws -> URL {
|
||||
func readEnv(key: String) -> URL? {
|
||||
guard let value = self[key].map(URL.init(fileURLWithPath:)) else {
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func readEnv(key: String) throws -> URL {
|
||||
guard let value: URL = readEnv(key: key) else {
|
||||
throw EnvironmentError.missingEnv(key)
|
||||
}
|
||||
return value
|
||||
|
||||
@@ -21,7 +21,7 @@ import Foundation
|
||||
import XCRemoteCache
|
||||
|
||||
/// Wrapper for a `LD` program that copies the dynamic executable from a cached-downloaded location
|
||||
/// Fallbacks to a standard `clang` when the Ramote cache is not applicable (e.g. modified sources)
|
||||
/// Fallbacks to a standard `clang` when the Remote cache is not applicable (e.g. modified sources)
|
||||
public class XCLDMain {
|
||||
public func main() {
|
||||
let args = ProcessInfo().arguments
|
||||
@@ -48,7 +48,16 @@ public class XCLDMain {
|
||||
i += 1
|
||||
}
|
||||
guard let outputInput = output, let filelistInput = filelist, let dependencyInfoInput = dependencyInfo else {
|
||||
exit(1, "Missing 'output' argument. Args: \(args)")
|
||||
let ldCommand = "clang"
|
||||
print("Fallbacking to compilation using \(ldCommand).")
|
||||
|
||||
let args = ProcessInfo().arguments
|
||||
let paramList = [ldCommand] + args.dropFirst()
|
||||
let cargs = paramList.map { strdup($0) } + [nil]
|
||||
execvp(ldCommand, cargs)
|
||||
|
||||
/// C-function `execv` returns only when the command fails
|
||||
exit(1)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import Foundation
|
||||
import XCRemoteCache
|
||||
|
||||
/// Wrapper for a `LDPLUSPLUS` program that copies the dynamic executable from a cached-downloaded location
|
||||
/// Fallbacks to a standard `clang++` when the Remote cache is not applicable (e.g. modified sources)
|
||||
public class XCLDPlusPlusMain {
|
||||
public func main() {
|
||||
let args = ProcessInfo().arguments
|
||||
var output: String?
|
||||
var filelist: String?
|
||||
var dependencyInfo: String?
|
||||
var i = 0
|
||||
while i < args.count {
|
||||
switch args[i] {
|
||||
case "-o":
|
||||
output = args[i + 1]
|
||||
i += 1
|
||||
case "-filelist":
|
||||
filelist = args[i + 1]
|
||||
i += 1
|
||||
case "-dependency_info":
|
||||
// Skip following `-Xlinker` argument. Sample call:
|
||||
// `clang -dynamiclib ... -Xlinker -dependency_info -Xlinker /path/Target_dependency_info.dat`
|
||||
dependencyInfo = args[i + 2]
|
||||
i += 2
|
||||
default:
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
guard let outputInput = output, let filelistInput = filelist, let dependencyInfoInput = dependencyInfo else {
|
||||
let ldCommand = "clang++"
|
||||
print("Fallbacking to compilation using \(ldCommand).")
|
||||
|
||||
let args = ProcessInfo().arguments
|
||||
let paramList = [ldCommand] + args.dropFirst()
|
||||
let cargs = paramList.map { strdup($0) } + [nil]
|
||||
execvp(ldCommand, cargs)
|
||||
|
||||
/// C-function `execv` returns only when the command fails
|
||||
exit(1)
|
||||
}
|
||||
|
||||
|
||||
// TODO: consider using `clang_command` from .rcinfo
|
||||
/// concrete clang path should be taken from the current toolchain
|
||||
let fallbackCommand = "clang++"
|
||||
XCCreateBinary(
|
||||
output: outputInput,
|
||||
filelist: filelistInput,
|
||||
dependencyInfo: dependencyInfoInput,
|
||||
fallbackCommand: fallbackCommand,
|
||||
stepDescription: "xcldplusplus"
|
||||
).run()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import XCRemoteCache
|
||||
|
||||
XCLDPlusPlusMain().main()
|
||||
@@ -60,8 +60,16 @@ public class XCSwiftcMain {
|
||||
let targetInputInput = target,
|
||||
let swiftFileListInput = swiftFileList
|
||||
else {
|
||||
print("Missing argument. Args: \(args)")
|
||||
exit(1)
|
||||
let swiftcCommand = "swiftc"
|
||||
print("Fallbacking to compilation using \(swiftcCommand).")
|
||||
|
||||
let args = ProcessInfo().arguments
|
||||
let paramList = [swiftcCommand] + args.dropFirst()
|
||||
let cargs = paramList.map { strdup($0) } + [nil]
|
||||
execvp(swiftcCommand, cargs)
|
||||
|
||||
/// C-function `execv` returns only when the command fails
|
||||
exit(1)
|
||||
}
|
||||
let swiftcArgsInput = SwiftcArgInput(
|
||||
objcHeaderOutput: objcHeaderOutputInput,
|
||||
|
||||
@@ -131,7 +131,7 @@ class ThinningCreatorPluginTests: FileXCTestCase {
|
||||
|
||||
XCTAssertEqual(extraKeys, [
|
||||
"thinning_Generated": "000",
|
||||
"thinning_Reused": "999"
|
||||
"thinning_Reused": "999",
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ class PostbuildContextTests: FileXCTestCase {
|
||||
private static let SampleEnvs = [
|
||||
"TARGET_NAME": "TARGET_NAME",
|
||||
"TARGET_TEMP_DIR": "TARGET_TEMP_DIR",
|
||||
"DERIVED_FILE_DIR": "DERIVED_FILE_DIR",
|
||||
"ARCHS": "x86_64",
|
||||
"OBJECT_FILE_DIR_normal": "/OBJECT_FILE_DIR_normal" ,
|
||||
"CONFIGURATION": "CONFIGURATION",
|
||||
@@ -42,7 +43,7 @@ class PostbuildContextTests: FileXCTestCase {
|
||||
"DWARF_DSYM_FILE_NAME": "DWARF_DSYM_FILE_NAME",
|
||||
"BUILT_PRODUCTS_DIR": "BUILT_PRODUCTS_DIR",
|
||||
"DERIVED_SOURCES_DIR": "DERIVED_SOURCES_DIR",
|
||||
"CURRENT_VARIANT": "normal"
|
||||
"CURRENT_VARIANT": "normal",
|
||||
]
|
||||
|
||||
override func setUpWithError() throws {
|
||||
|
||||
@@ -20,12 +20,14 @@
|
||||
@testable import XCRemoteCache
|
||||
import XCTest
|
||||
|
||||
// swiftlint:disable file_length
|
||||
// swiftlint:disable:next type_body_length
|
||||
class PostbuildTests: FileXCTestCase {
|
||||
private var postbuildContext = PostbuildContext(
|
||||
mode: .producer,
|
||||
targetName: "",
|
||||
targetTempDir: "",
|
||||
derivedFilesDir: "",
|
||||
compilationTempDir: "",
|
||||
configuration: "",
|
||||
platform: "",
|
||||
@@ -52,7 +54,8 @@ class PostbuildTests: FileXCTestCase {
|
||||
thinnedTargets: [],
|
||||
action: .build,
|
||||
modeMarkerPath: "",
|
||||
overlayHeadersPath: ""
|
||||
overlayHeadersPath: "",
|
||||
irrelevantDependenciesPaths: []
|
||||
)
|
||||
private var network = RemoteNetworkClientImpl(
|
||||
NetworkClientFake(fileManager: .default),
|
||||
@@ -77,7 +80,9 @@ class PostbuildTests: FileXCTestCase {
|
||||
product: "/Product",
|
||||
source: "/Source",
|
||||
intermediate: "/Intermediate",
|
||||
bundle: nil
|
||||
derivedFiles: "/DerivedFiles",
|
||||
bundle: nil,
|
||||
skippedRegexes: []
|
||||
)
|
||||
private var overrideManager = FingerprintOverrideManagerImpl(
|
||||
overridingFileExtensions: ["swiftmodule"],
|
||||
@@ -639,4 +644,3 @@ class PostbuildTests: FileXCTestCase {
|
||||
XCTAssertEqual(downloadedMeta, expectedMeta)
|
||||
}
|
||||
}
|
||||
// swiftlint:disable:next file_length
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
@testable import XCRemoteCache
|
||||
import XCTest
|
||||
|
||||
class XcodeProjBuildSettingsIntegrateAppenderTests: XCTestCase {
|
||||
private let rootURL: URL = "/root"
|
||||
private let binariesDir: URL = "/binaries"
|
||||
private var buildSettings: BuildSettings!
|
||||
private var binaries: XCRCBinariesPaths!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
buildSettings = BuildSettings()
|
||||
binaries = XCRCBinariesPaths(
|
||||
prepare: binariesDir.appendingPathComponent("xcprepare"),
|
||||
cc: binariesDir.appendingPathComponent("xccc"),
|
||||
swiftc: binariesDir.appendingPathComponent("xcswiftc"),
|
||||
libtool: binariesDir.appendingPathComponent("xclibtool"),
|
||||
ld: binariesDir.appendingPathComponent("xcld"),
|
||||
ldplusplus: binariesDir.appendingPathComponent("xcldplusplus"),
|
||||
prebuild: binariesDir.appendingPathComponent("xcprebuild"),
|
||||
postbuild: binariesDir.appendingPathComponent("xcpostbuild")
|
||||
)
|
||||
}
|
||||
|
||||
func testProducerSettingFakeSrcRoot() throws {
|
||||
let mode: Mode = .producer
|
||||
let fakeRootURL: URL = "/xxxxxxxxxxP"
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(mode: mode, repoRoot: rootURL, fakeSrcRoot: fakeRootURL)
|
||||
let result = appender.appendToBuildSettings(buildSettings: buildSettings, wrappers: binaries)
|
||||
let resultURL = try XCTUnwrap(result["XCRC_FAKE_SRCROOT"] as? String)
|
||||
|
||||
XCTAssertEqual(resultURL, fakeRootURL.path)
|
||||
}
|
||||
|
||||
func testConsumerSettingFakeSrcRoot() throws {
|
||||
let mode: Mode = .consumer
|
||||
let fakeRootURL: URL = "/xxxxxxxxxxC"
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(mode: mode, repoRoot: rootURL, fakeSrcRoot: fakeRootURL)
|
||||
let result = appender.appendToBuildSettings(buildSettings: buildSettings, wrappers: binaries)
|
||||
let resultURL: String = try XCTUnwrap(result["XCRC_FAKE_SRCROOT"] as? String)
|
||||
|
||||
XCTAssertEqual(resultURL, fakeRootURL.path)
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,8 @@ class TemplateBasedCCWrapperBuilderTests: FileXCTestCase {
|
||||
let app = appDir.appendingPathComponent("xccc")
|
||||
try? fileManager.removeItem(at: appDir)
|
||||
try? FileManager.default.createDirectory(at: appDir, withIntermediateDirectories: true, attributes: nil)
|
||||
try? builder.compile(to: app, commitSha: commitSha)
|
||||
// swiftlint:disable:next force_try
|
||||
try! builder.compile(to: app, commitSha: commitSha)
|
||||
return app
|
||||
}()
|
||||
|
||||
@@ -332,11 +333,31 @@ class TemplateBasedCCWrapperBuilderTests: FileXCTestCase {
|
||||
XCTAssertNotEqual(newFileOutputData, Data())
|
||||
}
|
||||
|
||||
func testPCHCompilationFallbacks() throws {
|
||||
// Marker is empty to mimic the new file scenario
|
||||
let pchFile = directory.appendingPathComponent("input.pch")
|
||||
createValidPCHFile(pchFile)
|
||||
arguments = ["-x", "objective-c-header", "-MF", dependencyFile.path, "-o", outputFile.path, pchFile.path]
|
||||
|
||||
try shellExec(Self.xccc.path, args: arguments, inDir: directory.path)
|
||||
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: outputFile.path))
|
||||
}
|
||||
|
||||
/// Creates a simple C code in the location
|
||||
private func createValidCFile(_ location: URL) {
|
||||
fileManager.createFile(atPath: location.path, contents: "int main(){}".data(using: .utf8), attributes: nil)
|
||||
}
|
||||
|
||||
/// Creates a simple PCH code in the location
|
||||
private func createValidPCHFile(_ location: URL) {
|
||||
fileManager.createFile(
|
||||
atPath: location.path,
|
||||
contents: "#import <Availability.h>".data(using: .utf8),
|
||||
attributes: nil
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a C code that requires extra CUSTOM_STR clang macro to compile
|
||||
private func createInvalidCFile(_ location: URL) {
|
||||
fileManager.createFile(
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2022 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
@testable import XCRemoteCache
|
||||
import XCTest
|
||||
|
||||
class XCRemoteCacheConfigReaderTests: XCTestCase {
|
||||
|
||||
private var fileReader: FileAccessorFake!
|
||||
private var reader: XCRemoteCacheConfigReader!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
fileReader = FileAccessorFake(mode: .normal)
|
||||
reader = XCRemoteCacheConfigReader(srcRootPath: "/", fileReader: fileReader)
|
||||
}
|
||||
|
||||
func testReadsFromExtraConfig() throws {
|
||||
try fileReader.write(toPath: "/.rcinfo", contents: "cache_addresses: [test]")
|
||||
|
||||
let config = try reader.readConfiguration()
|
||||
|
||||
XCTAssertEqual(config.cacheAddresses, ["test"])
|
||||
}
|
||||
|
||||
func testOverridesExtraConfigFromExtraFile() throws {
|
||||
try fileReader.write(toPath: "/.rcinfo", contents: "cache_addresses: [test]")
|
||||
try fileReader.write(toPath: "/user.rcinfo", contents: "cache_addresses: [user]")
|
||||
|
||||
let config = try reader.readConfiguration()
|
||||
|
||||
XCTAssertEqual(config.cacheAddresses, ["user"])
|
||||
}
|
||||
|
||||
func testReadsExtraConfigMultipleTimes() throws {
|
||||
try fileReader.write(toPath: "/.rcinfo", contents: "cache_addresses: [test]")
|
||||
try fileReader.write(toPath: "/user.rcinfo", contents: """
|
||||
cache_addresses: [user]
|
||||
extra_configuration_file: user2.rcinfo
|
||||
""")
|
||||
try fileReader.write(toPath: "/user2.rcinfo", contents: "cache_addresses: [user2]")
|
||||
|
||||
let config = try reader.readConfiguration()
|
||||
|
||||
XCTAssertEqual(config.cacheAddresses, ["user2"])
|
||||
}
|
||||
|
||||
func testBreaksImportingExtraConfigIfReachingALoop() throws {
|
||||
try fileReader.write(toPath: "/.rcinfo", contents: "cache_addresses: [test]")
|
||||
try fileReader.write(toPath: "/user.rcinfo", contents: """
|
||||
cache_addresses: [user]
|
||||
extra_configuration_file: .rcinfo
|
||||
""")
|
||||
|
||||
let config = try reader.readConfiguration()
|
||||
|
||||
XCTAssertEqual(config.cacheAddresses, ["user"])
|
||||
}
|
||||
|
||||
func testBreaksImportingExtraConfigIfFileDoesntExist() throws {
|
||||
try fileReader.write(toPath: "/.rcinfo", contents: "cache_addresses: [test]")
|
||||
try fileReader.write(toPath: "/user.rcinfo", contents: """
|
||||
cache_addresses: [user]
|
||||
extra_configuration_file: nonexisting.rcinfo
|
||||
""")
|
||||
|
||||
let config = try reader.readConfiguration()
|
||||
|
||||
XCTAssertEqual(config.cacheAddresses, ["user"])
|
||||
XCTAssertEqual(config.extraConfigurationFile, "nonexisting.rcinfo")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
@testable import XCRemoteCache
|
||||
|
||||
import XCTest
|
||||
|
||||
class DependenciesReaderPerformanceTest: XCTestCase {
|
||||
|
||||
private static let resourcesSubdirectory = "TestData/Dependencies/DependenciesReaderPerformanceTest"
|
||||
|
||||
private func pathForTestData(name: String) throws -> URL {
|
||||
return try XCTUnwrap(Bundle.module.url(
|
||||
forResource: name,
|
||||
withExtension: "d",
|
||||
subdirectory: DependenciesReaderPerformanceTest.resourcesSubdirectory
|
||||
))
|
||||
}
|
||||
|
||||
func testFindDependenciesPerformance() throws {
|
||||
let file = try pathForTestData(name: "dependencies")
|
||||
let reader = FileDependenciesReader(file, accessor: FileManager.default)
|
||||
|
||||
self.measure { // 0.005
|
||||
do {
|
||||
_ = try reader.findDependencies()
|
||||
} catch {
|
||||
print("Error reading dependencies")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testReadRawFilePerformance() throws {
|
||||
let file = try pathForTestData(name: "dependencies")
|
||||
let reader = FileDependenciesReader(file, accessor: FileManager.default)
|
||||
|
||||
self.measure { // 0.002
|
||||
do {
|
||||
_ = try reader.readRaw()
|
||||
} catch {
|
||||
print("Error reading dependencies")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testGetFileDataPerformance() throws {
|
||||
let file = try pathForTestData(name: "dependencies")
|
||||
let reader = FileDependenciesReader(file, accessor: FileManager.default)
|
||||
|
||||
self.measure { // 0.00008
|
||||
do {
|
||||
_ = try reader.getFileData()
|
||||
} catch {
|
||||
print("Error reading dependencies")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testGetFileStringPerformance() throws {
|
||||
let file = try pathForTestData(name: "dependencies")
|
||||
let reader = FileDependenciesReader(file, accessor: FileManager.default)
|
||||
let fileData = try reader.getFileData()
|
||||
|
||||
self.measure { // 0.00002
|
||||
do {
|
||||
_ = try reader.getFileStringFromData(fileData: fileData)
|
||||
} catch {
|
||||
print("Error reading dependencies")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testGetYamlPerformance() throws { // 0.222
|
||||
let file = try pathForTestData(name: "dependencies")
|
||||
let reader = FileDependenciesReader(file, accessor: FileManager.default)
|
||||
let fileData = try reader.getFileData()
|
||||
let fileString = try reader.getFileStringFromData(fileData: fileData)
|
||||
|
||||
self.measure { // 0.0022
|
||||
do {
|
||||
_ = try reader.getYaml(fileString: fileString)
|
||||
} catch {
|
||||
print("Error reading dependencies")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testParseDependencyFileListUsingUTF8View() throws {
|
||||
let file = try pathForTestData(name: "dependencies")
|
||||
let reader = FileDependenciesReader(file, accessor: FileManager.default)
|
||||
let fileData = try reader.getFileData()
|
||||
let fileString = try reader.getFileStringFromData(fileData: fileData)
|
||||
let yaml = try reader.getYaml(fileString: fileString)
|
||||
|
||||
guard let dependencies = yaml["dependencies"] else {
|
||||
XCTAssertTrue(false)
|
||||
return
|
||||
}
|
||||
|
||||
self.measure { // 0.004
|
||||
let deps = reader.parseDependencyFileList(dependencies)
|
||||
XCTAssertTrue(deps.count == 1000)
|
||||
}
|
||||
}
|
||||
|
||||
func testDeprecatedParseDependenciesFilesListOfAnObjectUsingUTF8View() throws {
|
||||
let file = try pathForTestData(name: "dependencies")
|
||||
let reader = FileDependenciesReader(file, accessor: FileManager.default)
|
||||
let fileData = try reader.getFileData()
|
||||
let fileString = try reader.getFileStringFromData(fileData: fileData)
|
||||
let yaml = try reader.getYaml(fileString: fileString)
|
||||
|
||||
guard let dependencies = yaml["/This/Is/A/Path/To/Some/Object/objectfile.o"] else {
|
||||
XCTAssertTrue(false)
|
||||
return
|
||||
}
|
||||
|
||||
self.measure { // 0.00048
|
||||
let deps = reader.parseDependencyFileList(dependencies)
|
||||
XCTAssertTrue(deps.count == 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,8 +81,18 @@ class DependenciesRemapperCompositeTests: XCTestCase {
|
||||
|
||||
func testRemapsMultipleMatchingMappers() throws {
|
||||
let remapper = DependenciesRemapperComposite([
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(ROOT)", local: "/root")]),
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(SPECIFIC)", local: "$(ROOT)/specific")])
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(ROOT)",
|
||||
local: "/root"
|
||||
),
|
||||
]),
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(SPECIFIC)",
|
||||
local: "$(ROOT)/specific"
|
||||
),
|
||||
]),
|
||||
])
|
||||
let localPaths = ["/root/specific/file"]
|
||||
|
||||
@@ -93,8 +103,18 @@ class DependenciesRemapperCompositeTests: XCTestCase {
|
||||
|
||||
func testRemapsBackToLocalWithRevertedRemappersOrder() throws {
|
||||
let remapper = DependenciesRemapperComposite([
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(ROOT)", local: "/root")]),
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(SPECIFIC)", local: "$(ROOT)/specific")])
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(ROOT)",
|
||||
local: "/root"
|
||||
),
|
||||
]),
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(SPECIFIC)",
|
||||
local: "$(ROOT)/specific"
|
||||
),
|
||||
]),
|
||||
])
|
||||
let genericPaths = ["$(SPECIFIC)/file"]
|
||||
|
||||
@@ -105,8 +125,18 @@ class DependenciesRemapperCompositeTests: XCTestCase {
|
||||
|
||||
func testRemappingTwoMappingsBackAndForthIsIdentical() throws {
|
||||
let remapper = DependenciesRemapperComposite([
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(ROOT)", local: "/root")]),
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(SPECIFIC)", local: "$(ROOT)/specific")])
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(ROOT)",
|
||||
local: "/root"
|
||||
),
|
||||
]),
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(SPECIFIC)",
|
||||
local: "$(ROOT)/specific"
|
||||
),
|
||||
]),
|
||||
])
|
||||
let localPaths = ["/root/specific/file"]
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
@testable import XCRemoteCache
|
||||
import XCTest
|
||||
|
||||
class FileDependenciesWriterTests: XCTestCase {
|
||||
|
||||
private func generateTempFileURL(name: String = #function) throws -> URL {
|
||||
let directory = NSTemporaryDirectory()
|
||||
return try NSURL.fileURL(withPathComponents: [directory, name]).unwrap()
|
||||
}
|
||||
|
||||
func testWriteDependencyWithSpace() throws {
|
||||
let url = try generateTempFileURL()
|
||||
let writer = FileDependenciesWriter(url, accessor: FileManager.default)
|
||||
|
||||
try writer.writeGeneric(dependencies: [
|
||||
"/SomePath/Pods/Target Support Files/lottie-ios/lottie-ios-dummy.m",
|
||||
])
|
||||
|
||||
let expectedContent = """
|
||||
dependencies: /SomePath/Pods/Target\\ Support\\ Files/lottie-ios/lottie-ios-dummy.m
|
||||
|
||||
"""
|
||||
|
||||
let content = String(data: try Data(contentsOf: url), encoding: .utf8)
|
||||
|
||||
XCTAssertEqual(content, expectedContent)
|
||||
}
|
||||
}
|
||||
@@ -27,17 +27,21 @@ class DependencyProcessorImplTests: FileXCTestCase {
|
||||
product: "/Product",
|
||||
source: "/Source",
|
||||
intermediate: "/Intermediate",
|
||||
bundle: "/Bundle"
|
||||
derivedFiles: "/DerivedFiles",
|
||||
bundle: "/Bundle",
|
||||
skippedRegexes: []
|
||||
)
|
||||
|
||||
func testIntermediateFileIsSkippedForProductAndSourceSubdirectory() {
|
||||
func testIntermediateFileIsskippedRegexesForProductAndSourceSubdirectory() {
|
||||
let intermediateFile: URL = "/Intermediate/some"
|
||||
let processor = DependencyProcessorImpl(
|
||||
xcode: "/Xcode",
|
||||
product: "/",
|
||||
source: "/",
|
||||
intermediate: "/Intermediate",
|
||||
bundle: nil
|
||||
derivedFiles: "/DerivedFiles",
|
||||
bundle: nil,
|
||||
skippedRegexes: []
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
@@ -46,14 +50,16 @@ class DependencyProcessorImplTests: FileXCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
func testBundleFileIsSkippedForProductAndSourceSubdirectory() {
|
||||
func testBundleFileIsskippedRegexesForProductAndSourceSubdirectory() {
|
||||
let bundleFile: URL = "/Bundle/some"
|
||||
let processor = DependencyProcessorImpl(
|
||||
xcode: "/Xcode",
|
||||
product: "/",
|
||||
source: "/",
|
||||
intermediate: "/Intermediate",
|
||||
bundle: "/Bundle"
|
||||
derivedFiles: "/DerivedFiles",
|
||||
bundle: "/Bundle",
|
||||
skippedRegexes: []
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
@@ -62,6 +68,22 @@ class DependencyProcessorImplTests: FileXCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
func testFiltersOutGeneratedSwiftHeaders() throws {
|
||||
let dependencies = processor.process([
|
||||
"/DerivedFiles/ModuleName-Swift.h",
|
||||
])
|
||||
|
||||
XCTAssertEqual(dependencies, [])
|
||||
}
|
||||
|
||||
func testFiltersOutDerivedFile() throws {
|
||||
let dependencies = processor.process([
|
||||
"/DerivedFiles/output.h",
|
||||
])
|
||||
|
||||
XCTAssertEqual(dependencies, [])
|
||||
}
|
||||
|
||||
func testFiltersOutProductModulemap() throws {
|
||||
let dependencies = processor.process([
|
||||
"/Product/some.modulemap",
|
||||
@@ -130,13 +152,19 @@ class DependencyProcessorImplTests: FileXCTestCase {
|
||||
product: "/Product",
|
||||
source: "/Source",
|
||||
intermediate: intermediateDirReal,
|
||||
bundle: "/Bundle"
|
||||
derivedFiles: "/DerivedFiles",
|
||||
bundle: "/Bundle",
|
||||
skippedRegexes: []
|
||||
)
|
||||
|
||||
let intermediateFileSymlink = createSymlink(filename: someFilename, sourceDir: symlink, destinationDir: intermediateDirReal)
|
||||
let intermediateFileSymlink = createSymlink(
|
||||
filename: someFilename,
|
||||
sourceDir: symlink,
|
||||
destinationDir: intermediateDirReal
|
||||
)
|
||||
|
||||
let dependencies = processor.process([
|
||||
intermediateFileSymlink
|
||||
intermediateFileSymlink,
|
||||
])
|
||||
|
||||
XCTAssertEqual(dependencies, [])
|
||||
@@ -154,13 +182,19 @@ class DependencyProcessorImplTests: FileXCTestCase {
|
||||
product: "/Product",
|
||||
source: sourceDirReal,
|
||||
intermediate: "/Intermediate",
|
||||
bundle: "/Bundle"
|
||||
derivedFiles: "/DerivedFiles",
|
||||
bundle: "/Bundle",
|
||||
skippedRegexes: []
|
||||
)
|
||||
|
||||
let sourceFileSymlink = createSymlink(filename: someFilename, sourceDir: symlink, destinationDir: sourceDirReal)
|
||||
let sourceFileSymlink = createSymlink(
|
||||
filename: someFilename,
|
||||
sourceDir: symlink,
|
||||
destinationDir: sourceDirReal
|
||||
)
|
||||
|
||||
let dependencies = processor.process([
|
||||
sourceFileSymlink
|
||||
sourceFileSymlink,
|
||||
])
|
||||
|
||||
XCTAssertEqual(dependencies, [.init(url: sourceFileSymlink, type: .source)])
|
||||
@@ -173,10 +207,84 @@ class DependencyProcessorImplTests: FileXCTestCase {
|
||||
fileprivate func createSymlink(filename: String, sourceDir: URL, destinationDir: URL) -> URL {
|
||||
let fileMng = FileManager.default
|
||||
|
||||
XCTAssertNoThrow(try fileMng.spt_forceSymbolicLink(at: sourceDir,
|
||||
withDestinationURL: destinationDir))
|
||||
XCTAssertNoThrow(try fileMng.spt_forceSymbolicLink(
|
||||
at: sourceDir,
|
||||
withDestinationURL: destinationDir
|
||||
))
|
||||
XCTAssertNoThrow(try fileMng.spt_createEmptyFile(destinationDir.appendingPathComponent(filename)))
|
||||
|
||||
return sourceDir.appendingPathComponent(filename)
|
||||
}
|
||||
|
||||
func testSkipsCustomizedDerivedDirFileUnderSources() {
|
||||
let derivedFile: URL = "/DerivedFiles/Module-Swift.h"
|
||||
let processor = DependencyProcessorImpl(
|
||||
xcode: "/Xcode",
|
||||
product: "/",
|
||||
source: "/",
|
||||
intermediate: "/Intermediate",
|
||||
derivedFiles: "/DerivedFiles",
|
||||
bundle: nil,
|
||||
skippedRegexes: []
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
processor.process([derivedFile]),
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
func testSkippsFilesWithFullMatch() {
|
||||
let source: URL = "/someFile.m"
|
||||
let processor = DependencyProcessorImpl(
|
||||
xcode: "/Xcode",
|
||||
product: "/",
|
||||
source: "/",
|
||||
intermediate: "/Intermediate",
|
||||
derivedFiles: "/DerivedFiles",
|
||||
bundle: nil,
|
||||
skippedRegexes: ["/someFile\\.m"]
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
processor.process([source]),
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
func testSkippsFilesWithPartialMatch() {
|
||||
let derivedModulemap: URL = "/module.modulemap"
|
||||
let processor = DependencyProcessorImpl(
|
||||
xcode: "/Xcode",
|
||||
product: "/product",
|
||||
source: "/",
|
||||
intermediate: "/Intermediate",
|
||||
derivedFiles: "/DerivedFiles",
|
||||
bundle: nil,
|
||||
skippedRegexes: ["\\.modulemap$"]
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
processor.process([derivedModulemap]),
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
func testDoesntSkipFileIfInvalidRegex() {
|
||||
let source: URL = "/someFile.m"
|
||||
let processor = DependencyProcessorImpl(
|
||||
xcode: "/Xcode",
|
||||
product: "/product",
|
||||
source: "/",
|
||||
intermediate: "/Intermediate",
|
||||
derivedFiles: "/DerivedFiles",
|
||||
bundle: nil,
|
||||
skippedRegexes: ["\\"]
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
processor.process([source]),
|
||||
[.init(url: source, type: .source)]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,20 +40,20 @@ class OverlayDependenciesRemapperTests: XCTestCase {
|
||||
XCTAssertEqual(dependencies, ["/Intermediate/Some/file.h"])
|
||||
}
|
||||
|
||||
func testGenericDependenciesAreMerged() throws {
|
||||
func testGenericDependenciesAreNotMerged() throws {
|
||||
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
|
||||
|
||||
|
||||
let dependencies = try remapper.replace(localPaths: ["/Intermediate/Some/file.h", "/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/file.h", "/file.h"])
|
||||
}
|
||||
|
||||
func testLocalDependenciesAreMerged() throws {
|
||||
func testLocalDependenciesAreNotMerged() throws {
|
||||
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
|
||||
|
||||
|
||||
let dependencies = try remapper.replace(genericPaths: ["/Intermediate/Some/file.h", "/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/Intermediate/Some/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/Intermediate/Some/file.h", "/Intermediate/Some/file.h"])
|
||||
}
|
||||
|
||||
func testMappingsAreReadOnce() throws {
|
||||
@@ -68,4 +68,3 @@ class OverlayDependenciesRemapperTests: XCTestCase {
|
||||
XCTAssertEqual(secondDependencies, ["/file.h"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,14 @@ class JsonOverlayReaderTests: FileXCTestCase {
|
||||
let mappings = try reader.provideMappings()
|
||||
|
||||
let expectedMappings = [
|
||||
OverlayMapping(virtual: "/DerivedDataProducts/Target1.framework/Headers/Target1.h", local: "/Path/Target1/Target1.h"),
|
||||
OverlayMapping(virtual: "/DerivedDataProducts/Target2.framework/Modules/module.modulemap", local: "/DerivedDataIntermediate/Target2.build/module.modulemap")
|
||||
OverlayMapping(
|
||||
virtual: "/DerivedDataProducts/Target1.framework/Headers/Target1.h",
|
||||
local: "/Path/Target1/Target1.h"
|
||||
),
|
||||
OverlayMapping(
|
||||
virtual: "/DerivedDataProducts/Target2.framework/Modules/module.modulemap",
|
||||
local: "/DerivedDataIntermediate/Target2.build/module.modulemap"
|
||||
),
|
||||
]
|
||||
XCTAssertEqual(Set(mappings), Set(expectedMappings))
|
||||
}
|
||||
@@ -79,6 +85,10 @@ class JsonOverlayReaderTests: FileXCTestCase {
|
||||
}
|
||||
|
||||
private func pathForTestData(name: String) throws -> URL {
|
||||
return try XCTUnwrap(Bundle.module.url(forResource: name, withExtension: "json", subdirectory: JsonOverlayReaderTests.resourcesSubdirectory))
|
||||
return try XCTUnwrap(Bundle.module.url(
|
||||
forResource: name,
|
||||
withExtension: "json",
|
||||
subdirectory: JsonOverlayReaderTests.resourcesSubdirectory
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
+28
-5
@@ -20,11 +20,12 @@
|
||||
@testable import XCRemoteCache
|
||||
import XCTest
|
||||
|
||||
class StringDependenciesRemapperFactoryTests: XCTestCase {
|
||||
private var factory: StringDependenciesRemapperFactory!
|
||||
class PathDependenciesRemapperFactoryTests: XCTestCase {
|
||||
private var factory: PathDependenciesRemapperFactory!
|
||||
|
||||
override func setUp() {
|
||||
factory = StringDependenciesRemapperFactory()
|
||||
super.setUp()
|
||||
factory = PathDependenciesRemapperFactory()
|
||||
}
|
||||
|
||||
func testMappingsFromEnvMaps() throws {
|
||||
@@ -38,8 +39,30 @@ class StringDependenciesRemapperFactoryTests: XCTestCase {
|
||||
XCTAssertEqual(localPaths, ["/tmp/root/some.swift"])
|
||||
}
|
||||
|
||||
func testInvalidMappingsFromEnvFails() throws {
|
||||
XCTAssertThrowsError(
|
||||
func testMappingsGenericWhenMappingHasParentDir() throws {
|
||||
let remapper = try factory.build(
|
||||
orderKeys: ["SRC_ROOT"],
|
||||
envs: ["SRC_ROOT": "/tmp/root/extra/.."],
|
||||
customMappings: [:]
|
||||
)
|
||||
|
||||
let localPaths = try remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
|
||||
XCTAssertEqual(localPaths, ["/tmp/root/some.swift"])
|
||||
}
|
||||
|
||||
func testMappingsLocalWhenMappingHasParentDir() throws {
|
||||
let remapper = try factory.build(
|
||||
orderKeys: ["SRC_ROOT"],
|
||||
envs: ["SRC_ROOT": "/tmp/root/excessive/.."],
|
||||
customMappings: [:]
|
||||
)
|
||||
|
||||
let localPaths = try remapper.replace(localPaths: ["/tmp/root/some.swift"])
|
||||
XCTAssertEqual(localPaths, ["$(SRC_ROOT)/some.swift"])
|
||||
}
|
||||
|
||||
func testMissingEnvIsSkipped() throws {
|
||||
XCTAssertNoThrow(
|
||||
try factory.build(
|
||||
orderKeys: ["SRC_ROOT"],
|
||||
envs: ["NO_SRC_ROOT": ""],
|
||||
@@ -33,19 +33,23 @@ class EnvironmentFingerprintGeneratorTests: XCTestCase {
|
||||
"DYLIB_COMPATIBILITY_VERSION": "2",
|
||||
"DYLIB_CURRENT_VERSION": "3",
|
||||
"PRODUCT_MODULE_NAME": "4",
|
||||
"ARCHS": "AR"
|
||||
"ARCHS": "AR",
|
||||
]
|
||||
/// Corresponds to EnvironmentFingerprintGenerator.version
|
||||
private static let currentVersion = "5"
|
||||
|
||||
private var config: XCRemoteCacheConfig!
|
||||
private var generator: FingerprintAccumulator!
|
||||
private var generator: FingerprintAccumulator! {
|
||||
return generatorFake
|
||||
}
|
||||
|
||||
private var generatorFake: FingerprintAccumulatorFake!
|
||||
private var fingerprintGenerator: EnvironmentFingerprintGenerator!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
config = XCRemoteCacheConfig(sourceRoot: "")
|
||||
generator = FingerprintAccumulatorFake()
|
||||
generatorFake = FingerprintAccumulatorFake()
|
||||
fingerprintGenerator = EnvironmentFingerprintGenerator(
|
||||
configuration: config,
|
||||
env: Self.defaultENV,
|
||||
@@ -92,4 +96,11 @@ class EnvironmentFingerprintGeneratorTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(fingerprint, "GCC,YES,TARGET,CONG,PLAT,XC,1,2,3,4,AR,CUSTOM_VALUE,\(Self.currentVersion)")
|
||||
}
|
||||
|
||||
func testFingerprintIsGeneratedOnce() throws {
|
||||
let fingerprint1 = try fingerprintGenerator.generateFingerprint()
|
||||
let fingerprint2 = try fingerprintGenerator.generateFingerprint()
|
||||
XCTAssertEqual(fingerprint1, fingerprint2)
|
||||
XCTAssertEqual(generatorFake.generateCallsCount, 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,12 @@ class AWSV4SignatureTest: XCTestCase {
|
||||
|
||||
let key = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"
|
||||
let accessKey = "AKIDEXAMPLE"
|
||||
let securityToken = "IQoJb3JpZ2luX2VjENv//////////wEaCXVzLWVhc3Q+bsHwqnovXtl/1JVe61XHMnAw3AIXwOAOxqMvhw=="
|
||||
|
||||
AWSV4Signature(
|
||||
secretKey: key,
|
||||
accessKey: accessKey,
|
||||
securityToken: securityToken,
|
||||
region: "us-east-1",
|
||||
service: "iam",
|
||||
date: Date(timeIntervalSince1970: 1_440_938_160)
|
||||
@@ -49,8 +51,8 @@ class AWSV4SignatureTest: XCTestCase {
|
||||
func testAuthHeaderContainsCorrectSignature() throws {
|
||||
XCTAssertEqual(
|
||||
"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, " +
|
||||
"SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, " +
|
||||
"Signature=dd479fa8a80364edf2119ec24bebde66712ee9c9cb2b0d92eb3ab9ccdc0c3947",
|
||||
"SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-amz-security-token, " +
|
||||
"Signature=d26ab974bba1b248f041ea1120064e1fa672d6f06cac2cff42b38acea87b76e5",
|
||||
request.allHTTPHeaderFields?["Authorization"]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -72,7 +72,13 @@ class NetworkClientImplTests: XCTestCase {
|
||||
configuration.protocolClasses = [URLProtocolStub.self]
|
||||
session = URLSession(configuration: configuration)
|
||||
fileManager = FileManager.default
|
||||
client = NetworkClientImpl(session: session, retries: 0, fileManager: fileManager, awsV4Signature: nil)
|
||||
client = NetworkClientImpl(
|
||||
session: session,
|
||||
retries: 0,
|
||||
retryDelay: 0,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: nil
|
||||
)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -87,7 +93,7 @@ class NetworkClientImplTests: XCTestCase {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func waitForResponse<R>(_ action: (@escaping Completion<R>) -> Void) throws -> Result<R, NetworkClientError> {
|
||||
func waitForResponse<R>(_ action: (@escaping Completion<R>) -> Void, timeout: TimeInterval = 0.1) throws -> Result<R, NetworkClientError> {
|
||||
let responseExpectation = expectation(description: "RequestResponse")
|
||||
var receivedResponse: Result<R, NetworkClientError>?
|
||||
|
||||
@@ -95,7 +101,7 @@ class NetworkClientImplTests: XCTestCase {
|
||||
receivedResponse = response
|
||||
responseExpectation.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 0.1)
|
||||
waitForExpectations(timeout: timeout)
|
||||
return try receivedResponse.unwrap()
|
||||
}
|
||||
|
||||
@@ -141,9 +147,15 @@ class NetworkClientImplTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testUploadFilureWith400Retries() throws {
|
||||
client = NetworkClientImpl(session: session, retries: 2, fileManager: fileManager, awsV4Signature: nil)
|
||||
client = NetworkClientImpl(
|
||||
session: session,
|
||||
retries: 2,
|
||||
retryDelay: 0,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: nil
|
||||
)
|
||||
responses[url] = .success(failureResponse, Data())
|
||||
_ = try waitForResponse { client.upload(fileURL, as: url, completion: $0) }
|
||||
_ = try waitForResponse({ client.upload(fileURL, as: url, completion: $0) }, timeout: 0.5)
|
||||
|
||||
XCTAssertEqual(
|
||||
requests.map(\.url),
|
||||
@@ -153,7 +165,13 @@ class NetworkClientImplTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testUploadSuccessDoesntRetry() throws {
|
||||
client = NetworkClientImpl(session: session, retries: 0, fileManager: fileManager, awsV4Signature: nil)
|
||||
client = NetworkClientImpl(
|
||||
session: session,
|
||||
retries: 0,
|
||||
retryDelay: 0,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: nil
|
||||
)
|
||||
responses[url] = .success(successResponse, Data())
|
||||
_ = try waitForResponse { client.upload(fileURL, as: url, completion: $0) }
|
||||
|
||||
@@ -203,18 +221,25 @@ class NetworkClientImplTests: XCTestCase {
|
||||
let signature = AWSV4Signature(
|
||||
secretKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
||||
accessKey: "AKIDEXAMPLE",
|
||||
securityToken: "IQoJb3JpZ2luX2VjENv//////////wEaCXVzLWVhc3Q+bsHwqnovXtl/1JVe61XHMnAw3AIXwOAOxqMvhw==",
|
||||
region: "us-east-1",
|
||||
service: "iam",
|
||||
date: Date(timeIntervalSince1970: 1_440_938_160)
|
||||
)
|
||||
client = NetworkClientImpl(session: session, retries: 0, fileManager: fileManager, awsV4Signature: signature)
|
||||
client = NetworkClientImpl(
|
||||
session: session,
|
||||
retries: 0,
|
||||
retryDelay: 0,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: signature
|
||||
)
|
||||
responses[url] = .success(successResponse, Data())
|
||||
_ = try waitForResponse { client.fetch(url, completion: $0) }
|
||||
|
||||
XCTAssertEqual(
|
||||
"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, " +
|
||||
"SignedHeaders=host;x-amz-content-sha256;x-amz-date, " +
|
||||
"Signature=acdb223475463b6ce711b063f199387a7a12bd638e4dccaaeb5efcbf159b6454",
|
||||
"SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token, " +
|
||||
"Signature=e5578464567fb97fd26e871702e4ec4ff7d61cb87eb72a40d22b80e12da30c34",
|
||||
try requests[0].allHTTPHeaderFields?["Authorization"].unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
+1002
File diff suppressed because one or more lines are too long
@@ -34,5 +34,11 @@ class FingerprintAccumulatorFake: FingerprintAccumulator {
|
||||
appendedStrings.append("FILE{\(file.path)}")
|
||||
}
|
||||
|
||||
func generate() throws -> RawFingerprint { return appendedStrings.joined(separator: ",") }
|
||||
private(set) var generateCallsCount = 0
|
||||
func generate() throws -> RawFingerprint {
|
||||
defer {
|
||||
generateCallsCount += 1
|
||||
}
|
||||
return appendedStrings.joined(separator: ",")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import Foundation
|
||||
|
||||
class OverlayReaderFake: OverlayReader {
|
||||
var mappings: [OverlayMapping]
|
||||
|
||||
|
||||
init(mappings: [OverlayMapping]) {
|
||||
self.mappings = mappings
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ An object that is passed to the `xcremotecache` can contain all properties suppo
|
||||
| `xccc_file` | The path where should be placed the `xccc` binary (in the pod installation phase) | `{podfile_dir}/.rc/xccc` | ⬜️ |
|
||||
| `remote_commit_file` | The path of the file with the remote commit sha (in the pod installation phase) | `{podfile_dir}/.rc/arc.rc`| ⬜️ |
|
||||
| `prettify_meta_files` | A Boolean value that opts-in pretty JSON formatting for meta files | `false` | ⬜️ |
|
||||
| `fake_src_root` | An arbitrary source location shared between producers and consumers. Should be unique for a project. | `/xxxxxxxxxx` | ⬜️ |
|
||||
| `disable_certificate_verification` | A Boolean value that opts-in SSL certificate validation is disabled | `false` | ⬜️ |
|
||||
|
||||
## Uninstalling
|
||||
@@ -62,7 +63,3 @@ To fully uninstall the plugin, call:
|
||||
```bash
|
||||
gem uninstall cocoapods-xcremotecache
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
* When `generate_multiple_pod_projects` mode is enabled, only first-party targets are cached by XCRemoteCache (all dependencies are compiled locally).
|
||||
|
||||
@@ -24,12 +24,12 @@ module CocoapodsXCRemoteCacheModifier
|
||||
# Registers for CocoaPods plugin hooks
|
||||
module Hooks
|
||||
BIN_DIR = '.rc'
|
||||
FAKE_SRCROOT = "/#{'x' * 10 }"
|
||||
LLDB_INIT_COMMENT="#RemoteCacheCustomSourceMap"
|
||||
LLDB_INIT_PATH = "#{ENV['HOME']}/.lldbinit"
|
||||
FAT_ARCHIVE_NAME_INFIX = 'arm64-x86_64'
|
||||
XCRC_COOCAPODS_ROOT_KEY = 'XCRC_COOCAPODS_ROOT'
|
||||
|
||||
# List of plugins' user properties that should be copied to .rcinfo
|
||||
# List of plugins' user properties that should not be copied to .rcinfo
|
||||
CUSTOM_CONFIGURATION_KEYS = [
|
||||
'enabled',
|
||||
'xcrc_location',
|
||||
@@ -39,8 +39,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
'check_build_configuration',
|
||||
'check_platform',
|
||||
'modify_lldb_init',
|
||||
'prettify_meta_files',
|
||||
'disable_certificate_verification'
|
||||
'fake_src_root',
|
||||
]
|
||||
|
||||
class XCRemoteCache
|
||||
@@ -63,9 +62,15 @@ module CocoapodsXCRemoteCacheModifier
|
||||
'remote_commit_file' => "#{BIN_DIR}/arc.rc",
|
||||
'exclude_targets' => [],
|
||||
'prettify_meta_files' => false,
|
||||
'disable_certificate_verification' => false
|
||||
'fake_src_root' => "/#{'x' * 10 }",
|
||||
'disable_certificate_verification' => false,
|
||||
'custom_rewrite_envs' => []
|
||||
}
|
||||
@@configuration.merge! default_values.select { |k, v| !@@configuration.key?(k) }
|
||||
# Always include XCRC_COOCAPODS_ROOT_KEY in custom_rewrite_envs
|
||||
unless @@configuration['custom_rewrite_envs'].include?(XCRC_COOCAPODS_ROOT_KEY)
|
||||
@@configuration['custom_rewrite_envs'] << XCRC_COOCAPODS_ROOT_KEY
|
||||
end
|
||||
end
|
||||
|
||||
def self.validate_configuration()
|
||||
@@ -106,8 +111,10 @@ module CocoapodsXCRemoteCacheModifier
|
||||
# @param mode [String] mode name ('consumer', 'producer', 'producer-fast' etc.)
|
||||
# @param exclude_build_configurations [String[]] list of targets that should have disabled remote cache
|
||||
# @param final_target [String] name of target that should trigger marking
|
||||
def self.enable_xcremotecache(target, repo_distance, xc_location, xc_cc_path, mode, exclude_build_configurations, final_target)
|
||||
def self.enable_xcremotecache(target, repo_distance, xc_location, xc_cc_path, mode, exclude_build_configurations, final_target, fake_src_root)
|
||||
srcroot_relative_xc_location = parent_dir(xc_location, repo_distance)
|
||||
# location of the entrite CocoaPods project, relative to SRCROOT
|
||||
srcroot_relative_project_location = parent_dir('', repo_distance)
|
||||
|
||||
target.build_configurations.each do |config|
|
||||
# apply only for relevant Configurations
|
||||
@@ -120,9 +127,12 @@ module CocoapodsXCRemoteCacheModifier
|
||||
config.build_settings['SWIFT_EXEC'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xcswiftc"]
|
||||
config.build_settings['LIBTOOL'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xclibtool"]
|
||||
config.build_settings['LD'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xcld"]
|
||||
config.build_settings['LDPLUSPLUS'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xcldplusplus"]
|
||||
config.build_settings['SWIFT_USE_INTEGRATED_DRIVER'] = ['NO']
|
||||
|
||||
config.build_settings['XCREMOTE_CACHE_FAKE_SRCROOT'] = FAKE_SRCROOT
|
||||
config.build_settings['XCREMOTE_CACHE_FAKE_SRCROOT'] = fake_src_root
|
||||
config.build_settings['XCRC_PLATFORM_PREFERRED_ARCH'] = ["$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)"]
|
||||
config.build_settings[XCRC_COOCAPODS_ROOT_KEY] = ["$SRCROOT/#{srcroot_relative_project_location}"]
|
||||
debug_prefix_map_replacement = '$(SRCROOT' + ':dir:standardizepath' * repo_distance + ')'
|
||||
add_cflags!(config.build_settings, '-fdebug-prefix-map', "#{debug_prefix_map_replacement}=$(XCREMOTE_CACHE_FAKE_SRCROOT)")
|
||||
add_swiftflags!(config.build_settings, '-debug-prefix-map', "#{debug_prefix_map_replacement}=$(XCREMOTE_CACHE_FAKE_SRCROOT)")
|
||||
@@ -146,8 +156,10 @@ module CocoapodsXCRemoteCacheModifier
|
||||
prebuild_script.dependency_file = "$(TARGET_TEMP_DIR)/prebuild.d"
|
||||
|
||||
# Move prebuild (last element) to the position before compile sources phase (to make it real 'prebuild')
|
||||
compile_phase_index = target.build_phases.index(target.source_build_phase)
|
||||
target.build_phases.insert(compile_phase_index, target.build_phases.delete(prebuild_script))
|
||||
if !existing_prebuild_script
|
||||
compile_phase_index = target.build_phases.index(target.source_build_phase)
|
||||
target.build_phases.insert(compile_phase_index, target.build_phases.delete(prebuild_script))
|
||||
end
|
||||
elsif mode == 'producer' || mode == 'producer-fast'
|
||||
# Delete existing prebuild build phase (to support switching between modes)
|
||||
target.build_phases.delete_if do |phase|
|
||||
@@ -171,6 +183,11 @@ module CocoapodsXCRemoteCacheModifier
|
||||
"$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(XCRC_PLATFORM_PREFERRED_ARCH)-$(LLVM_TARGET_TRIPLE_VENDOR)-$(SWIFT_PLATFORM_TARGET_PREFIX)$(LLVM_TARGET_TRIPLE_SUFFIX).swiftmodule.md5"
|
||||
]
|
||||
postbuild_script.dependency_file = "$(TARGET_TEMP_DIR)/postbuild.d"
|
||||
# Move postbuild (last element) to the position after compile sources phase (to make it real 'postbuild')
|
||||
if !existing_postbuild_script
|
||||
compile_phase_index = target.build_phases.index(target.source_build_phase)
|
||||
target.build_phases.insert(compile_phase_index + 1, target.build_phases.delete(postbuild_script))
|
||||
end
|
||||
|
||||
# Mark a sha as ready for a given platform and configuration when building the final_target
|
||||
if (mode == 'producer' || mode == 'producer-fast') && target.name == final_target
|
||||
@@ -180,7 +197,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
end
|
||||
mark_script = existing_mark_script || target.new_shell_script_build_phase("[XCRC] Mark")
|
||||
mark_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\" mark --configuration $CONFIGURATION --platform $PLATFORM_NAME"
|
||||
mark_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\" mark --configuration \"$CONFIGURATION\" --platform $PLATFORM_NAME"
|
||||
mark_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcprepare"]
|
||||
else
|
||||
# Delete existing mark build phase (to support switching between modes or changing the final target)
|
||||
@@ -198,9 +215,12 @@ module CocoapodsXCRemoteCacheModifier
|
||||
config.build_settings.delete('SWIFT_EXEC') if config.build_settings.key?('SWIFT_EXEC')
|
||||
config.build_settings.delete('LIBTOOL') if config.build_settings.key?('LIBTOOL')
|
||||
config.build_settings.delete('LD') if config.build_settings.key?('LD')
|
||||
config.build_settings.delete('LDPLUSPLUS') if config.build_settings.key?('LDPLUSPLUS')
|
||||
config.build_settings.delete('SWIFT_USE_INTEGRATED_DRIVER') if config.build_settings.key?('SWIFT_USE_INTEGRATED_DRIVER')
|
||||
# Remove Fake src root for ObjC & Swift
|
||||
config.build_settings.delete('XCREMOTE_CACHE_FAKE_SRCROOT')
|
||||
config.build_settings.delete('XCRC_PLATFORM_PREFERRED_ARCH')
|
||||
config.build_settings.delete(XCRC_COOCAPODS_ROOT_KEY)
|
||||
remove_cflags!(config.build_settings, '-fdebug-prefix-map')
|
||||
remove_swiftflags!(config.build_settings, '-debug-prefix-map')
|
||||
end
|
||||
@@ -220,7 +240,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
|
||||
def self.download_xcrc_if_needed(local_location)
|
||||
required_binaries = ['xcld', 'xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc']
|
||||
required_binaries = ['xcld', 'xcldplusplus', 'xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc']
|
||||
binaries_exist = required_binaries.reduce(true) do |exists, filename|
|
||||
file_path = File.join(local_location, filename)
|
||||
exists = exists && File.exist?(file_path)
|
||||
@@ -306,7 +326,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
|
||||
# Remove .lldbinit rewrite
|
||||
save_lldbinit_rewrite(nil) unless !@@configuration['modify_lldb_init']
|
||||
save_lldbinit_rewrite(nil,nil) unless !@@configuration['modify_lldb_init']
|
||||
end
|
||||
|
||||
# Returns the content (array of lines) of the lldbinit with stripped XCRemoteCache rewrite
|
||||
@@ -328,17 +348,17 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
|
||||
# Append source rewrite command to the lldbinit content
|
||||
def self.add_lldbinit_rewrite(lines_content, user_proj_directory)
|
||||
def self.add_lldbinit_rewrite(lines_content, user_proj_directory,fake_src_root)
|
||||
all_lines = lines_content.clone
|
||||
all_lines << LLDB_INIT_COMMENT
|
||||
all_lines << "settings set target.source-map #{FAKE_SRCROOT} #{user_proj_directory}"
|
||||
all_lines << "settings set target.source-map #{fake_src_root} #{user_proj_directory}"
|
||||
all_lines << ""
|
||||
all_lines
|
||||
end
|
||||
|
||||
def self.save_lldbinit_rewrite(user_proj_directory)
|
||||
def self.save_lldbinit_rewrite(user_proj_directory,fake_src_root)
|
||||
lldbinit_lines = clean_lldbinit_content(LLDB_INIT_PATH)
|
||||
lldbinit_lines = add_lldbinit_rewrite(lldbinit_lines, user_proj_directory) unless user_proj_directory.nil?
|
||||
lldbinit_lines = add_lldbinit_rewrite(lldbinit_lines, user_proj_directory,fake_src_root) unless user_proj_directory.nil?
|
||||
File.write(LLDB_INIT_PATH, lldbinit_lines.join("\n"), mode: "w")
|
||||
end
|
||||
|
||||
@@ -370,6 +390,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
final_target = @@configuration['final_target']
|
||||
check_build_configuration = @@configuration['check_build_configuration']
|
||||
check_platform = @@configuration['check_platform']
|
||||
fake_src_root = @@configuration['fake_src_root']
|
||||
|
||||
xccc_location_absolute = "#{user_proj_directory}/#{xccc_location}"
|
||||
xcrc_location_absolute = "#{user_proj_directory}/#{xcrc_location}"
|
||||
@@ -401,7 +422,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
next if target.name.start_with?("Pods-")
|
||||
next if target.name.end_with?("Tests")
|
||||
next if exclude_targets.include?(target.name)
|
||||
enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target)
|
||||
enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root)
|
||||
end
|
||||
|
||||
# Create .rcinfo into `Pods` directory as that .xcodeproj reads configuration from .xcodeproj location
|
||||
@@ -414,7 +435,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
next if target.source_build_phase.files_references.empty?
|
||||
next if target.name.end_with?("Tests")
|
||||
next if exclude_targets.include?(target.name)
|
||||
enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target)
|
||||
enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root)
|
||||
end
|
||||
generated_project.save()
|
||||
end
|
||||
@@ -454,15 +475,15 @@ module CocoapodsXCRemoteCacheModifier
|
||||
# Attach XCRC to the app targets
|
||||
user_project.targets.each do |target|
|
||||
next if exclude_targets.include?(target.name)
|
||||
enable_xcremotecache(target, 0, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target)
|
||||
enable_xcremotecache(target, 0, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root)
|
||||
end
|
||||
|
||||
# Set Target sourcemap
|
||||
if @@configuration['modify_lldb_init']
|
||||
save_lldbinit_rewrite(user_proj_directory)
|
||||
save_lldbinit_rewrite(user_proj_directory,fake_src_root)
|
||||
else
|
||||
Pod::UI.puts "[XCRC] lldbinit modification is disabled. Debugging may behave weirdly"
|
||||
Pod::UI.puts "[XCRC] put \"settings set target.source-map #{FAKE_SRCROOT} \#{your_project_directory}\" to your \".lldbinit\" "
|
||||
Pod::UI.puts "[XCRC] put \"settings set target.source-map #{fake_src_root} \#{your_project_directory}\" to your \".lldbinit\" "
|
||||
end
|
||||
|
||||
user_project.save()
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
# limitations under the License.
|
||||
|
||||
module CocoapodsXcremotecache
|
||||
VERSION = "0.0.6"
|
||||
VERSION = "0.0.13"
|
||||
end
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
## Development - How to build
|
||||
|
||||
Generated [docs](https://spotify.github.io/XCRemoteCache/documentation/xcremotecache/).
|
||||
|
||||
### Building the library:
|
||||
|
||||
`CONFIG=Debug rake build`
|
||||
@@ -23,6 +25,34 @@ If you prefer to edit in Xcode, run `swift package generate-xcodeproj`. Do **not
|
||||
|
||||
The generated Xcode project contains schemes for each output application (like `xcswiftc`, `xcprebuild` etc.) so to build a single app, just select the appropriate scheme and build (⌘+B). If you want to build all applications at once, select `Aggregator` scheme that automatically builds all apps. `Aggregator` target in `Package.swift` is defined only for development convenience, it shouldn't be ever used as a dependency.
|
||||
|
||||
#### Debugging the app in a real project
|
||||
|
||||
Debugging XCRemoteCache in a real project is simple:
|
||||
* Open XCRemoteCache's `Package.swift` in Xcode and select a scheme that corresponds to the process you want to debug, e.g. `xcpostbuild`
|
||||
|
||||

|
||||
|
||||
* Build the app with Product->Build (or ⌘+B)
|
||||
* Open the scheme's settings (⌘+⇧+<) and for the "Run" action, select "Wait for the executable to be launched"
|
||||
|
||||

|
||||
|
||||
* Find the produced binary in your DerivedData. The default location would be `~/Library/Developer/Xcode/DerivedData/XCRemoteCache-{hash}/Build/Products/Debug/xcpostbuild`
|
||||
* In the project you want to debug, pick the target you want to inspect and expand the build phase that calls the process
|
||||
* Replace the Input Files path so it points the locally built binary placed in DerivedData
|
||||
|
||||

|
||||
|
||||
* Now, you can run the **XCRemoteCache** project (not the project you want to debug). Because Xcode will not initiate a new process, it will just wait until someone else triggers it.
|
||||
|
||||

|
||||
|
||||
* Finally, build the project to want to debug and expect your XCRemoteCache breakpoints to hit
|
||||
|
||||

|
||||
|
||||
> Tip: If your breakpoints don't hit, try adding a dummy `sleep(1)` in the process entry point (e.g. at the top of `XCPostbuild.main` function)
|
||||
|
||||
#### Running tests in Xcode
|
||||
|
||||
All unit tests are placed in `XCRemoteCacheTests`. To run them from Xcode, just pick any application scheme and run tests (⌘+U).
|
||||
|
||||
+15
@@ -50,3 +50,18 @@ log show --predicate 'sender == "xcprepare"' --style compact --info --debug -las
|
||||
log show --predicate 'sender BEGINSWITH "xc"' --style compact --info --debug -last 10m
|
||||
```
|
||||
</details>
|
||||
|
||||
### Troubleshooting cache misses
|
||||
|
||||
Here is a non-exhaustive list of steps that may help with troubleshooting poor cache hit rate.
|
||||
|
||||
1. ***Producer&Consumer:*** Review XCRemoteCache [Requirements](../#Requirements) and [Limitations](../#limitations)
|
||||
1. ***Producer&Consumer:*** Make sure a producer build uses the same architecture(s) as a consumer. You can inspect `ARCHS` Build Setting in Xcode's Script Phase output logs. Navigate to the report navigator (⌘+9) and expand XCRemoteCache's `prebuild` step output using the "collapsed menu icon" (aka hamburger menu)
|
||||
1. ***Producer:*** Verify that all Xcode targets have a Build Phase called `postbuild`
|
||||
1. ***Producer:*** If you are using optional XCRemoteCache auto-marking feature (`--final-producer-target` or `final_target`) verify an extra Build Phase called `mark` is added to the specified target
|
||||
1. ***Producer:*** After a full build, review logs according to [docs](#how-can-i-find-xcremotecache-logs)
|
||||
1. ***Consumer:*** Verify that all Xcode targets have extra XCRemoteCache Build Phase called `prebuild` and `postbuild`
|
||||
1. ***Consumer:*** After a full build, review according to [docs](#how-can-i-find-xcremotecache-logs). Find a ***first:*** target that reports a cache miss with a message like `Prebuild step failed with error: ...`. If a target reports faces a cache miss, it may have a knock-on effect where a lot of its consumers (dependant targets) need to be built locally too
|
||||
1. ***Consumer:*** ***After a full build, review all meta files placed in `~/Library/Caches/XCRemoteCache/{your_host_path}/meta/*.json` and make sure no absolute paths are used in its `dependencies`. All paths should start a placeholder, like `$(SRCROOT)` or `$(BUILD_DIR)`***
|
||||
1. ***Consumer:*** If you are integrating XCRemoteCache and rebuild artifacts for the same sha, previously downloaded artifacts placed in a local cache may still be used on a consumer side. You can either manually delete your local cache at `~/Library/Caches/XCRemoteCache/` before any consumer build or disable a local cache with `artifact_maximum_age: 0` property in `.rcinfo`
|
||||
1. ***Consumer:*** To find an actual cache hit, before building in Xcode reset statistics with `xcprepare stats --reset` and once it is done, call `xcprepare stats` to find a cache hit rate
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 218 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 204 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 370 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,8 @@
|
||||
---
|
||||
cache_addresses:
|
||||
- 'http://localhost:8080/cache/pods'
|
||||
primary_repo: '.'
|
||||
primary_branch: 'e2e-test-branch'
|
||||
mode: 'consumer'
|
||||
final_target': XCRemoteCacheSample'
|
||||
artifact_maximum_age: 0 # do not use local cache in ~/Library/Caches/XCRemoteCache
|
||||
@@ -0,0 +1,5 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "SomeObjC.h"
|
||||
@@ -0,0 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
@objc
|
||||
public class SomeClass: NSObject {
|
||||
@objc public var someEnum: SomeEnum = .default
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, SomeEnum) {
|
||||
SomeEnumDefault
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,544 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 55;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
36201A102843B3C3002FF70F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36201A0F2843B3C3002FF70F /* AppDelegate.swift */; };
|
||||
36201A122843B3C3002FF70F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36201A112843B3C3002FF70F /* SceneDelegate.swift */; };
|
||||
36201A142843B3C3002FF70F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36201A132843B3C3002FF70F /* ViewController.swift */; };
|
||||
36201A172843B3C3002FF70F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 36201A152843B3C3002FF70F /* Main.storyboard */; };
|
||||
36201A192843B3C7002FF70F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 36201A182843B3C7002FF70F /* Assets.xcassets */; };
|
||||
36201A1C2843B3C7002FF70F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 36201A1A2843B3C7002FF70F /* LaunchScreen.storyboard */; };
|
||||
36201A2A2843B3D3002FF70F /* MixedTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36201A292843B3D3002FF70F /* MixedTarget.swift */; };
|
||||
36201A362843B435002FF70F /* libMixedTarget.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 36201A272843B3D3002FF70F /* libMixedTarget.a */; };
|
||||
36201A392843BDDC002FF70F /* StandaloneObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = 36201A382843BDDC002FF70F /* StandaloneObjc.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
36201A332843B431002FF70F /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 36201A042843B3C3002FF70F /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 36201A262843B3D3002FF70F;
|
||||
remoteInfo = MixedTarget;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
36201A252843B3D3002FF70F /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
36201A0C2843B3C3002FF70F /* StandaloneApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StandaloneApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
36201A0F2843B3C3002FF70F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
36201A112843B3C3002FF70F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
36201A132843B3C3002FF70F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
36201A162843B3C3002FF70F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
36201A182843B3C7002FF70F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
36201A1B2843B3C7002FF70F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
36201A1D2843B3C7002FF70F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
36201A272843B3D3002FF70F /* libMixedTarget.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMixedTarget.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
36201A292843B3D3002FF70F /* MixedTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixedTarget.swift; sourceTree = "<group>"; };
|
||||
36201A2F2843B413002FF70F /* MixedTarget-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MixedTarget-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
36201A302843B414002FF70F /* SomeObjC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SomeObjC.h; sourceTree = "<group>"; };
|
||||
36201A372843BDDC002FF70F /* StandaloneObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StandaloneObjc.h; sourceTree = "<group>"; };
|
||||
36201A382843BDDC002FF70F /* StandaloneObjc.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StandaloneObjc.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
36201A092843B3C3002FF70F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
36201A362843B435002FF70F /* libMixedTarget.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
36201A242843B3D3002FF70F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
36201A032843B3C3002FF70F = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
36201A0E2843B3C3002FF70F /* StandaloneApp */,
|
||||
36201A282843B3D3002FF70F /* MixedTarget */,
|
||||
36201A0D2843B3C3002FF70F /* Products */,
|
||||
36201A352843B435002FF70F /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
36201A0D2843B3C3002FF70F /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
36201A0C2843B3C3002FF70F /* StandaloneApp.app */,
|
||||
36201A272843B3D3002FF70F /* libMixedTarget.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
36201A0E2843B3C3002FF70F /* StandaloneApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
36201A0F2843B3C3002FF70F /* AppDelegate.swift */,
|
||||
36201A112843B3C3002FF70F /* SceneDelegate.swift */,
|
||||
36201A132843B3C3002FF70F /* ViewController.swift */,
|
||||
36201A152843B3C3002FF70F /* Main.storyboard */,
|
||||
36201A182843B3C7002FF70F /* Assets.xcassets */,
|
||||
36201A1A2843B3C7002FF70F /* LaunchScreen.storyboard */,
|
||||
36201A1D2843B3C7002FF70F /* Info.plist */,
|
||||
36201A372843BDDC002FF70F /* StandaloneObjc.h */,
|
||||
36201A382843BDDC002FF70F /* StandaloneObjc.m */,
|
||||
);
|
||||
path = StandaloneApp;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
36201A282843B3D3002FF70F /* MixedTarget */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
36201A292843B3D3002FF70F /* MixedTarget.swift */,
|
||||
36201A302843B414002FF70F /* SomeObjC.h */,
|
||||
36201A2F2843B413002FF70F /* MixedTarget-Bridging-Header.h */,
|
||||
);
|
||||
path = MixedTarget;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
36201A352843B435002FF70F /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
36201A0B2843B3C3002FF70F /* StandaloneApp */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 36201A202843B3C7002FF70F /* Build configuration list for PBXNativeTarget "StandaloneApp" */;
|
||||
buildPhases = (
|
||||
36201A082843B3C3002FF70F /* Sources */,
|
||||
36201A092843B3C3002FF70F /* Frameworks */,
|
||||
36201A0A2843B3C3002FF70F /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
36201A342843B431002FF70F /* PBXTargetDependency */,
|
||||
);
|
||||
name = StandaloneApp;
|
||||
productName = StandaloneApp;
|
||||
productReference = 36201A0C2843B3C3002FF70F /* StandaloneApp.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
36201A262843B3D3002FF70F /* MixedTarget */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 36201A2B2843B3D3002FF70F /* Build configuration list for PBXNativeTarget "MixedTarget" */;
|
||||
buildPhases = (
|
||||
36201A232843B3D3002FF70F /* Sources */,
|
||||
36201A242843B3D3002FF70F /* Frameworks */,
|
||||
36201A252843B3D3002FF70F /* CopyFiles */,
|
||||
36201A3A2843BE0E002FF70F /* Copy Swift Objective-C Interface Header */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = MixedTarget;
|
||||
productName = MixedTarget;
|
||||
productReference = 36201A272843B3D3002FF70F /* libMixedTarget.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
36201A042843B3C3002FF70F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1320;
|
||||
LastUpgradeCheck = 1320;
|
||||
TargetAttributes = {
|
||||
36201A0B2843B3C3002FF70F = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
LastSwiftMigration = 1320;
|
||||
};
|
||||
36201A262843B3D3002FF70F = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
LastSwiftMigration = 1320;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 36201A072843B3C3002FF70F /* Build configuration list for PBXProject "StandaloneApp" */;
|
||||
compatibilityVersion = "Xcode 13.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 36201A032843B3C3002FF70F;
|
||||
productRefGroup = 36201A0D2843B3C3002FF70F /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
36201A0B2843B3C3002FF70F /* StandaloneApp */,
|
||||
36201A262843B3D3002FF70F /* MixedTarget */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
36201A0A2843B3C3002FF70F /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
36201A1C2843B3C7002FF70F /* LaunchScreen.storyboard in Resources */,
|
||||
36201A192843B3C7002FF70F /* Assets.xcassets in Resources */,
|
||||
36201A172843B3C3002FF70F /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
36201A3A2843BE0E002FF70F /* Copy Swift Objective-C Interface Header */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)",
|
||||
"$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME).md5",
|
||||
);
|
||||
name = "Copy Swift Objective-C Interface Header";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)",
|
||||
"$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME).md5",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "ditto \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\"\nditto \"${SCRIPT_INPUT_FILE_1}\" \"${SCRIPT_OUTPUT_FILE_1}\" || true\n\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
36201A082843B3C3002FF70F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
36201A142843B3C3002FF70F /* ViewController.swift in Sources */,
|
||||
36201A102843B3C3002FF70F /* AppDelegate.swift in Sources */,
|
||||
36201A392843BDDC002FF70F /* StandaloneObjc.m in Sources */,
|
||||
36201A122843B3C3002FF70F /* SceneDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
36201A232843B3D3002FF70F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
36201A2A2843B3D3002FF70F /* MixedTarget.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
36201A342843B431002FF70F /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 36201A262843B3D3002FF70F /* MixedTarget */;
|
||||
targetProxy = 36201A332843B431002FF70F /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
36201A152843B3C3002FF70F /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
36201A162843B3C3002FF70F /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
36201A1A2843B3C7002FF70F /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
36201A1B2843B3C7002FF70F /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
36201A1E2843B3C7002FF70F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
36201A1F2843B3C7002FF70F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
36201A212843B3C7002FF70F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/include/";
|
||||
INFOPLIST_FILE = StandaloneApp/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcremotecache.StandaloneApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
36201A222843B3C7002FF70F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/include/";
|
||||
INFOPLIST_FILE = StandaloneApp/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcremotecache.StandaloneApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
36201A2C2843B3D3002FF70F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "MixedTarget/MixedTarget-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
36201A2D2843B3D3002FF70F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "MixedTarget/MixedTarget-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
36201A072843B3C3002FF70F /* Build configuration list for PBXProject "StandaloneApp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
36201A1E2843B3C7002FF70F /* Debug */,
|
||||
36201A1F2843B3C7002FF70F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
36201A202843B3C7002FF70F /* Build configuration list for PBXNativeTarget "StandaloneApp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
36201A212843B3C7002FF70F /* Debug */,
|
||||
36201A222843B3C7002FF70F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
36201A2B2843B3D3002FF70F /* Build configuration list for PBXNativeTarget "MixedTarget" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
36201A2C2843B3D3002FF70F /* Debug */,
|
||||
36201A2D2843B3D3002FF70F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 36201A042843B3C3002FF70F /* Project object */;
|
||||
}
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,29 @@
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: UISceneSession Lifecycle
|
||||
|
||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
// Called when a new scene session is being created.
|
||||
// Use this method to select a configuration to create the new scene with.
|
||||
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
|
||||
// Called when the user discards a scene session.
|
||||
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
|
||||
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+98
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,45 @@
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
||||
guard let _ = (scene as? UIWindowScene) else { return }
|
||||
}
|
||||
|
||||
func sceneDidDisconnect(_ scene: UIScene) {
|
||||
// Called as the scene is being released by the system.
|
||||
// This occurs shortly after the scene enters the background, or when its session is discarded.
|
||||
// Release any resources associated with this scene that can be re-created the next time the scene connects.
|
||||
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
|
||||
}
|
||||
|
||||
func sceneDidBecomeActive(_ scene: UIScene) {
|
||||
// Called when the scene has moved from an inactive state to an active state.
|
||||
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
|
||||
}
|
||||
|
||||
func sceneWillResignActive(_ scene: UIScene) {
|
||||
// Called when the scene will move from an active state to an inactive state.
|
||||
// This may occur due to temporary interruptions (ex. an incoming phone call).
|
||||
}
|
||||
|
||||
func sceneWillEnterForeground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the background to the foreground.
|
||||
// Use this method to undo the changes made on entering the background.
|
||||
}
|
||||
|
||||
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the foreground to the background.
|
||||
// Use this method to save data, release shared resources, and store enough scene-specific state information
|
||||
// to restore the scene back to its current state.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface StandaloneObjc : NSObject
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,6 @@
|
||||
#import "StandaloneObjc.h"
|
||||
#import "MixedTarget/MixedTarget-Swift.h"
|
||||
|
||||
@implementation StandaloneObjc
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
import UIKit
|
||||
import MixedTarget
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import UIKit
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
return true
|
||||
}
|
||||
@@ -38,4 +37,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user