xccache 0.0.1a1 → 0.0.1a3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/xccache/assets/templates/cachemap.html.template +55 -0
  3. data/lib/xccache/assets/templates/cachemap.js.template +95 -0
  4. data/lib/xccache/assets/templates/cachemap.style.css.template +94 -0
  5. data/lib/xccache/assets/templates/framework.info.plist.template +25 -0
  6. data/lib/xccache/assets/templates/framework.modulemap.template +6 -0
  7. data/lib/xccache/assets/templates/resource_bundle_accessor.m.template +27 -0
  8. data/lib/xccache/assets/templates/resource_bundle_accessor.swift.template +24 -0
  9. data/lib/xccache/assets/templates/umbrella.Package.swift.template +183 -0
  10. data/lib/xccache/cache/cachemap.rb +56 -0
  11. data/lib/xccache/command/base.rb +29 -0
  12. data/lib/xccache/command/build.rb +41 -0
  13. data/lib/xccache/command/pkg/build.rb +30 -0
  14. data/lib/xccache/command/pkg.rb +1 -0
  15. data/lib/xccache/command/remote/pull.rb +15 -0
  16. data/lib/xccache/command/remote/push.rb +15 -0
  17. data/lib/xccache/command/remote.rb +39 -0
  18. data/lib/xccache/command/rollback.rb +14 -0
  19. data/lib/xccache/command/use.rb +19 -0
  20. data/lib/xccache/command.rb +27 -1
  21. data/lib/xccache/core/cacheable.rb +28 -0
  22. data/lib/xccache/core/config.rb +110 -0
  23. data/lib/xccache/core/error.rb +7 -0
  24. data/lib/xccache/core/git.rb +27 -0
  25. data/lib/xccache/core/hash.rb +21 -0
  26. data/lib/xccache/core/lockfile.rb +40 -0
  27. data/lib/xccache/core/log.rb +51 -0
  28. data/lib/xccache/core/parallel.rb +10 -0
  29. data/lib/xccache/core/sh.rb +51 -0
  30. data/lib/xccache/core/syntax/hash.rb +31 -0
  31. data/lib/xccache/core/syntax/json.rb +16 -0
  32. data/lib/xccache/core/syntax/plist.rb +17 -0
  33. data/lib/xccache/core/syntax/yml.rb +16 -0
  34. data/lib/xccache/core/syntax.rb +1 -0
  35. data/lib/xccache/core/system.rb +62 -0
  36. data/lib/xccache/core.rb +1 -0
  37. data/lib/xccache/installer/build.rb +23 -0
  38. data/lib/xccache/installer/rollback.rb +37 -0
  39. data/lib/xccache/installer/use.rb +28 -0
  40. data/lib/xccache/installer.rb +113 -0
  41. data/lib/xccache/main.rb +3 -1
  42. data/lib/xccache/spm/build.rb +53 -0
  43. data/lib/xccache/spm/desc/base.rb +68 -0
  44. data/lib/xccache/spm/desc/dep.rb +40 -0
  45. data/lib/xccache/spm/desc/desc.rb +126 -0
  46. data/lib/xccache/spm/desc/product.rb +36 -0
  47. data/lib/xccache/spm/desc/target/binary.rb +8 -0
  48. data/lib/xccache/spm/desc/target/macro.rb +8 -0
  49. data/lib/xccache/spm/desc/target.rb +164 -0
  50. data/lib/xccache/spm/desc.rb +1 -0
  51. data/lib/xccache/spm/macro.rb +44 -0
  52. data/lib/xccache/spm/mixin.rb +12 -0
  53. data/lib/xccache/spm/pkg/base.rb +107 -0
  54. data/lib/xccache/spm/pkg/umbrella/build.rb +30 -0
  55. data/lib/xccache/spm/pkg/umbrella/cachemap.rb +110 -0
  56. data/lib/xccache/spm/pkg/umbrella/descs.rb +35 -0
  57. data/lib/xccache/spm/pkg/umbrella/manifest.rb +83 -0
  58. data/lib/xccache/spm/pkg/umbrella/viz.rb +40 -0
  59. data/lib/xccache/spm/pkg/umbrella/xcconfigs.rb +31 -0
  60. data/lib/xccache/spm/pkg/umbrella.rb +91 -0
  61. data/lib/xccache/spm/pkg.rb +1 -0
  62. data/lib/xccache/spm/xcframework/metadata.rb +41 -0
  63. data/lib/xccache/spm/xcframework/slice.rb +180 -0
  64. data/lib/xccache/spm/xcframework/xcframework.rb +56 -0
  65. data/lib/xccache/spm/xcframework.rb +2 -0
  66. data/lib/xccache/spm.rb +1 -0
  67. data/lib/xccache/storage/base.rb +26 -0
  68. data/lib/xccache/storage/git.rb +46 -0
  69. data/lib/xccache/storage/s3.rb +53 -0
  70. data/lib/xccache/storage.rb +1 -0
  71. data/lib/xccache/swift/sdk.rb +49 -0
  72. data/lib/xccache/swift/swiftc.rb +16 -0
  73. data/lib/xccache/utils/template.rb +21 -0
  74. data/lib/xccache/xcodeproj/build_configuration.rb +20 -0
  75. data/lib/xccache/xcodeproj/config.rb +9 -0
  76. data/lib/xccache/xcodeproj/file_system_synchronized_root_group.rb +17 -0
  77. data/lib/xccache/xcodeproj/group.rb +26 -0
  78. data/lib/xccache/xcodeproj/pkg.rb +73 -0
  79. data/lib/xccache/xcodeproj/pkg_product_dependency.rb +19 -0
  80. data/lib/xccache/xcodeproj/project.rb +83 -0
  81. data/lib/xccache/xcodeproj/target.rb +52 -0
  82. data/lib/xccache/xcodeproj.rb +2 -0
  83. metadata +107 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 002b0ffd2e4591fe6f68e8af8a380eb8a6926ac4949ab64968484e2350b20386
4
- data.tar.gz: 6ab1e4a2e8e00e0729e70e1e82f01cf16560e05635ea36ce00639b24132c5c6e
3
+ metadata.gz: 2f65f29ba5115620a4f02708c9bffea0dc76d03665970232a580c22f6659df51
4
+ data.tar.gz: 7e98a3268e17d0198762c31d631b6e27516e3d4e2e851c5aeeaf33de28e2b5fa
5
5
  SHA512:
6
- metadata.gz: 57f007540cfc9ee48d94d210a7f4821ac449e734c5d00e44b9b09fb97ea5bfbc340eff8e9c27251245ac5dbf850f90cdc11f72c32732a8c0d1193109c0b6aadd
7
- data.tar.gz: 1d3e971e8546f26870d0b5fd0f2c7898eeb722fa76f1eed2c4b8bb1d7faea20abe4cc4047c40b7fdcdd0e0e59ceae428550216f0712edc1e80335c1b60512b73
6
+ metadata.gz: 80d8a6b25273bcbb2ebbb6a7985d1a092caae35beaa60ba49b1b8e6a3c626196dd9c9d5397dde9391d228c8e5e9fa26e13bf5135542425efa18ffafb8cbc7746
7
+ data.tar.gz: 821e59573bf41ca862db69118e60d5f9a16a4d338a900760063fd97e885fdf4bf88ae3d7792db11c10ac3a38d7546929c6117527ee4f6d72c70cfde506452d6c
@@ -0,0 +1,55 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cachemap Visualization</title>
7
+ <link rel="stylesheet" href="assets/style.css">
8
+ <script src="https://unpkg.com/jquery@3.7.1/dist/jquery.min.js"></script>
9
+ <script src="https://unpkg.com/cytoscape@3.31.2/dist/cytoscape.min.js"></script>
10
+ <script src="https://unpkg.com/layout-base/layout-base.js"></script>
11
+ <script src="https://unpkg.com/cose-base/cose-base.js"></script>
12
+ <script src="https://unpkg.com/cytoscape-fcose/cytoscape-fcose.js"></script>
13
+ </head>
14
+ <body>
15
+ <div class="container">
16
+ <aside id="sidebar">
17
+ <div class="toggle-btn"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M512 256A256 256 0 1 0 0 256a256 256 0 1 0 512 0zM271 135c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-87 87 87 87c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0L167 273c-9.4-9.4-9.4-24.6 0-33.9L271 135z"/></svg></div>
18
+ <div class="sidebar-content">
19
+ <h3 class="title">Cachemap Visualization</h3>
20
+ <section class="legend">
21
+ <div class="section-header">Cache result</div>
22
+ <div>
23
+ <div class="hit"><span class="node">A</span> hit <span class="desc"><%= desc_hit %></span></div>
24
+ <div class="missed"><span class="node">B</span> missed <span class="desc"><%= desc_missed %></span></div>
25
+ <div class="ignored"><span class="node">C</span> ignored <span class="desc"><%= desc_ignored %></span></div>
26
+ </div>
27
+ </section>
28
+ <section class="metadata">
29
+ <div class="section-header">Metadata</div>
30
+ <div>
31
+ <div class="info">Workspace: <a href="<%= root_dir %>"><%= root_dir_short %></a></div>
32
+ <div class="info">Lockfile: <a href="<%= lockfile_path %>"><%= lockfile_path_short %></a></div>
33
+ <div class="info">Binaries: <a href="<%= binaries_dir %>"><%= binaries_dir_short %></a></div>
34
+ </div>
35
+ </section>
36
+ <section class="node-info">
37
+ <div class="section-header">Cache info</div>
38
+ <div>
39
+ <div class="info">Target: <span class="value target">TBU</span></div>
40
+ <div class="info">Checksum: <span class="value checksum">TBU</span></div>
41
+ <div class="info">Binary: <a class="value binary" href="">TBU</a></div>
42
+ <div class="info"><p class="others">TBU</p></div>
43
+ </div>
44
+ </section>
45
+ </div>
46
+ </aside>
47
+ <div id="cy"></div>
48
+ <div class="footnote">Powered by <a href="https://github.com/trinhngocthuyen/xccache">xccache</a></div>
49
+ </div>
50
+ <script src="assets/cachemap.js"></script>
51
+ <script>
52
+ $('.toggle-btn').on('click', () => $('#sidebar').toggleClass('collapsed'));
53
+ </script>
54
+ </body>
55
+ </html>
@@ -0,0 +1,95 @@
1
+ const graph = JSON.parse(`
2
+ <%= json %>
3
+ `);
4
+
5
+ // ------------------------------------------------
6
+
7
+ const COLORS = {
8
+ 'hit': '#339966',
9
+ 'missed': '#ff6f00',
10
+ 'ignored': '#888',
11
+ 'NA': '#888',
12
+ }
13
+ const cy = cytoscape({
14
+ container: $('#cy'),
15
+ elements: ([...graph.nodes, ...graph.edges]).map(x => ({data: x})),
16
+ style: [
17
+ {
18
+ selector: 'node',
19
+ style: {
20
+ 'label': (e) => e.id().split("/")[1],
21
+ 'color': '#fff',
22
+ 'text-valign': 'center',
23
+ 'text-halign': 'center',
24
+ 'font-size': '14px',
25
+ 'shape': 'roundrectangle',
26
+ 'width': (e) => Math.max(50, e.id().split('/')[1].length * 8),
27
+ 'background-color': (e) => COLORS[e.data('cache') || 'NA'],
28
+ }
29
+ },
30
+ {
31
+ selector: 'node:selected',
32
+ style: {
33
+ 'font-weight': 'bold',
34
+ 'border-width': 3,
35
+ 'border-color': '#333',
36
+ }
37
+ },
38
+ {
39
+ selector: 'node[type="agg"]',
40
+ style: {
41
+ 'background-color': '#333',
42
+ }
43
+ },
44
+ {
45
+ selector: 'edge',
46
+ style: {
47
+ 'width': 1,
48
+ 'target-arrow-shape': 'triangle',
49
+ 'curve-style': 'bezier',
50
+ 'line-color': '#ccc',
51
+ 'target-arrow-color': '#ccc',
52
+ }
53
+ },
54
+ ],
55
+ layout: {
56
+ name: 'fcose',
57
+ animationDuration: 200,
58
+ nodeRepulsion: 10000,
59
+ idealEdgeLength: 120,
60
+ gravity: 0.25,
61
+ }
62
+ });
63
+
64
+ cy.on('select', 'node', function(event) {
65
+ const node = event.target;
66
+ node.displayDetails();
67
+ node.neighborhood().add(node).focus();
68
+ });
69
+
70
+ cy.on('tap', function(event) {
71
+ if (event.target == cy) {
72
+ $('.node-info').css('display', 'none');
73
+ cy.elements().animateStyle({'opacity': 1, 'line-color': '#ccc', 'target-arrow-color': '#ccc'});
74
+ }
75
+ });
76
+
77
+ // -----------------------------------------------------------------
78
+
79
+ cytoscape('collection', 'animateStyle', function(style) {
80
+ this.animate({style: style, duration: 200, easing: 'ease-out'})
81
+ });
82
+ cytoscape('collection', 'focus', function() {
83
+ this.animateStyle({'opacity': 1, 'line-color': '#666', 'target-arrow-color': '#666'});
84
+ cy.elements().not(this).animateStyle({'opacity': 0.15, 'line-color': '#ccc', 'target-arrow-color': '#ccc'});
85
+ });
86
+ cytoscape('collection', 'displayDetails', function() {
87
+ $('.node-info').css('display', 'block');
88
+ const info = $('.node-info .info');
89
+ info.find('.target').html(this.id());
90
+ info.find('.checksum').html(this.data('checksum') || 'NA');
91
+ info.find('.binary')
92
+ .html((this.data('binary') || 'NA').split('/').slice(-1))
93
+ .attr({'href': this.data('binary') || ''});
94
+ info.find('.others').html(`Node degree: ${this.degree()} (${this.indegree()} in, ${this.outdegree()} out)`);
95
+ });
@@ -0,0 +1,94 @@
1
+ :root {
2
+ --primary-color: #1492A0;
3
+ --bg-color: color-mix(in srgb, var(--primary-color), white 80%);
4
+ }
5
+ body {
6
+ font-family: Helvetica, Arial, sans-serif;
7
+ font-size: 12px;
8
+ margin: 0;
9
+ line-height: 1.6;
10
+ }
11
+ a { color: var(--primary-color) }
12
+ a:hover { color: #339966; }
13
+ .fa-solid { color: var(--primary-color) }
14
+ .container {
15
+ display: flex;
16
+ height: 100vh;
17
+ }
18
+ #cy {
19
+ flex: 1;
20
+ }
21
+ #sidebar {
22
+ position: relative;
23
+ background-color: var(--bg-color);
24
+ width: 250px;
25
+ transition: all 0.3s ease;
26
+ }
27
+ .sidebar-content {
28
+ width: calc(250px - 32px);
29
+ padding: 16px;
30
+ transform: translateX(0px);
31
+ transition: all 0.3s ease;
32
+ }
33
+ #sidebar.collapsed {
34
+ width: 0;
35
+ }
36
+ #sidebar.collapsed .sidebar-content{
37
+ transform: translateX(-250px);
38
+ }
39
+ #sidebar.collapsed .toggle-btn {
40
+ right: -36px;
41
+ transform: rotate(180deg);
42
+ }
43
+ .toggle-btn {
44
+ position: absolute;
45
+ top: 20px;
46
+ right: 20px;
47
+ z-index: 999;
48
+ cursor: pointer;
49
+ width: 16px;
50
+ height: 16px;
51
+ fill: var(--primary-color);
52
+ transition: all 0.3s;
53
+ }
54
+ #sidebar .title {
55
+ color: var(--primary-color);
56
+ font-size: 16px;
57
+ margin-top: 0;
58
+ }
59
+ #sidebar section {
60
+ padding: 16px 0;
61
+ }
62
+ #sidebar .section-header {
63
+ color: color-mix(in srgb, var(--primary-color), grey 20%);
64
+ font-weight: bold;
65
+ margin-block-end: 4px;
66
+ }
67
+ .node-info {
68
+ display: none;
69
+ }
70
+ .metadata .info {
71
+ font-size: 10px;
72
+ }
73
+ .info {
74
+ color: #888;
75
+ }
76
+ .info .value {
77
+ color: #666;
78
+ }
79
+ .footnote {
80
+ color: #888;
81
+ position: absolute;
82
+ left: 16px;
83
+ bottom: 8px;
84
+ }
85
+ .node {
86
+ border-radius: 3px;
87
+ padding: 1px 3px;
88
+ color: white;
89
+ background-color: var(--color)
90
+ }
91
+ .desc { color: var(--color) }
92
+ .hit { --color: #339966 }
93
+ .missed { --color: #ff6f00 }
94
+ .ignored { --color: #888 }
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>AvailableLibraries</key>
6
+ <array>
7
+ <dict>
8
+ <key>BinaryPath</key>
9
+ <string><%= module_name %>.framework/<%= module_name %></string>
10
+ <key>LibraryPath</key>
11
+ <string><%= module_name %>.framework</string>
12
+ </dict>
13
+ </array>
14
+ <key>CFBundleExecutable</key>
15
+ <string><%= module_name %></string>
16
+ <key>CFBundleName</key>
17
+ <string><%= module_name %></string>
18
+ <key>CFBundleIdentifier</key>
19
+ <string>com.xccache.<%= module_name %></string>
20
+ <key>CFBundlePackageType</key>
21
+ <string>XFWK</string>
22
+ <key>XCFrameworkFormatVersion</key>
23
+ <string>1.0</string>
24
+ </dict>
25
+ </plist>
@@ -0,0 +1,6 @@
1
+ framework module <%= module_name %> {
2
+ umbrella header "<%= target %>-umbrella.h"
3
+
4
+ export *
5
+ module * { export * }
6
+ }
@@ -0,0 +1,27 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ @interface BundleFinder_<%= module_name %> : NSObject
4
+ @end
5
+
6
+ @implementation BundleFinder_<%= module_name %>
7
+ @end
8
+
9
+ NSBundle* <%= module_name %>_SWIFTPM_MODULE_BUNDLE() {
10
+ NSString *bundleName = @"<%= pkg %>_<%= target %>";
11
+ NSArray<NSURL *> *candidates = @[
12
+ NSBundle.mainBundle.resourceURL,
13
+ [NSBundle bundleForClass:[BundleFinder_<%= module_name %> class]].resourceURL,
14
+ NSBundle.mainBundle.bundleURL,
15
+ [NSBundle.mainBundle.bundleURL URLByAppendingPathComponent:@"Frameworks/<%= target %>.framework"]
16
+ ];
17
+
18
+ for (NSURL *candidate in candidates) {
19
+ NSURL *bundlePath = [candidate URLByAppendingPathComponent:[bundleName stringByAppendingString:@".bundle"]];
20
+ NSBundle *bundle = [NSBundle bundleWithURL:bundlePath];
21
+ if (bundle) {
22
+ return bundle;
23
+ }
24
+ }
25
+ [NSException raise:NSInternalInconsistencyException format:@"Unable to find bundle named %@", bundleName];
26
+ return nil;
27
+ }
@@ -0,0 +1,24 @@
1
+ import Foundation
2
+
3
+ private class BundleFinder {}
4
+
5
+ extension Bundle {
6
+ @available(iOS 8.0, *)
7
+ static let module: Bundle = {
8
+ let bundleName = "<%= pkg %>_<%= target %>"
9
+ let candidates = [
10
+ Bundle.main.resourceURL,
11
+ Bundle(for: BundleFinder.self).resourceURL,
12
+ Bundle.main.bundleURL,
13
+ Bundle.main.bundleURL.appendingPathComponent("Frameworks/<%= target %>.framework")
14
+ ]
15
+
16
+ for candidate in candidates {
17
+ let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
18
+ if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
19
+ return bundle
20
+ }
21
+ }
22
+ fatalError("unable to find bundle named \(bundleName)")
23
+ }()
24
+ }
@@ -0,0 +1,183 @@
1
+ // swift-tools-version: 6.0
2
+ import Foundation
3
+ import PackageDescription
4
+
5
+ /*
6
+ MARK: INSTRUCTIONS
7
+ ----------------------------------------------------------------------------------------------------------------------
8
+ This Package manifest is auto-generated from xccache (last updated: <%= timestamp %>).
9
+ ----------------------------------------------------------------------------------------------------------------------
10
+ During local development, you can switch between *BINARY mode* and *SOURCE mode* by adding or removing the `.binary`
11
+ suffix in the JSON below.
12
+ As a user, you just need to care about the JSON. The rest is just the tool's internal logic.
13
+
14
+ NOTE that a product (ex. `pkg/foo.binary`) can only be used as binary if:
15
+ - Binary exists for `pkg/foo` (in `xccache/packages/binaries/foo/foo.xcframework`)
16
+ - Binary exists for all of its dependencies
17
+ The tool auto-fallbacks to the *SOURCE mode* if the conditions are not met.
18
+ ----------------------------------------------------------------------------------------------------------------------
19
+ */
20
+ let JSON = """
21
+ <%= json %>
22
+ """
23
+ let DEPENDENCIES: [Package.Dependency] = [
24
+ <%= dependencies %>
25
+ ]
26
+ let PLATFORMS: [SupportedPlatform] = [
27
+ <%= platforms %>
28
+ ]
29
+ let PRODUCTS_TO_TARGETS: [String: [String]] = try parseJSON("""
30
+ <%= products_to_targets %>
31
+ """)
32
+ // ------------------------------------------------------------------------------------
33
+
34
+ // MARK: Main
35
+ _ = try XCCache.Package(parseJSON(JSON)).spm
36
+
37
+ // MARK: XCCache
38
+ enum XCCache {
39
+ // MARK: Config
40
+ @MainActor
41
+ struct Config {
42
+ static let pkgDir = URL(filePath: #filePath).deletingLastPathComponent()
43
+ static let repoDir = URL.homeDirectory.appending(path: ".xccache/default")
44
+ // NOTE: Do NOT change `binariesDir` to `static let`
45
+ // Somehow, incremental resolution doesnt work causing the `binaryExist` wrongly cached
46
+ static var binariesDir: URL { pkgDir.appending(path: "binaries") }
47
+ }
48
+
49
+ // MARK: Package
50
+ @MainActor
51
+ final class Package {
52
+ let targets: [XCCache.Target]
53
+ init(_ dict: [String: [String]]) {
54
+ targets = dict.map { XCCache.Target($0, $1) }
55
+ }
56
+
57
+ var spm: PackageDescription.Package {
58
+ let regularTargets = targets.map(\.spm)
59
+ let binaryTargets = targets.flatMap(\.flattenRegularDeps).unique(by: \.name).compactMap(\.spmBinaryTarget)
60
+ return .init(
61
+ name: "xccache",
62
+ platforms: PLATFORMS,
63
+ products: targets.map(\.spmProduct),
64
+ dependencies: DEPENDENCIES,
65
+ targets: regularTargets + binaryTargets
66
+ )
67
+ }
68
+ }
69
+
70
+ // MARK: Target
71
+ @MainActor
72
+ final class Target {
73
+ let name: String
74
+ let deps: [UmbrellaDependency]
75
+ init(_ name: String, _ deps: [String]) {
76
+ self.name = name
77
+ self.deps = deps.map { UmbrellaDependency($0) }
78
+ }
79
+
80
+ var spmProduct: PackageDescription.Product {
81
+ .library(name: name, targets: [name])
82
+ }
83
+ var spm: PackageDescription.Target {
84
+ .target(
85
+ name: name,
86
+ dependencies: flattenRegularDeps.map(\.spm),
87
+ path: ".Sources/\(name)",
88
+ swiftSettings: [
89
+ .unsafeFlags(macroFlags),
90
+ ]
91
+ )
92
+ }
93
+ var flattenDeps: [Dependency] { deps.flatMap(\.toBinariesIfOk).unique(by: \.name) }
94
+ var flattenRegularDeps: [Dependency] { flattenDeps.filter(\.regular) }
95
+ var macroFlags: [String] { flattenDeps.filter(\.macroBinary).flatMap(\.macroFlags) }
96
+ }
97
+
98
+ // MARK: Dependency
99
+ @MainActor
100
+ class Dependency {
101
+ let name: String
102
+ let bareName: String
103
+ let binaryURL: URL
104
+ let binary: Bool
105
+ let macro: Bool
106
+ var regular: Bool { !macro }
107
+ var regularBinary: Bool { binary && regular }
108
+ var macroBinary: Bool { binary && macro }
109
+
110
+ init(_ name: String) {
111
+ self.name = name
112
+ self.bareName = String(name.basename.split(separator: ".")[0])
113
+ self.macro = name.contains(".macro")
114
+ self.binaryURL = if macro {
115
+ Config.binariesDir.appending(path: "\(bareName)/\(bareName).macro").readlink()
116
+ } else {
117
+ Config.binariesDir.appending(path: "\(bareName)/\(bareName).xcframework").readlink()
118
+ }
119
+ self.binary = name.hasSuffix(".binary") && binaryURL.exist
120
+ }
121
+
122
+ var macroFlags: [String] {
123
+ ["-load-plugin-executable", "\(binaryURL.path())#\(bareName)"]
124
+ }
125
+
126
+ var spm: PackageDescription.Target.Dependency {
127
+ if binary { return .byName(name: name) }
128
+ return .product(name: bareName, package: name.slug)
129
+ }
130
+ var spmBinaryTarget: PackageDescription.Target? {
131
+ regularBinary ? .binaryTarget(name: name, path: "binaries/\(bareName)/\(bareName).xcframework") : nil
132
+ }
133
+ }
134
+
135
+ @MainActor
136
+ class UmbrellaDependency: Dependency {
137
+ let binaries: [Dependency]
138
+ override init(_ name: String) {
139
+ binaries = (PRODUCTS_TO_TARGETS[name.withoutBinary] ?? []).map { Dependency("\($0).binary") }
140
+ super.init(name)
141
+ }
142
+ var toBinariesIfOk: [Dependency] {
143
+ if name.hasSuffix(".binary"), !binaries.isEmpty && binaries.allSatisfy(\.binary) { return binaries }
144
+ return [Dependency(name.withoutBinary)]
145
+ }
146
+ }
147
+ }
148
+
149
+ // MARK: Helpers
150
+ func parseJSON<T>(_ content: String) throws -> T {
151
+ if let data = content.data(using: .utf8), let result = try JSONSerialization.jsonObject(with: data) as? T {
152
+ return result
153
+ }
154
+ throw NSError(domain: "Invalid JSON:\n\(content)", code: 111)
155
+ }
156
+
157
+ extension URL {
158
+ var basename: String { lastPathComponent }
159
+ var exist: Bool { FileManager.default.fileExists(atPath: readlink().path()) }
160
+ /// Resolve symlinks recursively, equivalent to `readlink -f` in bash
161
+ func readlink() -> URL {
162
+ var prev = self, cur = resolvingSymlinksInPath()
163
+ while prev != cur {
164
+ prev = cur
165
+ cur = cur.resolvingSymlinksInPath()
166
+ }
167
+ return cur
168
+ }
169
+ }
170
+
171
+ extension String {
172
+ var slug: String { (self as NSString).deletingLastPathComponent.basename }
173
+ var basename: String { (self as NSString).lastPathComponent }
174
+ var withoutExtenstion: String { (self as NSString).deletingPathExtension }
175
+ var withoutBinary: String { replacing(#/\.binary$/#, with: "") }
176
+ }
177
+
178
+ extension Sequence {
179
+ func unique<T: Hashable>(by: (Element) -> T) -> [Element] {
180
+ var seen: Set<T> = []
181
+ return filter { seen.insert(by($0)).inserted }
182
+ }
183
+ }
@@ -0,0 +1,56 @@
1
+ require "xccache/core"
2
+
3
+ module XCCache
4
+ module Cache
5
+ class Cachemap < JSONRepresentable
6
+ def depgraph_data
7
+ raw["depgraph"] ||= {}
8
+ end
9
+
10
+ def cache_data
11
+ raw["cache"] ||= {}
12
+ end
13
+
14
+ def manifest_data
15
+ raw["manifest"] ||= {
16
+ "targets" => {},
17
+ "deps" => {},
18
+ "macros" => {},
19
+ }
20
+ end
21
+
22
+ def missed?(name)
23
+ missed.include?(name)
24
+ end
25
+
26
+ def missed
27
+ get_cache_data(:missed)
28
+ end
29
+
30
+ def stats
31
+ %i[hit missed ignored].to_h do |type|
32
+ count, total_count = get_cache_data(type).count, cache_data.count
33
+ percent = total_count.positive? ? (count.to_f * 100 / total_count).round : 0
34
+ [type, "#{percent}% (#{count}/#{total_count})"]
35
+ end
36
+ end
37
+
38
+ def print_stats
39
+ hit, missed, ignored = %i[hit missed ignore].map { |type| get_cache_data(type) }
40
+ total_count = cache_data.count
41
+ UI.message <<~DESC
42
+ -------------------------------------------------------------------
43
+ Cache stats
44
+ • Hit (#{hit.count}/#{total_count}): #{hit.to_s.green.dark}
45
+ • Missed (#{missed.count}/#{total_count}): #{missed.to_s.yellow.dark}
46
+ • Ignored (#{ignored.count}/#{total_count}): #{ignored.to_s.yellow.dark}
47
+ -------------------------------------------------------------------
48
+ DESC
49
+ end
50
+
51
+ def get_cache_data(type)
52
+ cache_data.select { |_, v| v == type }.keys
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,29 @@
1
+ require "xccache/installer"
2
+
3
+ module XCCache
4
+ class Command
5
+ class Options
6
+ SDK = ["--sdk=foo,bar", "SDKs (iphonesimulator, iphoneos, etc.)"].freeze
7
+ CONFIG = ["--config=foo", "Configuration (debug, release)"].freeze
8
+ SKIP_RESOLVING_DEPENDENCIES = [
9
+ "--skip-resolving-dependencies", "Skip resolving package dependencies",
10
+ ].freeze
11
+ MERGE_SLICES = [
12
+ "--merge-slices/--no-merge-slices",
13
+ "Whether to merge with existing slices/sdks in the xcframework (default: true)",
14
+ ].freeze
15
+ LIBRARY_EVOLUTION = [
16
+ "--library-evolution/--no-library-evolution",
17
+ "Whether to enable library evolution (build for distribution) (default: false)",
18
+ ].freeze
19
+
20
+ def self.install_options
21
+ [SDK, SKIP_RESOLVING_DEPENDENCIES]
22
+ end
23
+
24
+ def self.build_options
25
+ install_options + [CONFIG, MERGE_SLICES, LIBRARY_EVOLUTION]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ require "xccache/installer"
2
+ require_relative "base"
3
+
4
+ module XCCache
5
+ class Command
6
+ class Build < Command
7
+ self.summary = "Build packages to xcframeworks"
8
+ def self.options
9
+ [
10
+ *Options.build_options,
11
+ ["--integrate/no-integrate", "Whether to integrate after building target (default: true)"],
12
+ ["--recursive", "Whether to build their recursive targets if cache-missed (default: false)"],
13
+ ].concat(super)
14
+ end
15
+ self.arguments = [
16
+ CLAide::Argument.new("TARGET", false, true),
17
+ ]
18
+
19
+ def initialize(argv)
20
+ super
21
+ @targets = argv.arguments!
22
+ @should_integrate = argv.flag?("integrate", true)
23
+ end
24
+
25
+ def run
26
+ installer = Installer::Build.new(
27
+ ctx: self,
28
+ targets: @targets,
29
+ )
30
+ installer.install!
31
+
32
+ # Reuse umbrella_pkg from previous installers
33
+ return unless @should_integrate
34
+ Installer::Use.new(
35
+ ctx: self,
36
+ umbrella_pkg: installer.umbrella_pkg,
37
+ ).install!
38
+ end
39
+ end
40
+ end
41
+ end