xcode-archive-cache 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/xcode-archive-cache +11 -0
- data/lib/artifact_cache/abstract_storage.rb +18 -0
- data/lib/artifact_cache/archiver.rb +110 -0
- data/lib/artifact_cache/artifact_extractor.rb +31 -0
- data/lib/artifact_cache/local_storage.rb +46 -0
- data/lib/build/performer.rb +48 -0
- data/lib/build/product_extractor.rb +129 -0
- data/lib/build_graph/builder.rb +154 -0
- data/lib/build_graph/graph.rb +37 -0
- data/lib/build_graph/native_target_finder.rb +101 -0
- data/lib/build_graph/node.rb +104 -0
- data/lib/build_graph/rebuild_evaluator.rb +30 -0
- data/lib/build_graph/sha_calculator.rb +102 -0
- data/lib/build_settings/extractor.rb +80 -0
- data/lib/build_settings/filter.rb +289 -0
- data/lib/build_settings/loader.rb +80 -0
- data/lib/build_settings/parser.rb +77 -0
- data/lib/build_settings/string_interpolator.rb +36 -0
- data/lib/command/command.rb +45 -0
- data/lib/command/inject.rb +52 -0
- data/lib/config/config.rb +211 -0
- data/lib/config/dsl.rb +69 -0
- data/lib/injection/build_flags_changer.rb +172 -0
- data/lib/injection/dependency_remover.rb +146 -0
- data/lib/injection/framework_embedder.rb +47 -0
- data/lib/injection/headers_mover.rb +65 -0
- data/lib/injection/injector.rb +220 -0
- data/lib/injection/pods_script_fixer.rb +52 -0
- data/lib/injection/storage.rb +122 -0
- data/lib/logs/logs.rb +47 -0
- data/lib/runner/runner.rb +139 -0
- data/lib/shell/executor.rb +69 -0
- data/lib/xcode-archive-cache.rb +59 -0
- data/lib/xcodebuild/executor.rb +146 -0
- metadata +133 -0
@@ -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
|
data/lib/logs/logs.rb
ADDED
@@ -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
|