Skip to content

Support privacy manifests for non-plugin packages calling required reason APIs directly via FFI #147186

Description

@stuartmorgan-g

We have a privacy manifest system in place for plugins that allows plugin developers to declare required reason API usage. However, I didn't consider the case of a non-plugin, pure-Dart package that uses FFI to directly call required reason APIs; we should have a way for packages doing that to declare a manifest and have it bundled into App.framework.

We could probably do this in the Flutter tool, by having it create privacy manifest bundles in App.framework (like what Cocoapods does for static linking). We'd need a way for package developers to declare a manifest that we would bundle; probably a flutter: sub-entry in pubspec.yaml is the best option?

In the meantime, the workaround for this (hopefully very rare) case would be either:

  • As the package developer, declare your package as a plugin instead—the native code can be essentially a no-op—and use the podspec (or later, SPM) to bundle a manifest.
  • As an app developer, include the reasons at the app level.

Activity

  1. added
    toolAffects the "flutter" command-line tool. See also t: labels.
    a: buildBuilding flutter applications with the tool
    fyi-ecosystemFor the attention of Ecosystem team
    team-iosOwned by iOS platform team
    on Apr 22, 2024
  2. jmagman commented on Apr 22, 2024

    @jmagman
    Member

    We'd need a way for package developers to declare a manifest that we would bundle; probably a flutter: sub-entry in pubspec.yaml is the best option?

    It's a big magical, but we could also have some known path (in lib/?) where, if there's a PrivacyInfo.xcprivacy., the tooling automatically bundles it up. Kind of how AppFrameworkInfo.plist is used as the App.framework Info.plist:

    https://github.com/flutter/flutter/blob/master/packages/flutter_tools/templates/app_shared/ios.tmpl/Flutter/AppFrameworkInfo.plist

    flutterProject.ios.appFrameworkInfoPlist
    .copySync(environment.outputDir
    .childDirectory('App.framework')
    .childFile('Info.plist').path);

  3. jmagman commented on Apr 22, 2024

    @jmagman
    Member
  4. dcharkes commented on Apr 23, 2024

    @dcharkes
    Contributor

    One question to answer is whether any other embedders than Flutter would ever need to include such privacy manifest. If the answer is yes, then hook/build.dart should output it as meta-data to the NativeCodeAsset. That way a package with a build hook would work in such other embedder as well. If the answer is no, we can build a Flutter-specific solution.

    Currently, I am not aware of any Dart embedders other than Flutter that end up being published in the Apple App store. Are you aware of any @mit-mit @mariamhas @mraleph?

    Duplicate issue:

  5. stuartmorgan-g commented on Apr 23, 2024

    @stuartmorgan-g
    ContributorAuthor

    Duplicate issue

    It's not a duplicate issue; this one is about the case where someone writes a pure Dart package that directly makes FFI calls to a required reason API.

    then hook/build.dart should output it as meta-data to the NativeCodeAsset.

    In this scenario there is no hook/build.dart, nor any native code assets.

    (Adjusting title to try to make the distinction clearer.)

  6. changed the title [-]Support privacy manifests for non-plugin packages using FFI[/-] [+]Support privacy manifests for non-plugin packages calling required reason APIs directly via FFI[/+] on Apr 23, 2024
  7. dcharkes commented on Apr 23, 2024

    @dcharkes
    Contributor

    Ah, interesting!

    So these would be using DynamicLibrary.process.lookup to access a system API. Do I understand this correctly?

    We could consider saying users should use @Native instead and report a NativeCodeAsset with LookupInProcess from their build.dart when addressing #140966. However, since native assets are currently in experimental, that wouldn't solve it for users currently using DynamicLibrary.process.lookup without the experiment.

  8. stuartmorgan-g commented on Apr 23, 2024

    @stuartmorgan-g
    ContributorAuthor

    So these would be using DynamicLibrary.process.lookup to access a system API. Do I understand this correctly?

    Yes (probably via ffigen or one of the related tools).

    We could consider saying users should use @Native instead and report a NativeCodeAsset with LookupInProcess from their build.dart

    Oh, I guess build.dart doesn't actually require building anything, right? So someone could have have a native asset package whose only native asset was the privacy manifest bundle, and no native code?

    Although it's not clear to me how we would wire to get the right output. In the case envisioned in #140966, my understanding is that we would end up with a TheNativeCodeICompiled.framework, containing a privacy manifest for the RRAPI calls made by that code, which would work out. In this issue's scenario though, the RRAPI calls are (for a Flutter build) in App.framework, so the corresponding privacy manifest must be in App.framework for the App Store validator to accept it. But it doesn't seem like build.dart would have a way to make that happen.

    Or did you mean that we should recommend that nobody ever call, e.g., stat via FFI, and if they really want to call stat they should instead write a trivial my_stat and package it via native assets, and call that? If so, forcing people to add a native build step to their package for basically no reason doesn't seem desireable.

  9. dcharkes commented on Apr 23, 2024

    @dcharkes
    Contributor

    So these would be using DynamicLibrary.process.lookup to access a system API. Do I understand this correctly?

    Yes (probably via ffigen or one of the related tools).

    We could consider saying users should use @Native instead and report a NativeCodeAsset with LookupInProcess from their build.dart

    Oh, I guess build.dart doesn't actually require building anything, right? So someone could have have a native asset package whose only native asset was the privacy manifest bundle, and no native code?

    yes. 👍

    Although it's not clear to me how we would wire to get the right output. In the case envisioned in #140966, my understanding is that we would end up with a TheNativeCodeICompiled.framework, containing a privacy manifest for the RRAPI calls made by that code, which would work out. In this issue's scenario though, the RRAPI calls are (for a Flutter build) in App.framework, so the corresponding privacy manifest must be in App.framework for the App Store validator to accept it. But it doesn't seem like build.dart would have a way to make that happen.

    Well, flutter_tools is the consumer of build.darts output. build.dart only outputs .dylibs and meta data. Flutter Tools can decide whatever it wants to do. Flutter tools is the one taking the dylib and making it a framework.

    If App.framework is the right place, we should thread the privacy manifests from all individual NativeCodeAssets to the App.framework instead of in their individual frameworks.

    Or did you mean that we should recommend that nobody ever call, e.g., stat via FFI, and if they really want to call stat they should instead write a trivial my_stat and package it via native assets, and call that? If so, forcing people to add a native build step to their package for basically no reason doesn't seem desireable.

    No. I'd suggest having a hook/build.dart with:

    void main() async {
      await build(config, output) {
        output.addAsset(
          NativeCodeAsset(
            package: 'my_package',
            name: 'stat.dart',
            linkMode: LookupInProcess(), // Will cause `@Native` to be `DynamicLibrary.process.lookup` at runtime.
            os: config.targetOS,
            architecture: config.targetArchitecture,
            // privacyManifest: ...,
          ),
        );
      });
    }

    And then the code in the package:

    // package:my_package/stat.dart
    
    // Generated by FFIgen with `ffi-native:`
    @Native<Int Function(Pointer<Char>, Pointer<Stat>)>()
    int stat(Pointer<Char> pathname, Pointer<Stat> statbuf);

    This would not do any native build. It's basically only some meta-data communicated back to to Flutter from hook/build.dart.

    Or did you mean that we should recommend that nobody ever call, e.g., stat via FFI.

    I would then indeed recommend nobody call stat via DynamicLibrary.process.lookup. (Both are FFI, I presume you mean DynamicLibrary API with FFI here.)

    I agree that using build.dart with LookupInProcess() is more ceremony than DynamicLibrary.process.lookup. So maybe this isn't the right way to go. On the other hand, we only have to introduce one solution instead of two if we move users to the @Natives instead of DynamicLibrary.

  10. stuartmorgan-g commented on Apr 23, 2024

    @stuartmorgan-g
    ContributorAuthor

    Well, flutter_tools is the consumer of build.darts output. build.dart only outputs .dylibs and meta data. Flutter Tools can decide whatever it wants to do. Flutter tools is the one taking the dylib and making it a framework.

    Ah, okay. I had been thinking build.dart was going to be essentially a black box that would spit out the framework. This makes sense then.

    On the other hand, we only have to introduce one solution instead of two if we move users to the @Natives instead of DynamicLibrary.

    As long as we don't need to actually introduce a native compile step, I'm in favor of fewer solutions :)

  11. added
    P2Important issues not at the top of the work list
    triaged-iosTriaged by iOS platform team
    on Apr 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: buildBuilding flutter applications with the toolteam-iosOwned by iOS platform teamtoolAffects the "flutter" command-line tool. See also t: labels.triaged-iosTriaged by iOS platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions