Skip to content

Conversation

@jmagman
Copy link
Member

@jmagman jmagman commented Sep 1, 2020

Description

Add NSBonjourServices _dartobservatory._tcp and NSLocalNetworkUsageDescription to debug and profile Info.plist in the produced, bundled .app (not the user-facing Info.plist created in the template and included in Xcode).

This prompts the "Your app would like to find and connect to devices on your local network" permission popup, which is required to unblock the observatory publisher on iOS 14.

NSLocalNetworkUsageDescription is underneath:

flutter_01

Note: this does not add the keys for add-to-app host apps, so they still have the issue of blocking the observatory publisher.

Related Issues

Fixes #60634
Fixes #35707.
Related to conversation at #63893, but that is for add-to-app.

Tests

Updated ios_content_validation_test to validate Release does not have the keys, but Debug does.

Kudos

Thanks to @gaaclarke for suggesting to just add the keys in the generated bundle Info.plist, and not the one that's created in the template. That avoided a huge migration headache.

Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I signed the [CLA].
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

  • No, no existing tests failed, so this is not a breaking change.
  • Yes, this is a breaking change. If not, delete the remainder of this section.

@jmagman jmagman added platform-ios iOS applications specifically t: xcode "xcodebuild" on iOS and general Xcode project management labels Sep 1, 2020
@jmagman jmagman self-assigned this Sep 1, 2020
@flutter-dashboard flutter-dashboard bot added c: contributor-productivity Team-specific productivity, code health, technical debt. tool Affects the "flutter" command-line tool. See also t: labels. labels Sep 1, 2020
}
});

if (foundProjectName) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved these failures to where they are detected, instead of the end of the test.

EchoError "========================================================================"
exit -1;;
esac
retval="${build_mode}"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but is there a more idiomatic way to return a string from a bash function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


local bitcode_flag=""
if [[ $ENABLE_BITCODE == "YES" ]]; then
if [[ "$ENABLE_BITCODE" == "YES" ]]; then
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this change, but I noticed the quotes were missing.

Comment on lines 373 to 374
"observatory_bonjour_service")
AddObservatoryBonjourService ;;
Copy link
Member Author

@jmagman jmagman Sep 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new xcode_backend entrypoint. At the least, we can tell add-to-app customers to call this in a new build script phase.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would rather not add this for add-to-app.

Those customers should be sophisticated enough to read an error log and know which plist(s) to update.

This is likely to get things wrong for some customer and we'll get requests to change it or a specific CI system or wahtever.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about .frameworks? Will those cases never work unless users type in something manually in their real checked-in info.plist?

Copy link
Member Author

@jmagman jmagman Sep 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those customers should be sophisticated enough to read an error log and know which plist(s) to update.

How would they see an error message from the VM if they can't attach? Edit: oh right, they would see it in Xcode.
It definitely requires some Xcode-foo to know you can edit the Info.plist per configuration. You need to know how to expand the build setting, which people seem confused by.
Screen Shot 2020-09-01 at 12 16 17 AM
Or they can do something like Info-${CONFIGURATION}.plist
We'll have to update the docs, for sure.

Will those cases never work unless users type in something manually in their real checked-in info.plist?

Right, they would need to edit their plist.

At least in the CocoaPods case they can add the build phase I suggested in #63893 (comment)

@jonahwilliams
Copy link
Contributor

Why do this in bash and not in dart?

# Don't override the local network description the Flutter app developer specified (uncommon).
# This text will appear below the "Your app would like to find and connect to devices on your local network" permissions popup.
if ! plutil -extract NSLocalNetworkUsageDescription xml1 -o - "${built_products_plist}"; then
RunCommand plutil -insert NSLocalNetworkUsageDescription -string "Allow Flutter tools on your computer to connect and debug your Flutter application. This prompt will not appear on release builds." "${built_products_plist}"
Copy link
Member Author

@jmagman jmagman Sep 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out the screenshot in the description. We can probably remove the second "Flutter". Maybe also "connect"?

Allow Flutter tools on your computer to debug your application. This prompt will not appear on release builds.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the second connect. I'd maybe leave it. Otherwise people might think I'm just flutter running and then maybe taking a screenshot or something. I'm not technically "debugging", why doesn't it work?

On the second Flutter, don't have a strong opinion. To be specific, we're not really debugging their (objective-c) application. This is strictly dart/observatory/flutter stuff.

await flutter('clean');
});

section('Validate debug contents');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is new.

),
],
canFail: true,
) == 0;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't really care what it says since that seems like a moving target, just that it exists.

@jmagman
Copy link
Member Author

jmagman commented Sep 1, 2020

Why do this in bash and not in dart?

@jonahwilliams We don't do any of the post-assemble app bundling in dart (embedding frameworks, codesigning, thinning). Are you suggesting a new flutter command? Or a new xcode_backend.dart script maybe?

@@ -0,0 +1,177 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
Copy link
Member Author

@jmagman jmagman Sep 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was moved from general.shard, it's not a hermetic test.

await expectXcodeBackendFails(unknownFlutterBuildMode);
await expectXcodeBackendFails(localEngineDebugBuildModeRelease);
await expectXcodeBackendFails(localEngineProfileBuildeModeRelease);
});
Copy link
Member Author

@jmagman jmagman Sep 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These were disabled in #35707, but as an integration test they should get a precache, so I fixed them up and reenabled.

await expectXcodeBackendFails(localEngineProfileBuildeModeRelease);
});

test('Xcode backend warns archiving a non-release build.', () async {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is new, the behavior changed #57688 but the installWithoutRelease test was being skipped so it didn't fail.

if [[ "${build_mode}" == "release" ]]; then
return
fi
local built_products_plist="${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not the same file, that's the project's ios/Runner/Info.plist (build setting INFOPLIST_FILE), and that's checked into source code, and where the docs tell users to update some fields before they submit to the app store.

This PR is editing the Xcode-generated Info.plist that gets embedded into the Runner.app.
After Xcode compiles everything and it embeds resources (storyboards, images, etc), it creates a Runner.app/Info.plist, which is a combination what's in the user's Info.plist in Xcode, plus a bunch of other Xcode-generated keys and metadata
Screen Shot 2020-09-01 at 12 05 55 AM

By editing this final generated Info.plist, we don't need to update the user's checked in Info.plist, update the template, or have a complicated migration where we set different Info.plists depending on configuration, which would cause flavor-users a bunch of pain. We know the user needs this service in debug and profile when copied to the device, and it also means it won't slip into the release version that gets submitted to the App Store.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it matter which Plist these permissions are added to, or could it be done to the flutter framework plist itself?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs to be on the app bundle, so Runner.app/Info.plist. A framework can't set app-level permission requests or entitlements. Otherwise this problem would be a whole lot simpler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

understood.

@dnfield
Copy link
Contributor

dnfield commented Sep 1, 2020

For the add to app case, can't we just log an appropriate error with instructions on how to update the plist in FlutterObservatoryPublisher.mm if the returncode is kDnsServiceErr_NoAuth?

Copy link
Member

@xster xster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Though I'd like to figure out a workaround plan for add-to-app.


Future<bool> localNetworkUsageFound(String appBundlePath) async =>
await exec(
'plutil',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh lol, TIL. So there's an actual built-in tool to turn plists to things like json so it can be more easily consumed by things like dart?

Copy link
Member Author

@jmagman jmagman Sep 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, it's being used for other plist parsing, see

executable, '-convert', 'json', '-o', '-', normalizedPlistPath,
Except the json part doesn't totally work... #62160

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh lol, sad

}

ParseFlutterBuildMode() {
# Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh cool, is this a whole new feature (that confused matt forever) bundled here? :D

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been in there for .. a while now.

Basically, we try to use the $CONFIGURATION because that's probably what the user wants if they're running from Xcode (in particular, we don't want instruments tests from debug mode Dart, and we set up instruments to run in a "PROFILE" configuration by default).

But we do let a user override it if necessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this logic and error messaging was just moved into a common function so I could use it in two places.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EchoError "ERROR: Unknown FLUTTER_BUILD_MODE: ${build_mode}."
EchoError "Valid values are 'Debug', 'Profile', or 'Release' (case insensitive)."
EchoError "This is controlled by the FLUTTER_BUILD_MODE environment variable."
EchoError "If that is not set, the CONFIGURATION environment variable is used."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add a common name (as seen in the xcode gui) for this? People might not know what the CONFIGURATION setting maps to.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was all just moved.

EchoError ""
EchoError "You can fix this by either adding an appropriately named build"
EchoError "configuration, or adding an appropriate value for FLUTTER_BUILD_MODE to the"
EchoError ".xcconfig file for the current build configuration (${CONFIGURATION})."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add a gui description if possible (I assume people can go into the build settings tab, add a new row, expand the configurations and type in something in xcode).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CONFIGURATION is an env var provided by Xcode. It's available in the build settings if you dig into them. There's no special GUI name.

By default anXcode project has Debug and Release. We added profile to our default template when this was added.

RunCommand codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/Flutter.framework/Flutter"
fi

AddObservatoryBonjourService
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come this works here and not for add-to-app? Because our template build phase puts xcode_backend.sh later than where cocoapods can help us put it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because our template build phase puts xcode_backend.sh later than where cocoapods can help us put it?

Bingo. I put some more details in #63893 (comment).


# Don't override the local network description the Flutter app developer specified (uncommon).
# This text will appear below the "Your app would like to find and connect to devices on your local network" permissions popup.
if ! plutil -extract NSLocalNetworkUsageDescription xml1 -o - "${built_products_plist}"; then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Display an error message otherwise?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the developer sets their own NSLocalNetworkUsageDescription because their app is doing their own local networking we don't override it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's ok. I just meant if that's the case, we should print something in the console to indicate that you'll get a popup in debug and we can't set the message in it but it's for flutter tools to connect.

# Don't override the local network description the Flutter app developer specified (uncommon).
# This text will appear below the "Your app would like to find and connect to devices on your local network" permissions popup.
if ! plutil -extract NSLocalNetworkUsageDescription xml1 -o - "${built_products_plist}"; then
RunCommand plutil -insert NSLocalNetworkUsageDescription -string "Allow Flutter tools on your computer to connect and debug your Flutter application. This prompt will not appear on release builds." "${built_products_plist}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the second connect. I'd maybe leave it. Otherwise people might think I'm just flutter running and then maybe taking a screenshot or something. I'm not technically "debugging", why doesn't it work?

On the second Flutter, don't have a strong opinion. To be specific, we're not really debugging their (objective-c) application. This is strictly dart/observatory/flutter stuff.

Comment on lines 373 to 374
"observatory_bonjour_service")
AddObservatoryBonjourService ;;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about .frameworks? Will those cases never work unless users type in something manually in their real checked-in info.plist?

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I think about this, the more I'm opposed to adding more "API surface" to our dirty little bash script :)

Is there some way we could avoid having a new "method" on there that we tell people to call?

@jmagman
Copy link
Member Author

jmagman commented Sep 1, 2020

For the add to app case, can't we just log an appropriate error with instructions on how to update the plist in FlutterObservatoryPublisher.mm if the returncode is kDnsServiceErr_NoAuth?

That's definitely a good idea.

@jmagman
Copy link
Member Author

jmagman commented Sep 1, 2020

The more I think about this, the more I'm opposed to adding more "API surface" to our dirty little bash script :)

Is there some way we could avoid having a new "method" on there that we tell people to call?

The more I think about this, the more I'm opposed to adding more "API surface" to our dirty little bash script :)

Is there some way we could avoid having a new "method" on there that we tell people to call?

If we decide not to allow the workaround build script I suggested at the end of #63893 (comment), we can remove this "method" but still keep the plutil logic in EmbedFlutterFrameworks.

@xster
Copy link
Member

xster commented Sep 1, 2020

The more I think about this, the more I'm opposed to adding more "API surface" to our dirty little bash script :)

How is this different from us having a bunch of methods in our podhelper.rb we ask people to call?

I think having dart files xcode phases calling into would be cooler but I don't know if this is particularly bad.

Maybe your comment is just saying inline it into the existing function body which is fine too.

Copy link
Member

@gaaclarke gaaclarke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many cooks in the kitchen so I just gave it a quick glance through with a couple of notes. I raised a question about a tweak to this approach that might be better suited for add-to-app here: #63893 (comment)

return !emptyBitcodeMarkerFound;
}

Future<bool> bonjourServiceFound(String appBundlePath) async =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we call this dartObservatoryBonjourServiceFound? or take in _dartobservatory._tcp as a parameter? From the callsite it just looks like we are throwing if any bonjour service is found.

if (await bonjourServiceFound(outputAppPath)) {
throw TaskResult.failure('Release bundle has unexpected NSBonjourServices');
}
if (await localNetworkUsageFound(outputAppPath)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this throwing an error if a user legitimately has their own mdns service for their release build? We want to make sure to support that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an integration test, it doesn't have any mDNS service set. The xcode_build script handles that case, it detects if the NSBounjourServices key is set, and adds it to the list.

Copy link
Contributor

@jonahwilliams jonahwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Good that we have test coverage for the new stuff, even though it is bash I think that should help substantially with the maintainability.

@jmagman
Copy link
Member Author

jmagman commented Sep 2, 2020

The more I think about this, the more I'm opposed to adding more "API surface" to our dirty little bash script :)

Is there some way we could avoid having a new "method" on there that we tell people to call?

@dnfield I kept the endpoint for the integration tests but renamed to test_observatory_bonjour_service with a comment that you're a jerk if you try to actually use it.

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jmagman jmagman merged commit 4fde217 into flutter:master Sep 2, 2020
@jmagman jmagman deleted the bonjour-service branch September 2, 2020 18:11
jmagman added a commit that referenced this pull request Sep 2, 2020
jmagman added a commit that referenced this pull request Sep 2, 2020
jmagman added a commit that referenced this pull request Sep 2, 2020
jmagman added a commit to jmagman/flutter that referenced this pull request Sep 3, 2020
mingwandroid pushed a commit to mingwandroid/flutter that referenced this pull request Sep 6, 2020
mingwandroid pushed a commit to mingwandroid/flutter that referenced this pull request Sep 6, 2020
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

c: contributor-productivity Team-specific productivity, code health, technical debt. platform-ios iOS applications specifically t: xcode "xcodebuild" on iOS and general Xcode project management tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

6 participants