Unverified Commit bb8cf609 authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Build local maven repo when using local engine (#44243)

parent 3e4bf575
......@@ -73,14 +73,6 @@ flutter {
}
dependencies {
// TODO(egarciad): Remove these dependencies once https://github.com/flutter/flutter/issues/40866 is fixed.
implementation "androidx.annotation:annotation:1.1.0"
def lifecycle_version = "2.1.0"
implementation "androidx.lifecycle:lifecycle-common:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
implementation "androidx.fragment:fragment:1.1.0"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'androidx.test:runner:1.1.1'
......
......@@ -96,7 +96,6 @@ class FlutterPlugin implements Plugin<Project> {
private String localEngine
private String localEngineSrcPath
private Properties localProperties
private File flutterJar
private String engineVersion
@Override
......@@ -144,8 +143,9 @@ class FlutterPlugin implements Plugin<Project> {
throw new GradleException("flutter.sdk must point to the Flutter SDK directory")
}
engineVersion = Paths.get(flutterRoot.absolutePath, "bin", "internal", "engine.version")
.toFile().text.trim()
engineVersion = useLocalEngine()
? "+" // Match any version since there's only one.
: "1.0.0-" + Paths.get(flutterRoot.absolutePath, "bin", "internal", "engine.version").toFile().text.trim()
String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile();
......@@ -178,32 +178,19 @@ class FlutterPlugin implements Plugin<Project> {
}
}
}
if (useLocalEngine()) {
String engineOutPath = project.property('localEngineOut')
// This is required to pass the local engine to flutter build aot.
String engineOutPath = project.property('local-engine-out')
File engineOut = project.file(engineOutPath)
if (!engineOut.isDirectory()) {
throw new GradleException('localEngineOut must point to a local engine build')
}
Path baseEnginePath = Paths.get(engineOut.absolutePath)
flutterJar = baseEnginePath.resolve("flutter.jar").toFile()
if (!flutterJar.isFile()) {
throw new GradleException("Local engine jar not found: $flutterJar")
throw new GradleException('local-engine-out must point to a local engine build')
}
localEngine = engineOut.name
localEngineSrcPath = engineOut.parentFile.parent
// The local engine is built for one of the build type.
// However, we use the same engine for each of the build types.
project.android.buildTypes.each {
addApiDependencies(project, it.name, project.files {
flutterJar
})
}
} else {
project.android.buildTypes.each this.&addFlutterDependencies
project.android.buildTypes.whenObjectAdded this.&addFlutterDependencies
}
}
/**
* Adds the dependencies required by the Flutter project.
......@@ -212,17 +199,24 @@ class FlutterPlugin implements Plugin<Project> {
* 2. libflutter.so
*/
void addFlutterDependencies(buildType) {
String flutterBuildMode = buildModeFor(buildType)
if (!supportsBuildMode(flutterBuildMode)) {
return
}
String repository = useLocalEngine()
? project.property('local-engine-repo')
: MAVEN_REPO
project.rootProject.allprojects {
repositories {
maven {
url MAVEN_REPO
url repository
}
}
}
String flutterBuildMode = buildModeFor(buildType)
// Add the embedding dependency.
addApiDependencies(project, buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:1.0.0-$engineVersion")
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")
List<String> platforms = getTargetPlatforms().collect()
// Debug mode includes x86 and x64, which are commonly used in emulators.
......@@ -234,7 +228,7 @@ class FlutterPlugin implements Plugin<Project> {
String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_")
// Add the `libflutter.so` dependency.
addApiDependencies(project, buildType.name,
"io.flutter:${arch}_$flutterBuildMode:1.0.0-$engineVersion")
"io.flutter:${arch}_$flutterBuildMode:$engineVersion")
}
}
......@@ -261,9 +255,6 @@ class FlutterPlugin implements Plugin<Project> {
getPluginList().each this.&configurePluginProject
return
}
if (useLocalEngine()) {
throw new GradleException("Local engine isn't supported when building the plugins as AAR")
}
project.repositories {
maven {
url "${getPluginBuildDir()}/outputs/repo"
......@@ -326,24 +317,13 @@ class FlutterPlugin implements Plugin<Project> {
// In AGP 3.5, the embedding must be added as an API implementation,
// so java8 features are desugared against the runtime classpath.
// For more, see https://github.com/flutter/flutter/issues/40126
if (flutterJar) {
addApiDependencies(
pluginProject,
buildType.name,
project.files {
flutterJar
}
)
if (!supportsBuildMode(flutterBuildMode)) {
return
}
addApiDependencies(
pluginProject,
buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:1.0.0-$engineVersion",
{
// Include the embedding transitive dependencies since plugins may depend on them.
transitive = true
}
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion"
)
}
pluginProject.afterEvaluate {
......@@ -425,7 +405,7 @@ class FlutterPlugin implements Plugin<Project> {
}
private Boolean useLocalEngine() {
return project.hasProperty('localEngineOut')
return project.hasProperty('local-engine-repo')
}
private Boolean isVerbose() {
......@@ -452,6 +432,19 @@ class FlutterPlugin implements Plugin<Project> {
return System.getProperty('build-plugins-as-aars') == 'true'
}
// Returns true if the build mode is supported by the current call to Gradle.
// This only relevant when using a local engine. Because the engine
// is built for a specific mode, the call to Gradle must match that mode.
private Boolean supportsBuildMode(String flutterBuildMode) {
if (!useLocalEngine()) {
return true;
}
assert project.hasProperty('local-engine-build-mode')
// Don't configure dependencies for a build mode that the local engine
// doesn't support.
return project.property('local-engine-build-mode') == flutterBuildMode
}
private void addCompileOnlyDependency(Project project, String variantName, Object dependency, Closure config = null) {
if (project.state.failure) {
return
......
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:crypto/crypto.dart';
import 'package:meta/meta.dart';
import 'package:xml/xml.dart' as xml;
import '../android/android_sdk.dart';
import '../artifacts.dart';
......@@ -282,8 +283,17 @@ Future<void> buildGradleApp({
}
if (artifacts is LocalEngineArtifacts) {
final LocalEngineArtifacts localEngineArtifacts = artifacts;
printTrace('Using local engine: ${localEngineArtifacts.engineOutPath}');
command.add('-PlocalEngineOut=${localEngineArtifacts.engineOutPath}');
final Directory localEngineRepo = _getLocalEngineRepo(
engineOutPath: localEngineArtifacts.engineOutPath,
androidBuildInfo: androidBuildInfo,
);
printTrace(
'Using local engine: ${localEngineArtifacts.engineOutPath}\n'
'Local Maven repo: ${localEngineRepo.path}'
);
command.add('-Plocal-engine-repo=${localEngineRepo.path}');
command.add('-Plocal-engine-build-mode=${buildInfo.modeName}');
command.add('-Plocal-engine-out=${localEngineArtifacts.engineOutPath}');
}
if (target != null) {
command.add('-Ptarget=$target');
......@@ -504,8 +514,17 @@ Future<void> buildGradleAar({
}
if (artifacts is LocalEngineArtifacts) {
final LocalEngineArtifacts localEngineArtifacts = artifacts;
printTrace('Using local engine: ${localEngineArtifacts.engineOutPath}');
command.add('-PlocalEngineOut=${localEngineArtifacts.engineOutPath}');
final Directory localEngineRepo = _getLocalEngineRepo(
engineOutPath: localEngineArtifacts.engineOutPath,
androidBuildInfo: androidBuildInfo,
);
printTrace(
'Using local engine: ${localEngineArtifacts.engineOutPath}\n'
'Local Maven repo: ${localEngineRepo.path}'
);
command.add('-Plocal-engine-repo=${localEngineRepo.path}');
command.add('-Plocal-engine-build-mode=${androidBuildInfo.buildInfo.modeName}');
command.add('-Plocal-engine-out=${localEngineArtifacts.engineOutPath}');
}
command.add(aarTask);
......@@ -781,3 +800,110 @@ void _exitWithExpectedFileNotFound({
'but the tool couldn\'t find it.'
);
}
void _createSymlink(String targetPath, String linkPath) {
final File targetFile = fs.file(targetPath);
if (!targetFile.existsSync()) {
throwToolExit('The file $targetPath wasn\'t found in the local engine out directory.');
}
final File linkFile = fs.file(linkPath);
final Link symlink = linkFile.parent.childLink(linkFile.basename);
try {
symlink.createSync(targetPath, recursive: true);
} on FileSystemException catch (exception) {
throwToolExit(
'Failed to create the symlink $linkPath->$targetPath: $exception'
);
}
}
String _getLocalArtifactVersion(String pomPath) {
final File pomFile = fs.file(pomPath);
if (!pomFile.existsSync()) {
throwToolExit('The file $pomPath wasn\'t found in the local engine out directory.');
}
xml.XmlDocument document;
try {
document = xml.parse(pomFile.readAsStringSync());
} on xml.XmlParserException {
throwToolExit(
'Error parsing $pomPath. Please ensure that this is a valid XML document.'
);
} on FileSystemException {
throwToolExit(
'Error reading $pomPath. Please ensure that you have read permission to this'
'file and try again.');
}
final Iterable<xml.XmlElement> project = document.findElements('project');
assert(project.isNotEmpty);
for (xml.XmlElement versionElement in document.findAllElements('version')) {
if (versionElement.parent == project.first) {
return versionElement.text;
}
}
throwToolExit('Error while parsing the <version> element from $pomPath');
return null;
}
/// Returns the local Maven repository for a local engine build.
/// For example, if the engine is built locally at <home>/engine/src/out/android_release_unopt
/// This method generates symlinks in the temp directory to the engine artifacts
/// following the convention specified on https://maven.apache.org/pom.html#Repositories
Directory _getLocalEngineRepo({
@required String engineOutPath,
@required AndroidBuildInfo androidBuildInfo,
}) {
assert(engineOutPath != null);
assert(androidBuildInfo != null);
final String abi = getEnumName(androidBuildInfo.targetArchs.first);
final Directory localEngineRepo = fs.systemTempDirectory
.createTempSync('flutter_tool_local_engine_repo.');
// Remove the local engine repo before the tool exits.
addShutdownHook(
() => localEngineRepo.deleteSync(recursive: true),
ShutdownStage.CLEANUP,
);
final String buildMode = androidBuildInfo.buildInfo.modeName;
final String artifactVersion = _getLocalArtifactVersion(
fs.path.join(
engineOutPath,
'flutter_embedding_$buildMode.pom',
)
);
for (String artifact in const <String>['pom', 'jar']) {
// The Android embedding artifacts.
_createSymlink(
fs.path.join(
engineOutPath,
'flutter_embedding_$buildMode.$artifact',
),
fs.path.join(
localEngineRepo.path,
'io',
'flutter',
'flutter_embedding_$buildMode',
artifactVersion,
'flutter_embedding_$buildMode-$artifactVersion.$artifact',
),
);
// The engine artifacts (libflutter.so).
_createSymlink(
fs.path.join(
engineOutPath,
'${abi}_$buildMode.$artifact',
),
fs.path.join(
localEngineRepo.path,
'io',
'flutter',
'${abi}_$buildMode',
artifactVersion,
'${abi}_$buildMode-$artifactVersion.$artifact',
),
);
}
return localEngineRepo;
}
......@@ -1484,7 +1484,7 @@ plugin2=${plugin2.path}
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[],
localGradleErrors: const <GradleHandledError>[],
);
final BufferLogger logger = context.get<Logger>();
......@@ -1564,6 +1564,96 @@ Consuming the Module
To learn more, visit https://flutter.dev/go/build-aar'''));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fs,
ProcessManager: () => mockProcessManager,
});
testUsingContext('build apk uses selected local engine', () async {
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework,
platform: TargetPlatform.android_arm, mode: anyNamed('mode'))).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'android_arm'));
fs.file('out/android_arm/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync(
'''<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fs.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true);
fs.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true);
fs.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true);
fs.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true);
fs.file('android/gradlew').createSync(recursive: true);
fs.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fs.file('android/build.gradle')
.createSync(recursive: true);
fs.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 1,
)
);
});
await expectLater(() async {
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
}, throwsToolExit());
final List<String> actualGradlewCall = verify(
mockProcessManager.start(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')
),
).captured.last;
expect(actualGradlewCall, contains('/android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
......@@ -1579,6 +1669,21 @@ To learn more, visit https://flutter.dev/go/build-aar'''));
platform: TargetPlatform.android_arm, mode: anyNamed('mode'))).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'android_arm'));
fs.file('out/android_arm/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync(
'''<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fs.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true);
fs.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true);
fs.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true);
fs.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true);
final File manifestFile = fs.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
......@@ -1588,8 +1693,7 @@ To learn more, visit https://flutter.dev/go/build-aar'''));
'''
);
final File gradlew = fs.file('.android/gradlew');
gradlew.createSync(recursive: true);
fs.file('.android/gradlew').createSync(recursive: true);
fs.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
......@@ -1597,12 +1701,6 @@ To learn more, visit https://flutter.dev/go/build-aar'''));
fs.file('.android/build.gradle')
.createSync(recursive: true);
when(mockProcessManager.run(
<String> ['.android/gradlew', '-v'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((_) async => ProcessResult(1, 0, '5.1.1', ''));
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
......@@ -1619,14 +1717,19 @@ To learn more, visit https://flutter.dev/go/build-aar'''));
target: '',
);
final List<String> actualGradlewCall = verify(mockProcessManager.run(
final List<String> actualGradlewCall = verify(
mockProcessManager.run(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')),
workingDirectory: anyNamed('workingDirectory'),
),
).captured.last;
expect(actualGradlewCall, contains('/.android/gradlew'));
expect(actualGradlewCall, contains('-PlocalEngineOut=out/android_arm'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
......@@ -1661,7 +1764,9 @@ FlutterProject generateFakeAppBundle(String directoryName, String fileName) {
}
Platform fakePlatform(String name) {
return FakePlatform.fromPlatform(const LocalPlatform())..operatingSystem = name;
return FakePlatform.fromPlatform(const LocalPlatform())
..operatingSystem = name
..stdoutSupportsAnsi = false;
}
class FakeGradleUtils extends 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