Skip to content

Commit 3c71a04

Browse files
FIX: Unknown product dependencies due to missing pkg (#82)
1 parent 64d5ab7 commit 3c71a04

File tree

11 files changed

+185
-124
lines changed

11 files changed

+185
-124
lines changed

docs/res/unknown_deps_err.png

150 KB
Loading

docs/troubleshooting.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,53 @@
11
[< Knowledge Base](README.md)
22

33
# 🩺 Troubleshooting
4-
TBU
4+
5+
### Unknown Product Dependencies
6+
7+
<img src="res/unknown_deps_err.png" width="600px">
8+
9+
If you encountered this issue, it's because there are some product dependencies that the tool failed to infer their packages. This case is likely to happen when having **local packages** that were not properly added to the project. Below is the instructions to resolve this issue (once only).
10+
11+
After seeing this error, you should see `__unknown__/<Product>` (ex. `__unknown__/DebugKit`) in the lockfile (xccache.lock).
12+
```json
13+
"EX.xcodeproj": {
14+
"packages": [
15+
],
16+
"dependencies": {
17+
"EX": [
18+
"__unknown__/DebugKit" // <-- HERE
19+
]
20+
}
21+
}
22+
```
23+
This mean, the tool cannot infer the package of `DebugKit`. So, what you need to do in this lockfile is:
24+
25+
(1) Specify the package of that unknown product.
26+
```json
27+
"EX.xcodeproj": {
28+
"packages": [
29+
{
30+
"path_from_root": "LocalPackages/core-utils" // <-- HERE
31+
}
32+
]
33+
}
34+
```
35+
36+
(2) Then, replace the `__unknown__` in the dependencies by the package slug (ex. changing `__unknown__/DebugKit` to `core-utils/DebugKit`).
37+
38+
```json
39+
"EX.xcodeproj": {
40+
"packages": [
41+
{
42+
"path_from_root": "LocalPackages/core-utils" // <-- HERE
43+
}
44+
],
45+
"dependencies": {
46+
"EX": [
47+
"core-utils/DebugKit" // <-- HERE
48+
]
49+
}
50+
}
51+
```
52+
53+
(3) After that, run the xccache workflow again.

examples/xccache.lock

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,37 @@
22
"EX.xcodeproj": {
33
"packages": [
44
{
5-
"repositoryURL": "https://github.com/firebase/firebase-ios-sdk",
6-
"requirement": {
7-
"kind": "upToNextMajorVersion",
8-
"minimumVersion": "11.4.0"
9-
}
5+
"path_from_root": "LocalPackages/core-utils"
106
},
117
{
12-
"repositoryURL": "https://github.com/yeatse/KingfisherWebP",
8+
"path_from_root": "LocalPackages/wizard"
9+
},
10+
{
11+
"repositoryURL": "https://github.com/facebook/facebook-ios-sdk",
1312
"requirement": {
1413
"kind": "upToNextMajorVersion",
15-
"minimumVersion": "1.6.0"
14+
"minimumVersion": "9.0.0"
1615
}
1716
},
1817
{
19-
"repositoryURL": "https://github.com/SwiftyBeaver/SwiftyBeaver",
18+
"repositoryURL": "https://github.com/firebase/firebase-ios-sdk",
2019
"requirement": {
2120
"kind": "upToNextMajorVersion",
22-
"minimumVersion": "2.1.1"
21+
"minimumVersion": "11.4.0"
2322
}
2423
},
2524
{
26-
"repositoryURL": "https://github.com/Moya/Moya",
25+
"repositoryURL": "https://github.com/googlemaps/ios-maps-sdk",
2726
"requirement": {
2827
"kind": "upToNextMajorVersion",
29-
"minimumVersion": "15.0.3"
28+
"minimumVersion": "9.4.0"
3029
}
3130
},
3231
{
33-
"relative_path": "LocalPackages/core-utils",
34-
"path": null,
35-
"path_from_root": "LocalPackages/core-utils"
36-
},
37-
{
38-
"repositoryURL": "https://github.com/googlemaps/ios-maps-sdk",
32+
"repositoryURL": "https://github.com/Moya/Moya",
3933
"requirement": {
4034
"kind": "upToNextMajorVersion",
41-
"minimumVersion": "9.4.0"
35+
"minimumVersion": "15.0.3"
4236
}
4337
},
4438
{
@@ -56,32 +50,34 @@
5650
}
5751
},
5852
{
59-
"repositoryURL": "https://github.com/facebook/facebook-ios-sdk",
53+
"repositoryURL": "https://github.com/SwiftyBeaver/SwiftyBeaver",
6054
"requirement": {
6155
"kind": "upToNextMajorVersion",
62-
"minimumVersion": "9.0.0"
56+
"minimumVersion": "2.1.1"
6357
}
6458
},
6559
{
66-
"relative_path": "LocalPackages/wizard",
67-
"path": null,
68-
"path_from_root": "LocalPackages/wizard"
60+
"repositoryURL": "https://github.com/yeatse/KingfisherWebP",
61+
"requirement": {
62+
"kind": "upToNextMajorVersion",
63+
"minimumVersion": "1.6.0"
64+
}
6965
}
7066
],
7167
"dependencies": {
7268
"EX": [
73-
"SwiftyBeaver/SwiftyBeaver",
74-
"Moya/Moya",
75-
"ios-maps-sdk/GoogleMaps",
69+
"core-utils/DebugKit",
70+
"core-utils/DisplayKit",
71+
"core-utils/ResourceKit",
72+
"core-utils/Swizzler",
7673
"facebook-ios-sdk/FacebookLogin",
7774
"firebase-ios-sdk/FirebaseCrashlytics",
75+
"ios-maps-sdk/GoogleMaps",
76+
"KingfisherWebP/KingfisherWebP",
77+
"Moya/Moya",
7878
"SDWebImage/SDWebImage",
7979
"SnapKit/SnapKit-Dynamic",
80-
"KingfisherWebP/KingfisherWebP",
81-
"core-utils/DebugKit",
82-
"core-utils/ResourceKit",
83-
"core-utils/Swizzler",
84-
"core-utils/DisplayKit",
80+
"SwiftyBeaver/SwiftyBeaver",
8581
"wizard/Wizard"
8682
],
8783
"EXTests": [

lib/xccache/core/hash.rb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
class Hash
2-
def deep_merge(other, uniq_block: nil, &block)
3-
dup.deep_merge!(other, uniq_block: uniq_block, &block)
2+
def deep_merge(other, uniq_block: nil, sort_block: nil, &block)
3+
dup.deep_merge!(other, uniq_block: uniq_block, sort_block: sort_block, &block)
44
end
55

6-
def deep_merge!(other, uniq_block: nil, &block)
6+
def deep_merge!(other, uniq_block: nil, sort_block: nil, &block)
77
merge!(other) do |key, this_val, other_val|
88
result = if this_val.is_a?(Hash) && other_val.is_a?(Hash)
9-
this_val.deep_merge(other_val, uniq_block: uniq_block, &block)
9+
this_val.deep_merge(other_val, uniq_block: uniq_block, sort_block: sort_block, &block)
1010
elsif this_val.is_a?(Array) && other_val.is_a?(Array)
1111
this_val + other_val
1212
elsif block_given?
1313
block.call(key, this_val, other_val)
1414
else
1515
other_val
1616
end
17-
next result if uniq_block.nil? || !result.is_a?(Array)
18-
result.reverse.uniq(&uniq_block).reverse # prefer updates
17+
18+
# uniq by block, prefer updates
19+
result = result.reverse.uniq(&uniq_block).reverse if uniq_block && result.is_a?(Array)
20+
result = result.sort_by(&sort_block) if sort_block && result.is_a?(Array)
21+
result
1922
end
2023
end
2124
end

lib/xccache/core/lockfile.rb

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22

33
module XCCache
44
class Lockfile < JSONRepresentable
5+
class Pkg < Hash
6+
def self.from_h(h)
7+
Pkg.new.merge(h)
8+
end
9+
10+
def key
11+
@key ||= ["repositoryURL", "path_from_root", "relative_path"].find { |x| key?(x) }
12+
end
13+
14+
def id
15+
self[key]
16+
end
17+
18+
def local?
19+
key != "repositoryURL"
20+
end
21+
22+
def slug
23+
@slug ||= File.basename(id, ".*")
24+
end
25+
26+
def relative_path_from_dir(dir)
27+
return id if key == "relative_path"
28+
(Pathname.pwd / id).relative_path_from(dir) if key == "path_from_root"
29+
end
30+
end
31+
532
def hash_for_project(project)
633
raw[project.display_name] ||= {}
734
end
@@ -11,22 +38,31 @@ def product_dependencies_by_targets
1138
end
1239

1340
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)
41+
raw.deep_merge!(
42+
hash,
43+
uniq_block: proc { |h| h.is_a?(Hash) ? Pkg.from_h(h).id || h : h },
44+
sort_block: proc { |x| x.to_s.downcase },
45+
)
46+
# After deep_merge, clear property cache
47+
(instance_variables - %i[@path @raw]).each do |ivar|
48+
remove_instance_variable(ivar)
49+
end
1850
end
1951

2052
def pkgs
21-
@pkgs ||= raw.values.flat_map { |h| h["packages"] || [] }
53+
@pkgs ||= raw.values.flat_map { |h| h["packages"] || [] }.map { |h| Pkg.from_h(h) }
2254
end
2355

2456
def local_pkgs
25-
@local_pkgs ||= pkgs.select { |h| h.key?("relative_path") || h.key?("path") }
57+
@local_pkgs ||= pkgs.select(&:local?).uniq
2658
end
2759

2860
def local_pkg_slugs
29-
@local_pkg_slugs ||= local_pkgs.map { |h| File.basename(h["relative_path"] || h["path"]) }.uniq
61+
@local_pkg_slugs ||= local_pkgs.map(&:slug).uniq
62+
end
63+
64+
def known_product_dependencies
65+
raw.empty? ? [] : product_dependencies.reject { |d| File.dirname(d) == "__unknown__" }
3066
end
3167

3268
def product_dependencies
@@ -36,5 +72,20 @@ def product_dependencies
3672
def targets_data
3773
@targets_data ||= product_dependencies_by_targets.transform_keys { |k| "#{k}.xccache" }
3874
end
75+
76+
def verify!
77+
known_slugs = pkgs.map(&:slug)
78+
unknown = product_dependencies.reject { |d| known_slugs.include?(File.dirname(d)) }
79+
return if unknown.empty?
80+
81+
UI.error! <<~DESC
82+
Unknown product dependencies at #{path}:
83+
84+
#{unknown.sort.map { |d| " • #{d}" }.join("\n")}
85+
86+
Refer to this doc for how to resolve this issue:
87+
https://github.com/trinhngocthuyen/xccache/blob/main/docs/troubleshooting.md#unknown-product-dependencies
88+
DESC
89+
end
3990
end
4091
end

lib/xccache/installer.rb

Lines changed: 13 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@ def perform_install
3434

3535
def sync_lockfile
3636
UI.info("Syncing lockfile")
37+
known_dependencies = lockfile.known_product_dependencies
3738
update_projects do |project|
38-
lockfile.deep_merge!(project.display_name => lockfile_hash_for_project(project))
39+
lockfile.deep_merge!(
40+
project.display_name => lockfile_hash_for_project(project, known_dependencies)
41+
)
3942
end
4043
lockfile.save
44+
lockfile.verify!
4145
end
4246

4347
def lockfile
@@ -62,10 +66,15 @@ def update_projects
6266

6367
private
6468

65-
def lockfile_hash_for_project(project)
69+
def lockfile_hash_for_project(project, known_dependencies)
6670
deps_by_targets = project.targets.to_h do |target|
67-
deps = target.non_xccache_pkg_product_dependencies.select(&:pkg).map { |d| "#{d.pkg.slug}/#{d.product_name}" }
68-
[target.name, deps]
71+
deps = target.non_xccache_pkg_product_dependencies.map do |dep|
72+
next dep.full_name unless dep.pkg.nil?
73+
known = known_dependencies.find { |x| File.basename(x) == dep.product_name }
74+
UI.warn("-> Assuming #{known} for #{dep.full_name}".dark) if known
75+
known || dep.full_name
76+
end
77+
[target.name, deps.sort]
6978
end
7079
{
7180
"packages" => project.non_xccache_pkgs.map(&:to_h),
@@ -75,50 +84,6 @@ def lockfile_hash_for_project(project)
7584

7685
def verify_projects!
7786
raise "No projects detected. Are you running on the correct project directory?" if projects.empty?
78-
projects.each { |project| verify_product_dependencies!(project) }
79-
end
80-
81-
def verify_product_dependencies!(project)
82-
invalid = project.targets.flat_map(&:pkg_product_dependencies).reject(&:pkg)
83-
return if invalid.empty?
84-
85-
# Remove invalid product dependencies
86-
project.targets.each do |target|
87-
target.remove_pkg_product_dependencies { |d| d.pkg.nil? }
88-
end
89-
project.save
90-
91-
items_desc = invalid.map { |x| "• #{x.to_hash}" }.join("\n")
92-
pkgs_desc = project.pkgs.map { |x| "• #{x.display_name}" }.join("\n")
93-
UI.error! <<~DESC
94-
Invalid product dependency:
95-
96-
#{items_desc}
97-
98-
REASON:
99-
A product dependency must have a valid reference to its package.
100-
In this case, the package reference might be missing, or having a mismatched UDID.
101-
-----------------------------------------------------
102-
78A6451F74BE75DC0D90CDBD /* PRODUCT-X */ = {
103-
isa = XCSwiftPackageProductDependency;
104-
<---- 👈 HERE: Missing `package`
105-
productName = PRODUCT-X;
106-
};
107-
-----------------------------------------------------
108-
78A6451F74BE75DC0D90CDBD /* PRODUCT-Y */ = {
109-
isa = XCSwiftPackageProductDependency;
110-
package = <MISMATCHED_UDID> /* XCRemoteSwiftPackageReference "PACKAGE-Y" */; <---- 👈 HERE: Mismatched UDID
111-
productName = PRODUCT-Y;
112-
};
113-
-----------------------------------------------------
114-
🚩 Are you sure the packages were added to the project? Found packages:
115-
#{pkgs_desc}
116-
117-
ACTION:
118-
- Ensure packages were added to the project
119-
- The tool already removed those invalid products from the target.
120-
Please help RE-ADD THOSE PRODUCTS to the target. Then re-run the command again.
121-
DESC
12287
end
12388

12489
def add_xccache_refs_to_project(project)

lib/xccache/installer/use.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def replace_binaries_for_project(project)
1919
project.add_xccache_pkg unless project.has_xccache_pkg?
2020
project.targets.each do |target|
2121
target.add_xccache_product_dependency unless target.has_xccache_product_dependency?
22-
target.remove_pkg_product_dependencies { |d| !d.pkg.xccache_pkg? }
22+
target.remove_pkg_product_dependencies { |d| d.pkg.nil? || !d.pkg.xccache_pkg? }
2323
end
2424
project.remove_pkgs(&:non_xccache_pkg?) unless config.keep_pkgs_in_project?
2525
end

lib/xccache/xcodeproj/pkg.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ def ascii_plist_annotation
5050

5151
def to_h
5252
{
53-
"relative_path" => relative_path,
54-
"path" => path,
55-
"path_from_root" => absolute_path.relative_path_from(Pathname(".").expand_path).to_s,
53+
"path_from_root" => absolute_path.relative_path_from(Pathname.pwd).to_s,
5654
}
5755
end
5856

0 commit comments

Comments
 (0)