ci: TestFlight opt-in toggle + Xcode 27 build fixes (#54)
* ci: gate TestFlight behind opt-in toggle + Xcode 27 build fixes TestFlight is now opt-in (default OFF). A workflow_dispatch boolean `enable_testflight` (or the `ENABLE_TESTFLIGHT` repo variable) drives a `prepare.outputs.testflight_enabled` flag that gates the macOS app-store-pkg build leg and both testflight_ios/testflight_macos upload legs. Missing Apple signing secrets no longer fail the normal DMG/IPA release path (package-macos-app-store-pkg.sh hard-exits without them). Xcode 27 build compatibility: - Align Apple deployment targets so no pod sits below the app minimum (Xcode 27 rejects this): macOS pods + RunnerTests -> 15.6, iOS pods -> 15.5 to match the Runner targets. - Add a `lipo` shim (scripts/xcode-tools/lipo) wired onto PATH in the iOS/macOS build phases; Xcode 27 only accepts one `-verify_arch` architecture per call while Flutter passes them all at once. - macOS project hygiene: correct PrivacyInfo.xcprivacy path, set app display name + LSApplicationCategoryType. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: make temp-dir cleanup resilient to concurrent-write races The assistant execution target tests deleted their temp HOME/workspace dirs with a raw recursive delete in addTearDown. A background flush (e.g. controller dispose still persisting state) can keep writing into the dir while the delete walks it, so the delete races and fails with "Directory not empty" (errno 39), failing the test on CI. Route all unguarded teardown deletes through the existing _resilientDelete helper (re-check existence + retry), and harden that helper so its final fallback never re-throws — a temp-dir cleanup failure must never fail a test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Haitao Pan <manbuzhe2009@qq.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ca0d8a49a8
commit
01515f95ca
31
.github/workflows/build-and-release.yml
vendored
31
.github/workflows/build-and-release.yml
vendored
@ -30,6 +30,11 @@ on:
|
||||
- ".github/actions/setup-flutter-sdk/action.yml"
|
||||
- ".github/workflows/build-and-release.yml"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
enable_testflight:
|
||||
description: "Build & upload TestFlight (macOS/iOS App Store) artifacts"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@ -49,6 +54,7 @@ jobs:
|
||||
contents: write
|
||||
outputs:
|
||||
should_release: ${{ steps.flags.outputs.should_release }}
|
||||
testflight_enabled: ${{ steps.flags.outputs.testflight_enabled }}
|
||||
release_tag: ${{ steps.meta.outputs.release_tag }}
|
||||
release_title: ${{ steps.meta.outputs.release_title }}
|
||||
release_notes: ${{ steps.meta.outputs.release_notes }}
|
||||
@ -61,6 +67,9 @@ jobs:
|
||||
- name: Determine release mode
|
||||
id: flags
|
||||
shell: bash
|
||||
env:
|
||||
ENABLE_TESTFLIGHT_INPUT: ${{ github.event.inputs.enable_testflight }}
|
||||
ENABLE_TESTFLIGHT_VAR: ${{ vars.ENABLE_TESTFLIGHT }}
|
||||
run: |
|
||||
if [[ "${GITHUB_REF:-}" == refs/tags/v* || "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" || "${GITHUB_REF:-}" == "refs/heads/main" ]]; then
|
||||
echo "should_release=true" >> "$GITHUB_OUTPUT"
|
||||
@ -68,6 +77,16 @@ jobs:
|
||||
echo "should_release=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# TestFlight is opt-in (default OFF). Enabled only when explicitly
|
||||
# requested via the workflow_dispatch input or the ENABLE_TESTFLIGHT
|
||||
# repo/org variable. Keeps missing Apple signing secrets from failing
|
||||
# the normal DMG/IPA release path.
|
||||
if [[ "${ENABLE_TESTFLIGHT_INPUT:-}" == "true" || "${ENABLE_TESTFLIGHT_VAR:-}" == "true" ]]; then
|
||||
echo "testflight_enabled=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "testflight_enabled=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Compute release metadata
|
||||
id: meta
|
||||
shell: bash
|
||||
@ -257,12 +276,12 @@ jobs:
|
||||
go-version: "1.24.1"
|
||||
|
||||
- name: Build platform artifacts
|
||||
if: ${{ steps.preflight.outputs.should_build_platform == 'true' && (matrix.release_only != 'true' || env.SHOULD_RELEASE == 'true') }}
|
||||
if: ${{ steps.preflight.outputs.should_build_platform == 'true' && (matrix.release_only != 'true' || env.SHOULD_RELEASE == 'true') && (matrix.package != 'app-store-pkg' || needs.prepare.outputs.testflight_enabled == 'true') }}
|
||||
shell: bash
|
||||
run: bash ./scripts/ci/build_matrix_artifacts.sh "$PLATFORM" "$ARCH" "${{ matrix.package }}" "$SHOULD_RELEASE"
|
||||
|
||||
- name: Upload build artifacts
|
||||
if: ${{ steps.preflight.outputs.should_build_platform == 'true' && (matrix.release_only != 'true' || env.SHOULD_RELEASE == 'true') }}
|
||||
if: ${{ steps.preflight.outputs.should_build_platform == 'true' && (matrix.release_only != 'true' || env.SHOULD_RELEASE == 'true') && (matrix.package != 'app-store-pkg' || needs.prepare.outputs.testflight_enabled == 'true') }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
@ -338,7 +357,7 @@ jobs:
|
||||
|
||||
- name: Load App Store Connect secrets
|
||||
id: vault
|
||||
if: ${{ matrix.target != 'github_release' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
||||
if: ${{ matrix.target != 'github_release' && needs.prepare.outputs.testflight_enabled == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
||||
uses: hashicorp/vault-action@v4
|
||||
with:
|
||||
url: ${{ env.VAULT_ADDR }}
|
||||
@ -356,7 +375,7 @@ jobs:
|
||||
kv/data/github-actions/xworkmate-app APP_STORE_CONNECT_API_KEY_P8_BASE64 | APP_STORE_CONNECT_API_KEY_P8_BASE64
|
||||
|
||||
- name: Export App Store Connect secrets
|
||||
if: ${{ matrix.target != 'github_release' }}
|
||||
if: ${{ matrix.target != 'github_release' && needs.prepare.outputs.testflight_enabled == 'true' }}
|
||||
run: |
|
||||
{
|
||||
echo "APPLE_CERT_P12_BASE64=${{ steps.vault.outputs.APPLE_CERT_P12_BASE64 }}"
|
||||
@ -385,13 +404,13 @@ jobs:
|
||||
RELEASE_NOTES: ${{ needs.prepare.outputs.release_notes }}
|
||||
|
||||
- name: Download TestFlight artifact
|
||||
if: ${{ matrix.target != 'github_release' }}
|
||||
if: ${{ matrix.target != 'github_release' && needs.prepare.outputs.testflight_enabled == 'true' }}
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ matrix.artifact_path }}
|
||||
|
||||
- name: Upload to TestFlight
|
||||
if: ${{ matrix.target != 'github_release' }}
|
||||
if: ${{ matrix.target != 'github_release' && needs.prepare.outputs.testflight_enabled == 'true' }}
|
||||
shell: bash
|
||||
run: bash ./scripts/ci/testflight_upload.sh "${{ matrix.testflight_platform }}" "${{ matrix.artifact_path }}"
|
||||
|
||||
@ -39,6 +39,11 @@ post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
|
||||
target.build_configurations.each do |config|
|
||||
# Xcode 27 rejects dependency targets below the app's iOS 15.5 minimum.
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.5'
|
||||
end
|
||||
|
||||
next unless ['Pods-Runner', 'Pods-RunnerTests'].include?(target.name)
|
||||
|
||||
target.build_configurations.each do |config|
|
||||
|
||||
@ -67,6 +67,6 @@ SPEC CHECKSUMS:
|
||||
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
||||
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
|
||||
|
||||
PODFILE CHECKSUM: 5ab2a375a52a76f419425b2b219d2743259d6f1f
|
||||
PODFILE CHECKSUM: ca16f6ef66890e172b6528d5f0eb390e0410291e
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@ -291,7 +291,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
shellScript = "export PATH=\"$PROJECT_DIR/../scripts/xcode-tools:$PATH\"\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@ -306,7 +306,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
shellScript = "export PATH=\"$PROJECT_DIR/../scripts/xcode-tools:$PATH\"\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
BA47ED2B244B5E2B99043424 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
platform :osx, '14.0'
|
||||
platform :osx, '15.6'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
@ -97,7 +97,8 @@ post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '14.0'
|
||||
# Xcode 27 rejects dependency targets below the app's 15.6 minimum.
|
||||
config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '15.6'
|
||||
|
||||
next unless ['Pods-Runner', 'Pods-RunnerTests', 'WebRTC-SDK', 'flutter_webrtc'].include?(target.name)
|
||||
|
||||
|
||||
@ -61,6 +61,6 @@ SPEC CHECKSUMS:
|
||||
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
|
||||
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
|
||||
|
||||
PODFILE CHECKSUM: 1eb7d5d1472c632b8f775dd34562291c20ae818a
|
||||
PODFILE CHECKSUM: 7804cba3ecbc9953edc70dee53b2ce2b4aeaa013
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@ -371,7 +371,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
|
||||
shellScript = "export PATH=\"$PROJECT_DIR/../scripts/xcode-tools:$PATH\"\necho \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
|
||||
};
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@ -392,7 +392,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if [[ \"${WORKSPACE_DIR:-}\" == *.xcodeproj ]]; then\n echo \"error: XWorkmate macOS builds with CocoaPods plugins must be launched from macos/Runner.xcworkspace, not Runner.xcodeproj.\" >&2\n echo \"error: Close this project, open macos/Runner.xcworkspace in Xcode, and build the shared Runner scheme for My Mac.\" >&2\n echo \"error: Pods targets appearing in the workspace are expected. Only configure signing on the Runner target.\" >&2\n echo \"error: For release packaging, run 'flutter build macos' from the repository root.\" >&2\n exit 1\nfi\n\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
shellScript = "export PATH=\"$PROJECT_DIR/../scripts/xcode-tools:$PATH\"\nif [[ \"${WORKSPACE_DIR:-}\" == *.xcodeproj ]]; then\n echo \"error: XWorkmate macOS builds with CocoaPods plugins must be launched from macos/Runner.xcworkspace, not Runner.xcodeproj.\" >&2\n echo \"error: Close this project, open macos/Runner.xcworkspace in Xcode, and build the shared Runner scheme for My Mac.\" >&2\n echo \"error: Pods targets appearing in the workspace are expected. Only configure signing on the Runner target.\" >&2\n echo \"error: For release packaging, run 'flutter build macos' from the repository root.\" >&2\n exit 1\nfi\n\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
8E8C2A3EBAA3461603096C04 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@ -511,7 +511,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
@ -539,7 +539,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
@ -567,7 +567,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
@ -661,11 +661,13 @@
|
||||
ENABLE_RESOURCE_ACCESS_USB = NO;
|
||||
ENABLE_USER_SELECTED_FILES = readonly;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Xworkmate;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
"-Wno-ignored-attributes",
|
||||
@ -823,11 +825,13 @@
|
||||
ENABLE_RESOURCE_ACCESS_USB = NO;
|
||||
ENABLE_USER_SELECTED_FILES = readonly;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Xworkmate;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
"-Wno-ignored-attributes",
|
||||
@ -863,11 +867,13 @@
|
||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||
ENABLE_USER_SELECTED_FILES = readonly;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Xworkmate;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
"-Wno-ignored-attributes",
|
||||
|
||||
26
scripts/xcode-tools/lipo
Executable file
26
scripts/xcode-tools/lipo
Executable file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
real_lipo="$(xcrun --find lipo)"
|
||||
args=("$@")
|
||||
verify_index=-1
|
||||
|
||||
for ((index = 0; index < ${#args[@]}; index++)); do
|
||||
if [[ "${args[index]}" == "-verify_arch" ]]; then
|
||||
verify_index=$index
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Xcode 27 accepts one architecture per -verify_arch invocation. Flutter
|
||||
# passes all requested architectures at once, so verify each one separately.
|
||||
if ((verify_index >= 0 && ${#args[@]} - verify_index > 2)); then
|
||||
command_prefix=("${args[@]:0:verify_index}")
|
||||
architectures=("${args[@]:verify_index+1}")
|
||||
for architecture in "${architectures[@]}"; do
|
||||
"$real_lipo" "${command_prefix[@]}" -verify_arch "$architecture"
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec "$real_lipo" "$@"
|
||||
@ -322,9 +322,7 @@ void main() {
|
||||
'xworkmate-no-runtime-main-home-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localHome.exists()) {
|
||||
await localHome.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(localHome);
|
||||
});
|
||||
final controller = _sandboxController(
|
||||
environmentOverride: const <String, String>{},
|
||||
@ -358,9 +356,7 @@ void main() {
|
||||
'xworkmate-refresh-no-session-one-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localHome.exists()) {
|
||||
await localHome.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(localHome);
|
||||
});
|
||||
final controller = _sandboxController(
|
||||
environmentOverride: const <String, String>{},
|
||||
@ -439,9 +435,7 @@ void main() {
|
||||
'xworkmate-stable-task-selection-home-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localHome.exists()) {
|
||||
await localHome.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(localHome);
|
||||
});
|
||||
final controller = _sandboxController(
|
||||
environmentOverride: const <String, String>{},
|
||||
@ -1716,9 +1710,7 @@ void main() {
|
||||
'xworkmate-acp-interrupt-artifacts-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localWorkspace.exists()) {
|
||||
await localWorkspace.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(localWorkspace);
|
||||
});
|
||||
final fakeGoTaskService = _RecordingGoTaskServiceClient()
|
||||
..onExecuteTask = ((request) async {
|
||||
@ -2017,9 +2009,7 @@ void main() {
|
||||
'xworkmate-acp-handshake-interrupt-artifacts-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localWorkspace.exists()) {
|
||||
await localWorkspace.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(localWorkspace);
|
||||
});
|
||||
final fakeGoTaskService = _RecordingGoTaskServiceClient()
|
||||
..updatesBeforeNextOutcome.add(
|
||||
@ -2383,9 +2373,7 @@ void main() {
|
||||
'xworkmate-background-completion-home-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localHome.exists()) {
|
||||
await localHome.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(localHome);
|
||||
});
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedController(
|
||||
@ -2508,9 +2496,7 @@ void main() {
|
||||
'xworkmate-same-prompt-home-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localHome.exists()) {
|
||||
await localHome.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(localHome);
|
||||
});
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedController(
|
||||
@ -2683,9 +2669,7 @@ void main() {
|
||||
'xworkmate-same-prompt-empty-home-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localHome.exists()) {
|
||||
await localHome.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(localHome);
|
||||
});
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedController(
|
||||
@ -2707,9 +2691,7 @@ void main() {
|
||||
continue;
|
||||
}
|
||||
final directory = Directory(workspace);
|
||||
if (await directory.exists()) {
|
||||
await directory.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(directory);
|
||||
}
|
||||
});
|
||||
|
||||
@ -2845,9 +2827,7 @@ void main() {
|
||||
'xworkmate-terminal-failure-home-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localHome.exists()) {
|
||||
await localHome.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(localHome);
|
||||
});
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedController(
|
||||
@ -2925,9 +2905,7 @@ void main() {
|
||||
'xworkmate-empty-output-home-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localHome.exists()) {
|
||||
await localHome.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(localHome);
|
||||
});
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedController(
|
||||
@ -3297,9 +3275,7 @@ void main() {
|
||||
addTearDown(() async {
|
||||
fakeGoTaskService.completeAll();
|
||||
controller.dispose();
|
||||
if (await localHome.exists()) {
|
||||
await localHome.delete(recursive: true);
|
||||
}
|
||||
await _resilientDelete(localHome);
|
||||
});
|
||||
|
||||
for (
|
||||
@ -4902,19 +4878,28 @@ UiFeatureManifest _defaultDesktopManifest() {
|
||||
}
|
||||
|
||||
Future<void> _resilientDelete(Directory dir) async {
|
||||
if (!await dir.exists()) {
|
||||
return;
|
||||
}
|
||||
for (var attempt = 0; attempt < 8; attempt++) {
|
||||
if (!await dir.exists()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await dir.delete(recursive: true);
|
||||
return;
|
||||
} catch (error) {
|
||||
// A background flush (e.g. controller dispose still persisting state)
|
||||
// may keep writing into the temp dir, so a recursive delete can race
|
||||
// and fail with "Directory not empty". Retry a few times.
|
||||
debugPrint('Temporary directory delete retry: $error');
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
}
|
||||
}
|
||||
await dir.delete(recursive: true);
|
||||
// Best-effort cleanup: never fail a test over leftover temp files; the OS
|
||||
// reclaims the temp directory regardless.
|
||||
try {
|
||||
await dir.delete(recursive: true);
|
||||
} catch (error) {
|
||||
debugPrint('Giving up on temporary directory cleanup: $error');
|
||||
}
|
||||
}
|
||||
|
||||
AppController _sandboxController({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user