#!/usr/bin/env ruby # frozen_string_literal: true require 'xcodeproj' ROOT = File.expand_path('..', __dir__) PROJECT_PATH = File.join(ROOT, 'QuickLocation.xcodeproj') project = Xcodeproj::Project.open(PROJECT_PATH) main_target = project.targets.find { |t| t.name == 'QuickLocation' } raise 'QuickLocation target not found' unless main_target def ensure_group(project, path_components) group = project.main_group path_components.each do |name| next_group = group.children.find { |c| c.respond_to?(:name) && (c.name == name || c.path == name) } group = next_group || group.new_group(name) end group end def add_file(project, target, group, relative_path, root:, resources: false) abs = File.join(root, relative_path) raise "Missing file: #{abs}" unless File.exist?(abs) ref = group.files.find { |f| f.real_path.to_s == abs || f.path == File.basename(relative_path) } ref ||= group.new_file(abs) if resources unless target.resources_build_phase.files_references.include?(ref) target.add_resources([ref]) end else unless target.source_build_phase.files_references.include?(ref) target.source_build_phase.add_file_reference(ref) end end ref end def configure_extension_target(target, bundle_id, entitlements, info_plist) target.build_configurations.each do |config| bs = config.build_settings bs['PRODUCT_BUNDLE_IDENTIFIER'] = bundle_id bs['PRODUCT_NAME'] = target.name bs['PRODUCT_MODULE_NAME'] = target.name bs['CODE_SIGN_ENTITLEMENTS'] = entitlements bs['INFOPLIST_FILE'] = info_plist bs['GENERATE_INFOPLIST_FILE'] = 'NO' bs['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0' bs['TARGETED_DEVICE_FAMILY'] = '1' bs['SKIP_INSTALL'] = 'YES' bs['LD_RUNPATH_SEARCH_PATHS'] = '$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks' bs['DEVELOPMENT_TEAM'] = 'LRDLWZ2Y83' bs['SWIFT_DEFAULT_ACTOR_ISOLATION'] = 'nonisolated' bs['SWIFT_VERSION'] = '5.0' bs['CODE_SIGN_STYLE'] = 'Automatic' bs['CURRENT_PROJECT_VERSION'] = '1' bs['MARKETING_VERSION'] = '1.0' end end def find_or_create_extension(project, name) existing = project.targets.find { |t| t.name == name } return existing if existing project.new_target(:app_extension, name, :ios, '15.0') end def link_system_framework(target, name) frameworks = project = target.project frameworks_group = project.frameworks_group path = "System/Library/Frameworks/#{name}.framework" ref = frameworks_group.files.find { |f| f.path == "#{name}.framework" || f.name == "#{name}.framework" } ref ||= frameworks_group.new_file(path) ref.name = "#{name}.framework" ref.source_tree = 'SDKROOT' unless target.frameworks_build_phase.files_references.include?(ref) target.frameworks_build_phase.add_file_reference(ref) end end # Shared shared_group = ensure_group(project, ['AppRestrictShared']) add_file(project, main_target, shared_group, 'AppRestrictShared/AppRestrictShared.swift', root: ROOT) # Main app mgr_group = ensure_group(project, ['QuickLocation', 'Manager', 'AppRestrict']) add_file(project, main_target, mgr_group, 'QuickLocation/Manager/AppRestrict/AppRestrictManager.swift', root: ROOT) section_group = ensure_group(project, ['QuickLocation', 'Section', 'AppRestrict']) %w[ AppCatalogStore.swift AppRestrictCell.swift AppRestrictVC.swift AppRestrictView.swift AppRestrictShieldSettingsVC.swift FamilyActivityPickerHost.swift SelectActivityVC.swift ].each do |name| add_file(project, main_target, section_group, "QuickLocation/Section/AppRestrict/#{name}", root: ROOT) end add_file(project, main_target, section_group, 'QuickLocation/Section/AppRestrict/app_catalog.json', root: ROOT, resources: true) %w[FamilyControls ManagedSettings DeviceActivity ManagedSettingsUI].each do |fw| link_system_framework(main_target, fw) end # Monitor extension monitor = find_or_create_extension(project, 'DeviceActivityMonitorExtension') monitor_group = ensure_group(project, ['DeviceActivityMonitorExtension']) add_file(project, monitor, monitor_group, 'DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.swift', root: ROOT) add_file(project, monitor, shared_group, 'AppRestrictShared/AppRestrictShared.swift', root: ROOT) configure_extension_target( monitor, 'cn.zuomeng.jisuloca.DeviceActivityMonitor', 'DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.entitlements', 'DeviceActivityMonitorExtension/Info.plist' ) %w[DeviceActivity FamilyControls ManagedSettings].each { |fw| link_system_framework(monitor, fw) } # Shield extension shield = find_or_create_extension(project, 'ShieldConfigurationExtension') shield_group = ensure_group(project, ['ShieldConfigurationExtension']) add_file(project, shield, shield_group, 'ShieldConfigurationExtension/ShieldConfigurationExtension.swift', root: ROOT) add_file(project, shield, shared_group, 'AppRestrictShared/AppRestrictShared.swift', root: ROOT) configure_extension_target( shield, 'cn.zuomeng.jisuloca.ShieldConfiguration', 'ShieldConfigurationExtension/ShieldConfigurationExtension.entitlements', 'ShieldConfigurationExtension/Info.plist' ) %w[ManagedSettings ManagedSettingsUI FamilyControls UIKit].each { |fw| link_system_framework(shield, fw) } # Embed embed_phase = main_target.copy_files_build_phases.find { |p| p.dst_subfolder_spec == '13' || p.name == 'Embed Foundation Extensions' } unless embed_phase embed_phase = main_target.new_copy_files_build_phase('Embed Foundation Extensions') embed_phase.symbol_dst_subfolder_spec = :plug_ins end [monitor, shield].each do |ext| main_target.add_dependency(ext) unless main_target.dependencies.any? { |d| d.target == ext } product = ext.product_reference unless embed_phase.files.any? { |f| f.file_ref == product } bf = embed_phase.add_file_reference(product) bf.settings = { 'ATTRIBUTES' => ['RemoveHeadersOnCopy'] } end end project.save puts 'OK: App Restrict targets wired into QuickLocation.xcodeproj'