mirror of
https://github.com/easychen/pushdeer.git
synced 2024-11-02 00:29:20 +08:00
448 lines
15 KiB
Ruby
448 lines
15 KiB
Ruby
|
###### CONSISTENCY BETWEEN MACOS AND IOS #####
|
||
|
#
|
||
|
# In order to use the same PodFile with MacOS, we need to unlink the libraries that do not support Catalyst, filter
|
||
|
# files in native targets build phases, filter dependencies and make sure the unsupported frameworks along with their
|
||
|
# their bundle resources are not included in the final archive. For that, we use `platform_filter` to specify 'ios' and
|
||
|
# 'OTHER_LDFLAGS[sdk=iphone*]' to link those libraries for iPhone and iPad. Besides, we modify "*frameworks.sh" and
|
||
|
# "*resrouces.sh" to skip installation for architecture x86_64.
|
||
|
#
|
||
|
# *Notice*: 'sdk=iphone*' excludes macOS, even though Catalyst is compiled with iOS SDK.
|
||
|
#
|
||
|
# ADDING A NEW LIBRARY
|
||
|
#
|
||
|
# Pass the name of the new library to the script
|
||
|
#
|
||
|
###### RESOURCES #######
|
||
|
#
|
||
|
# https://www.bitbuildr.com/tech-blog/mac-catalyst-porting-an-app-using-crashlytics-firebase - Article that inspired this script
|
||
|
# https://github.com/CocoaPods/Xcodeproj - Xcode configuration using ruby. This Framework is already included on cocoapods environment
|
||
|
# https://www.rubydoc.info/gems/xcodeproj/Xcodeproj/Project/Object/AbstractTarget Wiki for Xcodeproj
|
||
|
#
|
||
|
|
||
|
include Xcodeproj::Project::Object
|
||
|
include Pod
|
||
|
|
||
|
$verbose = false
|
||
|
|
||
|
def loggs string
|
||
|
if $verbose
|
||
|
puts string
|
||
|
end
|
||
|
return
|
||
|
end
|
||
|
|
||
|
# EXTENSIONS
|
||
|
|
||
|
class String
|
||
|
def filter_lines
|
||
|
lines = []
|
||
|
each_line do |line|
|
||
|
if yield line
|
||
|
lines = lines + [line]
|
||
|
end
|
||
|
end
|
||
|
return lines
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class PBXNativeTarget
|
||
|
|
||
|
###### STEP 4 ######
|
||
|
# In "Pods-" targets, modify "*frameworks.sh" to not install unsupported frameworks for platform architectures
|
||
|
def uninstall_frameworks frameworks, platform, configurations
|
||
|
uninstall frameworks, "#{name}-frameworks.sh", platform.architectures, configurations
|
||
|
end
|
||
|
|
||
|
###### STEP 5 ######
|
||
|
# In "Pods-" targets, modify "*resources.sh" to not install unsupported frameworks for platform architectures
|
||
|
def uninstall_resources resources, platform, configurations
|
||
|
uninstall resources, "#{name}-resources.sh", platform.architectures, configurations
|
||
|
end
|
||
|
|
||
|
def support_files_folder
|
||
|
build_configurations.filter do |config| not config.base_configuration_reference.nil? end.first.base_configuration_reference.real_path.parent
|
||
|
end
|
||
|
|
||
|
@private
|
||
|
def uninstall keys, file_name, architectures, configurations=nil
|
||
|
configurations = configurations.nil? ? build_configurations.map { |b| b.name } : configurations
|
||
|
keys = keys.to_set.to_a
|
||
|
loggs "\t\t\tUninstalling for configurations: #{configurations}"
|
||
|
if support_files_folder.nil?
|
||
|
loggs "\t\t\tNothing to uninstall"
|
||
|
return
|
||
|
end
|
||
|
|
||
|
script_path = support_files_folder.join file_name
|
||
|
if !script_path.exist?
|
||
|
loggs "\t\t\tNothing to uninstall"
|
||
|
return
|
||
|
end
|
||
|
|
||
|
script = File.read(script_path)
|
||
|
snippets = script.scan(/if \[\[ \"\$CONFIGURATION\" [\S\s]*?(?=fi\n)fi/)
|
||
|
condition = architectures.map do |arch| "[ \"$ARCHS\" != \"#{arch}\" ]" end.reduce("") do |total, piece| total.empty? ? piece : total + " || " + piece end
|
||
|
changed = false
|
||
|
|
||
|
snippets.filter do |snippet|
|
||
|
configurations.map do |string| snippet.include? string end.reduce(false) do |total, condition| total = total || condition end
|
||
|
end.each do |snippet|
|
||
|
new_snippet = snippet.clone
|
||
|
keys.each do |key|
|
||
|
lines_to_replace = snippet.filter_lines do |line| line.include? "#{key}" end.to_set.to_a
|
||
|
unless lines_to_replace.empty?
|
||
|
changed = true
|
||
|
lines_to_replace.each do |line|
|
||
|
new_snippet.gsub! line, "\tif #{condition}; then \n\t#{line}\tfi\n"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
script.gsub! snippet, new_snippet
|
||
|
end
|
||
|
|
||
|
if changed
|
||
|
File.open(script_path, "w") { |file| file << script }
|
||
|
end
|
||
|
loggs "\t\t\t#{changed ? "Succeded" : "Nothing to uninstall"}"
|
||
|
end
|
||
|
|
||
|
###### STEP 1 ######
|
||
|
# In native target's build phases, add platform filter to:
|
||
|
# - Resources
|
||
|
# - Compile Sources
|
||
|
# - Frameworks
|
||
|
# - Headers
|
||
|
def add_platform_filter_to_build_phases platform
|
||
|
loggs "\t\t- Filtering resources"
|
||
|
resources_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end
|
||
|
|
||
|
loggs "\t\t- Filtering compile sources"
|
||
|
source_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end
|
||
|
|
||
|
loggs "\t\t- Filtering frameworks"
|
||
|
frameworks_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end
|
||
|
|
||
|
loggs "\t\t- Filtering headers"
|
||
|
headers_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
class PodTarget
|
||
|
|
||
|
def module_name
|
||
|
string = name.clone.gsub! /-iOS[0-9]+(\.[0-9])+$/, ''
|
||
|
return string.nil? ? name : string
|
||
|
end
|
||
|
|
||
|
def resources
|
||
|
return file_accessors.flat_map do |accessor| accessor.resources end.map do |path| "#{path.basename}" end
|
||
|
end
|
||
|
|
||
|
def vendor_products
|
||
|
return file_accessors.flat_map do |accessor|
|
||
|
accessor.vendored_frameworks + accessor.vendored_libraries
|
||
|
end.map do |s| s.basename
|
||
|
end.map do |s|
|
||
|
name = "#{s}"
|
||
|
if name.include? "framework"
|
||
|
PodDependency.newFramework name.sub(".framework", "")
|
||
|
else
|
||
|
PodDependency.newLibrary name.sub("lib", "").sub(".a", "")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def frameworks
|
||
|
return file_accessors.flat_map do |accessor|
|
||
|
accessor.spec_consumer.frameworks.map do |name| PodDependency.newFramework name end + accessor.spec_consumer.libraries.map do |name| PodDependency.newLibrary name end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
class PBXTargetDependency
|
||
|
def module_name
|
||
|
string = name.clone.gsub! /-iOS[0-9]+(\.[0-9])+$/, ''
|
||
|
return string.nil? ? name : string
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class AbstractTarget
|
||
|
|
||
|
def module_name
|
||
|
string = name.clone.gsub! /-iOS[0-9]+(\.[0-9])+$/, ''
|
||
|
return string.nil? ? name : string
|
||
|
end
|
||
|
|
||
|
###### STEP 2 ######
|
||
|
# In all targets (aggregates + native), filter dependencies
|
||
|
def add_platform_filter_to_dependencies platform
|
||
|
loggs "\t\t- Filtering dependencies"
|
||
|
dependencies.each do |dependency|
|
||
|
dependency.platform_filter = platform.name
|
||
|
end
|
||
|
end
|
||
|
|
||
|
###### STEP 3 ######
|
||
|
# If any unsupported library, then flag as platform-dependant for every build configuration
|
||
|
def flag_libraries libraries, platform
|
||
|
loggs "\tTarget: #{name}"
|
||
|
build_configurations.filter do |config| not config.base_configuration_reference.nil?
|
||
|
end.each do |config|
|
||
|
loggs "\t\tScheme: #{config.name}"
|
||
|
xcconfig_path = config.base_configuration_reference.real_path
|
||
|
xcconfig = File.read(xcconfig_path)
|
||
|
|
||
|
changed = false
|
||
|
libraries.each do |framework|
|
||
|
if xcconfig.include? framework
|
||
|
xcconfig.gsub!(framework, '')
|
||
|
unless xcconfig.include? "OTHER_LDFLAGS[sdk=#{platform.sdk}]"
|
||
|
changed = true
|
||
|
xcconfig += "OTHER_LDFLAGS[sdk=#{platform.sdk}] = $(inherited) -ObjC "
|
||
|
end
|
||
|
xcconfig += framework + ' '
|
||
|
end
|
||
|
end
|
||
|
|
||
|
File.open(xcconfig_path, "w") { |file| file << xcconfig }
|
||
|
loggs "\t\t\t#{changed ? "Succeded" : "Nothing to flag"}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def to_dependency
|
||
|
# We return both as we don't know if build as library or framework
|
||
|
return [PodDependency.newFramework(module_name), PodDependency.newLibrary(module_name)]
|
||
|
end
|
||
|
|
||
|
# Dependencies contained in Other Linker Flags
|
||
|
def other_linker_flags_dependencies
|
||
|
frameworks = Array.new
|
||
|
libraries = Array.new
|
||
|
|
||
|
config = build_configurations.filter do |config| not config.base_configuration_reference.nil? end.first
|
||
|
xcconfig_path = config.base_configuration_reference.real_path
|
||
|
xcconfig = File.read(xcconfig_path)
|
||
|
xcconfig.gsub!(/\r\n?/, "\n")
|
||
|
|
||
|
xcconfig.each_line do |line|
|
||
|
if line.start_with? 'OTHER_LDFLAGS'
|
||
|
frameworks = frameworks + line.split("-framework").map do |s|
|
||
|
s.strip.delete("\n") end.filter do |s|
|
||
|
s.strip.start_with? '"' end
|
||
|
libraries = libraries + line.split("-l").filter do |s| s.strip.start_with? '"' end.map do |s| s.strip.split(' ').first end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
libraries = libraries.map do |name| PodDependency.newLibrary(name.gsub!("\"", "")) end
|
||
|
frameworks = frameworks.map do |name| PodDependency.newFramework(name.gsub!("\"", "")) end
|
||
|
|
||
|
return OtherLinkerFlagsDependencies.new libraries, frameworks
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# HELPER CLASSES
|
||
|
|
||
|
class PodDependency
|
||
|
attr_reader :name
|
||
|
attr_reader :type
|
||
|
|
||
|
def link
|
||
|
if library?
|
||
|
return "-l\"#{name}\""
|
||
|
else
|
||
|
return "-framework \"#{name}\""
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def library?
|
||
|
return type == "library"
|
||
|
end
|
||
|
|
||
|
def framework?
|
||
|
return type == "framework"
|
||
|
end
|
||
|
|
||
|
def self.newFramework name
|
||
|
return PodDependency.new(name, "framework")
|
||
|
end
|
||
|
|
||
|
def self.newLibrary name
|
||
|
return PodDependency.new(name, "library")
|
||
|
end
|
||
|
|
||
|
def ==(other)
|
||
|
name == other.name && type == other.type
|
||
|
end
|
||
|
|
||
|
def eql?(other)
|
||
|
name == other.name
|
||
|
end
|
||
|
|
||
|
private
|
||
|
def initialize(name, type)
|
||
|
@name = name
|
||
|
@type = type
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
class OtherLinkerFlagsDependencies
|
||
|
attr_reader :libraries
|
||
|
attr_reader :frameworks
|
||
|
|
||
|
def initialize(libraries = [], frameworks = [])
|
||
|
@libraries = libraries
|
||
|
@frameworks = frameworks
|
||
|
end
|
||
|
|
||
|
def combine dependencies
|
||
|
frameworks = (dependencies.frameworks + @frameworks).to_set.to_a
|
||
|
libraries = (dependencies.libraries + @libraries).to_set.to_a
|
||
|
return OtherLinkerFlagsDependencies.new libraries, frameworks
|
||
|
end
|
||
|
|
||
|
def dependencies
|
||
|
libraries + frameworks
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
class OSPlatform
|
||
|
attr_reader :sdk
|
||
|
attr_reader :name
|
||
|
attr_reader :architectures
|
||
|
|
||
|
def self.ios
|
||
|
OSPlatform.new 'ios', 'iphone*', ['arm64', 'armv7s', 'armv7']
|
||
|
end
|
||
|
|
||
|
def self.macos
|
||
|
OSPlatform.new 'macos', 'macosx*', ['x86_64']
|
||
|
end
|
||
|
|
||
|
def self.wtachos
|
||
|
OSPlatform.new 'watchos', 'watchos*', ['arm64_32', 'armv7k']
|
||
|
end
|
||
|
|
||
|
def self.tvos
|
||
|
OSPlatform.new 'tvos', 'appletvos*', ['arm64']
|
||
|
end
|
||
|
|
||
|
private
|
||
|
def initialize(name, sdk, architectures)
|
||
|
@name = name
|
||
|
@sdk = sdk
|
||
|
@architectures = architectures
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# SCRIPT
|
||
|
|
||
|
class Installer
|
||
|
|
||
|
def configure_support_catalyst pod_names_to_keep, pod_names_to_remove, configurations=nil
|
||
|
|
||
|
###### Variable definition ######
|
||
|
targets = pods_project.targets
|
||
|
|
||
|
pod_names_to_remove = pod_names_to_remove.map do |name| name.sub('/', '') end
|
||
|
pod_names_to_keep = pod_names_to_keep.map do |name| name.sub('/', '') end
|
||
|
|
||
|
pod_names_to_keep = recursive_dependencies pod_names_to_keep
|
||
|
pod_names_to_remove = recursive_dependencies(pod_names_to_remove).filter do |name| !pod_names_to_keep.include? name end
|
||
|
|
||
|
pod_targets_to_keep = pod_targets.filter do |pod| pod_names_to_keep.include? pod.module_name end # PodTarget
|
||
|
pod_targets_to_remove = pod_targets.filter do |pod| pod_names_to_remove.include? pod.module_name end # PodTarget
|
||
|
|
||
|
loggs "\n#### Unsupported Libraries ####\n#{pod_names_to_remove}\n"
|
||
|
|
||
|
targets_to_remove = targets.filter do |target| pod_names_to_remove.include?(target.module_name) end # AbstractTarget
|
||
|
pods_targets = targets.filter do |target| target.name.start_with? "Pods-" end # AbstractTarget
|
||
|
cross_platform_targets = targets.filter do |target| !targets_to_remove.include?(target) && !pods_targets.include?(target) end # AbstractTarget
|
||
|
|
||
|
###### Determine which dependencies should be removed ######
|
||
|
dependencies_to_keep = cross_platform_targets.reduce(OtherLinkerFlagsDependencies.new) do |dependencies, target|
|
||
|
dependencies.combine target.other_linker_flags_dependencies
|
||
|
end.dependencies
|
||
|
|
||
|
# [PodDependency]
|
||
|
dependencies_to_keep = dependencies_to_keep + cross_platform_targets.flat_map do |target| target.to_dependency end + pod_targets_to_keep.flat_map do |pod| pod.vendor_products + pod.frameworks end
|
||
|
|
||
|
dependencies_to_remove = targets_to_remove.reduce(OtherLinkerFlagsDependencies.new) do |dependencies, target|
|
||
|
dependencies.combine target.other_linker_flags_dependencies
|
||
|
end.dependencies
|
||
|
|
||
|
# [PodDependency]
|
||
|
dependencies_to_remove = dependencies_to_remove + targets_to_remove.flat_map do |target| target.to_dependency end + pod_targets_to_remove.flat_map do |pod| pod.vendor_products + pod.frameworks end
|
||
|
dependencies_to_remove = dependencies_to_remove.filter do |d| !dependencies_to_keep.include? d end
|
||
|
|
||
|
###### CATALYST NOT SUPPORTED LINKS ######
|
||
|
unsupported_links = dependencies_to_remove.map do |d| d.link end.to_set.to_a
|
||
|
|
||
|
loggs "#### Unsupported dependencies ####\n"
|
||
|
loggs "#{dependencies_to_remove.map do |d| d.name end.to_set.to_a }\n\n"
|
||
|
|
||
|
###### CATALYST NOT SUPPORTED FRAMEWORKS AND RESOURCES
|
||
|
frameworks_to_uninstall = dependencies_to_remove.filter do |d| d.framework? end.map do |d| "#{d.name}.framework" end.to_set.to_a
|
||
|
resources_to_uninstall = pod_targets_to_remove.flat_map do |pod| pod.resources end.to_set.to_a
|
||
|
|
||
|
loggs "#### Frameworks not to be included in the Archive ####\n"
|
||
|
loggs "#{frameworks_to_uninstall}\n\n"
|
||
|
|
||
|
loggs "#### Resources not to be included in the Archive ####\n"
|
||
|
loggs "#{resources_to_uninstall}\n\n"
|
||
|
|
||
|
###### OTHER LINKER FLAGS -> to iphone* ######
|
||
|
loggs "#### Flagging unsupported libraries ####"
|
||
|
targets.each do |target| target.flag_libraries unsupported_links, OSPlatform.ios end
|
||
|
|
||
|
###### BUILD_PHASES AND DEPENDENCIES -> PLATFORM_FILTER 'ios' ######
|
||
|
loggs "\n#### Filtering build phases ####"
|
||
|
targets_to_remove.filter do |target|
|
||
|
pods_project.native_targets.include? target
|
||
|
end.each do |target|
|
||
|
loggs "\tTarget: #{target.name}"
|
||
|
target.add_platform_filter_to_build_phases OSPlatform.ios
|
||
|
target.add_platform_filter_to_dependencies OSPlatform.ios
|
||
|
end
|
||
|
|
||
|
loggs "\n#### Filtering dependencies ####"
|
||
|
targets_to_remove.filter do |target|
|
||
|
!pods_project.native_targets.include? target
|
||
|
end.each do |target|
|
||
|
loggs "\tTarget: #{target.name}"
|
||
|
target.add_platform_filter_to_dependencies OSPlatform.ios
|
||
|
end
|
||
|
|
||
|
###### FRAMEWORKS AND RESOURCES SCRIPT -> if [ "$ARCHS" != "x86_64" ]; then #######
|
||
|
loggs "\n#### Chagings frameworks and resources script ####"
|
||
|
pods_targets.each do |target|
|
||
|
loggs "\tTarget: #{target.name}"
|
||
|
loggs "\t\t-Uninstalling frameworks"
|
||
|
target.uninstall_frameworks frameworks_to_uninstall, OSPlatform.macos, configurations
|
||
|
|
||
|
loggs "\t\t-Uninstalling resources"
|
||
|
target.uninstall_resources resources_to_uninstall, OSPlatform.macos, configurations
|
||
|
end
|
||
|
end
|
||
|
|
||
|
@private
|
||
|
def recursive_dependencies to_filter_names
|
||
|
targets = pods_project.targets
|
||
|
targets_to_remove = targets.filter do |target| to_filter_names.include? target.module_name end
|
||
|
dependencies = targets_to_remove.flat_map do |target| target.dependencies end
|
||
|
dependencies_names = dependencies.map do |d| d.module_name end
|
||
|
|
||
|
if dependencies.empty?
|
||
|
return to_filter_names + dependencies_names
|
||
|
else
|
||
|
return to_filter_names + recursive_dependencies(dependencies_names)
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|