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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 49b93ea668f7de9a3723feec1518e934f0b46755afaf941866cf028ed0cc7f0b
4
+ data.tar.gz: 16c0538e9665fe7f7c95c747d34f8ae48a3d8c2c5ed7adb1cc865dbbcbe0c43f
5
+ SHA512:
6
+ metadata.gz: 07424e1f9f50852d8d3a2fb873454d6e441860d33d220a6b37aeb11bb5dd6e90a872bbd37f8c749bb3b0e6a0693fe9aab8fb6c344f7294127861f7ddd9321e8f
7
+ data.tar.gz: 670815a9f5e0bf971f5f0868e42bdeb0b2d0f12b745572688c0022914f57d6d0ba3016cddd54b5aca92203101df21c52b84d6c01de9c080bfa6eb3be1e9b0273
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if $PROGRAM_NAME == __FILE__
4
+ require 'pathname'
5
+ ROOT = Pathname.new(File.expand_path('../../', __FILE__))
6
+ $LOAD_PATH.unshift((ROOT + 'lib').to_s)
7
+ end
8
+
9
+ require 'xcode-archive-cache'
10
+
11
+ XcodeArchiveCache::Command.run(ARGV)
@@ -0,0 +1,18 @@
1
+ module XcodeArchiveCache
2
+ module ArtifactCache
3
+ class AbstractStorage
4
+
5
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
6
+ #
7
+ # @return [String] cached artifact path, nil if no cached artifact found
8
+ #
9
+ def cached_artifact_path(node)
10
+ nil
11
+ end
12
+
13
+ def store(node, path)
14
+
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,110 @@
1
+ module XcodeArchiveCache
2
+ module ArtifactCache
3
+ class Archiver
4
+
5
+ include XcodeArchiveCache::Logs
6
+
7
+ # @param [String] path
8
+ # @param [String] destination
9
+ #
10
+ def archive(path, destination)
11
+ if File.exists?(destination)
12
+ raise ArgumentError.new, "Artifact cache path #{destination} is already taken"
13
+ end
14
+
15
+ if File.file?(path)
16
+ archive_single_file(path, destination)
17
+ elsif File.directory?(path)
18
+ archive_directory(path, destination)
19
+ else
20
+ raise ArgumentError.new, "No artifact found at path #{path}"
21
+ end
22
+ end
23
+
24
+ # @param [String] path
25
+ # @param [String] destination
26
+ #
27
+ def unarchive(path, destination)
28
+ unless File.file?(path)
29
+ raise ArgumentError.new, "Artifact archive not found: #{path}"
30
+ end
31
+
32
+ unless File.directory?(destination)
33
+ FileUtils.mkdir_p(destination)
34
+ end
35
+
36
+ Zip::File.open(path) do |archive|
37
+ archive.each do |archive_entry|
38
+ destination_file_path = File.join(destination, archive_entry.name)
39
+ destination_dir_path = File.dirname(destination_file_path)
40
+ unless File.exists?(destination_dir_path) && File.directory?(destination_dir_path)
41
+ FileUtils.mkdir_p(destination_dir_path)
42
+ end
43
+
44
+ archive_entry.extract(destination_file_path)
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # @param [String] path
52
+ # @param [String] destination
53
+ #
54
+ def archive_single_file(path, destination)
55
+ info("archiving #{path}")
56
+
57
+ Zip::File.open(destination, Zip::File::CREATE) do |archive|
58
+ archive.add(File.basename(path), path)
59
+ end
60
+ end
61
+
62
+ # @param [String] path
63
+ # @param [String] destination
64
+ #
65
+ def archive_directory(path, destination)
66
+ info("archiving #{path}")
67
+
68
+ Zip::File.open(destination, Zip::File::CREATE) do |archive|
69
+ add_entries(list_entries_in_directory(path), path, archive)
70
+ end
71
+ end
72
+
73
+ # @param [Array<String>] entries
74
+ # @param [String] root_dir
75
+ # @param [Zip::File] archive
76
+ #
77
+ def add_entries(entries, root_dir, archive)
78
+ entries.each do |entry|
79
+ if File.directory?(entry)
80
+ add_entries(list_entries_in_directory(entry), root_dir, archive)
81
+ elsif File.file?(entry)
82
+ add_single_file_entry(entry, root_dir, archive)
83
+ else
84
+ raise ArgumentError.new, "No file found at path #{entry}"
85
+ end
86
+ end
87
+ end
88
+
89
+ # @param [String] path
90
+ #
91
+ def list_entries_in_directory(path)
92
+ (Dir.entries(path) - %w(. ..)).map {|entry| File.join(path, entry)}
93
+ end
94
+
95
+ # @param [String] path_on_disk
96
+ # @param [String] root_dir
97
+ # @param [Zip::File] archive
98
+ #
99
+ def add_single_file_entry(path_on_disk, root_dir, archive)
100
+ file_path = Pathname.new(path_on_disk)
101
+ root_dir_path = Pathname.new(root_dir)
102
+ path_in_archive = file_path.relative_path_from(root_dir_path).to_s
103
+
104
+ archive.get_output_stream(path_in_archive) do |stream|
105
+ stream.write(File.open(path_on_disk, 'rb').read)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,31 @@
1
+ module XcodeArchiveCache
2
+ module ArtifactCache
3
+ class ArtifactExtractor
4
+
5
+ # @param [XcodeArchiveCache::ArtifactCache::AbstractStorage] storage
6
+ #
7
+ def initialize(storage)
8
+ @storage = storage
9
+ @archiver = Archiver.new
10
+ end
11
+
12
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
13
+ # @param [String] destination
14
+ #
15
+ def unpack(node, destination)
16
+ cached_artifact_path = storage.cached_artifact_path(node)
17
+ archiver.unarchive(cached_artifact_path, destination)
18
+ end
19
+
20
+ private
21
+
22
+ # @return [XcodeArchiveCache::ArtifactCache::AbstractStorage]
23
+ #
24
+ attr_reader :storage
25
+
26
+ # @return [XcodeArchiveCache::ArtifactCache::Archiver]
27
+ #
28
+ attr_reader :archiver
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,46 @@
1
+ module XcodeArchiveCache
2
+ module ArtifactCache
3
+ class LocalStorage < AbstractStorage
4
+
5
+ # @param [String] cache_dir_path
6
+ #
7
+ def initialize(cache_dir_path)
8
+ @cache_dir_path = cache_dir_path
9
+ @archiver = Archiver.new
10
+ end
11
+
12
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
13
+ #
14
+ # @return [String] cached artifact path, nil if no artifact found in cache dir
15
+ #
16
+ def cached_artifact_path(node)
17
+ path = path_inside_cache_dir(node)
18
+ File.exist?(path) ? path : nil
19
+ end
20
+
21
+ def store(node, path)
22
+ archive_path = path_inside_cache_dir(node)
23
+ archive_directory = File.expand_path("..", archive_path)
24
+ unless File.exist?(archive_directory)
25
+ FileUtils.mkdir_p(archive_directory)
26
+ end
27
+
28
+ archiver.archive(path, archive_path)
29
+ end
30
+
31
+ private
32
+
33
+ # @return [String]
34
+ #
35
+ attr_reader :cache_dir_path
36
+
37
+ # @return [XcodeArchiveCache::ArtifactCache::Archiver]
38
+ #
39
+ attr_reader :archiver
40
+
41
+ def path_inside_cache_dir(node)
42
+ File.join(cache_dir_path, node.name, node.sha)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,48 @@
1
+ module XcodeArchiveCache
2
+ module Build
3
+ class Performer
4
+
5
+ include XcodeArchiveCache::Logs
6
+
7
+ # @param [String] derived_data_path
8
+ #
9
+ def initialize(xcodebuild_executor, derived_data_path)
10
+ @xcodebuild_executor = xcodebuild_executor
11
+ @derived_data_path = derived_data_path
12
+ end
13
+
14
+ # @param [String] configuration
15
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
16
+ # @param [XcodeArchiveCache::BuildGraph::Graph] graph
17
+ #
18
+ def rebuild_missing(target, graph)
19
+ should_rebuild_anything = should_rebuild?(graph)
20
+ if should_rebuild_anything
21
+ rebuild_list = graph.nodes.select(&:rebuild).map(&:name).join(", ")
22
+ info("going to rebuild:\n#{rebuild_list}")
23
+
24
+ build_result = xcodebuild_executor.build(target.project.path, target.name, derived_data_path)
25
+ unless build_result
26
+ raise StandardError.new, "Failed to perform rebuild"
27
+ end
28
+ else
29
+ info("no need to rebuild anything")
30
+ end
31
+ end
32
+
33
+ # @param [XcodeArchiveCache::BuildGraph::Graph] graph
34
+ #
35
+ def should_rebuild?(graph)
36
+ graph.nodes.reduce(false) {|rebuild, node| rebuild || node.rebuild}
37
+ end
38
+
39
+ private
40
+
41
+ # @return [String]
42
+ #
43
+ attr_reader :derived_data_path
44
+
45
+ attr_reader :xcodebuild_executor
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,129 @@
1
+ module XcodeArchiveCache
2
+ module Build
3
+ class ProductExtractor
4
+
5
+ # @param [String] configuration
6
+ # @param [String] derived_data_path
7
+ #
8
+ def initialize(configuration, derived_data_path)
9
+ @configuration = configuration
10
+ @derived_data_path = derived_data_path
11
+ @shell_executor = XcodeArchiveCache::Shell::Executor.new
12
+ end
13
+
14
+ def list_product_contents(root_target_name, built_node)
15
+ file_paths = list_products(root_target_name, built_node)
16
+ file_paths.select {|path| File.exist?(path)}.map {|path| File.realpath(path) }
17
+ end
18
+
19
+ private
20
+
21
+ # @return [String]
22
+ #
23
+ attr_reader :configuration
24
+
25
+ # @return [String]
26
+ #
27
+ attr_reader :derived_data_path
28
+
29
+ # @return [XcodeArchiveCache::Shell::Executor]
30
+ #
31
+ attr_reader :shell_executor
32
+
33
+ # @param [XcodeArchiveCache::BuildGraph::Node] built_node
34
+ #
35
+ def list_products(root_target_name, built_node)
36
+ if built_node.has_framework_product?
37
+ list_framework_products(root_target_name, built_node)
38
+ elsif built_node.has_static_library_product?
39
+ list_static_lib_products(root_target_name, built_node)
40
+ end
41
+ end
42
+
43
+ # @param [XcodeArchiveCache::BuildGraph::Node] built_node
44
+ #
45
+ # @return [Array<String>]
46
+ #
47
+ def list_framework_products(root_target_name, built_node)
48
+ framework_glob = get_main_product_glob(root_target_name, built_node)
49
+ framework_path = Dir.glob(framework_glob).first
50
+ unless framework_path
51
+ raise Informative, "Framework product not found for #{built_node.name}"
52
+ end
53
+
54
+ framework_dsym_glob = File.join(File.dirname(framework_glob), built_node.dsym_file_name)
55
+ framework_dsym_path = Dir.glob(framework_dsym_glob).first
56
+
57
+ bc_symbolmap_paths = list_framework_bc_symbolmaps(framework_path)
58
+
59
+ ([framework_path, framework_dsym_path] + bc_symbolmap_paths).flatten.compact
60
+ end
61
+
62
+ # @param [XcodeArchiveCache::BuildGraph::Node] built_node
63
+ #
64
+ # @return [Array<String>]
65
+ #
66
+ def list_static_lib_products(root_target_name, built_node)
67
+ static_lib_glob = get_main_product_glob(root_target_name, built_node)
68
+ static_lib_path = Dir.glob(static_lib_glob).first
69
+ unless static_lib_path
70
+ raise Informative, "Static library product not found for #{built_node.name}"
71
+ end
72
+
73
+ [static_lib_path]
74
+ end
75
+
76
+ # @param [XcodeArchiveCache::BuildGraph::Node] built_node
77
+ #
78
+ # @return [String]
79
+ #
80
+ def get_main_product_glob(root_target_name, built_node)
81
+ product_name = built_node.native_target.product_reference.name ?
82
+ built_node.native_target.product_reference.name :
83
+ built_node.native_target.product_reference.path
84
+ File.join(derived_data_path,
85
+ "**",
86
+ File.basename(product_name))
87
+ end
88
+
89
+ # @param [XcodeArchiveCache::BuildGraph::Node] built_node
90
+ #
91
+ # @return [String]
92
+ #
93
+ def configuration_dir(built_node)
94
+ "#{configuration}-#{built_node.native_target.sdk}"
95
+ end
96
+
97
+ # @param [String] framework_path
98
+ #
99
+ # @return [Array<String>]
100
+ #
101
+ def list_framework_bc_symbolmaps(framework_path)
102
+ executable_name = File.basename(framework_path, File.extname(framework_path))
103
+ executable_path = File.join(framework_path, executable_name)
104
+ unless File.exist?(executable_path)
105
+ raise Informative, "Failed to find executable inside framework: #{framework_path}"
106
+ end
107
+
108
+ uuids = list_bc_symbolmap_uuids(executable_path)
109
+ uuids.map {|uuid| find_bc_symbolmap(uuid)}.flatten
110
+ end
111
+
112
+ # @param [String] executable_path
113
+ #
114
+ # @return [Array<String>]
115
+ #
116
+ def list_bc_symbolmap_uuids(executable_path)
117
+ shell_executor.execute_for_output("otool -l #{executable_path} | grep uuid | awk {'print $2'}").split("\n")
118
+ end
119
+
120
+ # @param [String] uuid
121
+ #
122
+ # @return [String]
123
+ #
124
+ def find_bc_symbolmap(uuid)
125
+ Dir.glob(File.join(derived_data_path, "**", "#{uuid}.bcsymbolmap")).first
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,154 @@
1
+ module XcodeArchiveCache
2
+ module BuildGraph
3
+ class Builder
4
+
5
+ include XcodeArchiveCache::Logs
6
+
7
+ # @param [XcodeArchiveCache::Xcodebuild::Executor] xcodebuild_executor
8
+ #
9
+ def initialize(native_target_finder, xcodebuild_executor)
10
+ @build_settings_loader = XcodeArchiveCache::BuildSettings::Loader.new(xcodebuild_executor)
11
+ @native_target_finder = native_target_finder
12
+ @sha_calculator = NodeShaCalculator.new
13
+ end
14
+
15
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] dependency_target
16
+ #
17
+ # @return [Graph]
18
+ #
19
+ def build_graph(dependent_target, dependency_target)
20
+ native_target_finder.set_platform_name_filter(dependency_target.platform_name)
21
+
22
+ build_settings = load_setting_for_target(dependent_target)
23
+ graph = Graph.new(dependency_target.project, build_settings)
24
+
25
+ add_to_graph(dependency_target, graph, true)
26
+ load_settings(graph)
27
+ calculate_shas(graph)
28
+
29
+ graph
30
+ end
31
+
32
+ private
33
+
34
+ # @return [XcodeArchiveCache::BuildSettings::Loader]
35
+ #
36
+ attr_accessor :build_settings_loader
37
+
38
+ # @return [XcodeArchive::BuildGraph::NativeTargetFinder]
39
+ #
40
+ attr_accessor :native_target_finder
41
+
42
+ # @return [XcodeArchiveCache::BuildGraph::NodeShaCalculator]
43
+ #
44
+ attr_accessor :sha_calculator
45
+
46
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
47
+ # @param [Graph] graph
48
+ # @param [Boolean] is_root
49
+ # @param [Array<String>] target_stack
50
+ # Stack of native target display names at this level of traverse
51
+ #
52
+ # @return [Node] new or existing node
53
+ #
54
+ def add_to_graph(target, graph, is_root, target_stack = [])
55
+ debug("traversing #{target.display_name}")
56
+
57
+ unless target
58
+ raise ArgumentError.new, "Target is required"
59
+ end
60
+
61
+ display_name = target.display_name
62
+ existing_node = graph.node_by_name(display_name)
63
+ if existing_node
64
+ debug("already added this one")
65
+ return existing_node
66
+ end
67
+
68
+ if target_stack.include?(display_name)
69
+ target_stack.push(display_name)
70
+ raise Informative, "Circular dependency detected: #{target_stack.join(" -> ")}"
71
+ end
72
+
73
+ node = graph.node_by_name(display_name)
74
+ if node
75
+ debug("already traversed this one")
76
+ return node
77
+ else
78
+ debug("adding new node")
79
+ node = Node.new(display_name, target, is_root)
80
+ graph.nodes.push(node)
81
+ end
82
+
83
+ dependencies = []
84
+ target_stack.push(display_name)
85
+
86
+ dependency_targets = target.dependencies.map {|dependency| native_target_finder.find_for_dependency(dependency)} +
87
+ target.frameworks_build_phase.files.map {|file| native_target_finder.find_for_file(file)}
88
+
89
+ # PBXNativeTarget has no custom equality check
90
+ deduplicated_targets = dependency_targets.compact.uniq {|dependency_target| dependency_target.uuid + dependency_target.display_name}
91
+ debug("dependency targets: #{deduplicated_targets.map(&:display_name)}")
92
+
93
+ deduplicated_targets.each do |dependency_target|
94
+ dependency_node = add_to_graph(dependency_target, graph, false, target_stack)
95
+
96
+ unless dependency_node.dependent.include?(node)
97
+ debug("adding #{node.name} as dependent to #{dependency_node.name}")
98
+ dependency_node.dependent.push(node)
99
+ end
100
+
101
+ unless dependencies.include?(dependency_node)
102
+ debug("adding #{dependency_node.name} as dependency to #{node.name}")
103
+ dependencies.push(dependency_node)
104
+ end
105
+ end
106
+
107
+ target_stack.pop
108
+ node.dependencies.push(*dependencies)
109
+
110
+ debug("done with #{target.display_name}")
111
+ node
112
+ end
113
+
114
+ # @param [XcodeArchiveCache::BuildGraph::Graph] graph
115
+ #
116
+ def calculate_shas(graph)
117
+ graph.nodes.each do |node|
118
+ debug("calculating sha for #{node.name}")
119
+ sha_calculator.calculate(node)
120
+ debug("sha calculated for #{node.name}: #{node.sha}")
121
+ end
122
+ end
123
+
124
+ # @param [XcodeArchiveCache::BuildGraph::Graph] graph
125
+ #
126
+ def load_settings(graph)
127
+ counter = 1
128
+
129
+ graph.nodes.each do |node|
130
+ node_settings = load_setting_for_target(node.native_target)
131
+ unless node_settings
132
+ raise Informative, "No build settings loaded for #{node.name}"
133
+ end
134
+
135
+ node.build_settings = node_settings
136
+
137
+ debug("settings loaded for #{node.name} (#{counter} / #{graph.nodes.length})")
138
+ counter += 1
139
+ end
140
+ end
141
+
142
+
143
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
144
+ #
145
+ def load_setting_for_target(target)
146
+ info("loading settings for #{target.display_name}")
147
+
148
+ project_path = target.project.path
149
+ build_settings_loader.load_settings(project_path)
150
+ build_settings_loader.get_settings(project_path, target.display_name)
151
+ end
152
+ end
153
+ end
154
+ end