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