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