xccache 0.0.1a1 → 0.0.1a2

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/lib/xccache/assets/templates/cachemap.html.template +54 -0
  3. data/lib/xccache/assets/templates/cachemap.js.template +94 -0
  4. data/lib/xccache/assets/templates/cachemap.style.css.template +92 -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 +147 -0
  10. data/lib/xccache/cache/cachemap.rb +60 -0
  11. data/lib/xccache/command/build.rb +35 -0
  12. data/lib/xccache/command/pkg/build.rb +34 -0
  13. data/lib/xccache/command/rollback.rb +13 -0
  14. data/lib/xccache/command/use.rb +18 -0
  15. data/lib/xccache/command.rb +11 -1
  16. data/lib/xccache/core/cacheable.rb +28 -0
  17. data/lib/xccache/core/config.rb +82 -0
  18. data/lib/xccache/core/error.rb +7 -0
  19. data/lib/xccache/core/git.rb +19 -0
  20. data/lib/xccache/core/hash.rb +21 -0
  21. data/lib/xccache/core/lockfile.rb +40 -0
  22. data/lib/xccache/core/log.rb +41 -0
  23. data/lib/xccache/core/sh.rb +50 -0
  24. data/lib/xccache/core/syntax/hash.rb +31 -0
  25. data/lib/xccache/core/syntax/json.rb +16 -0
  26. data/lib/xccache/core/syntax/yml.rb +16 -0
  27. data/lib/xccache/core/syntax.rb +1 -0
  28. data/lib/xccache/core/system.rb +54 -0
  29. data/lib/xccache/core.rb +1 -0
  30. data/lib/xccache/framework/slice.rb +183 -0
  31. data/lib/xccache/framework/xcframework.rb +51 -0
  32. data/lib/xccache/installer/build.rb +27 -0
  33. data/lib/xccache/installer/rollback.rb +36 -0
  34. data/lib/xccache/installer/use.rb +27 -0
  35. data/lib/xccache/installer.rb +67 -0
  36. data/lib/xccache/main.rb +3 -1
  37. data/lib/xccache/spm/desc/base.rb +61 -0
  38. data/lib/xccache/spm/desc/dep.rb +40 -0
  39. data/lib/xccache/spm/desc/desc.rb +110 -0
  40. data/lib/xccache/spm/desc/product.rb +36 -0
  41. data/lib/xccache/spm/desc/target.rb +138 -0
  42. data/lib/xccache/spm/desc.rb +1 -0
  43. data/lib/xccache/spm/mixin.rb +9 -0
  44. data/lib/xccache/spm/pkg/base.rb +103 -0
  45. data/lib/xccache/spm/pkg/umbrella/build.rb +30 -0
  46. data/lib/xccache/spm/pkg/umbrella/cachemap.rb +93 -0
  47. data/lib/xccache/spm/pkg/umbrella/descs.rb +46 -0
  48. data/lib/xccache/spm/pkg/umbrella/manifest.rb +83 -0
  49. data/lib/xccache/spm/pkg/umbrella/viz.rb +40 -0
  50. data/lib/xccache/spm/pkg/umbrella.rb +81 -0
  51. data/lib/xccache/spm/pkg.rb +1 -0
  52. data/lib/xccache/spm.rb +1 -0
  53. data/lib/xccache/swift/sdk.rb +30 -0
  54. data/lib/xccache/swift/swiftc.rb +16 -0
  55. data/lib/xccache/utils/template.rb +21 -0
  56. data/lib/xccache/xcodeproj/pkg.rb +69 -0
  57. data/lib/xccache/xcodeproj/pkg_product_dependency.rb +19 -0
  58. data/lib/xccache/xcodeproj/project.rb +63 -0
  59. data/lib/xccache/xcodeproj/target.rb +50 -0
  60. data/lib/xccache/xcodeproj.rb +2 -0
  61. metadata +72 -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: 8478aeb0498b7eaa2e6c231f65cf65421d247958a30b2cfca0750e0d8b0603e9
4
+ data.tar.gz: 4f5266ef2925ce5ca1b06596da4b6aa879c53926eaf2a460638b912187f9316b
5
5
  SHA512:
6
- metadata.gz: 57f007540cfc9ee48d94d210a7f4821ac449e734c5d00e44b9b09fb97ea5bfbc340eff8e9c27251245ac5dbf850f90cdc11f72c32732a8c0d1193109c0b6aadd
7
- data.tar.gz: 1d3e971e8546f26870d0b5fd0f2c7898eeb722fa76f1eed2c4b8bb1d7faea20abe4cc4047c40b7fdcdd0e0e59ceae428550216f0712edc1e80335c1b60512b73
6
+ metadata.gz: 78daeec82245936d646cfe0f8f5d8ea1e82d2d851333aa8523a010e8a78c257d7fadbbb3fed8a1a88bdd59a57070843c87330ad79e5bab5f3dc6e9774623b420
7
+ data.tar.gz: d9f4e5ba9057726fd4a6679ba7014b8c444cd195c7481f36c2f66f59bedefd5d409d473420016869bf327951b4ccfc3e42799bfc8a4709b5244476d10ef306ba
@@ -0,0 +1,54 @@
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>
43
+ </section>
44
+ </div>
45
+ </aside>
46
+ <div id="cy"></div>
47
+ <div class="footnote">Powered by <a href="https://github.com/trinhngocthuyen/xccache">xccache</a></div>
48
+ </div>
49
+ <script src="assets/cachemap.js"></script>
50
+ <script>
51
+ $('.toggle-btn').on('click', () => $('#sidebar').toggleClass('collapsed'));
52
+ </script>
53
+ </body>
54
+ </html>
@@ -0,0 +1,94 @@
1
+ // MARK: GENERATED
2
+ // ------------------------------------------------
3
+ graph = JSON.parse(`
4
+ <%= json %>
5
+ `);
6
+ // ------------------------------------------------
7
+
8
+ const cy = cytoscape({
9
+ container: $('#cy'),
10
+ elements: ([...graph.nodes, ...graph.edges]).map(x => ({data: x})),
11
+ style: [
12
+ {
13
+ selector: 'node',
14
+ style: {
15
+ 'label': (e) => e.id().split("/")[1],
16
+ 'color': '#fff',
17
+ 'text-valign': 'center',
18
+ 'text-halign': 'center',
19
+ 'font-size': '14px',
20
+ 'shape': 'roundrectangle',
21
+ 'width': (e) => Math.max(50, e.id().split("/")[1].length * 8),
22
+ }
23
+ },
24
+ {
25
+ selector: 'node:selected',
26
+ style: {
27
+ 'font-weight': 'bold',
28
+ 'border-width': 3,
29
+ 'border-color': '#333',
30
+ }
31
+ },
32
+ {
33
+ selector: 'node[cache="hit"]',
34
+ style: {'background-color': '#339966'}
35
+ },
36
+ {
37
+ selector: 'node[cache="missed"]',
38
+ style: {'background-color': '#ff6f00'}
39
+ },
40
+ {
41
+ selector: 'node[type="agg"]',
42
+ style: {
43
+ 'background-color': '#333',
44
+ }
45
+ },
46
+ {
47
+ selector: 'node[cache="ignored"]',
48
+ style: {
49
+ 'background-color': '#888',
50
+ }
51
+ },
52
+ {
53
+ selector: 'edge',
54
+ style: {
55
+ 'width': 1,
56
+ 'target-arrow-shape': 'triangle',
57
+ 'curve-style': 'bezier',
58
+ 'line-color': '#ccc',
59
+ // 'line-color': e => e.target().style('background-color') || '#999',
60
+ 'target-arrow-color': e => e.style('line-color'),
61
+ }
62
+ },
63
+ ],
64
+ layout: {
65
+ name: 'fcose',
66
+ animationDuration: 300,
67
+ nodeRepulsion: 10000,
68
+ idealEdgeLength: 120,
69
+ gravity: 0.25,
70
+ }
71
+ });
72
+
73
+ cy.on('select', 'node', function(event) {
74
+ $('.node-info').css('display', 'block');
75
+ const node = event.target;
76
+ const info = $('.node-info .info');
77
+ info.find('.target').html(node.id());
78
+ info.find('.checksum').html(node.data('checksum') || 'NA');
79
+ info.find('.binary')
80
+ .html((node.data('binary') || 'NA').split('/').slice(-1))
81
+ .attr({'href': node.data('binary') || ''});
82
+
83
+ // Mark neighbor as focused, dim others
84
+ const focused = node.neighborhood().add(node);
85
+ focused.animate({style: {'opacity': 1}, duration: 200});
86
+ cy.elements().not(focused).animate({style: {'opacity': 0.15}, duration: 200});
87
+ });
88
+
89
+ cy.on('tap', function(event) {
90
+ if (event.target == cy) {
91
+ $('.node-info').css('display', 'none');
92
+ cy.elements().animate({style: {'opacity': 1}});
93
+ }
94
+ });
@@ -0,0 +1,92 @@
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
+ .info {
71
+ color: #888;
72
+ font-size: 10px;
73
+ }
74
+ .info .value {
75
+ color: #666;
76
+ }
77
+ .footnote {
78
+ color: #888;
79
+ position: absolute;
80
+ left: 16px;
81
+ bottom: 8px;
82
+ }
83
+ .node {
84
+ border-radius: 3px;
85
+ padding: 1px 3px;
86
+ color: white;
87
+ background-color: var(--color)
88
+ }
89
+ .desc { color: var(--color) }
90
+ .hit { --color: #339966 }
91
+ .missed { --color: #ff6f00 }
92
+ .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 "<%= module_name %>-umbrella.h"
3
+
4
+ export *
5
+ module * { export * }
6
+ }
@@ -0,0 +1,27 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ @interface BundleFinder : NSObject
4
+ @end
5
+
6
+ @implementation BundleFinder
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 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,147 @@
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
+ // NOTE: Do NOT change `binariesDir` to `static let`
44
+ // Somehow, incremental resolution doesnt work causing the `binaryExist` wrongly cached
45
+ static var binariesDir: URL { pkgDir.appending(path: "binaries") }
46
+ }
47
+
48
+ // MARK: Package
49
+ @MainActor
50
+ final class Package {
51
+ let targets: [XCCache.Target]
52
+ init(_ dict: [String: [String]]) {
53
+ targets = dict.map { XCCache.Target($0, $1) }
54
+ }
55
+
56
+ var spm: PackageDescription.Package {
57
+ let regularTargets = targets.map(\.spm)
58
+ let binaryTargets = targets.flatMap(\.flattenDeps).unique(by: \.name).compactMap(\.spmBinaryTarget)
59
+ return .init(
60
+ name: "xccache",
61
+ platforms: PLATFORMS,
62
+ products: targets.map(\.spmProduct),
63
+ dependencies: DEPENDENCIES,
64
+ targets: regularTargets + binaryTargets
65
+ )
66
+ }
67
+ }
68
+
69
+ // MARK: Target
70
+ @MainActor
71
+ final class Target {
72
+ let name: String
73
+ let deps: [UmbrellaDependency]
74
+ init(_ name: String, _ deps: [String]) {
75
+ self.name = name
76
+ self.deps = deps.map { UmbrellaDependency($0) }
77
+ }
78
+
79
+ var spmProduct: PackageDescription.Product {
80
+ .library(name: name, targets: [name])
81
+ }
82
+ var spm: PackageDescription.Target {
83
+ .target(name: name, dependencies: flattenDeps.map(\.spm), path: ".Sources/\(name)")
84
+ }
85
+ var flattenDeps: [Dependency] { deps.flatMap(\.toBinariesIfOk).unique(by: \.name) }
86
+ }
87
+
88
+ // MARK: Dependency
89
+ @MainActor
90
+ class Dependency {
91
+ let name: String
92
+ let binary: Bool
93
+ private let bareName: String
94
+
95
+ init(_ name: String) {
96
+ self.name = name
97
+ self.bareName = name.basename.withoutExtenstion
98
+ self.binary = name.hasSuffix(".binary") && Config.binariesDir.appending(path: "\(bareName)/\(bareName).xcframework").exist
99
+ }
100
+ var spm: PackageDescription.Target.Dependency {
101
+ if binary { return .byName(name: name) }
102
+ return .product(name: bareName, package: name.slug)
103
+ }
104
+ var spmBinaryTarget: PackageDescription.Target? {
105
+ binary ? .binaryTarget(name: name, path: "binaries/\(bareName)/\(bareName).xcframework") : nil
106
+ }
107
+ }
108
+
109
+ @MainActor
110
+ class UmbrellaDependency: Dependency {
111
+ let binaries: [Dependency]
112
+ override init(_ name: String) {
113
+ binaries = (PRODUCTS_TO_TARGETS[name.withoutBinary] ?? []).map { Dependency("\($0).binary") }
114
+ super.init(name)
115
+ }
116
+ var toBinariesIfOk: [Dependency] {
117
+ if name.hasSuffix(".binary"), !binaries.isEmpty && binaries.allSatisfy(\.binary) { return binaries }
118
+ return [Dependency(name.withoutBinary)]
119
+ }
120
+ }
121
+ }
122
+
123
+ // MARK: Helpers
124
+ func parseJSON<T>(_ content: String) throws -> T {
125
+ if let data = content.data(using: .utf8), let result = try JSONSerialization.jsonObject(with: data) as? T {
126
+ return result
127
+ }
128
+ throw NSError(domain: "Invalid JSON:\n\(content)", code: 111)
129
+ }
130
+
131
+ extension URL {
132
+ var exist: Bool { FileManager.default.fileExists(atPath: path()) }
133
+ }
134
+
135
+ extension String {
136
+ var slug: String { (self as NSString).deletingLastPathComponent.basename }
137
+ var basename: String { (self as NSString).lastPathComponent }
138
+ var withoutExtenstion: String { (self as NSString).deletingPathExtension }
139
+ var withoutBinary: String { replacing(#/\.binary$/#, with: "") }
140
+ }
141
+
142
+ extension Sequence {
143
+ func unique<T: Hashable>(by: (Element) -> T) -> [Element] {
144
+ var seen: Set<T> = []
145
+ return filter { seen.insert(by($0)).inserted }
146
+ }
147
+ }
@@ -0,0 +1,60 @@
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"] ||= { "targets" => {}, "deps" => {} }
16
+ end
17
+
18
+ def missed?(name)
19
+ missed.include?(name)
20
+ end
21
+
22
+ def missed
23
+ get_cache_data(:missed)
24
+ end
25
+
26
+ def stats
27
+ describe = proc do |type|
28
+ count = get_cache_data(type).count
29
+ total_count = [cache_data.count, 1].max
30
+ percent = (count * 100 / total_count).to_i
31
+ "#{percent}% (#{count}/#{total_count})"
32
+ end
33
+ {
34
+ :hit => describe.call(:hit),
35
+ :missed => describe.call(:missed),
36
+ :ignored => describe.call(:ignored),
37
+ }
38
+ end
39
+
40
+ def print_stats
41
+ hit = get_cache_data(:hit)
42
+ missed = get_cache_data(:missed)
43
+ ignored = get_cache_data(:ignored)
44
+ total_count = cache_data.count
45
+ UI.message <<~DESC
46
+ -------------------------------------------------------------------
47
+ Cache stats
48
+ • Hit (#{hit.count}/#{total_count}): #{hit.to_s.green.dark}
49
+ • Missed (#{missed.count}/#{total_count}): #{missed.to_s.yellow.dark}
50
+ • Ignored (#{ignored.count}/#{total_count}): #{ignored.to_s.yellow.dark}
51
+ -------------------------------------------------------------------
52
+ DESC
53
+ end
54
+
55
+ def get_cache_data(type)
56
+ cache_data.select { |k, v| !k.end_with?(".xccache") && v == type }.keys
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,35 @@
1
+ require "xccache/installer"
2
+
3
+ module XCCache
4
+ class Command
5
+ class Build < Command
6
+ self.summary = "Build packages to xcframeworks"
7
+ def self.options
8
+ [
9
+ ["--sdk=iphonesimulator", "SDKs to build (comma separated)"],
10
+ ["--integrate/no-integrate", "Whether to integrate after building target (default: true)"],
11
+ ["--recursive", "Whether to build their recursive targets if cache-missed (default: false)"],
12
+ ["--skip-resolving-dependencies", "Skip resolving package dependencies"],
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
+ @sdk = argv.option("sdk")
23
+ @should_integrate = argv.flag?("integrate", true)
24
+ @recursive = argv.flag?("recursive", false)
25
+ end
26
+
27
+ def run
28
+ installer = Installer::Build.new(targets: @targets, sdk: @sdk, recursive: @recursive, **@install_options)
29
+ installer.install!
30
+ # Reuse umbrella_pkg from previous installers
31
+ Installer::Use.new(umbrella_pkg: installer.umbrella_pkg, **@install_options).install! if @should_integrate
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,8 +1,42 @@
1
+ require "xccache/spm"
2
+
1
3
  module XCCache
2
4
  class Command
3
5
  class Pkg < Command
4
6
  class Build < Pkg
5
7
  self.summary = "Build a Swift package into an xcframework"
8
+ def self.options
9
+ [
10
+ ["--sdk=foo,bar", "Sdk (iphonesimulator, iphoneos, etc.)"],
11
+ ["--config=foo", "Configuration (debug, release)"],
12
+ ["--out=foo", "Output directory for the xcframework"],
13
+ ["--checksum/no-checksum", "Whether to include checksum to the binary name"],
14
+ ].concat(super)
15
+ end
16
+ self.arguments = [
17
+ CLAide::Argument.new("TARGET", false, true),
18
+ ]
19
+
20
+ def initialize(argv)
21
+ super
22
+ @targets = argv.arguments!
23
+ @sdk = argv.option("sdk")
24
+ @config = argv.option("config")
25
+ @out_dir = argv.option("out")
26
+ @include_checksum = argv.flag?("checksum")
27
+ end
28
+
29
+ def run
30
+ pkg = SPM::Package.new
31
+ pkg.build(
32
+ targets: @targets,
33
+ sdk: @sdk,
34
+ config: @config,
35
+ out_dir: @out_dir,
36
+ checksum: @include_checksum,
37
+ skip_resolve: @skip_resolving_dependencies,
38
+ )
39
+ end
6
40
  end
7
41
  end
8
42
  end
@@ -0,0 +1,13 @@
1
+ require "xccache/installer"
2
+
3
+ module XCCache
4
+ class Command
5
+ class Rollback < Command
6
+ self.summary = "Roll back prebuilt cache for packages"
7
+
8
+ def run
9
+ Installer::Rollback.new.install!
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ require "xccache/installer"
2
+
3
+ module XCCache
4
+ class Command
5
+ class Use < Command
6
+ self.summary = "Use prebuilt cache for packages"
7
+ def self.options
8
+ [
9
+ ["--skip-resolving-dependencies", "Skip resolving package dependencies"],
10
+ ].concat(super)
11
+ end
12
+
13
+ def run
14
+ Installer::Use.new(**@install_options).install!
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,10 +1,20 @@
1
1
  require "claide"
2
+ require "xccache/core/config"
2
3
 
3
4
  module XCCache
4
5
  class Command < CLAide::Command
5
- require "xccache/command/pkg"
6
+ include Config::Mixin
7
+ Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
6
8
 
7
9
  self.abstract_command = true
10
+ self.default_subcommand = "use"
8
11
  self.summary = "xccache - a build caching tool"
12
+
13
+ def initialize(argv)
14
+ super
15
+ config.verbose = verbose unless verbose.nil?
16
+ @skip_resolving_dependencies = argv.flag?("skip-resolving-dependencies")
17
+ @install_options = { :skip_resolving_dependencies => @skip_resolving_dependencies }
18
+ end
9
19
  end
10
20
  end