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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/xccache/assets/templates/cachemap.html.template +55 -0
  3. data/lib/xccache/assets/templates/cachemap.js.template +95 -0
  4. data/lib/xccache/assets/templates/cachemap.style.css.template +94 -0
  5. data/lib/xccache/assets/templates/framework.info.plist.template +25 -0
  6. data/lib/xccache/assets/templates/framework.modulemap.template +6 -0
  7. data/lib/xccache/assets/templates/resource_bundle_accessor.m.template +27 -0
  8. data/lib/xccache/assets/templates/resource_bundle_accessor.swift.template +24 -0
  9. data/lib/xccache/assets/templates/umbrella.Package.swift.template +183 -0
  10. data/lib/xccache/cache/cachemap.rb +56 -0
  11. data/lib/xccache/command/base.rb +29 -0
  12. data/lib/xccache/command/build.rb +41 -0
  13. data/lib/xccache/command/pkg/build.rb +30 -0
  14. data/lib/xccache/command/pkg.rb +1 -0
  15. data/lib/xccache/command/remote/pull.rb +15 -0
  16. data/lib/xccache/command/remote/push.rb +15 -0
  17. data/lib/xccache/command/remote.rb +39 -0
  18. data/lib/xccache/command/rollback.rb +14 -0
  19. data/lib/xccache/command/use.rb +19 -0
  20. data/lib/xccache/command.rb +27 -1
  21. data/lib/xccache/core/cacheable.rb +28 -0
  22. data/lib/xccache/core/config.rb +110 -0
  23. data/lib/xccache/core/error.rb +7 -0
  24. data/lib/xccache/core/git.rb +27 -0
  25. data/lib/xccache/core/hash.rb +21 -0
  26. data/lib/xccache/core/lockfile.rb +40 -0
  27. data/lib/xccache/core/log.rb +51 -0
  28. data/lib/xccache/core/parallel.rb +10 -0
  29. data/lib/xccache/core/sh.rb +51 -0
  30. data/lib/xccache/core/syntax/hash.rb +31 -0
  31. data/lib/xccache/core/syntax/json.rb +16 -0
  32. data/lib/xccache/core/syntax/plist.rb +17 -0
  33. data/lib/xccache/core/syntax/yml.rb +16 -0
  34. data/lib/xccache/core/syntax.rb +1 -0
  35. data/lib/xccache/core/system.rb +62 -0
  36. data/lib/xccache/core.rb +1 -0
  37. data/lib/xccache/installer/build.rb +23 -0
  38. data/lib/xccache/installer/rollback.rb +37 -0
  39. data/lib/xccache/installer/use.rb +28 -0
  40. data/lib/xccache/installer.rb +113 -0
  41. data/lib/xccache/main.rb +3 -1
  42. data/lib/xccache/spm/build.rb +53 -0
  43. data/lib/xccache/spm/desc/base.rb +68 -0
  44. data/lib/xccache/spm/desc/dep.rb +40 -0
  45. data/lib/xccache/spm/desc/desc.rb +126 -0
  46. data/lib/xccache/spm/desc/product.rb +36 -0
  47. data/lib/xccache/spm/desc/target/binary.rb +8 -0
  48. data/lib/xccache/spm/desc/target/macro.rb +8 -0
  49. data/lib/xccache/spm/desc/target.rb +164 -0
  50. data/lib/xccache/spm/desc.rb +1 -0
  51. data/lib/xccache/spm/macro.rb +44 -0
  52. data/lib/xccache/spm/mixin.rb +12 -0
  53. data/lib/xccache/spm/pkg/base.rb +107 -0
  54. data/lib/xccache/spm/pkg/umbrella/build.rb +30 -0
  55. data/lib/xccache/spm/pkg/umbrella/cachemap.rb +110 -0
  56. data/lib/xccache/spm/pkg/umbrella/descs.rb +35 -0
  57. data/lib/xccache/spm/pkg/umbrella/manifest.rb +83 -0
  58. data/lib/xccache/spm/pkg/umbrella/viz.rb +40 -0
  59. data/lib/xccache/spm/pkg/umbrella/xcconfigs.rb +31 -0
  60. data/lib/xccache/spm/pkg/umbrella.rb +91 -0
  61. data/lib/xccache/spm/pkg.rb +1 -0
  62. data/lib/xccache/spm/xcframework/metadata.rb +41 -0
  63. data/lib/xccache/spm/xcframework/slice.rb +180 -0
  64. data/lib/xccache/spm/xcframework/xcframework.rb +56 -0
  65. data/lib/xccache/spm/xcframework.rb +2 -0
  66. data/lib/xccache/spm.rb +1 -0
  67. data/lib/xccache/storage/base.rb +26 -0
  68. data/lib/xccache/storage/git.rb +46 -0
  69. data/lib/xccache/storage/s3.rb +53 -0
  70. data/lib/xccache/storage.rb +1 -0
  71. data/lib/xccache/swift/sdk.rb +49 -0
  72. data/lib/xccache/swift/swiftc.rb +16 -0
  73. data/lib/xccache/utils/template.rb +21 -0
  74. data/lib/xccache/xcodeproj/build_configuration.rb +20 -0
  75. data/lib/xccache/xcodeproj/config.rb +9 -0
  76. data/lib/xccache/xcodeproj/file_system_synchronized_root_group.rb +17 -0
  77. data/lib/xccache/xcodeproj/group.rb +26 -0
  78. data/lib/xccache/xcodeproj/pkg.rb +73 -0
  79. data/lib/xccache/xcodeproj/pkg_product_dependency.rb +19 -0
  80. data/lib/xccache/xcodeproj/project.rb +83 -0
  81. data/lib/xccache/xcodeproj/target.rb +52 -0
  82. data/lib/xccache/xcodeproj.rb +2 -0
  83. 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,12 @@
1
+ module XCCache
2
+ module PkgMixin
3
+ include Config::Mixin
4
+
5
+ def umbrella_pkg
6
+ @umbrella_pkg ||= SPM::Package::Umbrella.new(
7
+ root_dir: config.spm_umbrella_sandbox,
8
+ warn_if_not_direct_target: false,
9
+ )
10
+ end
11
+ end
12
+ 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