Unverified Commit 6d1c244b authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

Always use POSIX paths for generated CMake files (#65493)

The Windows plugin CMake generation had code to ensure that the paths
written to it used POSIX separators, but the Linux version didn't; that
meant that plugin updates run on Windows machines would corrupt the
generated (but checked in) Linux CMake file.

This change shares that code so that both will use POSIX paths
regardless of what OS they are generated on.

Fixes https://github.com/flutter/flutter/issues/64591
parent 6d360562
......@@ -929,20 +929,30 @@ Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plug
}
}
/// The relative path from a project's main CMake file to the plugin symlink
/// directory to use in the generated plugin CMake file.
///
/// Because the generated file is checked in, it can't use absolute paths. It is
/// designed to be included by the main CMakeLists.txt, so it relative to
/// that file, rather than the generated file.
String _cmakeRelativePluginSymlinkDirectoryPath(CmakeBasedProject project) {
final String makefileDirPath = project.cmakeFile.parent.absolute.path;
// CMake alway uses posix-style path separators, regardless of the platform.
final path.Context cmakePathContext = path.Context(style: path.Style.posix);
final List<String> relativePathComponents = globals.fs.path.split(globals.fs.path.relative(
project.pluginSymlinkDirectory.absolute.path,
from: makefileDirPath,
));
return cmakePathContext.joinAll(relativePathComponents);
}
Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins) async {
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, LinuxPlugin.kConfigKey);
final List<Map<String, dynamic>> linuxPlugins = _extractPlatformMaps(nativePlugins, LinuxPlugin.kConfigKey);
// The generated file is checked in, so can't use absolute paths. It is
// included by the main CMakeLists.txt, so relative paths must be relative to
// that file's directory.
final String makefileDirPath = project.linux.cmakeFile.parent.absolute.path;
final Map<String, dynamic> context = <String, dynamic>{
'os': 'linux',
'plugins': linuxPlugins,
'pluginsDir': globals.fs.path.relative(
project.linux.pluginSymlinkDirectory.absolute.path,
from: makefileDirPath,
),
'pluginsDir': _cmakeRelativePluginSymlinkDirectoryPath(project.linux),
};
await _writeLinuxPluginRegistrant(project.linux.managedDirectory, context);
await _writePluginCmakefile(project.linux.generatedPluginCmakeFile, context);
......@@ -1005,19 +1015,10 @@ List<Plugin> _filterNativePlugins(List<Plugin> plugins, String platformKey) {
Future<void> _writeWindowsPluginFiles(FlutterProject project, List<Plugin> plugins) async {
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey);
final List<Map<String, dynamic>> windowsPlugins = _extractPlatformMaps(nativePlugins, WindowsPlugin.kConfigKey);
// The generated file is checked in, so can't use absolute paths. It is
// included by the main CMakeLists.txt, so relative paths must be relative to
// that file's directory.
final String makefileDirPath = project.windows.cmakeFile.parent.absolute.path;
final path.Context cmakePathContext = path.Context(style: path.Style.posix);
final List<String> relativePathComponents = globals.fs.path.split(globals.fs.path.relative(
project.windows.pluginSymlinkDirectory.absolute.path,
from: makefileDirPath,
));
final Map<String, dynamic> context = <String, dynamic>{
'os': 'windows',
'plugins': windowsPlugins,
'pluginsDir': cmakePathContext.joinAll(relativePathComponents),
'pluginsDir': _cmakeRelativePluginSymlinkDirectoryPath(project.windows),
};
await _writeCppPluginRegistrant(project.windows.managedDirectory, context);
await _writePluginCmakefile(project.windows.generatedPluginCmakeFile, context);
......
......@@ -336,6 +336,9 @@ abstract class CmakeBasedProject {
/// Includable CMake with rules and variables for plugin builds.
File get generatedPluginCmakeFile;
/// The directory to write plugin symlinks.
Directory get pluginSymlinkDirectory;
}
/// Represents the iOS sub-project of a Flutter project.
......@@ -1043,6 +1046,9 @@ class WindowsProject extends FlutterProjectPlatform implements CmakeBasedProject
@override
File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
@override
Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
Directory get _editableDirectory => parent.directory.childDirectory('windows');
/// The directory in the project that is managed by Flutter. As much as
......@@ -1055,9 +1061,6 @@ class WindowsProject extends FlutterProjectPlatform implements CmakeBasedProject
/// checked in should live here.
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
/// The directory to write plugin symlinks.
Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
Future<void> ensureReadyForPlatformSpecificTooling() async {}
}
......@@ -1097,7 +1100,7 @@ class LinuxProject extends FlutterProjectPlatform implements CmakeBasedProject {
@override
File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
/// The directory to write plugin symlinks.
@override
Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
Future<void> ensureReadyForPlatformSpecificTooling() async {}
......
......@@ -32,49 +32,55 @@ void main() {
MockWebProject webProject;
MockWindowsProject windowsProject;
MockLinuxProject linuxProject;
File packagesFile;
Directory dummyPackageDirectory;
SystemClock mockClock;
FlutterVersion mockVersion;
// A Windows-style filesystem. This is not populated by default, so tests
// using it instead of fs must re-run any necessary setup (e.g.,
// setUpProject).
FileSystem fsWindows;
setUp(() async {
fs = MemoryFileSystem();
mockClock = MockClock();
mockVersion = MockFlutterVersion();
// Add basic properties to the Flutter project and subprojects
// Adds basic properties to the flutterProject and its subprojects.
void setUpProject(FileSystem fileSystem) {
flutterProject = MockFlutterProject();
when(flutterProject.directory).thenReturn(fs.directory('/'));
// TODO(franciscojma): Remove logic for .flutter-plugins it's deprecated.
when(flutterProject.directory).thenReturn(fileSystem.systemTempDirectory.childDirectory('app'));
// TODO(franciscojma): Remove logic for .flutter-plugins once it's deprecated.
when(flutterProject.flutterPluginsFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins'));
when(flutterProject.flutterPluginsDependenciesFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins-dependencies'));
iosProject = MockIosProject();
when(flutterProject.ios).thenReturn(iosProject);
final Directory iosDirectory = flutterProject.directory.childDirectory('ios');
when(iosProject.pluginRegistrantHost).thenReturn(flutterProject.directory.childDirectory('Runner'));
when(iosProject.podfile).thenReturn(flutterProject.directory.childDirectory('ios').childFile('Podfile'));
when(iosProject.podManifestLock).thenReturn(flutterProject.directory.childDirectory('ios').childFile('Podfile.lock'));
when(iosProject.podfile).thenReturn(iosDirectory.childFile('Podfile'));
when(iosProject.podManifestLock).thenReturn(iosDirectory.childFile('Podfile.lock'));
when(iosProject.pluginConfigKey).thenReturn('ios');
when(iosProject.existsSync()).thenReturn(false);
macosProject = MockMacOSProject();
when(flutterProject.macos).thenReturn(macosProject);
when(macosProject.podfile).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile'));
when(macosProject.podManifestLock).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile.lock'));
final Directory macosManagedDirectory = flutterProject.directory.childDirectory('macos').childDirectory('Flutter');
final Directory macosDirectory = flutterProject.directory.childDirectory('macos');
when(macosProject.podfile).thenReturn(macosDirectory.childFile('Podfile'));
when(macosProject.podManifestLock).thenReturn(macosDirectory.childFile('Podfile.lock'));
final Directory macosManagedDirectory = macosDirectory.childDirectory('Flutter');
when(macosProject.managedDirectory).thenReturn(macosManagedDirectory);
when(macosProject.pluginConfigKey).thenReturn('macos');
when(macosProject.existsSync()).thenReturn(false);
androidProject = MockAndroidProject();
when(flutterProject.android).thenReturn(androidProject);
when(androidProject.pluginRegistrantHost).thenReturn(flutterProject.directory.childDirectory('android').childDirectory('app'));
when(androidProject.hostAppGradleRoot).thenReturn(flutterProject.directory.childDirectory('android'));
final Directory androidDirectory = flutterProject.directory.childDirectory('android');
when(androidProject.pluginRegistrantHost).thenReturn(androidDirectory.childDirectory('app'));
when(androidProject.hostAppGradleRoot).thenReturn(androidDirectory);
when(androidProject.pluginConfigKey).thenReturn('android');
when(androidProject.existsSync()).thenReturn(false);
webProject = MockWebProject();
when(flutterProject.web).thenReturn(webProject);
when(webProject.libDirectory).thenReturn(flutterProject.directory.childDirectory('lib'));
when(webProject.existsSync()).thenReturn(true);
when(webProject.pluginConfigKey).thenReturn('web');
when(webProject.existsSync()).thenReturn(false);
windowsProject = MockWindowsProject();
when(flutterProject.windows).thenReturn(windowsProject);
when(windowsProject.pluginConfigKey).thenReturn('windows');
......@@ -84,6 +90,7 @@ void main() {
when(windowsProject.generatedPluginCmakeFile).thenReturn(windowsManagedDirectory.childFile('generated_plugins.mk'));
when(windowsProject.pluginSymlinkDirectory).thenReturn(windowsManagedDirectory.childDirectory('ephemeral').childDirectory('.plugin_symlinks'));
when(windowsProject.existsSync()).thenReturn(false);
linuxProject = MockLinuxProject();
when(flutterProject.linux).thenReturn(linuxProject);
when(linuxProject.pluginConfigKey).thenReturn('linux');
......@@ -95,6 +102,17 @@ void main() {
when(linuxProject.cmakeFile).thenReturn(linuxManagedDirectory.parent.childFile('CMakeLists.txt'));
when(linuxProject.generatedPluginCmakeFile).thenReturn(linuxManagedDirectory.childFile('generated_plugins.mk'));
when(linuxProject.existsSync()).thenReturn(false);
}
setUp(() async {
fs = MemoryFileSystem();
fsWindows = MemoryFileSystem(style: FileSystemStyle.windows);
mockClock = MockClock();
mockVersion = MockFlutterVersion();
// Add basic properties to the Flutter project and subprojects
setUpProject(fs);
flutterProject.directory.childFile('.packages').createSync(recursive: true);
when(mockClock.now()).thenAnswer(
(Invocation _) => DateTime(1970, 1, 1)
......@@ -102,12 +120,6 @@ void main() {
when(mockVersion.frameworkVersion).thenAnswer(
(Invocation _) => '1.0.0'
);
// Set up a simple .packages file for all the tests to use, pointing to one package.
dummyPackageDirectory = fs.directory('/pubcache/apackage/lib/');
packagesFile = fs.file(fs.path.join(flutterProject.directory.path, '.packages'));
packagesFile..createSync(recursive: true)
..writeAsStringSync('apackage:file://${dummyPackageDirectory.path}\n');
});
const String _pluginYaml = '''
......@@ -129,9 +141,18 @@ void main() {
pluginClass: SomePlugin
package: AndroidPackage
''';
// Makes the dummy package pointed to by packagesFile look like a plugin.
void configureDummyPackageAsPlugin() {
dummyPackageDirectory.parent.childFile('pubspec.yaml')..createSync(recursive: true)..writeAsStringSync(_pluginYaml);
// Makes a fake plugin package, adds it to flutterProject, and returns its directory.
Directory createFakePlugin(FileSystem fileSystem) {
const String name = 'apackage';
final Directory packageDirectory = fileSystem.systemTempDirectory.childDirectory('cache').childDirectory(name);
flutterProject.directory.childFile('.packages')
..createSync(recursive: true)
..writeAsStringSync('$name:file://${packageDirectory.childFile('lib').uri}\n');
packageDirectory.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync(_pluginYaml);
return packageDirectory;
}
void createNewJavaPlugin1() {
......@@ -347,7 +368,7 @@ dependencies:
});
testUsingContext('Refreshing the plugin list creates a plugin directory when there are plugins', () async {
configureDummyPackageAsPlugin();
createFakePlugin(fs);
when(iosProject.existsSync()).thenReturn(true);
await refreshPluginsList(flutterProject);
......@@ -466,7 +487,7 @@ dependencies:
testUsingContext('Changes to the plugin list invalidates the Cocoapod lockfiles', () async {
simulatePodInstallRun(iosProject);
simulatePodInstallRun(macosProject);
configureDummyPackageAsPlugin();
createFakePlugin(fs);
when(iosProject.existsSync()).thenReturn(true);
when(macosProject.existsSync()).thenReturn(true);
......@@ -481,7 +502,7 @@ dependencies:
});
testUsingContext('No changes to the plugin list does not invalidate the Cocoapod lockfiles', () async {
configureDummyPackageAsPlugin();
createFakePlugin(fs);
when(iosProject.existsSync()).thenReturn(true);
when(macosProject.existsSync()).thenReturn(true);
......@@ -856,9 +877,8 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toStr
when(featureFlags.isMacOSEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(true);
// Create a plugin without a pluginClass.
dummyPackageDirectory.parent.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
final Directory pluginDirectory = createFakePlugin(fs);
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
platforms:
......@@ -883,9 +903,8 @@ flutter:
when(featureFlags.isMacOSEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(true);
// Create a plugin without a pluginClass.
dummyPackageDirectory.parent.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
final Directory pluginDirectory = createFakePlugin(fs);
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
platforms:
......@@ -912,9 +931,8 @@ flutter:
when(featureFlags.isMacOSEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(true);
// Create a plugin without a pluginClass.
dummyPackageDirectory.parent.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync(r'''
final Directory pluginDirectory = createFakePlugin(fs);
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync(r'''
"aws ... \"Branch\": $BITBUCKET_BRANCH, \"Date\": $(date +"%m-%d-%y"), \"Time\": $(date +"%T")}\"
''');
......@@ -933,7 +951,7 @@ flutter:
when(linuxProject.existsSync()).thenReturn(true);
when(featureFlags.isLinuxEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
configureDummyPackageAsPlugin();
createFakePlugin(fs);
await injectPlugins(flutterProject, checkProjects: true);
......@@ -954,9 +972,8 @@ flutter:
when(featureFlags.isLinuxEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
// Create a plugin without a pluginClass.
dummyPackageDirectory.parent.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
final Directory pluginDirectory = createFakePlugin(fs);
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
platforms:
......@@ -982,9 +999,8 @@ flutter:
when(featureFlags.isLinuxEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
// Create a plugin without a pluginClass.
dummyPackageDirectory.parent.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
final Directory pluginDirectory = createFakePlugin(fs);
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
platforms:
......@@ -1010,7 +1026,7 @@ flutter:
when(linuxProject.existsSync()).thenReturn(true);
when(featureFlags.isLinuxEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
configureDummyPackageAsPlugin();
createFakePlugin(fs);
await injectPlugins(flutterProject, checkProjects: true);
......@@ -1033,7 +1049,7 @@ flutter:
when(windowsProject.existsSync()).thenReturn(true);
when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
configureDummyPackageAsPlugin();
createFakePlugin(fs);
await injectPlugins(flutterProject, checkProjects: true);
......@@ -1054,9 +1070,8 @@ flutter:
when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
// Create a plugin without a pluginClass.
dummyPackageDirectory.parent.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
final Directory pluginDirectory = createFakePlugin(fs);
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
platforms:
......@@ -1081,9 +1096,8 @@ flutter:
when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
// Create a plugin without a pluginClass.
dummyPackageDirectory.parent.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
final Directory pluginDirectory = createFakePlugin(fs);
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
platforms:
......@@ -1109,7 +1123,7 @@ flutter:
when(windowsProject.existsSync()).thenReturn(true);
when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
configureDummyPackageAsPlugin();
createFakePlugin(fs);
await injectPlugins(flutterProject, checkProjects: true);
......@@ -1126,6 +1140,32 @@ flutter:
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => featureFlags,
});
testUsingContext('Generated plugin CMake files always use posix-style paths', () async {
// Re-run the setup using the Windows filesystem.
setUpProject(fsWindows);
createFakePlugin(fsWindows);
when(linuxProject.existsSync()).thenReturn(true);
when(windowsProject.existsSync()).thenReturn(true);
when(featureFlags.isLinuxEnabled).thenReturn(true);
when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
await injectPlugins(flutterProject, checkProjects: true);
for (final CmakeBasedProject project in <CmakeBasedProject>[linuxProject, windowsProject]) {
final File pluginCmakefile = project.generatedPluginCmakeFile;
expect(pluginCmakefile.existsSync(), isTrue);
final String contents = pluginCmakefile.readAsStringSync();
expect(contents, contains('add_subdirectory(flutter/ephemeral/.plugin_symlinks'));
}
}, overrides: <Type, Generator>{
FileSystem: () => fsWindows,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => featureFlags,
});
});
group('createPluginSymlinks', () {
......@@ -1139,7 +1179,7 @@ flutter:
testUsingContext('Symlinks are created for Linux plugins', () async {
when(linuxProject.existsSync()).thenReturn(true);
configureDummyPackageAsPlugin();
createFakePlugin(fs);
// refreshPluginsList should call createPluginSymlinks.
await refreshPluginsList(flutterProject);
......@@ -1152,7 +1192,7 @@ flutter:
testUsingContext('Symlinks are created for Windows plugins', () async {
when(windowsProject.existsSync()).thenReturn(true);
configureDummyPackageAsPlugin();
createFakePlugin(fs);
// refreshPluginsList should call createPluginSymlinks.
await refreshPluginsList(flutterProject);
......@@ -1199,7 +1239,7 @@ flutter:
}
// refreshPluginsList should remove existing links and recreate on changes.
configureDummyPackageAsPlugin();
createFakePlugin(fs);
await refreshPluginsList(flutterProject);
for (final File file in dummyFiles) {
......@@ -1238,7 +1278,7 @@ flutter:
testUsingContext('createPluginSymlinks repairs missing links', () async {
when(linuxProject.existsSync()).thenReturn(true);
when(windowsProject.existsSync()).thenReturn(true);
configureDummyPackageAsPlugin();
createFakePlugin(fs);
await refreshPluginsList(flutterProject);
final List<Link> links = <Link>[
......
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