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,37 @@
|
|
1
|
+
module XcodeArchiveCache
|
2
|
+
module BuildGraph
|
3
|
+
class Graph
|
4
|
+
# @return [Array<Node>] graph nodes
|
5
|
+
#
|
6
|
+
attr_reader :nodes
|
7
|
+
|
8
|
+
# @return [Xcodeproj::Project] project
|
9
|
+
#
|
10
|
+
attr_reader :project
|
11
|
+
|
12
|
+
# @return [XcodeArchiveCache::BuildSettings::Container] root target build settings
|
13
|
+
#
|
14
|
+
attr_reader :dependent_build_settings
|
15
|
+
|
16
|
+
# @param [Xcodeproj::Project] project
|
17
|
+
# @param [XcodeArchiveCache::BuildSettings::Container] dependent_build_settings
|
18
|
+
#
|
19
|
+
def initialize(project, dependent_build_settings)
|
20
|
+
@nodes = []
|
21
|
+
@project = project
|
22
|
+
@dependent_build_settings = dependent_build_settings
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [String] name
|
26
|
+
# Native target display name
|
27
|
+
#
|
28
|
+
def node_by_name(name)
|
29
|
+
nodes.select {|node| node.name == name}.first
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
nodes.map(&:to_s).join("\n")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module XcodeArchiveCache
|
2
|
+
module BuildGraph
|
3
|
+
class NativeTargetFinder
|
4
|
+
|
5
|
+
# @param [Array<Xcodeproj::Project>] projects
|
6
|
+
#
|
7
|
+
def initialize(projects)
|
8
|
+
@all_targets = projects
|
9
|
+
.map {|project| unnest(project)}
|
10
|
+
.flatten
|
11
|
+
.uniq
|
12
|
+
.map(&:native_targets)
|
13
|
+
.flatten
|
14
|
+
.select {|target| !target.test_target_type? }
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [String] platform_name
|
18
|
+
#
|
19
|
+
def set_platform_name_filter(platform_name)
|
20
|
+
@platform_name = platform_name
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [Xcodeproj::Project::Object::PBXTargetDependency] dependency
|
24
|
+
#
|
25
|
+
# @return [Xcodeproj::Project::Object::PBXNativeTarget]
|
26
|
+
#
|
27
|
+
def find_for_dependency(dependency)
|
28
|
+
# targets from embedded projects are proxied
|
29
|
+
target = dependency.target ? dependency.target : dependency.target_proxy.proxied_object
|
30
|
+
target.is_a?(Xcodeproj::Project::Object::PBXNativeTarget) ? target : nil
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [Xcodeproj::Project::Object::PBXBuildFile] file
|
34
|
+
#
|
35
|
+
# @return [Xcodeproj::Project::Object::PBXNativeTarget]
|
36
|
+
#
|
37
|
+
def find_for_file(file)
|
38
|
+
if file.file_ref.is_a?(Xcodeproj::Project::Object::PBXReferenceProxy)
|
39
|
+
project = file.file_ref.remote_ref.container_portal_object
|
40
|
+
product_reference_uuid = file.file_ref.remote_ref.remote_global_id_string
|
41
|
+
find_with_product_ref_uuid(project, product_reference_uuid)
|
42
|
+
elsif file.file_ref.is_a?(Xcodeproj::Project::Object::PBXFileReference)
|
43
|
+
# products of sibling project targets are added as PBXFileReferences
|
44
|
+
targets = find_with_product_path(file.file_ref.path)
|
45
|
+
if targets.length > 1
|
46
|
+
raise Informative, "Found more than one target with product #{File.basename(file.file_ref.path)} in:\n#{targets.map(&:project)}"
|
47
|
+
end
|
48
|
+
|
49
|
+
targets.first
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param [String] product_name
|
54
|
+
#
|
55
|
+
def find_for_product_name(product_name)
|
56
|
+
all_targets.select {|native_target| native_target.name == product_name || native_target.product_reference.display_name == product_name}
|
57
|
+
.first
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# @return [Array<Xcodeproj::Project::Object::PBXNativeTarget>]
|
63
|
+
#
|
64
|
+
attr_accessor :all_targets
|
65
|
+
|
66
|
+
# @return [String]
|
67
|
+
#
|
68
|
+
attr_accessor :platform_name
|
69
|
+
|
70
|
+
# @param [Xcodeproj::Project] project
|
71
|
+
#
|
72
|
+
# @return [Array<Xcodeproj::Project>]
|
73
|
+
#
|
74
|
+
# Project + subprojects at all levels of nesting
|
75
|
+
#
|
76
|
+
def unnest(project)
|
77
|
+
nested_projects = project.files
|
78
|
+
.select {|file_ref| File.extname(file_ref.path) == ".xcodeproj" && File.exist?(file_ref.real_path)}
|
79
|
+
.map {|file_ref| Xcodeproj::Project.open(file_ref.real_path)}
|
80
|
+
subnested_projects = nested_projects.map {|nested_project| unnest(nested_project)}.flatten
|
81
|
+
[project] + nested_projects + subnested_projects
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param [String] uuid
|
85
|
+
#
|
86
|
+
# @return [Xcodeproj::Project::Object::PBXNativeTarget]
|
87
|
+
#
|
88
|
+
def find_with_product_ref_uuid(project, uuid)
|
89
|
+
project.native_targets.select {|target| target.product_reference.uuid == uuid}.first
|
90
|
+
end
|
91
|
+
|
92
|
+
# @param [String] path
|
93
|
+
#
|
94
|
+
# @return [Array<Xcodeproj::Project::Object::PBXNativeTarget>]
|
95
|
+
#
|
96
|
+
def find_with_product_path(path)
|
97
|
+
all_targets.select {|target| target.platform_name == platform_name && target.product_reference.path == path}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module XcodeArchiveCache
|
2
|
+
module BuildGraph
|
3
|
+
class Node
|
4
|
+
|
5
|
+
# @return [String] native target display name
|
6
|
+
#
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
# @return [Boolean]
|
10
|
+
#
|
11
|
+
attr_reader :is_root
|
12
|
+
|
13
|
+
# @return [Bool] should target be rebuilt
|
14
|
+
#
|
15
|
+
attr_accessor :rebuild
|
16
|
+
|
17
|
+
# @return [String] sha256 of (input files + build settings + dependency shas)
|
18
|
+
#
|
19
|
+
attr_accessor :sha
|
20
|
+
|
21
|
+
# @return [Array<XcodeArchiveCache::BuildGraph::Node>] dependent nodes
|
22
|
+
#
|
23
|
+
attr_reader :dependent
|
24
|
+
|
25
|
+
# @return [Array<XcodeArchiveCache::BuildGraph::Node>] dependency nodes
|
26
|
+
#
|
27
|
+
attr_reader :dependencies
|
28
|
+
|
29
|
+
# @return [Xcodeproj::Project::Object::PBXNativeTarget] corresponding native target
|
30
|
+
#
|
31
|
+
attr_reader :native_target
|
32
|
+
|
33
|
+
# @return [XcodeArchiveCache::BuildSettings::Container]
|
34
|
+
#
|
35
|
+
attr_accessor :build_settings
|
36
|
+
|
37
|
+
# @param [String] name
|
38
|
+
# @param [Xcodeproj::Project::Object::PBXNativeTarget] native_target
|
39
|
+
# @param [Boolean] is_root
|
40
|
+
#
|
41
|
+
def initialize(name, native_target, is_root = false)
|
42
|
+
@name = name
|
43
|
+
@native_target = native_target
|
44
|
+
@is_root = is_root
|
45
|
+
@dependent = []
|
46
|
+
@dependencies = []
|
47
|
+
end
|
48
|
+
|
49
|
+
def has_framework_product?
|
50
|
+
native_target.product_type == Xcodeproj::Constants::PRODUCT_TYPE_UTI[:framework]
|
51
|
+
end
|
52
|
+
|
53
|
+
def has_static_library_product?
|
54
|
+
native_target.product_type == Xcodeproj::Constants::PRODUCT_TYPE_UTI[:static_library]
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [String]
|
58
|
+
#
|
59
|
+
def product_file_name
|
60
|
+
return nil unless build_settings
|
61
|
+
|
62
|
+
product_name = build_settings[XcodeArchiveCache::BuildSettings::FULL_PRODUCT_NAME_KEY]
|
63
|
+
return product_name if product_name
|
64
|
+
|
65
|
+
product_name = native_target.product_reference.name
|
66
|
+
if has_framework_product? && product_name
|
67
|
+
product_file_name = product_name
|
68
|
+
end
|
69
|
+
|
70
|
+
unless product_file_name
|
71
|
+
product_file_name = File.basename(native_target.product_reference.real_path)
|
72
|
+
end
|
73
|
+
|
74
|
+
product_file_name
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [String]
|
78
|
+
#
|
79
|
+
def dsym_file_name
|
80
|
+
return nil unless build_settings
|
81
|
+
|
82
|
+
build_settings[XcodeArchiveCache::BuildSettings::DWARF_DSYM_FILE_NAME_KEY]
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [Array<Node>]
|
86
|
+
# Direct + transitive dependents
|
87
|
+
#
|
88
|
+
def all_dependent_nodes
|
89
|
+
(dependent + dependent.map(&:all_dependent_nodes)).flatten.uniq
|
90
|
+
end
|
91
|
+
|
92
|
+
def ==(other_node)
|
93
|
+
other_node && native_target.uuid == other_node.native_target.uuid && native_target.project == other_node.native_target.project
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
sha_string = sha ? sha : "<none>"
|
98
|
+
dependent_names = dependent.length > 0 ? dependent.map(&:name).join(", ") : "<none>"
|
99
|
+
dependency_names = dependencies.length > 0 ? dependencies.map(&:name).join(", ") : "<none>"
|
100
|
+
"#{name}\n\troot: #{is_root}\n\tproduct: #{product_file_name}\n\tsha: #{sha_string}\n\trebuild: #{rebuild}\n\tdependent: #{dependent_names}\n\tdependencies: #{dependency_names}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module XcodeArchiveCache
|
2
|
+
module BuildGraph
|
3
|
+
class RebuildEvaluator
|
4
|
+
|
5
|
+
# @param [XcodeArchiveCache::ArtifactCache::AbstractStorage] cache_storage
|
6
|
+
#
|
7
|
+
def initialize(cache_storage)
|
8
|
+
@cache_storage = cache_storage
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [XcodeArchiveCache::BuildGraph::Node] node
|
12
|
+
#
|
13
|
+
def evaluate(node)
|
14
|
+
return if node.rebuild != nil
|
15
|
+
|
16
|
+
# we include dependency shas in every node sha calculation,
|
17
|
+
# so if some dependency changes, that change propagates
|
18
|
+
# all the way to the top level
|
19
|
+
#
|
20
|
+
node.rebuild = cache_storage.cached_artifact_path(node) == nil
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# @return [XcodeArchiveCache::ArtifactCache::AbstractStorage]
|
26
|
+
#
|
27
|
+
attr_reader :cache_storage
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module XcodeArchiveCache
|
2
|
+
module BuildGraph
|
3
|
+
class NodeShaCalculator
|
4
|
+
|
5
|
+
# @param [XcodeArchiveCache::BuildGraph::Node] node
|
6
|
+
#
|
7
|
+
def calculate(node)
|
8
|
+
return if node.sha
|
9
|
+
|
10
|
+
dependency_shas = []
|
11
|
+
node.dependencies.each do |dependency_node|
|
12
|
+
calculate(dependency_node)
|
13
|
+
dependency_shas.push(dependency_node.sha)
|
14
|
+
end
|
15
|
+
|
16
|
+
auxiliary_file = Tempfile.new(node.name)
|
17
|
+
build_settings = settings_hash_to_string(node.build_settings.filtered)
|
18
|
+
save_auxiliary_data(build_settings, dependency_shas, auxiliary_file)
|
19
|
+
|
20
|
+
input_paths = list_input_paths(node)
|
21
|
+
node.sha = calculate_sha(input_paths + [auxiliary_file.path])
|
22
|
+
auxiliary_file.close(true)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# @param [XcodeArchiveCache::BuildGraph::Node] node
|
28
|
+
#
|
29
|
+
# @return [Array<String>]
|
30
|
+
# List of input file paths for native target
|
31
|
+
#
|
32
|
+
def list_input_paths(node)
|
33
|
+
inputs = []
|
34
|
+
|
35
|
+
node.native_target.build_phases.each do |build_phase|
|
36
|
+
inputs << list_build_phase_inputs(build_phase)
|
37
|
+
end
|
38
|
+
|
39
|
+
# file path order should not affect evaluation result
|
40
|
+
inputs.flatten.compact.sort
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [Xcodeproj::Project::Object::AbstractBuildPhase] build_phase
|
44
|
+
#
|
45
|
+
# @return [Array<String>]
|
46
|
+
# List of input file paths for build phase
|
47
|
+
#
|
48
|
+
def list_build_phase_inputs(build_phase)
|
49
|
+
build_phase.files_references.map do |file_ref|
|
50
|
+
next unless file_ref.is_a?(Xcodeproj::Project::Object::PBXFileReference)
|
51
|
+
|
52
|
+
begin
|
53
|
+
path = file_ref.real_path.to_s
|
54
|
+
rescue
|
55
|
+
next
|
56
|
+
end
|
57
|
+
|
58
|
+
if File.file?(path)
|
59
|
+
next path
|
60
|
+
elsif File.directory?(path)
|
61
|
+
# NOTE: find doesn't follow symlinks, shouldn't we follow them?
|
62
|
+
next Find.find(path).select {|found| File.file?(found)}
|
63
|
+
end
|
64
|
+
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param [Hash{String => String}] hash
|
70
|
+
#
|
71
|
+
# @return [String]
|
72
|
+
#
|
73
|
+
def settings_hash_to_string(hash)
|
74
|
+
hash.map {|name, value| "#{name} = #{value}"}.join("\n")
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param [Tempfile] tempfile
|
78
|
+
# @param [String] build_settings
|
79
|
+
# @param [Array<String>] dependency_shas
|
80
|
+
#
|
81
|
+
def save_auxiliary_data(build_settings, dependency_shas, tempfile)
|
82
|
+
file_contents = build_settings + dependency_shas.join("\n")
|
83
|
+
tempfile << file_contents
|
84
|
+
tempfile.flush
|
85
|
+
end
|
86
|
+
|
87
|
+
# @param [Array<String>] file_paths
|
88
|
+
# File paths to include in resulting sha
|
89
|
+
#
|
90
|
+
# @return [String] sha256 over specified files
|
91
|
+
#
|
92
|
+
def calculate_sha(file_paths)
|
93
|
+
hash = Digest::SHA256.new
|
94
|
+
file_paths.map do |path|
|
95
|
+
hash << Digest::SHA256.file(path).hexdigest
|
96
|
+
end
|
97
|
+
|
98
|
+
hash.to_s
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module XcodeArchiveCache
|
2
|
+
module BuildSettings
|
3
|
+
class Extractor
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@parser = Parser.new
|
7
|
+
@filter = Filter.new
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param [String] build_settings
|
11
|
+
# Raw `xcodebuild -alltargets -showBuildSettings` output
|
12
|
+
#
|
13
|
+
# @return [Hash{String => Container}]
|
14
|
+
# Target build settings keyed by target name
|
15
|
+
#
|
16
|
+
def extract_per_target(build_settings)
|
17
|
+
per_target_settings = build_settings.split("Build settings for action")
|
18
|
+
result = Hash.new
|
19
|
+
|
20
|
+
per_target_settings.each do |target_settings|
|
21
|
+
parsed_settings = parse(target_settings)
|
22
|
+
target_name = get_target_name(parsed_settings)
|
23
|
+
next unless target_name
|
24
|
+
|
25
|
+
filtered_settings = filter.filter(parsed_settings)
|
26
|
+
result[target_name] = Container.new(parsed_settings, filtered_settings)
|
27
|
+
end
|
28
|
+
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# @return [Filter]
|
35
|
+
#
|
36
|
+
attr_reader :filter
|
37
|
+
|
38
|
+
# @return [Parser]
|
39
|
+
#
|
40
|
+
attr_reader :parser
|
41
|
+
|
42
|
+
# @param [String] settings
|
43
|
+
#
|
44
|
+
# @return [Hash{String => String}]
|
45
|
+
# Setting values keyed by setting names
|
46
|
+
#
|
47
|
+
def parse(settings)
|
48
|
+
# remove leading spaces in each line, then split to lines
|
49
|
+
lines = settings.strip.gsub(/\n\s{2,}/, "\n").split("\n")
|
50
|
+
# drop "Build settings for action ..."
|
51
|
+
lines.drop(1)
|
52
|
+
|
53
|
+
result = Hash.new
|
54
|
+
|
55
|
+
lines.each do |line|
|
56
|
+
name = parser.parse_name(line)
|
57
|
+
next unless name
|
58
|
+
|
59
|
+
value = parser.parse_value(line)
|
60
|
+
next unless value
|
61
|
+
|
62
|
+
result[name] = value
|
63
|
+
end
|
64
|
+
|
65
|
+
result
|
66
|
+
end
|
67
|
+
|
68
|
+
TARGET_NAME_KEY = "TARGETNAME".freeze
|
69
|
+
|
70
|
+
# @param [Hash{String => String}] parsed_settings
|
71
|
+
#
|
72
|
+
# @return [String]
|
73
|
+
# Name of target that settings apply to
|
74
|
+
#
|
75
|
+
def get_target_name(parsed_settings)
|
76
|
+
parsed_settings[TARGET_NAME_KEY]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|