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,52 @@
1
+ module XcodeArchiveCache
2
+ module Injection
3
+ class PodsScriptFixer
4
+
5
+ include XcodeArchiveCache::Logs
6
+
7
+ def initialize
8
+ @build_settings_interpolator = XcodeArchiveCache::BuildSettings::StringInterpolator.new
9
+ end
10
+
11
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
12
+ # @param [XcodeArchiveCache::BuildGraph::Graph] graph
13
+ # @param [String] products_dir
14
+ #
15
+ def fix_embed_frameworks_script(target, graph, products_dir)
16
+ build_settings = graph.dependent_build_settings
17
+ file_path = find_embed_frameworks_script(target, build_settings)
18
+ return unless file_path
19
+
20
+ info("fixing #{file_path}")
21
+ script = File.read(file_path)
22
+ graph.nodes.each do |node|
23
+ relative_product_path = "#{node.native_target.display_name}/#{node.product_file_name}"
24
+ script = script.gsub("${BUILT_PRODUCTS_DIR}/#{relative_product_path}", File.join(products_dir, relative_product_path))
25
+ end
26
+
27
+ File.open(file_path, "w") {|file| file.puts(script)}
28
+ end
29
+
30
+ private
31
+
32
+ # @return [XcodeArchiveCache::BuildSettings::StringInterpolator]
33
+ #
34
+ attr_reader :build_settings_interpolator
35
+
36
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
37
+ # @param [XcodeArchiveCache::BuildSettings::Container] build_settings
38
+ #
39
+ # @return [String]
40
+ #
41
+ def find_embed_frameworks_script(target, build_settings)
42
+ target.shell_script_build_phases.each do |phase|
43
+ if phase.display_name == "[CP] Embed Pods Frameworks"
44
+ return build_settings_interpolator.interpolate(phase.shell_script, build_settings)
45
+ .gsub(/^"|"$/, "")
46
+ .strip
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,122 @@
1
+ module XcodeArchiveCache
2
+ module Injection
3
+ class Storage
4
+
5
+ # @return [String]
6
+ #
7
+ attr_reader :container_dir_path
8
+
9
+ # @return [Hash{XcodeArchiveCache::BuildGraph::Node => String}]
10
+ #
11
+ attr_reader :headers_storage_dir_paths
12
+
13
+ # @param [String] path
14
+ #
15
+ def initialize(path)
16
+ @container_dir_path = path
17
+ @headers_storage_dir_paths = Hash.new
18
+
19
+ prepare_container_dir
20
+ end
21
+
22
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
23
+ # @param [String] path
24
+ # @param [Array<String>] file_paths
25
+ #
26
+ def store_headers(node, path, file_paths)
27
+ storage_path = get_full_header_storage_path(path)
28
+
29
+ unless File.exist?(storage_path)
30
+ FileUtils.mkdir_p(storage_path)
31
+ end
32
+
33
+ file_paths.each do |file_path|
34
+ FileUtils.cp(file_path, File.join(storage_path, File.basename(file_path)))
35
+ end
36
+
37
+ save_header_storage_path(storage_path, node)
38
+ end
39
+
40
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
41
+ # @param [Array<String>] file_paths
42
+ #
43
+ def store_products(node, file_paths)
44
+ storage_path = prepare_storage(node)
45
+
46
+ file_paths.each do |path|
47
+ FileUtils.cp_r(path, storage_path)
48
+ end
49
+ end
50
+
51
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
52
+ #
53
+ def prepare_storage(node)
54
+ path = get_storage_path(node)
55
+ if File.exist?(path)
56
+ raise StandardError.new, "Injection storage path is already busy"
57
+ end
58
+
59
+ FileUtils.mkdir_p(path)
60
+ path
61
+ end
62
+
63
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
64
+ #
65
+ # @return [String]
66
+ #
67
+ def get_storage_path(node)
68
+ File.join(container_dir_path, node.name)
69
+ end
70
+
71
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
72
+ #
73
+ # @return [Array<String>]
74
+ #
75
+ def get_headers_storage_paths(node)
76
+ headers_storage_dir_paths[node.name]
77
+ end
78
+
79
+ def get_all_headers_storage_paths
80
+ headers_storage_dir_paths
81
+ .map {|_, path| path}
82
+ .flatten
83
+ .uniq
84
+ end
85
+
86
+ private
87
+
88
+ def prepare_container_dir
89
+ if File.exist?(container_dir_path)
90
+ FileUtils.rm_rf(container_dir_path)
91
+ end
92
+ end
93
+
94
+ # @param [String] path
95
+ #
96
+ # @return [String]
97
+ #
98
+ def get_full_header_storage_path(path)
99
+ File.absolute_path(path, container_dir_path)
100
+ end
101
+
102
+ # @param [String] path
103
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
104
+ #
105
+ def save_header_storage_path(path, node)
106
+ paths = get_headers_storage_paths(node) || []
107
+ containing_directory = File.dirname(path)
108
+ unless paths.include?(containing_directory)
109
+ paths.push(containing_directory)
110
+ set_all_headers_storage_paths(paths, node)
111
+ end
112
+ end
113
+
114
+ # @param [String] paths
115
+ # @param [XcodeArchiveCache::BuildGraph::Node] node
116
+ #
117
+ def set_all_headers_storage_paths(paths, node)
118
+ headers_storage_dir_paths[node.name] = paths
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,47 @@
1
+ module XcodeArchiveCache
2
+ module Logs
3
+
4
+ private
5
+
6
+ COMMON_LOGGER = Logger.new(STDOUT)
7
+ COMMON_LOGGER.formatter = proc do |_, _, _, msg|
8
+ "#{msg}\n"
9
+ end
10
+
11
+ ERROR_LOGGER = Logger.new(STDERR)
12
+ ERROR_LOGGER.formatter = COMMON_LOGGER.formatter
13
+
14
+ public
15
+
16
+ def set_log_level(level)
17
+ case level
18
+ when "info"
19
+ COMMON_LOGGER.level = Logger::Severity::INFO
20
+ when "verbose"
21
+ COMMON_LOGGER.level = Logger::Severity::DEBUG
22
+ when "nothing"
23
+ COMMON_LOGGER.level = Logger::Severity::ERROR
24
+ else
25
+ COMMON_LOGGER.level = Logger::Severity::ERROR
26
+ end
27
+ end
28
+
29
+ # @param [String] message
30
+ #
31
+ def debug(message)
32
+ COMMON_LOGGER.debug(message)
33
+ end
34
+
35
+ # @param [String] message
36
+ #
37
+ def info(message)
38
+ COMMON_LOGGER.info(message)
39
+ end
40
+
41
+ # @param [String] message
42
+ #
43
+ def error(message)
44
+ ERROR_LOGGER.fatal(message)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,139 @@
1
+ module XcodeArchiveCache
2
+ class Runner
3
+
4
+ include XcodeArchiveCache::Logs
5
+
6
+ # @param [XcodeArchiveCache::Config::Entry] config
7
+ #
8
+ def initialize(config)
9
+ @config = config
10
+
11
+ projects = list_projects
12
+ @native_target_finder = XcodeArchiveCache::BuildGraph::NativeTargetFinder.new(projects)
13
+
14
+ storage_path = File.absolute_path(config.storage.path)
15
+ @cache_storage = XcodeArchiveCache::ArtifactCache::LocalStorage.new(storage_path)
16
+ @rebuild_evaluator = XcodeArchiveCache::BuildGraph::RebuildEvaluator.new(@cache_storage)
17
+
18
+ @artifact_extractor = XcodeArchiveCache::ArtifactCache::ArtifactExtractor.new(@cache_storage)
19
+
20
+ derived_data_path = File.absolute_path(config.settings.derived_data_path)
21
+ @product_extractor = XcodeArchiveCache::Build::ProductExtractor.new(config.active_configuration.build_configuration, derived_data_path)
22
+
23
+ unpacked_artifacts_dir = File.absolute_path(File.join(derived_data_path, "cached"))
24
+ @injection_storage = XcodeArchiveCache::Injection::Storage.new(unpacked_artifacts_dir)
25
+ @injector = XcodeArchiveCache::Injection::Injector.new(config.active_configuration.build_configuration, @injection_storage)
26
+ end
27
+
28
+ def list_projects
29
+ file_path = File.absolute_path(config.file_path)
30
+
31
+ if config.is_a?(XcodeArchiveCache::Config::Project)
32
+ return [Xcodeproj::Project.new(file_path)]
33
+ elsif config.is_a?(XcodeArchiveCache::Config::Workspace)
34
+ workspace = Xcodeproj::Workspace.new_from_xcworkspace(file_path)
35
+ workspace_dir = File.expand_path("..", file_path)
36
+
37
+ return workspace.file_references.map {|file_reference| Xcodeproj::Project.open(file_reference.absolute_path(workspace_dir))}
38
+ end
39
+
40
+ raise Informative, "Configuration misses no entry point -- must have either a project or a workspace"
41
+ end
42
+
43
+ def run
44
+ perform_cleanup
45
+
46
+ config.targets.each do |target_config|
47
+ handle_target(target_config)
48
+ end
49
+ end
50
+
51
+ def perform_cleanup
52
+ if File.exist?(config.settings.derived_data_path)
53
+ FileUtils.rm_rf(config.settings.derived_data_path)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # @return [XcodeArchiveCache::Config::Entry]
60
+ #
61
+ attr_reader :config
62
+
63
+ # @param [XcodeArchiveCache::Config::Target] target_config
64
+ #
65
+ def handle_target(target_config)
66
+ target = @native_target_finder.find_for_product_name(target_config.name)
67
+ unless target
68
+ raise Informative, "Target not found for #{target_config.name}"
69
+ end
70
+
71
+ target_config.dependencies.each do |dependency_name|
72
+ handle_dependency(target, dependency_name)
73
+ end
74
+ end
75
+
76
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target
77
+ # @param [String] dependency_name
78
+ #
79
+ def handle_dependency(target, dependency_name)
80
+ info("checking #{dependency_name}")
81
+
82
+ dependency_target = @native_target_finder.find_for_product_name(dependency_name)
83
+ unless dependency_target
84
+ raise Informative, "Target not found for #{dependency_name} of #{target.display_name}"
85
+ end
86
+
87
+ xcodebuild_executor = XcodeArchiveCache::Xcodebuild::Executor.new(config.active_configuration.build_configuration,
88
+ dependency_target.platform_name,
89
+ config.settings.destination,
90
+ config.active_configuration.action,
91
+ config.active_configuration.xcodebuild_args)
92
+ graph_builder = XcodeArchiveCache::BuildGraph::Builder.new(@native_target_finder, xcodebuild_executor)
93
+ graph = graph_builder.build_graph(target, dependency_target)
94
+
95
+ evaluate_for_rebuild(graph)
96
+ extract_cached_artifacts(graph)
97
+ rebuild_if_needed(xcodebuild_executor, dependency_target, graph)
98
+ @injector.perform_outgoing_injection(graph, target)
99
+ end
100
+
101
+ # @param [XcodeArchiveCache::BuildGraph::Graph] graph
102
+ #
103
+ def evaluate_for_rebuild(graph)
104
+ graph.nodes.each do |node|
105
+ @rebuild_evaluator.evaluate(node)
106
+ end
107
+ end
108
+
109
+ # @param [XcodeArchiveCache::BuildGraph::Graph] graph
110
+ #
111
+ def extract_cached_artifacts(graph)
112
+ graph.nodes.each do |node|
113
+ next if node.rebuild
114
+
115
+ destination = @injection_storage.prepare_storage(node)
116
+ @artifact_extractor.unpack(node, destination)
117
+ end
118
+ end
119
+
120
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] root_target
121
+ # @param [XcodeArchiveCache::BuildGraph::Graph] graph
122
+ #
123
+ def rebuild_if_needed(xcodebuild_executor, root_target, graph)
124
+ rebuild_performer = XcodeArchiveCache::Build::Performer.new(xcodebuild_executor, config.settings.derived_data_path)
125
+ return unless rebuild_performer.should_rebuild?(graph)
126
+
127
+ @injector.perform_internal_injection(graph)
128
+ rebuild_performer.rebuild_missing(root_target, graph)
129
+
130
+ graph.nodes.each do |node|
131
+ next unless node.rebuild
132
+
133
+ file_paths = @product_extractor.list_product_contents(root_target.name, node)
134
+ @injection_storage.store_products(node, file_paths)
135
+ @cache_storage.store(node, @injection_storage.get_storage_path(node))
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,69 @@
1
+ module XcodeArchiveCache
2
+ module Shell
3
+ class Executor
4
+
5
+ # @param [String] command
6
+ # @param [Boolean] print_command
7
+ #
8
+ # @return [String]
9
+ #
10
+ def execute_for_output(command, print_command = false)
11
+ actual_command = extend_for_pipefail(command, print_command)
12
+ output, status = Open3.capture2e(actual_command)
13
+
14
+ if status.exitstatus != 0
15
+ raise Informative, "#{command}\nexecution failed\n#{output}"
16
+ end
17
+
18
+ output
19
+ end
20
+
21
+ # @param [String] command
22
+ # @param [Boolean] print_command
23
+ #
24
+ # @return [Boolean] true if command succeeded and returned 0, false otherwise
25
+ #
26
+ def execute(command, print_command = false)
27
+ actual_command = extend_for_pipefail(command, print_command)
28
+ result = system actual_command
29
+
30
+ return false if result == nil
31
+ result
32
+ end
33
+
34
+ private
35
+
36
+ # @param [String] command
37
+ # @param [Boolean] print_command
38
+ #
39
+ # @return [String]
40
+ #
41
+ def extend_for_pipefail(command, print_command)
42
+ return command unless contains_pipes?(command)
43
+
44
+ "set #{pipefail_flags(print_command: print_command)} && #{command}"
45
+ end
46
+
47
+ # @param [String] command
48
+ #
49
+ # @return [Boolean]
50
+ #
51
+ def contains_pipes?(command)
52
+ command.include?("|")
53
+ end
54
+
55
+ # @param [Boolean] print_command
56
+ #
57
+ # @return [String]
58
+ #
59
+ def pipefail_flags(print_command)
60
+ flags = %w(e o)
61
+ if print_command
62
+ flags.insert(1, "x")
63
+ end
64
+
65
+ "-" + flags.join("")
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,59 @@
1
+ require 'zip'
2
+ require 'pathname'
3
+ require 'fileutils'
4
+ require 'logger'
5
+ require 'tempfile'
6
+ require 'find'
7
+ require 'digest'
8
+ require 'xcodeproj'
9
+ require 'open3'
10
+ require 'claide'
11
+
12
+ require 'logs/logs'
13
+
14
+ require 'command/command'
15
+ require 'command/inject'
16
+
17
+ require 'config/dsl'
18
+ require 'config/config'
19
+
20
+ require 'build_graph/graph'
21
+ require 'build_graph/node'
22
+ require 'build_graph/builder'
23
+ require 'build_graph/native_target_finder'
24
+ require 'build_graph/sha_calculator'
25
+ require 'build_graph/rebuild_evaluator'
26
+
27
+ require 'artifact_cache/abstract_storage'
28
+ require 'artifact_cache/local_storage'
29
+ require 'artifact_cache/artifact_extractor'
30
+ require 'artifact_cache/archiver'
31
+
32
+ require 'build/performer'
33
+ require 'build/product_extractor'
34
+
35
+ require 'build_settings/filter'
36
+ require 'build_settings/loader'
37
+ require 'build_settings/extractor'
38
+ require 'build_settings/string_interpolator'
39
+ require 'build_settings/parser'
40
+
41
+ require 'injection/injector'
42
+ require 'injection/pods_script_fixer'
43
+ require 'injection/build_flags_changer'
44
+ require 'injection/dependency_remover'
45
+ require 'injection/headers_mover'
46
+ require 'injection/storage'
47
+ require 'injection/framework_embedder'
48
+
49
+ require 'runner/runner'
50
+
51
+ require 'shell/executor'
52
+
53
+ require 'xcodebuild/executor'
54
+
55
+ module XcodeArchiveCache
56
+ class Informative < StandardError
57
+ include CLAide::InformativeError
58
+ end
59
+ end