From b52297da0620f1b3df4860cea8338bb8d338dfd4 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:24:55 -0500 Subject: [PATCH] Fix Xcode 15 build failure due to DT_TOOLCHAIN_DIR (#132803) Starting in Xcode 15, when building macOS, DT_TOOLCHAIN_DIR cannot be used to evaluate LD_RUNPATH_SEARCH_PATHS or LIBRARY_SEARCH_PATHS. `xcodebuild` error message recommend using TOOLCHAIN_DIR instead. Since Xcode 15 isn't in CI, I tested it in a one-off `led` test: * [Pre-fix failure](https://luci-milo.appspot.com/raw/build/logs.chromium.org/flutter/led/vashworth_google.com/04e485a0b152a0720f5e561266f7a6e4fb64fc76227fcacc95b67486ae2771e7/+/build.proto) * [Post-fix success](https://luci-milo.appspot.com/raw/build/logs.chromium.org/flutter/led/vashworth_google.com/d454a3e181e1a97692bdc1fcc197738fe04e4acf1cb20026fd040fd78513f3b0/+/build.proto) Fixes https://github.com/flutter/flutter/issues/132755. --- .../lib/src/macos/cocoapods.dart | 6 + ...coapods_toolchain_directory_migration.dart | 62 ++++++++ .../flutter_tools/lib/src/xcode_project.dart | 9 +- .../ios/ios_project_migration_test.dart | 148 ++++++++++++++++++ 4 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 packages/flutter_tools/lib/src/migrations/cocoapods_toolchain_directory_migration.dart diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart index 95a967e450..e675b50c5e 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapods.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart @@ -19,6 +19,7 @@ import '../build_info.dart'; import '../cache.dart'; import '../ios/xcodeproj.dart'; import '../migrations/cocoapods_script_symlink.dart'; +import '../migrations/cocoapods_toolchain_directory_migration.dart'; import '../reporting/reporting.dart'; import '../xcode_project.dart'; @@ -172,6 +173,11 @@ class CocoaPods { // This migrator works around a CocoaPods bug, and should be run after `pod install` is run. final ProjectMigration postPodMigration = ProjectMigration([ CocoaPodsScriptReadlink(xcodeProject, _xcodeProjectInterpreter, _logger), + CocoaPodsToolchainDirectoryMigration( + xcodeProject, + _xcodeProjectInterpreter, + _logger, + ), ]); postPodMigration.run(); diff --git a/packages/flutter_tools/lib/src/migrations/cocoapods_toolchain_directory_migration.dart b/packages/flutter_tools/lib/src/migrations/cocoapods_toolchain_directory_migration.dart new file mode 100644 index 0000000000..4ab406bda6 --- /dev/null +++ b/packages/flutter_tools/lib/src/migrations/cocoapods_toolchain_directory_migration.dart @@ -0,0 +1,62 @@ +// 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 '../base/version.dart'; +import '../ios/xcodeproj.dart'; +import '../xcode_project.dart'; + +/// Starting in Xcode 15, when building macOS, DT_TOOLCHAIN_DIR cannot be used +/// to evaluate LD_RUNPATH_SEARCH_PATHS or LIBRARY_SEARCH_PATHS. `xcodebuild` +/// error message recommend using TOOLCHAIN_DIR instead. +/// +/// This has been fixed upstream in CocoaPods, but migrate a copy of their +/// workaround so users don't need to update. +class CocoaPodsToolchainDirectoryMigration extends ProjectMigrator { + CocoaPodsToolchainDirectoryMigration( + XcodeBasedProject project, + XcodeProjectInterpreter xcodeProjectInterpreter, + super.logger, + ) : _podRunnerTargetSupportFiles = project.podRunnerTargetSupportFiles, + _xcodeProjectInterpreter = xcodeProjectInterpreter; + + final Directory _podRunnerTargetSupportFiles; + final XcodeProjectInterpreter _xcodeProjectInterpreter; + + @override + void migrate() { + if (!_podRunnerTargetSupportFiles.existsSync()) { + logger.printTrace('CocoaPods Pods-Runner Target Support Files not found, skipping TOOLCHAIN_DIR workaround.'); + return; + } + + final Version? version = _xcodeProjectInterpreter.version; + + // If Xcode not installed or less than 15, skip this migration. + if (version == null || version < Version(15, 0, 0)) { + logger.printTrace('Detected Xcode version is $version, below 15.0, skipping TOOLCHAIN_DIR workaround.'); + return; + } + + final List files = _podRunnerTargetSupportFiles.listSync(); + for (final FileSystemEntity file in files) { + if (file.basename.endsWith('xcconfig') && file is File) { + processFileLines(file); + } + } + } + + @override + String? migrateLine(String line) { + final String trimmedString = line.trim(); + if (trimmedString.startsWith('LD_RUNPATH_SEARCH_PATHS') || trimmedString.startsWith('LIBRARY_SEARCH_PATHS')) { + const String originalReadLinkLine = r'{DT_TOOLCHAIN_DIR}'; + const String replacementReadLinkLine = r'{TOOLCHAIN_DIR}'; + + return line.replaceAll(originalReadLinkLine, replacementReadLinkLine); + } + return line; + } +} diff --git a/packages/flutter_tools/lib/src/xcode_project.dart b/packages/flutter_tools/lib/src/xcode_project.dart index 0e658a410d..add59a60b1 100644 --- a/packages/flutter_tools/lib/src/xcode_project.dart +++ b/packages/flutter_tools/lib/src/xcode_project.dart @@ -104,11 +104,14 @@ abstract class XcodeBasedProject extends FlutterProjectPlatform { File get podManifestLock => hostAppRoot.childDirectory('Pods').childFile('Manifest.lock'); /// The CocoaPods generated 'Pods-Runner-frameworks.sh'. - File get podRunnerFrameworksScript => hostAppRoot + File get podRunnerFrameworksScript => podRunnerTargetSupportFiles + .childFile('Pods-Runner-frameworks.sh'); + + /// The CocoaPods generated directory 'Pods-Runner'. + Directory get podRunnerTargetSupportFiles => hostAppRoot .childDirectory('Pods') .childDirectory('Target Support Files') - .childDirectory('Pods-Runner') - .childFile('Pods-Runner-frameworks.sh'); + .childDirectory('Pods-Runner'); } /// Represents the iOS sub-project of a Flutter project. diff --git a/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart index c5ed8b619c..3f2277a22c 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart @@ -16,6 +16,7 @@ import 'package:flutter_tools/src/ios/migrations/remove_framework_link_and_embed import 'package:flutter_tools/src/ios/migrations/xcode_build_system_migration.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/migrations/cocoapods_script_symlink.dart'; +import 'package:flutter_tools/src/migrations/cocoapods_toolchain_directory_migration.dart'; import 'package:flutter_tools/src/migrations/xcode_project_object_version_migration.dart'; import 'package:flutter_tools/src/migrations/xcode_script_build_phase_migration.dart'; import 'package:flutter_tools/src/migrations/xcode_thin_binary_build_phase_input_paths_migration.dart'; @@ -1003,6 +1004,150 @@ platform :ios, '11.0' expect(testLogger.statusText, contains('Upgrading Pods-Runner-frameworks.sh')); }); }); + + group('Cocoapods migrate toolchain directory', () { + late MemoryFileSystem memoryFileSystem; + late BufferLogger testLogger; + late FakeIosProject project; + late Directory podRunnerTargetSupportFiles; + late ProcessManager processManager; + late XcodeProjectInterpreter xcode15ProjectInterpreter; + + setUp(() { + memoryFileSystem = MemoryFileSystem(); + podRunnerTargetSupportFiles = memoryFileSystem.directory('Pods-Runner'); + testLogger = BufferLogger.test(); + project = FakeIosProject(); + processManager = FakeProcessManager.any(); + xcode15ProjectInterpreter = XcodeProjectInterpreter.test(processManager: processManager, version: Version(15, 0, 0)); + project.podRunnerTargetSupportFiles = podRunnerTargetSupportFiles; + }); + + testWithoutContext('skip if directory is missing', () { + final CocoaPodsToolchainDirectoryMigration iosProjectMigration = CocoaPodsToolchainDirectoryMigration( + project, + xcode15ProjectInterpreter, + testLogger, + ); + iosProjectMigration.migrate(); + expect(podRunnerTargetSupportFiles.existsSync(), isFalse); + + expect(testLogger.traceText, contains('CocoaPods Pods-Runner Target Support Files not found')); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('skip if xcconfig files are missing', () { + podRunnerTargetSupportFiles.createSync(); + final CocoaPodsToolchainDirectoryMigration iosProjectMigration = CocoaPodsToolchainDirectoryMigration( + project, + xcode15ProjectInterpreter, + testLogger, + ); + iosProjectMigration.migrate(); + expect(podRunnerTargetSupportFiles.existsSync(), isTrue); + expect(testLogger.traceText, isEmpty); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('skip if nothing to upgrade', () { + podRunnerTargetSupportFiles.createSync(); + final File debugConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.debug.xcconfig'); + const String contents = r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" +LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''; + debugConfig.writeAsStringSync(contents); + + final File profileConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.profile.xcconfig'); + profileConfig.writeAsStringSync(contents); + + final File releaseConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.release.xcconfig'); + releaseConfig.writeAsStringSync(contents); + + final CocoaPodsToolchainDirectoryMigration iosProjectMigration = CocoaPodsToolchainDirectoryMigration( + project, + xcode15ProjectInterpreter, + testLogger, + ); + iosProjectMigration.migrate(); + expect(debugConfig.existsSync(), isTrue); + expect(testLogger.traceText, isEmpty); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('skipped if Xcode version below 15', () { + podRunnerTargetSupportFiles.createSync(); + final File debugConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.debug.xcconfig'); + const String contents = r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" +LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''; + debugConfig.writeAsStringSync(contents); + + final File profileConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.profile.xcconfig'); + profileConfig.writeAsStringSync(contents); + + final File releaseConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.release.xcconfig'); + releaseConfig.writeAsStringSync(contents); + + final XcodeProjectInterpreter xcode14ProjectInterpreter = XcodeProjectInterpreter.test( + processManager: processManager, + version: Version(14, 0, 0), + ); + + final CocoaPodsToolchainDirectoryMigration iosProjectMigration = CocoaPodsToolchainDirectoryMigration( + project, + xcode14ProjectInterpreter, + testLogger, + ); + iosProjectMigration.migrate(); + expect(debugConfig.existsSync(), isTrue); + expect(testLogger.traceText, contains('Detected Xcode version is 14.0.0, below 15.0')); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('Xcode project is migrated and ignores leading whitespace', () { + podRunnerTargetSupportFiles.createSync(); + final File debugConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.debug.xcconfig'); + const String contents = r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''; + debugConfig.writeAsStringSync(contents); + + final File profileConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.profile.xcconfig'); + profileConfig.writeAsStringSync(contents); + + final File releaseConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.release.xcconfig'); + releaseConfig.writeAsStringSync(contents); + + final CocoaPodsToolchainDirectoryMigration iosProjectMigration = CocoaPodsToolchainDirectoryMigration( + project, + xcode15ProjectInterpreter, + testLogger, + ); + iosProjectMigration.migrate(); + + expect(debugConfig.existsSync(), isTrue); + expect(debugConfig.readAsStringSync(), r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''); + expect(profileConfig.existsSync(), isTrue); + expect(profileConfig.readAsStringSync(), r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''); + expect(releaseConfig.existsSync(), isTrue); + expect(releaseConfig.readAsStringSync(), r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''); + expect(testLogger.statusText, contains('Upgrading Pods-Runner.debug.xcconfig')); + expect(testLogger.statusText, contains('Upgrading Pods-Runner.profile.xcconfig')); + expect(testLogger.statusText, contains('Upgrading Pods-Runner.release.xcconfig')); + }); + }); }); group('update Xcode script build phase', () { @@ -1239,6 +1384,9 @@ class FakeIosProject extends Fake implements IosProject { @override File podRunnerFrameworksScript = MemoryFileSystem.test().file('podRunnerFrameworksScript'); + + @override + Directory podRunnerTargetSupportFiles = MemoryFileSystem.test().directory('Pods-Runner'); } class FakeIOSMigrator extends ProjectMigrator {