Unverified Commit 354f80b8 authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Check and give execute permission to Gradle if needed (#46748)

parent 8b88c829
......@@ -46,14 +46,17 @@ class GradleUtils {
final Directory androidDir = project.android.hostAppGradleRoot;
// Update the project if needed.
// TODO(egarciad): https://github.com/flutter/flutter/issues/40460
migrateToR8(androidDir);
injectGradleWrapperIfNeeded(androidDir);
gradleUtils.migrateToR8(androidDir);
gradleUtils.injectGradleWrapperIfNeeded(androidDir);
final File gradle = androidDir.childFile(
platform.isWindows ? 'gradlew.bat' : 'gradlew',
);
if (gradle.existsSync()) {
printTrace('Using gradle from ${gradle.absolute.path}.');
// If the Gradle executable doesn't have execute permission,
// then attempt to set it.
_giveExecutePermissionIfNeeded(gradle);
return gradle.absolute.path;
}
throwToolExit(
......@@ -62,71 +65,69 @@ class GradleUtils {
);
return null;
}
}
/// Migrates the Android's [directory] to R8.
/// https://developer.android.com/studio/build/shrink-code
@visibleForTesting
void migrateToR8(Directory directory) {
final File gradleProperties = directory.childFile('gradle.properties');
if (!gradleProperties.existsSync()) {
throwToolExit(
'Expected file ${gradleProperties.path}. '
'Please ensure that this file exists or that ${gradleProperties.dirname} can be read.'
);
}
final String propertiesContent = gradleProperties.readAsStringSync();
if (propertiesContent.contains('android.enableR8')) {
printTrace('gradle.properties already sets `android.enableR8`');
return;
}
printTrace('set `android.enableR8=true` in gradle.properties');
try {
if (propertiesContent.isNotEmpty && !propertiesContent.endsWith('\n')) {
// Add a new line if the file doesn't end with a new line.
gradleProperties.writeAsStringSync('\n', mode: FileMode.append);
/// Migrates the Android's [directory] to R8.
/// https://developer.android.com/studio/build/shrink-code
@visibleForTesting
void migrateToR8(Directory directory) {
final File gradleProperties = directory.childFile('gradle.properties');
if (!gradleProperties.existsSync()) {
throwToolExit(
'Expected file ${gradleProperties.path}. '
'Please ensure that this file exists or that ${gradleProperties.dirname} can be read.'
);
}
final String propertiesContent = gradleProperties.readAsStringSync();
if (propertiesContent.contains('android.enableR8')) {
printTrace('gradle.properties already sets `android.enableR8`');
return;
}
printTrace('set `android.enableR8=true` in gradle.properties');
try {
if (propertiesContent.isNotEmpty && !propertiesContent.endsWith('\n')) {
// Add a new line if the file doesn't end with a new line.
gradleProperties.writeAsStringSync('\n', mode: FileMode.append);
}
gradleProperties.writeAsStringSync('android.enableR8=true\n', mode: FileMode.append);
} on FileSystemException {
throwToolExit(
'The tool failed to add `android.enableR8=true` to ${gradleProperties.path}. '
'Please update the file manually and try this command again.'
);
}
gradleProperties.writeAsStringSync('android.enableR8=true\n', mode: FileMode.append);
} on FileSystemException {
throwToolExit(
'The tool failed to add `android.enableR8=true` to ${gradleProperties.path}. '
'Please update the file manually and try this command again.'
);
}
}
/// Injects the Gradle wrapper files if any of these files don't exist in [directory].
void injectGradleWrapperIfNeeded(Directory directory) {
copyDirectorySync(
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) {
final String modes = sourceFile.statSync().modeString();
if (modes != null && modes.contains('x')) {
os.makeExecutable(destinationFile);
}
},
);
// Add the `gradle-wrapper.properties` file if it doesn't exist.
final File propertiesFile = directory.childFile(
fs.path.join('gradle', 'wrapper', 'gradle-wrapper.properties'));
if (!propertiesFile.existsSync()) {
final String gradleVersion = getGradleVersionForAndroidPlugin(directory);
propertiesFile.writeAsStringSync('''
/// Injects the Gradle wrapper files if any of these files don't exist in [directory].
void injectGradleWrapperIfNeeded(Directory directory) {
copyDirectorySync(
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 (_hasExecutePermission(sourceFile)) {
_giveExecutePermissionIfNeeded(destinationFile);
}
},
);
// Add the `gradle-wrapper.properties` file if it doesn't exist.
final File propertiesFile = directory.childFile(
fs.path.join('gradle', 'wrapper', 'gradle-wrapper.properties'));
if (!propertiesFile.existsSync()) {
final String gradleVersion = getGradleVersionForAndroidPlugin(directory);
propertiesFile.writeAsStringSync('''
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,
);
);
}
}
}
const String _defaultGradleVersion = '5.6.2';
final RegExp _androidPluginRegExp = RegExp('com\.android\.tools\.build\:gradle\:(\\d+\.\\d+\.\\d+\)');
......@@ -150,6 +151,24 @@ String getGradleVersionForAndroidPlugin(Directory directory) {
return getGradleVersionFor(androidPluginVersion);
}
const int _kExecPermissionMask = 0x49; // a+x
/// Returns [true] if [executable] has execute permission.
bool _hasExecutePermission(File executable) {
final FileStat stat = executable.statSync();
assert(stat.type != FileSystemEntityType.notFound);
printTrace('${executable.path} mode: ${stat.mode} ${stat.modeString()}.');
return stat.mode & _kExecPermissionMask == _kExecPermissionMask;
}
/// Gives execute permission to [executable] if it doesn't have it already.
void _giveExecutePermissionIfNeeded(File executable) {
if (!_hasExecutePermission(executable)) {
printTrace('Trying to give execute permission to ${executable.path}.');
os.makeExecutable(executable);
}
}
/// Returns true if [targetVersion] is within the range [min] and [max] inclusive.
bool _isWithinVersionRange(
String targetVersion, {
......
......@@ -904,16 +904,13 @@ class AndroidMavenArtifacts extends ArtifactSet {
Future<void> update() async {
final Directory tempDir =
fs.systemTempDirectory.createTempSync('flutter_gradle_wrapper.');
injectGradleWrapperIfNeeded(tempDir);
gradleUtils.injectGradleWrapperIfNeeded(tempDir);
final Status status = logger.startProgress('Downloading Android Maven dependencies...',
timeout: timeoutConfiguration.slowOperation);
final File gradle = tempDir.childFile(
platform.isWindows ? 'gradlew.bat' : 'gradlew',
);
assert(gradle.existsSync());
os.makeExecutable(gradle);
try {
final String gradleExecutable = gradle.absolute.path;
final String flutterSdk = escapePath(Cache.flutterRoot);
......
......@@ -630,7 +630,7 @@ class AndroidProject {
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory);
gradle.injectGradleWrapperIfNeeded(_editableHostAppDirectory);
gradle.gradleUtils.injectGradleWrapperIfNeeded(_editableHostAppDirectory);
gradle.writeLocalProperties(_editableHostAppDirectory.childFile('local.properties'));
await injectPlugins(parent);
}
......@@ -647,7 +647,7 @@ class AndroidProject {
featureFlags.isAndroidEmbeddingV2Enabled ? 'library_new_embedding' : 'library',
), ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
gradle.injectGradleWrapperIfNeeded(ephemeralDirectory);
gradle.gradleUtils.injectGradleWrapperIfNeeded(ephemeralDirectory);
}
void _overwriteFromTemplate(String path, Directory target) {
......
......@@ -814,107 +814,6 @@ flutter:
});
});
group('migrateToR8', () {
MemoryFileSystem memoryFileSystem;
setUp(() {
memoryFileSystem = MemoryFileSystem();
});
testUsingContext('throws ToolExit if gradle.properties doesn\'t exist', () {
final Directory sampleAppAndroid = fs.directory('/sample-app/android');
sampleAppAndroid.createSync(recursive: true);
expect(() {
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(() {
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 = fs.directory('/sample-app/android');
sampleAppAndroid.createSync(recursive: true);
sampleAppAndroid.childFile('gradle.properties')
.writeAsStringSync('android.enableR8=true');
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 = fs.directory('/sample-app/android');
sampleAppAndroid.createSync(recursive: true);
sampleAppAndroid.childFile('gradle.properties')
.writeAsStringSync('org.gradle.jvmargs=-Xmx1536M\n');
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 = fs.directory('/sample-app/android');
sampleAppAndroid.createSync(recursive: true);
sampleAppAndroid.childFile('gradle.properties')
.writeAsStringSync('org.gradle.jvmargs=-Xmx1536M');
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('isAppUsingAndroidX', () {
FileSystem fs;
......
......@@ -6,7 +6,10 @@ 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/base/platform.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../../src/common.dart';
......@@ -38,11 +41,11 @@ void main() {
.writeAsStringSync('irrelevant');
});
testUsingContext('Inject the wrapper when all files are missing', () {
testUsingContext('injects the wrapper when all files are missing', () {
final Directory sampleAppAndroid = fs.directory('/sample-app/android');
sampleAppAndroid.createSync(recursive: true);
injectGradleWrapperIfNeeded(sampleAppAndroid);
gradleUtils.injectGradleWrapperIfNeeded(sampleAppAndroid);
expect(sampleAppAndroid.childFile('gradlew').existsSync(), isTrue);
......@@ -74,14 +77,14 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Inject the wrapper when some files are missing', () {
testUsingContext('injects the wrapper when some files are missing', () {
final Directory sampleAppAndroid = fs.directory('/sample-app/android');
sampleAppAndroid.createSync(recursive: true);
// There's an existing gradlew
sampleAppAndroid.childFile('gradlew').writeAsStringSync('existing gradlew');
injectGradleWrapperIfNeeded(sampleAppAndroid);
gradleUtils.injectGradleWrapperIfNeeded(sampleAppAndroid);
expect(sampleAppAndroid.childFile('gradlew').existsSync(), isTrue);
expect(sampleAppAndroid.childFile('gradlew').readAsStringSync(),
......@@ -114,24 +117,211 @@ void main() {
FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
});
group('migrateToR8', () {
MemoryFileSystem memoryFileSystem;
setUp(() {
memoryFileSystem = MemoryFileSystem();
});
testUsingContext('Gives executable permission to gradle', () {
testUsingContext('throws ToolExit if gradle.properties doesn\'t exist', () {
final Directory sampleAppAndroid = fs.directory('/sample-app/android');
sampleAppAndroid.createSync(recursive: true);
// Make gradlew in the wrapper executable.
os.makeExecutable(gradleWrapperDirectory.childFile('gradlew'));
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());
injectGradleWrapperIfNeeded(sampleAppAndroid);
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 = fs.directory('/sample-app/android');
sampleAppAndroid.createSync(recursive: true);
sampleAppAndroid.childFile('gradle.properties')
.writeAsStringSync('android.enableR8=true');
final File gradlew = sampleAppAndroid.childFile('gradlew');
expect(gradlew.existsSync(), isTrue);
expect(gradlew.statSync().modeString().contains('x'), isTrue);
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>{
Cache: () => Cache(rootOverride: tempDir),
FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.any(),
OperatingSystemUtils: () => OperatingSystemUtils(),
});
testUsingContext('sets android.enableR8=true', () {
final Directory sampleAppAndroid = 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 = 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()
});
});
}
\ No newline at end of file
group('GradleUtils.getExecutable', () {
final String gradlewFilename = 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 = 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('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 {}
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