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,36 @@
1
+ module XCCache
2
+ class Installer
3
+ class Rollback < Installer
4
+ def install!
5
+ update_projects do |project|
6
+ UI.section("Rolling back cache for project #{project.display_name}".bold.green) do
7
+ rollback_for_project(project)
8
+ end
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def rollback_for_project(project)
15
+ hash = lockfile.hash_for_project(project)
16
+ pkgs, deps_by_targets = hash["packages"], hash["dependencies"]
17
+
18
+ # Add packages back to the project
19
+ pkgs.reject { |h| project.has_pkg?(h) }.each do |h|
20
+ project.add_pkg(h)
21
+ end
22
+
23
+ # Add products back to `Link Binary with Libraries` of targets
24
+ deps_by_targets.each do |name, deps|
25
+ target = project.get_target(name)
26
+ deps.reject { |d| target.has_pkg_product_dependency?(d) }.each do |d|
27
+ target.add_pkg_product_dependency(d)
28
+ end
29
+ end
30
+
31
+ # Remove .binary product from the project
32
+ project.targets.each(&:remove_xccache_product_dependencies)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ require "xccache/spm"
2
+
3
+ module XCCache
4
+ class Installer
5
+ class Use < Installer
6
+ def install!
7
+ perform_install do
8
+ update_projects do |project|
9
+ UI.section("Using cache for project #{project.display_name}".bold.green) do
10
+ replace_binaries_for_project(project)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def replace_binaries_for_project(project)
19
+ project.add_xccache_pkg unless project.has_xccache_pkg?
20
+ project.targets.each do |target|
21
+ target.add_xccache_product_dependency unless target.has_xccache_product_dependency?
22
+ target.remove_pkg_product_dependencies { |d| !d.pkg.xccache_pkg? }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,67 @@
1
+ require "xccache/spm"
2
+ Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
3
+
4
+ module XCCache
5
+ class Installer
6
+ include PkgMixin
7
+
8
+ def initialize(options = {})
9
+ @umbrella_pkg = options[:umbrella_pkg]
10
+ @skip_resolving_dependencies = options[:skip_resolving_dependencies]
11
+ end
12
+
13
+ def perform_install
14
+ verify_projects!
15
+ sync_lockfile if @umbrella_pkg.nil?
16
+ umbrella_pkg.prepare(skip_resolve: @skip_resolving_dependencies) if @umbrella_pkg.nil?
17
+ yield
18
+ umbrella_pkg.write_manifest
19
+ umbrella_pkg.gen_cachemap_viz
20
+ end
21
+
22
+ def sync_lockfile
23
+ UI.info("Syncing lockfile")
24
+ update_projects do |project|
25
+ lockfile.deep_merge!(project.display_name => lockfile_hash_for_project(project))
26
+ end
27
+ lockfile.save
28
+ end
29
+
30
+ def lockfile
31
+ config.lockfile
32
+ end
33
+
34
+ def projects
35
+ config.projects
36
+ end
37
+
38
+ def save_projects
39
+ yield if block_given?
40
+ projects.each(&:save)
41
+ end
42
+
43
+ def update_projects
44
+ projects.each do |project|
45
+ yield project if block_given?
46
+ project.save
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def lockfile_hash_for_project(project)
53
+ deps_by_targets = project.targets.to_h do |target|
54
+ deps = target.non_xccache_pkg_product_dependencies.map { |d| "#{d.pkg.slug}/#{d.product_name}" }
55
+ [target.name, deps]
56
+ end
57
+ {
58
+ "packages" => project.non_xccache_pkgs.map(&:to_h),
59
+ "dependencies" => deps_by_targets,
60
+ }
61
+ end
62
+
63
+ def verify_projects!
64
+ raise "No projects detected. Are you running on the correct project directory?" if projects.empty?
65
+ end
66
+ end
67
+ end
data/lib/xccache/main.rb CHANGED
@@ -1 +1,3 @@
1
- require "xccache/command"
1
+ require "pry" if ENV["XCCACHE_IMPORT_PRY"] == "true"
2
+ require "pathname"
3
+ Dir["#{__dir__}/*.rb"].sort.each { |f| require f unless f == __FILE__ }
@@ -0,0 +1,61 @@
1
+ require "xccache/core"
2
+
3
+ module XCCache
4
+ module SPM
5
+ class Package
6
+ class BaseObject < JSONRepresentable
7
+ include Config::Mixin
8
+
9
+ attr_accessor :root, :retrieve_pkg_desc
10
+
11
+ def name
12
+ raw["name"]
13
+ end
14
+
15
+ def full_name
16
+ is_a?(Description) ? name : "#{pkg_slug}/#{name}"
17
+ end
18
+
19
+ def inspect
20
+ to_s
21
+ end
22
+
23
+ def display_name
24
+ name
25
+ end
26
+
27
+ def to_s
28
+ "<#{self.class} name=#{display_name}>"
29
+ end
30
+
31
+ def pkg_name
32
+ @pkg_name ||= root.name
33
+ end
34
+
35
+ def pkg_slug
36
+ @pkg_slug ||= root.path.basename(".json").to_s
37
+ end
38
+
39
+ def fetch(key, dtype)
40
+ raw[key].map do |h|
41
+ o = dtype.new(nil, raw: h)
42
+ o.root = root
43
+ o.retrieve_pkg_desc = retrieve_pkg_desc
44
+ o
45
+ end
46
+ end
47
+
48
+ def pkg_desc_of(name)
49
+ retrieve_pkg_desc.call(name)
50
+ end
51
+
52
+ def src_dir
53
+ @src_dir ||= begin
54
+ path = raw.fetch("packageKind", {}).fetch("root", [])[0]
55
+ Pathname.new(path) unless path.nil?
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,40 @@
1
+ require "xccache/spm/desc/base"
2
+
3
+ module XCCache
4
+ module SPM
5
+ class Package
6
+ class Dependency < BaseObject
7
+ def display_name
8
+ slug
9
+ end
10
+
11
+ def local?
12
+ raw.key?("fileSystem")
13
+ end
14
+
15
+ def hash
16
+ @hash ||= local? ? raw["fileSystem"].first : raw["sourceControl"].first
17
+ end
18
+
19
+ def slug
20
+ @slug ||=
21
+ if hash.key?("path")
22
+ File.basename(hash["path"])
23
+ elsif (location = hash["location"]) && location.key?("remote")
24
+ File.basename(location["remote"].flat_map(&:values)[0], ".*")
25
+ else
26
+ hash["identity"]
27
+ end
28
+ end
29
+
30
+ def path
31
+ @path ||= Pathname(hash["path"]).expand_path if local?
32
+ end
33
+
34
+ def pkg_desc
35
+ @pkg_desc ||= pkg_desc_of(slug)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,110 @@
1
+ require "xccache/spm/desc/base"
2
+
3
+ module XCCache
4
+ module SPM
5
+ class Package
6
+ class Description < BaseObject
7
+ include Cacheable
8
+ cacheable :resolve_recursive_dependencies
9
+
10
+ def self.in_dir(dir, save_to_dir: nil, checksum: true)
11
+ path = save_to_dir / "#{dir.basename}.json" unless save_to_dir.nil?
12
+ begin
13
+ raw = JSON.parse(Sh.capture_output("swift package dump-package --package-path #{dir}"))
14
+ this = Description.new(path, raw: raw)
15
+ this.calc_checksum if checksum
16
+ this
17
+ rescue StandardError => e
18
+ UI.error("Failed to dump package in #{dir}. Error: #{e}")
19
+ end
20
+ end
21
+
22
+ def root
23
+ self
24
+ end
25
+
26
+ def metadata
27
+ raw["_metadata"] ||= {}
28
+ end
29
+
30
+ def checksum
31
+ metadata["checksum"]
32
+ end
33
+
34
+ def dependencies
35
+ @dependencies ||= fetch("dependencies", Dependency)
36
+ end
37
+
38
+ def uniform_dependencies
39
+ dependencies.filter_map(&:pkg_desc)
40
+ end
41
+
42
+ def products
43
+ @products ||= fetch("products", Product)
44
+ end
45
+
46
+ def targets
47
+ @targets ||= fetch("targets", Target)
48
+ end
49
+
50
+ def binary_targets
51
+ @binary_targets ||= targets.select(&:binary?)
52
+ end
53
+
54
+ def has_target?(name)
55
+ targets.any? { |t| t.name == name }
56
+ end
57
+
58
+ def get_target(name)
59
+ targets.find { |t| t.name == name }
60
+ end
61
+
62
+ def targets_of_products(name)
63
+ matched_products = products.select { |p| p.name == name }
64
+ matched_products
65
+ .flat_map { |p| targets.select { |t| p.target_names.include?(t.name) } }
66
+ end
67
+
68
+ def resolve_recursive_dependencies(platform: nil)
69
+ products.to_h { |p| [p, p.recursive_targets(platform: platform)] }
70
+ end
71
+
72
+ def local?
73
+ # Workaround: If the pkg dir is under the build checkouts dir -> remote
74
+ !src_dir.to_s.start_with?((config.spm_build_dir / "checkouts").to_s)
75
+ end
76
+
77
+ def calc_checksum
78
+ metadata["checksum"] = git.nil? ? src_dir.checksum : git.sha
79
+ end
80
+
81
+ def traverse
82
+ nodes, edges, parents = [], [], {}
83
+ to_visit = targets.dup
84
+ visited = Set.new
85
+ until to_visit.empty?
86
+ cur = to_visit.pop
87
+ next if visited.include?(cur)
88
+
89
+ visited << cur
90
+ nodes << cur
91
+ yield cur if block_given?
92
+ cur.direct_dependency_targets.each do |t|
93
+ to_visit << t
94
+ edges << [cur, t]
95
+ parents[t] ||= []
96
+ parents[t] << cur
97
+ end
98
+ end
99
+ [nodes, edges, parents]
100
+ end
101
+
102
+ private
103
+
104
+ def git
105
+ @git ||= Git.new(src_dir) if Dir.git?(src_dir)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,36 @@
1
+ require "xccache/spm/desc/base"
2
+
3
+ module XCCache
4
+ module SPM
5
+ class Package
6
+ class Product < BaseObject
7
+ include Cacheable
8
+ cacheable :recursive_targets
9
+
10
+ def target_names
11
+ raw["targets"]
12
+ end
13
+
14
+ def flatten_as_targets
15
+ targets
16
+ end
17
+
18
+ def targets
19
+ @targets ||= root.targets.select { |t| target_names.include?(t.name) }
20
+ end
21
+
22
+ def library?
23
+ type == :library
24
+ end
25
+
26
+ def type
27
+ @type ||= raw["type"].keys.first.to_sym
28
+ end
29
+
30
+ def recursive_targets(platform: nil)
31
+ targets + targets.flat_map { |t| t.recursive_targets(platform: platform) }.uniq
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,138 @@
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
+ def xccache?
11
+ name.end_with?(".xccache")
12
+ end
13
+
14
+ def type
15
+ @type ||= raw["type"].to_sym
16
+ end
17
+
18
+ def bundle_name
19
+ "#{pkg_name}_#{name}.bundle"
20
+ end
21
+
22
+ def flatten_as_targets
23
+ [self]
24
+ end
25
+
26
+ def sources_path
27
+ @sources_path ||= begin
28
+ path = raw["path"] || "Sources/#{name}"
29
+ root.src_dir / path
30
+ end
31
+ end
32
+
33
+ def use_clang?
34
+ !header_paths.empty?
35
+ end
36
+
37
+ def header_paths(options = {})
38
+ paths = []
39
+ paths += public_header_paths if options.fetch(:public, true)
40
+ paths += header_search_paths if options.fetch(:search, false)
41
+ paths
42
+ .flat_map { |p| p.glob("**/*.h*") }
43
+ .map(&:realpath)
44
+ .uniq
45
+ end
46
+
47
+ def settings
48
+ raw["settings"]
49
+ end
50
+
51
+ def header_search_paths
52
+ @header_search_paths ||=
53
+ settings
54
+ .filter_map { |h| h.fetch("kind", {})["headerSearchPath"] }
55
+ .flat_map(&:values)
56
+ .map { |p| sources_path / p }
57
+ end
58
+
59
+ def public_header_paths
60
+ @public_header_paths ||= begin
61
+ res = []
62
+ implicit_path = sources_path / "include"
63
+ res << implicit_path unless implicit_path.glob("**/*.h*").empty?
64
+ res << (sources_path / raw["publicHeadersPath"]) if raw.key?("publicHeadersPath")
65
+ res
66
+ end
67
+ end
68
+
69
+ def resource_paths
70
+ @resource_paths ||= begin
71
+ res = raw.fetch("resources", []).map { |h| sources_path / h["path"] }
72
+ # Refer to the following link for the implicit resources
73
+ # https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package#Add-resource-files
74
+ implicit = sources_path.glob("*.{xcassets,xib,storyboard,xcdatamodeld,lproj}")
75
+ res + implicit
76
+ end
77
+ end
78
+
79
+ def recursive_targets(platform: nil)
80
+ children = direct_dependency_targets(platform: platform)
81
+ children += children.flat_map { |t| t.recursive_targets(platform: platform) }
82
+ children.uniq
83
+ end
84
+
85
+ def direct_dependencies(platform: nil)
86
+ raw["dependencies"].flat_map do |hash|
87
+ dep_types = ["byName", "target", "product"]
88
+ if (dep_type = dep_types.intersection(hash.keys).first).nil?
89
+ raise GeneralError, "Unexpected dependency type. Must be one of #{dep_types}. Hash: #{hash}"
90
+ end
91
+ next [] unless match_platform?(hash[dep_type][-1], platform)
92
+ pkg_name = hash[dep_type][1] if dep_type == "product"
93
+ find_deps(hash[dep_type][0], pkg_name, dep_type)
94
+ end
95
+ end
96
+
97
+ def direct_dependency_targets(platform: nil)
98
+ direct_dependencies(platform: platform).flat_map(&:flatten_as_targets).uniq
99
+ end
100
+
101
+ def match_platform?(_condition, _platform)
102
+ true # FIXME: Handle this
103
+ end
104
+
105
+ def binary?
106
+ type == :binary
107
+ end
108
+
109
+ def binary_path
110
+ sources_path if binary?
111
+ end
112
+
113
+ def local_binary_path
114
+ binary_path if binary? && root.local?
115
+ end
116
+
117
+ private
118
+
119
+ def find_deps(name, pkg_name, dep_type)
120
+ # If `dep_type` is `target` -> constrained within current pkg only
121
+ # If `dep_type` is `product` -> `pkg_name` must be present
122
+ # If `dep_type` is `byName` -> it's either from this pkg, or its children/dependencies
123
+ res = []
124
+ descs = pkg_name.nil? ? [root] + root.uniform_dependencies : [pkg_desc_of(pkg_name)]
125
+ descs.each do |desc|
126
+ by_target = -> { desc.targets.select { |t| t.name == name } }
127
+ by_product = -> { desc.products.select { |t| t.name == name } }
128
+ return by_target.call if dep_type == "target"
129
+ return by_product.call if dep_type == "product"
130
+ return res unless (res = by_target.call).empty?
131
+ return res unless (res = by_product.call).empty?
132
+ end
133
+ []
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1 @@
1
+ Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
@@ -0,0 +1,9 @@
1
+ module XCCache
2
+ module PkgMixin
3
+ include Config::Mixin
4
+
5
+ def umbrella_pkg
6
+ @umbrella_pkg ||= SPM::Package::Umbrella.new(root_dir: config.spm_umbrella_sandbox)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,103 @@
1
+ require "json"
2
+ require "xccache/framework/slice"
3
+ require "xccache/framework/xcframework"
4
+ require "xccache/swift/sdk"
5
+
6
+ module XCCache
7
+ module SPM
8
+ class Package
9
+ attr_reader :root_dir
10
+
11
+ def initialize(options = {})
12
+ @root_dir = Pathname(options[:root_dir] || ".").expand_path
13
+ end
14
+
15
+ def build(options = {})
16
+ validate!
17
+ targets = options.delete(:targets) || []
18
+ raise GeneralError, "No targets were specified" if targets.empty?
19
+
20
+ targets.map { |t| t.split("/")[-1] }.each do |t|
21
+ UI.section("\n▶ Building target: #{t}".bold.magenta) do
22
+ build_target(**options, target: t)
23
+ rescue StandardError => e
24
+ UI.error("Failed to build target: #{t}. Error: #{e}")
25
+ raise e unless config.ignore_build_errors?
26
+ end
27
+ end
28
+ end
29
+
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])
32
+ if target_pkg_desc.binary_targets.any? { |t| t.name == target }
33
+ return UI.warn("Target #{target} is a binary target -> no need to build")
34
+ end
35
+
36
+ sdks = (sdk || "iphonesimulator").split(",")
37
+
38
+ 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
50
+ end
51
+
52
+ def resolve(force: false)
53
+ return if @resolved && !force
54
+
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
58
+ @resolved = true
59
+ end
60
+
61
+ private
62
+
63
+ def validate!
64
+ return unless root_dir.glob("Package*.swift").empty?
65
+ raise GeneralError, "No Package.swift in #{root_dir}. Are you sure you're running on a package dir?"
66
+ end
67
+
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)
73
+
74
+ UI.message(
75
+ "#{name.yellow.dark} is not a direct target of package #{root_dir.basename.to_s.dark} " \
76
+ "-> trigger from dependencies"
77
+ )
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
+ 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]
90
+ end
91
+
92
+ def pkg_desc
93
+ @pkg_desc ||= Description.in_dir(root_dir)
94
+ 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
+ end
102
+ end
103
+ end
@@ -0,0 +1,30 @@
1
+ module XCCache
2
+ module SPM
3
+ class Package
4
+ module UmbrellaBuilldMixin
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
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