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,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