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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/lib/xccache/assets/templates/cachemap.html.template +54 -0
  3. data/lib/xccache/assets/templates/cachemap.js.template +94 -0
  4. data/lib/xccache/assets/templates/cachemap.style.css.template +92 -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 +147 -0
  10. data/lib/xccache/cache/cachemap.rb +60 -0
  11. data/lib/xccache/command/build.rb +35 -0
  12. data/lib/xccache/command/pkg/build.rb +34 -0
  13. data/lib/xccache/command/rollback.rb +13 -0
  14. data/lib/xccache/command/use.rb +18 -0
  15. data/lib/xccache/command.rb +11 -1
  16. data/lib/xccache/core/cacheable.rb +28 -0
  17. data/lib/xccache/core/config.rb +82 -0
  18. data/lib/xccache/core/error.rb +7 -0
  19. data/lib/xccache/core/git.rb +19 -0
  20. data/lib/xccache/core/hash.rb +21 -0
  21. data/lib/xccache/core/lockfile.rb +40 -0
  22. data/lib/xccache/core/log.rb +41 -0
  23. data/lib/xccache/core/sh.rb +50 -0
  24. data/lib/xccache/core/syntax/hash.rb +31 -0
  25. data/lib/xccache/core/syntax/json.rb +16 -0
  26. data/lib/xccache/core/syntax/yml.rb +16 -0
  27. data/lib/xccache/core/syntax.rb +1 -0
  28. data/lib/xccache/core/system.rb +54 -0
  29. data/lib/xccache/core.rb +1 -0
  30. data/lib/xccache/framework/slice.rb +183 -0
  31. data/lib/xccache/framework/xcframework.rb +51 -0
  32. data/lib/xccache/installer/build.rb +27 -0
  33. data/lib/xccache/installer/rollback.rb +36 -0
  34. data/lib/xccache/installer/use.rb +27 -0
  35. data/lib/xccache/installer.rb +67 -0
  36. data/lib/xccache/main.rb +3 -1
  37. data/lib/xccache/spm/desc/base.rb +61 -0
  38. data/lib/xccache/spm/desc/dep.rb +40 -0
  39. data/lib/xccache/spm/desc/desc.rb +110 -0
  40. data/lib/xccache/spm/desc/product.rb +36 -0
  41. data/lib/xccache/spm/desc/target.rb +138 -0
  42. data/lib/xccache/spm/desc.rb +1 -0
  43. data/lib/xccache/spm/mixin.rb +9 -0
  44. data/lib/xccache/spm/pkg/base.rb +103 -0
  45. data/lib/xccache/spm/pkg/umbrella/build.rb +30 -0
  46. data/lib/xccache/spm/pkg/umbrella/cachemap.rb +93 -0
  47. data/lib/xccache/spm/pkg/umbrella/descs.rb +46 -0
  48. data/lib/xccache/spm/pkg/umbrella/manifest.rb +83 -0
  49. data/lib/xccache/spm/pkg/umbrella/viz.rb +40 -0
  50. data/lib/xccache/spm/pkg/umbrella.rb +81 -0
  51. data/lib/xccache/spm/pkg.rb +1 -0
  52. data/lib/xccache/spm.rb +1 -0
  53. data/lib/xccache/swift/sdk.rb +30 -0
  54. data/lib/xccache/swift/swiftc.rb +16 -0
  55. data/lib/xccache/utils/template.rb +21 -0
  56. data/lib/xccache/xcodeproj/pkg.rb +69 -0
  57. data/lib/xccache/xcodeproj/pkg_product_dependency.rb +19 -0
  58. data/lib/xccache/xcodeproj/project.rb +63 -0
  59. data/lib/xccache/xcodeproj/target.rb +50 -0
  60. data/lib/xccache/xcodeproj.rb +2 -0
  61. metadata +72 -2
@@ -0,0 +1,28 @@
1
+ module XCCache
2
+ module Cacheable
3
+ def cacheable(*method_names)
4
+ method_names.each do |method_name|
5
+ const_get(__cacheable_module_name).class_eval do
6
+ define_method(method_name) do |*args, **kwargs|
7
+ @_cache ||= {}
8
+ @_cache[method_name] ||= {}
9
+ @_cache[method_name][args.hash | kwargs.hash] ||=
10
+ method(method_name).super_method.call(*args, **kwargs)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ def __cacheable_module_name
17
+ "#{name}_Cacheable".gsub(':', '_')
18
+ end
19
+
20
+ def self.included(base)
21
+ base.extend(self)
22
+
23
+ module_name = base.send(:__cacheable_module_name)
24
+ remove_const(module_name) if const_defined?(module_name)
25
+ base.prepend(const_set(module_name, Module.new))
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,82 @@
1
+ require "xcodeproj"
2
+ require "xccache/core/syntax/yml"
3
+
4
+ module XCCache
5
+ class Config < YAMLRepresentable
6
+ module Mixin
7
+ def config
8
+ Config.instance
9
+ end
10
+ end
11
+
12
+ def self.instance
13
+ @instance ||= new(Pathname("xccache.yml").expand_path)
14
+ end
15
+
16
+ attr_accessor :verbose
17
+ alias verbose? verbose
18
+
19
+ def sandbox
20
+ @sandbox = Dir.prepare("xccache").expand_path
21
+ end
22
+
23
+ def spm_sandbox
24
+ @spm_sandbox ||= Dir.prepare(sandbox / "packages").expand_path
25
+ end
26
+
27
+ def spm_binaries_frameworks_dir
28
+ @spm_binaries_frameworks_dir ||= Dir.prepare(spm_umbrella_sandbox / "binaries")
29
+ end
30
+
31
+ def spm_build_dir
32
+ @spm_build_dir ||= spm_umbrella_sandbox / ".build"
33
+ end
34
+
35
+ def spm_artifacts_dir
36
+ @spm_artifacts_dir ||= spm_build_dir / "artifacts"
37
+ end
38
+
39
+ def spm_umbrella_sandbox
40
+ @spm_umbrella_sandbox ||= Dir.prepare(spm_sandbox / "umbrella")
41
+ end
42
+
43
+ def spm_metadata_dir
44
+ @spm_metadata_dir ||= Dir.prepare(spm_sandbox / "metadata")
45
+ end
46
+
47
+ def lockfile
48
+ @lockfile ||= Lockfile.new(Pathname("xccache.lock").expand_path)
49
+ end
50
+
51
+ def cachemap
52
+ @cachemap ||= Cache::Cachemap.new(sandbox / "cachemap.json")
53
+ end
54
+
55
+ def projects
56
+ @projects ||= Pathname(".").glob("*.xcodeproj").map do |p|
57
+ Xcodeproj::Project.open(p)
58
+ end
59
+ end
60
+
61
+ def project_targets
62
+ projects.flat_map(&:targets)
63
+ end
64
+
65
+ def ignore_list
66
+ raw["ignore"] || []
67
+ end
68
+
69
+ def ignore?(item)
70
+ return true if ignore_local? && lockfile.local_pkg_slugs.include?(item.split("/").first)
71
+ ignore_list.any? { |p| File.fnmatch(p, item) }
72
+ end
73
+
74
+ def ignore_local?
75
+ raw["ignore_local"]
76
+ end
77
+
78
+ def ignore_build_errors?
79
+ raw["ignore_build_errors"]
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,7 @@
1
+ module XCCache
2
+ class BaseError < StandardError
3
+ end
4
+
5
+ class GeneralError < BaseError
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module XCCache
2
+ class Git
3
+ attr_reader :root
4
+
5
+ def initialize(root)
6
+ @root = Pathname(root)
7
+ end
8
+
9
+ def sha
10
+ run("rev-parse --short HEAD", capture: true, log_cmd: false)[0].strip
11
+ end
12
+
13
+ private
14
+
15
+ def run(cmd, options = {})
16
+ Sh.run("git -C #{root} #{cmd}", options)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ class Hash
2
+ def deep_merge(other, uniq_block: nil, &block)
3
+ dup.deep_merge!(other, uniq_block: uniq_block, &block)
4
+ end
5
+
6
+ def deep_merge!(other, uniq_block: nil, &block)
7
+ merge!(other) do |key, this_val, other_val|
8
+ result = if this_val.is_a?(Hash) && other_val.is_a?(Hash)
9
+ this_val.deep_merge(other_val, uniq_block: uniq_block, &block)
10
+ elsif this_val.is_a?(Array) && other_val.is_a?(Array)
11
+ this_val + other_val
12
+ elsif block_given?
13
+ block.call(key, this_val, other_val)
14
+ else
15
+ other_val
16
+ end
17
+ next result if uniq_block.nil? || !result.is_a?(Array)
18
+ result.reverse.uniq(&uniq_block).reverse # prefer updates
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ require "xccache/core/syntax/json"
2
+
3
+ module XCCache
4
+ class Lockfile < JSONRepresentable
5
+ def hash_for_project(project)
6
+ raw[project.display_name] ||= {}
7
+ end
8
+
9
+ def product_dependencies_by_targets
10
+ @product_dependencies_by_targets ||= raw.values.map { |h| h["dependencies"] }.reduce { |acc, h| acc.merge(h) }
11
+ end
12
+
13
+ def deep_merge!(hash)
14
+ keys = ["repositoryURL", "path_from_root", "relative_path"]
15
+ raw.deep_merge!(hash, uniq_block: proc do |h|
16
+ h.is_a?(Hash) && (k = keys.find { |k| h.key?(k) }) ? h[k] : h
17
+ end)
18
+ end
19
+
20
+ def pkgs
21
+ @pkgs ||= raw.values.flat_map { |h| h["packages"] || [] }
22
+ end
23
+
24
+ def local_pkgs
25
+ @local_pkgs ||= pkgs.select { |h| h.key?("relative_path") || h.key?("path") }
26
+ end
27
+
28
+ def local_pkg_slugs
29
+ @local_pkg_slugs ||= local_pkgs.map { |h| File.basename(h["relative_path"] || h["path"]) }.uniq
30
+ end
31
+
32
+ def product_dependencies
33
+ @product_dependencies ||= product_dependencies_by_targets.values.flatten.uniq
34
+ end
35
+
36
+ def targets_data
37
+ @targets_data ||= product_dependencies_by_targets.transform_keys { |k| "#{k}.xccache" }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ require "xccache/core/config"
2
+ require "colored2"
3
+
4
+ module XCCache
5
+ module UI
6
+ @indent = 0
7
+
8
+ class << self
9
+ include Config::Mixin
10
+ attr_accessor :indent
11
+
12
+ def section(title)
13
+ UI.puts(title)
14
+ self.indent += 2
15
+ res = yield if block_given?
16
+ self.indent -= 2
17
+ res
18
+ end
19
+
20
+ def message(message)
21
+ UI.puts(message) if config.verbose?
22
+ end
23
+
24
+ def info(message)
25
+ UI.puts(message)
26
+ end
27
+
28
+ def warn(message)
29
+ UI.puts(message.yellow)
30
+ end
31
+
32
+ def error(message)
33
+ UI.puts("[ERROR] #{message}".red)
34
+ end
35
+
36
+ def puts(message)
37
+ $stdout.puts("#{' ' * self.indent}#{message}")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,50 @@
1
+ require "open3"
2
+ require "xccache/core/config"
3
+ require "xccache/core/log"
4
+ require "xccache/core/error"
5
+
6
+ module XCCache
7
+ class Sh
8
+ class ExecError < BaseError
9
+ end
10
+
11
+ class << self
12
+ include Config::Mixin
13
+
14
+ def capture_output(cmd)
15
+ run(cmd, capture: true, log_cmd: false)[0].strip
16
+ end
17
+
18
+ def run(cmd, options = {})
19
+ cmd = cmd.join(" ") if cmd.is_a?(Array)
20
+ UI.message("$ #{cmd}".cyan.dark) if config.verbose? && options[:log_cmd] != false
21
+
22
+ out, err = [], []
23
+ handle_out = proc do |line|
24
+ if options[:capture]
25
+ out << line
26
+ else
27
+ UI.puts line
28
+ end
29
+ end
30
+ handle_err = proc do |line|
31
+ if options[:capture]
32
+ err << line
33
+ else
34
+ UI.puts line.strip.yellow unless options[:suppress_err]&.match(line)
35
+ end
36
+ end
37
+
38
+ Open3.popen3(cmd) do |_stdin, stdout, stderr, wait_thr|
39
+ stdout_thread = Thread.new { stdout.each { |l| handle_out.call(l) } }
40
+ stderr_thread = Thread.new { stderr.each { |l| handle_err.call(l) } }
41
+ [stdout_thread, stderr_thread].each(&:join)
42
+ result = wait_thr.value
43
+ result.exitstatus
44
+ raise ExecError, "Command '#{cmd}' failed with status: #{result.exitstatus}" unless result.success?
45
+ end
46
+ [out.join("\n"), err.join("\n")]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ module XCCache
2
+ class HashRepresentable
3
+ attr_reader :path
4
+ attr_accessor :raw
5
+
6
+ def initialize(path, raw: nil)
7
+ @path = path
8
+ @raw = raw || load || {}
9
+ end
10
+
11
+ def load
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def merge!(other)
16
+ raw.merge!(other)
17
+ end
18
+
19
+ def save(to: nil)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def [](key)
24
+ raw[key]
25
+ end
26
+
27
+ def []=(key, value)
28
+ raw[key] = value
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ require "json"
2
+ require_relative "hash"
3
+
4
+ module XCCache
5
+ class JSONRepresentable < HashRepresentable
6
+ def load
7
+ JSON.parse(path.read) if path.exist?
8
+ rescue StandardError
9
+ {}
10
+ end
11
+
12
+ def save(to: nil)
13
+ (to || path).write(JSON.pretty_generate(raw))
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ require "yaml"
2
+ require_relative "hash"
3
+
4
+ module XCCache
5
+ class YAMLRepresentable < HashRepresentable
6
+ def load
7
+ YAML.safe_load(path.read) if path.exist?
8
+ rescue StandardError
9
+ {}
10
+ end
11
+
12
+ def save(to: nil)
13
+ (to || path).write(raw.to_yaml)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1 @@
1
+ Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
@@ -0,0 +1,54 @@
1
+ require "digest"
2
+
3
+ class String
4
+ def c99extidentifier
5
+ gsub(/[^a-zA-Z0-9.]/, "_")
6
+ end
7
+ end
8
+
9
+ class Dir
10
+ def self.prepare(dir)
11
+ dir = Pathname(dir)
12
+ dir.mkpath
13
+ dir
14
+ end
15
+
16
+ def self.create_tmpdir
17
+ dir = Pathname(Dir.mktmpdir("xccache"))
18
+ res = block_given? ? (yield dir) : dir
19
+ dir.rmtree if block_given?
20
+ res
21
+ end
22
+
23
+ def self.git?(dir)
24
+ XCCache::Sh.capture_output("git -C #{dir} rev-parse --git-dir") == ".git"
25
+ end
26
+ end
27
+
28
+ class Pathname
29
+ def symlink_to(dst)
30
+ dst = Pathname(dst)
31
+ dst.rmtree if dst.symlink?
32
+ dst.parent.mkpath
33
+ File.symlink(expand_path, dst)
34
+ end
35
+
36
+ def copy(to: nil, to_dir: nil)
37
+ dst = to || (Pathname(to_dir) / basename)
38
+ dst.rmtree if dst.exist? || dst.symlink?
39
+ FileUtils.copy_entry(self, dst)
40
+ dst
41
+ end
42
+
43
+ def checksum
44
+ hasher = Digest::SHA256.new
45
+ glob("**/*").reject { |p| p.directory? || p.symlink? }.sort.each do |p|
46
+ p.open("rb") do |f|
47
+ while (chunk = f.read(65_536)) # Read 64KB chunks
48
+ hasher.update(chunk)
49
+ end
50
+ end
51
+ end
52
+ hasher.hexdigest[...8]
53
+ end
54
+ end
@@ -0,0 +1 @@
1
+ Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
@@ -0,0 +1,183 @@
1
+ require "xccache/utils/template"
2
+
3
+ module XCCache
4
+ class Framework
5
+ class Slice
6
+ attr_reader :name, :module_name, :pkg_dir, :pkg_desc, :sdk, :config, :path, :tmpdir
7
+
8
+ def initialize(options = {})
9
+ @name = options[:name]
10
+ @module_name = @name.c99extidentifier
11
+ @pkg_dir = Pathname(options[:pkg_dir] || ".").expand_path
12
+ @pkg_desc = options[:pkg_desc]
13
+ @sdk = options[:sdk]
14
+ @config = options[:config] || "debug"
15
+ @path = options[:path]
16
+ @tmpdir = options[:tmpdir]
17
+ end
18
+
19
+ def build
20
+ UI.section("Building slice: #{name} (#{config}, #{sdk})".bold) do
21
+ cmd = ["swift", "build"] + swift_build_args
22
+ cmd << "--package-path" << pkg_dir
23
+ cmd << "--target" << name
24
+ cmd << "--sdk" << sdk.sdk_path
25
+ # Workaround for swiftinterface emission
26
+ # https://github.com/swiftlang/swift/issues/64669#issuecomment-1535335601
27
+ cmd << "-Xswiftc" << "-enable-library-evolution"
28
+ cmd << "-Xswiftc" << "-alias-module-names-in-module-interface"
29
+ cmd << "-Xswiftc" << "-emit-module-interface"
30
+ cmd << "-Xswiftc" << "-no-verify-emitted-module-interface"
31
+ Sh.run(cmd, suppress_err: /(dependency '.*' is not used by any target|unable to create symbolic link)/)
32
+ create_framework
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def override_resource_bundle_accessor
39
+ # By default, Swift generates resource_bundle_accessor.swift for targets having resources
40
+ # (Check .build/<Target>.build/DerivedSources/resource_bundle_accessor.swift)
41
+ # This enables accessing the resource bundle via `Bundle.module`.
42
+ # However, `Bundle.module` expects the resource bundle to be under the app bundle,
43
+ # which is not the case for binary targets. Instead the bundle is under `Frameworks/<Target>.framework`
44
+ # WORKAROUND:
45
+ # - Overriding resource_bundle_accessor.swift to add `Frameworks/<Target>.framework` to the search list
46
+ # - Compiling this file into an `.o` file before using `libtool` to create the framework binary
47
+ UI.message("Override resource_bundle_accessor")
48
+ template_name = use_clang? ? "resource_bundle_accessor.m" : "resource_bundle_accessor.swift"
49
+ source_path = tmpdir / File.basename(template_name)
50
+ obj_path = products_dir / "#{module_name}.build" / "#{source_path.basename}.o"
51
+ Template.new(template_name).render(
52
+ { :pkg => pkg_target.pkg_name, :target => name, :module_name => module_name },
53
+ save_to: source_path
54
+ )
55
+
56
+ if use_clang?
57
+ cmd = ["xcrun", "clang"]
58
+ cmd << "-x" << "objective-c"
59
+ cmd << "-target" << sdk.triple << "-isysroot" << sdk.sdk_path
60
+ cmd << "-o" << obj_path.to_s
61
+ cmd << "-c" << source_path
62
+ else
63
+ cmd = ["xcrun", "swiftc"]
64
+ cmd << "-emit-library" << "-emit-object"
65
+ cmd << "-module-name" << module_name
66
+ cmd << "-target" << sdk.triple << "-sdk" << sdk.sdk_path
67
+ cmd << "-o" << obj_path.to_s
68
+ cmd << source_path
69
+ end
70
+ Sh.run(cmd)
71
+ end
72
+
73
+ def create_framework
74
+ override_resource_bundle_accessor if resource_bundle_product_path.exist?
75
+ create_info_plist
76
+ create_framework_binary
77
+ create_headers
78
+ create_modules
79
+ copy_resource_bundles if resource_bundle_product_path.exist?
80
+ end
81
+
82
+ def create_framework_binary
83
+ # Write .o file list into a file
84
+ obj_paths = products_dir.glob("#{module_name}.build/**/*.o")
85
+ raise GeneralError, "Detected no object files for #{name}" if obj_paths.empty?
86
+
87
+ objlist_path = tmpdir / "objects.txt"
88
+ objlist_path.write(obj_paths.map(&:to_s).join("\n"))
89
+
90
+ cmd = ["libtool", "-static"]
91
+ cmd << "-o" << "#{path}/#{module_name}"
92
+ cmd << "-filelist" << objlist_path.to_s
93
+ Sh.run(cmd)
94
+ FileUtils.chmod("+x", path / module_name)
95
+ end
96
+
97
+ def create_info_plist
98
+ Template.new("framework.info.plist").render(
99
+ { :module_name => module_name },
100
+ save_to: path / "Info.plist",
101
+ )
102
+ end
103
+
104
+ def create_headers
105
+ Dir.prepare(path / "Headers")
106
+ copy_headers if use_clang?
107
+ end
108
+
109
+ def create_modules
110
+ Dir.prepare(path / "Modules")
111
+ return copy_swiftmodules unless use_clang?
112
+
113
+ UI.message("Creating framework modulemap")
114
+ Template.new("framework.modulemap").render(
115
+ { :module_name => module_name },
116
+ save_to: path / "Modules" / "module.modulemap"
117
+ )
118
+ end
119
+
120
+ def copy_headers
121
+ UI.message("Copying headers")
122
+ framework_headers_path = path / "Headers"
123
+ umbrella_header_content =
124
+ pkg_target
125
+ .header_paths
126
+ .map { |p| p.copy(to_dir: framework_headers_path) }
127
+ .map { |p| "#include \"#{p.basename}\"" }
128
+ .join("\n")
129
+ (framework_headers_path / "#{module_name}-umbrella.h").write(umbrella_header_content)
130
+ end
131
+
132
+ def copy_swiftmodules
133
+ UI.message("Copying swiftmodules")
134
+ swiftmodule_dir = Dir.prepare("#{path}/Modules/#{module_name}.swiftmodule")
135
+ swiftinterfaces = products_dir.glob("#{module_name}.build/#{module_name}.swiftinterface")
136
+ to_copy = products_dir.glob("Modules/#{module_name}.*") + swiftinterfaces
137
+ to_copy.each do |p|
138
+ p.copy(to: swiftmodule_dir / p.basename.sub(module_name, sdk.triple))
139
+ end
140
+ end
141
+
142
+ def copy_resource_bundles
143
+ resolve_resource_symlinks
144
+ UI.message("Copy resource bundle to framework: #{resource_bundle_product_path.basename}")
145
+ resource_bundle_product_path.copy(to_dir: path)
146
+ end
147
+
148
+ def resolve_resource_symlinks
149
+ # Well, Xcode seems to well handle symlinks in resources. In xcodebuild log, you would see something like:
150
+ # CpResource: builtin-copy ... -resolve-src-symlinks
151
+ # But this is not the case if we build with `swift build`. Here, we have to manually handle it
152
+ resource_bundle_product_path.glob("**/*").select(&:symlink?).reject(&:exist?).each do |p|
153
+ UI.message("Resolve resource symlink: #{p}")
154
+ original = pkg_target.resource_paths.find { |rp| rp.symlink? && rp.readlink == p.readlink }
155
+ original&.realpath&.copy(to: p)
156
+ end
157
+ end
158
+
159
+ def products_dir
160
+ @products_dir ||= pkg_dir / ".build" / sdk.triple / config
161
+ end
162
+
163
+ def swift_build_args
164
+ [
165
+ "--configuration", config,
166
+ "--triple", sdk.triple,
167
+ ]
168
+ end
169
+
170
+ def use_clang?
171
+ pkg_target.use_clang?
172
+ end
173
+
174
+ def pkg_target
175
+ @pkg_target ||= pkg_desc.get_target(name)
176
+ end
177
+
178
+ def resource_bundle_product_path
179
+ @resource_bundle_product_path ||= products_dir / pkg_target.bundle_name
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,51 @@
1
+ module XCCache
2
+ class Framework
3
+ class XCFramework
4
+ attr_reader :name, :module_name, :pkg_dir, :pkg_desc, :config, :sdks, :path
5
+
6
+ def initialize(options = {})
7
+ @name = options[:name]
8
+ @module_name = @name.c99extidentifier
9
+ @pkg_dir = options[:pkg_dir]
10
+ @pkg_desc = options[:pkg_desc]
11
+ @config = options[:config]
12
+ @sdks = options[:sdks]
13
+ @path = options[:path]
14
+ end
15
+
16
+ def create
17
+ slices.each(&:build)
18
+ UI.section("Creating #{name}.xcframework from slices") do
19
+ path.rmtree if path.exist?
20
+
21
+ cmd = ["xcodebuild", "-create-xcframework"]
22
+ cmd << "-output" << path
23
+ slices.each { |slice| cmd << "-framework" << slice.path }
24
+ Sh.run(cmd)
25
+ end
26
+ tmpdir.rmtree
27
+
28
+ # TODO: Should we dispose tmpdir here as well?
29
+ end
30
+
31
+ def tmpdir
32
+ @tmpdir ||= Dir.create_tmpdir
33
+ end
34
+
35
+ def slices
36
+ @slices ||= sdks.map do |s|
37
+ sdk = Swift::Sdk.new(s)
38
+ Framework::Slice.new(
39
+ name: name,
40
+ pkg_dir: pkg_dir,
41
+ pkg_desc: pkg_desc,
42
+ sdk: sdk,
43
+ config: config,
44
+ path: Dir.prepare(tmpdir / sdk.triple / "#{module_name}.framework"),
45
+ tmpdir: tmpdir,
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ require "xccache/spm"
2
+
3
+ module XCCache
4
+ class Installer
5
+ class Build < Installer
6
+ def initialize(options = {})
7
+ super
8
+ @targets = options[:targets]
9
+ @sdk = options[:sdk]
10
+ @recursive = options[:recursive]
11
+ end
12
+
13
+ def install!
14
+ perform_install do
15
+ umbrella_pkg.build(
16
+ targets: @targets,
17
+ sdk: @sdk,
18
+ out_dir: config.spm_binaries_frameworks_dir,
19
+ checksum: true,
20
+ recursive: @recursive,
21
+ skip_resolve: @skip_resolving_dependencies,
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end