@@ -632,6 +632,55 @@ public final class InfoPlistProcessorTaskAction: TaskAction
632632 return ( . succeeded, messages)
633633 }
634634
635+ private let usageDescriptionStringKeys : Set < String > = [
636+ " NFCReaderUsageDescription " ,
637+ " NSAppleEventsUsageDescription " ,
638+ " NSAppleMusicUsageDescription " ,
639+ " NSBluetoothAlwaysUsageDescription " ,
640+ " NSBluetoothPeripheralUsageDescription " ,
641+ " NSBluetoothWhileInUseUsageDescription " ,
642+ " NSCalendarsUsageDescription " ,
643+ " NSCameraUsageDescription " ,
644+ " NSContactsUsageDescription " ,
645+ " NSDesktopFolderUsageDescription " ,
646+ " NSDocumentsFolderUsageDescription " ,
647+ " NSDownloadsFolderUsageDescription " ,
648+ " NSFaceIDUsageDescription " ,
649+ " NSFallDetectionUsageDescription " ,
650+ " NSFileProviderDomainUsageDescription " ,
651+ " NSFileProviderPresenceUsageDescription " ,
652+ " NSFocusStatusUsageDescription " ,
653+ " NSGKFriendListUsageDescription " ,
654+ " NSHealthClinicalHealthRecordsShareUsageDescription " ,
655+ " NSHealthShareUsageDescription " ,
656+ " NSHealthUpdateUsageDescription " ,
657+ " NSHomeKitUsageDescription " ,
658+ " NSIdentityUsageDescription " ,
659+ " NSLocalNetworkUsageDescription " ,
660+ " NSLocationAlwaysAndWhenInUseUsageDescription " ,
661+ " NSLocationAlwaysUsageDescription " ,
662+ " NSLocationUsageDescription " ,
663+ " NSLocationWhenInUseUsageDescription " ,
664+ " NSMicrophoneUsageDescription " ,
665+ " NSMotionUsageDescription " ,
666+ " NSNearbyInteractionAllowOnceUsageDescription " ,
667+ " NSNearbyInteractionUsageDescription " ,
668+ " NSNetworkVolumesUsageDescription " ,
669+ " NSPhotoLibraryAddUsageDescription " ,
670+ " NSPhotoLibraryUsageDescription " ,
671+ " NSRemindersUsageDescription " ,
672+ " NSRemovableVolumesUsageDescription " ,
673+ " NSSensorKitUsageDescription " ,
674+ " NSSiriUsageDescription " ,
675+ " NSSpeechRecognitionUsageDescription " ,
676+ " NSSystemAdministrationUsageDescription " ,
677+ " NSSystemExtensionUsageDescription " ,
678+ " NSUserTrackingUsageDescription " ,
679+ " NSVideoSubscriberAccountUsageDescription " ,
680+ " NSVoIPUsageDescription " ,
681+ " OSBundleUsageDescription " ,
682+ ]
683+
635684 /// Produce a collection of default Info.plist keys based on content in the project, taking platform and product type into account.
636685 /// Could be done through InfoPlistAdditions properties in product types, but that would result in more duplication at this point
637686 private func defaultInfoPlistContent( scope: MacroEvaluationScope , platform: Platform ? , productType: ProductTypeSpec ? ) -> [ String : PropertyListItem ]
@@ -687,59 +736,9 @@ public final class InfoPlistProcessorTaskAction: TaskAction
687736 " LSApplicationCategoryType " ,
688737 " NSHumanReadableCopyright " ,
689738 " NSPrincipalClass " ,
690-
691- // Usage Descriptions
692-
693739 " ITSAppUsesNonExemptEncryption " ,
694740 " ITSEncryptionExportComplianceCode " ,
695- " NFCReaderUsageDescription " ,
696- " NSAppleEventsUsageDescription " ,
697- " NSAppleMusicUsageDescription " ,
698- " NSBluetoothAlwaysUsageDescription " ,
699- " NSBluetoothPeripheralUsageDescription " ,
700- " NSBluetoothWhileInUseUsageDescription " ,
701- " NSCalendarsUsageDescription " ,
702- " NSCameraUsageDescription " ,
703- " NSContactsUsageDescription " ,
704- " NSDesktopFolderUsageDescription " ,
705- " NSDocumentsFolderUsageDescription " ,
706- " NSDownloadsFolderUsageDescription " ,
707- " NSFaceIDUsageDescription " ,
708- " NSFallDetectionUsageDescription " ,
709- " NSFileProviderDomainUsageDescription " ,
710- " NSFileProviderPresenceUsageDescription " ,
711- " NSFocusStatusUsageDescription " ,
712- " NSGKFriendListUsageDescription " ,
713- " NSHealthClinicalHealthRecordsShareUsageDescription " ,
714- " NSHealthShareUsageDescription " ,
715- " NSHealthUpdateUsageDescription " ,
716- " NSHomeKitUsageDescription " ,
717- " NSIdentityUsageDescription " ,
718- " NSLocalNetworkUsageDescription " ,
719- " NSLocationAlwaysAndWhenInUseUsageDescription " ,
720- " NSLocationAlwaysUsageDescription " ,
721741 " NSLocationTemporaryUsageDescriptionDictionary " ,
722- " NSLocationUsageDescription " ,
723- " NSLocationWhenInUseUsageDescription " ,
724- " NSMicrophoneUsageDescription " ,
725- " NSMotionUsageDescription " ,
726- " NSNearbyInteractionAllowOnceUsageDescription " ,
727- " NSNearbyInteractionUsageDescription " ,
728- " NSNetworkVolumesUsageDescription " ,
729- " NSPhotoLibraryAddUsageDescription " ,
730- " NSPhotoLibraryUsageDescription " ,
731- " NSRemindersUsageDescription " ,
732- " NSRemovableVolumesUsageDescription " ,
733- " NSSensorKitPrivacyPolicyURL " ,
734- " NSSensorKitUsageDescription " ,
735- " NSSiriUsageDescription " ,
736- " NSSpeechRecognitionUsageDescription " ,
737- " NSSystemAdministrationUsageDescription " ,
738- " NSSystemExtensionUsageDescription " ,
739- " NSUserTrackingUsageDescription " ,
740- " NSVideoSubscriberAccountUsageDescription " ,
741- " NSVoIPUsageDescription " ,
742- " OSBundleUsageDescription " ,
743742
744743 // macOS
745744
@@ -759,6 +758,7 @@ public final class InfoPlistProcessorTaskAction: TaskAction
759758 // iOS
760759
761760 " LSSupportsOpeningDocumentsInPlace " ,
761+ " NSSensorKitPrivacyPolicyURL " ,
762762 " NSSupportsLiveActivities " ,
763763 " NSSupportsLiveActivitiesFrequentUpdates " ,
764764 " UIApplicationSupportsIndirectInputEvents " ,
@@ -791,7 +791,7 @@ public final class InfoPlistProcessorTaskAction: TaskAction
791791 // Sticker Packs
792792
793793 " NSStickerSharingLevel " ,
794- ]
794+ ] + usageDescriptionStringKeys
795795
796796 for key in generatedInfoPlistKeys {
797797 updateContentWithInfoPlistKeyMacroValue ( & content, key)
@@ -1511,9 +1511,27 @@ public final class InfoPlistProcessorTaskAction: TaskAction
15111511 }
15121512 }
15131513
1514+ validateUsageStringDefinitions ( plistDict, outputDelegate: outputDelegate)
15141515 return true
15151516 }
15161517
1518+ private func validateUsageStringDefinitions( _ plistDict: [ String : PropertyListItem ] , outputDelegate: any TaskOutputDelegate ) {
1519+ let usageStringPlistEntries = plistDict. filter { key, _ in
1520+ usageDescriptionStringKeys. contains ( key)
1521+ }
1522+
1523+ for (key, value) in usageStringPlistEntries {
1524+ switch value {
1525+ case . plString( let stringValue) :
1526+ if stringValue. isEmpty {
1527+ outputDelegate. emitWarning ( " The value for \( key) must be a non-empty string. " )
1528+ }
1529+ default :
1530+ outputDelegate. emitWarning ( " The value for \( key) must be of type string, but is \( value. typeDisplayString. withIndefiniteArticle) . " )
1531+ }
1532+ }
1533+ }
1534+
15171535 /// Scans the path for an `PrivacyInfo.xcprivacy` file and returns the `Path` to that file, if found. Otherwise, returns `nil`.
15181536 private func scanForPrivacyFile( at path: Path , fs: any FSProxy ) -> Path ? {
15191537 do {
0 commit comments