Generated docs.
CONFIG=Debug rake build
CONFIG=Debug rake test
The library uses swift-format linting tool.
- To verify that the library aligns to the formatting style, call
rake lint - To apply all possible autocorrections, run
rake autocorrect
If you prefer to edit in Xcode, run swift package generate-xcodeproj. Do not check it in, the source of truth is the Package.swift file.
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 XCRemoteCache in a real project is simple:
- Open XCRemoteCache's
Package.swiftin Xcode and select a scheme that corresponds to the process you want to debug, e.g.xcpostbuild
- Build the app with Product->Build (or ⌘+B)
- Open the scheme's settings (⌘+⇧+<) and for the "Run" action, select "Wait for the executable to be launched"
- Find the produced binary in your DerivedData. The default location would be
~/Library/Developer/Xcode/DerivedData/XCRemoteCache-{hash}/Build/Products/Debug/xcpostbuild - In the project you want to debug, pick the target you want to inspect and expand the build phase that calls the process
- Replace the Input Files path so it points the locally built binary placed in DerivedData
- Now, you can run the XCRemoteCache project (not the project you want to debug). Because Xcode will not initiate a new process, it will just wait until someone else triggers it.
- Finally, build the project to want to debug and expect your XCRemoteCache breakpoints to hit
Tip: If your breakpoints don't hit, try adding a dummy
sleep(1)in the process entry point (e.g. at the top ofXCPostbuild.mainfunction)
All unit tests are placed in XCRemoteCacheTests. To run them from Xcode, just pick any application scheme and run tests (⌘+U).
E2E tests build a CocoaPods plugin, locally build both producer and consumer modes and verify 100% hit rate. All Podfile templates are placed in e2eTests/tests. As a backend server, nginx server with a sample nginx.conf configuration is used. The sample server exposes local location /tmp/cache under http://localhost:8080.
To run tests locally, install nginx (e.g. brew install nginx) and call:
rake 'build[release]'
rake e2e_onlyThe entry point of each application, main.swift parses the command arguments using swift-argument-parser or manually iterating all arguments. All wrappers that XCRemoteCache provides should be a thin layer and detailed parameters (like -module-cache-path or -enable-objc-interop) are irrelevant - these parameters are only transparently passed if a fallback to a local command happens. Because ArgumentParser fails if a non-documented argument is passed, that library can be used only for non-wrappers apps (xcprepare, xcprebuild, xcpostbuild) where we have a full control of all supported arguments.
Majority of XCRemoteCache logic is stored in a XCRemoteCache target. It is shared between all applications and provides several entry points for each application. For example, the xcprepare application parses all arguments in the XCPrepareMain class (part of the xcprepare target) and calls one of the public entry point in the XCRemoteCache, XCPrepare().main.
XCRemoteCache applications trigger an entrypoint in a class with XC prefix (e.g. XCPrepare) that instantiates concrete implementations of protocol abstractions. The business logic is usually placed in the corresponding class without the XC prefix (e.f. Prepare). Concrete implementations of the protocol should not be referenced outside of that entry point class - the rest of the codebase should rely only on protocol abstraction.
Besides dependencies instantiation, the entry point class is responsible to parse a shared configuration (.rcinfo) and collect all context information, so the rest of the codebase doesn't rely on global variables like environment variables.
Since XC* entrypoint classes do not contain any business logic and only behave as a glue class that links all required objects, this is the only class that cannot and shouldn't be tested.
XCRemoteCache logic is very procedural, with very limited parallelization. Because almost every step depends on a result from a previous phase, the majority of network requests are done synchronously. The class that makes HTTP requests is described by the NetworkClient protocol. There are two implementations that conform to that protocol: NetworkClientImpl and CachedNetworkClient.
All business logic classes should depend on RemoteNetworkClient, transport-agnostic protocol responsible to download/upload file represented by RemoteCacheFile enum.
XCRemoteCache allows extending caching phases (like prebuild or postbuild) with some extra features. The Plugin API is defined in Sources/XCRemoteCache/Artifacts/ArtifactPlugin.swift.
Thinning plugin allows aggregating caching multiple targets from a single target (called aggregation target). With that, the Xcode project can contain only a subset of targets while in runtime, all thinned targets will be available (taken from the remote cache).
To enable thinning target on the consumer side:
- enable
thinning_enabled=truein.rcinfo - set a list of all thinned targets in
SPT_XCREMOTE_CACHE_THINNED_TARGETS=Target1,Taret2...build settings for the aggregation target
- static libraries only
-
Prefer using fakes instead of spies or mocks. Place testing doubles in Tests/XCRemoteCacheTests/TestDoubles so other tests can reuse them
-
If you test a scenario that accesses a file on a disk, consider using the
DiskUsageSizeProviderTestsclass that isolates a working directory and eliminates potential file leaks between testcases -
For dependency injection arguments, avoid passing default values (e.g.
init(dep: SomeDependency = SomeDependency())and require passing explicit values (e.g.init(dep: SomeDependency)). It will be clear from a call site which dependencies is used and suggests adding a testcase when a new dependency is added.




