Compare commits

..

139 Commits

Author SHA1 Message Date
Vadim Smal 25ff5a790b Merge pull request #157 from CognitiveDisson/addUploadRetryDelay
Add a delay between network request retries
2022-07-15 18:38:20 +01:00
Vadim Smal f446f6061d Fix config read 2022-07-15 18:01:22 +01:00
Vadim Smal b46d1e2ca1 Fixed retryDelay decoding 2022-07-14 18:06:35 +01:00
Vadim Smal 88d0666da9 Fix swiftlint violations 2022-07-14 17:44:31 +01:00
Vadim Smal 08bf5c21bf Update documentation 2022-07-14 17:38:48 +01:00
Vadim Smal be97a6a247 Fix swiftlint violations 2022-07-14 17:37:46 +01:00
Vadim Smal dff71f8070 Add a delay between upload retries 2022-07-14 16:59:40 +01:00
Vadim Smal cda5fc1188 Add a delay between upload retries 2022-07-11 17:21:51 +01:00
Bartosz Polaczyk 15586755d2 Merge pull request #152 from polac24/xcode14-fallback
Xcode 14.0 support
2022-06-20 09:29:41 +02:00
Bartosz Polaczyk e978eb182c Add limitation 2022-06-18 17:51:54 +02:00
Bartosz Polaczyk 7e98cebdd9 Merge remote-tracking branch 'upstream/master' into xcode14-fallback 2022-06-18 17:48:18 +02:00
Bartosz Polaczyk fbc2982aa3 [Xcode 14.0] Disable Swift integration driver 2022-06-18 17:43:33 +02:00
Bartosz Polaczyk cbcc028cad Merge pull request #151 from ainopara/fix/denpendency-writer-white-space
Escape white space in denpendency writer
2022-06-14 15:25:57 +02:00
ainopara 2acf97eca1 Fix lint issue 2022-06-14 13:37:33 +08:00
ainopara 40803cf747 Add unit test 2022-06-13 14:45:15 +08:00
ainopara 97496ed7b8 Escape white space in denpendency writer 2022-06-13 01:35:15 +08:00
Bartosz Polaczyk 45222f8e33 Merge pull request #150 from jarbogast-salesforce/cplusplus-support
Support C++ Linker
2022-06-11 22:26:58 +02:00
Jonathan Arbogast 91b5b5e590 Support C++ Linker 2022-06-11 15:07:30 -04:00
Bartosz Polaczyk f2a7880c24 Merge pull request #148 from polac24/20220606-integrate-postbuild
Place postbuild build phase right after compilation
2022-06-07 07:29:14 -04:00
Bartosz Polaczyk 00cb8cc23d Merge pull request #147 from polac24/20220606-bridging-headers-enum-test
Add E2E test for exposing public enums from ObjC to Swift
2022-06-07 07:28:56 -04:00
Bartosz Polaczyk a3cd6bea07 Add postbuild right after compilation for CocoaPods 2022-06-06 17:53:11 -04:00
Bartosz Polaczyk 86c762b070 Add postbuild right after compilation for integrate 2022-06-06 17:49:44 -04:00
Bartosz Polaczyk bd21156695 Add E2E test for exposing public enums from ObjC to Swift 2022-06-06 17:34:14 -04:00
Bartosz Polaczyk 3a82ad91b2 Merge pull request #144 from devMEremenko/fix-caching-in-env-fingerprint-generator
Fixed caching in `generatedFingerprint` in `EnvironmentFingerprintGenerator`
2022-06-02 23:03:38 +02:00
Maxim Eremenko 5a5bf35c4a Clean up 2022-06-02 22:46:10 +03:00
Maxim Eremenko 1f766ad4a4 Fixed SwiftLint in EnvironmentFingerprintGeneratorTests 2022-06-02 22:34:23 +03:00
Maxim Eremenko 3c3cd84d81 Fixed swiftlint issues 2022-06-02 22:23:42 +03:00
Maxim Eremenko 7728733aef Cached generatedFingerprint in EnvironmentFingerprintGenerator 2022-06-02 21:06:41 +03:00
Bartosz Polaczyk 50580bf9fd Merge pull request #139 from samuelsainz/feature/postbuild-dependencies-reader-perf-improvement
Improved DependenciesReader performance (~10x faster)
2022-05-24 17:40:21 +02:00
Samuel Sainz ceaff318d6 Fixed SwiftLint issues and CR improvements 2022-05-24 11:09:18 -03:00
Samuel Sainz 4cc932a592 Improved Dependencies Reader performance to be 10x faster 2022-05-23 15:31:41 -03:00
Bartosz Polaczyk 75bef0baf6 Merge pull request #136 from polac24/20220517-fix-vendored-frameworks
Support Vendored frameworks in the CocoaPods development pod
2022-05-19 09:03:04 +02:00
Bartosz Polaczyk 03671e71b7 Support Vendored frameworks in the CocoaPods plugin 2022-05-17 19:46:15 +02:00
Bartosz Polaczyk 91505a59d2 Merge pull request #135 from polac24/fix-hybrid-incremental
Fix hybrid incremental performance
2022-05-16 09:43:46 +02:00
Bartosz Polaczyk 59c1d999b1 Always clean history when prebuilding 2022-05-15 20:04:27 +02:00
Bartosz Polaczyk ba58c1c21e Do not add marker dependency 2022-05-15 19:59:42 +02:00
Bartosz Polaczyk 96ce18bc31 Retrigger clang compilations when a target is switching between cache miss<->hit 2022-05-13 12:07:12 +02:00
Bartosz Polaczyk 30e49ef7bc Merge pull request #132 from zzzworm/master
Update gem_version.rb
2022-05-12 12:25:57 +02:00
zzzworm d46f09c6dd Update gem_version.rb
just update the gem version to fix pod install error when disable XCRemoteCache
2022-05-12 15:30:44 +08:00
Bartosz Polaczyk 2fdf6c39e2 Merge pull request #131 from zzzworm/master
fix pod install  error when disable cache
2022-05-12 06:48:03 +02:00
zhouchanghong 064b22a2d1 fix pod install error when disable cache 2022-05-11 17:45:26 +08:00
Bartosz Polaczyk ddeffe75d6 Merge pull request #130 from polac24/irrelevant-dependency
Add irrelevant dependencies property
2022-05-11 08:59:32 +02:00
Bartosz Polaczyk 181997e3d2 Test fix 2022-05-11 08:37:01 +02:00
Bartosz Polaczyk 1d6ac2c171 Apply reviewer suggestions 2022-05-11 08:30:10 +02:00
Bartosz Polaczyk 3dfd6e296f Typo cleanup 2022-05-10 21:31:34 +02:00
Bartosz Polaczyk c39b286222 Fix swiftlint 2022-05-10 13:15:22 +02:00
Bartosz Polaczyk a55a4547ac Add skipped dependencies regex 2022-05-10 12:58:19 +02:00
Bartosz Polaczyk 51c2007c5b Merge pull request #129 from polac24/docs
Generate gh-pages docs using docc
2022-05-09 09:22:08 +02:00
Bartosz Polaczyk fc092fc4e3 Merge pull request #128 from zzzworm/master
cocoapods plugin support custom FAKE_SRCROOT
2022-05-08 10:55:55 +02:00
Bartosz Polaczyk 3f9596f6e6 Bump to Xcode 13.3.1 2022-05-07 18:00:43 +02:00
Bartosz Polaczyk 15f4ee7eab Generate gh-pages docs using docc 2022-05-07 17:55:26 +02:00
zhouchanghong a6325db0a5 [Docs] add fake_src_root doc for cocoapods plugin 2022-05-07 19:47:11 +08:00
zhouchanghong cfa4fbd070 cocoapods plugin support custom FAKE_SRCROOT 2022-05-07 19:40:10 +08:00
Bartosz Polaczyk c573ced4f4 Add irrelevant_dependencies_paths property 2022-05-07 09:29:20 +02:00
Bartosz Polaczyk 0c2afc15fc Merge pull request #125 from ainopara/master
Fix xcode preview compile issue
2022-04-27 17:48:06 +02:00
ainopara 586e8df56e Fix xcode preview compile issue
When SWIFT_EXEC and LD is setted in a terget, files in the taget can not be previewed due to compile failure which is caused by missing argument.
2022-04-26 19:20:09 +08:00
Bartosz Polaczyk ef3a88c6ec Merge pull request #122 from polac24/20220418-derived
Fix building target dependencies list with custom DERIVED_FILE_DIR
2022-04-19 09:32:43 +02:00
Bartosz Polaczyk 95f95be29e Identify custom DerivedData under sources dir 2022-04-18 09:07:36 +02:00
Bartosz Polaczyk 4d12800096 Skip all DerivedData files in dependencies list 2022-04-18 09:04:17 +02:00
Bartosz Polaczyk fbb456b0e0 Merge pull request #121 from cezarsignori/csignori/rc_derived_files_dir
Set auto-generated Swift header under DERIVED_FILES_DIR as irrelevant dependency
2022-04-18 08:57:44 +02:00
Cezar Signori a321ea3362 Set auto-generated Swift header under DERIVED_FILES_DIR as irrelevant dependency 2022-04-14 14:20:30 +02:00
Bartosz Polaczyk 963e6858ee Merge pull request #117 from polac24/20220411-loop-configs
Load extra configuration in a chain
2022-04-12 22:31:46 +02:00
Bartosz Polaczyk f9999a402f Merge pull request #118 from polac24/20220412-bump-cocoapods
Bump CocoaPods plugin to 0.0.9
2022-04-12 22:25:42 +02:00
Bartosz Polaczyk dbfe1dd8d4 Bump CocoaPods plugin to 0.0.9 2022-04-12 15:44:52 +02:00
Bartosz Polaczyk def12fab6f Merge pull request #116 from samuelsainz/bugfix/prebuild-phase-order
Fix error that moves the Prebuild phase script after the source phase
2022-04-12 00:01:12 +02:00
Bartosz Polaczyk 6fee69f081 Fix linter 2022-04-11 20:10:43 +02:00
Bartosz Polaczyk b74bafa5d7 Cleanup tests 2022-04-11 19:53:33 +02:00
Bartosz Polaczyk 33de361317 Add Readme docs 2022-04-11 19:40:00 +02:00
Bartosz Polaczyk 0aeb5aee36 Allow importing extra config multiple times 2022-04-11 19:37:36 +02:00
Samuel Sainz 8682731f30 Fix error that moves the Prebuild phase script after the source build phase 2022-04-11 11:03:15 -03:00
Bartosz Polaczyk 62b637bdaa Merge pull request #113 from polac24/cocoapods-bump-008
Bump cocoapods plugin version 0.0.8
2022-04-04 09:27:19 +02:00
Bartosz Polaczyk 0bca5e5bc4 Bump cocoapods plugin version 0.0.8 2022-04-04 08:14:06 +02:00
Bartosz Polaczyk eb0d4b17b7 Merge pull request #95 from polac24/20220226-pch-suport
Switch to local PCH headers compilation
2022-04-01 08:41:39 +02:00
Bartosz Polaczyk d9ea5bfd49 Apply suggestions from code review
Co-authored-by: Aleksander Grzyb <aleksander.grzyb@gmail.com>
2022-04-01 08:24:07 +02:00
Bartosz Polaczyk ee8bf69814 Add comments 2022-03-31 22:09:55 +02:00
Bartosz Polaczyk 2c0f78056f Merge branch 'master' into 20220226-pch-suport 2022-03-31 22:03:10 +02:00
Bartosz Polaczyk b6b17bdc8a Merge pull request #111 from devMEremenko/integrate-aws-security-token
Integrate AWS Temporary Access Keys
2022-03-31 20:15:30 +02:00
Maxim Eremenko a387359930 Removed trailing_whitespace 2022-03-31 20:44:13 +03:00
Maxim Eremenko 456233b90e Update tests 2022-03-30 23:46:15 +03:00
Maxim Eremenko d6f6e2e9bc Integrate AWS Temporary Security Token 2022-03-30 23:21:22 +03:00
Bartosz Polaczyk a35bd0b394 Merge pull request #110 from samuelsainz/bugfix/mark-script_build-config-param
Adding quotes to the config param in the Mark script
2022-03-25 23:20:02 +01:00
Samuel Sainz def131f2a0 Fix config param in Mark script considering build configs with a space in the name 2022-03-25 17:40:17 -03:00
Bartosz Polaczyk c052ed8ed6 Merge pull request #105 from cezarsignori/csignori/xcrc_fake_src_root
Set provided fakeSrcRoot on Xcode proj build settings as XCRC_FAKE_SRCROOT
2022-03-22 17:00:59 +01:00
Cezar Signori d4f9486b92 Set provided fakeSrcRoot on Xcode proj build settings as XCRC_FAKE_SRCROOT 2022-03-22 16:26:16 +01:00
Bartosz Polaczyk 8241914543 Merge pull request #106 from alecgarza96/master
Fixes issue #49
2022-03-21 20:08:19 +01:00
alecgarza96 b5ff16484f removed trailing whitespace 2022-03-21 11:31:09 -05:00
alecgarza96 2b9dde9aec added support for both ".S" and ".s" extensions 2022-03-21 11:16:40 -05:00
alecgarza96 136e7a99ff added .s file extension for assembly support 2022-03-10 14:40:58 -06:00
Bartosz Polaczyk c626d51f97 Merge pull request #99 from polac24/20220304-document-debugging
[Docs] Local development steps
2022-03-07 17:29:49 +01:00
Bartosz Polaczyk e8ddc9297d [Docs] Local development steps 2022-03-04 18:47:29 +01:00
Bartosz Polaczyk 3b614c6172 Merge pull request #97 from polac24/20220228-fix-swiftlint
Fix swiftlint errors
2022-03-02 08:47:44 +01:00
Bartosz Polaczyk 87a214104e Add headers linting 2022-03-01 22:58:00 +01:00
Bartosz Polaczyk 599e1e229d Fix linter errors 2022-03-01 22:44:21 +01:00
Bartosz Polaczyk 423da7cc4a Enable trailing_dot_in_comments 2022-03-01 22:44:03 +01:00
Bartosz Polaczyk 4b082e9dd2 Run CI linting in strict mode 2022-03-01 22:22:45 +01:00
Vadim Smal 36803d6b5d Merge pull request #96 from CognitiveDisson/update-dependencies
Update dependencies
2022-03-01 08:54:36 +00:00
Bartosz Polaczyk 5809bc963c Fix serious errors 2022-02-28 23:14:52 +01:00
Bartosz Polaczyk cb6626cfbc Add exclude to E2E Pods 2022-02-28 22:57:45 +01:00
Bartosz Polaczyk 3e18711e09 Apply autocorrect 2022-02-28 22:56:34 +01:00
Bartosz Polaczyk ccda424791 Formatting 2022-02-28 22:55:04 +01:00
Vadim Smal 90d784cc8d Update dependencies 2022-02-28 16:05:59 +00:00
Bartosz Polaczyk 6715824195 Update Sources/XCRemoteCache/Commands/Prepare/CCWrapperBuilder.swift 2022-02-28 08:29:35 +01:00
Bartosz Polaczyk c4e08d4288 Remove limitation 2022-02-26 19:11:54 +01:00
Bartosz Polaczyk a2067f8dfe Add unit test 2022-02-26 18:37:56 +01:00
Bartosz Polaczyk 253e9597bd Force compilation success in xccc generation tests 2022-02-26 18:37:56 +01:00
Bartosz Polaczyk 4a93f944fd Fallback when compiling pch header 2022-02-26 16:03:54 +01:00
Bartosz Polaczyk f839b4064b Merge pull request #93 from polac24/20220223-add-troubleshootings
[Docs] Add troubleshooting tips
2022-02-24 20:48:29 +01:00
Bartosz Polaczyk e71837b8b2 Merge pull request #94 from polac24/20220223-missing-overlay-log
Print overlay error messages to os_log only
2022-02-24 20:48:12 +01:00
Bartosz Polaczyk b45792646b Apply suggestions from code review
Co-authored-by: Aleksander Grzyb <aleksander.grzyb@gmail.com>
2022-02-24 08:46:57 +01:00
Bartosz Polaczyk 1966562eef Print overlay error messages to logs only 2022-02-23 21:58:03 +01:00
Bartosz Polaczyk 883b207c5b Fix formatting 2022-02-23 21:43:40 +01:00
Bartosz Polaczyk ca137d0ce4 Add troubleshooting steps 2022-02-23 21:24:07 +01:00
Bartosz Polaczyk b9a633c86f Merge pull request #91 from polac24/20220222-disable-urlcache
Disable local URLSession cache
2022-02-22 17:33:59 +01:00
Bartosz Polaczyk 55a87eb4e9 Disable local URLSession cache 2022-02-22 14:37:06 +01:00
Bartosz Polaczyk e6b56024b9 Merge pull request #90 from polac24/bump-cocoapods-007
Bump CocoaPods plugin version
2022-02-21 10:25:45 +01:00
Bartosz Polaczyk 8c34a31110 Bump CocoaPods plugin version 2022-02-19 08:54:22 +01:00
Bartosz Polaczyk f7c32d6e80 Merge pull request #87 from polac24/20220217-custom-mappings-envs
Customize rewritting dependency paths
2022-02-18 11:30:51 +01:00
Bartosz Polaczyk b3a16ae5d0 Merge pull request #88 from polac24/20220217-decorate-logs
Decorate logs with a target name
2022-02-18 11:30:35 +01:00
Bartosz Polaczyk d013fe4c81 Decorate logs with a target name 2022-02-17 20:35:13 +01:00
Bartosz Polaczyk 4aefee078e Customize rewritting paths 2022-02-17 20:13:25 +01:00
Bartosz Polaczyk 9363e68d51 Merge pull request #85 from polac24/20220214-remapper-stable
Do not change dependencies order when remapping overlay
2022-02-15 09:06:50 +01:00
Bartosz Polaczyk 4af8156da5 Do not change dependencies order when remapping overlay 2022-02-14 18:46:51 +01:00
Bartosz Polaczyk f086feb005 Merge pull request #83 from polac24/issue-template
Add issue templates
2022-02-14 09:01:51 +01:00
Bartosz Polaczyk d98d4cd0a3 Apply suggestions from code review 2022-02-14 08:46:25 +01:00
Bartosz Polaczyk a38d445ae9 Merge pull request #82 from polac24/20220210-best-effort-overlay
Do not fail prebuild/postbuild for invalid vfs overlay
2022-02-14 08:45:22 +01:00
Bartosz Polaczyk 0436f6ae27 Separate templates 2022-02-13 21:55:56 +01:00
Bartosz Polaczyk be78959437 Add Issue template 2022-02-11 18:10:00 +01:00
Bartosz Polaczyk cccae0d9f6 Add disable_vfs_overlay feature flag 2022-02-10 21:03:53 +01:00
Bartosz Polaczyk e7d1e905cf Do not throw overlay for the best-effort mode 2022-02-10 20:57:05 +01:00
Bartosz Polaczyk 20d53a1c71 Merge pull request #75 from polac24/20220209-prebuild-index
[Integrate] Place xcprebuild script right before compilation
2022-02-10 20:17:16 +01:00
Bartosz Polaczyk f4ba03d581 Merge pull request #72 from polac24/20220207-non-throwing-init
Make DependenciesRemapper throwable
2022-02-09 22:36:36 +01:00
Bartosz Polaczyk 0e48d39818 [Integrate] Move prebuild script right before compilation 2022-02-09 17:52:31 +01:00
Bartosz Polaczyk 3b30939f99 Merge pull request #74 from vasvf/master
Rename prebuild and postbuild phases, adjust prebuild position
2022-02-09 17:40:41 +01:00
Vasily Fedorov e263cb6e25 fix comment 2022-02-09 17:01:35 +03:00
Vasily Fedorov b805bf4a99 revert some changes 2022-02-09 16:55:13 +03:00
Vasily Fyodorov fd7b68a344 Merge branch 'spotify:master' into master 2022-02-08 19:18:39 +03:00
Vasily Fedorov a63488a043 Move build configs to xcconfigs 2022-02-08 19:02:38 +03:00
Bartosz Polaczyk 48bcdd8ce9 Apply suggestions from code review
Co-authored-by: PatrikBillgren <PatrikBillgren@users.noreply.github.com>
2022-02-08 16:49:57 +01:00
Bartosz Polaczyk 41dd1cae03 Change to throwing remapping 2022-02-07 20:40:44 +01:00
109 changed files with 3491 additions and 387 deletions
+65
View File
@@ -0,0 +1,65 @@
---
name: ⚠️ Bug Report
about: Something isn't working as expected
---
<!--
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
-->
**My integration setup**
[ ] CocoaPods cocoapods-xcremotecache plugin
[ ] Automatic integration using `xcprepare integrate ...`
[ ] Manual integration
[ ] Carthage
**Expected/desired behavior**
<!-- Describe what the desired behavior would be. -->
**Minimal reproduction of the problem with instructions**
<!-- Please provide the *STEPS TO REPRODUCE*. -->
**Producer Logs**
<!-- Capture logs from 10 minutes: `log show --predicate 'sender BEGINSWITH "xc"' --style compact --info --debug -last 10m` -->
<details>
<pre> [REPLACE THIS WITH YOUR INFORMATION] </pre>
</details>
**Consumer Logs**
<!-- Capture logs from 10 minutes: `log show --predicate 'sender BEGINSWITH "xc"' --style compact --info --debug -last 10m` -->
<details>
<pre> [REPLACE THIS WITH YOUR INFORMATION] </pre>
</details>
**Pods/Carthage file**
<!-- Delete if you don't use CocoaPods or Carthage -->
<details>
<pre> [REPLACE THIS WITH YOUR INFORMATION] </pre>
</details>
**Environment**
* **XCRemoteCache:** X.Y.Z
* **cocoapods-xcremotecache:** X.Y.Z <!-- check with `gem list cocoapods-xcremotecache` >
* **HTTP cache server:** ... <!-- e.g. demo docker, nginx, AWS etc. >
* **Xcode:** X.Y.Z
**Post build stats**
<!--
To capture build statistics:
* call `xcprepare stats --reset` (or `XCRC/xcprepare stats --reset` for CocoaPods)
* Build a project in Xcode
* `xcprepare stats` (or `XCRC/xcprepare stats` for CocoaPods)
-->
<details>
<pre> [REPLACE THIS WITH YOUR INFORMATION] </pre>
</details>
**Others**
<!-- Anything else relevant? Operating system version, , ... -->
@@ -0,0 +1,16 @@
---
name: 📕 Documentation Issue
about: Suggestion for a change in a documentation
---
<!--
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
-->
**A suggestion**
<!-- Describe how could the documentation be improved. -->
**Which file, section, line**
<!-- Provide a section it relates (if exist). -->
+20
View File
@@ -0,0 +1,20 @@
---
name: 🙏 Future Request
about: Suggestion for an improvement, either behaviour or implementation
---
<!--
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
-->
**Expected/desired behavior**
<!-- Describe what the desired behavior would be. -->
**Relevant integration setup**
[ ] CocoaPods cocoapods-xcremotecache plugin
[ ] Automatic integration using `xcprepare integrate ...`
[ ] Manual integration
[ ] Carthage
+4 -2
View File
@@ -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"
+24
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+14 -2
View File
@@ -8,6 +8,7 @@ _XCRemoteCache is a remote cache tool for Xcode projects. It reuses target artif
[![Build Status](https://github.com/spotify/XCRemoteCache/workflows/CI/badge.svg)](https://github.com/spotify/XCRemoteCache/workflows/CI/badge.svg)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[![Slack](https://slackin.spotify.com/badge.svg)](https://slackin.spotify.com)
[![Docs](https://spotify.github.io/XCRemoteCache/documentation/xcremotecache/)](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,10 +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
@@ -349,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
...
@@ -402,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
+1 -1
View File
@@ -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 \
@@ -153,7 +153,7 @@ class Postbuild {
// Replace all local paths to the generic ones (e.g. $SRCROOT)
let remappers = [remapper] + creatorPlugins.compactMap(\.customPathsRemapper)
let remapper = DependenciesRemapperComposite(remappers)
let abstractFingerprintFiles = remapper.replace(localPaths: dependencies.map(\.path))
let abstractFingerprintFiles = try remapper.replace(localPaths: dependencies.map(\.path))
// TODO: use `inputs` read by dependenciesReader
var meta = MainArtifactMeta(
dependencies: abstractFingerprintFiles,
@@ -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
)
@@ -145,24 +149,32 @@ public class XCPostbuild {
fileDependeciesReaderFactory: fileReaderFactory,
dirScanner: fileManager
)
// 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.
let overlayReader = JsonOverlayReader(
context.overlayHeadersPath,
mode: .bestEffort,
fileReader: fileManager
)
let overlayRemapper = try OverlayDependenciesRemapper(
overlayReader: overlayReader
)
let pathRemapper = DependenciesRemapperComposite([overlayRemapper, envsRemapper])
var remappers: [DependenciesRemapper] = []
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
let overlayReader = JsonOverlayReader(
context.overlayHeadersPath,
mode: .bestEffort,
fileReader: fileManager
)
let overlayRemapper = OverlayDependenciesRemapper(
overlayReader: overlayReader
)
remappers.append(overlayRemapper)
}
remappers.append(envsRemapper)
let pathRemapper = DependenciesRemapperComposite(remappers)
let dependencyProcessor = DependencyProcessorImpl(
xcode: context.xcodeDir,
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(
@@ -313,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 = 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,22 +122,27 @@ 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
)
// As PrebuildContext assumes file location and its filename (`all-product-headers.yaml`)
// do not fail in case of a missing headers overlay file.
let overlayReader = JsonOverlayReader(
context.overlayHeadersPath,
mode: .bestEffort,
fileReader: fileManager
)
let overlayRemapper = try OverlayDependenciesRemapper(
overlayReader: overlayReader
)
let pathRemapper = DependenciesRemapperComposite([overlayRemapper, envsRemapper])
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
let overlayReader = JsonOverlayReader(
context.overlayHeadersPath,
mode: .bestEffort,
fileReader: fileManager
)
let overlayRemapper = OverlayDependenciesRemapper(
overlayReader: overlayReader
)
remappers.append(overlayRemapper)
}
remappers.append(envsRemapper)
let pathRemapper = DependenciesRemapperComposite(remappers)
let filesFingerprintGenerator = FingerprintAccumulatorImpl(
algorithm: MD5Algorithm(),
fileManager: fileManager
@@ -141,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] = []
@@ -182,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(
@@ -190,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
@@ -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
}
@@ -227,13 +227,15 @@ struct XcodeProjIntegrate: Integrate {
let previousRCPhases = target.buildPhases.filter(isRCPhase)
target.buildPhases.removeAll(where: previousRCPhases.contains)
if target.buildPhases.map(\.buildPhase).contains(.sources) {
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)
pbxproj.add(object: prebuildPhase)
target.buildPhases.insert(prebuildPhase, at: 0)
// 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.append(postbuildPhase)
target.buildPhases.insert(postbuildPhase, at: sourceIndex + 1)
pbxproj.add(object: prebuildPhase)
target.buildPhases.insert(prebuildPhase, at: sourceIndex)
}
}
@@ -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,19 +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 {
@@ -163,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
@@ -179,10 +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
}
@@ -227,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]?
@@ -240,10 +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 {
@@ -271,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"
@@ -284,10 +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"
}
}
@@ -300,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()
@@ -332,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
}
}
@@ -22,9 +22,9 @@ import Foundation
/// Replaces paths formats between generic (placeholders-based) and local
protocol DependenciesRemapper {
/// Replaces all generic paths (with placeholders) to a local paths
func replace(genericPaths: [String]) -> [String]
func replace(genericPaths: [String]) throws -> [String]
/// Replaces all local paths to the generic dependencies paths
func replace(localPaths: [String]) -> [String]
func replace(localPaths: [String]) throws -> [String]
}
class DependenciesRemapperComposite: DependenciesRemapper {
@@ -34,15 +34,15 @@ class DependenciesRemapperComposite: DependenciesRemapper {
self.remappers = remappers
}
func replace(genericPaths: [String]) -> [String] {
remappers.reversed().reduce(genericPaths) { prev, mapper in
mapper.replace(genericPaths: prev)
func replace(genericPaths: [String]) throws -> [String] {
try remappers.reversed().reduce(genericPaths) { prev, mapper in
try mapper.replace(genericPaths: prev)
}
}
func replace(localPaths: [String]) -> [String] {
remappers.reduce(localPaths) { prev, mapper in
mapper.replace(localPaths: prev)
func replace(localPaths: [String]) throws -> [String] {
try remappers.reduce(localPaths) { prev, mapper in
try mapper.replace(localPaths: prev)
}
}
}
@@ -59,7 +59,7 @@ final class StringDependenciesRemapper: DependenciesRemapper {
self.mappings = mappings
}
func replace(genericPaths: [String]) -> [String] {
func replace(genericPaths: [String]) throws -> [String] {
return genericPaths.map { path in
let localPath = mappings.reversed().reduce(path) { prevPath, mapping in
prevPath.replacingOccurrences(of: mapping.generic, with: mapping.local)
@@ -68,7 +68,7 @@ final class StringDependenciesRemapper: DependenciesRemapper {
}
}
func replace(localPaths: [String]) -> [String] {
func replace(localPaths: [String]) throws -> [String] {
return localPaths.map { path in
let result = mappings.reduce(path) { prevPath, mapping in
prevPath.replacingOccurrences(of: mapping.local, with: mapping.generic)
@@ -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)
}
}
@@ -20,20 +20,32 @@
import Foundation
/// File paths remapper according the virtual file system mappings
/// Warning: this class is not thread safe
/// - Warning: this class is not thread safe
class OverlayDependenciesRemapper: DependenciesRemapper {
private var mappings: [OverlayMapping]
private let overlayReader: OverlayReader
private var mappings: [OverlayMapping]?
init(overlayReader: OverlayReader) throws {
mappings = try overlayReader.provideMappings()
init(overlayReader: OverlayReader) {
self.overlayReader = overlayReader
}
/// Lazily Reads mappings from a file
/// - Warning: this function is not thread safe
private func getMappings() throws -> [OverlayMapping] {
guard let mappings = mappings else {
let mappings = try overlayReader.provideMappings()
self.mappings = mappings
return mappings
}
return mappings
}
private func mapPath(
_ path: String,
source: KeyPath<OverlayMapping,URL>,
destination: KeyPath<OverlayMapping,URL>
) -> String {
guard let mapping = mappings.first(where: { $0[keyPath: source].path == path }) else {
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
// no direct mapping found
return path
@@ -41,15 +53,15 @@ class OverlayDependenciesRemapper: DependenciesRemapper {
return mapping[keyPath: destination].path
}
func replace(genericPaths: [String]) -> [String] {
Set(genericPaths.map {
mapPath($0, source: \.virtual, destination: \.local)
}).sorted()
func replace(genericPaths: [String]) throws -> [String] {
try genericPaths.map {
try mapPath($0, source: \.virtual, destination: \.local)
}
}
func replace(localPaths: [String]) -> [String] {
Set(localPaths.map {
mapPath($0, source: \.local, destination: \.virtual)
}).sorted()
func replace(localPaths: [String]) throws -> [String] {
try localPaths.map {
try mapPath($0, source: \.local, destination: \.virtual)
}
}
}
@@ -91,35 +91,43 @@ 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 []
}
}
do {
let overlay: Overlay = try jsonDecoder.decode(Overlay.self, from: jsonContent)
let mappings: [OverlayMapping] = try overlay.roots.reduce([]) { prev, root in
switch root.type {
case .directory:
//iterate all contents
let dir = URL(fileURLWithPath: root.name)
let mappings: [OverlayMapping] = try root.contents.map { content in
switch content.type {
case .file:
let virtual = dir.appendingPathComponent(content.name)
let local = URL(fileURLWithPath: content.externalContents)
return .init(virtual: virtual, local: local)
case .directory:
throw JsonOverlayReaderError.unsupportedFormat
}
let mappings: [OverlayMapping] = try overlay.roots.reduce([]) { prev, root in
switch root.type {
case .directory:
// iterate all contents
let dir = URL(fileURLWithPath: root.name)
let mappings: [OverlayMapping] = try root.contents.map { content in
switch content.type {
case .file:
let virtual = dir.appendingPathComponent(content.name)
let local = URL(fileURLWithPath: content.externalContents)
return .init(virtual: virtual, local: local)
case .directory:
throw JsonOverlayReaderError.unsupportedFormat
}
}
return prev + mappings
case .file:
throw JsonOverlayReaderError.unsupportedFormat
}
return prev + mappings
case .file:
throw JsonOverlayReaderError.unsupportedFormat
}
return mappings
} catch {
switch mode {
case .strict:
throw error
case .bestEffort:
errorLog("Overlay reader has failed with an error \(error). Best-effort mode - skipping an overlay.")
return []
}
}
return mappings
}
}
@@ -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
+13 -7
View File
@@ -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
}
+8 -1
View File
@@ -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
+11 -2
View File
@@ -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)
}
+75
View File
@@ -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()
}
}
+22
View File
@@ -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()
+10 -2
View File
@@ -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
@@ -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)
}
}
}
@@ -29,89 +29,119 @@ class DependenciesRemapperCompositeTests: XCTestCase {
StringDependenciesRemapper.Mapping(generic: "$(PWD)", local: "/pwd"),
]
func testNoRemappersIsTransparent() {
func testNoRemappersIsTransparent() throws {
let remapper = DependenciesRemapperComposite([])
let genericPath = remapper.replace(localPaths: ["/tmp/root/some.swift"])
let genericPath = try remapper.replace(localPaths: ["/tmp/root/some.swift"])
XCTAssertEqual(genericPath, ["/tmp/root/some.swift"])
}
func testOneRemapperReplacesLocalPaths() {
func testOneRemapperReplacesLocalPaths() throws {
let remapper = DependenciesRemapperComposite([
StringDependenciesRemapper(mappings: mappings1),
])
let genericPath = remapper.replace(localPaths: ["/tmp/root/some.swift"])
let genericPath = try remapper.replace(localPaths: ["/tmp/root/some.swift"])
XCTAssertEqual(genericPath, ["$(SRC_ROOT)/some.swift"])
}
func testOneRemapperReplacesGenericPaths() {
func testOneRemapperReplacesGenericPaths() throws {
let remapper = DependenciesRemapperComposite([
StringDependenciesRemapper(mappings: mappings1),
])
let localPath = remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
let localPath = try remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
XCTAssertEqual(localPath, ["/tmp/root/some.swift"])
}
func testTwoRemappersReplacesLocalPaths() {
func testTwoRemappersReplacesLocalPaths() throws {
let remapper = DependenciesRemapperComposite([
StringDependenciesRemapper(mappings: mappings1),
StringDependenciesRemapper(mappings: mappings2),
])
let genericPath = remapper.replace(localPaths: ["/tmp/root/some.swift", "/pwd/other.swift"])
let genericPath = try remapper.replace(localPaths: ["/tmp/root/some.swift", "/pwd/other.swift"])
XCTAssertEqual(genericPath, ["$(SRC_ROOT)/some.swift", "$(PWD)/other.swift"])
}
func testOneRemappersReplacesGenericPaths() {
func testOneRemappersReplacesGenericPaths() throws {
let remapper = DependenciesRemapperComposite([
StringDependenciesRemapper(mappings: mappings1),
StringDependenciesRemapper(mappings: mappings2),
])
let localPath = remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift", "$(PWD)/other.swift"])
let localPath = try remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift", "$(PWD)/other.swift"])
XCTAssertEqual(localPath, ["/tmp/root/some.swift", "/pwd/other.swift"])
}
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"]
let genericPaths = remapper.replace(localPaths: localPaths)
let genericPaths = try remapper.replace(localPaths: localPaths)
XCTAssertEqual(genericPaths, ["$(SPECIFIC)/file"])
}
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"]
let localPaths = remapper.replace(genericPaths: genericPaths)
let localPaths = try remapper.replace(genericPaths: genericPaths)
XCTAssertEqual(localPaths, ["/root/specific/file"])
}
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"]
let genericPaths = remapper.replace(localPaths: localPaths)
let remappedLocalPaths = remapper.replace(genericPaths: genericPaths)
let genericPaths = try remapper.replace(localPaths: localPaths)
let remappedLocalPaths = try remapper.replace(genericPaths: genericPaths)
XCTAssertEqual(localPaths, remappedLocalPaths)
}
@@ -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)]
)
}
}
@@ -26,40 +26,45 @@ class OverlayDependenciesRemapperTests: XCTestCase {
)
func testMappingFromLocalToGeneric() throws {
let reader = try OverlayDependenciesRemapper(
overlayReader: overlayReader
)
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
let dependencies = reader.replace(localPaths: ["/Intermediate/Some/file.h"])
let dependencies = try remapper.replace(localPaths: ["/Intermediate/Some/file.h"])
XCTAssertEqual(dependencies, ["/file.h"])
}
func testMappingFromGenericToLocal() throws {
let reader = try OverlayDependenciesRemapper(
overlayReader: overlayReader
)
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
let dependencies = reader.replace(genericPaths: ["/file.h"])
let dependencies = try remapper.replace(genericPaths: ["/file.h"])
XCTAssertEqual(dependencies, ["/Intermediate/Some/file.h"])
}
func testGenericDependenciesAreMerged() throws {
func testGenericDependenciesAreNotMerged() throws {
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
let reader = try OverlayDependenciesRemapper(
overlayReader: overlayReader
)
let dependencies = reader.replace(localPaths: ["/Intermediate/Some/file.h", "/file.h"])
XCTAssertEqual(dependencies, ["/file.h"])
let dependencies = try remapper.replace(localPaths: ["/Intermediate/Some/file.h", "/file.h"])
XCTAssertEqual(dependencies, ["/file.h", "/file.h"])
}
func testLocalDependenciesAreMerged() throws {
let reader = try OverlayDependenciesRemapper(
overlayReader: overlayReader
)
func testLocalDependenciesAreNotMerged() throws {
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
let dependencies = reader.replace(genericPaths: ["/Intermediate/Some/file.h", "/file.h"])
XCTAssertEqual(dependencies, ["/Intermediate/Some/file.h"])
let dependencies = try remapper.replace(genericPaths: ["/Intermediate/Some/file.h", "/file.h"])
XCTAssertEqual(dependencies, ["/Intermediate/Some/file.h", "/Intermediate/Some/file.h"])
}
func testMappingsAreReadOnce() throws {
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
let firstDependencies = try remapper.replace(localPaths: ["/Intermediate/Some/file.h"])
// Update mappings in-fly to verify the previous value is cached in a remapper
overlayReader.mappings = []
let secondDependencies = try remapper.replace(localPaths: ["/Intermediate/Some/file.h"])
XCTAssertEqual(firstDependencies, ["/file.h"])
XCTAssertEqual(secondDependencies, ["/file.h"])
}
}
@@ -20,7 +20,7 @@
@testable import XCRemoteCache
import XCTest
class JsonOverlayReaderTests: XCTestCase {
class JsonOverlayReaderTests: FileXCTestCase {
private static let resourcesSubdirectory = "TestData/Dependencies/JsonOverlayReaderTests"
func testParsingWithSuccess() throws {
@@ -29,8 +29,14 @@ class JsonOverlayReaderTests: XCTestCase {
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))
}
@@ -59,7 +65,30 @@ class JsonOverlayReaderTests: XCTestCase {
XCTAssertEqual(mappings, [])
}
func testInvalidJsonDoesntThrowForBestEffortMode() throws {
let workingDir = try prepareTempDir()
let file = workingDir.appendingPathExtension("overlay.json")
try fileManager.spt_createEmptyFile(file)
let reader = JsonOverlayReader(file, mode: .bestEffort, fileReader: fileManager)
let mappings = try reader.provideMappings()
XCTAssertEqual(mappings, [])
}
func testInvalidJsonThrowsForStrictMode() throws {
let workingDir = try prepareTempDir()
let file = workingDir.appendingPathExtension("overlay.json")
try fileManager.spt_createEmptyFile(file)
let reader = JsonOverlayReader(file, mode: .strict, fileReader: fileManager)
XCTAssertThrowsError(try reader.provideMappings())
}
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
))
}
}
@@ -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 {
@@ -34,12 +35,34 @@ class StringDependenciesRemapperFactoryTests: XCTestCase {
customMappings: [:]
)
let localPaths = remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
let localPaths = try remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
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": ""],
@@ -55,7 +78,7 @@ class StringDependenciesRemapperFactoryTests: XCTestCase {
customMappings: ["TMP": "/tmp"]
)
let genericPaths = remapper.replace(localPaths: ["/some/repoFile.swift", "/tmp/externalFile.swift"])
let genericPaths = try remapper.replace(localPaths: ["/some/repoFile.swift", "/tmp/externalFile.swift"])
XCTAssertEqual(genericPaths, ["$(PWD)/repoFile.swift", "$(TMP)/externalFile.swift"])
}
@@ -30,34 +30,34 @@ class StringDependenciesRemapperTests: XCTestCase {
remapper = StringDependenciesRemapper(mappings: mappings)
}
func testMappingSingleGenericPathReplacesWithLocalPath() {
let localPaths = remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
func testMappingSingleGenericPathReplacesWithLocalPath() throws {
let localPaths = try remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
XCTAssertEqual(localPaths, ["/tmp/root/some.swift"])
}
func testRewritingSingleLocalPathReplacesWithGenericPath() {
let genericPaths = remapper.replace(localPaths: ["/tmp/root/some.swift"])
func testRewritingSingleLocalPathReplacesWithGenericPath() throws {
let genericPaths = try remapper.replace(localPaths: ["/tmp/root/some.swift"])
XCTAssertEqual(genericPaths, ["$(SRC_ROOT)/some.swift"])
}
func testRewritingLocalToGenericAndLocalIsIdentical() {
func testRewritingLocalToGenericAndLocalIsIdentical() throws {
let inputLocalPaths = ["/tmp/root/some.swift"]
let genericPaths = remapper.replace(localPaths: inputLocalPaths)
let localPaths = remapper.replace(genericPaths: genericPaths)
let genericPaths = try remapper.replace(localPaths: inputLocalPaths)
let localPaths = try remapper.replace(genericPaths: genericPaths)
XCTAssertEqual(localPaths, inputLocalPaths)
}
func testRewritingUnrelatedDirReturnsInputPath() {
let genericPaths = remapper.replace(localPaths: ["/other/some.swift"])
func testRewritingUnrelatedDirReturnsInputPath() throws {
let genericPaths = try remapper.replace(localPaths: ["/other/some.swift"])
XCTAssertEqual(genericPaths, ["/other/some.swift"])
}
func testMultipleMatchesTakeTheFirstMapping() {
func testMultipleMatchesTakeTheFirstMapping() throws {
let mappings: [StringDependenciesRemapper.Mapping] = [
.init(generic: "$(SRC_ROOT)", local: "/tmp/root"),
.init(generic: "$(PWD)", local: "/tmp"),
@@ -65,12 +65,12 @@ class StringDependenciesRemapperTests: XCTestCase {
remapper = StringDependenciesRemapper(mappings: mappings)
let genericPaths = remapper.replace(localPaths: ["/tmp/root/some.swift", "/tmp/extra.swift"])
let genericPaths = try remapper.replace(localPaths: ["/tmp/root/some.swift", "/tmp/extra.swift"])
XCTAssertEqual(genericPaths, ["$(SRC_ROOT)/some.swift", "$(PWD)/extra.swift"])
}
func testMappingsLocalPathsIsDoneInOrder() {
func testMappingsLocalPathsIsDoneInOrder() throws {
let mappings: [StringDependenciesRemapper.Mapping] = [
.init(generic: "$(TMP)", local: "/tmp"),
.init(generic: "$(ROOT)", local: "$(TMP)/root"),
@@ -78,12 +78,12 @@ class StringDependenciesRemapperTests: XCTestCase {
remapper = StringDependenciesRemapper(mappings: mappings)
let genericPaths = remapper.replace(localPaths: ["/tmp/root/some.swift"])
let genericPaths = try remapper.replace(localPaths: ["/tmp/root/some.swift"])
XCTAssertEqual(genericPaths, ["$(ROOT)/some.swift"])
}
func testMappingsGenericPathsIsDoneInReversedOrder() {
func testMappingsGenericPathsIsDoneInReversedOrder() throws {
let mappings: [StringDependenciesRemapper.Mapping] = [
.init(generic: "$(TMP)", local: "/tmp"),
.init(generic: "$(ROOT)", local: "$(TMP)/root"),
@@ -91,7 +91,7 @@ class StringDependenciesRemapperTests: XCTestCase {
remapper = StringDependenciesRemapper(mappings: mappings)
let localPaths = remapper.replace(genericPaths: ["$(ROOT)/some.swift"])
let localPaths = try remapper.replace(genericPaths: ["$(ROOT)/some.swift"])
XCTAssertEqual(localPaths, ["/tmp/root/some.swift"])
}
@@ -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()
)
}
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: ",")
}
}
@@ -21,7 +21,8 @@ import Foundation
@testable import XCRemoteCache
class OverlayReaderFake: OverlayReader {
private let mappings: [OverlayMapping]
var mappings: [OverlayMapping]
init(mappings: [OverlayMapping]) {
self.mappings = mappings
}
+1 -4
View File
@@ -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)")
@@ -135,7 +145,8 @@ module CocoapodsXCRemoteCacheModifier
phase.name != nil && phase.name.start_with?("[XCRC] Prebuild")
end
end
prebuild_script = existing_prebuild_script || target.new_shell_script_build_phase("[XCRC] Prebuild")
prebuild_script = existing_prebuild_script || target.new_shell_script_build_phase("[XCRC] Prebuild #{target.name}")
prebuild_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\""
prebuild_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcprebuild"]
prebuild_script.output_paths = [
@@ -144,8 +155,11 @@ module CocoapodsXCRemoteCacheModifier
]
prebuild_script.dependency_file = "$(TARGET_TEMP_DIR)/prebuild.d"
# Move prebuild (last element) to the first position (to make it real 'prebuild')
target.build_phases.rotate!(-1) if existing_prebuild_script.nil?
# Move prebuild (last element) to the position before compile sources phase (to make it real 'prebuild')
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|
@@ -161,7 +175,7 @@ module CocoapodsXCRemoteCacheModifier
phase.name != nil && phase.name.start_with?("[XCRC] Postbuild")
end
end
postbuild_script = existing_postbuild_script || target.new_shell_script_build_phase("[XCRC] Postbuild")
postbuild_script = existing_postbuild_script || target.new_shell_script_build_phase("[XCRC] Postbuild #{target.name}")
postbuild_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\""
postbuild_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcpostbuild"]
postbuild_script.output_paths = [
@@ -169,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
@@ -178,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)
@@ -196,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
@@ -218,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)
@@ -304,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
@@ -326,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
@@ -368,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}"
@@ -399,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
@@ -412,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
@@ -452,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
+30
View File
@@ -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`
![Select scheme](./img/debug-scheme.png)
* 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"
![Configure scheme](./img/debug-scheme-wait.png)
* 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
![Update phase](./img/debug-phase-update.png)
* 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.
![LLDB waiting](./img/debug-wait.png)
* Finally, build the project to want to debug and expect your XCRemoteCache breakpoints to hit
![Breakpoint hit](./img/debug-breakpoint.png)
> 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
View File
@@ -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

+8
View File
@@ -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 */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -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.
}
}
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -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>

Some files were not shown because too many files have changed in this diff Show More