diff --git a/packages/shorebird_cli/lib/src/cache.dart b/packages/shorebird_cli/lib/src/cache.dart index a1fb713c..e83009b5 100644 --- a/packages/shorebird_cli/lib/src/cache.dart +++ b/packages/shorebird_cli/lib/src/cache.dart @@ -49,8 +49,7 @@ class Cache { Cache() { registerArtifact(PatchArtifact(cache: this, platform: platform)); registerArtifact(BundleToolArtifact(cache: this, platform: platform)); - registerArtifact(AotToolsDillArtifact(cache: this, platform: platform)); - registerArtifact(AotToolsExeArtifact(cache: this, platform: platform)); + registerArtifact(AotToolsArtifact(cache: this, platform: platform)); } void registerArtifact(CachedArtifact artifact) => _artifacts.add(artifact); @@ -66,7 +65,7 @@ class Cache { maxAttempts: 3, onRetry: (e) { logger - ..detail('Failed to update ${artifact.name}, retrying...') + ..detail('Failed to update ${artifact.fileName}, retrying...') ..detail(e.toString()); }, ); @@ -131,7 +130,7 @@ abstract class CachedArtifact { final Platform platform; /// The on-disk name of the artifact. - String get name; + String get fileName; /// Should the artifact be marked executable. bool get isExecutable; @@ -139,10 +138,6 @@ abstract class CachedArtifact { /// The URL from which the artifact can be downloaded. String get storageUrl; - /// Whether the artifact is required for Shorebird to function. - /// If we fail to fetch it we will exit with an error. - bool get required => true; - /// The SHA256 checksum of the artifact binary. /// /// When null, the checksum is not verified and the downloaded artifact @@ -150,13 +145,15 @@ abstract class CachedArtifact { String? get checksum; Future extractArtifact(http.ByteStream stream, String outputPath) { - final file = File(p.join(outputPath, name))..createSync(recursive: true); + final file = File(p.join(outputPath, fileName)) + ..createSync(recursive: true); return stream.pipe(file.openWrite()); } - Directory get location => cache.getArtifactDirectory(name); + File get file => + File(p.join(cache.getArtifactDirectory(fileName).path, fileName)); - Future isUpToDate() async => location.existsSync(); + Future isUpToDate() async => file.existsSync(); Future update() async { final request = http.Request('GET', Uri.parse(storageUrl)); @@ -166,41 +163,33 @@ abstract class CachedArtifact { } catch (error) { throw CacheUpdateFailure( ''' -Failed to download $name: $error +Failed to download $fileName: $error If you're behind a firewall/proxy, please, make sure shorebird_cli is allowed to access $storageUrl.''', ); } if (response.statusCode != HttpStatus.ok) { - if (!required && response.statusCode == HttpStatus.notFound) { - logger.detail( - '[cache] optional artifact: "$name" was not found, skipping...', - ); - return; - } - throw CacheUpdateFailure( - '''Failed to download $name: ${response.statusCode} ${response.reasonPhrase}''', + '''Failed to download $fileName: ${response.statusCode} ${response.reasonPhrase}''', ); } - await extractArtifact(response.stream, location.path); + final artifactDirectory = Directory(p.dirname(file.path)); + await extractArtifact(response.stream, artifactDirectory.path); final expectedChecksum = checksum; if (expectedChecksum != null) { - final artifactFile = File(p.join(location.path, name)); - - if (!checksumChecker.checkFile(artifactFile, expectedChecksum)) { + if (!checksumChecker.checkFile(file, expectedChecksum)) { // Delete the location, so if the download is retried, it will be // re-downloaded. - location.deleteSync(recursive: true); + artifactDirectory.deleteSync(recursive: true); throw CacheUpdateFailure( - '''Failed to download $name: checksum mismatch''', + '''Failed to download $fileName: checksum mismatch''', ); } else { logger.detail( - 'No checksum provided for $name, skipping file corruption validation', + 'No checksum provided for $fileName, skipping file corruption validation', ); } } @@ -208,78 +197,34 @@ allowed to access $storageUrl.''', if (!platform.isWindows && isExecutable) { final result = await process.start( 'chmod', - ['+x', p.join(location.path, name)], + ['+x', file.path], ); await result.exitCode; } } } -class AotToolsDillArtifact extends CachedArtifact { - AotToolsDillArtifact({required super.cache, required super.platform}); +class AotToolsArtifact extends CachedArtifact { + AotToolsArtifact({required super.cache, required super.platform}); @override - String get name => 'aot-tools.dill'; + String get fileName => 'aot-tools.dill'; @override bool get isExecutable => false; - /// The aot-tools are only available for revisions that support mixed-mode. @override - bool get required => false; - - @override - Directory get location => Directory( + File get file => File( p.join( - cache.getArtifactDirectory(name).path, + cache.getArtifactDirectory(fileName).path, shorebirdEnv.shorebirdEngineRevision, + fileName, ), ); @override String get storageUrl => - '${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/${shorebirdEnv.shorebirdEngineRevision}/$name'; - - @override - String? get checksum => null; -} - -/// For a few revisions in Dec 2023, we distributed aot-tools as an executable. -/// Should be removed sometime after June 2024. -class AotToolsExeArtifact extends CachedArtifact { - AotToolsExeArtifact({required super.cache, required super.platform}); - - @override - String get name => 'aot-tools'; - - @override - bool get isExecutable => true; - - /// The aot-tools are only available for revisions that support mixed-mode. - @override - bool get required => false; - - @override - Directory get location => Directory( - p.join( - cache.getArtifactDirectory(name).path, - shorebirdEnv.shorebirdEngineRevision, - ), - ); - - @override - String get storageUrl { - var artifactName = 'aot-tools-'; - if (platform.isMacOS) { - artifactName += 'darwin-x64'; - } else if (platform.isLinux) { - artifactName += 'linux-x64'; - } else if (platform.isWindows) { - artifactName += 'windows-x64'; - } - - return '${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/${shorebirdEnv.shorebirdEngineRevision}/$artifactName'; - } + '${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/${shorebirdEnv.shorebirdEngineRevision}/$fileName'; @override String? get checksum => null; @@ -289,7 +234,7 @@ class PatchArtifact extends CachedArtifact { PatchArtifact({required super.cache, required super.platform}); @override - String get name => 'patch'; + String get fileName => 'patch'; @override bool get isExecutable => true; @@ -300,7 +245,7 @@ class PatchArtifact extends CachedArtifact { String outputPath, ) async { final tempDir = Directory.systemTemp.createTempSync(); - final artifactPath = p.join(tempDir.path, '$name.zip'); + final artifactPath = p.join(tempDir.path, '$fileName.zip'); await stream.pipe(File(artifactPath).openWrite()); await artifactManager.extractZip( zipFile: File(artifactPath), @@ -330,7 +275,7 @@ class BundleToolArtifact extends CachedArtifact { BundleToolArtifact({required super.cache, required super.platform}); @override - String get name => 'bundletool.jar'; + String get fileName => 'bundletool.jar'; @override bool get isExecutable => false; diff --git a/packages/shorebird_cli/test/src/cache_test.dart b/packages/shorebird_cli/test/src/cache_test.dart index 3e60c59e..a67276f6 100644 --- a/packages/shorebird_cli/test/src/cache_test.dart +++ b/packages/shorebird_cli/test/src/cache_test.dart @@ -237,41 +237,6 @@ void main() { ); }); - test('skips optional artifacts if a 404 is returned', () async { - when(() => httpClient.send(any())).thenAnswer( - (invocation) async { - final request = - invocation.positionalArguments.first as http.BaseRequest; - final fileName = p.basename(request.url.path); - if (fileName.startsWith('aot-tools')) { - return http.StreamedResponse( - const Stream.empty(), - HttpStatus.notFound, - reasonPhrase: 'Not Found', - ); - } - return http.StreamedResponse( - Stream.value(ZipEncoder().encode(Archive())!), - HttpStatus.ok, - ); - }, - ); - await expectLater( - runWithOverrides(cache.updateAll), - completes, - ); - verify( - () => logger.detail( - '''[cache] optional artifact: "aot-tools.dill" was not found, skipping...''', - ), - ).called(1); - verify( - () => logger.detail( - '''[cache] optional artifact: "aot-tools" was not found, skipping...''', - ), - ).called(1); - }); - test('downloads correct artifacts', () async { final patchArtifactDirectory = runWithOverrides( () => cache.getArtifactDirectory('patch'), @@ -325,77 +290,6 @@ void main() { expect(requests, equals(expected)); }); - test('aot-tools falls back to executable', () async { - setMockPlatform(Platform.macOS); - - when(() => httpClient.send(any())).thenAnswer( - (invocation) async { - final request = - invocation.positionalArguments.first as http.BaseRequest; - final fileName = p.basename(request.url.path); - if (fileName == 'aot-tools.dill') { - return http.StreamedResponse( - const Stream.empty(), - HttpStatus.notFound, - reasonPhrase: 'Not Found', - ); - } - return http.StreamedResponse( - Stream.value(ZipEncoder().encode(Archive())!), - HttpStatus.ok, - ); - }, - ); - - await expectLater(runWithOverrides(cache.updateAll), completes); - - final requests = verify(() => httpClient.send(captureAny())) - .captured - .cast() - .map((r) => r.url) - .toList(); - - String perEngine(String name) => - '${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/$shorebirdEngineRevision/$name'; - - final expected = [ - perEngine('patch-darwin-x64.zip'), - 'https://github.com/google/bundletool/releases/download/1.15.6/bundletool-all-1.15.6.jar', - // Requests the .dill, fails and falls back to executable: - perEngine('aot-tools.dill'), - perEngine('aot-tools-darwin-x64'), - ].map(Uri.parse).toList(); - - expect(requests, equals(expected)); - }); - - test('aot-tools executable paths by platform', () async { - setMockPlatform(Platform.windows); - expect( - runWithOverrides( - () => AotToolsExeArtifact(cache: cache, platform: platform) - .storageUrl, - ), - endsWith('aot-tools-windows-x64'), - ); - setMockPlatform(Platform.linux); - expect( - runWithOverrides( - () => AotToolsExeArtifact(cache: cache, platform: platform) - .storageUrl, - ), - endsWith('aot-tools-linux-x64'), - ); - setMockPlatform(Platform.macOS); - expect( - runWithOverrides( - () => AotToolsExeArtifact(cache: cache, platform: platform) - .storageUrl, - ), - endsWith('aot-tools-darwin-x64'), - ); - }); - test('pull correct artifact for Windows', () async { setMockPlatform(Platform.windows); diff --git a/packages/shorebird_code_push_client/test/src/code_push_client_test.dart b/packages/shorebird_code_push_client/test/src/code_push_client_test.dart index 624f4dc5..f9667088 100644 --- a/packages/shorebird_code_push_client/test/src/code_push_client_test.dart +++ b/packages/shorebird_code_push_client/test/src/code_push_client_test.dart @@ -1686,7 +1686,7 @@ void main() { id: 0, number: 1, channel: 'stable', - artifacts: [], + artifacts: const [], ); response = GetReleasePatchesResponse(patches: [patch]); when(() => httpClient.send(any())).thenAnswer(