diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index dc9bbbb68c3..92673b02b17 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -209,9 +209,20 @@ List _xcodeBuildSettingsLines({ if (globals.artifacts is LocalEngineArtifacts) { final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts; - final String engineOutPath = localEngineArtifacts.engineOutPath; + final String engineOutPath = globals.fs.path.basename(localEngineArtifacts.engineOutPath); + String engineBuildMode = 'release'; + if (engineOutPath.toLowerCase().contains('debug')) { + engineBuildMode = 'debug'; + } else if (engineOutPath.toLowerCase().contains('profile')) { + engineBuildMode = 'profile'; + } xcodeBuildSettings.add('FLUTTER_ENGINE=${globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath))}'); - xcodeBuildSettings.add('LOCAL_ENGINE=${globals.fs.path.basename(engineOutPath)}'); + xcodeBuildSettings.add('LOCAL_ENGINE=$engineOutPath'); + // Only write this or local engines, where it is supposed to be sticky to + // match the engine configuration. Avoid writing it otherwise so that it + // does not stick the user with the wrong build mode, particularly for + // existing app use cases. + xcodeBuildSettings.add('FLUTTER_BUILD_MODE=$engineBuildMode'); // Tell Xcode not to build universal binaries for local engines, which are // single-architecture. diff --git a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart index 233e9d09528..0e990a8ba5e 100644 --- a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart @@ -433,22 +433,24 @@ Information about project "Runner": }); group('updateGeneratedXcodeProperties', () { - MockLocalEngineArtifacts mockArtifacts; + MockLocalEngineArtifacts mockEngineArtifacts; + MockArtifacts mockArtifacts; MockProcessManager mockProcessManager; FakePlatform macOS; FileSystem fs; setUp(() { fs = MemoryFileSystem(); - mockArtifacts = MockLocalEngineArtifacts(); + mockEngineArtifacts = MockLocalEngineArtifacts(); + mockArtifacts = MockArtifacts(); mockProcessManager = MockProcessManager(); macOS = fakePlatform('macos'); fs.file(xcodebuild).createSync(recursive: true); }); - void testUsingOsxContext(String description, dynamic testMethod()) { + void testUsingOsxContext(String description, dynamic testMethod(), {bool isLocalEngine = true}) { testUsingContext(description, testMethod, overrides: { - Artifacts: () => mockArtifacts, + Artifacts: () => isLocalEngine ? mockEngineArtifacts : mockArtifacts, Platform: () => macOS, FileSystem: () => fs, ProcessManager: () => mockProcessManager, @@ -507,9 +509,9 @@ Information about project "Runner": }); testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () async { - when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, + when(mockEngineArtifacts.getArtifactPath(Artifact.flutterFramework, platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); + when(mockEngineArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, treeShakeIcons: false); final FlutterProject project = FlutterProject.fromPath('path/to/project'); @@ -531,10 +533,59 @@ Information about project "Runner": expect(buildPhaseScriptContents.contains('ARCHS=armv7'), isTrue); }); - testUsingOsxContext('sets TRACK_WIDGET_CREATION=true when trackWidgetCreation is true', () async { + testUsingOsxContext('sets FLUTTER_BUILD_MODE local engine is set', () async { + when(mockEngineArtifacts.getArtifactPath(Artifact.flutterFramework, + platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); + when(mockEngineArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); + + const BuildInfo buildInfo = BuildInfo(BuildMode.profile, null, treeShakeIcons: false); + final FlutterProject project = FlutterProject.fromPath('path/to/project'); + await updateGeneratedXcodeProperties( + project: project, + buildInfo: buildInfo, + ); + + final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig'); + expect(config.existsSync(), isTrue); + + final String contents = config.readAsStringSync(); + expect(contents, contains('FLUTTER_BUILD_MODE=profile')); + + final File buildPhaseScript = fs.file('path/to/project/ios/Flutter/flutter_export_environment.sh'); + expect(buildPhaseScript.existsSync(), isTrue); + + final String buildPhaseScriptContents = buildPhaseScript.readAsStringSync(); + expect(buildPhaseScriptContents, contains('FLUTTER_BUILD_MODE=profile')); + }); + + testUsingOsxContext('does not set FLUTTER_BUILD_MODE without local engine', () async { when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); + + const BuildInfo buildInfo = BuildInfo(BuildMode.profile, null, treeShakeIcons: false); + final FlutterProject project = FlutterProject.fromPath('path/to/project'); + await updateGeneratedXcodeProperties( + project: project, + buildInfo: buildInfo, + ); + + final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig'); + expect(config.existsSync(), isTrue); + + final String contents = config.readAsStringSync(); + expect(contents, isNot(contains('FLUTTER_BUILD_MODE='))); + + final File buildPhaseScript = fs.file('path/to/project/ios/Flutter/flutter_export_environment.sh'); + expect(buildPhaseScript.existsSync(), isTrue); + + final String buildPhaseScriptContents = buildPhaseScript.readAsStringSync(); + expect(buildPhaseScriptContents, isNot(contains('FLUTTER_BUILD_MODE='))); + }, isLocalEngine: false); + + testUsingOsxContext('sets TRACK_WIDGET_CREATION=true when trackWidgetCreation is true', () async { + when(mockEngineArtifacts.getArtifactPath(Artifact.flutterFramework, + platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); + when(mockEngineArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, trackWidgetCreation: true, treeShakeIcons: false); final FlutterProject project = FlutterProject.fromPath('path/to/project'); await updateGeneratedXcodeProperties( @@ -556,9 +607,9 @@ Information about project "Runner": }); testUsingOsxContext('does not set TRACK_WIDGET_CREATION when trackWidgetCreation is false', () async { - when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, + when(mockEngineArtifacts.getArtifactPath(Artifact.flutterFramework, platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); + when(mockEngineArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, treeShakeIcons: false); final FlutterProject project = FlutterProject.fromPath('path/to/project'); await updateGeneratedXcodeProperties( @@ -580,9 +631,9 @@ Information about project "Runner": }); testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () async { - when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, + when(mockEngineArtifacts.getArtifactPath(Artifact.flutterFramework, platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile')); + when(mockEngineArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile')); const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, treeShakeIcons: false); final FlutterProject project = FlutterProject.fromPath('path/to/project'); @@ -613,9 +664,9 @@ Information about project "Runner": String expectedBuildName, String expectedBuildNumber, }) async { - when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, + when(mockEngineArtifacts.getArtifactPath(Artifact.flutterFramework, platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); - when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios')); + when(mockEngineArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios')); final File manifestFile = fs.file('path/to/project/pubspec.yaml'); manifestFile.createSync(recursive: true); @@ -809,3 +860,4 @@ class MockAnsiTerminal extends Mock implements AnsiTerminal { @override bool get supportsColor => false; } +class MockArtifacts extends Mock implements Artifacts {}