xccache 0.0.1a2 → 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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/xccache/assets/templates/cachemap.html.template +1 -0
  3. data/lib/xccache/assets/templates/cachemap.js.template +35 -34
  4. data/lib/xccache/assets/templates/cachemap.style.css.template +3 -1
  5. data/lib/xccache/assets/templates/framework.modulemap.template +1 -1
  6. data/lib/xccache/assets/templates/resource_bundle_accessor.m.template +3 -3
  7. data/lib/xccache/assets/templates/umbrella.Package.swift.template +43 -7
  8. data/lib/xccache/cache/cachemap.rb +11 -15
  9. data/lib/xccache/command/base.rb +29 -0
  10. data/lib/xccache/command/build.rb +12 -6
  11. data/lib/xccache/command/pkg/build.rb +2 -6
  12. data/lib/xccache/command/pkg.rb +1 -0
  13. data/lib/xccache/command/remote/pull.rb +15 -0
  14. data/lib/xccache/command/remote/push.rb +15 -0
  15. data/lib/xccache/command/remote.rb +39 -0
  16. data/lib/xccache/command/rollback.rb +2 -1
  17. data/lib/xccache/command/use.rb +3 -2
  18. data/lib/xccache/command.rb +18 -2
  19. data/lib/xccache/core/config.rb +30 -2
  20. data/lib/xccache/core/git.rb +11 -3
  21. data/lib/xccache/core/log.rb +11 -1
  22. data/lib/xccache/core/parallel.rb +10 -0
  23. data/lib/xccache/core/sh.rb +4 -3
  24. data/lib/xccache/core/syntax/plist.rb +17 -0
  25. data/lib/xccache/core/system.rb +8 -0
  26. data/lib/xccache/installer/build.rb +2 -6
  27. data/lib/xccache/installer/rollback.rb +1 -0
  28. data/lib/xccache/installer/use.rb +3 -2
  29. data/lib/xccache/installer.rb +50 -4
  30. data/lib/xccache/spm/build.rb +53 -0
  31. data/lib/xccache/spm/desc/base.rb +10 -3
  32. data/lib/xccache/spm/desc/desc.rb +31 -15
  33. data/lib/xccache/spm/desc/target/binary.rb +8 -0
  34. data/lib/xccache/spm/desc/target/macro.rb +8 -0
  35. data/lib/xccache/spm/desc/target.rb +28 -2
  36. data/lib/xccache/spm/macro.rb +44 -0
  37. data/lib/xccache/spm/mixin.rb +4 -1
  38. data/lib/xccache/spm/pkg/base.rb +48 -44
  39. data/lib/xccache/spm/pkg/umbrella/build.rb +2 -2
  40. data/lib/xccache/spm/pkg/umbrella/cachemap.rb +44 -27
  41. data/lib/xccache/spm/pkg/umbrella/descs.rb +2 -13
  42. data/lib/xccache/spm/pkg/umbrella/manifest.rb +1 -1
  43. data/lib/xccache/spm/pkg/umbrella/viz.rb +3 -3
  44. data/lib/xccache/spm/pkg/umbrella/xcconfigs.rb +31 -0
  45. data/lib/xccache/spm/pkg/umbrella.rb +20 -10
  46. data/lib/xccache/spm/xcframework/metadata.rb +41 -0
  47. data/lib/xccache/{framework → spm/xcframework}/slice.rb +50 -53
  48. data/lib/xccache/spm/xcframework/xcframework.rb +56 -0
  49. data/lib/xccache/spm/xcframework.rb +2 -0
  50. data/lib/xccache/storage/base.rb +26 -0
  51. data/lib/xccache/storage/git.rb +46 -0
  52. data/lib/xccache/storage/s3.rb +53 -0
  53. data/lib/xccache/storage.rb +1 -0
  54. data/lib/xccache/swift/sdk.rb +21 -2
  55. data/lib/xccache/xcodeproj/build_configuration.rb +20 -0
  56. data/lib/xccache/xcodeproj/config.rb +9 -0
  57. data/lib/xccache/xcodeproj/file_system_synchronized_root_group.rb +17 -0
  58. data/lib/xccache/xcodeproj/group.rb +26 -0
  59. data/lib/xccache/xcodeproj/pkg.rb +5 -1
  60. data/lib/xccache/xcodeproj/project.rb +21 -1
  61. data/lib/xccache/xcodeproj/target.rb +5 -3
  62. metadata +40 -5
  63. data/lib/xccache/framework/xcframework.rb +0 -51
@@ -1,15 +1,20 @@
1
1
  require "json"
2
- require "xccache/framework/slice"
3
- require "xccache/framework/xcframework"
2
+ require "xccache/spm/xcframework/slice"
3
+ require "xccache/spm/xcframework/xcframework"
4
+ require "xccache/spm/xcframework/metadata"
4
5
  require "xccache/swift/sdk"
5
6
 
6
7
  module XCCache
7
8
  module SPM
8
9
  class Package
10
+ include Cacheable
11
+ cacheable :pkg_desc_of_target
12
+
9
13
  attr_reader :root_dir
10
14
 
11
15
  def initialize(options = {})
12
16
  @root_dir = Pathname(options[:root_dir] || ".").expand_path
17
+ @warn_if_not_direct_target = options.fetch(:warn_if_not_direct_target, true)
13
18
  end
14
19
 
15
20
  def build(options = {})
@@ -17,44 +22,50 @@ module XCCache
17
22
  targets = options.delete(:targets) || []
18
23
  raise GeneralError, "No targets were specified" if targets.empty?
19
24
 
20
- targets.map { |t| t.split("/")[-1] }.each do |t|
21
- UI.section("\n▶ Building target: #{t}".bold.magenta) do
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
22
27
  build_target(**options, target: t)
23
28
  rescue StandardError => e
24
29
  UI.error("Failed to build target: #{t}. Error: #{e}")
25
- raise e unless config.ignore_build_errors?
30
+ raise e unless Config.instance.ignore_build_errors?
26
31
  end
27
32
  end
28
33
  end
29
34
 
30
- def build_target(target: nil, sdk: nil, config: nil, out_dir: nil, **options)
31
- target_pkg_desc = pkg_desc_of_target(target, skip_resolve: options[:skip_resolve])
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])
32
37
  if target_pkg_desc.binary_targets.any? { |t| t.name == target }
33
38
  return UI.warn("Target #{target} is a binary target -> no need to build")
34
39
  end
35
40
 
36
- sdks = (sdk || "iphonesimulator").split(",")
41
+ target = target_pkg_desc.get_target(target)
37
42
 
38
43
  out_dir = Pathname(out_dir || ".")
39
- out_dir /= target if options[:checksum]
40
- basename = options[:checksum] ? "#{target}-#{target_pkg_desc.checksum}.xcframework" : "#{target}.xcframework"
41
-
42
- Framework::XCFramework.new(
43
- name: target,
44
- pkg_dir: root_dir,
45
- config: config,
46
- sdks: sdks,
47
- path: out_dir / basename,
48
- pkg_desc: target_pkg_desc,
49
- ).create
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
50
61
  end
51
62
 
52
63
  def resolve(force: false)
53
64
  return if @resolved && !force
54
65
 
55
- UI.info("Resolving package dependencies (package: #{root_dir.basename.to_s.dark})")
56
- Sh.run("swift package resolve --package-path #{root_dir} 2>&1")
57
- create_symlinks_to_local_pkgs
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
58
69
  @resolved = true
59
70
  end
60
71
 
@@ -65,39 +76,32 @@ module XCCache
65
76
  raise GeneralError, "No Package.swift in #{root_dir}. Are you sure you're running on a package dir?"
66
77
  end
67
78
 
68
- def pkg_desc_of_target(name, skip_resolve: false)
69
- # TODO: Refactor this resolution logic
70
- find_pkg_desc = proc do
71
- # The current package contains the given target
72
- return pkg_desc if pkg_desc.has_target?(name)
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)
73
82
 
83
+ if @warn_if_not_direct_target
74
84
  UI.message(
75
85
  "#{name.yellow.dark} is not a direct target of package #{root_dir.basename.to_s.dark} " \
76
86
  "-> trigger from dependencies"
77
87
  )
78
- # Otherwise, it's inside one of the dependencies. Need to resolve then find it
79
- resolve unless skip_resolve
80
- root_dir.glob(".build/checkouts/*").each do |dir|
81
- desc = Description.in_dir(dir)
82
- return desc if desc.has_target?(name)
83
- end
84
- raise GeneralError, "Cannot find package with the given target #{name}"
85
88
  end
86
-
87
- @cache_pkg_desc_by_name ||= {}
88
- @cache_pkg_desc_by_name[name] = find_pkg_desc.call unless @cache_pkg_desc_by_name.key?(name)
89
- @cache_pkg_desc_by_name[name]
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}"
90
100
  end
91
101
 
92
102
  def pkg_desc
93
103
  @pkg_desc ||= Description.in_dir(root_dir)
94
104
  end
95
-
96
- def create_symlinks_to_local_pkgs
97
- pkg_desc.dependencies.select(&:local?).each do |dep|
98
- dep.path.symlink_to(root_dir / ".build/checkouts/#{dep.slug}")
99
- end
100
- end
101
105
  end
102
106
  end
103
107
  end
@@ -1,14 +1,14 @@
1
1
  module XCCache
2
2
  module SPM
3
3
  class Package
4
- module UmbrellaBuilldMixin
4
+ module UmbrellaBuildMixin
5
5
  def build(options = {})
6
6
  to_build = targets_to_build(options)
7
7
  return UI.warn("Detected no targets to build among cache-missed targets") if to_build.empty?
8
8
 
9
9
  UI.info("-> Targets to build: #{to_build.to_s.bold}")
10
10
  super(options.merge(:targets => to_build))
11
- sync_cachemap
11
+ sync_cachemap(sdks: options[:sdks])
12
12
  end
13
13
 
14
14
  def targets_to_build(options)
@@ -2,27 +2,29 @@ module XCCache
2
2
  module SPM
3
3
  class Package
4
4
  module UmbrellaCachemapMixin
5
- def sync_cachemap
6
- UI.section("Syncing cachemap")
5
+ def sync_cachemap(sdks: [])
6
+ UI.section("Syncing cachemap (sdks: #{sdks.map(&:name)})")
7
7
  nodes, edges, parents = xccache_desc.traverse
8
- cache_data = gen_cache_data(nodes, parents)
9
- targets_data, deps_data = {}, {}
8
+ cache_data = gen_cache_data(nodes, parents, sdks)
9
+ targets_data, macros_data, deps_data = {}, {}, {}
10
10
  xccache_desc.targets.each do |agg_target|
11
- targets_data[agg_target.name] = agg_target.direct_dependencies.flat_map do |d|
11
+ targets, macros = [], []
12
+ agg_target.direct_dependencies.each do |d|
13
+ all_hit = d.recursive_targets.all? { |t| cache_data[t] == :hit }
12
14
  # If any associated targets is missed -> use original product form
13
15
  # 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)
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
21
22
  end
22
23
 
23
24
  config.cachemap.raw = {
24
25
  "manifest" => {
25
26
  "targets" => targets_data,
27
+ "macros" => macros_data,
26
28
  "deps" => deps_data,
27
29
  },
28
30
  "cache" => cache_data.transform_keys(&:full_name),
@@ -37,11 +39,11 @@ module XCCache
37
39
 
38
40
  private
39
41
 
40
- def gen_cache_data(nodes, parents)
42
+ def gen_cache_data(nodes, parents, sdks)
41
43
  result = nodes.to_h do |node|
42
44
  res = if config.ignore?(node.full_name) then :ignored
43
45
  else
44
- verify_binary?(node) ? :hit : :missed
46
+ verify_binary?(node, sdks) ? :hit : :missed
45
47
  end
46
48
  [node, res]
47
49
  end
@@ -56,35 +58,50 @@ module XCCache
56
58
  result[node] = :missed if result[node] == :hit
57
59
  to_visit += parents[node] if parents.key?(node)
58
60
  end
59
- result
61
+ result.reject { |k, _| k.name.end_with?(".xccache") }
60
62
  end
61
63
 
62
- def verify_binary?(target)
64
+ def verify_binary?(target, sdks)
63
65
  return true if target.binary?
64
66
 
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`
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`
68
82
  # Otherwise, remove symlink `A.xcframework`
69
- if bpath_with_checksum.exist?
83
+ if check.call
70
84
  bpath_with_checksum.symlink_to(bpath)
71
85
  elsif bpath.exist?
72
86
  bpath.rmtree
73
87
  end
74
- bpath_with_checksum.exist?
88
+ bpath.exist?
75
89
  end
76
90
 
77
- def binary_path(name, checksum: nil)
91
+ def binary_path(name, checksum: nil, in_repo: false)
78
92
  suffix = checksum.nil? ? "" : "-#{checksum}"
79
- p = config.spm_binaries_frameworks_dir / File.basename(name, ".binary")
80
- p / "#{p.basename}#{suffix}.xcframework"
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}"
81
97
  end
82
98
 
83
99
  def target_to_cytoscape_node(x, cache_data)
84
100
  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
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
88
105
  h
89
106
  end
90
107
  end
@@ -3,19 +3,8 @@ module XCCache
3
3
  class Package
4
4
  module UmbrellaDescsMixin
5
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
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)
19
8
  end
20
9
  end
21
10
 
@@ -3,7 +3,7 @@ module XCCache
3
3
  class Package
4
4
  module UmbrellaManifestMixin
5
5
  def write_manifest(no_cache: false)
6
- UI.info("Writing Package.swift (package: #{root_dir.basename.to_s.dark})")
6
+ UI.info("Writing Package.swift (package: #{root_dir.basename})")
7
7
  Template.new("umbrella.Package.swift").render(
8
8
  {
9
9
  :timestamp => Time.new.strftime("%F %T"),
@@ -13,15 +13,15 @@ module XCCache
13
13
  p.to_s.start_with?(root_dir.to_s) ? p.relative_path_from(root_dir).to_s : p.to_s
14
14
  end
15
15
 
16
- UI.message("Cachemap visualization: #{html_path}")
16
+ UI.info("Cachemap visualization: #{html_path}")
17
17
  Template.new("cachemap.html").render(
18
18
  {
19
19
  :root_dir => root_dir.to_s,
20
20
  :root_dir_short => root_dir.basename.to_s,
21
21
  :lockfile_path => config.lockfile.path.to_s,
22
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),
23
+ :binaries_dir => config.spm_binaries_dir.to_s,
24
+ :binaries_dir_short => to_relative.call(config.spm_binaries_dir),
25
25
  :desc_hit => stats[:hit],
26
26
  :desc_missed => stats[:missed],
27
27
  :desc_ignored => stats[:ignored],
@@ -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
@@ -12,9 +12,10 @@ module XCCache
12
12
  include Config::Mixin
13
13
  include UmbrellaCachemapMixin
14
14
  include UmbrellaDescsMixin
15
- include UmbrellaBuilldMixin
15
+ include UmbrellaBuildMixin
16
16
  include UmbrellaManifestMixin
17
17
  include UmbrellaVizMixin
18
+ include UmbrellaXCConfigsMixin
18
19
 
19
20
  def initialize(options = {})
20
21
  super
@@ -25,12 +26,13 @@ module XCCache
25
26
 
26
27
  def prepare(options = {})
27
28
  create
28
- resolve unless options[:skip_resolve]
29
- create_symlinks
29
+ resolve unless options[:skip_resolving_dependencies]
30
+ create_symlinks_for_convenience
31
+ create_symlinks_to_local_pkgs
30
32
  gen_metadata
31
33
  resolve_recursive_dependencies
32
34
  create_symlinks_to_artifacts
33
- sync_cachemap
35
+ sync_cachemap(sdks: options[:sdks])
34
36
  end
35
37
 
36
38
  def resolve_recursive_dependencies
@@ -51,22 +53,30 @@ module XCCache
51
53
  end
52
54
  end
53
55
 
54
- def create_symlinks
56
+ def create_symlinks_for_convenience
55
57
  # Symlinks for convenience
56
58
  (root_dir / "binaries").symlink_to(root_dir.parent / "binaries")
57
59
  (root_dir / ".build").symlink_to(root_dir.parent / ".build")
58
60
  (root_dir / ".build/checkouts").symlink_to(root_dir.parent / "checkouts")
59
61
  end
60
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
+
61
72
  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?
73
+ # Clean up symlinks beforehand
74
+ config.spm_binaries_dir.glob("*/*.{xcframework,macro}").each do |p|
75
+ p.rmtree if p.symlink?
65
76
  end
66
77
 
67
- UI.message("Creating symlinks to binary artifacts of targets: #{binary_targets.map(&:full_name).to_s.dark}")
68
78
  binary_targets.each do |target|
69
- dst_path = config.spm_binaries_frameworks_dir / target.name / "#{target.name}.xcframework"
79
+ dst_path = config.spm_binaries_dir / target.name / "#{target.name}.xcframework"
70
80
  # For local xcframework, just symlink to the path
71
81
  # Zip frameworks (either of local or remote pkgs) are unzipped in the build artifacts
72
82
  target.local_binary_path.symlink_to(dst_path) if target.local_binary_path&.extname == ".xcframework"
@@ -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
@@ -1,34 +1,11 @@
1
1
  require "xccache/utils/template"
2
2
 
3
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
4
+ module SPM
5
+ class FrameworkSlice < Buildable
6
+ def build(_options = {})
20
7
  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)/)
8
+ swift_build
32
9
  create_framework
33
10
  end
34
11
  end
@@ -102,36 +79,59 @@ module XCCache
102
79
  end
103
80
 
104
81
  def create_headers
105
- Dir.prepare(path / "Headers")
106
- copy_headers if use_clang?
82
+ copy_headers
107
83
  end
108
84
 
109
85
  def create_modules
110
- Dir.prepare(path / "Modules")
111
- return copy_swiftmodules unless use_clang?
86
+ copy_swiftmodules unless use_clang?
112
87
 
113
88
  UI.message("Creating framework modulemap")
114
89
  Template.new("framework.modulemap").render(
115
- { :module_name => module_name },
116
- save_to: path / "Modules" / "module.modulemap"
90
+ { :module_name => module_name, :target => name },
91
+ save_to: modules_dir / "module.modulemap"
117
92
  )
118
93
  end
119
94
 
120
95
  def copy_headers
121
96
  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)
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
130
  end
131
131
 
132
132
  def copy_swiftmodules
133
133
  UI.message("Copying swiftmodules")
134
- swiftmodule_dir = Dir.prepare("#{path}/Modules/#{module_name}.swiftmodule")
134
+ swiftmodule_dir = Dir.prepare("#{modules_dir}/#{module_name}.swiftmodule")
135
135
  swiftinterfaces = products_dir.glob("#{module_name}.build/#{module_name}.swiftinterface")
136
136
  to_copy = products_dir.glob("Modules/#{module_name}.*") + swiftinterfaces
137
137
  to_copy.each do |p|
@@ -160,23 +160,20 @@ module XCCache
160
160
  @products_dir ||= pkg_dir / ".build" / sdk.triple / config
161
161
  end
162
162
 
163
- def swift_build_args
164
- [
165
- "--configuration", config,
166
- "--triple", sdk.triple,
167
- ]
168
- end
169
-
170
163
  def use_clang?
171
164
  pkg_target.use_clang?
172
165
  end
173
166
 
174
- def pkg_target
175
- @pkg_target ||= pkg_desc.get_target(name)
167
+ def resource_bundle_product_path
168
+ @resource_bundle_product_path ||= products_dir / pkg_target.resource_bundle_name
176
169
  end
177
170
 
178
- def resource_bundle_product_path
179
- @resource_bundle_product_path ||= products_dir / pkg_target.bundle_name
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")
180
177
  end
181
178
  end
182
179
  end