Skip to content

Commit

Permalink
plisttool app extension validation flags
Browse files Browse the repository at this point in the history
  • Loading branch information
maustinstar committed Apr 16, 2023
1 parent 55d3d70 commit 00ee929
Show file tree
Hide file tree
Showing 3 changed files with 359 additions and 11 deletions.
4 changes: 2 additions & 2 deletions apple/internal/partials/resources.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

"""Partial implementations for resource processing.
Resources are procesed according to type, by a series of methods that deal with the specifics for
Resources are processed according to type, by a series of methods that deal with the specifics for
each resource type. Each of this methods returns a struct, which always have a `files` field
containing resource tuples as described in processor.bzl. Optionally, the structs can also have an
`infoplists` field containing a list of plists that should be merged into the root Info.plist.
Expand Down Expand Up @@ -371,7 +371,7 @@ def resources_partial(
occur.
bundle_name: The name of the output bundle.
executable_name: The name of the output executable.
bundle_verification_targets: List of structs that reference embedable targets that need to
bundle_verification_targets: List of structs that reference embeddable targets that need to
be validated. The structs must have a `target` field with the target containing an
Info.plist file that will be validated. The structs may also have a
`parent_bundle_id_reference` field that contains the plist path, in list form, to the
Expand Down
178 changes: 173 additions & 5 deletions tools/plisttool/plisttool.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
compared against the final compiled plist for consistency. The keys of
the dictionary are the labels of the targets to which the associated
plists belong. See below for the details of how these are validated.
child_plist_required_values: If present, a dictionary constaining the
child_plist_required_values: If present, a dictionary containing the
entries for key/value pairs a child is required to have. This
dictionary is keyed by the label of the child targets (just like the
`child_plists`), and the valures are a list of key/value pairs. The
Expand Down Expand Up @@ -185,10 +185,98 @@
'has the wrong value for "%s"; expected %r, but found %r.'
)

MISSING_VERSION_KEY_MSG = (
MISSING_PLIST_KEY_MSG = (
'Target "%s" is missing %s.'
)

MISSING_PLIST_KEY_IN_PARENT_MSG = (
'Target "%s" is missing %s in dictionary %s.'
)

UNEXPECTED_NSEXTENSION_HELPER = (
'Target %s has an unexpected key, NSExtension, for product type com.apple.product-type.extensionkit-extension. '
'ExtensionKit extensions expect key EXAppExtensionAttributes. '
'To build an app extension without ExtensionKit, set `extensionkit_extension = False`. '
)

UNEXPECTED_EXAPPEXTENSIONATTRIBUTES_HELPER = (
'Target %s has an unexpected key, EXAppExtensionAttributes, for product type com.apple.product-type.app-extension. '
'Plugin extensions expect key NSExtension. '
'To build an app extension with ExtensionKit, set `extensionkit_extension = True`. '
)

KNOWN_EXTENSIONKIT_EXTENSION_POINT_IDENTIFIERS = (
# iOS
'com.apple.appintents-extension',
'com.apple.background-asset-downloader-extension',
'com.apple.deviceactivityui.report-extension',
'com.apple.discovery-extension'
)

KNOWN_NSEXTENSION_WARNING = (
'Target %s with extension point %s is known to be an NSExtension. '
'The target rule may be misconfigured. '
'NSExtension targets expect the attribute `extensionkit_extension = False`.'
)

KNOWN_EXTENSIONKIT_WARNING = (
'Target %s with extension point %s is known to be an ExtensionKit App Extension. '
'The target rule may be misconfigured. '
'ExtensionKit App Extension targets expect the attribute `extensionkit_extension = True`.'
)

KNOWN_NSEXTENSION_EXTENSION_POINT_IDENTIFIERS = (
'com.apple.AppSSO.idp-extension',
'com.apple.AudioUnit',
'com.apple.AudioUnit-UI',
'com.apple.FinderSync',
'com.apple.ManagedSettings.shield-action-service',
'com.apple.ManagedSettingsUI.shield-configuration-service',
'com.apple.Safari.content-blocker',
'com.apple.Safari.extension',
'com.apple.Safari.web-extension',
'com.apple.authentication-services-account-authentication-modification-ui',
'com.apple.authentication-services-credential-provider-ui',
'com.apple.broadcast-services-setupui',
'com.apple.broadcast-services-upload',
'com.apple.calendar.virtualconference',
'com.apple.callkit.call-directory',
'com.apple.classkit.context-provider',
'com.apple.ctk-tokens',
'com.apple.deviceactivity.monitor-extension',
'com.apple.dt.Xcode.extension.source-editor',
'com.apple.email.extension',
'com.apple.fileprovider-actionsui',
'com.apple.fileprovider-nonui',
'com.apple.identitylookup.classification-ui',
'com.apple.identitylookup.message-filter',
'com.apple.intents-service',
'com.apple.intents-ui-service',
'com.apple.keyboard-service',
'com.apple.location.push.service',
'com.apple.matter.support.extension.device-setup',
'com.apple.message-payload-provider',
'com.apple.message-payload-provider',
'com.apple.networkextension.app-proxy',
'com.apple.networkextension.dns-proxy',
'com.apple.networkextension.filter-control',
'com.apple.networkextension.filter-data',
'com.apple.networkextension.packet-tunnel',
'com.apple.photo-editing',
'com.apple.photo-project',
'com.apple.printing.discovery',
'com.apple.quicklook.preview',
'com.apple.quicklook.thumbnail',
'com.apple.services',
'com.apple.share-services',
'com.apple.spotlight.import',
'com.apple.tv-top-shelf',
'com.apple.ui-services',
'com.apple.usernotifications.content-extension',
'com.apple.usernotifications.service',
'com.apple.widgetkit-extension',
)

INVALID_VERSION_KEY_VALUE_MSG = (
'Target "%s" has a %s that doesn\'t meet Apple\'s guidelines: "%s". See '
'https://developer.apple.com/library/content/technotes/tn2420/_index.html'
Expand Down Expand Up @@ -343,8 +431,8 @@

# All valid keys in the info_plist_options control structure.
_INFO_PLIST_OPTIONS_KEYS = frozenset([
'child_plists', 'child_plist_required_values', 'pkginfo', 'version_file',
'version_keys_required',
'child_plists', 'child_plist_required_values', 'extensionkit_keys_required',
'nsextension_keys_required', 'pkginfo', 'version_file', 'version_keys_required',
])

# All valid keys in the entitlements_options control structure.
Expand Down Expand Up @@ -897,11 +985,16 @@ def update_plist(self, out_plist, subs_engine):
out_plist['CFBundleShortVersionString'] = short_version_string

def validate_plist(self, plist):
self.validate_plist_version(plist)
self.validate_plist_extensionkit(plist)
self.validate_plist_nsextension(plist)

def validate_plist_version(self, plist):
if self.options.get('version_keys_required'):
for k in ('CFBundleVersion', 'CFBundleShortVersionString'):
# This also errors if they are there but the empty string or zero.
if not plist.get(k, None):
raise PlistToolError(MISSING_VERSION_KEY_MSG % (self.target, k))
raise PlistToolError(MISSING_PLIST_KEY_MSG % (self.target, k))

# If the version keys are set, they must be valid (even if they were
# not required).
Expand All @@ -928,6 +1021,81 @@ def validate_plist(self, plist):
else:
self._write_pkginfo(pkginfo_file, plist)

def validate_plist_extensionkit(self, plist):
if not self.options.get('extensionkit_keys_required'):
return

# Check for extension point identifiers within any keys, so we can surface useful warnings before errors.
unchecked_extension_point_identifier = self.any_extension_point_identifier(plist)
known_extension_point = False
for known_extension_point_identifier in KNOWN_EXTENSIONKIT_EXTENSION_POINT_IDENTIFIERS:
if unchecked_extension_point_identifier == known_extension_point_identifier:
# This is a known ExtensionKit extension point
known_extension_point = True

if not known_extension_point:
for known_extension_point_identifier in KNOWN_NSEXTENSION_EXTENSION_POINT_IDENTIFIERS:
if unchecked_extension_point_identifier == known_extension_point_identifier:
# This is a known NSExtension extension point, and we can provide a useful warning.
# Don't raise a blocking error, since Apple may change available extension points in the future.
print(KNOWN_NSEXTENSION_WARNING % (self.target, unchecked_extension_point_identifier))

# Explicitly check against None, since an empty dictionary should raise an error
if plist.get('NSExtension', None) is not None:
raise PlistToolError(UNEXPECTED_NSEXTENSION_HELPER % self.target)

self.validate_extension_point_identifier(plist, 'EXAppExtensionAttributes', 'EXExtensionPointIdentifier')

def validate_plist_nsextension(self, plist):
if not self.options.get('nsextension_keys_required'):
return

# Check for extension point identifiers within any keys, so we can surface useful warnings before errors.
unchecked_extension_point_identifier = self.any_extension_point_identifier(plist)
known_extension_point = False
for known_extension_point_identifier in KNOWN_NSEXTENSION_EXTENSION_POINT_IDENTIFIERS:
if unchecked_extension_point_identifier == known_extension_point_identifier:
# This is a known NSExtension extension point
known_extension_point = True

if not known_extension_point:
for known_extension_point_identifier in KNOWN_EXTENSIONKIT_EXTENSION_POINT_IDENTIFIERS:
if unchecked_extension_point_identifier == known_extension_point_identifier:
# This is a known ExtensionKit extension point, and we can provide a useful warning.
# Don't raise a blocking error, since Apple may change available extension points in the future.
print(KNOWN_EXTENSIONKIT_WARNING % (self.target, unchecked_extension_point_identifier))

# Explicitly check against None, since an empty dictionary should raise an error
if plist.get('EXAppExtensionAttributes', None) is not None:
raise PlistToolError(UNEXPECTED_EXAPPEXTENSIONATTRIBUTES_HELPER % self.target)

self.validate_extension_point_identifier(plist, 'NSExtension', 'NSExtensionPointIdentifier')

# Any recognized extension point identifier, which may not exist in the correct key
def any_extension_point_identifier(self, plist):
nsextension_parent = plist.get('NSExtension', None)
if nsextension_parent:
nsextension_point_identifier = nsextension_parent.get('NSExtensionPointIdentifier')
if nsextension_point_identifier:
return nsextension_point_identifier

exappextension_parent = plist.get('EXAppExtensionAttributes', None)
if exappextension_parent:
exappextension_point_identifier = exappextension_parent.get('EXExtensionPointIdentifier')
if exappextension_point_identifier:
return exappextension_point_identifier

return None

def validate_extension_point_identifier(self, plist, parent_key, extension_point_identifier_key):
parent = plist.get(parent_key, None)
if not parent:
raise PlistToolError(MISSING_PLIST_KEY_MSG % (self.target, parent_key))

extension_point_identifier = parent.get(extension_point_identifier_key, None)
if not extension_point_identifier:
raise PlistToolError(MISSING_PLIST_KEY_IN_PARENT_MSG % (self.target, extension_point_identifier_key, parent_key))

@staticmethod
def _validate_children(plist, child_plists, child_required_values, target):
"""Validates a target's plist is consistent with its children.
Expand Down
Loading

0 comments on commit 00ee929

Please sign in to comment.