Migrator for android 35/16kb page size cmake flags for plugin_ffi (#156221)

Migrates existing instantions of `--template plugin_ffi` to deal with Android 15 16kb memory pages.

Issue:

* https://github.com/flutter/flutter/issues/155933

@reidbaker I could only find migrations that run from the root application. However the file needing to be migrated is a plugin. The plugin is being referenced from the example in the example dir and that's where the migrator is run, so I wrote the code so that it walks up to find the plugin. Do you know of a way to run migrators to non-root projects? (Can we even safely do so, e.g. the non-root could be in the pub cache, could be a different project on the users' system etc. So maybe checking if we are in the examples dir is the only sane thing to do?)

Tests:

* Added unit tests in `test/general.shard/android/migrations/cmake_android_16k_pages_migration_test.dart`
This commit is contained in:
Daco Harkes 2024-10-08 10:41:09 +02:00 committed by GitHub
parent d768a72b85
commit e695cd6e9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 216 additions and 1 deletions

View File

@ -35,6 +35,7 @@ import 'gradle_errors.dart';
import 'gradle_utils.dart';
import 'java.dart';
import 'migrations/android_studio_java_gradle_conflict_migration.dart';
import 'migrations/cmake_android_16k_pages_migration.dart';
import 'migrations/min_sdk_version_migration.dart';
import 'migrations/multidex_removal_migration.dart';
import 'migrations/top_level_gradle_build_file_migration.dart';
@ -311,6 +312,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
java: globals.java),
MinSdkVersionMigration(project.android, _logger),
MultidexRemovalMigration(project.android, _logger),
CmakeAndroid16kPagesMigration(project.android, _logger),
];
final ProjectMigration migration = ProjectMigration(migrators);

View File

@ -0,0 +1,74 @@
// 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 '../../base/file_system.dart';
import '../../base/project_migrator.dart';
import '../../project.dart';
/// Adds the snippet to the CMake file to compile for Android 15.
///
/// Location of CMakeLists.txt is the src/CMakeLists.txt in the plugin
/// created with --template plugin_ffi.
///
/// ```cmake
/// if (ANDROID)
/// # Support Android 15 16k page size.
/// target_link_options({{projectName}} PRIVATE "-Wl,-z,max-page-size=16384")
/// endif()
/// ```
class CmakeAndroid16kPagesMigration extends ProjectMigrator {
CmakeAndroid16kPagesMigration(AndroidProject project, super.logger)
: _project = project;
final AndroidProject _project;
@override
Future<void> migrate() async {
// If the migrator is run in the example directory, navigate to the
// plugin directory which contains src/CMakeLists.txt.
final File cmakeLists = _project.parent.directory.parent
.childDirectory('src/')
.childFile('CMakeLists.txt');
if (!cmakeLists.existsSync()) {
logger.printTrace(
'CMake project not found, skipping support Android 15 16k page size migration.');
return;
}
final String original = cmakeLists.readAsStringSync();
if (original.contains('-Wl,-z,max-page-size=16384')) {
// Link flags already present.
return;
}
final RegExp regex =
RegExp(r'target_compile_definitions\(([^ ]*) PUBLIC DART_SHARED_LIB\)');
final String? projectName = regex.firstMatch(original)?.group(1);
const String before = '''
PUBLIC DART_SHARED_LIB)
''';
/// Relevant template: templates/plugin_ffi/src.tmpl/CMakeLists.txt.tmpl
final String linkerFlags = '''
if (ANDROID)
# Support Android 15 16k page size.
target_link_options($projectName PRIVATE "-Wl,-z,max-page-size=16384")
endif()
''';
final String updated = original.replaceFirst(
before,
'$before$linkerFlags',
);
if (original != updated) {
logger.printStatus(
'CMake missing support Android 15 16k page size, updating.');
cmakeLists.writeAsStringSync(updated);
}
}
}

View File

@ -16,7 +16,7 @@ set_target_properties({{projectName}} PROPERTIES
target_compile_definitions({{projectName}} PUBLIC DART_SHARED_LIB)
if(ANDROID)
if (ANDROID)
# Support Android 15 16k page size
target_link_options({{projectName}} PRIVATE "-Wl,-z,max-page-size=16384")
endif()

View File

@ -3202,6 +3202,9 @@ void main() {
expect(cmakeLists.existsSync(), true);
final String cmakeListsContent = await cmakeLists.readAsString();
// If we ever change the flags, this should be accounted for in the
// migration as well:
// lib/src/android/migrations/cmake_android_16k_pages_migration.dart
const String expected16KbFlags = 'PRIVATE "-Wl,-z,max-page-size=16384")';
expect(cmakeListsContent, contains(expected16KbFlags));
});

View File

@ -0,0 +1,136 @@
// 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/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/migrations/cmake_android_16k_pages_migration.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';
import '../../../src/common.dart';
const String _sampleCmakeListsTxtUnmigrated = r'''
# The Flutter tooling requires that developers have CMake 3.10 or later
# installed. You should not increase this version, as doing so will cause
# the plugin to fail to compile for some customers of the plugin.
cmake_minimum_required(VERSION 3.10)
project(my_plugin_library VERSION 0.0.1 LANGUAGES C)
add_library(my_plugin SHARED
"my_plugin.c"
)
set_target_properties(my_plugin PROPERTIES
PUBLIC_HEADER my_plugin.h
OUTPUT_NAME "my_plugin"
)
target_compile_definitions(my_plugin PUBLIC DART_SHARED_LIB)
''';
const String _sampleCmakeListsTxtMigrated = r'''
# The Flutter tooling requires that developers have CMake 3.10 or later
# installed. You should not increase this version, as doing so will cause
# the plugin to fail to compile for some customers of the plugin.
cmake_minimum_required(VERSION 3.10)
project(my_plugin_library VERSION 0.0.1 LANGUAGES C)
add_library(my_plugin SHARED
"my_plugin.c"
)
set_target_properties(my_plugin PROPERTIES
PUBLIC_HEADER my_plugin.h
OUTPUT_NAME "my_plugin"
)
target_compile_definitions(my_plugin PUBLIC DART_SHARED_LIB)
if (ANDROID)
# Support Android 15 16k page size.
target_link_options(my_plugin PRIVATE "-Wl,-z,max-page-size=16384")
endif()
''';
void main() {
group('Android migration', () {
group('CMake file', () {
late MemoryFileSystem memoryFileSystem;
late File cmakeFile;
late BufferLogger bufferLogger;
late FakeAndroidProject project;
late CmakeAndroid16kPagesMigration migration;
setUp(() {
memoryFileSystem = MemoryFileSystem.test();
final Directory pluginDir = memoryFileSystem.currentDirectory;
final Directory exampleDir = pluginDir.childDirectory('example');
exampleDir.createSync();
final Directory androidDir = exampleDir.childDirectory('android');
androidDir.createSync();
final Directory srcDir = pluginDir.childDirectory('src');
srcDir.createSync();
cmakeFile = srcDir.childFile('CMakeLists.txt');
cmakeFile.writeAsString(_sampleCmakeListsTxtMigrated);
bufferLogger = BufferLogger.test();
project = FakeAndroidProject(
parent: FakeFlutterProject(
directory: exampleDir,
),
);
migration = CmakeAndroid16kPagesMigration(project, bufferLogger);
});
testWithoutContext('do nothing when files missing', () async {
cmakeFile.deleteSync();
await migration.migrate();
expect(
bufferLogger.traceText,
contains(
'CMake project not found, skipping support Android 15 16k page size migration.',
),
);
});
testWithoutContext('migrate', () async {
cmakeFile.writeAsStringSync(_sampleCmakeListsTxtUnmigrated);
await migration.migrate();
expect(
cmakeFile.readAsStringSync(),
_sampleCmakeListsTxtMigrated,
);
});
testWithoutContext('do nothing when already migrated', () async {
expect(
cmakeFile.readAsStringSync(),
_sampleCmakeListsTxtMigrated,
);
await migration.migrate();
expect(
cmakeFile.readAsStringSync(),
_sampleCmakeListsTxtMigrated,
);
});
});
});
}
class FakeAndroidProject extends Fake implements AndroidProject {
FakeAndroidProject({required this.parent});
@override
FlutterProject parent;
}
class FakeFlutterProject extends Fake implements FlutterProject {
FakeFlutterProject({required this.directory});
@override
final Directory directory;
}