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.
- checksums.yaml +4 -4
- data/lib/xccache/assets/templates/cachemap.html.template +55 -0
- data/lib/xccache/assets/templates/cachemap.js.template +95 -0
- data/lib/xccache/assets/templates/cachemap.style.css.template +94 -0
- data/lib/xccache/assets/templates/framework.info.plist.template +25 -0
- data/lib/xccache/assets/templates/framework.modulemap.template +6 -0
- data/lib/xccache/assets/templates/resource_bundle_accessor.m.template +27 -0
- data/lib/xccache/assets/templates/resource_bundle_accessor.swift.template +24 -0
- data/lib/xccache/assets/templates/umbrella.Package.swift.template +183 -0
- data/lib/xccache/cache/cachemap.rb +56 -0
- data/lib/xccache/command/base.rb +29 -0
- data/lib/xccache/command/build.rb +41 -0
- data/lib/xccache/command/pkg/build.rb +30 -0
- data/lib/xccache/command/pkg.rb +1 -0
- data/lib/xccache/command/remote/pull.rb +15 -0
- data/lib/xccache/command/remote/push.rb +15 -0
- data/lib/xccache/command/remote.rb +39 -0
- data/lib/xccache/command/rollback.rb +14 -0
- data/lib/xccache/command/use.rb +19 -0
- data/lib/xccache/command.rb +27 -1
- data/lib/xccache/core/cacheable.rb +28 -0
- data/lib/xccache/core/config.rb +110 -0
- data/lib/xccache/core/error.rb +7 -0
- data/lib/xccache/core/git.rb +27 -0
- data/lib/xccache/core/hash.rb +21 -0
- data/lib/xccache/core/lockfile.rb +40 -0
- data/lib/xccache/core/log.rb +51 -0
- data/lib/xccache/core/parallel.rb +10 -0
- data/lib/xccache/core/sh.rb +51 -0
- data/lib/xccache/core/syntax/hash.rb +31 -0
- data/lib/xccache/core/syntax/json.rb +16 -0
- data/lib/xccache/core/syntax/plist.rb +17 -0
- data/lib/xccache/core/syntax/yml.rb +16 -0
- data/lib/xccache/core/syntax.rb +1 -0
- data/lib/xccache/core/system.rb +62 -0
- data/lib/xccache/core.rb +1 -0
- data/lib/xccache/installer/build.rb +23 -0
- data/lib/xccache/installer/rollback.rb +37 -0
- data/lib/xccache/installer/use.rb +28 -0
- data/lib/xccache/installer.rb +113 -0
- data/lib/xccache/main.rb +3 -1
- data/lib/xccache/spm/build.rb +53 -0
- data/lib/xccache/spm/desc/base.rb +68 -0
- data/lib/xccache/spm/desc/dep.rb +40 -0
- data/lib/xccache/spm/desc/desc.rb +126 -0
- data/lib/xccache/spm/desc/product.rb +36 -0
- data/lib/xccache/spm/desc/target/binary.rb +8 -0
- data/lib/xccache/spm/desc/target/macro.rb +8 -0
- data/lib/xccache/spm/desc/target.rb +164 -0
- data/lib/xccache/spm/desc.rb +1 -0
- data/lib/xccache/spm/macro.rb +44 -0
- data/lib/xccache/spm/mixin.rb +12 -0
- data/lib/xccache/spm/pkg/base.rb +107 -0
- data/lib/xccache/spm/pkg/umbrella/build.rb +30 -0
- data/lib/xccache/spm/pkg/umbrella/cachemap.rb +110 -0
- data/lib/xccache/spm/pkg/umbrella/descs.rb +35 -0
- data/lib/xccache/spm/pkg/umbrella/manifest.rb +83 -0
- data/lib/xccache/spm/pkg/umbrella/viz.rb +40 -0
- data/lib/xccache/spm/pkg/umbrella/xcconfigs.rb +31 -0
- data/lib/xccache/spm/pkg/umbrella.rb +91 -0
- data/lib/xccache/spm/pkg.rb +1 -0
- data/lib/xccache/spm/xcframework/metadata.rb +41 -0
- data/lib/xccache/spm/xcframework/slice.rb +180 -0
- data/lib/xccache/spm/xcframework/xcframework.rb +56 -0
- data/lib/xccache/spm/xcframework.rb +2 -0
- data/lib/xccache/spm.rb +1 -0
- data/lib/xccache/storage/base.rb +26 -0
- data/lib/xccache/storage/git.rb +46 -0
- data/lib/xccache/storage/s3.rb +53 -0
- data/lib/xccache/storage.rb +1 -0
- data/lib/xccache/swift/sdk.rb +49 -0
- data/lib/xccache/swift/swiftc.rb +16 -0
- data/lib/xccache/utils/template.rb +21 -0
- data/lib/xccache/xcodeproj/build_configuration.rb +20 -0
- data/lib/xccache/xcodeproj/config.rb +9 -0
- data/lib/xccache/xcodeproj/file_system_synchronized_root_group.rb +17 -0
- data/lib/xccache/xcodeproj/group.rb +26 -0
- data/lib/xccache/xcodeproj/pkg.rb +73 -0
- data/lib/xccache/xcodeproj/pkg_product_dependency.rb +19 -0
- data/lib/xccache/xcodeproj/project.rb +83 -0
- data/lib/xccache/xcodeproj/target.rb +52 -0
- data/lib/xccache/xcodeproj.rb +2 -0
- metadata +107 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f65f29ba5115620a4f02708c9bffea0dc76d03665970232a580c22f6659df51
|
4
|
+
data.tar.gz: 7e98a3268e17d0198762c31d631b6e27516e3d4e2e851c5aeeaf33de28e2b5fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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,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
|