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
@@ -0,0 +1,93 @@
|
|
1
|
+
module XCCache
|
2
|
+
module SPM
|
3
|
+
class Package
|
4
|
+
module UmbrellaCachemapMixin
|
5
|
+
def sync_cachemap
|
6
|
+
UI.section("Syncing cachemap")
|
7
|
+
nodes, edges, parents = xccache_desc.traverse
|
8
|
+
cache_data = gen_cache_data(nodes, parents)
|
9
|
+
targets_data, deps_data = {}, {}
|
10
|
+
xccache_desc.targets.each do |agg_target|
|
11
|
+
targets_data[agg_target.name] = agg_target.direct_dependencies.flat_map do |d|
|
12
|
+
# If any associated targets is missed -> use original product form
|
13
|
+
# Otherwise, replace with recursive targets' binaries
|
14
|
+
deps_data[d.full_name] = d.recursive_targets.map(&:full_name)
|
15
|
+
if d.recursive_targets.all? { |t| cache_data[t] == :hit }
|
16
|
+
"#{d.full_name}.binary"
|
17
|
+
else
|
18
|
+
d.full_name
|
19
|
+
end
|
20
|
+
end.uniq.sort_by(&:downcase)
|
21
|
+
end
|
22
|
+
|
23
|
+
config.cachemap.raw = {
|
24
|
+
"manifest" => {
|
25
|
+
"targets" => targets_data,
|
26
|
+
"deps" => deps_data,
|
27
|
+
},
|
28
|
+
"cache" => cache_data.transform_keys(&:full_name),
|
29
|
+
"depgraph" => {
|
30
|
+
"nodes" => nodes.map { |x| target_to_cytoscape_node(x, cache_data) },
|
31
|
+
"edges" => edges.map { |x, y| { :source => x.full_name, :target => y.full_name } },
|
32
|
+
},
|
33
|
+
}
|
34
|
+
config.cachemap.save
|
35
|
+
config.cachemap.print_stats
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def gen_cache_data(nodes, parents)
|
41
|
+
result = nodes.to_h do |node|
|
42
|
+
res = if config.ignore?(node.full_name) then :ignored
|
43
|
+
else
|
44
|
+
verify_binary?(node) ? :hit : :missed
|
45
|
+
end
|
46
|
+
[node, res]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Propagate cache miss
|
50
|
+
to_visit = result.select { |_, v| %i[missed ignore].include?(v) }.keys
|
51
|
+
visited = Set.new
|
52
|
+
until to_visit.empty?
|
53
|
+
node = to_visit.pop
|
54
|
+
next if visited.include?(node)
|
55
|
+
visited << node
|
56
|
+
result[node] = :missed if result[node] == :hit
|
57
|
+
to_visit += parents[node] if parents.key?(node)
|
58
|
+
end
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
def verify_binary?(target)
|
63
|
+
return true if target.binary?
|
64
|
+
|
65
|
+
bpath = binary_path(target.name)
|
66
|
+
bpath_with_checksum = binary_path(target.name, checksum: target.root.checksum)
|
67
|
+
# If checksum matches, create symlink from `A-abc123.xcframework` -> `A.framework`
|
68
|
+
# Otherwise, remove symlink `A.xcframework`
|
69
|
+
if bpath_with_checksum.exist?
|
70
|
+
bpath_with_checksum.symlink_to(bpath)
|
71
|
+
elsif bpath.exist?
|
72
|
+
bpath.rmtree
|
73
|
+
end
|
74
|
+
bpath_with_checksum.exist?
|
75
|
+
end
|
76
|
+
|
77
|
+
def binary_path(name, checksum: nil)
|
78
|
+
suffix = checksum.nil? ? "" : "-#{checksum}"
|
79
|
+
p = config.spm_binaries_frameworks_dir / File.basename(name, ".binary")
|
80
|
+
p / "#{p.basename}#{suffix}.xcframework"
|
81
|
+
end
|
82
|
+
|
83
|
+
def target_to_cytoscape_node(x, cache_data)
|
84
|
+
h = { :id => x.full_name, :cache => cache_data[x] }
|
85
|
+
h[:type] = "agg" if x.name.end_with?(".xccache")
|
86
|
+
h[:checksum] = x.root.checksum
|
87
|
+
h[:binary] = binary_path(x.name) if cache_data[x] == :hit
|
88
|
+
h
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module XCCache
|
2
|
+
module SPM
|
3
|
+
class Package
|
4
|
+
module UmbrellaDescsMixin
|
5
|
+
def gen_metadata
|
6
|
+
UI.section("Generating metadata of packages") do
|
7
|
+
dirs = [root_dir] + root_dir.glob(".build/checkouts/*").reject { |p| p.glob("Package*.swift").empty? }
|
8
|
+
dirs.each do |dir|
|
9
|
+
desc = Description.in_dir(dir, save_to_dir: config.spm_metadata_dir)
|
10
|
+
next if desc.nil?
|
11
|
+
|
12
|
+
desc.retrieve_pkg_desc = proc { |name| @descs_by_name[name] }
|
13
|
+
desc.save
|
14
|
+
desc.save(to: desc.path.parent / "#{desc.name}.json") if desc.name != dir.basename.to_s
|
15
|
+
@descs << desc
|
16
|
+
@descs_by_name[desc.name] = desc
|
17
|
+
@descs_by_name[dir.basename.to_s] = desc
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def xccache_desc
|
23
|
+
@xccache_desc ||= desc_of("xccache")
|
24
|
+
end
|
25
|
+
|
26
|
+
def targets_of_products(products)
|
27
|
+
products = [products] if products.is_a?(String)
|
28
|
+
products.flat_map { |x| desc_of(x).targets_of_products(File.basename(x)) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def dependency_targets_of_products(products)
|
32
|
+
products = [products] if products.is_a?(String)
|
33
|
+
products.flat_map { |p| @dependency_targets_by_products[p] || [p] }.uniq
|
34
|
+
end
|
35
|
+
|
36
|
+
def desc_of(d)
|
37
|
+
@descs_by_name[d.split("/").first]
|
38
|
+
end
|
39
|
+
|
40
|
+
def binary_targets
|
41
|
+
@descs_by_name.values.flatten.uniq.flat_map(&:binary_targets)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
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.to_s.dark})")
|
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.message("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_frameworks_dir.to_s,
|
24
|
+
:binaries_dir_short => to_relative.call(config.spm_binaries_frameworks_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,81 @@
|
|
1
|
+
require "xccache/swift/swiftc"
|
2
|
+
require "xccache/utils/template"
|
3
|
+
require "xccache/cache/cachemap"
|
4
|
+
require "xccache/spm/pkg/base"
|
5
|
+
|
6
|
+
Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
|
7
|
+
|
8
|
+
module XCCache
|
9
|
+
module SPM
|
10
|
+
class Package
|
11
|
+
class Umbrella < Package
|
12
|
+
include Config::Mixin
|
13
|
+
include UmbrellaCachemapMixin
|
14
|
+
include UmbrellaDescsMixin
|
15
|
+
include UmbrellaBuilldMixin
|
16
|
+
include UmbrellaManifestMixin
|
17
|
+
include UmbrellaVizMixin
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
super
|
21
|
+
@descs = []
|
22
|
+
@descs_by_name = {}
|
23
|
+
@dependency_targets_by_products = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def prepare(options = {})
|
27
|
+
create
|
28
|
+
resolve unless options[:skip_resolve]
|
29
|
+
create_symlinks
|
30
|
+
gen_metadata
|
31
|
+
resolve_recursive_dependencies
|
32
|
+
create_symlinks_to_artifacts
|
33
|
+
sync_cachemap
|
34
|
+
end
|
35
|
+
|
36
|
+
def resolve_recursive_dependencies
|
37
|
+
UI.section("Resolving recursive dependencies")
|
38
|
+
@descs.each do |desc|
|
39
|
+
@dependency_targets_by_products.merge!(desc.resolve_recursive_dependencies.transform_keys(&:full_name))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def create
|
44
|
+
UI.info("Creating umbrella package")
|
45
|
+
# Initially, write json with the original data in lockfile (without cache)
|
46
|
+
write_manifest(no_cache: true)
|
47
|
+
# Create dummy sources dirs prefixed with `.` so that they do not show up in Xcode
|
48
|
+
config.project_targets.each do |target|
|
49
|
+
dir = Dir.prepare(root_dir / ".Sources" / "#{target.name}.xccache")
|
50
|
+
(dir / "dummy.swift").write("")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_symlinks
|
55
|
+
# Symlinks for convenience
|
56
|
+
(root_dir / "binaries").symlink_to(root_dir.parent / "binaries")
|
57
|
+
(root_dir / ".build").symlink_to(root_dir.parent / ".build")
|
58
|
+
(root_dir / ".build/checkouts").symlink_to(root_dir.parent / "checkouts")
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_symlinks_to_artifacts
|
62
|
+
# Clean up broken symlinks
|
63
|
+
config.spm_binaries_frameworks_dir.glob("*/*.xcframework").each do |p|
|
64
|
+
p.rmtree if p.symlink? && !p.readlink.exist?
|
65
|
+
end
|
66
|
+
|
67
|
+
UI.message("Creating symlinks to binary artifacts of targets: #{binary_targets.map(&:full_name).to_s.dark}")
|
68
|
+
binary_targets.each do |target|
|
69
|
+
dst_path = config.spm_binaries_frameworks_dir / target.name / "#{target.name}.xcframework"
|
70
|
+
# For local xcframework, just symlink to the path
|
71
|
+
# Zip frameworks (either of local or remote pkgs) are unzipped in the build artifacts
|
72
|
+
target.local_binary_path.symlink_to(dst_path) if target.local_binary_path&.extname == ".xcframework"
|
73
|
+
config.spm_artifacts_dir.glob("#{target.full_name}/*.xcframework").each do |p|
|
74
|
+
p.symlink_to(dst_path)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
|
data/lib/xccache/spm.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "xccache/core/sh"
|
2
|
+
|
3
|
+
module XCCache
|
4
|
+
module Swift
|
5
|
+
class Sdk
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
NAME_TO_TRIPLE = {
|
9
|
+
:iphonesimulator => "arm64-apple-ios-simulator",
|
10
|
+
:iphoneos => "arm64-apple-ios",
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def initialize(name)
|
14
|
+
@name = name
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
name
|
19
|
+
end
|
20
|
+
|
21
|
+
def triple
|
22
|
+
NAME_TO_TRIPLE[name.to_sym]
|
23
|
+
end
|
24
|
+
|
25
|
+
def sdk_path
|
26
|
+
@sdk_path ||= Pathname(Sh.capture_output("xcrun --sdk #{name} --show-sdk-path")).realpath
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module XCCache
|
2
|
+
module Swift
|
3
|
+
module Swiftc
|
4
|
+
def self.version
|
5
|
+
@version ||= begin
|
6
|
+
m = /Apple Swift version ([\d\.]+)/.match(Sh.capture_output("xcrun swift -version"))
|
7
|
+
m.nil? ? "6.0" : m[1]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.version_without_patch
|
12
|
+
version.split(".")[...2].join(".")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "xccache/core/error"
|
3
|
+
|
4
|
+
module XCCache
|
5
|
+
class Template
|
6
|
+
attr_reader :name, :path
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
@path = Gem.find_files("xccache/assets/templates/#{name}.template").first
|
11
|
+
end
|
12
|
+
|
13
|
+
def render(hash = {}, save_to: nil)
|
14
|
+
raise GeneralError, "Template not found: #{name}" if path.nil?
|
15
|
+
|
16
|
+
rendered = ERB.new(File.read(@path)).result_with_hash(hash)
|
17
|
+
Pathname(save_to).write(rendered) unless save_to.nil?
|
18
|
+
rendered
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "xcodeproj"
|
2
|
+
|
3
|
+
module Xcodeproj
|
4
|
+
class Project
|
5
|
+
module Object
|
6
|
+
module PkgRefMixin
|
7
|
+
def id
|
8
|
+
local? ? (relative_path || path) : repositoryURL
|
9
|
+
end
|
10
|
+
|
11
|
+
def slug
|
12
|
+
File.basename(id, File.extname(id))
|
13
|
+
end
|
14
|
+
|
15
|
+
def local?
|
16
|
+
is_a?(XCLocalSwiftPackageReference)
|
17
|
+
end
|
18
|
+
|
19
|
+
def xccache_pkg?
|
20
|
+
local? && id == "xccache/packages/umbrella"
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_pkg_product_dependency_ref(product)
|
24
|
+
ref = project.new(XCSwiftPackageProductDependency)
|
25
|
+
ref.package = self
|
26
|
+
ref.product_name = product
|
27
|
+
ref
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_target_dependency_ref(product)
|
31
|
+
ref = project.new(PBXTargetDependency)
|
32
|
+
ref.name = product
|
33
|
+
ref.product_ref = create_pkg_product_dependency_ref(product)
|
34
|
+
ref
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class XCLocalSwiftPackageReference
|
39
|
+
include PkgRefMixin
|
40
|
+
|
41
|
+
def ascii_plist_annotation
|
42
|
+
# Workaround: Xcode is using display_name while Xcodeproj is using File.basename(display_name)
|
43
|
+
# Here, the plugin forces to use display_name so that updates either by Xcode or Xcodeproj are consistent
|
44
|
+
" #{isa} \"#{display_name}\" "
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_h
|
48
|
+
{
|
49
|
+
"relative_path" => relative_path,
|
50
|
+
"path" => path,
|
51
|
+
"path_from_root" => absolute_path.relative_path_from(Pathname(".").expand_path).to_s,
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def absolute_path
|
56
|
+
path.nil? ? project.path.parent / relative_path : path
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class XCRemoteSwiftPackageReference
|
61
|
+
include PkgRefMixin
|
62
|
+
|
63
|
+
def to_h
|
64
|
+
{ "repositoryURL" => repositoryURL, "requirement" => requirement }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "xcodeproj"
|
2
|
+
|
3
|
+
module Xcodeproj
|
4
|
+
class Project
|
5
|
+
module Object
|
6
|
+
class XCSwiftPackageProductDependency
|
7
|
+
def full_name
|
8
|
+
"#{pkg.slug}/#{product_name}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def pkg
|
12
|
+
return package unless package.nil?
|
13
|
+
|
14
|
+
Log.warn("Missing pkg for #{inspect}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "xcodeproj"
|
2
|
+
|
3
|
+
module Xcodeproj
|
4
|
+
class Project
|
5
|
+
Log = XCCache::UI
|
6
|
+
|
7
|
+
def display_name
|
8
|
+
relative_path.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def relative_path
|
12
|
+
@relative_path ||= path.relative_path_from(Pathname(".").expand_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def pkgs
|
16
|
+
root_object.package_references
|
17
|
+
end
|
18
|
+
|
19
|
+
def non_xccache_pkgs
|
20
|
+
pkgs.reject(&:xccache_pkg?)
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_pkg?(hash)
|
24
|
+
id = hash[pkg_key_in_hash(hash)]
|
25
|
+
pkgs.any? { |p| p.id == id }
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_xccache_pkg?
|
29
|
+
pkgs.any?(&:xccache_pkg?)
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_pkg(hash)
|
33
|
+
key = pkg_key_in_hash(hash)
|
34
|
+
is_local = ["relative_path", "path"].include?(key)
|
35
|
+
|
36
|
+
Log.message("Add package #{hash[key].bold} to project #{display_name.bold}")
|
37
|
+
cls = is_local ? XCLocalSwiftPackageReference : XCRemoteSwiftPackageReference
|
38
|
+
ref = new(cls)
|
39
|
+
hash.each { |k, v| ref.send("#{k}=", v) }
|
40
|
+
root_object.package_references << ref
|
41
|
+
ref
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_xccache_pkg
|
45
|
+
sandbox_path = XCCache::Config.instance.spm_umbrella_sandbox
|
46
|
+
add_pkg("relative_path" => sandbox_path.relative_path_from(path.parent).to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_target(name)
|
50
|
+
targets.find { |t| t.name == name }
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_pkg(name)
|
54
|
+
pkgs.find { |p| p.slug == name }
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def pkg_key_in_hash(hash)
|
60
|
+
["repositoryURL", "relative_path", "path"].find { |k| hash.key?(k) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "xcodeproj"
|
2
|
+
|
3
|
+
module Xcodeproj
|
4
|
+
class Project
|
5
|
+
module Object
|
6
|
+
class PBXNativeTarget
|
7
|
+
alias pkg_product_dependencies package_product_dependencies
|
8
|
+
|
9
|
+
def non_xccache_pkg_product_dependencies
|
10
|
+
pkg_product_dependencies.reject { |d| d.pkg.xccache_pkg? }
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_xccache_product_dependency?
|
14
|
+
pkg_product_dependencies.any? { |d| d.pkg.xccache_pkg? }
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_pkg_product_dependency?(name)
|
18
|
+
pkg_product_dependencies.any? { |d| "#{d.pkg.slug}/#{d.name}" == name }
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_pkg_product_dependency(name)
|
22
|
+
Log.message("(+) Add dependency #{name.blue} to target #{display_name.bold}")
|
23
|
+
pkg_name, product_name = name.split("/")
|
24
|
+
pkg = project.get_pkg(pkg_name)
|
25
|
+
pkg_product_dependencies << pkg.create_target_dependency_ref(product_name).product_ref
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_xccache_product_dependency
|
29
|
+
add_pkg_product_dependency("umbrella/#{name}.xccache")
|
30
|
+
end
|
31
|
+
|
32
|
+
def remove_xccache_product_dependencies
|
33
|
+
remove_pkg_product_dependencies { |d| d.pkg.xccache_pkg? }
|
34
|
+
end
|
35
|
+
|
36
|
+
def remove_pkg_product_dependencies(&block)
|
37
|
+
package_product_dependencies.select(&block).each do |d|
|
38
|
+
XCCache::UI.info(
|
39
|
+
"(-) Remove #{d.product_name.red} from product dependencies of target #{display_name.bold}"
|
40
|
+
)
|
41
|
+
build_phases.each do |phase|
|
42
|
+
phase.files.select { |f| f.remove_from_project if f.product_ref == d }
|
43
|
+
end
|
44
|
+
d.remove_from_project
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|