xcode-archive-cache 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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