diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index e740ebd40b..eb647f27ac 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -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); diff --git a/packages/flutter_tools/lib/src/android/migrations/cmake_android_16k_pages_migration.dart b/packages/flutter_tools/lib/src/android/migrations/cmake_android_16k_pages_migration.dart new file mode 100644 index 0000000000..7f3a9447c5 --- /dev/null +++ b/packages/flutter_tools/lib/src/android/migrations/cmake_android_16k_pages_migration.dart @@ -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 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); + } + } +} diff --git a/packages/flutter_tools/templates/plugin_ffi/src.tmpl/CMakeLists.txt.tmpl b/packages/flutter_tools/templates/plugin_ffi/src.tmpl/CMakeLists.txt.tmpl index eb1f02bc59..cf6b028e91 100644 --- a/packages/flutter_tools/templates/plugin_ffi/src.tmpl/CMakeLists.txt.tmpl +++ b/packages/flutter_tools/templates/plugin_ffi/src.tmpl/CMakeLists.txt.tmpl @@ -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() diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart index bb2e1419bf..a330992c17 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart @@ -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)); }); diff --git a/packages/flutter_tools/test/general.shard/android/migration/cmake_android_16k_pages_migration_test.dart b/packages/flutter_tools/test/general.shard/android/migration/cmake_android_16k_pages_migration_test.dart new file mode 100644 index 0000000000..ebb8e05c0e --- /dev/null +++ b/packages/flutter_tools/test/general.shard/android/migration/cmake_android_16k_pages_migration_test.dart @@ -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; +}