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.
- checksums.yaml +4 -4
- data/lib/xccache/assets/templates/cachemap.html.template +54 -0
- data/lib/xccache/assets/templates/cachemap.js.template +94 -0
- data/lib/xccache/assets/templates/cachemap.style.css.template +92 -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 +147 -0
- data/lib/xccache/cache/cachemap.rb +60 -0
- data/lib/xccache/command/build.rb +35 -0
- data/lib/xccache/command/pkg/build.rb +34 -0
- data/lib/xccache/command/rollback.rb +13 -0
- data/lib/xccache/command/use.rb +18 -0
- data/lib/xccache/command.rb +11 -1
- data/lib/xccache/core/cacheable.rb +28 -0
- data/lib/xccache/core/config.rb +82 -0
- data/lib/xccache/core/error.rb +7 -0
- data/lib/xccache/core/git.rb +19 -0
- data/lib/xccache/core/hash.rb +21 -0
- data/lib/xccache/core/lockfile.rb +40 -0
- data/lib/xccache/core/log.rb +41 -0
- data/lib/xccache/core/sh.rb +50 -0
- data/lib/xccache/core/syntax/hash.rb +31 -0
- data/lib/xccache/core/syntax/json.rb +16 -0
- data/lib/xccache/core/syntax/yml.rb +16 -0
- data/lib/xccache/core/syntax.rb +1 -0
- data/lib/xccache/core/system.rb +54 -0
- data/lib/xccache/core.rb +1 -0
- data/lib/xccache/framework/slice.rb +183 -0
- data/lib/xccache/framework/xcframework.rb +51 -0
- data/lib/xccache/installer/build.rb +27 -0
- data/lib/xccache/installer/rollback.rb +36 -0
- data/lib/xccache/installer/use.rb +27 -0
- data/lib/xccache/installer.rb +67 -0
- data/lib/xccache/main.rb +3 -1
- data/lib/xccache/spm/desc/base.rb +61 -0
- data/lib/xccache/spm/desc/dep.rb +40 -0
- data/lib/xccache/spm/desc/desc.rb +110 -0
- data/lib/xccache/spm/desc/product.rb +36 -0
- data/lib/xccache/spm/desc/target.rb +138 -0
- data/lib/xccache/spm/desc.rb +1 -0
- data/lib/xccache/spm/mixin.rb +9 -0
- data/lib/xccache/spm/pkg/base.rb +103 -0
- data/lib/xccache/spm/pkg/umbrella/build.rb +30 -0
- data/lib/xccache/spm/pkg/umbrella/cachemap.rb +93 -0
- data/lib/xccache/spm/pkg/umbrella/descs.rb +46 -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.rb +81 -0
- data/lib/xccache/spm/pkg.rb +1 -0
- data/lib/xccache/spm.rb +1 -0
- data/lib/xccache/swift/sdk.rb +30 -0
- data/lib/xccache/swift/swiftc.rb +16 -0
- data/lib/xccache/utils/template.rb +21 -0
- data/lib/xccache/xcodeproj/pkg.rb +69 -0
- data/lib/xccache/xcodeproj/pkg_product_dependency.rb +19 -0
- data/lib/xccache/xcodeproj/project.rb +63 -0
- data/lib/xccache/xcodeproj/target.rb +50 -0
- data/lib/xccache/xcodeproj.rb +2 -0
- metadata +72 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8478aeb0498b7eaa2e6c231f65cf65421d247958a30b2cfca0750e0d8b0603e9
|
4
|
+
data.tar.gz: 4f5266ef2925ce5ca1b06596da4b6aa879c53926eaf2a460638b912187f9316b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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,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,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
|
data/lib/xccache/command.rb
CHANGED
@@ -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
|
-
|
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
|