| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:file/memory.dart'; |
| import 'package:file_testing/file_testing.dart'; |
| import 'package:flutter_tools/src/artifacts.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/base/logger.dart'; |
| import 'package:flutter_tools/src/base/process.dart'; |
| import 'package:flutter_tools/src/base/version.dart'; |
| import 'package:flutter_tools/src/build_info.dart'; |
| import 'package:flutter_tools/src/build_system/build_system.dart'; |
| import 'package:flutter_tools/src/cache.dart'; |
| import 'package:flutter_tools/src/features.dart'; |
| import 'package:flutter_tools/src/flutter_manifest.dart'; |
| import 'package:flutter_tools/src/ios/xcodeproj.dart'; |
| import 'package:flutter_tools/src/project.dart'; |
| import 'package:test/fake.dart'; |
| |
| import '../src/common.dart'; |
| import '../src/context.dart'; |
| import '../src/fake_process_manager.dart'; |
| import '../src/fakes.dart'; |
| |
| void main() { |
| group('IosProject', () { |
| testWithoutContext('managedDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect(project.managedDirectory.path, 'app_name/ios/Flutter'); |
| }); |
| |
| testWithoutContext('module managedDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs, isModule: true)); |
| expect(project.managedDirectory.path, 'app_name/.ios/Flutter'); |
| }); |
| |
| testWithoutContext('ephemeralDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect(project.ephemeralDirectory.path, 'app_name/ios/Flutter/ephemeral'); |
| }); |
| |
| testWithoutContext('module ephemeralDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs, isModule: true)); |
| expect(project.ephemeralDirectory.path, 'app_name/.ios/Flutter/ephemeral'); |
| }); |
| |
| testWithoutContext('flutterSwiftPackagesDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect(project.flutterSwiftPackagesDirectory.path, 'app_name/ios/Flutter/ephemeral/Packages'); |
| }); |
| |
| testWithoutContext('relativeSwiftPackagesDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect( |
| project.relativeSwiftPackagesDirectory.path, |
| 'app_name/ios/Flutter/ephemeral/Packages/.packages', |
| ); |
| }); |
| |
| testWithoutContext('flutterFrameworkSwiftPackageDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect( |
| project.flutterFrameworkSwiftPackageDirectory.path, |
| 'app_name/ios/Flutter/ephemeral/Packages/.packages/FlutterFramework', |
| ); |
| }); |
| |
| testWithoutContext('flutterPluginSwiftPackageDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect( |
| project.flutterPluginSwiftPackageDirectory.path, |
| 'app_name/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage', |
| ); |
| }); |
| |
| testWithoutContext('module flutterPluginSwiftPackageDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs, isModule: true)); |
| expect( |
| project.flutterPluginSwiftPackageDirectory.path, |
| 'app_name/.ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage', |
| ); |
| }); |
| |
| testWithoutContext('xcodeConfigFor', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect(project.xcodeConfigFor('Debug').path, 'app_name/ios/Flutter/Debug.xcconfig'); |
| }); |
| |
| testWithoutContext('lldbInitFile', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect(project.lldbInitFile.path, 'app_name/ios/Flutter/ephemeral/flutter_lldbinit'); |
| }); |
| |
| testWithoutContext('lldbHelperPythonFile', () { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect( |
| project.lldbHelperPythonFile.path, |
| 'app_name/ios/Flutter/ephemeral/flutter_lldb_helper.py', |
| ); |
| }); |
| |
| group('projectInfo', () { |
| testUsingContext('is null if XcodeProjectInterpreter is null', () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| project.xcodeProject.createSync(recursive: true); |
| expect(await project.projectInfo(), isNull); |
| }, overrides: <Type, Generator>{XcodeProjectInterpreter: () => null}); |
| |
| testUsingContext( |
| 'is null if XcodeProjectInterpreter is not installed', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| project.xcodeProject.createSync(recursive: true); |
| expect(await project.projectInfo(), isNull); |
| }, |
| overrides: <Type, Generator>{ |
| XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(isInstalled: false), |
| }, |
| ); |
| |
| testUsingContext( |
| 'is null if xcodeproj does not exist', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect(await project.projectInfo(), isNull); |
| }, |
| overrides: <Type, Generator>{XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter()}, |
| ); |
| |
| testUsingContext( |
| 'returns XcodeProjectInfo', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| project.xcodeProject.createSync(recursive: true); |
| expect(await project.projectInfo(), isNotNull); |
| }, |
| overrides: <Type, Generator>{XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter()}, |
| ); |
| }); |
| |
| testUsingContext('schemeForBuildInfo succeeds', () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| project.xcodeProject.createSync(recursive: true); |
| const BuildInfo buildInfo = BuildInfo.debug; |
| expect(await project.schemeForBuildInfo(buildInfo), 'Runner'); |
| }, overrides: <Type, Generator>{XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter()}); |
| |
| testUsingContext('schemeForBuildInfo returns null if unable to find project', () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| const BuildInfo buildInfo = BuildInfo.debug; |
| expect(await project.schemeForBuildInfo(buildInfo), isNull); |
| }, overrides: <Type, Generator>{XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter()}); |
| |
| testUsingContext( |
| 'schemeForBuildInfo succeeds with flavor', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| project.xcodeProject.createSync(recursive: true); |
| const buildInfo = BuildInfo( |
| BuildMode.debug, |
| 'my_flavor', |
| treeShakeIcons: true, |
| packageConfigPath: '', |
| ); |
| expect(await project.schemeForBuildInfo(buildInfo), 'my_flavor'); |
| }, |
| overrides: <Type, Generator>{ |
| XcodeProjectInterpreter: () => |
| FakeXcodeProjectInterpreter(schemes: ['Runner', 'my_flavor']), |
| }, |
| ); |
| |
| testUsingContext( |
| 'schemeForBuildInfo throws error if flavor is not found', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| project.xcodeProject.createSync(recursive: true); |
| const buildInfo = BuildInfo( |
| BuildMode.debug, |
| 'invalid_flavor', |
| treeShakeIcons: true, |
| packageConfigPath: '', |
| ); |
| await expectLater(project.schemeForBuildInfo(buildInfo), throwsToolExit()); |
| }, |
| overrides: <Type, Generator>{ |
| XcodeProjectInterpreter: () => |
| FakeXcodeProjectInterpreter(schemes: ['Runner', 'my_flavor']), |
| }, |
| ); |
| |
| group('usesSwiftPackageManager', () { |
| testUsingContext( |
| 'is true when iOS project exists', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| projectDirectory.childDirectory('ios').createSync(recursive: true); |
| final FlutterManifest manifest = FakeFlutterManifest(); |
| final project = FlutterProject(projectDirectory, manifest, manifest); |
| expect(project.ios.usesSwiftPackageManager, isTrue); |
| }, |
| overrides: <Type, Generator>{ |
| FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), |
| XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), |
| }, |
| ); |
| |
| testUsingContext( |
| "is false when iOS project doesn't exist", |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| final FlutterManifest manifest = FakeFlutterManifest(); |
| final project = FlutterProject(projectDirectory, manifest, manifest); |
| expect(project.ios.usesSwiftPackageManager, isFalse); |
| }, |
| overrides: <Type, Generator>{ |
| FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), |
| XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), |
| }, |
| ); |
| |
| testUsingContext( |
| 'is false when Xcode is less than 15', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| projectDirectory.childDirectory('ios').createSync(recursive: true); |
| final FlutterManifest manifest = FakeFlutterManifest(); |
| final project = FlutterProject(projectDirectory, manifest, manifest); |
| expect(project.ios.usesSwiftPackageManager, isFalse); |
| }, |
| overrides: <Type, Generator>{ |
| FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), |
| XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(14, 0, 0)), |
| }, |
| ); |
| |
| testUsingContext( |
| 'is false when Swift Package Manager feature is not enabled', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| projectDirectory.childDirectory('ios').createSync(recursive: true); |
| final FlutterManifest manifest = FakeFlutterManifest(); |
| final project = FlutterProject(projectDirectory, manifest, manifest); |
| expect(project.ios.usesSwiftPackageManager, isFalse); |
| }, |
| overrides: <Type, Generator>{ |
| FeatureFlags: () => TestFeatureFlags(), |
| XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), |
| }, |
| ); |
| |
| testUsingContext( |
| 'is false when project is a module', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| projectDirectory.childDirectory('ios').createSync(recursive: true); |
| final FlutterManifest manifest = FakeFlutterManifest(isModule: true); |
| final project = FlutterProject(projectDirectory, manifest, manifest); |
| expect(project.ios.usesSwiftPackageManager, isFalse); |
| }, |
| overrides: <Type, Generator>{ |
| FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), |
| XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), |
| }, |
| ); |
| }); |
| |
| group('parseFlavorFromConfiguration', () { |
| testWithoutContext('from FLAVOR when CONFIGURATION is null', () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| final env = Environment.test( |
| fs.currentDirectory, |
| fileSystem: fs, |
| logger: BufferLogger.test(), |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| defines: <String, String>{kFlavor: 'strawberry'}, |
| ); |
| expect(await project.parseFlavorFromConfiguration(env), 'strawberry'); |
| }); |
| |
| testWithoutContext('from FLAVOR when CONFIGURATION is does not contain delimiter', () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| final env = Environment.test( |
| fs.currentDirectory, |
| fileSystem: fs, |
| logger: BufferLogger.test(), |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| defines: <String, String>{kFlavor: 'strawberry', kXcodeConfiguration: 'Debug'}, |
| ); |
| expect(await project.parseFlavorFromConfiguration(env), 'strawberry'); |
| }); |
| |
| testUsingContext( |
| 'from CONFIGURATION when has flavor following a hyphen that matches a scheme', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| final env = Environment.test( |
| fs.currentDirectory, |
| fileSystem: fs, |
| logger: BufferLogger.test(), |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| defines: <String, String>{kFlavor: 'strawberry', kXcodeConfiguration: 'Debug-vanilla'}, |
| ); |
| project.xcodeProject.createSync(recursive: true); |
| expect(await project.parseFlavorFromConfiguration(env), 'vanilla'); |
| }, |
| overrides: <Type, Generator>{ |
| XcodeProjectInterpreter: () => |
| FakeXcodeProjectInterpreter(schemes: <String>['Runner', 'vanilla']), |
| }, |
| ); |
| |
| testUsingContext( |
| 'from CONFIGURATION when has flavor following a space that matches a scheme', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| final env = Environment.test( |
| fs.currentDirectory, |
| fileSystem: fs, |
| logger: BufferLogger.test(), |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| defines: <String, String>{kFlavor: 'strawberry', kXcodeConfiguration: 'Debug vanilla'}, |
| ); |
| project.xcodeProject.createSync(recursive: true); |
| expect(await project.parseFlavorFromConfiguration(env), 'vanilla'); |
| }, |
| overrides: <Type, Generator>{ |
| XcodeProjectInterpreter: () => |
| FakeXcodeProjectInterpreter(schemes: <String>['Runner', 'vanilla']), |
| }, |
| ); |
| |
| testUsingContext( |
| 'from FLAVOR when CONFIGURATION does not match a scheme', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| final env = Environment.test( |
| fs.currentDirectory, |
| fileSystem: fs, |
| logger: BufferLogger.test(), |
| artifacts: Artifacts.test(), |
| processManager: FakeProcessManager.any(), |
| defines: <String, String>{kFlavor: 'strawberry', kXcodeConfiguration: 'Debug-random'}, |
| ); |
| project.xcodeProject.createSync(recursive: true); |
| expect(await project.parseFlavorFromConfiguration(env), 'strawberry'); |
| }, |
| overrides: <Type, Generator>{ |
| XcodeProjectInterpreter: () => |
| FakeXcodeProjectInterpreter(schemes: <String>['Runner', 'vanilla']), |
| }, |
| ); |
| }); |
| |
| group('ensureReadyForPlatformSpecificTooling', () { |
| group('lldb files are generated', () { |
| testUsingContext('when they are missing', () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| projectDirectory.childDirectory('ios').createSync(recursive: true); |
| final FlutterManifest manifest = FakeFlutterManifest(); |
| final flutterProject = FlutterProject(projectDirectory, manifest, manifest); |
| final project = IosProject.fromFlutter(flutterProject); |
| expect(project.lldbInitFile, isNot(exists)); |
| expect(project.lldbHelperPythonFile, isNot(exists)); |
| |
| await project.ensureReadyForPlatformSpecificTooling(); |
| |
| expect(project.lldbInitFile, exists); |
| expect(project.lldbHelperPythonFile, exists); |
| }, overrides: <Type, Generator>{Cache: () => FakeCache(olderThanToolsStamp: true)}); |
| |
| testUsingContext('when they are older than tool', () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| projectDirectory.childDirectory('ios').createSync(recursive: true); |
| final FlutterManifest manifest = FakeFlutterManifest(); |
| final flutterProject = FlutterProject(projectDirectory, manifest, manifest); |
| final project = IosProject.fromFlutter(flutterProject); |
| project.lldbInitFile.createSync(recursive: true); |
| project.lldbInitFile.writeAsStringSync('old'); |
| project.lldbHelperPythonFile.createSync(recursive: true); |
| project.lldbHelperPythonFile.writeAsStringSync('old'); |
| |
| await project.ensureReadyForPlatformSpecificTooling(); |
| |
| expect(project.lldbInitFile.readAsStringSync(), contains('Generated file, do not edit.')); |
| expect( |
| project.lldbHelperPythonFile.readAsStringSync(), |
| contains('Generated file, do not edit.'), |
| ); |
| }, overrides: <Type, Generator>{Cache: () => FakeCache(olderThanToolsStamp: true)}); |
| }); |
| }); |
| }); |
| |
| group('MacOSProject', () { |
| testWithoutContext('managedDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = MacOSProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect(project.managedDirectory.path, 'app_name/macos/Flutter'); |
| }); |
| |
| testWithoutContext('module managedDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = MacOSProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect(project.managedDirectory.path, 'app_name/macos/Flutter'); |
| }); |
| |
| testWithoutContext('ephemeralDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = MacOSProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect(project.ephemeralDirectory.path, 'app_name/macos/Flutter/ephemeral'); |
| }); |
| |
| testWithoutContext('flutterSwiftPackagesDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = MacOSProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect( |
| project.flutterSwiftPackagesDirectory.path, |
| 'app_name/macos/Flutter/ephemeral/Packages', |
| ); |
| }); |
| |
| testWithoutContext('relativeSwiftPackagesDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = MacOSProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect( |
| project.relativeSwiftPackagesDirectory.path, |
| 'app_name/macos/Flutter/ephemeral/Packages/.packages', |
| ); |
| }); |
| |
| testWithoutContext('flutterPluginSwiftPackageDirectory', () { |
| final fs = MemoryFileSystem.test(); |
| final project = MacOSProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect( |
| project.flutterPluginSwiftPackageDirectory.path, |
| 'app_name/macos/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage', |
| ); |
| }); |
| |
| testWithoutContext('xcodeConfigFor', () { |
| final fs = MemoryFileSystem.test(); |
| final project = MacOSProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| expect(project.xcodeConfigFor('Debug').path, 'app_name/macos/Flutter/Flutter-Debug.xcconfig'); |
| }); |
| |
| group('usesSwiftPackageManager', () { |
| testUsingContext( |
| 'is true when macOS project exists', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| projectDirectory.childDirectory('macos').createSync(recursive: true); |
| final FlutterManifest manifest = FakeFlutterManifest(); |
| final project = FlutterProject(projectDirectory, manifest, manifest); |
| expect(project.macos.usesSwiftPackageManager, isTrue); |
| }, |
| overrides: <Type, Generator>{ |
| FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), |
| XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), |
| }, |
| ); |
| |
| testUsingContext( |
| "is false when macOS project doesn't exist", |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| final FlutterManifest manifest = FakeFlutterManifest(); |
| final project = FlutterProject(projectDirectory, manifest, manifest); |
| expect(project.ios.usesSwiftPackageManager, isFalse); |
| expect(project.macos.usesSwiftPackageManager, isFalse); |
| }, |
| overrides: <Type, Generator>{ |
| FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), |
| XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), |
| }, |
| ); |
| |
| testUsingContext( |
| 'is false when Xcode is less than 15', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| projectDirectory.childDirectory('macos').createSync(recursive: true); |
| final FlutterManifest manifest = FakeFlutterManifest(); |
| final project = FlutterProject(projectDirectory, manifest, manifest); |
| expect(project.macos.usesSwiftPackageManager, isFalse); |
| }, |
| overrides: <Type, Generator>{ |
| FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), |
| XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(14, 0, 0)), |
| }, |
| ); |
| |
| testUsingContext( |
| 'is false when Swift Package Manager feature is not enabled', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| projectDirectory.childDirectory('macos').createSync(recursive: true); |
| final FlutterManifest manifest = FakeFlutterManifest(); |
| final project = FlutterProject(projectDirectory, manifest, manifest); |
| expect(project.macos.usesSwiftPackageManager, isFalse); |
| }, |
| overrides: <Type, Generator>{ |
| FeatureFlags: () => TestFeatureFlags(), |
| XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), |
| }, |
| ); |
| |
| testUsingContext( |
| 'is false when project is a module', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final Directory projectDirectory = fs.directory('path'); |
| projectDirectory.childDirectory('macos').createSync(recursive: true); |
| final FlutterManifest manifest = FakeFlutterManifest(isModule: true); |
| final project = FlutterProject(projectDirectory, manifest, manifest); |
| expect(project.macos.usesSwiftPackageManager, isFalse); |
| }, |
| overrides: <Type, Generator>{ |
| FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), |
| XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), |
| }, |
| ); |
| }); |
| |
| group('prefetchSwiftPackages', () { |
| testWithoutContext('returns early if usesSwiftPackageManager is false', () async { |
| final fs = MemoryFileSystem.test(); |
| final testLogger = BufferLogger.test(); |
| final fakeProcessManager = FakeProcessManager.empty(); |
| final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); |
| |
| final iosProject = FakeIosProjectWithCustomFlags.fromFlutter( |
| FakeFlutterProject(fileSystem: fs), |
| usesSwiftPackageManager: false, |
| ); |
| await iosProject.prefetchSwiftPackages( |
| xcodebuildProjectCommandArguments: <String>[], |
| processUtils: processUtils, |
| logger: testLogger, |
| ); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| }); |
| |
| testWithoutContext( |
| 'returns early if flutterPluginSwiftPackageInProjectSettings is false', |
| () async { |
| final fs = MemoryFileSystem.test(); |
| final testLogger = BufferLogger.test(); |
| final fakeProcessManager = FakeProcessManager.empty(); |
| final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); |
| |
| final iosProject = FakeIosProjectWithCustomFlags.fromFlutter( |
| FakeFlutterProject(fileSystem: fs), |
| flutterPluginSwiftPackageInProjectSettings: false, |
| ); |
| await iosProject.prefetchSwiftPackages( |
| xcodebuildProjectCommandArguments: <String>[], |
| processUtils: processUtils, |
| logger: testLogger, |
| ); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| }, |
| ); |
| |
| testWithoutContext('starts the process and resolves packages successfully', () async { |
| final fs = MemoryFileSystem.test(); |
| final testLogger = BufferLogger.test(); |
| const projectPath = 'path/to/project'; |
| final Directory buildDirectory = fs.directory('$projectPath/build/ios'); |
| final fakeProcessManager = FakeProcessManager.empty(); |
| fakeProcessManager.addCommands(<FakeCommand>[ |
| FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'xcodebuild', |
| '-clonedSourcePackagesDirPath', |
| '/${buildDirectory.path}/SourcePackages', |
| '-resolvePackageDependencies', |
| ], |
| stdout: ''' |
| Resolve Package Graph |
| |
| Fetching from https://github.com/apple/swift-algorithms.git (cached) |
| |
| Fetching from https://github.com/apple/swift-numerics.git (cached) |
| |
| Creating working copy of package ‘swift-numerics’ |
| |
| Creating working copy of package ‘swift-algorithms’ |
| |
| Checking out 1.2.2 of package ‘swift-algorithms’ |
| |
| Checking out 1.1.1 of package ‘swift-numerics’ |
| |
| |
| Resolved source packages: |
| swift-numerics: https://github.com/apple/swift-numerics.git @ 1.1.1 |
| swift-algorithms: https://github.com/apple/swift-algorithms.git @ 1.2.2 |
| ''', |
| ), |
| ]); |
| final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); |
| |
| final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| await iosProject.prefetchSwiftPackages( |
| xcodebuildProjectCommandArguments: <String>[ |
| 'xcrun', |
| 'xcodebuild', |
| '-clonedSourcePackagesDirPath', |
| '/${buildDirectory.path}/SourcePackages', |
| ], |
| processUtils: processUtils, |
| logger: testLogger, |
| ); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect( |
| testLogger.statusText, |
| contains(''' |
| Xcode is fetching Swift Package Manager dependencies. This may take several minutes... |
| Fetching from https://github.com/apple/swift-algorithms.git (cached)... |
| Fetching from https://github.com/apple/swift-numerics.git (cached)...'''), |
| ); |
| }); |
| |
| testWithoutContext('skips running if already completed or in progress', () async { |
| final fs = MemoryFileSystem.test(); |
| final testLogger = BufferLogger.test(); |
| const projectPath = 'path/to/project'; |
| final Directory buildDirectory = fs.directory('$projectPath/build/ios'); |
| final fakeProcessManager = FakeProcessManager.empty(); |
| fakeProcessManager.addCommands(<FakeCommand>[ |
| FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'xcodebuild', |
| '-clonedSourcePackagesDirPath', |
| '/${buildDirectory.path}/SourcePackages', |
| '-resolvePackageDependencies', |
| ], |
| ), |
| ]); |
| final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); |
| |
| final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| await iosProject.prefetchSwiftPackages( |
| xcodebuildProjectCommandArguments: <String>[ |
| 'xcrun', |
| 'xcodebuild', |
| '-clonedSourcePackagesDirPath', |
| '/${buildDirectory.path}/SourcePackages', |
| ], |
| processUtils: processUtils, |
| logger: testLogger, |
| ); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| |
| // Second call should skip starting a new process |
| await iosProject.prefetchSwiftPackages( |
| xcodebuildProjectCommandArguments: <String>[ |
| 'xcrun', |
| 'xcodebuild', |
| '-clonedSourcePackagesDirPath', |
| '/${buildDirectory.path}/SourcePackages', |
| ], |
| processUtils: processUtils, |
| logger: testLogger, |
| ); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| }); |
| |
| testWithoutContext('throws exception when resolving fails', () async { |
| final fs = MemoryFileSystem.test(); |
| final testLogger = BufferLogger.test(); |
| const projectPath = 'path/to/project'; |
| final Directory buildDirectory = fs.directory('$projectPath/build/ios'); |
| final fakeProcessManager = FakeProcessManager.empty(); |
| fakeProcessManager.addCommands(<FakeCommand>[ |
| FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'xcodebuild', |
| '-clonedSourcePackagesDirPath', |
| '/${buildDirectory.path}/SourcePackages', |
| '-resolvePackageDependencies', |
| ], |
| exitCode: 1, |
| stderr: 'error: some error', |
| ), |
| ]); |
| final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); |
| |
| final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| await expectLater( |
| iosProject.prefetchSwiftPackages( |
| xcodebuildProjectCommandArguments: <String>[ |
| 'xcrun', |
| 'xcodebuild', |
| '-clonedSourcePackagesDirPath', |
| '/${buildDirectory.path}/SourcePackages', |
| ], |
| processUtils: processUtils, |
| logger: testLogger, |
| ), |
| throwsToolExit(), |
| ); |
| }); |
| |
| testWithoutContext('prefetchSwiftPackages can run for both platforms', () async { |
| final fs = MemoryFileSystem.test(); |
| final testLogger = BufferLogger.test(); |
| const projectPath = 'path/to/project'; |
| final Directory iosBuildDirectory = fs.directory('$projectPath/build/ios'); |
| final Directory macosBuildDirectory = fs.directory('$projectPath/build/macos'); |
| |
| final fakeProcessManager = FakeProcessManager.empty(); |
| fakeProcessManager.addCommands(<FakeCommand>[ |
| FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'xcodebuild', |
| '-clonedSourcePackagesDirPath', |
| '/${iosBuildDirectory.path}/SourcePackages', |
| '-resolvePackageDependencies', |
| ], |
| ), |
| FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'xcodebuild', |
| '-clonedSourcePackagesDirPath', |
| '/${macosBuildDirectory.path}/SourcePackages', |
| '-resolvePackageDependencies', |
| ], |
| ), |
| ]); |
| final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); |
| |
| final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| await iosProject.prefetchSwiftPackages( |
| xcodebuildProjectCommandArguments: [ |
| 'xcrun', |
| 'xcodebuild', |
| '-clonedSourcePackagesDirPath', |
| '/${iosBuildDirectory.path}/SourcePackages', |
| ], |
| processUtils: processUtils, |
| logger: testLogger, |
| ); |
| |
| final macosProject = FakeMacOSProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); |
| await macosProject.prefetchSwiftPackages( |
| xcodebuildProjectCommandArguments: [ |
| 'xcrun', |
| 'xcodebuild', |
| '-clonedSourcePackagesDirPath', |
| '/${macosBuildDirectory.path}/SourcePackages', |
| ], |
| processUtils: processUtils, |
| logger: testLogger, |
| ); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| }); |
| }); |
| }); |
| } |
| |
| class FakeFlutterProject extends Fake implements FlutterProject { |
| FakeFlutterProject({required this.fileSystem, this.isModule = false}); |
| |
| MemoryFileSystem fileSystem; |
| |
| @override |
| late final Directory directory = fileSystem.directory('app_name'); |
| |
| @override |
| bool isModule = false; |
| |
| @override |
| FlutterManifest get manifest => FakeFlutterManifest(); |
| } |
| |
| class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter { |
| FakeXcodeProjectInterpreter({ |
| this.isInstalled = true, |
| this.version, |
| this.schemes = const <String>['Runner'], |
| }); |
| |
| @override |
| final bool isInstalled; |
| |
| @override |
| final Version? version; |
| |
| List<String> schemes; |
| |
| @override |
| Future<XcodeProjectInfo?> getInfo( |
| XcodeBasedProject xcodeProject, { |
| String? projectFilename, |
| required Directory buildDirectory, |
| }) async { |
| return XcodeProjectInfo(<String>[], <String>[], schemes, BufferLogger.test()); |
| } |
| } |
| |
| class FakeFlutterManifest extends Fake implements FlutterManifest { |
| FakeFlutterManifest({this.isModule = false}); |
| |
| @override |
| bool isModule; |
| |
| @override |
| String? buildName; |
| |
| @override |
| String? buildNumber; |
| |
| @override |
| String? get iosBundleIdentifier => null; |
| |
| @override |
| String get appName => ''; |
| |
| @override |
| List<String> get workspace => <String>[]; |
| |
| @override |
| PluginPlatformConfig? get ios => null; |
| |
| @override |
| PluginPlatformConfig? get macos => null; |
| } |
| |
| class FakeCache extends Fake implements Cache { |
| FakeCache({this.olderThanToolsStamp = false}); |
| |
| bool olderThanToolsStamp; |
| Map<String, bool> filesOlderThanToolsStamp = <String, bool>{}; |
| |
| @override |
| bool isOlderThanToolsStamp(FileSystemEntity entity) { |
| if (filesOlderThanToolsStamp.containsKey(entity.basename)) { |
| return filesOlderThanToolsStamp[entity.basename]!; |
| } |
| return olderThanToolsStamp; |
| } |
| } |
| |
| class FakeIosProject extends IosProject { |
| FakeIosProject.fromFlutter(super.parent) : super.fromFlutter(); |
| |
| @override |
| bool usesSwiftPackageManager = true; |
| |
| @override |
| bool flutterPluginSwiftPackageInProjectSettings = true; |
| } |
| |
| class FakeMacOSProject extends MacOSProject { |
| FakeMacOSProject.fromFlutter(super.parent) : super.fromFlutter(); |
| |
| @override |
| bool usesSwiftPackageManager = true; |
| |
| @override |
| bool flutterPluginSwiftPackageInProjectSettings = true; |
| } |
| |
| class FakeIosProjectWithCustomFlags extends IosProject { |
| FakeIosProjectWithCustomFlags.fromFlutter( |
| super.parent, { |
| this.usesSwiftPackageManager = true, |
| this.flutterPluginSwiftPackageInProjectSettings = true, |
| }) : super.fromFlutter(); |
| |
| @override |
| final bool usesSwiftPackageManager; |
| |
| @override |
| final bool flutterPluginSwiftPackageInProjectSettings; |
| } |