Skip to content

Commit

Permalink
Fix Xcode 15 build failure due to DT_TOOLCHAIN_DIR (#132803)
Browse files Browse the repository at this point in the history
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 flutter/flutter#132755.
  • Loading branch information
vashworth authored Aug 18, 2023
1 parent 312ef54 commit b52297d
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 3 deletions.
6 changes: 6 additions & 0 deletions packages/flutter_tools/lib/src/macos/cocoapods.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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(<ProjectMigrator>[
CocoaPodsScriptReadlink(xcodeProject, _xcodeProjectInterpreter, _logger),
CocoaPodsToolchainDirectoryMigration(
xcodeProject,
_xcodeProjectInterpreter,
_logger,
),
]);
postPodMigration.run();

Expand Down
Original file line number Diff line number Diff line change
@@ -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<FileSystemEntity> 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;
}
}
9 changes: 6 additions & 3 deletions packages/flutter_tools/lib/src/xcode_project.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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', () {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit b52297d

Please sign in to comment.