New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Integrate ODRs for app specs. #10845
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,10 @@ | |
# platform :ios, '9.0' | ||
|
||
target 'OnDemandResourcesDemo' do | ||
platform :ios, '13.2' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
# Comment the next line if you don't want to use dynamic frameworks | ||
use_frameworks! | ||
pod 'TestLibrary', :path => './TestLibrary' | ||
pod 'TestLibrary', :path => './TestLibrary', :appspecs => ['App1', 'App2'], :testspecs => ['Tests'] | ||
|
||
# Pods for OnDemandResourcesDemo | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import UIKit | ||
|
||
class ViewController: UIViewController { | ||
override func viewDidLoad() { | ||
view.backgroundColor = .green | ||
} | ||
} | ||
|
||
@UIApplicationMain | ||
class AppDelegate: NSObject, UIApplicationDelegate { | ||
var window: UIWindow? | ||
|
||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { | ||
window = UIWindow(frame: UIScreen.main.bounds) | ||
window?.rootViewController = UINavigationController(rootViewController: ViewController()) | ||
window?.makeKeyAndVisible() | ||
|
||
return true | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import UIKit | ||
|
||
class ViewController: UIViewController { | ||
override func viewDidLoad() { | ||
view.backgroundColor = .green | ||
} | ||
} | ||
|
||
@UIApplicationMain | ||
class AppDelegate: NSObject, UIApplicationDelegate { | ||
var window: UIWindow? | ||
|
||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { | ||
window = UIWindow(frame: UIScreen.main.bounds) | ||
window?.rootViewController = UINavigationController(rootViewController: ViewController()) | ||
window?.makeKeyAndVisible() | ||
|
||
return true | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -433,6 +433,85 @@ def embed_frameworks_output_paths(framework_paths, xcframeworks) | |
end | ||
paths + xcframework_paths | ||
end | ||
|
||
# Updates a projects native targets to include on demand resources specified by the supplied parameters. | ||
# Note that currently, only app level targets are allowed to include on demand resources. | ||
# | ||
# @param [Sandbox] sandbox | ||
# The sandbox to use for calculating ODR file references. | ||
# | ||
# @param [Xcodeproj::Project] project | ||
# The project to update known asset tags as well as add the ODR group. | ||
# | ||
# @param [Xcodeproj::PBXNativeTarget, Array<Xcodeproj::PBXNativeTarget>] native_targets | ||
# The native targets to integrate on demand resources into. | ||
# | ||
# @param [Sandbox::FileAccessor, Array<Sandbox::FileAccessor>] file_accessors | ||
# The file accessors that that provide the ODRs to integrate. | ||
# | ||
# @param [Xcodeproj::PBXGroup] parent_odr_group | ||
# The group to use as the parent to add ODR file references into. | ||
# | ||
# @param [String] target_odr_group_name | ||
# The name to use for the group created that contains the ODR file references. | ||
# | ||
# @return [void] | ||
# | ||
def add_on_demand_resources(sandbox, project, native_targets, file_accessors, parent_odr_group, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this became a "helper" class method as the others. Helps do the job easily without repeating stuff in both |
||
target_odr_group_name) | ||
asset_tags_added = Set.new | ||
file_accessors = Array(file_accessors) | ||
native_targets = Array(native_targets) | ||
|
||
# Target no longer provides ODR references so remove everything related to this target. | ||
if file_accessors.all? { |fa| fa.on_demand_resources.empty? } | ||
old_target_odr_group = parent_odr_group[target_odr_group_name] | ||
old_odr_file_refs = old_target_odr_group&.recursive_children_groups&.each_with_object({}) do |group, hash| | ||
hash[group.name] = group.files | ||
end || {} | ||
native_targets.each do |user_target| | ||
user_target.remove_on_demand_resources(old_odr_file_refs) | ||
end | ||
old_target_odr_group&.remove_from_project | ||
return | ||
end | ||
|
||
target_odr_group = parent_odr_group[target_odr_group_name] || parent_odr_group.new_group(target_odr_group_name) | ||
current_file_refs = target_odr_group.recursive_children_groups.flat_map(&:files) | ||
|
||
added_file_refs = file_accessors.flat_map do |file_accessor| | ||
target_odr_files_refs = Hash[file_accessor.on_demand_resources.map do |tag, resources| | ||
tag_group = target_odr_group[tag] || target_odr_group.new_group(tag) | ||
asset_tags_added << tag | ||
resources_file_refs = resources.map do |resource| | ||
odr_resource_file_ref = Pathname.new(resource).relative_path_from(sandbox.root) | ||
tag_group.find_file_by_path(odr_resource_file_ref.to_s) || tag_group.new_file(odr_resource_file_ref) | ||
end | ||
[tag, resources_file_refs] | ||
end] | ||
native_targets.each do |user_target| | ||
user_target.add_on_demand_resources(target_odr_files_refs) | ||
end | ||
target_odr_files_refs.values.flatten | ||
end | ||
|
||
# if the target ODR file references were updated, make sure we remove the ones that are no longer present | ||
# for the target. | ||
remaining_refs = current_file_refs - added_file_refs | ||
remaining_refs.each do |ref| | ||
native_targets.each do |user_target| | ||
user_target.resources_build_phase.remove_file_reference(ref) | ||
end | ||
ref.remove_from_project | ||
end | ||
target_odr_group.recursive_children_groups.each { |g| g.remove_from_project if g.empty? } | ||
|
||
unless asset_tags_added.empty? | ||
attributes = project.root_object.attributes | ||
attributes['KnownAssetTags'] = (attributes['KnownAssetTags'] ||= []) | asset_tags_added.to_a | ||
dnkoutso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
project.root_object.attributes = attributes | ||
end | ||
end | ||
end | ||
|
||
# Integrates the user project targets. Only the targets that do **not** | ||
|
@@ -632,68 +711,17 @@ def remove_obsolete_script_phases(removed_phase_names = REMOVED_SCRIPT_PHASE_NAM | |
end | ||
end | ||
|
||
# Updates all user targets to include on demand resources specified by libraries. Note that currently, | ||
# only app level targets are allowed to include on demand resources. | ||
# | ||
# @return [void] | ||
# | ||
def add_on_demand_resources | ||
user_project = target.user_project | ||
|
||
asset_tags_added = target.pod_targets.each_with_object(Set.new) do |pod_target, asset_tags| | ||
target_odr_group_name = "#{pod_target.label}-OnDemandResources" | ||
target.pod_targets.each do |pod_target| | ||
# When integrating with the user's project we are only interested in integrating ODRs from library specs | ||
# and not test specs or app specs. | ||
library_file_accessors = pod_target.file_accessors.select { |fa| fa.spec.library_specification? } | ||
|
||
# Target no longer provides ODR references so remove everything related to this target. | ||
if library_file_accessors.all? { |fa| fa.on_demand_resources.empty? } | ||
old_target_odr_group = user_project.main_group.find_subpath("Pods/#{target_odr_group_name}") | ||
old_odr_file_refs = old_target_odr_group&.recursive_children_groups&.each_with_object({}) do |group, hash| | ||
hash[group.name] = group.files | ||
end || {} | ||
target.user_targets.each do |user_target| | ||
user_target.remove_on_demand_resources(old_odr_file_refs) | ||
end | ||
old_target_odr_group&.remove_from_project | ||
next | ||
end | ||
|
||
target_odr_group = user_project['Pods'][target_odr_group_name] || user_project['Pods'].new_group(target_odr_group_name) | ||
current_file_refs = target_odr_group.recursive_children_groups.flat_map(&:files) | ||
|
||
added_file_refs = library_file_accessors.flat_map do |file_accessor| | ||
target_odr_files_refs = Hash[file_accessor.on_demand_resources.map do |tag, resources| | ||
tag_group = target_odr_group[tag] || target_odr_group.new_group(tag) | ||
asset_tags << tag | ||
resources_file_refs = resources.map do |resource| | ||
odr_resource_file_ref = Pathname.new(resource).relative_path_from(target.sandbox.root) | ||
tag_group.find_file_by_path(odr_resource_file_ref.to_s) || tag_group.new_file(odr_resource_file_ref) | ||
end | ||
[tag, resources_file_refs] | ||
end] | ||
target.user_targets.each do |user_target| | ||
user_target.add_on_demand_resources(target_odr_files_refs) | ||
end | ||
target_odr_files_refs.values.flatten | ||
end | ||
|
||
# if the target ODR file references were updated, make sure we remove the ones that are no longer present | ||
# for the target. | ||
remaining_refs = current_file_refs - added_file_refs | ||
unless remaining_refs.empty? | ||
remaining_refs.each do |ref| | ||
target.user_targets.each do |user_target| | ||
user_target.resources_build_phase.remove_file_reference(ref) | ||
end | ||
ref.remove_from_project | ||
end | ||
target_odr_group.recursive_children_groups.each { |g| g.remove_from_project if g.empty? } | ||
end | ||
end | ||
|
||
unless asset_tags_added.empty? | ||
attributes = user_project.root_object.attributes | ||
attributes['KnownAssetTags'] = (attributes['KnownAssetTags'] ||= []) | asset_tags_added.to_a | ||
target.user_project.root_object.attributes = attributes | ||
target_odr_group_name = "#{pod_target.label}-OnDemandResources" | ||
# The 'Pods' group would always be there for production code however for tests its sometimes not added. | ||
# This ensures its always present and makes it easier for existing and new tests. | ||
parent_odr_group = target.user_project.main_group['Pods'] || target.user_project.new_group('Pods') | ||
TargetIntegrator.add_on_demand_resources(target.sandbox, target.user_project, target.user_targets, | ||
library_file_accessors, parent_odr_group, target_odr_group_name) | ||
end | ||
end | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ def integrate! | |
target_installation_result.non_library_specs_by_native_target.each do |native_target, spec| | ||
add_embed_frameworks_script_phase(native_target, spec) | ||
add_copy_resources_script_phase(native_target, spec) | ||
add_on_demand_resources(native_target, spec) if spec.app_specification? | ||
UserProjectIntegrator::TargetIntegrator.create_or_update_user_script_phases(script_phases_for_specs(spec), native_target) | ||
end | ||
add_copy_dsyms_script_phase(target_installation_result.native_target) | ||
|
@@ -205,7 +206,7 @@ def add_copy_xcframeworks_script_phase(native_target) | |
# vendored dSYMs. | ||
# | ||
# @param [PBXNativeTarget] native_target | ||
# the native target for which to add the the copy dSYM files build phase. | ||
# the native target for which to add the copy dSYM files build phase. | ||
# | ||
# @return [void] | ||
# | ||
|
@@ -248,6 +249,41 @@ def add_copy_dsyms_script_phase(native_target) | |
UserProjectIntegrator::TargetIntegrator.set_input_output_paths(phase, input_paths_by_config, output_paths_by_config) | ||
end | ||
|
||
# Adds the ODRs that are related to this app spec. This includes the app spec dependencies as well as the ODRs | ||
# coming from the app spec itself. | ||
# | ||
# @param [Xcodeproj::PBXNativeTarget] native_target | ||
# the native target for which to add the ODR file references into. | ||
# | ||
# @param [Specification] app_spec | ||
# the app spec to integrate ODRs for. | ||
# | ||
# @return [void] | ||
# | ||
def add_on_demand_resources(native_target, app_spec) | ||
dependent_targets = target.dependent_targets_for_app_spec(app_spec) | ||
parent_odr_group = native_target.project.group_for_spec(app_spec.name) | ||
|
||
# Add ODRs of the app spec dependencies first. | ||
dependent_targets.each do |pod_target| | ||
file_accessors = pod_target.file_accessors.select do |fa| | ||
fa.spec.library_specification? || | ||
fa.spec.test_specification? && pod_target.test_app_hosts_by_spec[fa.spec]&.first == app_spec | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't know the structure of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It returns a tuple: # @return [Hash{Specification => (Specification,PodTarget)}] tuples of app specs and pod targets by test spec.
#
attr_accessor :test_app_hosts_by_spec I am interested only on the We could potentially expose a method in |
||
end | ||
target_odr_group_name = "#{pod_target.label}-OnDemandResources" | ||
UserProjectIntegrator::TargetIntegrator.add_on_demand_resources(target.sandbox, native_target.project, | ||
native_target, file_accessors, | ||
parent_odr_group, target_odr_group_name) | ||
end | ||
|
||
# Now add the ODRs of our own app spec declaration. | ||
file_accessor = target.file_accessors.find { |fa| fa.spec == app_spec } | ||
target_odr_group_name = "#{target.subspec_label(app_spec)}-OnDemandResources" | ||
UserProjectIntegrator::TargetIntegrator.add_on_demand_resources(target.sandbox, native_target.project, | ||
native_target, file_accessor, | ||
parent_odr_group, target_odr_group_name) | ||
end | ||
|
||
# @return [String] the message that should be displayed for the target | ||
# integration. | ||
# | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -79,12 +79,12 @@ def initialize(target, native_target, resource_bundle_targets = [], test_native_ | |
# @param [Specification] spec | ||
# The specification to base from in order to find the native target. | ||
# | ||
# @return [PBXNativeTarget] the native target to use or `nil` if none is found. | ||
# @return [PBXNativeTarget, Nil] the native target to use or `nil` if none is found. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IDE warning fix |
||
# | ||
def native_target_for_spec(spec) | ||
return native_target if spec.library_specification? | ||
return test_native_target_from_spec(spec) if spec.test_specification? | ||
return app_native_target_from_spec(spec) if spec.app_specification? | ||
app_native_target_from_spec(spec) if spec.app_specification? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IDE warning fix |
||
end | ||
|
||
# @return [Hash{PBXNativeTarget => Specification}] a hash where the keys are the test native targets and the value | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changelog entry was in the wrong spot.