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
@@ -0,0 +1,164 @@
|
|
1
|
+
require "xccache/spm/desc/base"
|
2
|
+
|
3
|
+
module XCCache
|
4
|
+
module SPM
|
5
|
+
class Package
|
6
|
+
class Target < BaseObject
|
7
|
+
include Cacheable
|
8
|
+
cacheable :recursive_targets, :direct_dependency_targets, :direct_dependencies
|
9
|
+
|
10
|
+
Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
|
11
|
+
|
12
|
+
def xccache?
|
13
|
+
name.end_with?(".xccache")
|
14
|
+
end
|
15
|
+
|
16
|
+
def xccache_id
|
17
|
+
macro? ? "#{full_name}.macro" : full_name
|
18
|
+
end
|
19
|
+
|
20
|
+
def type
|
21
|
+
@type ||= raw["type"].to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
def downcast
|
25
|
+
cls = {
|
26
|
+
:binary => BinaryTarget,
|
27
|
+
:macro => MacroTarget,
|
28
|
+
}[type]
|
29
|
+
cls.nil? ? self : cast_to(cls)
|
30
|
+
end
|
31
|
+
|
32
|
+
def module_name
|
33
|
+
name.c99extidentifier
|
34
|
+
end
|
35
|
+
|
36
|
+
def resource_bundle_name
|
37
|
+
"#{pkg_name}_#{name}.bundle"
|
38
|
+
end
|
39
|
+
|
40
|
+
def flatten_as_targets
|
41
|
+
[self]
|
42
|
+
end
|
43
|
+
|
44
|
+
def sources_path
|
45
|
+
@sources_path ||= begin
|
46
|
+
path = raw["path"] || "Sources/#{name}"
|
47
|
+
root.src_dir / path
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def use_clang?
|
52
|
+
!header_paths.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
def header_paths(options = {})
|
56
|
+
paths = []
|
57
|
+
paths += public_header_paths if options.fetch(:public, true)
|
58
|
+
paths += header_search_paths if options.fetch(:search, false)
|
59
|
+
paths
|
60
|
+
.flat_map { |p| p.glob("**/*.h*") }
|
61
|
+
.map(&:realpath)
|
62
|
+
.uniq
|
63
|
+
end
|
64
|
+
|
65
|
+
def settings
|
66
|
+
raw["settings"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def header_search_paths
|
70
|
+
@header_search_paths ||=
|
71
|
+
settings
|
72
|
+
.filter_map { |h| h.fetch("kind", {})["headerSearchPath"] }
|
73
|
+
.flat_map(&:values)
|
74
|
+
.map { |p| sources_path / p }
|
75
|
+
end
|
76
|
+
|
77
|
+
def public_header_paths
|
78
|
+
@public_header_paths ||= begin
|
79
|
+
res = []
|
80
|
+
implicit_path = sources_path / "include"
|
81
|
+
res << implicit_path unless implicit_path.glob("**/*.h*").empty?
|
82
|
+
res << (sources_path / raw["publicHeadersPath"]) if raw.key?("publicHeadersPath")
|
83
|
+
res
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def resource_paths
|
88
|
+
@resource_paths ||= begin
|
89
|
+
res = raw.fetch("resources", []).map { |h| sources_path / h["path"] }
|
90
|
+
# Refer to the following link for the implicit resources
|
91
|
+
# https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package#Add-resource-files
|
92
|
+
implicit = sources_path.glob("*.{xcassets,xib,storyboard,xcdatamodeld,lproj}")
|
93
|
+
res + implicit
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def recursive_targets(platform: nil)
|
98
|
+
children = direct_dependency_targets(platform: platform)
|
99
|
+
children += children.flat_map { |t| t.macro? ? [t] : t.recursive_targets(platform: platform) }
|
100
|
+
children.uniq
|
101
|
+
end
|
102
|
+
|
103
|
+
def direct_dependencies(platform: nil)
|
104
|
+
raw["dependencies"].flat_map do |hash|
|
105
|
+
dep_types = ["byName", "target", "product"]
|
106
|
+
if (dep_type = dep_types.intersection(hash.keys).first).nil?
|
107
|
+
raise GeneralError, "Unexpected dependency type. Must be one of #{dep_types}. Hash: #{hash}"
|
108
|
+
end
|
109
|
+
next [] unless match_platform?(hash[dep_type][-1], platform)
|
110
|
+
pkg_name = hash[dep_type][1] if dep_type == "product"
|
111
|
+
find_deps(hash[dep_type][0], pkg_name, dep_type)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def direct_dependency_targets(platform: nil)
|
116
|
+
direct_dependencies(platform: platform).flat_map(&:flatten_as_targets).uniq
|
117
|
+
end
|
118
|
+
|
119
|
+
def match_platform?(_condition, _platform)
|
120
|
+
true # FIXME: Handle this
|
121
|
+
end
|
122
|
+
|
123
|
+
def macro?
|
124
|
+
type == :macro
|
125
|
+
end
|
126
|
+
|
127
|
+
def binary?
|
128
|
+
type == :binary
|
129
|
+
end
|
130
|
+
|
131
|
+
def binary_path
|
132
|
+
sources_path if binary?
|
133
|
+
end
|
134
|
+
|
135
|
+
def local_binary_path
|
136
|
+
binary_path if binary? && root.local?
|
137
|
+
end
|
138
|
+
|
139
|
+
def checksum
|
140
|
+
@checksum ||= root.git&.sha || sources_path.checksum
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def find_deps(name, pkg_name, dep_type)
|
146
|
+
# If `dep_type` is `target` -> constrained within current pkg only
|
147
|
+
# If `dep_type` is `product` -> `pkg_name` must be present
|
148
|
+
# If `dep_type` is `byName` -> it's either from this pkg, or its children/dependencies
|
149
|
+
res = []
|
150
|
+
descs = pkg_name.nil? ? [root] + root.uniform_dependencies : [pkg_desc_of(pkg_name)]
|
151
|
+
descs.each do |desc|
|
152
|
+
by_target = -> { desc.targets.select { |t| t.name == name } }
|
153
|
+
by_product = -> { desc.products.select { |t| t.name == name } }
|
154
|
+
return by_target.call if dep_type == "target"
|
155
|
+
return by_product.call if dep_type == "product"
|
156
|
+
return res unless (res = by_target.call).empty?
|
157
|
+
return res unless (res = by_product.call).empty?
|
158
|
+
end
|
159
|
+
[]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative "build"
|
2
|
+
|
3
|
+
module XCCache
|
4
|
+
module SPM
|
5
|
+
class Macro < Buildable
|
6
|
+
attr_reader :macosx_sdk
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
super
|
10
|
+
@library_evolution = false # swift-syntax is not compatible with library evolution
|
11
|
+
@macosx_sdk = Swift::Sdk.new(:macosx)
|
12
|
+
end
|
13
|
+
|
14
|
+
def build(_options = {})
|
15
|
+
# NOTE: Building macro binary is tricky...
|
16
|
+
# --------------------------------------------------------------------------------
|
17
|
+
# Consider this manifest config: .target(Macro) -> .macro(MacroImpl)
|
18
|
+
# where `.target(Macro)` contains the interfaces
|
19
|
+
# and `.target(MacroImpl)` contains the implementation
|
20
|
+
# --------------------------------------------------------------------------------
|
21
|
+
# Building `.macro(MacroImpl)` does not produce the tool binary (MacroImpl-tool)... Only `.o` files.
|
22
|
+
# Yet, linking those files are exhaustive due to many dependencies in swift-syntax
|
23
|
+
# Luckily, building `.target(Macro)` does produce the tool binary.
|
24
|
+
# -> WORKAROUND: Find the associated regular target and build it, then collect the tool binary
|
25
|
+
# ---------------------------------------------------------------------------------
|
26
|
+
associated_target = pkg_desc.targets.find { |t| t.direct_dependency_targets.include?(pkg_target) }
|
27
|
+
UI.message(
|
28
|
+
"#{name.yellow.dark} is a macro target. " \
|
29
|
+
"Will build the associated target #{associated_target.name.dark} to get the tool binary."
|
30
|
+
)
|
31
|
+
swift_build(target: associated_target.name)
|
32
|
+
binary_path = products_dir / "#{module_name}-tool"
|
33
|
+
raise GeneralError, "Tool binary not exist at: #{binary_path}" unless binary_path.exist?
|
34
|
+
binary_path.copy(to: path)
|
35
|
+
FileUtils.chmod("+x", path)
|
36
|
+
UI.info("-> Macro binary: #{path.to_s.dark}")
|
37
|
+
end
|
38
|
+
|
39
|
+
def products_dir
|
40
|
+
@products_dir ||= pkg_dir / ".build" / macosx_sdk.triple / config
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "json"
|
2
|
+
require "xccache/spm/xcframework/slice"
|
3
|
+
require "xccache/spm/xcframework/xcframework"
|
4
|
+
require "xccache/spm/xcframework/metadata"
|
5
|
+
require "xccache/swift/sdk"
|
6
|
+
|
7
|
+
module XCCache
|
8
|
+
module SPM
|
9
|
+
class Package
|
10
|
+
include Cacheable
|
11
|
+
cacheable :pkg_desc_of_target
|
12
|
+
|
13
|
+
attr_reader :root_dir
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
@root_dir = Pathname(options[:root_dir] || ".").expand_path
|
17
|
+
@warn_if_not_direct_target = options.fetch(:warn_if_not_direct_target, true)
|
18
|
+
end
|
19
|
+
|
20
|
+
def build(options = {})
|
21
|
+
validate!
|
22
|
+
targets = options.delete(:targets) || []
|
23
|
+
raise GeneralError, "No targets were specified" if targets.empty?
|
24
|
+
|
25
|
+
targets.map { |t| t.split("/")[-1] }.each_with_index do |t, i|
|
26
|
+
UI.section("\n▶ Building target: #{t} (#{i + 1}/#{targets.count})".bold.magenta) do
|
27
|
+
build_target(**options, target: t)
|
28
|
+
rescue StandardError => e
|
29
|
+
UI.error("Failed to build target: #{t}. Error: #{e}")
|
30
|
+
raise e unless Config.instance.ignore_build_errors?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_target(target: nil, sdks: nil, config: nil, out_dir: nil, **options)
|
36
|
+
target_pkg_desc = pkg_desc_of_target(target, skip_resolving_dependencies: options[:skip_resolving_dependencies])
|
37
|
+
if target_pkg_desc.binary_targets.any? { |t| t.name == target }
|
38
|
+
return UI.warn("Target #{target} is a binary target -> no need to build")
|
39
|
+
end
|
40
|
+
|
41
|
+
target = target_pkg_desc.get_target(target)
|
42
|
+
|
43
|
+
out_dir = Pathname(out_dir || ".")
|
44
|
+
out_dir /= target.name if options[:checksum]
|
45
|
+
basename = options[:checksum] ? "#{target.name}-#{target.checksum}" : target.name
|
46
|
+
basename += target.macro? ? ".macro" : ".xcframework"
|
47
|
+
|
48
|
+
Dir.create_tmpdir do |_tmpdir|
|
49
|
+
cls = target.macro? ? Macro : XCFramework
|
50
|
+
cls.new(
|
51
|
+
name: target.name,
|
52
|
+
pkg_dir: root_dir,
|
53
|
+
config: config,
|
54
|
+
sdks: sdks,
|
55
|
+
path: out_dir / basename,
|
56
|
+
tmpdir: Dir.create_tmpdir,
|
57
|
+
pkg_desc: target_pkg_desc,
|
58
|
+
library_evolution: options[:library_evolution],
|
59
|
+
).build(**options)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def resolve(force: false)
|
64
|
+
return if @resolved && !force
|
65
|
+
|
66
|
+
UI.section("Resolving package dependencies (package: #{root_dir.basename})", timing: true) do
|
67
|
+
Sh.run("swift package resolve --package-path #{root_dir} 2>&1")
|
68
|
+
end
|
69
|
+
@resolved = true
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def validate!
|
75
|
+
return unless root_dir.glob("Package*.swift").empty?
|
76
|
+
raise GeneralError, "No Package.swift in #{root_dir}. Are you sure you're running on a package dir?"
|
77
|
+
end
|
78
|
+
|
79
|
+
def pkg_desc_of_target(name, skip_resolving_dependencies: false)
|
80
|
+
# The current package contains the given target
|
81
|
+
return pkg_desc if pkg_desc.has_target?(name)
|
82
|
+
|
83
|
+
if @warn_if_not_direct_target
|
84
|
+
UI.message(
|
85
|
+
"#{name.yellow.dark} is not a direct target of package #{root_dir.basename.to_s.dark} " \
|
86
|
+
"-> trigger from dependencies"
|
87
|
+
)
|
88
|
+
end
|
89
|
+
# Otherwise, it's inside one of the dependencies. Need to resolve then find it
|
90
|
+
resolve unless skip_resolving_dependencies
|
91
|
+
|
92
|
+
@descs ||= if Config.instance.in_installation?
|
93
|
+
then Description.descs_in_metadata_dir[0]
|
94
|
+
else
|
95
|
+
Description.descs_in_dir(Pathname(".").expand_path)[0]
|
96
|
+
end
|
97
|
+
desc = @descs.find { |d| d.has_target?(name) }
|
98
|
+
return desc unless desc.nil?
|
99
|
+
raise GeneralError, "Cannot find package with the given target #{name}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def pkg_desc
|
103
|
+
@pkg_desc ||= Description.in_dir(root_dir)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module XCCache
|
2
|
+
module SPM
|
3
|
+
class Package
|
4
|
+
module UmbrellaBuildMixin
|
5
|
+
def build(options = {})
|
6
|
+
to_build = targets_to_build(options)
|
7
|
+
return UI.warn("Detected no targets to build among cache-missed targets") if to_build.empty?
|
8
|
+
|
9
|
+
UI.info("-> Targets to build: #{to_build.to_s.bold}")
|
10
|
+
super(options.merge(:targets => to_build))
|
11
|
+
sync_cachemap(sdks: options[:sdks])
|
12
|
+
end
|
13
|
+
|
14
|
+
def targets_to_build(options)
|
15
|
+
items = options[:targets] || []
|
16
|
+
items = config.cachemap.missed.map { |x| File.basename(x) } if items.empty?
|
17
|
+
targets = @descs.flat_map(&:targets).select { |t| items.include?(t.name) }
|
18
|
+
if options[:recursive]
|
19
|
+
UI.message("Will include cache-missed recursive targets")
|
20
|
+
targets += targets.flat_map do |t|
|
21
|
+
t.recursive_targets.select { |x| config.cachemap.missed?(x.full_name) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
# TODO: Sort by number of dependents
|
25
|
+
targets.map(&:full_name).uniq
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module XCCache
|
2
|
+
module SPM
|
3
|
+
class Package
|
4
|
+
module UmbrellaCachemapMixin
|
5
|
+
def sync_cachemap(sdks: [])
|
6
|
+
UI.section("Syncing cachemap (sdks: #{sdks.map(&:name)})")
|
7
|
+
nodes, edges, parents = xccache_desc.traverse
|
8
|
+
cache_data = gen_cache_data(nodes, parents, sdks)
|
9
|
+
targets_data, macros_data, deps_data = {}, {}, {}
|
10
|
+
xccache_desc.targets.each do |agg_target|
|
11
|
+
targets, macros = [], []
|
12
|
+
agg_target.direct_dependencies.each do |d|
|
13
|
+
all_hit = d.recursive_targets.all? { |t| cache_data[t] == :hit }
|
14
|
+
# If any associated targets is missed -> use original product form
|
15
|
+
# Otherwise, replace with recursive targets' binaries
|
16
|
+
deps_data[d.full_name] = d.recursive_targets.map(&:xccache_id)
|
17
|
+
targets << (all_hit ? "#{d.full_name}.binary" : d.full_name)
|
18
|
+
macros += d.recursive_targets.select(&:macro?).map(&:full_name) if all_hit
|
19
|
+
end
|
20
|
+
targets_data[agg_target.name] = targets.uniq.sort_by(&:downcase)
|
21
|
+
macros_data[agg_target.name] = macros.uniq
|
22
|
+
end
|
23
|
+
|
24
|
+
config.cachemap.raw = {
|
25
|
+
"manifest" => {
|
26
|
+
"targets" => targets_data,
|
27
|
+
"macros" => macros_data,
|
28
|
+
"deps" => deps_data,
|
29
|
+
},
|
30
|
+
"cache" => cache_data.transform_keys(&:full_name),
|
31
|
+
"depgraph" => {
|
32
|
+
"nodes" => nodes.map { |x| target_to_cytoscape_node(x, cache_data) },
|
33
|
+
"edges" => edges.map { |x, y| { :source => x.full_name, :target => y.full_name } },
|
34
|
+
},
|
35
|
+
}
|
36
|
+
config.cachemap.save
|
37
|
+
config.cachemap.print_stats
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def gen_cache_data(nodes, parents, sdks)
|
43
|
+
result = nodes.to_h do |node|
|
44
|
+
res = if config.ignore?(node.full_name) then :ignored
|
45
|
+
else
|
46
|
+
verify_binary?(node, sdks) ? :hit : :missed
|
47
|
+
end
|
48
|
+
[node, res]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Propagate cache miss
|
52
|
+
to_visit = result.select { |_, v| %i[missed ignore].include?(v) }.keys
|
53
|
+
visited = Set.new
|
54
|
+
until to_visit.empty?
|
55
|
+
node = to_visit.pop
|
56
|
+
next if visited.include?(node)
|
57
|
+
visited << node
|
58
|
+
result[node] = :missed if result[node] == :hit
|
59
|
+
to_visit += parents[node] if parents.key?(node)
|
60
|
+
end
|
61
|
+
result.reject { |k, _| k.name.end_with?(".xccache") }
|
62
|
+
end
|
63
|
+
|
64
|
+
def verify_binary?(target, sdks)
|
65
|
+
return true if target.binary?
|
66
|
+
|
67
|
+
bpath = binary_path(target.xccache_id)
|
68
|
+
bpath_with_checksum = binary_path(target.xccache_id, checksum: target.checksum, in_repo: true)
|
69
|
+
|
70
|
+
check = proc do
|
71
|
+
# For macro, we just need the tool binary to exist
|
72
|
+
# For regular targets, the xcframework must satisfy the sdk constraints (ie. containing all the slices)
|
73
|
+
next bpath_with_checksum.exist? if target.macro?
|
74
|
+
|
75
|
+
metadata = XCFramework::Metadata.new(bpath_with_checksum / "Info.plist")
|
76
|
+
expected_triples = sdks.map { |sdk| sdk.triple(without_vendor: true) }
|
77
|
+
missing_triples = expected_triples - metadata.triples
|
78
|
+
missing_triples.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
# If requirements are meet, create symlink `A-abc123.xcframework` -> `A.framework`
|
82
|
+
# Otherwise, remove symlink `A.xcframework`
|
83
|
+
if check.call
|
84
|
+
bpath_with_checksum.symlink_to(bpath)
|
85
|
+
elsif bpath.exist?
|
86
|
+
bpath.rmtree
|
87
|
+
end
|
88
|
+
bpath.exist?
|
89
|
+
end
|
90
|
+
|
91
|
+
def binary_path(name, checksum: nil, in_repo: false)
|
92
|
+
suffix = checksum.nil? ? "" : "-#{checksum}"
|
93
|
+
ext = File.extname(name) == ".macro" ? ".macro" : ".xcframework"
|
94
|
+
binaries_dir = in_repo ? config.spm_repo_dir : config.spm_binaries_dir
|
95
|
+
p = binaries_dir / File.basename(name, ".*")
|
96
|
+
p / "#{p.basename}#{suffix}#{ext}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def target_to_cytoscape_node(x, cache_data)
|
100
|
+
h = { :id => x.full_name, :cache => cache_data[x] }
|
101
|
+
h[:type] = if x.name.end_with?(".xccache") then "agg"
|
102
|
+
elsif x.macro? then "macro" end
|
103
|
+
h[:checksum] = x.checksum
|
104
|
+
h[:binary] = binary_path(x.xccache_id) if cache_data[x] == :hit
|
105
|
+
h
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module XCCache
|
2
|
+
module SPM
|
3
|
+
class Package
|
4
|
+
module UmbrellaDescsMixin
|
5
|
+
def gen_metadata
|
6
|
+
UI.section("Generating metadata of packages", timing: true) do
|
7
|
+
@descs, @descs_by_name = Description.descs_in_dir(root_dir, save_to_dir: config.spm_metadata_dir)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def xccache_desc
|
12
|
+
@xccache_desc ||= desc_of("xccache")
|
13
|
+
end
|
14
|
+
|
15
|
+
def targets_of_products(products)
|
16
|
+
products = [products] if products.is_a?(String)
|
17
|
+
products.flat_map { |x| desc_of(x).targets_of_products(File.basename(x)) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def dependency_targets_of_products(products)
|
21
|
+
products = [products] if products.is_a?(String)
|
22
|
+
products.flat_map { |p| @dependency_targets_by_products[p] || [p] }.uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
def desc_of(d)
|
26
|
+
@descs_by_name[d.split("/").first]
|
27
|
+
end
|
28
|
+
|
29
|
+
def binary_targets
|
30
|
+
@descs_by_name.values.flatten.uniq.flat_map(&:binary_targets)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module XCCache
|
2
|
+
module SPM
|
3
|
+
class Package
|
4
|
+
module UmbrellaManifestMixin
|
5
|
+
def write_manifest(no_cache: false)
|
6
|
+
UI.info("Writing Package.swift (package: #{root_dir.basename})")
|
7
|
+
Template.new("umbrella.Package.swift").render(
|
8
|
+
{
|
9
|
+
:timestamp => Time.new.strftime("%F %T"),
|
10
|
+
:json => manifest_targets_json(no_cache: no_cache),
|
11
|
+
:products_to_targets => manifest_products_to_targets_json(no_cache: no_cache),
|
12
|
+
:platforms => manifest_platforms,
|
13
|
+
:dependencies => manifest_pkg_dependencies,
|
14
|
+
:swift_version => Swift::Swiftc.version_without_patch,
|
15
|
+
},
|
16
|
+
save_to: root_dir / "Package.swift",
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def manifest_targets_json(no_cache: false)
|
21
|
+
data = no_cache ? config.lockfile.targets_data : config.cachemap.manifest_data["targets"]
|
22
|
+
JSON.pretty_generate(data)
|
23
|
+
end
|
24
|
+
|
25
|
+
def manifest_products_to_targets_json(no_cache: false)
|
26
|
+
data = no_cache ? {} : config.cachemap.manifest_data["deps"]
|
27
|
+
JSON.pretty_generate(data)
|
28
|
+
end
|
29
|
+
|
30
|
+
def manifest_pkg_dependencies
|
31
|
+
decl = proc do |hash|
|
32
|
+
if (path_from_root = hash["path_from_root"])
|
33
|
+
absolute_path = (Pathname(".") / path_from_root).expand_path
|
34
|
+
next ".package(path: \"#{absolute_path}\")"
|
35
|
+
end
|
36
|
+
|
37
|
+
requirement = hash["requirement"]
|
38
|
+
case requirement["kind"]
|
39
|
+
when "upToNextMajorVersion"
|
40
|
+
opt = ".upToNextMajor(from: \"#{requirement['minimumVersion']}\")"
|
41
|
+
when "upToNextMinorVersion"
|
42
|
+
opt = ".upToNextMinor(from: \"#{requirement['minimumVersion']}\")"
|
43
|
+
when "exactVersion"
|
44
|
+
opt = "exact: \"#{requirement['version']}\""
|
45
|
+
when "branch"
|
46
|
+
opt = "branch: \"#{requirement['branch']}\""
|
47
|
+
when "revision"
|
48
|
+
opt = "revision: \"#{requirement['revision']}\""
|
49
|
+
when "versionRange"
|
50
|
+
opt = "\"#{requirement['minimumVersion']}\"..<\"#{requirement['maximumVersion']}\""
|
51
|
+
end
|
52
|
+
".package(url: \"#{hash['repositoryURL']}\", #{opt})"
|
53
|
+
end
|
54
|
+
|
55
|
+
config.lockfile.pkgs.map { |h| " #{decl.call(h)}," }.join("\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
def manifest_platforms
|
59
|
+
@manifest_platforms ||= begin
|
60
|
+
to_spm_platform = {
|
61
|
+
:ios => "iOS",
|
62
|
+
:macos => "macOS",
|
63
|
+
:osx => "macOS",
|
64
|
+
:tvos => "tvOS",
|
65
|
+
:watchos => "watchOS",
|
66
|
+
:visionos => "visionOS",
|
67
|
+
}
|
68
|
+
hash = {}
|
69
|
+
config.project_targets.each do |t|
|
70
|
+
platform = to_spm_platform[t.platform_name]
|
71
|
+
hash[platform] ||= []
|
72
|
+
hash[platform] << t.deployment_target.split(".")[0]
|
73
|
+
end
|
74
|
+
hash
|
75
|
+
.transform_values(&:min)
|
76
|
+
.map { |platform, version| " .#{platform}(.v#{version})," }
|
77
|
+
.join("\n")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module XCCache
|
2
|
+
module SPM
|
3
|
+
class Package
|
4
|
+
module UmbrellaVizMixin
|
5
|
+
def gen_cachemap_viz
|
6
|
+
stats = config.cachemap.stats
|
7
|
+
html_path = config.sandbox / "cachemap.html"
|
8
|
+
js_path = Dir.prepare(config.sandbox / "assets") / "cachemap.js"
|
9
|
+
css_path = config.sandbox / "assets" / "style.css"
|
10
|
+
|
11
|
+
root_dir = Pathname(".").expand_path
|
12
|
+
to_relative = proc do |p|
|
13
|
+
p.to_s.start_with?(root_dir.to_s) ? p.relative_path_from(root_dir).to_s : p.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
UI.info("Cachemap visualization: #{html_path}")
|
17
|
+
Template.new("cachemap.html").render(
|
18
|
+
{
|
19
|
+
:root_dir => root_dir.to_s,
|
20
|
+
:root_dir_short => root_dir.basename.to_s,
|
21
|
+
:lockfile_path => config.lockfile.path.to_s,
|
22
|
+
:lockfile_path_short => to_relative.call(config.lockfile.path),
|
23
|
+
:binaries_dir => config.spm_binaries_dir.to_s,
|
24
|
+
:binaries_dir_short => to_relative.call(config.spm_binaries_dir),
|
25
|
+
:desc_hit => stats[:hit],
|
26
|
+
:desc_missed => stats[:missed],
|
27
|
+
:desc_ignored => stats[:ignored],
|
28
|
+
},
|
29
|
+
save_to: html_path
|
30
|
+
)
|
31
|
+
Template.new("cachemap.js").render(
|
32
|
+
{ :json => JSON.pretty_generate(config.cachemap.depgraph_data) },
|
33
|
+
save_to: js_path
|
34
|
+
)
|
35
|
+
Template.new("cachemap.style.css").render(save_to: css_path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module XCCache
|
2
|
+
module SPM
|
3
|
+
class Package
|
4
|
+
module UmbrellaXCConfigsMixin
|
5
|
+
def gen_xcconfigs
|
6
|
+
UI.section("Generating xcconfigs") do
|
7
|
+
macros_config_by_targets.each do |target, hash|
|
8
|
+
xcconfig_path = config.spm_xcconfig_dir / "#{target}.xcconfig"
|
9
|
+
UI.message("XCConfig of target #{target} at: #{xcconfig_path}")
|
10
|
+
Xcodeproj::Config.new(hash).save_as(xcconfig_path)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def macros_config_by_targets
|
18
|
+
config.cachemap.manifest_data["macros"].to_h do |target, macros|
|
19
|
+
swift_flags = macros.map do |m|
|
20
|
+
basename = File.basename(m, ".*")
|
21
|
+
binary_path = config.spm_binaries_dir / basename / "#{basename}.macro"
|
22
|
+
"-load-plugin-executable #{binary_path}##{basename}"
|
23
|
+
end
|
24
|
+
hash = { "OTHER_SWIFT_FLAGS" => "$(inherited) #{swift_flags.join(' ')}" }
|
25
|
+
[File.basename(target, ".*"), hash]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|