// 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 'package:file/memory.dart'; import 'package:flutter_tools/src/android/gradle_utils.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:mockito/mockito.dart'; import 'package:process/process.dart'; import '../../src/common.dart'; import '../../src/context.dart'; void main() { group('injectGradleWrapperIfNeeded', () { MemoryFileSystem memoryFileSystem; Directory tempDir; Directory gradleWrapperDirectory; setUp(() { memoryFileSystem = MemoryFileSystem(); tempDir = memoryFileSystem.systemTempDirectory.createTempSync('flutter_artifacts_test.'); gradleWrapperDirectory = memoryFileSystem.directory( memoryFileSystem.path.join(tempDir.path, 'bin', 'cache', 'artifacts', 'gradle_wrapper')); gradleWrapperDirectory.createSync(recursive: true); gradleWrapperDirectory .childFile('gradlew') .writeAsStringSync('irrelevant'); gradleWrapperDirectory .childDirectory('gradle') .childDirectory('wrapper') .createSync(recursive: true); gradleWrapperDirectory .childDirectory('gradle') .childDirectory('wrapper') .childFile('gradle-wrapper.jar') .writeAsStringSync('irrelevant'); }); testUsingContext('injects the wrapper when all files are missing', () { final Directory sampleAppAndroid = globals.fs.directory('/sample-app/android'); sampleAppAndroid.createSync(recursive: true); gradleUtils.injectGradleWrapperIfNeeded(sampleAppAndroid); expect(sampleAppAndroid.childFile('gradlew').existsSync(), isTrue); expect(sampleAppAndroid .childDirectory('gradle') .childDirectory('wrapper') .childFile('gradle-wrapper.jar') .existsSync(), isTrue); expect(sampleAppAndroid .childDirectory('gradle') .childDirectory('wrapper') .childFile('gradle-wrapper.properties') .existsSync(), isTrue); expect(sampleAppAndroid .childDirectory('gradle') .childDirectory('wrapper') .childFile('gradle-wrapper.properties') .readAsStringSync(), 'distributionBase=GRADLE_USER_HOME\n' 'distributionPath=wrapper/dists\n' 'zipStoreBase=GRADLE_USER_HOME\n' 'zipStorePath=wrapper/dists\n' 'distributionUrl=https\\://services.gradle.org/distributions/gradle-5.6.2-all.zip\n'); }, overrides: <Type, Generator>{ Cache: () => Cache(rootOverride: tempDir), FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('injects the wrapper when some files are missing', () { final Directory sampleAppAndroid = globals.fs.directory('/sample-app/android'); sampleAppAndroid.createSync(recursive: true); // There's an existing gradlew sampleAppAndroid.childFile('gradlew').writeAsStringSync('existing gradlew'); gradleUtils.injectGradleWrapperIfNeeded(sampleAppAndroid); expect(sampleAppAndroid.childFile('gradlew').existsSync(), isTrue); expect(sampleAppAndroid.childFile('gradlew').readAsStringSync(), equals('existing gradlew')); expect(sampleAppAndroid .childDirectory('gradle') .childDirectory('wrapper') .childFile('gradle-wrapper.jar') .existsSync(), isTrue); expect(sampleAppAndroid .childDirectory('gradle') .childDirectory('wrapper') .childFile('gradle-wrapper.properties') .existsSync(), isTrue); expect(sampleAppAndroid .childDirectory('gradle') .childDirectory('wrapper') .childFile('gradle-wrapper.properties') .readAsStringSync(), 'distributionBase=GRADLE_USER_HOME\n' 'distributionPath=wrapper/dists\n' 'zipStoreBase=GRADLE_USER_HOME\n' 'zipStorePath=wrapper/dists\n' 'distributionUrl=https\\://services.gradle.org/distributions/gradle-5.6.2-all.zip\n'); }, overrides: <Type, Generator>{ Cache: () => Cache(rootOverride: tempDir), FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); }); group('migrateToR8', () { MemoryFileSystem memoryFileSystem; setUp(() { memoryFileSystem = MemoryFileSystem(); }); testUsingContext("throws ToolExit if gradle.properties doesn't exist", () { final Directory sampleAppAndroid = globals.fs.directory('/sample-app/android'); sampleAppAndroid.createSync(recursive: true); expect(() { gradleUtils.migrateToR8(sampleAppAndroid); }, throwsToolExit(message: 'Expected file ${sampleAppAndroid.path}')); }, overrides: <Type, Generator>{ FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('throws ToolExit if it cannot write gradle.properties', () { final MockDirectory sampleAppAndroid = MockDirectory(); final MockFile gradleProperties = MockFile(); when(gradleProperties.path).thenReturn('foo/gradle.properties'); when(gradleProperties.existsSync()).thenReturn(true); when(gradleProperties.readAsStringSync()).thenReturn(''); when(gradleProperties.writeAsStringSync('android.enableR8=true\n', mode: FileMode.append)) .thenThrow(const FileSystemException()); when(sampleAppAndroid.childFile('gradle.properties')) .thenReturn(gradleProperties); expect(() { gradleUtils.migrateToR8(sampleAppAndroid); }, throwsToolExit(message: 'The tool failed to add `android.enableR8=true` to foo/gradle.properties. ' 'Please update the file manually and try this command again.')); }); testUsingContext('does not update gradle.properties if it already uses R8', () { final Directory sampleAppAndroid = globals.fs.directory('/sample-app/android'); sampleAppAndroid.createSync(recursive: true); sampleAppAndroid.childFile('gradle.properties') .writeAsStringSync('android.enableR8=true'); gradleUtils.migrateToR8(sampleAppAndroid); expect(testLogger.traceText, contains('gradle.properties already sets `android.enableR8`')); expect(sampleAppAndroid.childFile('gradle.properties').readAsStringSync(), equals('android.enableR8=true')); }, overrides: <Type, Generator>{ FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('sets android.enableR8=true', () { final Directory sampleAppAndroid = globals.fs.directory('/sample-app/android'); sampleAppAndroid.createSync(recursive: true); sampleAppAndroid.childFile('gradle.properties') .writeAsStringSync('org.gradle.jvmargs=-Xmx1536M\n'); gradleUtils.migrateToR8(sampleAppAndroid); expect(testLogger.traceText, contains('set `android.enableR8=true` in gradle.properties')); expect( sampleAppAndroid.childFile('gradle.properties').readAsStringSync(), equals( 'org.gradle.jvmargs=-Xmx1536M\n' 'android.enableR8=true\n' ), ); }, overrides: <Type, Generator>{ FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('appends android.enableR8=true to the new line', () { final Directory sampleAppAndroid = globals.fs.directory('/sample-app/android'); sampleAppAndroid.createSync(recursive: true); sampleAppAndroid.childFile('gradle.properties') .writeAsStringSync('org.gradle.jvmargs=-Xmx1536M'); gradleUtils.migrateToR8(sampleAppAndroid); expect(testLogger.traceText, contains('set `android.enableR8=true` in gradle.properties')); expect( sampleAppAndroid.childFile('gradle.properties').readAsStringSync(), equals( 'org.gradle.jvmargs=-Xmx1536M\n' 'android.enableR8=true\n' ), ); }, overrides: <Type, Generator>{ FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any() }); }); group('GradleUtils.getExecutable', () { final String gradlewFilename = globals.platform.isWindows ? 'gradlew.bat' : 'gradlew'; MemoryFileSystem memoryFileSystem; OperatingSystemUtils operatingSystemUtils; MockGradleUtils gradleUtils; setUp(() { memoryFileSystem = MemoryFileSystem(); operatingSystemUtils = MockOperatingSystemUtils(); gradleUtils = MockGradleUtils(); }); testUsingContext('returns the gradlew path', () { final Directory androidDirectory = globals.fs.directory('/android')..createSync(); androidDirectory.childFile('gradlew').createSync(); androidDirectory.childFile('gradlew.bat').createSync(); androidDirectory.childFile('gradle.properties').createSync(); when(gradleUtils.injectGradleWrapperIfNeeded(any)).thenReturn(null); when(gradleUtils.migrateToR8(any)).thenReturn(null); expect( GradleUtils().getExecutable(FlutterProject.current()), androidDirectory.childFile(gradlewFilename).path, ); }, overrides: <Type, Generator>{ FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), OperatingSystemUtils: () => operatingSystemUtils, GradleUtils: () => gradleUtils, }); testUsingContext('gives execute permission to gradle', () { final FlutterProject flutterProject = MockFlutterProject(); final AndroidProject androidProject = MockAndroidProject(); when(flutterProject.android).thenReturn(androidProject); final FileStat gradleStat = MockFileStat(); when(gradleStat.mode).thenReturn(444); final File gradlew = MockFile(); when(gradlew.path).thenReturn('gradlew'); when(gradlew.absolute).thenReturn(gradlew); when(gradlew.statSync()).thenReturn(gradleStat); when(gradlew.existsSync()).thenReturn(true); final Directory androidDirectory = MockDirectory(); when(androidDirectory.childFile(gradlewFilename)).thenReturn(gradlew); when(androidProject.hostAppGradleRoot).thenReturn(androidDirectory); when(gradleUtils.injectGradleWrapperIfNeeded(any)).thenReturn(null); when(gradleUtils.migrateToR8(any)).thenReturn(null); GradleUtils().getExecutable(flutterProject); verify(operatingSystemUtils.makeExecutable(gradlew)).called(1); }, overrides: <Type, Generator>{ FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), OperatingSystemUtils: () => operatingSystemUtils, GradleUtils: () => gradleUtils, }); testUsingContext('gives execute permission to gradle even when not all permission flags are set', () { final FlutterProject flutterProject = MockFlutterProject(); final AndroidProject androidProject = MockAndroidProject(); when(flutterProject.android).thenReturn(androidProject); final FileStat gradleStat = MockFileStat(); when(gradleStat.mode).thenReturn(400); final File gradlew = MockFile(); when(gradlew.path).thenReturn('gradlew'); when(gradlew.absolute).thenReturn(gradlew); when(gradlew.statSync()).thenReturn(gradleStat); when(gradlew.existsSync()).thenReturn(true); final Directory androidDirectory = MockDirectory(); when(androidDirectory.childFile(gradlewFilename)).thenReturn(gradlew); when(androidProject.hostAppGradleRoot).thenReturn(androidDirectory); when(gradleUtils.injectGradleWrapperIfNeeded(any)).thenReturn(null); when(gradleUtils.migrateToR8(any)).thenReturn(null); GradleUtils().getExecutable(flutterProject); verify(operatingSystemUtils.makeExecutable(gradlew)).called(1); }, overrides: <Type, Generator>{ FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), OperatingSystemUtils: () => operatingSystemUtils, GradleUtils: () => gradleUtils, }); testUsingContext("doesn't give execute permission to gradle if not needed", () { final FlutterProject flutterProject = MockFlutterProject(); final AndroidProject androidProject = MockAndroidProject(); when(flutterProject.android).thenReturn(androidProject); final FileStat gradleStat = MockFileStat(); when(gradleStat.mode).thenReturn(0x49 /* a+x */); final File gradlew = MockFile(); when(gradlew.path).thenReturn('gradlew'); when(gradlew.absolute).thenReturn(gradlew); when(gradlew.statSync()).thenReturn(gradleStat); when(gradlew.existsSync()).thenReturn(true); final Directory androidDirectory = MockDirectory(); when(androidDirectory.childFile(gradlewFilename)).thenReturn(gradlew); when(androidProject.hostAppGradleRoot).thenReturn(androidDirectory); when(gradleUtils.injectGradleWrapperIfNeeded(any)).thenReturn(null); when(gradleUtils.migrateToR8(any)).thenReturn(null); GradleUtils().getExecutable(flutterProject); verifyNever(operatingSystemUtils.makeExecutable(gradlew)); }, overrides: <Type, Generator>{ FileSystem: () => memoryFileSystem, ProcessManager: () => FakeProcessManager.any(), OperatingSystemUtils: () => operatingSystemUtils, GradleUtils: () => gradleUtils, }); }); } class MockAndroidProject extends Mock implements AndroidProject {} class MockDirectory extends Mock implements Directory {} class MockFile extends Mock implements File {} class MockFileStat extends Mock implements FileStat {} class MockFlutterProject extends Mock implements FlutterProject {} class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {} class MockGradleUtils extends Mock implements GradleUtils {}