dart-lang / native

Dart packages related to FFI and native assets bundling.
BSD 3-Clause "New" or "Revised" License
112 stars 39 forks source link

[native_assets_cli] iOS/MacOS privacy manifest #878

Open dcharkes opened 7 months ago

dcharkes commented 7 months ago

When using something like the camera on iOS/MacOS, the dylib needs to be bundled with a privacy manifest about why the permission is needed.

We need some way to communicate a privacy manifest from build.dart to Flutter.

This could possibly be a new asset type.

We need to check if we need a privacy manifest per dylib or just per Dart package.

dcharkes commented 1 month ago

Discussing with @mosuem we have multiple options that we can pursue with adding new asset types that are connected to existing asset types:

API options

1. NativeCodeAsset.privacyManifest Uri? field

Introduce a NativeCodeAsset.privacyManifest Uri? field

output.addAssets([
  NativeCodeAsset(
    package: 'native_add',
    name: 'native_add',
    linkMode: DynamicLoadingBundled(),
    os: OS.current,
    architecture: Architecture.current,
    file: builtDylibUri,
    privacyManifestFile:
      (OS.current == OS.macOS || OS.current == OS.iOS) ?
      config.packageRoot.resolve('assets/PrivacyInfo.xcprivacy') :
      null,
  ),
]);

2. PrivacyManifestAsset with dylib field

Introduce a PrivacyManifestAsset which implements Asset and has a field dylib which points to a NativeCodeAsset.

(Note the class should not implement/extend DataAsset because the bytes should not be bundled in Dart/Flutter apps and accessible with data assets at runtime.)

output.addAssets([
  NativeCodeAsset(
    package: 'native_add',
    name: 'native_add',
    linkMode: DynamicLoadingBundled(),
    os: OS.current,
    architecture: Architecture.current,
    file: builtDylibUri,
  ),
  if (OS.current == OS.macOS || OS.current == OS.iOS)
    PrivacyManifestAsset(
      // Asset IDs are mandatory, and required to be unique at the moment.
      package: 'native_add',
      name: 'native_add/PrivacyInfo.xcprivacy',
      // Together with `package` defines the asset ID that this privacy manifest belongs to.
      nativeCodeAssetName: 'native_add',
      file: config.packageRoot.resolve('assets/PrivacyInfo.xcprivacy'),
    ),
]);

3. Asset.metadata

Introduce Asset.metadata List<Asset> field. (Name to be discussed) This avoids the need for a PrivacyManifestAsset.dylib field as the PrivacyManifestAsset can be nested in the dylib

output.addAssets([
  NativeCodeAsset(
    package: 'native_add',
    name: 'native_add',
    linkMode: DynamicLoadingBundled(),
    os: OS.current,
    architecture: Architecture.current,
    file: builtDylibUri,
    metadata: [
      if (OS.current == OS.macOS || OS.current == OS.iOS)
        PrivacyManifestAsset(
          // Asset IDs are mandatory, and required to be unique at the moment.
          package: 'native_add',
          name: 'native_add/PrivacyInfo.xcprivacy',
          file: config.packageRoot.resolve('assets/native_add/PrivacyInfo.xcprivacy'),
        ),
    ],
  ),
]);

@mosuem I think I'm leaning towards option 2 now as well.

Class location

We could opt to not specify this asset type in package:hook/package:native_assets_cli. It would enable us to battle-test the extensibility of the protocol.

However, I would also be weary of introducing a new package for every new asset type. It would create churn specifying all the imports for users. (We can't be putting it in package:hook_flutter because once we start supporting it in Dart or another embedder it would have to be moved, and moving classes between packages is a real pain, I tried it once.)

dcharkes commented 1 month ago

cc @HosseinYousefi as well for discussion on how to deal with "assets-that-belong-to-other-assets" or "assets-that-augment-other-assets". ProguardRuleAssets augment JarAssets later on.

mosuem commented 1 month ago

Option2, but in it's own package.

I would try to keep things simple, and have a new asset type for the PrivacyManifestAsset defined in package:privacy_manifest_asset, which also exposes helper methods for creating the asset. Then all that has to be done is implement support for PrivacyManifestAsset in Flutter.

class PrivacyManifestAsset implements Asset{
  final Sting nativeCodeAssetId;
  ...
}

Asset buildPrivacyManifest(NativeCodeAsset library, PrivacyManifestOptions options) { ... }

It would create churn specifying all the imports for users.

What do you mean?

HosseinYousefi commented 1 month ago

In the most general form, the thing we're building could be a graph, not a tree. So using children-like API will not scale. So I like option 2 as well.

and moving classes between packages is a real pain

What if hook_flutter has a dependency on the privacy_manifest_asset but just exports it. The user will transitively depend on privacy_manifest_asset and won't have to import another thing. Later on if the same thing is available on dart standalone, hook itself can export the same class. Would this work?

dcharkes commented 1 month ago

Note from discussion with @mkustermann

Deprioritizing because the list of required reason APIs is quite small.