Unverified Commit 93e7d34d authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] Remove globals/mocks from GradleUtils (#76020)

parent 3c77c432
......@@ -25,6 +25,7 @@ import '../globals.dart' as globals hide logger, printStatus, printTrace, printE
import '../project.dart';
import '../reporting/reporting.dart';
import 'android_builder.dart';
import 'android_studio.dart';
import 'gradle_errors.dart';
import 'gradle_utils.dart';
......@@ -142,12 +143,15 @@ Future<void> checkGradleDependencies(Logger logger) async {
);
final FlutterProject flutterProject = FlutterProject.current();
await globals.processUtils.run(<String>[
gradleUtils.getExecutable(flutterProject),
globals.gradleUtils.getExecutable(flutterProject),
'dependencies',
],
throwOnError: true,
workingDirectory: flutterProject.android.hostAppGradleRoot.path,
environment: gradleEnvironment,
environment: <String, String>{
if (javaPath != null)
'JAVA_HOME': javaPath,
},
);
globals.androidSdk?.reinitialize();
progress.stop();
......@@ -364,7 +368,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
);
final List<String> command = <String>[
gradleUtils.getExecutable(project),
globals.gradleUtils.getExecutable(project),
];
if (_logger.isVerbose) {
command.add('-Pverbose=true');
......@@ -454,7 +458,10 @@ class AndroidGradleBuilder implements AndroidBuilder {
command,
workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true,
environment: gradleEnvironment,
environment: <String, String>{
if (javaPath != null)
'JAVA_HOME': javaPath,
},
mapFunction: consumeLog,
);
} on ProcessException catch (exception) {
......@@ -525,7 +532,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
}
if (isBuildingBundle) {
final File bundleFile = findBundleFile(project, buildInfo);
final File bundleFile = findBundleFile(project, buildInfo, _logger);
final String appSize = (buildInfo.mode == BuildMode.debug)
? '' // Don't display the size when building a debug variant.
: ' (${getSizeAsMB(bundleFile.lengthSync())})';
......@@ -542,7 +549,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
}
// Gradle produced an APK.
final Iterable<String> apkFilesPaths = project.isModule
? findApkFilesModule(project, androidBuildInfo)
? findApkFilesModule(project, androidBuildInfo, _logger)
: listApkPaths(androidBuildInfo);
final Directory apkDirectory = getApkDirectory(project);
final File apkFile = apkDirectory.childFile(apkFilesPaths.first);
......@@ -550,6 +557,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
_exitWithExpectedFileNotFound(
project: project,
fileExtension: '.apk',
logger: _logger,
);
}
......@@ -659,7 +667,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
'aar_init_script.gradle',
);
final List<String> command = <String>[
gradleUtils.getExecutable(project),
globals.gradleUtils.getExecutable(project),
'-I=$initScript',
'-Pflutter-root=$flutterRoot',
'-Poutput-dir=${outputDirectory.path}',
......@@ -702,14 +710,14 @@ class AndroidGradleBuilder implements AndroidBuilder {
// Copy the local engine repo in the output directory.
try {
globals.fsUtils.copyDirectorySync(
copyDirectory(
localEngineRepo,
getRepoDirectory(outputDirectory),
);
} on FileSystemException catch (_) {
} on FileSystemException catch (error, st) {
throwToolExit(
'Failed to copy the local engine ${localEngineRepo.path} repo '
'in ${outputDirectory.path}'
'in ${outputDirectory.path}: $error, $st'
);
}
command.add('-Ptarget-platform=${_getTargetPlatformByLocalEnginePath(
......@@ -730,7 +738,10 @@ class AndroidGradleBuilder implements AndroidBuilder {
command,
workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true,
environment: gradleEnvironment,
environment: <String, String>{
if (javaPath != null)
'JAVA_HOME': javaPath,
},
);
} finally {
status.stop();
......@@ -921,6 +932,7 @@ bool isAppUsingAndroidX(Directory androidDirectory) {
Iterable<String> findApkFilesModule(
FlutterProject project,
AndroidBuildInfo androidBuildInfo,
Logger logger,
) {
final Iterable<String> apkFileNames = _apkFilesFor(androidBuildInfo);
final Directory apkDirectory = getApkDirectory(project);
......@@ -953,6 +965,7 @@ Iterable<String> findApkFilesModule(
_exitWithExpectedFileNotFound(
project: project,
fileExtension: '.apk',
logger: logger,
);
}
return apks.map((File file) => file.path);
......@@ -991,7 +1004,7 @@ Iterable<String> listApkPaths(
}
@visibleForTesting
File findBundleFile(FlutterProject project, BuildInfo buildInfo) {
File findBundleFile(FlutterProject project, BuildInfo buildInfo, Logger logger) {
final List<File> fileCandidates = <File>[
getBundleDirectory(project)
.childDirectory(camelCase(buildInfo.modeName))
......@@ -1025,6 +1038,7 @@ File findBundleFile(FlutterProject project, BuildInfo buildInfo) {
_exitWithExpectedFileNotFound(
project: project,
fileExtension: '.aab',
logger: logger,
);
return null;
}
......@@ -1033,12 +1047,13 @@ File findBundleFile(FlutterProject project, BuildInfo buildInfo) {
void _exitWithExpectedFileNotFound({
@required FlutterProject project,
@required String fileExtension,
@required Logger logger,
}) {
assert(project != null);
assert(fileExtension != null);
final String androidGradlePluginVersion =
getGradleVersionForAndroidPlugin(project.android.hostAppGradleRoot);
getGradleVersionForAndroidPlugin(project.android.hostAppGradleRoot, logger);
BuildEvent('gradle-expected-file-not-found',
settings:
'androidGradlePluginVersion: $androidGradlePluginVersion, '
......
......@@ -3,7 +3,6 @@
// found in the LICENSE file.
// @dart = 2.8
import 'package:meta/meta.dart';
import '../base/error_handling_io.dart';
......@@ -13,7 +12,7 @@ import '../base/terminal.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../reporting/reporting.dart';
import 'gradle_utils.dart';
import 'android_studio.dart';
typedef GradleErrorTest = bool Function(String);
......@@ -295,14 +294,17 @@ final GradleHandledError flavorUndefinedHandler = GradleHandledError(
}) async {
final RunResult tasksRunResult = await globals.processUtils.run(
<String>[
gradleUtils.getExecutable(project),
globals.gradleUtils.getExecutable(project),
'app:tasks' ,
'--all',
'--console=auto',
],
throwOnError: true,
workingDirectory: project.android.hostAppGradleRoot.path,
environment: gradleEnvironment,
environment: <String, String>{
if (javaPath != null)
'JAVA_HOME': javaPath,
},
);
// Extract build types and product flavors.
final Set<String> variants = <String>{};
......
......@@ -7,8 +7,11 @@
import 'package:meta/meta.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/os.dart';
import '../base/platform.dart';
import '../base/terminal.dart';
import '../base/utils.dart';
import '../base/version.dart';
......@@ -17,41 +20,46 @@ import '../cache.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../reporting/reporting.dart';
import 'android_studio.dart';
/// The environment variables needed to run Gradle.
Map<String, String> get gradleEnvironment {
final Map<String, String> environment = Map<String, String>.of(globals.platform.environment);
if (javaPath != null) {
// Use java bundled with Android Studio.
environment['JAVA_HOME'] = javaPath;
}
// Don't log analytics for downstream Flutter commands.
// e.g. `flutter build bundle`.
environment['FLUTTER_SUPPRESS_ANALYTICS'] = 'true';
return environment;
}
const String _defaultGradleVersion = '6.7';
/// Gradle utils in the current [AppContext].
GradleUtils get gradleUtils => context.get<GradleUtils>();
final RegExp _androidPluginRegExp = RegExp(r'com\.android\.tools\.build:gradle:(\d+\.\d+\.\d+)');
/// Provides utilities to run a Gradle task,
/// such as finding the Gradle executable or constructing a Gradle project.
/// Provides utilities to run a Gradle task, such as finding the Gradle executable
/// or constructing a Gradle project.
class GradleUtils {
GradleUtils({
@required Platform platform,
@required Logger logger,
@required FileSystem fileSystem,
@required Cache cache,
@required OperatingSystemUtils operatingSystemUtils,
}) : _platform = platform,
_logger = logger,
_cache = cache,
_fileSystem = fileSystem,
_operatingSystemUtils = operatingSystemUtils;
final Cache _cache;
final FileSystem _fileSystem;
final Platform _platform;
final Logger _logger;
final OperatingSystemUtils _operatingSystemUtils;
/// Gets the Gradle executable path and prepares the Gradle project.
/// This is the `gradlew` or `gradlew.bat` script in the `android/` directory.
String getExecutable(FlutterProject project) {
final Directory androidDir = project.android.hostAppGradleRoot;
gradleUtils.injectGradleWrapperIfNeeded(androidDir);
injectGradleWrapperIfNeeded(androidDir);
final File gradle = androidDir.childFile(
globals.platform.isWindows ? 'gradlew.bat' : 'gradlew',
_platform.isWindows ? 'gradlew.bat' : 'gradlew',
);
if (gradle.existsSync()) {
globals.printTrace('Using gradle from ${gradle.absolute.path}.');
_logger.printTrace('Using gradle from ${gradle.absolute.path}.');
// If the Gradle executable doesn't have execute permission,
// then attempt to set it.
_giveExecutePermissionIfNeeded(gradle);
_operatingSystemUtils.makeExecutable(gradle);
return gradle.absolute.path;
}
throwToolExit(
......@@ -63,89 +71,61 @@ class GradleUtils {
/// Injects the Gradle wrapper files if any of these files don't exist in [directory].
void injectGradleWrapperIfNeeded(Directory directory) {
globals.fsUtils.copyDirectorySync(
globals.cache.getArtifactDirectory('gradle_wrapper'),
copyDirectory(
_cache.getArtifactDirectory('gradle_wrapper'),
directory,
shouldCopyFile: (File sourceFile, File destinationFile) {
// Don't override the existing files in the project.
return !destinationFile.existsSync();
},
onFileCopied: (File sourceFile, File destinationFile) {
if (_hasAnyExecutableFlagSet(sourceFile)) {
_giveExecutePermissionIfNeeded(destinationFile);
}
},
onFileCopied: (File source, File dest) {
_operatingSystemUtils.makeExecutable(dest);
}
);
// Add the `gradle-wrapper.properties` file if it doesn't exist.
final Directory propertiesDirectory = directory.childDirectory(
globals.fs.path.join('gradle', 'wrapper'));
final File propertiesFile = propertiesDirectory.childFile('gradle-wrapper.properties');
if (!propertiesFile.existsSync()) {
propertiesDirectory.createSync(recursive: true);
final String gradleVersion = getGradleVersionForAndroidPlugin(directory);
propertiesFile.writeAsStringSync('''
final Directory propertiesDirectory = directory
.childDirectory(_fileSystem.path.join('gradle', 'wrapper'));
final File propertiesFile = propertiesDirectory
.childFile('gradle-wrapper.properties');
if (propertiesFile.existsSync()) {
return;
}
propertiesDirectory.createSync(recursive: true);
final String gradleVersion = getGradleVersionForAndroidPlugin(directory, _logger);
final String propertyContents = '''
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersion-all.zip
''', flush: true,
);
}
''';
propertiesFile.writeAsStringSync(propertyContents);
}
}
const String _defaultGradleVersion = '6.7';
final RegExp _androidPluginRegExp = RegExp(r'com\.android\.tools\.build:gradle:(\d+\.\d+\.\d+)');
/// Returns the Gradle version that the current Android plugin depends on when found,
/// otherwise it returns a default version.
///
/// The Android plugin version is specified in the [build.gradle] file within
/// the project's Android directory.
String getGradleVersionForAndroidPlugin(Directory directory) {
String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) {
final File buildFile = directory.childFile('build.gradle');
if (!buildFile.existsSync()) {
globals.printTrace('$buildFile doesn\'t exist, assuming AGP version: $_defaultGradleVersion');
logger.printTrace('$buildFile doesn\'t exist, assuming AGP version: $_defaultGradleVersion');
return _defaultGradleVersion;
}
final String buildFileContent = buildFile.readAsStringSync();
final Iterable<Match> pluginMatches = _androidPluginRegExp.allMatches(buildFileContent);
if (pluginMatches.isEmpty) {
globals.printTrace('$buildFile doesn\'t provide an AGP version, assuming AGP version: $_defaultGradleVersion');
logger.printTrace('$buildFile doesn\'t provide an AGP version, assuming AGP version: $_defaultGradleVersion');
return _defaultGradleVersion;
}
final String androidPluginVersion = pluginMatches.first.group(1);
globals.printTrace('$buildFile provides AGP version: $androidPluginVersion');
logger.printTrace('$buildFile provides AGP version: $androidPluginVersion');
return getGradleVersionFor(androidPluginVersion);
}
const int _kExecPermissionMask = 0x49; // a+x
/// Returns [true] if [executable] has all executable flag set.
bool _hasAllExecutableFlagSet(File executable) {
final FileStat stat = executable.statSync();
assert(stat.type != FileSystemEntityType.notFound);
globals.printTrace('${executable.path} mode: ${stat.mode} ${stat.modeString()}.');
return stat.mode & _kExecPermissionMask == _kExecPermissionMask;
}
/// Returns [true] if [executable] has any executable flag set.
bool _hasAnyExecutableFlagSet(File executable) {
final FileStat stat = executable.statSync();
assert(stat.type != FileSystemEntityType.notFound);
globals.printTrace('${executable.path} mode: ${stat.mode} ${stat.modeString()}.');
return stat.mode & _kExecPermissionMask != 0;
}
/// Gives execute permission to [executable] if it doesn't have it already.
void _giveExecutePermissionIfNeeded(File executable) {
if (!_hasAllExecutableFlagSet(executable)) {
globals.printTrace('Trying to give execute permission to ${executable.path}.');
globals.os.makeExecutable(executable);
}
}
/// Returns true if [targetVersion] is within the range [min] and [max] inclusive.
bool _isWithinVersionRange(
String targetVersion, {
......@@ -162,6 +142,7 @@ bool _isWithinVersionRange(
/// Returns the Gradle version that is required by the given Android Gradle plugin version
/// by picking the largest compatible version from
/// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
@visibleForTesting
String getGradleVersionFor(String androidPluginVersion) {
if (_isWithinVersionRange(androidPluginVersion, min: '1.0.0', max: '1.1.3')) {
return '2.3';
......
......@@ -54,47 +54,6 @@ class FileSystemUtils {
}
}
/// Creates `destDir` if needed, then recursively copies `srcDir` to
/// `destDir`, invoking [onFileCopied], if specified, for each
/// source/destination file pair.
///
/// Skips files if [shouldCopyFile] returns `false`.
void copyDirectorySync(
Directory srcDir,
Directory destDir, {
bool shouldCopyFile(File srcFile, File destFile),
void onFileCopied(File srcFile, File destFile),
}) {
if (!srcDir.existsSync()) {
throw Exception('Source directory "${srcDir.path}" does not exist, nothing to copy');
}
if (!destDir.existsSync()) {
destDir.createSync(recursive: true);
}
for (final FileSystemEntity entity in srcDir.listSync()) {
final String newPath = destDir.fileSystem.path.join(destDir.path, entity.basename);
if (entity is File) {
final File newFile = destDir.fileSystem.file(newPath);
if (shouldCopyFile != null && !shouldCopyFile(entity, newFile)) {
continue;
}
newFile.writeAsBytesSync(entity.readAsBytesSync());
onFileCopied?.call(entity, newFile);
} else if (entity is Directory) {
copyDirectorySync(
entity,
destDir.fileSystem.directory(newPath),
shouldCopyFile: shouldCopyFile,
onFileCopied: onFileCopied,
);
} else {
throw Exception('${entity.path} is neither File nor Directory');
}
}
}
/// Appends a number to a filename in order to make it unique under a
/// directory.
File getUniqueFile(Directory dir, String baseName, String ext) {
......@@ -170,6 +129,50 @@ class FileSystemUtils {
}
}
/// Creates `destDir` if needed, then recursively copies `srcDir` to
/// `destDir`, invoking [onFileCopied], if specified, for each
/// source/destination file pair.
///
/// Skips files if [shouldCopyFile] returns `false`.
void copyDirectory(
Directory srcDir,
Directory destDir, {
bool shouldCopyFile(File srcFile, File destFile),
void onFileCopied(File srcFile, File destFile),
}) {
if (!srcDir.existsSync()) {
throw Exception('Source directory "${srcDir.path}" does not exist, nothing to copy');
}
if (!destDir.existsSync()) {
destDir.createSync(recursive: true);
}
for (final FileSystemEntity entity in srcDir.listSync()) {
final String newPath = destDir.fileSystem.path.join(destDir.path, entity.basename);
if (entity is Link) {
final Link newLink = destDir.fileSystem.link(newPath);
newLink.createSync(entity.targetSync());
} else if (entity is File) {
final File newFile = destDir.fileSystem.file(newPath);
if (shouldCopyFile != null && !shouldCopyFile(entity, newFile)) {
continue;
}
newFile.writeAsBytesSync(entity.readAsBytesSync());
onFileCopied?.call(entity, newFile);
} else if (entity is Directory) {
copyDirectory(
entity,
destDir.fileSystem.directory(newPath),
shouldCopyFile: shouldCopyFile,
onFileCopied: onFileCopied,
);
} else {
throw Exception('${entity.path} is neither File nor Directory, was ${entity.runtimeType}');
}
}
}
/// This class extends [local_fs.LocalFileSystem] in order to clean up
/// directories and files that the tool creates under the system temporary
/// directory when the tool exits either normally or when killed by a signal.
......
......@@ -12,7 +12,7 @@ import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:process/process.dart';
import 'android/gradle_utils.dart';
import 'android/android_studio.dart';
import 'base/common.dart';
import 'base/error_handling_io.dart';
import 'base/file_system.dart';
......@@ -1174,7 +1174,7 @@ class AndroidMavenArtifacts extends ArtifactSet {
final Directory tempDir = cache.getRoot().createTempSync(
'flutter_gradle_wrapper.',
);
gradleUtils.injectGradleWrapperIfNeeded(tempDir);
globals.gradleUtils.injectGradleWrapperIfNeeded(tempDir);
final Status status = logger.startProgress('Downloading Android Maven dependencies...');
final File gradle = tempDir.childFile(
......@@ -1190,7 +1190,11 @@ class AndroidMavenArtifacts extends ArtifactSet {
'--project-cache-dir', tempDir.path,
'resolveDependencies',
],
environment: gradleEnvironment);
environment: <String, String>{
if (javaPath != null)
'JAVA_HOME': javaPath,
},
);
if (processResult.exitCode != 0) {
logger.printError('Failed to download the Android dependencies');
}
......
......@@ -322,7 +322,7 @@ end
try {
// Copy xcframework engine cache framework to mode directory.
globals.fsUtils.copyDirectorySync(
copyDirectory(
globals.fs.directory(engineCacheFlutterFrameworkDirectory),
flutterFrameworkCopy,
);
......
......@@ -521,7 +521,7 @@ abstract class CreateBase extends FlutterCommand {
int _injectGradleWrapper(FlutterProject project) {
int filesCreated = 0;
globals.fsUtils.copyDirectorySync(
copyDirectory(
globals.cache.getArtifactDirectory('gradle_wrapper'),
project.android.hostAppGradleRoot,
onFileCopied: (File sourceFile, File destinationFile) {
......
......@@ -219,7 +219,13 @@ Future<T> runInContext<T>(
platform: globals.platform,
fuchsiaArtifacts: globals.fuchsiaArtifacts,
),
GradleUtils: () => GradleUtils(),
GradleUtils: () => GradleUtils(
fileSystem: globals.fs,
operatingSystemUtils: globals.os,
logger: globals.logger,
platform: globals.platform,
cache: globals.cache,
),
HotRunnerConfig: () => HotRunnerConfig(),
IOSSimulatorUtils: () => IOSSimulatorUtils(
logger: globals.logger,
......
......@@ -8,6 +8,7 @@ import 'package:process/process.dart';
import 'android/android_sdk.dart';
import 'android/android_studio.dart';
import 'android/gradle_utils.dart';
import 'artifacts.dart';
import 'base/bot_detector.dart';
import 'base/config.dart';
......@@ -203,3 +204,6 @@ PlistParser _plistInstance;
/// The global template renderer.
TemplateRenderer get templateRenderer => context.get<TemplateRenderer>();
/// Gradle utils in the current [AppContext].
GradleUtils get gradleUtils => context.get<GradleUtils>();
......@@ -435,7 +435,7 @@ Future<XcodeBuildResult> buildXcodeProject({
// (for example, kernel binary files produced from previous run).
globals.fs.directory(outputDir).deleteSync(recursive: true);
}
globals.fsUtils.copyDirectorySync(
copyDirectory(
globals.fs.directory(expectedOutputDirectory),
globals.fs.directory(outputDir),
);
......
......@@ -689,7 +689,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
)
);
if (framework.existsSync()) {
globals.fsUtils.copyDirectorySync(
copyDirectory(
framework,
engineCopyDirectory.childDirectory('Flutter.xcframework'),
);
......@@ -900,7 +900,7 @@ to migrate your project.
'library_new_embedding',
), ephemeralDirectory);
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
gradle.gradleUtils.injectGradleWrapperIfNeeded(ephemeralDirectory);
globals.gradleUtils.injectGradleWrapperIfNeeded(ephemeralDirectory);
}
Future<void> _overwriteFromTemplate(String path, Directory target) async {
......
......@@ -120,24 +120,18 @@ class ChromiumLauncher {
@required OperatingSystemUtils operatingSystemUtils,
@required BrowserFinder browserFinder,
@required Logger logger,
@visibleForTesting FileSystemUtils fileSystemUtils,
}) : _fileSystem = fileSystem,
_platform = platform,
_processManager = processManager,
_operatingSystemUtils = operatingSystemUtils,
_browserFinder = browserFinder,
_logger = logger,
_fileSystemUtils = fileSystemUtils ?? FileSystemUtils(
fileSystem: fileSystem,
platform: platform,
);
_logger = logger;
final FileSystem _fileSystem;
final Platform _platform;
final ProcessManager _processManager;
final OperatingSystemUtils _operatingSystemUtils;
final BrowserFinder _browserFinder;
final FileSystemUtils _fileSystemUtils;
final Logger _logger;
bool get hasChromeInstance => _currentCompleter.isCompleted;
......@@ -314,11 +308,10 @@ class ChromiumLauncher {
void _cacheUserSessionInformation(Directory userDataDir, Directory cacheDir) {
final Directory targetChromeDefault = _fileSystem.directory(_fileSystem.path.join(cacheDir?.path ?? '', _chromeDefaultPath));
final Directory sourceChromeDefault = _fileSystem.directory(_fileSystem.path.join(userDataDir.path, _chromeDefaultPath));
if (sourceChromeDefault.existsSync()) {
targetChromeDefault.createSync(recursive: true);
try {
_fileSystemUtils.copyDirectorySync(sourceChromeDefault, targetChromeDefault);
copyDirectory(sourceChromeDefault, targetChromeDefault);
} on FileSystemException catch (err) {
// This is a best-effort update. Display the message in case the failure is relevant.
// one possible example is a file lock due to multiple running chrome instances.
......@@ -343,11 +336,10 @@ class ChromiumLauncher {
void _restoreUserSessionInformation(Directory cacheDir, Directory userDataDir) {
final Directory sourceChromeDefault = _fileSystem.directory(_fileSystem.path.join(cacheDir.path ?? '', _chromeDefaultPath));
final Directory targetChromeDefault = _fileSystem.directory(_fileSystem.path.join(userDataDir.path, _chromeDefaultPath));
try {
if (sourceChromeDefault.existsSync()) {
targetChromeDefault.createSync(recursive: true);
_fileSystemUtils.copyDirectorySync(sourceChromeDefault, targetChromeDefault);
copyDirectory(sourceChromeDefault, targetChromeDefault);
}
} on FileSystemException catch (err) {
_logger.printError('Failed to restore Chrome preferences: $err');
......
......@@ -6,6 +6,7 @@
import 'package:archive/archive.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/android/android_studio.dart';
import 'package:flutter_tools/src/android/gradle.dart';
......@@ -38,7 +39,6 @@ void main() {
MockProcessManager mockProcessManager;
FakePlatform android;
FileSystem fileSystem;
FileSystemUtils fileSystemUtils;
Cache cache;
AndroidGradleBuilder builder;
......@@ -46,7 +46,6 @@ void main() {
logger = BufferLogger.test();
testUsage = TestUsage();
fileSystem = MemoryFileSystem.test();
fileSystemUtils = MockFileSystemUtils();
mockAndroidSdk = MockAndroidSdk();
mockAndroidStudio = MockAndroidStudio();
mockArtifacts = MockLocalEngineArtifacts();
......@@ -1284,8 +1283,6 @@ void main() {
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await builder.buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
......@@ -1308,15 +1305,11 @@ void main() {
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
expect(fileSystem.link(
'build/outputs/repo/io/flutter/flutter_embedding_release/'
'1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b/'
'flutter_embedding_release-1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b.pom'
), exists);
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
......@@ -1324,7 +1317,6 @@ void main() {
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
......@@ -1387,8 +1379,6 @@ void main() {
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await builder.buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
......@@ -1412,15 +1402,11 @@ void main() {
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
expect(fileSystem.link(
'build/outputs/repo/io/flutter/flutter_embedding_release/'
'1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b/'
'flutter_embedding_release-1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b.pom'
), exists);
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
......@@ -1428,7 +1414,6 @@ void main() {
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
......@@ -1491,8 +1476,6 @@ void main() {
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await builder.buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
......@@ -1516,15 +1499,11 @@ void main() {
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
expect(fileSystem.link(
'build/outputs/repo/io/flutter/flutter_embedding_release/'
'1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b/'
'flutter_embedding_release-1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b.pom'
), exists);
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
......@@ -1532,12 +1511,10 @@ void main() {
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build aar uses selected local engine,the engine abi is x64', () async {
testUsingContext('build aar uses selected local engine,the engine abi is x64', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
......@@ -1595,8 +1572,6 @@ void main() {
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await builder.buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
......@@ -1620,15 +1595,11 @@ void main() {
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
expect(fileSystem.link(
'build/outputs/repo/io/flutter/flutter_embedding_release/'
'1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b/'
'flutter_embedding_release-1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b.pom'
), exists);
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
......@@ -1636,7 +1607,6 @@ void main() {
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
});
......@@ -1653,5 +1623,4 @@ FakePlatform fakePlatform(String name) {
class MockProcessManager extends Mock implements ProcessManager {}
class MockAndroidSdk extends Mock implements AndroidSdk {}
class MockAndroidStudio extends Mock implements AndroidStudio {}
class MockFileSystemUtils extends Mock implements FileSystemUtils {}
class MockLocalEngineArtifacts extends Mock implements LocalEngineArtifacts {}
......@@ -77,11 +77,7 @@ void main() {
const String targetPath = '/some/non-existent/target';
final Directory targetDirectory = targetMemoryFs.directory(targetPath);
final FileSystemUtils fsUtils = FileSystemUtils(
fileSystem: sourceMemoryFs,
platform: FakePlatform(),
);
fsUtils.copyDirectorySync(sourceDirectory, targetDirectory);
copyDirectory(sourceDirectory, targetDirectory);
expect(targetDirectory.existsSync(), true);
targetMemoryFs.currentDirectory = targetPath;
......@@ -97,10 +93,6 @@ void main() {
testWithoutContext('Skip files if shouldCopyFile returns false', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final FileSystemUtils fsUtils = FileSystemUtils(
fileSystem: fileSystem,
platform: FakePlatform(),
);
final Directory origin = fileSystem.directory('/origin');
origin.createSync();
fileSystem.file(fileSystem.path.join('origin', 'a.txt')).writeAsStringSync('irrelevant');
......@@ -109,7 +101,7 @@ void main() {
fileSystem.file(fileSystem.path.join('origin', 'nested', 'b.txt')).writeAsStringSync('irrelevant');
final Directory destination = fileSystem.directory('/destination');
fsUtils.copyDirectorySync(origin, destination, shouldCopyFile: (File origin, File dest) {
copyDirectory(origin, destination, shouldCopyFile: (File origin, File dest) {
return origin.basename == 'b.txt';
});
......
......@@ -104,19 +104,17 @@ void main() {
});
testWithoutContext('does not crash if saving profile information fails due to a file system exception.', () async {
final MockFileSystemUtils fileSystemUtils = MockFileSystemUtils();
final BufferLogger logger = BufferLogger.test();
final ErrorThrowingFileSystem errorFileSystem = ErrorThrowingFileSystem(fileSystem);
errorFileSystem.addErrorEntity(FakeDirectory('/.tmp_rand0/flutter_tools_chrome_device.rand0/Default', errorFileSystem));
chromeLauncher = ChromiumLauncher(
fileSystem: fileSystem,
fileSystem: errorFileSystem,
platform: platform,
processManager: processManager,
operatingSystemUtils: operatingSystemUtils,
browserFinder: findChromeExecutable,
logger: logger,
fileSystemUtils: fileSystemUtils,
);
when(fileSystemUtils.copyDirectorySync(any, any))
.thenThrow(const FileSystemException());
processManager.addCommand(const FakeCommand(
command: <String>[
'example_chrome',
......@@ -143,19 +141,19 @@ void main() {
});
testWithoutContext('does not crash if restoring profile information fails due to a file system exception.', () async {
final MockFileSystemUtils fileSystemUtils = MockFileSystemUtils();
final BufferLogger logger = BufferLogger.test();
final ErrorThrowingFileSystem errorFileSystem = ErrorThrowingFileSystem(fileSystem);
errorFileSystem.addErrorEntity(FakeDirectory('/Default', errorFileSystem));
chromeLauncher = ChromiumLauncher(
fileSystem: fileSystem,
fileSystem: errorFileSystem,
platform: platform,
processManager: processManager,
operatingSystemUtils: operatingSystemUtils,
browserFinder: findChromeExecutable,
logger: logger,
fileSystemUtils: fileSystemUtils,
);
when(fileSystemUtils.copyDirectorySync(any, any))
.thenThrow(const FileSystemException());
processManager.addCommand(const FakeCommand(
command: <String>[
'example_chrome',
......@@ -171,7 +169,7 @@ void main() {
final Chromium chrome = await chromeLauncher.launch(
'example_url',
skipCheck: true,
cacheDir: fileSystem.currentDirectory,
cacheDir: errorFileSystem.currentDirectory,
);
// Create cache dir that the Chrome launcher will atttempt to persist.
......@@ -233,7 +231,6 @@ void main() {
testWithoutContext('can seed chrome temp directory with existing session data', () async {
final Completer<void> exitCompleter = Completer<void>.sync();
final Directory dataDir = fileSystem.directory('chrome-stuff');
final File preferencesFile = dataDir
.childDirectory('Default')
.childFile('preferences');
......@@ -265,7 +262,7 @@ void main() {
);
exitCompleter.complete();
await Future<void>.delayed(const Duration(microseconds: 1));
await Future<void>.delayed(const Duration(milliseconds: 1));
// writes non-crash back to dart_tool
expect(preferencesFile.readAsStringSync(), '"exit_type":"Normal"');
......@@ -366,3 +363,60 @@ Future<Chromium> _testLaunchChrome(String userDataDir, FakeProcessManager proces
skipCheck: true,
);
}
class FakeDirectory extends Fake implements Directory {
FakeDirectory(this.path, this.fileSystem);
@override
final FileSystem fileSystem;
@override
final String path;
@override
void createSync({bool recursive = false}) {}
@override
bool existsSync() {
return true;
}
@override
List<FileSystemEntity> listSync({bool recursive = false, bool followLinks = true}) {
throw FileSystemException(path, '');
}
}
class ErrorThrowingFileSystem extends ForwardingFileSystem {
ErrorThrowingFileSystem(FileSystem delegate) : super(delegate);
final Map<String, FileSystemEntity> errorEntities = <String, FileSystemEntity>{};
void addErrorEntity(FileSystemEntity entity) {
errorEntities[entity.path] = entity;
}
@override
Directory directory(dynamic path) {
if (errorEntities.containsKey(path)) {
return errorEntities[path] as Directory;
}
return delegate.directory(path);
}
@override
File file(dynamic path) {
if (errorEntities.containsKey(path)) {
return errorEntities[path] as File;
}
return delegate.file(path);
}
@override
Link link(dynamic path) {
if (errorEntities.containsKey(path)) {
return errorEntities[path] as Link;
}
return delegate.link(path);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment