require 'cocoapods-bb-PodAssistant/helpers' require 'cocoapods-bb-PodAssistant/config/stable_specs' require 'cocoapods-bb-PodAssistant/helpers/stable_manager_helper' require 'cocoapods-core' require 'cocoapods-bb-PodAssistant/helpers/stable_env_helper' # 数据源管理 module BB class SourceManager def initialize(verify_stable_env=true, isMatrix=false) @env = BB::StableEnv.new(verify_stable_env, isMatrix) @businessSpecName = @env.business_stable @stableMgr = BB::StableManager.new(@env, verify_stable_env) end def cache_path return @env.cache_path end # 公共源路径(远端) def public_stable_yaml return @stableMgr.public_stable_yaml end # 业务源路径(远端) def business_stable_yaml return @stableMgr.business_stable_yaml(@businessSpecName) end # 本地源路径 def local_stable_yaml return @stableMgr.local_stable_yaml end def get_stable_datas(yml) spec = BB::StableSpecs.new() return spec.readData(yml) end # 通用-公共lock数据 def common_stable_datas return get_stable_datas(public_stable_yaml) end # 业务线-公共lock数据 def business_stable_datas return get_stable_datas(business_stable_yaml) end # 产品线本地lock数据 def fetch_local_stable_datas localSpec = BB::StableSpecs.new() yml = local_stable_yaml if !File.file?(yml) puts "yml配置文件不存在,执行自动初始化项目yml配置文件".green generate_localStable end return get_stable_datas(yml) end # 远端公共lock数据 def fetch_origin_stable_datas # 策略:公共数据包含通用数据 + 业务线数据 pubSpec = BB::StableSpecs.new() common_data = pubSpec.readData(public_stable_yaml) # 业务线数据 business_config_file = business_stable_yaml if business_config_file if File.exist?(business_config_file) busimessSpec = BB::StableSpecs.new() busimess_data = busimessSpec.readData(business_config_file) else puts "业务线公共配置文件#{business_config_file}不存在,请确认!!!". send(:yellow) exit end end if busimess_data # 数据合并操作(策略:业务线公共盖通用数据,理论不存在该情况) newData = common_data listdata = newData[YAML_CONFIG_LIST_KEY] removedata = newData[YAML_CONFIG_REMOVE_KEY] dependenciesdata = newData[YAML_CONFIG_DEPENDENCIES_KEY] busimess_data.each do | key, val| if key == YAML_CONFIG_LIST_KEY if val.is_a?(Hash) val.each do |list_name,list_ver| listdata[list_name] = list_ver end end elsif key == YAML_CONFIG_REMOVE_KEY if val.is_a?(Array) val.each do |remove_name| removedata.push(remove_name) end end elsif key == YAML_CONFIG_DEPENDENCIES_KEY if val.is_a?(Hash) val.each do |dependencies_name,dependencies_val| dependenciesdata[dependencies_name] = dependencies_val end end end end return newData end return common_data end # 更新本地lock数据 def update_localstable_datas(stable_lock) update_stable_lock(local_stable_yaml, stable_lock) end # podfile 更新配置文件使用(通用) def update_common_stable_lock(stable_lock) update_stable_lock(public_stable_yaml, stable_lock) end # podfile 更新配置文件使用(业务线) def update_business_stable_lock(stable_lock) update_stable_lock(business_stable_yaml, stable_lock) end def update_stable_lock(stable_yaml, stable_lock) stableSpec = BB::StableSpecs.new() stableSpec.writeData(stable_yaml, stable_lock) end # 合并stable数据,参数,支持传入更新某几个数据 def merge_stable_data(update_pods=[], is_force = false) #4、show origin_stable_lock diff with local_stable_lock new_stable_spec = compare_specs(fetch_local_stable_datas, fetch_origin_stable_datas, update_pods, is_force) #5、rewirte local_stable_lock with origin_stable_lock update_localstable_datas(new_stable_spec) end ######################################## Help ######################################## # compare tags (>=) def versionGreatOrEqual(tag1, tag2) if (tag1.is_a? String) && (tag2.is_a? String) && tag1.to_i >= tag2.to_i return true else return false end return true end # compare tags (>) def versionGreat(tag1, tag2) result = versionGreatOrEqual(tag1,tag2) if result == true && tag1 == tag2 return false end return result end # 更新依赖/移除数据 def update_dependencies_and_remove_data(stable_yaml, stable_specs, dependencies_pods, remove_pods) if dependencies_pods || remove_pods listdata = stable_specs[YAML_CONFIG_LIST_KEY] removedata = stable_specs[YAML_CONFIG_REMOVE_KEY] dependenciesdata = stable_specs[YAML_CONFIG_DEPENDENCIES_KEY] if remove_pods remove_pods.each do |name| name = name.to_s if listdata[name].nil? puts "❌ 移除未知公共组件【#{name}】,请确认名称是否正确!!! 传入参数list:#{remove_pods}".red exit end if !removedata.include?(name) removedata.push(name) end end end if dependencies_pods dependencies_pods.each do |name, array| if array.length > 0 array.each do |pod_name| if listdata[pod_name].nil? puts "❌ 依赖未知公共组件【#{name}】,请确认名称是否正确!!! 传入参数list:#{array}".red exit end end dependenciesdata[name] = array end end end stable_specs[YAML_CONFIG_REMOVE_KEY] = removedata stable_specs[YAML_CONFIG_DEPENDENCIES_KEY] = dependenciesdata update_stable_lock(stable_yaml, stable_specs) puts "removedata:#{removedata} len:#{removedata.length}".red puts "dependenciesdata:#{dependenciesdata} count:#{dependenciesdata.length}".red # 提交依赖/移除数据 if removedata.length > 0 || dependenciesdata.length > 0 commit_git_data(stable_yaml, "dependencies:#{dependenciesdata} remove:#{removedata}") end end end def update_common_dependencies_and_remove_data(dependencies_pods, remove_pods) update_dependencies_and_remove_data(public_stable_yaml, common_stable_datas, dependencies_pods, remove_pods) end def update_business_dependencies_and_remove_data(dependencies_pods, remove_pods) update_dependencies_and_remove_data(business_stable_yaml, business_stable_datas, dependencies_pods, remove_pods) end def commit_localspec_data(is_matrix, commit_msg) if is_matrix stable_yaml = business_stable_yaml else stable_yaml = public_stable_yaml end commit_git_data(stable_yaml, commit_msg) end def commit_git_data(stable_yaml, commit_msg) @stableMgr.commit_data(stable_yaml, commit_msg) end # compare specs 参数1:本地,参数2:远端,参数3:指定更新pod库 def compare_specs(local_specs, common_specs, update_pods=[], is_force = false) added_projects = [] updated_projects = [] rollbacked_projects = [] deleted_projects = [] new_specs = local_specs listdata = common_specs[YAML_CONFIG_LIST_KEY] removedata = common_specs[YAML_CONFIG_REMOVE_KEY] dependenciesdata = common_specs[YAML_CONFIG_DEPENDENCIES_KEY] # puts "local_specs:#{local_specs}".send(:green) # puts "common_specs:#{common_specs}".send(:green) need_update_pod_lists={} # step.1 匹配组件版本信息 listdata.each do |name, version| name = name.to_s local_version = local_specs[name] if local_version.nil? # 本地不存在这个数据 elsif local_version != version # puts "merge name:#{name} local_version:#{local_version} route_version:#{version}".red # 版本不一致 podCoreName = subspec_podname(name) if (update_pods.length == 0) || (update_pods.include?(podCoreName)) if versionGreat(version, local_version) updated_projects << "【#{name}】 (#{local_version}) -> (#{version.to_s.send(:yellow)})" need_update_pod_lists[podCoreName] = version else # 本地指向分支,以本地为主 rollbacked_projects << "【#{name}】 (#{version.to_s.send(:red)}) <- (#{local_version})" end end end end # 解决subspec组件同步更新版本信息 need_update_pod_lists.each do |podCoreName, version| new_specs.keys.each do |podName| if (podName == podCoreName) || has_pod_subspec(podName, podCoreName) new_specs[podName] = version end end end # step.2 匹配组件新增 dependenciesdata.each do |name, array| name = name.to_s version = listdata[name] unless version puts "公共库缺少[#{name}]版本依赖 cls:#{listdata.class} listdata:#{listdata}".send(:red) exit end local_exist_ver = new_specs[name] if local_exist_ver.nil? if (update_pods.length == 0) || (update_pods.include?(name)) new_specs[name] = version added_projects << "【#{name}】 (#{version.to_s.send(:green)})" end end if array.is_a?(Array) array.each do |name| name = name.to_s local_exist_ver = new_specs[name] if local_exist_ver.nil? if (update_pods.length == 0) || (update_pods.include?(name)) new_specs[name] = version added_projects << "【#{name}】 (#{version.to_s.send(:green)})" end end end end end # step.3 匹配组件移除 removedata.each do |name| name = name.to_s version = listdata[name] if version deleted_projects << "【#{name}】 (#{"delete".send(:red)}) <- (#{version})" end new_specs.delete(name) end showMergeLog(added_projects, updated_projects, rollbacked_projects, deleted_projects) # step.4 强制更新组件 if is_force == true puts "强制升级组件#{update_pods}".red update_pods.each do |pod_name| pod = new_specs[pod_name] if (pod.is_a? String) && !pod.include?('>=') puts "强制升级name:#{pod_name} (>= #{pod})".red new_specs[pod_name] = ">= #{pod}" end end end # puts "new_specs:#{new_specs}".send(:red) return new_specs end def showMergeLog(added, updated, rollbacked, deleted) #31m: 红色 32m:绿色 33m:黄色 34m:蓝色 #puts "\e[34m#{string}\e[0m" if added.any? puts "新增了以下项目:".send(:green) puts added.join("\n") end if updated.any? puts "更新了以下项目:". send(:yellow) puts updated.join("\n") end if rollbacked.any? puts "回滚了以下项目:".send(:red) puts rollbacked.join("\n") end if deleted.any? puts "移除了以下项目:".send(:red) puts deleted.join("\n") end unless added.any? || updated.any? || added.any? || deleted.any? puts "已经是最新版本数据".send(:green) end end private def podfile_lock lock = File.join(Pathname.pwd,"Podfile.lock") raise "podfile.lock,不存在,请先pod install/update" unless File.exist?(lock) lockfile ||= Pod::Lockfile.from_file(Pathname.new(lock)) return lockfile end def name_and_version_from_string(string_representation) match_data = string_representation.match(/\A((?:\s?[^\s(])+)(?: \((.+)\))?\Z/) unless match_data raise Informative, 'Invalid string representation for a ' \ "specification: `#{string_representation}`. " \ 'The string representation should include the name and ' \ 'optionally the version of the Pod.' end name = match_data[1] vers = Pod::Version.new(match_data[2]) [name, vers] end def name_from_string(string_representation) match_data = string_representation.match(/\A((?:\s?[^\s(])+)(?: \((.+)\))?\Z/) unless match_data raise Informative, 'Invalid string representation for a ' \ "specification: `#{string_representation}`. " \ 'The string representation should include the name and ' \ 'optionally the version of the Pod.' end name = match_data[1] return name end def podfile_lock_pods lockfile = podfile_lock internal_data = lockfile.internal_data pods = internal_data['PODS'] list={} pods.each do |pod| if pod.is_a?(Hash) # 存在依赖关系 pod_name = pod.keys.first name, version = name_and_version_from_string(pod_name) data = pod[pod_name] data.each do | pod | dependencies_pod_name = name_from_string(pod) data = list[name] if data.nil? sub_data=[] sub_data.push(dependencies_pod_name) list[name]=sub_data else data.push(dependencies_pod_name) list[name]=data end end end end return list end def whitelist_podnames return ['BBPostion', 'FMDB', 'Masonry', 'lottie-ios', 'SDWebImage', 'SSZipArchive', 'YYModel'] end # 矩阵产品需要过滤组件 def matrix_filter_pod_names(pods, ignore_dependencies_pod_names=[]) ignore_dependencies_pod_names=['BBSAdvert'] unless ignore_dependencies_pod_names.nil? # 如果业务方配置,以业务方为主,默认处理广告业务 find_ok_dependencies_pod_names=[] filter_pod_names=[] whitelist_pods={} whitelist = whitelist_podnames # 查找需要进行过滤数据,解决多场景下组件动态更新,以广告为例 if ignore_dependencies_pod_names.length > 0 puts "业务方配置需要过滤数据:#{ignore_dependencies_pod_names} cls:#{ignore_dependencies_pod_names.class}".yellow pods.each do |pod| if pod.is_a?(Hash) # 存在依赖关系 pod_name = pod.keys.first name, version = name_and_version_from_string(pod_name) podCoreName = subspec_podname(name) if ignore_dependencies_pod_names.include?(podCoreName) || find_ok_dependencies_pod_names.include?(name) if !find_ok_dependencies_pod_names.include?(name) find_ok_dependencies_pod_names.push(name) end sub_pods=[] data = pod[pod_name] data.each do | pod | dependencies_pod_name = name_from_string(pod) if dependencies_pod_name.include?(name) #subspecs if !find_ok_dependencies_pod_names.include?(dependencies_pod_name) find_ok_dependencies_pod_names.push(dependencies_pod_name) end end if !sub_pods.include?(dependencies_pod_name) sub_pods.push(dependencies_pod_name) end tmp_whitelist_pod_names = whitelist_pods[podCoreName] if (podCoreName != name) && !tmp_whitelist_pod_names.include?(dependencies_pod_name) dependencies_core_pod_name = subspec_podname(dependencies_pod_name) if (podCoreName != dependencies_core_pod_name) && !whitelist.include?(dependencies_core_pod_name) && !filter_pod_names.include?(dependencies_core_pod_name) filter_pod_names.push(dependencies_core_pod_name) end end end if podCoreName == name whitelist_pods[podCoreName]=sub_pods end end end end puts "过滤组件数据依赖:#{find_ok_dependencies_pod_names} cls:#{find_ok_dependencies_pod_names.class}" # puts "==whitelist_pods:#{whitelist_pods} cls:#{whitelist_pods.class}".red puts "需要过滤一级黑名单数据:#{filter_pod_names} cls:#{filter_pod_names.class}".yellow if filter_pod_names.length > 0 pods_list = podfile_lock_pods pods_list.keys.each do | pod_name | podCoreName = subspec_podname(pod_name) if filter_pod_names.include?(podCoreName) data = pods_list[pod_name] data.each do | sub_pod_name | sub_podCoreName = subspec_podname(sub_pod_name) if !sub_podCoreName.include?('BB') && !whitelist.include?(sub_podCoreName) && !filter_pod_names.include?(sub_podCoreName) filter_pod_names.push(sub_podCoreName) end end end end puts "需要过滤all黑名单数据:#{filter_pod_names} cls:#{filter_pod_names.class}".yellow end end return filter_pod_names end # 生成项目stable配置文件 # 策略:根据podfile.lock dependencies依赖记录pod core标签/分支数据,不存储subspec数据 def generate_localStable(ignore_dependencies_pod_names=[]) lockfile = podfile_lock local_specs = {} internal_data = lockfile.internal_data pods = internal_data['PODS'] dependencies = lockfile.dependencies pod_names = lockfile.pod_names # puts "dependencies:#{dependencies}".red # puts "pod_names:#{pod_names}".green filter_pod_names = matrix_filter_pod_names(pods, ignore_dependencies_pod_names) # dependencies依赖数据 dependencies.each do | dependency | pod_name = dependency.name if has_include_subspec(pod_name) podCoreName = subspec_podname(pod_name) else podCoreName = pod_name end # puts "podCoreName:#{podCoreName} pod_name:#{pod_name}".red if dependency.external_source.is_a? Hash # 指向分支 external_source = dependency.external_source # checkout_options = lockfile.checkout_options_for_pod_named(podCoreName) # if checkout_options.is_a? Hash # commit = checkout_options[:commit] # external_source[:commit] = commit # puts "name:#{podCoreName} checkout_options:#{checkout_options} commit:#{commit}" # end if !local_specs.has_key?(podCoreName) local_specs[podCoreName] = external_source # puts "[分支] #{podCoreName} => (#{external_source})".yellow end else # 指向标签版本 version = lockfile.version(pod_name).to_s if !version.empty? && !local_specs.has_key?(podCoreName) local_specs[podCoreName] = version # puts "[标签] #{podCoreName} => (#{version})".yellow end end end # 被依赖数据 pod_names.each do | pod_name | if has_include_subspec(pod_name) podCoreName = subspec_podname(pod_name) else podCoreName = pod_name end pod = local_specs[podCoreName] # puts "podCoreName:#{podCoreName} pod:#{pod}".green if pod.nil? # 指向标签版本 version = lockfile.version(pod_name).to_s if !version.empty? && !local_specs.has_key?(podCoreName) local_specs[podCoreName] = version # puts "[标签] #{podCoreName} => (#{version})".yellow end end end # 删除过滤数据 filter_pod_names.each do | pod_name | if local_specs.include?(pod_name) # puts "删除过滤数据pod_name:#{pod_name} #{local_specs[pod_name]}" local_specs.delete(pod_name) end end # puts "yml配置数据:#{local_specs}" if local_specs.length > 0 update_localstable_datas(local_specs) puts "Generating yml配置文件=> #{local_stable_yaml} 更新成功".green end end # pod库是否包含core subspec def has_include_core_subspec(pod_name) if has_include_subspec(pod_name) subPodName = pod_name.include?('/') ? pod_name.split('/').last.strip : pod_name if (subPodName == "Core") || (subPodName == "Main") # 过滤core数据 return true end end return false end # pod库是否包含subspec def has_include_subspec(pod_name) podCoreName = subspec_podname(pod_name) if podCoreName != pod_name return true end return false end # subspec pod库名称 def subspec_podname(pod_name) return pod_name.include?('/') ? pod_name.split('/').first.strip : pod_name end # 是否pod subspec库 def has_pod_subspec(podName, podCoreName) return (has_include_subspec(podName) && podName.include?("#{podCoreName}/")) end # 更新单个pod组件 def update_podmodules(pod_lists) local_stable_data = fetch_local_stable_datas # 本地stable配置 need_update_pod_lists = podCoreNames_from_podmodules(pod_lists) is_resave_stable_data = false need_update_pod_lists.each do |podCoreName| local_stable_data.keys.each do |podName| if (podName == podCoreName) || has_pod_subspec(podName, podCoreName) pod = local_stable_data[podName] if pod.is_a?(String) version = pod.lstrip # 去除首字母空格 initial_str = version[0..0] # 第一个字母 # 项目配置固定版本,以本项目为主 is_fixed_version = initial_str.include?('=') ? true : false # 固定版本 pod 'A','= x.x.x' is_lessthan_version = version.include?('<') ? true : false # 限制《最高》组件版本 pod 'A','< x.x.x' 或者 pod 'A','<= x.x.x' is_greaterthan_version = version.include?('>') ? true : false # 限制《最低》组件版本 pod 'A','> x.x.x' 或者 pod 'A','>= x.x.x' is_fuzzy_version = version.include?('~>') ? true : false # 固定版本 pod 'A','~> x.x.x' is_fuzzy_versionV2 = (!is_fixed_version && !is_lessthan_version && !is_greaterthan_version && !is_fuzzy_version) ? true : false # 固定版本 pod 'A','x.x.x' if (is_fuzzy_versionV2 == true) local_stable_data[podName] = ">= #{version}" is_resave_stable_data = true end end end end end if is_resave_stable_data == true puts "[PodAssistant] 更新本地stable数据".yellow update_localstable_datas(local_stable_data) end end # 更新local stable组件 def update_local_stable_from_podmodules(pod_lists) local_stable_data = fetch_local_stable_datas # 本地stable配置 need_update_pod_lists = podCoreNames_from_podmodules(pod_lists) lockfile = podfile_lock pod_names = lockfile.pod_names # lock标签数据 is_resave_stable_data = false need_update_pod_lists.each do |podCoreName| local_stable_data.keys.each do |podName| if (podName == podCoreName) || has_pod_subspec(podName, podCoreName) pod = local_stable_data[podName] if pod.is_a?(String) version = pod.lstrip # 去除首字母空格 initial_str = version[0..0] # 第一个字母 is_greaterthan_version = version.include?('>') ? true : false # 限制《最低》组件版本 pod 'A','> x.x.x' 或者 pod 'A','>= x.x.x' if (is_greaterthan_version == true) version = lockfile.version(podCoreName).to_s if !version.empty? local_stable_data[podName] = version is_resave_stable_data = true puts "update pod #{podName} (#{version})".green end end end end end end if is_resave_stable_data == true puts "[PodAssistant] 更新本地stable数据".yellow update_localstable_datas(local_stable_data) end end private def podCoreNames_from_podmodules(pod_lists) need_update_pod_lists = [] pod_lists.each do |podName| podCoreName = subspec_podname(podName) if !need_update_pod_lists.include?(podCoreName) need_update_pod_lists.push(podCoreName) end end return need_update_pod_lists end # 生成pod模块配置文件 def generate_pod_module_dependency(buss_pod_names=[]) puts "准备生成pod模块依赖关系==>#{buss_pod_names}" lockfile = podfile_lock internal_data = lockfile.internal_data pods = internal_data['PODS'] dependencies = lockfile.dependencies all_dependency_pods = podfile_lock_pods buss_pod_names.each do | buss_pod_name | local_specs = {} dependency_pods_list = [] dependency_pods_list.insert(0, "#{buss_pod_name}") # 需要添加自己控制组件 pod_list = all_dependency_pods[buss_pod_name] if pod_list dependency_pods_list.concat(pod_list) # 需要添加自己控制组件 pod_list.each do | dependency_pod_name | if has_pod_subspec(dependency_pod_name, buss_pod_name) # 依赖core subspec sub_pod_list = all_dependency_pods[dependency_pod_name] if sub_pod_list dependency_pods_list.concat(sub_pod_list) # 需要添加自己控制组件 end end end end # 可能需要依赖组件库,包含subspec dependencies.each do | dependency | dependency_pod_name = dependency.name if has_pod_subspec(dependency_pod_name, buss_pod_name) dependency_pods_list.insert(dependency_pods_list.length, "#{dependency_pod_name}") # 需要添加自己控制组件 pod_list = all_dependency_pods[dependency_pod_name] if pod_list dependency_pods_list.concat(pod_list) # 需要添加自己控制组件 end end end dependency_pods_list.each do | pod_name | # 指向标签版本 version = lockfile.version(pod_name).to_s if !version.empty? local_specs[pod_name] = version # puts "pod_name:#{pod_name} version:#{version}".red end # dependencies依赖数据 dependencies.each do | dependency | dependency_pod_name = dependency.name if dependency_pod_name == pod_name if dependency.external_source.is_a? Hash # 指向分支 external_source = dependency.external_source local_specs[pod_name] = external_source # puts "pod_name:#{pod_name} external_source:#{external_source}" end end end end yaml = "#{buss_pod_name}.yml" update_stable_lock(yaml,local_specs) if local_specs.length > 0 puts "Generating 组件#{buss_pod_name} yml配置文件=> #{yaml} 更新成功".green else puts "Generating 组件#{buss_pod_name} yml配置文件=> #{yaml} 更新失败".red end end end end end