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,91 @@
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 UmbrellaBuildMixin
16
+ include UmbrellaManifestMixin
17
+ include UmbrellaVizMixin
18
+ include UmbrellaXCConfigsMixin
19
+
20
+ def initialize(options = {})
21
+ super
22
+ @descs = []
23
+ @descs_by_name = {}
24
+ @dependency_targets_by_products = {}
25
+ end
26
+
27
+ def prepare(options = {})
28
+ create
29
+ resolve unless options[:skip_resolving_dependencies]
30
+ create_symlinks_for_convenience
31
+ create_symlinks_to_local_pkgs
32
+ gen_metadata
33
+ resolve_recursive_dependencies
34
+ create_symlinks_to_artifacts
35
+ sync_cachemap(sdks: options[:sdks])
36
+ end
37
+
38
+ def resolve_recursive_dependencies
39
+ UI.section("Resolving recursive dependencies")
40
+ @descs.each do |desc|
41
+ @dependency_targets_by_products.merge!(desc.resolve_recursive_dependencies.transform_keys(&:full_name))
42
+ end
43
+ end
44
+
45
+ def create
46
+ UI.info("Creating umbrella package")
47
+ # Initially, write json with the original data in lockfile (without cache)
48
+ write_manifest(no_cache: true)
49
+ # Create dummy sources dirs prefixed with `.` so that they do not show up in Xcode
50
+ config.project_targets.each do |target|
51
+ dir = Dir.prepare(root_dir / ".Sources" / "#{target.name}.xccache")
52
+ (dir / "dummy.swift").write("")
53
+ end
54
+ end
55
+
56
+ def create_symlinks_for_convenience
57
+ # Symlinks for convenience
58
+ (root_dir / "binaries").symlink_to(root_dir.parent / "binaries")
59
+ (root_dir / ".build").symlink_to(root_dir.parent / ".build")
60
+ (root_dir / ".build/checkouts").symlink_to(root_dir.parent / "checkouts")
61
+ end
62
+
63
+ def create_symlinks_to_local_pkgs
64
+ pkg_desc.dependencies.select(&:local?).each do |dep|
65
+ # For metadata generation
66
+ dep.path.symlink_to(root_dir / ".build/checkouts/#{dep.slug}")
67
+ # For convenience, synced group under `xccache.config` group in xcodeproj
68
+ dep.path.symlink_to(Config.instance.spm_local_pkgs_dir / dep.slug)
69
+ end
70
+ end
71
+
72
+ def create_symlinks_to_artifacts
73
+ # Clean up symlinks beforehand
74
+ config.spm_binaries_dir.glob("*/*.{xcframework,macro}").each do |p|
75
+ p.rmtree if p.symlink?
76
+ end
77
+
78
+ binary_targets.each do |target|
79
+ dst_path = config.spm_binaries_dir / target.name / "#{target.name}.xcframework"
80
+ # For local xcframework, just symlink to the path
81
+ # Zip frameworks (either of local or remote pkgs) are unzipped in the build artifacts
82
+ target.local_binary_path.symlink_to(dst_path) if target.local_binary_path&.extname == ".xcframework"
83
+ config.spm_artifacts_dir.glob("#{target.full_name}/*.xcframework").each do |p|
84
+ p.symlink_to(dst_path)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1 @@
1
+ Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
@@ -0,0 +1,41 @@
1
+ require "xccache/core/syntax/plist"
2
+
3
+ module XCCache
4
+ module SPM
5
+ class XCFramework
6
+ class Metadata < PlistRepresentable
7
+ class Library < Hash
8
+ def id
9
+ self["LibraryIdentifier"]
10
+ end
11
+
12
+ def platform
13
+ self["SupportedPlatform"]
14
+ end
15
+
16
+ def archs
17
+ self["SupportedArchitectures"]
18
+ end
19
+
20
+ def simulator?
21
+ self["SupportedPlatformVariant"] == "simulator"
22
+ end
23
+
24
+ def triples
25
+ @triples ||= archs.map do |arch|
26
+ simulator? ? "#{arch}-#{platform}-simulator" : "#{arch}-#{platform}"
27
+ end
28
+ end
29
+ end
30
+
31
+ def available_libraries
32
+ @available_libraries ||= raw.fetch("AvailableLibraries", []).map { |h| Library.new.merge(h) }
33
+ end
34
+
35
+ def triples
36
+ @triples ||= available_libraries.flat_map(&:triples)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,180 @@
1
+ require "xccache/utils/template"
2
+
3
+ module XCCache
4
+ module SPM
5
+ class FrameworkSlice < Buildable
6
+ def build(_options = {})
7
+ UI.section("Building slice: #{name} (#{config}, #{sdk})".bold) do
8
+ swift_build
9
+ create_framework
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def override_resource_bundle_accessor
16
+ # By default, Swift generates resource_bundle_accessor.swift for targets having resources
17
+ # (Check .build/<Target>.build/DerivedSources/resource_bundle_accessor.swift)
18
+ # This enables accessing the resource bundle via `Bundle.module`.
19
+ # However, `Bundle.module` expects the resource bundle to be under the app bundle,
20
+ # which is not the case for binary targets. Instead the bundle is under `Frameworks/<Target>.framework`
21
+ # WORKAROUND:
22
+ # - Overriding resource_bundle_accessor.swift to add `Frameworks/<Target>.framework` to the search list
23
+ # - Compiling this file into an `.o` file before using `libtool` to create the framework binary
24
+ UI.message("Override resource_bundle_accessor")
25
+ template_name = use_clang? ? "resource_bundle_accessor.m" : "resource_bundle_accessor.swift"
26
+ source_path = tmpdir / File.basename(template_name)
27
+ obj_path = products_dir / "#{module_name}.build" / "#{source_path.basename}.o"
28
+ Template.new(template_name).render(
29
+ { :pkg => pkg_target.pkg_name, :target => name, :module_name => module_name },
30
+ save_to: source_path
31
+ )
32
+
33
+ if use_clang?
34
+ cmd = ["xcrun", "clang"]
35
+ cmd << "-x" << "objective-c"
36
+ cmd << "-target" << sdk.triple << "-isysroot" << sdk.sdk_path
37
+ cmd << "-o" << obj_path.to_s
38
+ cmd << "-c" << source_path
39
+ else
40
+ cmd = ["xcrun", "swiftc"]
41
+ cmd << "-emit-library" << "-emit-object"
42
+ cmd << "-module-name" << module_name
43
+ cmd << "-target" << sdk.triple << "-sdk" << sdk.sdk_path
44
+ cmd << "-o" << obj_path.to_s
45
+ cmd << source_path
46
+ end
47
+ Sh.run(cmd)
48
+ end
49
+
50
+ def create_framework
51
+ override_resource_bundle_accessor if resource_bundle_product_path.exist?
52
+ create_info_plist
53
+ create_framework_binary
54
+ create_headers
55
+ create_modules
56
+ copy_resource_bundles if resource_bundle_product_path.exist?
57
+ end
58
+
59
+ def create_framework_binary
60
+ # Write .o file list into a file
61
+ obj_paths = products_dir.glob("#{module_name}.build/**/*.o")
62
+ raise GeneralError, "Detected no object files for #{name}" if obj_paths.empty?
63
+
64
+ objlist_path = tmpdir / "objects.txt"
65
+ objlist_path.write(obj_paths.map(&:to_s).join("\n"))
66
+
67
+ cmd = ["libtool", "-static"]
68
+ cmd << "-o" << "#{path}/#{module_name}"
69
+ cmd << "-filelist" << objlist_path.to_s
70
+ Sh.run(cmd)
71
+ FileUtils.chmod("+x", path / module_name)
72
+ end
73
+
74
+ def create_info_plist
75
+ Template.new("framework.info.plist").render(
76
+ { :module_name => module_name },
77
+ save_to: path / "Info.plist",
78
+ )
79
+ end
80
+
81
+ def create_headers
82
+ copy_headers
83
+ end
84
+
85
+ def create_modules
86
+ copy_swiftmodules unless use_clang?
87
+
88
+ UI.message("Creating framework modulemap")
89
+ Template.new("framework.modulemap").render(
90
+ { :module_name => module_name, :target => name },
91
+ save_to: modules_dir / "module.modulemap"
92
+ )
93
+ end
94
+
95
+ def copy_headers
96
+ UI.message("Copying headers")
97
+ swift_header_paths = products_dir.glob("#{module_name}.build/*-Swift.h")
98
+ paths = swift_header_paths + pkg_target.header_paths
99
+ paths.each { |p| process_header(p) }
100
+
101
+ umbrella_header_content = paths.map { |p| "#include <#{module_name}/#{p.basename}>" }.join("\n")
102
+ (headers_dir / "#{name}-umbrella.h").write(umbrella_header_content)
103
+ end
104
+
105
+ def process_header(path)
106
+ handle_angle_bracket_import = proc do |statement, header|
107
+ next statement if header.include?("/")
108
+
109
+ # NOTE: If importing a header with flat angle-bracket style (ex. `#import <foo.h>`)
110
+ # The header `foo.h` may belong to a dependency's headers.
111
+ # When packaging into xcframework, `#import <foo.h>` no longer works because `foo.h`
112
+ # coz it's not visible within the framework's headers
113
+ # -> We need to explicitly specify the module it belongs to, ex. `#import <foo/foo.h>`
114
+ targets = [pkg_target] + pkg_target.recursive_targets
115
+ target = targets.find { |t| t.header_paths.any? { |p| p.basename.to_s == header } }
116
+ next statement if target.nil?
117
+
118
+ corrected_statement = statement.sub("<#{header}>", "<#{target.module_name}/#{header}>")
119
+ <<~CONTENT
120
+ // -------------------------------------------------------------------------------------------------
121
+ // NOTE: This import was corrected by xccache, from flat angle-bracket to nested angle-bracket style
122
+ // Original: `#{statement}`
123
+ #{corrected_statement}
124
+ // -------------------------------------------------------------------------------------------------
125
+ CONTENT
126
+ end
127
+
128
+ content = path.read.gsub(/^ *#import <(.+)>/) { |m| handle_angle_bracket_import.call(m, $1) }
129
+ (headers_dir / path.basename).write(content)
130
+ end
131
+
132
+ def copy_swiftmodules
133
+ UI.message("Copying swiftmodules")
134
+ swiftmodule_dir = Dir.prepare("#{modules_dir}/#{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 use_clang?
164
+ pkg_target.use_clang?
165
+ end
166
+
167
+ def resource_bundle_product_path
168
+ @resource_bundle_product_path ||= products_dir / pkg_target.resource_bundle_name
169
+ end
170
+
171
+ def headers_dir
172
+ @headers_dir ||= Dir.prepare(path / "Headers")
173
+ end
174
+
175
+ def modules_dir
176
+ @modules_dir ||= Dir.prepare(path / "Modules")
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,56 @@
1
+ require "xccache/spm/build"
2
+
3
+ module XCCache
4
+ module SPM
5
+ class XCFramework < Buildable
6
+ attr_reader :slices
7
+
8
+ def initialize(options = {})
9
+ super
10
+ @slices ||= @sdks.map do |sdk|
11
+ FrameworkSlice.new(
12
+ **options,
13
+ sdks: [sdk],
14
+ path: Dir.prepare(tmpdir / sdk.triple / "#{module_name}.framework"),
15
+ )
16
+ end
17
+ end
18
+
19
+ def build(merge_slices: false, **_options)
20
+ tmp_new_path = tmpdir / "new.xcframework"
21
+ tmp_existing_path = tmpdir / "existing.framework"
22
+
23
+ slices.each(&:build)
24
+ UI.section("Creating #{name}.xcframework from slices") do
25
+ create_xcframework(from: slices.map(&:path), to: tmp_new_path)
26
+ end
27
+
28
+ path.copy(to: tmp_existing_path) if path.exist? && merge_slices
29
+ path.rmtree if path.exist?
30
+
31
+ if merge_slices && tmp_existing_path.exist?
32
+ UI.section("Merging #{name}.xcframework with existing slices") do
33
+ framework_paths =
34
+ [tmp_new_path, tmp_existing_path]
35
+ .flat_map { |p| p.glob("*/*.framework") }
36
+ .uniq { |p| p.parent.basename.to_s } # uniq by id (ex. ios-arm64), preferred new ones
37
+ create_xcframework(from: framework_paths, to: path)
38
+ end
39
+ else
40
+ path.parent.mkpath
41
+ tmp_new_path.copy(to: path)
42
+ end
43
+ UI.info("-> XCFramework: #{path.to_s.dark}")
44
+ end
45
+
46
+ def create_xcframework(options = {})
47
+ cmd = ["xcodebuild", "-create-xcframework"]
48
+ cmd << "-allow-internal-distribution" unless library_evolution?
49
+ cmd << "-output" << options[:to]
50
+ options[:from].each { |p| cmd << "-framework" << p }
51
+ cmd << "> /dev/null" # Only care about errors
52
+ Sh.run(cmd)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,2 @@
1
+ require_relative "build"
2
+ Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
@@ -0,0 +1 @@
1
+ Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
@@ -0,0 +1,26 @@
1
+ module XCCache
2
+ class Storage
3
+ include Config::Mixin
4
+
5
+ def initialize(options = {}); end
6
+
7
+ def pull
8
+ print_warnings
9
+ end
10
+
11
+ def push
12
+ print_warnings
13
+ end
14
+
15
+ private
16
+
17
+ def print_warnings
18
+ UI.warn <<~DESC
19
+ Do nothing as remote cache is not set up yet.
20
+
21
+ To set it up, specify `remote` in `xccache.yml`.
22
+ See: https://github.com/trinhngocthuyen/xccache/blob/main/docs/configuration.md#remote
23
+ DESC
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ require_relative "base"
2
+
3
+ module XCCache
4
+ class GitStorage < Storage
5
+ attr_reader :branch
6
+
7
+ def initialize(options = {})
8
+ super
9
+ if (@remote = options[:remote])
10
+ @remote = File.expand_path(@remote) unless ["http", "https", "git"].include?(URI.parse(@remote).scheme)
11
+ ensure_remote
12
+ end
13
+ @branch = options[:branch]
14
+ end
15
+
16
+ def pull
17
+ git.fetch("--depth 1 origin #{branch}")
18
+ git.switch("--detach FETCH_HEAD", capture: true)
19
+ git.clean("-dfx", capture: true)
20
+ # Re-create local branch so that it has the latest from remote
21
+ git.branch("-D #{branch} || true", capture: true)
22
+ git.checkout("-b #{branch}", capture: true)
23
+ end
24
+
25
+ def push
26
+ return UI.info("No changes to push, cache repo is clean".magenta) if git.clean?
27
+
28
+ git.add(".")
29
+ git.commit("-m \"Update cache at #{Time.new}\"")
30
+ git.push("origin #{branch}")
31
+ end
32
+
33
+ private
34
+
35
+ def git
36
+ @git ||= Git.new(config.spm_repo_dir)
37
+ end
38
+
39
+ def ensure_remote
40
+ existing = git.remote("get-url origin || true", capture: true, log_cmd: false)[0].strip
41
+ return if @remote == existing
42
+ return git.remote("add origin #{@remote}") if existing.empty?
43
+ git.remote("set-url origin #{@remote}")
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,53 @@
1
+ require_relative "base"
2
+
3
+ module XCCache
4
+ class S3Storage < Storage
5
+ def initialize(options = {})
6
+ super
7
+ @uri = options[:uri]
8
+ @creds_path = Pathname(options[:creds_path] || "~/.xccache/s3.creds.json").expand_path
9
+ creds = JSONRepresentable.new(@creds_path)
10
+ @access_key_id = creds["access_key_id"]
11
+ @secret_access_key = creds["secret_access_key"]
12
+ end
13
+
14
+ def pull
15
+ s3_sync(src: @uri, dst: config.spm_repo_dir)
16
+ end
17
+
18
+ def push
19
+ s3_sync(src: config.spm_repo_dir, dst: @uri)
20
+ end
21
+
22
+ private
23
+
24
+ def s3_sync(src: nil, dst: nil)
25
+ validate!
26
+ UI.info("Syncing cache from #{src.to_s.bold} to #{dst.to_s.bold}...")
27
+ env = {
28
+ "AWS_ACCESS_KEY_ID" => @access_key_id,
29
+ "AWS_SECRET_ACCESS_KEY" => @secret_access_key,
30
+ }
31
+ cmd = ["aws", "s3", "sync"]
32
+ cmd << "--exact-timestamps" << "--delete"
33
+ cmd << "--include" << "*.xcframework"
34
+ cmd << "--include" << "*.macro"
35
+ cmd << src << dst
36
+ Sh.run(cmd, env: env)
37
+ end
38
+
39
+ def validate!
40
+ if File.which("awsss").nil?
41
+ raise GeneralError, "awscli is not installed. Please install it via brew: `brew install awscli`"
42
+ end
43
+ return unless @access_key_id.nil? || @secret_access_key.nil?
44
+ raise GeneralError, <<~DESC
45
+ Please ensure the credentials json at #{@creds_path}. Example:
46
+ {
47
+ "access_key_id": <ACCESS_KEY_ID>,
48
+ "secret_access_key": <SECRET_ACCESS_KEY>
49
+ }
50
+ DESC
51
+ end
52
+ end
53
+ end
@@ -0,0 +1 @@
1
+ Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
@@ -0,0 +1,49 @@
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
+ :macosx => "arm64-apple-macosx",
12
+ }.freeze
13
+
14
+ def initialize(name)
15
+ @name = name
16
+ end
17
+
18
+ def to_s
19
+ name
20
+ end
21
+
22
+ def triple(without_vendor: false)
23
+ res = NAME_TO_TRIPLE[name.to_sym]
24
+ res = res.sub("-apple", "") if without_vendor
25
+ res
26
+ end
27
+
28
+ def sdk_path
29
+ # rubocop:disable Layout/LineLength
30
+ # /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk
31
+ # rubocop:enable Layout/LineLength
32
+ @sdk_path ||= Pathname(Sh.capture_output("xcrun --sdk #{name} --show-sdk-path")).realpath
33
+ end
34
+
35
+ def sdk_platform_developer_path
36
+ @sdk_platform_developer_path ||= sdk_path.parent.parent # iPhoneSimulator.platform/Developer
37
+ end
38
+
39
+ def swiftc_args
40
+ developer_library_frameworks_path = sdk_platform_developer_path / "Library" / "Frameworks"
41
+ developer_usr_lib_path = sdk_platform_developer_path / "usr" / "lib"
42
+ [
43
+ "-F#{developer_library_frameworks_path}",
44
+ "-I#{developer_usr_lib_path}",
45
+ ]
46
+ end
47
+ end
48
+ end
49
+ 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,20 @@
1
+ require "xcodeproj"
2
+
3
+ module Xcodeproj
4
+ class Project
5
+ module Object
6
+ class XCBuildConfiguration
7
+ def base_configuration_xcconfig
8
+ path = base_configuration_xcconfig_path
9
+ Config.new(path) if path
10
+ end
11
+
12
+ def base_configuration_xcconfig_path
13
+ return base_configuration_reference.real_path if base_configuration_reference
14
+ return unless base_configuration_reference_anchor && base_configuration_reference_relative_path
15
+ project.dir / base_configuration_reference_anchor.path / base_configuration_reference_relative_path
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ require "xcodeproj"
2
+
3
+ module Xcodeproj
4
+ class Config
5
+ def path
6
+ @filepath
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ require "xcodeproj"
2
+
3
+ module Xcodeproj
4
+ class Project
5
+ module Object
6
+ class PBXFileSystemSynchronizedRootGroup
7
+ attribute :name, String
8
+
9
+ def display_name
10
+ return name if name
11
+ return File.basename(path) if path
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require "xcodeproj"
2
+
3
+ module Xcodeproj
4
+ class Project
5
+ module Object
6
+ class PBXGroup
7
+ def synced_groups
8
+ children.grep(PBXFileSystemSynchronizedRootGroup)
9
+ end
10
+
11
+ def ensure_synced_group(name: nil, path: nil)
12
+ synced_groups.find { |g| g.name == name } || new_synced_group(name: name, path: path)
13
+ end
14
+
15
+ def new_synced_group(name: nil, path: nil)
16
+ path = path.relative_path_from(project.dir) unless path.relative?
17
+ synced_group = project.new(PBXFileSystemSynchronizedRootGroup)
18
+ synced_group.path = path.to_s
19
+ synced_group.name = name || path.basename.to_s
20
+ self << synced_group
21
+ synced_group
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end