xcode-archive-cache 0.0.6

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.
@@ -0,0 +1,146 @@
1
+ module XcodeArchiveCache
2
+ module Injection
3
+ class DependencyRemover
4
+
5
+ include XcodeArchiveCache::Logs
6
+
7
+ # @param [XcodeArchiveCache::BuildGraph::Node] prebuilt_node
8
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] dependent_target
9
+ #
10
+ def remove_dependency(prebuilt_node, dependent_target)
11
+ prebuilt_target = prebuilt_node.native_target
12
+ debug("removing #{prebuilt_target.name} from #{dependent_target.display_name}")
13
+
14
+ remove_from_dependencies(prebuilt_target, dependent_target)
15
+ remove_from_schemes(prebuilt_target, dependent_target)
16
+
17
+ debug("finished removing #{prebuilt_target.name} from #{dependent_target.display_name}")
18
+ end
19
+
20
+ private
21
+
22
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] prebuilt_target
23
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] dependent_target
24
+ #
25
+ def remove_from_dependencies(prebuilt_target, dependent_target)
26
+ count_before = dependent_target.dependencies.length
27
+
28
+ dependent_target.dependencies.delete_if do |dependency|
29
+ if dependency.target
30
+ dependency.target.uuid == prebuilt_target.uuid
31
+ elsif dependency.target_proxy
32
+ dependency.target_proxy.remote_global_id_string == prebuilt_target.uuid
33
+ end
34
+ end
35
+
36
+ count_after = dependent_target.dependencies.length
37
+ if count_after == count_before
38
+ debug("found nothing in dependencies")
39
+ else
40
+ debug("removed #{count_before - count_after} dependencies")
41
+ end
42
+ end
43
+
44
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] dependent_target
45
+ # @param [XcodeArchiveCache::BuildGraph::Node] prebuilt_node
46
+ #
47
+ def remove_from_linking(prebuilt_node, dependent_target)
48
+ debug("product name is #{prebuilt_node.product_file_name}")
49
+ frameworks = dependent_target.frameworks_build_phase.files.select {|file| file.display_name == prebuilt_node.product_file_name}
50
+ debug("found #{frameworks.length} linked products")
51
+
52
+ frameworks.each do |framework|
53
+ dependent_target.frameworks_build_phase.remove_file_reference(framework.file_ref)
54
+ end
55
+ end
56
+
57
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] prebuilt_target
58
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] dependent_target
59
+ #
60
+ def remove_from_schemes(prebuilt_target, dependent_target)
61
+ schemes = find_schemes(dependent_target)
62
+ schemes.each do |scheme|
63
+ debug("fixing scheme")
64
+ remove_target_from_scheme(prebuilt_target, scheme)
65
+ scheme.save!
66
+ debug("finished fixing scheme")
67
+ end
68
+ end
69
+
70
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
71
+ #
72
+ # @return [Array<Xcodeproj::XCScheme>]
73
+ #
74
+ def find_schemes(target)
75
+ scheme_names = Xcodeproj::Project.schemes(target.project.path)
76
+ scheme_names
77
+ .map {|scheme_name| find_scheme_by_name(target, scheme_name)}
78
+ .compact
79
+ .select {|scheme| scheme_contains_target?(scheme, target)}
80
+ end
81
+
82
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
83
+ # @param [Xcodeproj::XCScheme] scheme
84
+ #
85
+ def remove_target_from_scheme(target, scheme)
86
+ target_entries = scheme.build_action.entries.select {|entry| scheme_entry_contains_target?(entry, target)}
87
+ return if target_entries.length == 0
88
+
89
+ target_entries.each do |entry|
90
+ entry.buildable_references.each do |reference|
91
+ if reference_contains_target?(reference, target)
92
+ debug("removing buildable reference")
93
+ entry.xml_element.delete_element(reference.xml_element)
94
+ end
95
+ end
96
+
97
+ if entry.buildable_references.empty?
98
+ debug("removing entry")
99
+ scheme.build_action.xml_element.elements['BuildActionEntries'].delete_element(entry.xml_element)
100
+ end
101
+ end
102
+ end
103
+
104
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
105
+ #
106
+ # @param [Xcodeproj::XCScheme] scheme_name
107
+ #
108
+ def find_scheme_by_name(target, scheme_name)
109
+ scheme_path = File.join(Xcodeproj::XCScheme.shared_data_dir(target.project.path),
110
+ "#{scheme_name}.xcscheme")
111
+ if File.exist?(scheme_path)
112
+ Xcodeproj::XCScheme.new(scheme_path)
113
+ end
114
+ end
115
+
116
+ # @param [Xcodeproj::XCScheme] scheme
117
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
118
+ #
119
+ def scheme_contains_target?(scheme, target)
120
+ scheme.build_action.entries.each do |entry|
121
+ if scheme_entry_contains_target?(entry, target)
122
+ return true
123
+ end
124
+ end
125
+ end
126
+
127
+ # @param [Xcodeproj::XCScheme::BuildAction::Entry] entry
128
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
129
+ #
130
+ def scheme_entry_contains_target?(entry, target)
131
+ entry.buildable_references.each do |reference|
132
+ if reference_contains_target?(reference, target)
133
+ return true
134
+ end
135
+ end
136
+ end
137
+
138
+ # @param [Xcodeproj::XCScheme::BuildableReference] reference
139
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
140
+ #
141
+ def reference_contains_target?(reference, target)
142
+ reference.target_uuid == target.uuid
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,47 @@
1
+ module XcodeArchiveCache
2
+ module Injection
3
+ class FrameworkEmbedder
4
+
5
+ include XcodeArchiveCache::Logs
6
+
7
+ def initialize
8
+ @shell_executor = XcodeArchiveCache::Shell::Executor.new
9
+ end
10
+
11
+ # @param [Array<String>] framework_file_paths
12
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
13
+ #
14
+ def embed(framework_file_paths, target)
15
+ dynamic_framework_file_paths = framework_file_paths.select do |path|
16
+ binary_name = File.basename(path, ".framework")
17
+ binary_path = File.join(path, binary_name)
18
+ shell_executor.execute("file #{binary_path} | grep dynamic")
19
+ end
20
+
21
+ return if dynamic_framework_file_paths.length == 0
22
+
23
+ debug("Embedding frameworks:\n\t#{dynamic_framework_file_paths.join("\n\t")}")
24
+
25
+ frameworks_group = target.project.main_group.new_group("XcodeArchiveCache Frameworks")
26
+ file_references = dynamic_framework_file_paths.map {|file_path| frameworks_group.new_reference(file_path)}
27
+ embed_frameworks_phase = target.new_copy_files_build_phase("Embed XcodeArchiveCache Frameworks")
28
+ embed_frameworks_phase.symbol_dst_subfolder_spec = :frameworks
29
+ embed_frameworks_phase.run_only_for_deployment_postprocessing = false
30
+
31
+ file_references.each do |file_reference|
32
+ build_file = target.project.new(Xcodeproj::Project::Object::PBXBuildFile)
33
+ build_file.file_ref = file_reference
34
+ build_file.settings = {"ATTRIBUTES" => %w(CodeSignOnCopy RemoveHeadersOnCopy)}
35
+ build_file.add_referrer(frameworks_group)
36
+ embed_frameworks_phase.files.push(build_file)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # @return [XcodeArchiveCache::Shell::Executor]
43
+ #
44
+ attr_reader :shell_executor
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,65 @@
1
+ module XcodeArchiveCache
2
+ module Injection
3
+ class HeadersMover
4
+
5
+ include XcodeArchiveCache::Logs
6
+
7
+ # @param [Storage] storage
8
+ #
9
+ def initialize(storage)
10
+ @storage = storage
11
+ @build_settings_interpolator = XcodeArchiveCache::BuildSettings::StringInterpolator.new
12
+ end
13
+
14
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
15
+ #
16
+ def prepare_headers_for_injection(node)
17
+ debug("checking #{node.name} headers")
18
+ header_count = 0
19
+
20
+ node.native_target.copy_files_build_phases.each do |build_phase|
21
+ file_paths = build_phase.files
22
+ .map {|build_file| get_real_path(build_file)}
23
+ .compact
24
+ .uniq
25
+ .select {|path| File.extname(path) == ".h"}
26
+ destination_path = get_destination_dir_path(node, build_phase)
27
+ storage.store_headers(node, destination_path, file_paths)
28
+
29
+ header_count += file_paths.length
30
+ end
31
+
32
+ debug("found #{header_count} headers")
33
+ end
34
+
35
+ private
36
+
37
+ # @return [Storage]
38
+ #
39
+ attr_reader :storage
40
+
41
+ # @return [XcodeArchiveCache::BuildSettings::StringInterpolator]
42
+ #
43
+ attr_reader :build_settings_interpolator
44
+
45
+ # @param [Xcodeproj::Project::Object::PBXBuildFile] build_file
46
+ #
47
+ # @return [String]
48
+ #
49
+ def get_real_path(build_file)
50
+ if build_file.file_ref.is_a?(Xcodeproj::Project::Object::PBXFileReference)
51
+ build_file.file_ref.real_path.to_s
52
+ end
53
+ end
54
+
55
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
56
+ # @param [Xcodeproj::Project::Object::PBXCopyFilesBuildPhase] build_phase
57
+ #
58
+ # @return [String]
59
+ #
60
+ def get_destination_dir_path(node, build_phase)
61
+ build_settings_interpolator.interpolate(build_phase.dst_path, node.build_settings)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,220 @@
1
+ module XcodeArchiveCache
2
+ module Injection
3
+ class Injector
4
+
5
+ include XcodeArchiveCache::Logs
6
+
7
+ # @param [String] configuration_name
8
+ # @param [XcodeArchiveCache::Injection::Storage] storage
9
+ #
10
+ def initialize(configuration_name, storage)
11
+ @configuration_name = configuration_name
12
+ @storage = storage
13
+ @headers_mover = HeadersMover.new(storage)
14
+ @dependency_remover = DependencyRemover.new
15
+ @build_flags_changer = BuildFlagsChanger.new
16
+ @pods_fixer = PodsScriptFixer.new
17
+ @framework_embedder = FrameworkEmbedder.new
18
+ end
19
+
20
+ # @param [XcodeArchiveCache::BuildGraph::Graph] graph
21
+ #
22
+ def perform_internal_injection(graph)
23
+ inject_cached(graph.nodes)
24
+ add_header_paths(graph.nodes)
25
+ save_graph_projects(graph)
26
+ end
27
+
28
+ # @param [XcodeArchiveCache::BuildGraph::Graph] graph
29
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
30
+ #
31
+ def perform_outgoing_injection(graph, target)
32
+ graph.nodes.each do |node|
33
+ headers_mover.prepare_headers_for_injection(node)
34
+ add_as_prebuilt_dependency(node, target, node.is_root)
35
+ remove_native_target_from_project(node)
36
+ end
37
+
38
+ add_header_paths_to_target(target, storage.get_all_headers_storage_paths)
39
+
40
+ # pretty dummy but should work in most cases;
41
+ # here we assume that if graph has a pods target
42
+ # then all graph nodes are built as pods and therefore
43
+ # are covered by "Embed Pods Frameworks" script
44
+ #
45
+ if graph.node_by_name(get_pods_target_name(target))
46
+ pods_fixer.fix_embed_frameworks_script(target, graph, storage.container_dir_path)
47
+ else
48
+ framework_nodes = graph.nodes.select {|node| node.has_framework_product?}
49
+ framework_file_paths = framework_nodes.map {|node| File.join(storage.get_storage_path(node), node.product_file_name)}
50
+ framework_embedder.embed(framework_file_paths, target)
51
+ end
52
+
53
+ save_graph_projects(graph)
54
+ target.project.save
55
+ end
56
+
57
+ private
58
+
59
+ # @return [String]
60
+ #
61
+ attr_reader :configuration_name
62
+
63
+ # @return [Storage]
64
+ #
65
+ attr_reader :storage
66
+
67
+ # @return [HeadersMover]
68
+ #
69
+ attr_reader :headers_mover
70
+
71
+ # @return [DependencyRemover]
72
+ #
73
+ attr_reader :dependency_remover
74
+
75
+ # @return [BuildFlagsChanger]
76
+ #
77
+ attr_reader :build_flags_changer
78
+
79
+ # @return [PodsScriptFixer]
80
+ #
81
+ attr_reader :pods_fixer
82
+
83
+ # @return [FrameworkEmbedder]
84
+ #
85
+ attr_reader :framework_embedder
86
+
87
+ # @param [Array<XcodeArchiveCache::BuildGraph::Node>] nodes
88
+ #
89
+ def inject_cached(nodes)
90
+ cached_nodes = nodes.select {|node| !node.rebuild}
91
+ cached_nodes.each do |node|
92
+ headers_mover.prepare_headers_for_injection(node)
93
+ add_as_prebuilt_to_dependents(node)
94
+ end
95
+ end
96
+
97
+ # @param [Array<XcodeArchiveCache::BuildGraph::Node>] nodes
98
+ #
99
+ def add_header_paths(nodes)
100
+ header_storage_paths = storage.get_all_headers_storage_paths
101
+
102
+ nodes
103
+ .select {|node| node.rebuild}
104
+ .each { |node| add_header_paths_to_target(node.native_target, header_storage_paths) }
105
+ end
106
+
107
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
108
+ # @param [Array<String>] paths
109
+ #
110
+ def add_header_paths_to_target(target, paths)
111
+ debug("adding #{paths} to #{target.display_name}")
112
+
113
+ return if paths == nil
114
+
115
+ build_configuration = find_build_configuration(target)
116
+ paths.each do |path|
117
+ build_flags_changer.add_headers_search_path(build_configuration, path)
118
+ build_flags_changer.add_iquote_path(build_configuration, path)
119
+ build_flags_changer.add_capital_i_path(build_configuration, path)
120
+ end
121
+ end
122
+
123
+ # @param [XcodeArchiveCache::BuildGraph::Node] prebuilt_node
124
+ #
125
+ def add_as_prebuilt_to_dependents(prebuilt_node)
126
+ dependent_to_rebuild = prebuilt_node
127
+ .all_dependent_nodes
128
+ .select {|node| node.rebuild}
129
+ dependent_to_rebuild.each do |dependent_node|
130
+ should_link = prebuilt_node.dependent.include?(dependent_node)
131
+ add_as_prebuilt_dependency(prebuilt_node, dependent_node.native_target, should_link)
132
+ end
133
+
134
+ remove_native_target_from_project(prebuilt_node)
135
+ end
136
+
137
+ # @param [XcodeArchiveCache::BuildGraph::Graph] graph
138
+ #
139
+ def save_graph_projects(graph)
140
+ projects = graph.nodes.map(&:native_target).map(&:project).uniq
141
+ debug("updating #{projects.length} projects")
142
+ projects.each {|project| project.save}
143
+ end
144
+
145
+ # @param [XcodeArchiveCache::BuildGraph::Node] prebuilt_node
146
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] dependent_target
147
+ #
148
+ def add_as_prebuilt_dependency(prebuilt_node, dependent_target, should_link)
149
+ debug("adding #{prebuilt_node.name} as prebuilt to #{dependent_target.display_name}")
150
+
151
+ if prebuilt_node.has_framework_product?
152
+ add_as_prebuilt_framework(prebuilt_node, dependent_target)
153
+ elsif prebuilt_node.has_static_library_product?
154
+ add_as_prebuilt_static_lib(prebuilt_node, dependent_target, should_link)
155
+ else
156
+ raise ArgumentError.new, "#{prebuilt_node.name} has unsupported product type: #{prebuilt_node.native_target.product_type}"
157
+ end
158
+
159
+ debug("done with #{prebuilt_node.name} for #{dependent_target.display_name}")
160
+ end
161
+
162
+ # @param [XcodeArchiveCache::BuildGraph::Node] prebuilt_node
163
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] dependent_target
164
+ #
165
+ def add_as_prebuilt_framework(prebuilt_node, dependent_target)
166
+ build_configuration = find_build_configuration(dependent_target)
167
+
168
+ artifact_location = storage.get_storage_path(prebuilt_node)
169
+ build_flags_changer.add_framework_search_path(build_configuration, artifact_location)
170
+ build_flags_changer.add_framework_headers_iquote(build_configuration, artifact_location, prebuilt_node)
171
+
172
+ dependency_remover.remove_dependency(prebuilt_node, dependent_target)
173
+ end
174
+
175
+ # @param [XcodeArchiveCache::BuildGraph::Node] prebuilt_node
176
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] dependent_target
177
+ # @param [Boolean] should_link
178
+ #
179
+ def add_as_prebuilt_static_lib(prebuilt_node, dependent_target, should_link)
180
+ build_configuration = find_build_configuration(dependent_target)
181
+
182
+ if should_link
183
+ artifact_location = storage.get_storage_path(prebuilt_node)
184
+ build_flags_changer.add_library_search_path(build_configuration, artifact_location)
185
+ end
186
+
187
+ dependency_remover.remove_dependency(prebuilt_node, dependent_target)
188
+ end
189
+
190
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
191
+ #
192
+ def find_build_configuration(target)
193
+ build_configuration = target.build_configurations.select {|configuration| configuration.name == configuration_name}.first
194
+ unless build_configuration
195
+ raise ArgumentError.new, "#{configuration_name} build configuration not found on target #{node.name}"
196
+ end
197
+
198
+ build_configuration
199
+ end
200
+
201
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
202
+ #
203
+ def get_pods_target_name(target)
204
+ "Pods-#{target.display_name}"
205
+ end
206
+
207
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
208
+ #
209
+ # since 10.2 Xcode looks for implicit dependencies
210
+ # in -l and -framework linker flags, so we need to delete
211
+ # dependency target to make sure Xcode has no way to build it
212
+ # as implicit dependency
213
+ #
214
+ def remove_native_target_from_project(node)
215
+ debug("deleting #{node.name} target")
216
+ node.native_target.project.targets.delete(node.native_target)
217
+ end
218
+ end
219
+ end
220
+ end