diff --git a/tool/grind.dart b/tool/grind.dart index cb34c4cdf5..5319826c6e 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -12,8 +12,8 @@ void main(List args) => grind(args); @Task('Check plugin URLs for live-ness') void checkUrls() async { log('checking URLs in FlutterBundle.properties...'); - var lines = - await File('flutter-idea/src/io/flutter/FlutterBundle.properties').readAsLines(); + var lines = await File('flutter-idea/src/io/flutter/FlutterBundle.properties') + .readAsLines(); for (var line in lines) { var split = line.split('='); if (split.length == 2) { diff --git a/tool/plugin/lib/artifact.dart b/tool/plugin/lib/artifact.dart index 6eb091c581..eb9b36de85 100644 --- a/tool/plugin/lib/artifact.dart +++ b/tool/plugin/lib/artifact.dart @@ -23,9 +23,10 @@ class Artifact { bool get isZip => file.endsWith('.zip'); bool exists() { - if (FileSystemEntity.isFileSync('artifacts/$file')) return true; + var artifactFilePath = p.join('artifacts', file); + if (FileSystemEntity.isFileSync(artifactFilePath)) return true; convertToTar(); - return FileSystemEntity.isFileSync('artifacts/$file'); + return FileSystemEntity.isFileSync(artifactFilePath); } String get outPath => p.join(rootPath, 'artifacts', output); @@ -39,7 +40,7 @@ class Artifact { } class ArtifactManager { - final String base = + final String baseUri = 'https://storage.googleapis.com/flutter_infra_release/flutter/intellij'; final List artifacts = []; @@ -72,42 +73,44 @@ class ArtifactManager { doDownload = false; } - var path = 'artifacts/${artifact.file}'; - if (FileSystemEntity.isFileSync(path)) { - alreadyDownloaded(path); + var artifactFilePath = p.join('artifacts', artifact.file); + if (FileSystemEntity.isFileSync(artifactFilePath)) { + alreadyDownloaded(artifactFilePath); } else { if (artifact.isZip) { - var tarPath = _convertToTar(path); + var tarPath = _convertToTar(artifactFilePath); if (FileSystemEntity.isFileSync(tarPath)) { artifact.convertToTar(); alreadyDownloaded(tarPath); } } if (doDownload) { - log('downloading $path...'); - result = await curl('$base/${artifact.file}', to: path); + log('downloading $artifactFilePath...'); + var artifactUri = p.join(baseUri, artifact.file); + + result = await download(artifactUri, to: artifactFilePath); if (result != 0) { log('download failed'); } - var archiveFile = File(path); + var archiveFile = File(artifactFilePath); if (!_isValidDownloadArtifact(archiveFile)) { // If the file is missing the server returns a small file containing // an error message. Delete it and try again. The smallest file we // store in the cloud is over 700K. - log('archive file not found: $base/${artifact.file}'); + log('archive file not found: $artifactUri'); archiveFile.deleteSync(); if (artifact.isZip) { artifact.convertToTar(); - path = 'artifacts/${artifact.file}'; - result = await curl('$base/${artifact.file}', to: path); + + result = await download(artifactUri, to: artifactFilePath); if (result != 0) { log('download failed'); artifacts.remove(artifact); continue; } - var archiveFile = File(path); + var archiveFile = File(artifactFilePath); if (!_isValidDownloadArtifact(archiveFile)) { - log('archive file not found: $base/${artifact.file}'); + log('archive file not found: $artifactUri'); archiveFile.deleteSync(); artifacts.remove(artifact); continue; @@ -122,23 +125,17 @@ class ArtifactManager { // clear unpacked cache if (rebuildCache || !FileSystemEntity.isDirectorySync(artifact.outPath)) { - await removeAll(artifact.outPath); + removeAll(artifact.outPath); } if (isCacheDirectoryValid(artifact)) { continue; } - // expand - if (Directory(artifact.outPath).existsSync()) { - await removeAll(artifact.outPath); - } - createDir(artifact.outPath); - if (artifact.isZip) { if (artifact.bareArchive) { - result = await exec( - 'unzip', ['-q', '-d', artifact.output, artifact.file], - cwd: 'artifacts'); + result = extractZip(artifact.file, + cwd: 'artifacts', targetDirectory: artifact.output); + var files = Directory(artifact.outPath).listSync(); if (files.length < 3) /* Might have .DS_Store */ { // This is the Mac zip case. @@ -152,24 +149,15 @@ class ArtifactManager { Directory("${artifact.outPath}Temp").renameSync(artifact.outPath); } } else { - result = await exec('unzip', ['-q', artifact.file], cwd: 'artifacts'); + result = extractZip(artifact.file, cwd: 'artifacts'); } } else { - result = await exec( - 'tar', - [ - '--strip-components=1', - '-zxf', - artifact.file, - '-C', - artifact.output - ], - cwd: p.join(rootPath, 'artifacts'), - ); + result = extractTar(artifact, + cwd: 'artifacts', targetDirectory: artifact.output); } if (result != 0) { log('unpacking failed'); - await removeAll(artifact.output); + removeAll(artifact.output); break; } diff --git a/tool/plugin/lib/edit.dart b/tool/plugin/lib/edit.dart index 2a59b6d270..8d7772a18d 100644 --- a/tool/plugin/lib/edit.dart +++ b/tool/plugin/lib/edit.dart @@ -10,6 +10,7 @@ import 'dart:async'; import 'dart:io'; import 'package:meta/meta.dart'; +import 'package:path/path.dart' as p; import 'build_spec.dart'; import 'util.dart'; @@ -96,17 +97,20 @@ class Unused extends EditCommand { } class EditAndroidModuleLibraryManager extends EditCommand { + final _moduleManagerPath = p.join('flutter-studio', 'src', 'io', 'flutter', + 'android', 'AndroidModuleLibraryManager.java'); + @override - String get path => - 'flutter-studio/src/io/flutter/android/AndroidModuleLibraryManager.java'; + String get path => _moduleManagerPath; @override String convert(BuildSpec spec) { // Starting with 3.6 we need to call a simplified init(). // This is where the $PROJECT_FILE$ macro is defined, #registerComponents. + + var libMgrPath = _moduleManagerPath; if (spec.version.startsWith('4.2')) { - var processedFile = File( - 'flutter-studio/src/io/flutter/android/AndroidModuleLibraryManager.java'); + var processedFile = File(libMgrPath); var source = processedFile.readAsStringSync(); var original = source; source = source.replaceAll("ProjectExImpl", "ProjectImpl"); @@ -133,8 +137,7 @@ class EditAndroidModuleLibraryManager extends EditCommand { return original; } else { if (spec.version.startsWith('2021.2')) { - var processedFile = File( - 'flutter-studio/src/io/flutter/android/AndroidModuleLibraryManager.java'); + var processedFile = File(libMgrPath); var source = processedFile.readAsStringSync(); var original = source; source = source.replaceAll( diff --git a/tool/plugin/lib/globals.dart b/tool/plugin/lib/globals.dart index 690343c579..30bcca4948 100644 --- a/tool/plugin/lib/globals.dart +++ b/tool/plugin/lib/globals.dart @@ -14,7 +14,7 @@ const int cloudErrorFileMaxSize = 1000; // In bytes. // Globals are initialized early in ProductCommand. These are used in various // top-level functions. This is not ideal, but the "proper" solution would be // to move nearly all the top-level functions to methods in ProductCommand. -String rootPath; +String rootPath = ''; String lastReleaseName; DateTime lastReleaseDate; int pluginCount = 0; diff --git a/tool/plugin/lib/plugin.dart b/tool/plugin/lib/plugin.dart index 1ba612f92d..3dcb58d61b 100644 --- a/tool/plugin/lib/plugin.dart +++ b/tool/plugin/lib/plugin.dart @@ -60,10 +60,10 @@ List createBuildSpecs(ProductCommand command) { Future deleteBuildContents() async { final dir = Directory(p.join(rootPath, 'build')); if (!dir.existsSync()) throw 'No build directory found'; - var args = []; - args.add('-rf'); - args.add(p.join(rootPath, 'build', '*')); - return await exec('rm', args); + log('Deleting build contents from ${dir.path}'); + dir.deleteSync(recursive: true); + + return 0; } List findJars(String path) { @@ -136,7 +136,7 @@ void genPresubmitYaml(List specs) { } bool isTravisFileValid() { - var travisPath = p.join(rootPath, '.github/workflows/presubmit.yaml'); + var travisPath = p.join(rootPath, '.github', 'workflows', 'presubmit.yaml'); var travisFile = File(travisPath); if (!travisFile.existsSync()) { return false; @@ -155,17 +155,33 @@ Future jar(String directory, String outFile) async { .listSync(followLinks: false) .map((f) => p.basename(f.path))); args.remove('.DS_Store'); - return await exec('jar', args, cwd: directory); + try { + return await exec('jar', args, cwd: directory); + } on ProcessException catch (e) { + if (e.message == 'No such file or directory') { + log( + '\nThe build command requires `java` to be installed.' + '\nPlease ensure `jar` is on your \$PATH.', + indent: false); + exit(e.errorCode); + } else { + rethrow; + } + } } Future moveToArtifacts(ProductCommand cmd, BuildSpec spec) async { final dir = Directory(p.join(rootPath, 'artifacts')); if (!dir.existsSync()) throw 'No artifacts directory found'; - var file = pluginRegistryIds[spec.pluginId]; - var args = []; - args.add(p.join(rootPath, 'build', file)); - args.add(cmd.releasesFilePath(spec)); - return await exec('mv', args); + + var targetFile = + File(p.join(rootPath, 'build', pluginRegistryIds[spec.pluginId])); + var newPath = cmd.releasesFilePath(spec); + + log('Moving ${targetFile.path} to $newPath'); + targetFile.renameSync(newPath); + + return 0; } Future performReleaseChecks(ProductCommand cmd) async { @@ -314,10 +330,11 @@ class AntBuildCommand extends BuildCommand { var r = await runner.javac2(spec); if (r == 0) { // copy resources - copyResources(from: 'src', to: 'build/classes'); - copyResources(from: 'resources', to: 'build/classes'); - copyResources(from: 'gen', to: 'build/classes'); - await genPluginFiles(spec, 'build/classes'); + var classPath = p.join('build', 'classes'); + copyResources(from: 'src', to: classPath); + copyResources(from: 'resources', to: classPath); + copyResources(from: 'gen', to: classPath); + await genPluginFiles(spec, classPath); } return r; } @@ -327,27 +344,26 @@ class AntBuildCommand extends BuildCommand { int result; // create the jars - createDir('build/flutter-intellij/lib'); - result = await jar( - 'build/classes', - 'build/flutter-intellij/lib/flutter-intellij.jar', - ); + var fijPath = p.join('build', 'flutter-intellij', 'lib'); + var fijarPath = p.join(fijPath, 'flutter-intellij.jar'); + var classPath = p.join('build', 'classes'); + + createDir(fijPath); + result = await jar(classPath, fijarPath); if (result != 0) { log('jar failed: ${result.toString()}'); return result; } if (spec.isTestTarget && !isReleaseMode && !isDevChannel) { _copyFile( - File('build/flutter-intellij/lib/flutter-intellij.jar'), + File(fijarPath), Directory(testTargetPath(spec)), filename: 'io.flutter.jar', ); } if (spec.isAndroidStudio) { - result = await jar( - 'build/studio', - 'build/flutter-intellij/lib/flutter-studio.jar', - ); + var studioPath = p.join('build', 'studio'); + result = await jar(studioPath, fijarPath); if (result != 0) { log('jar failed: ${result.toString()}'); return result; @@ -355,7 +371,7 @@ class AntBuildCommand extends BuildCommand { } // zip it up - result = await zip('build/flutter-intellij', releasesFilePath(spec)); + result = await zip(fijPath, releasesFilePath(spec)); if (result != 0) { log('zip failed: ${result.toString()}'); return result; @@ -376,8 +392,9 @@ class GradleBuildCommand extends BuildCommand { @override Future externalBuildCommand(BuildSpec spec) async { - var pluginFile = File('resources/META-INF/plugin.xml'); - var studioFile = File('resources/META-INF/studio-contribs.xml'); + var metaInfPath = p.join('resources', 'META-INF'); + var pluginFile = File(p.join(metaInfPath, 'plugin.xml')); + var studioFile = File(p.join(metaInfPath, 'studio-contribs.xml')); var pluginSrc = pluginFile.readAsStringSync(); var studioSrc = studioFile.readAsStringSync(); try { @@ -393,12 +410,13 @@ class GradleBuildCommand extends BuildCommand { Future savePluginArtifact(BuildSpec spec) async { final file = File(releasesFilePath(spec)); final version = buildVersionNumber(spec); - var source = File('build/distributions/flutter-intellij-$version.zip'); + var distPath = p.join('build', 'distributions'); + var source = File(p.join(distPath, 'flutter-intellij-$version.zip')); if (!source.existsSync()) { // Setting the plugin name in Gradle should eliminate the need for this, // but it does not. // TODO(messick) Find a way to make the Kokoro file name: flutter-intellij-DEV.zip - source = File('build/distributions/flutter-intellij-kokoro-$version.zip'); + source = File(p.join(distPath, 'flutter-intellij-kokoro-$version.zip')); } _copyFile( source, @@ -410,11 +428,7 @@ class GradleBuildCommand extends BuildCommand { } Future _stopDaemon() async { - if (Platform.isWindows) { - return await exec('.\\gradlew.bat', ['--stop']); - } else { - return await exec('./gradlew', ['--stop']); - } + return execGradleCommand(['--stop']); } } @@ -505,7 +519,7 @@ abstract class BuildCommand extends ProductCommand { } separator('Building flutter-intellij.jar'); - await removeAll('build'); + removeAll('build'); log('spec.version: ${spec.version}'); @@ -851,7 +865,8 @@ class RenamePackageCommand extends ProductCommand { @override Future doit() async { - if (argResults['studio']) baseDir = p.join(baseDir, 'flutter-studio/src'); + if (argResults['studio']) + baseDir = p.join(baseDir, 'flutter-studio', 'src'); oldName = argResults['package']; newName = argResults.wasParsed('new-name') ? argResults['new-name'] @@ -868,19 +883,19 @@ class RenamePackageCommand extends ProductCommand { } void moveFiles() { - final srcDir = Directory(p.join(baseDir, oldName.replaceAll('.', '/'))); - final destDir = Directory(p.join(baseDir, newName.replaceAll('.', '/'))); + final srcDir = Directory(p.joinAll([baseDir] + oldName.split('.'))); + final destDir = Directory(p.joinAll([baseDir] + newName.split('.'))); _editAndMoveAll(srcDir, destDir); } void editReferences() { - final srcDir = Directory(p.join(baseDir, oldName.replaceAll('.', '/'))); - final destDir = Directory(p.join(baseDir, newName.replaceAll('.', '/'))); + final srcDir = Directory(p.joinAll([baseDir] + oldName.split('.'))); + final destDir = Directory(p.joinAll([baseDir] + newName.split('.'))); _editAll(Directory(baseDir), skipOld: srcDir, skipNew: destDir); } Future deleteDir() async { - final dir = Directory(p.join(baseDir, oldName.replaceAll('.', '/'))); + final dir = Directory(p.joinAll([baseDir] + oldName.split('.'))); await dir.delete(recursive: true); return 0; } diff --git a/tool/plugin/lib/runner.dart b/tool/plugin/lib/runner.dart index 72c74b0216..e3896afd1c 100644 --- a/tool/plugin/lib/runner.dart +++ b/tool/plugin/lib/runner.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:path/path.dart' as p; import 'build_spec.dart'; import 'globals.dart'; @@ -58,8 +59,8 @@ compile void writeJxBrowserKeyToFile() { final jxBrowserKey = readTokenFromKeystore('FLUTTER_KEYSTORE_JXBROWSER_KEY_NAME'); - final propertiesFile = - File("$rootPath/resources/jxbrowser/jxbrowser.properties"); + final propertiesFile = File( + p.join(rootPath, 'resources', 'jxbrowser', 'jxbrowser.properties')); if (jxBrowserKey.isNotEmpty) { final contents = ''' jxbrowser.license.key=$jxBrowserKey @@ -92,7 +93,7 @@ testing=$testing buildSpec=${spec.version} baseVersion=${spec.baseVersion} '''; - final propertiesFile = File("$rootPath/gradle.properties"); + final propertiesFile = File(p.join(rootPath, 'gradle.properties')); final source = propertiesFile.readAsStringSync(); propertiesFile.writeAsStringSync(contents); int result; @@ -100,19 +101,15 @@ baseVersion=${spec.baseVersion} // --daemon => Invalid byte 1 of 1-byte UTF-8 sequence, which is nonsense. // During instrumentation of FlutterProjectStep.form, which is a UTF-8 file. try { - if (Platform.isWindows) { - if (spec.version == '4.1') { + if (spec.version == '4.1') { + if (Platform.isWindows) { log('CANNOT BUILD ${spec.version} ON WINDOWS'); return 0; - } - result = await exec('.\\gradlew.bat', command); - } else { - if (spec.version == '4.1') { - return await runShellScript(command, spec); } else { - result = await exec('./gradlew', command); + return await runShellScript(command, spec); } } + result = await execGradleCommand(command); } finally { propertiesFile.writeAsStringSync(source); } diff --git a/tool/plugin/lib/util.dart b/tool/plugin/lib/util.dart index 8a30bda03c..e5c1b43ccb 100644 --- a/tool/plugin/lib/util.dart +++ b/tool/plugin/lib/util.dart @@ -6,9 +6,12 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:math' as math; +import 'package:archive/archive.dart'; import 'package:cli_util/cli_logging.dart'; import 'package:git/git.dart'; +import 'package:http/http.dart'; import 'package:path/path.dart' as p; import 'artifact.dart'; @@ -22,7 +25,7 @@ Future exec(String cmd, List args, {String cwd}) async { log(_shorten('$cmd ${args.join(' ')}')); } - var codec = Platform.isWindows ? latin1: utf8; + var codec = Platform.isWindows ? latin1 : utf8; final process = await Process.start(cmd, args, workingDirectory: cwd); _toLineStream(process.stderr, codec).listen(log); _toLineStream(process.stdout, codec).listen(log); @@ -99,18 +102,95 @@ void log(String s, {bool indent = true}) { void createDir(String name) { final dir = Directory(name); if (!dir.existsSync()) { - log('creating $name/'); + log('creating $name'); dir.createSync(recursive: true); } } -Future curl(String url, {String to}) async { - return await exec('curl', ['-o', to, url]); +Future download(String url, {String to}) async { + final File tmpFile = File('$to.tmp'); + final httpClient = Client(); + final request = Request('GET', Uri.parse(url)); + + List> blocks = []; + int blocksDownloaded = 0; + int totalDownloaded = 0; + + log('\n% Done\tReceived'); + + try { + StreamedResponse response = await httpClient.send(request); + + final flushSize = (response.contentLength / 100).floor(); //arbitrary value + final fmtDownloadSize = formatBytes(response.contentLength); + + // helper function to write blocks to tmpFile and log output + _flushToFile(List> blocks) { + var pctComplete = + (totalDownloaded / response.contentLength * 100).floor(); + + log('$pctComplete\t${formatBytes(totalDownloaded)} of $fmtDownloadSize'); + + for (var block in blocks) { + tmpFile.writeAsBytesSync(block, mode: FileMode.append); + } + } + + await for (final List block in response.stream) { + blocks.add(block); + blocksDownloaded += block.length; + totalDownloaded += block.length; + + if (blocksDownloaded > flushSize) { + _flushToFile(blocks); + + blocks = []; + blocksDownloaded = 0; + } + } + + _flushToFile(blocks); + + tmpFile.renameSync(to); + } catch (e) { + log(e); + return -1; + } + + return 0; } -Future removeAll(String dir) async { - var args = ['-rf', dir]; - return await exec('rm', args); +String formatBytes(int bytes) { + if (bytes <= 0) return '0'; + const suffixes = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; + + var i = (math.log(bytes) / math.log(1024)).floor(); + var size = ((bytes / math.pow(1024, i)).toStringAsFixed(0)); + var suffix = suffixes[i]; + + return '$size$suffix'; +} + +int removeAll(String dir) { + var targetDirectory = Directory(dir); + + log('Removing Directory $dir'); + + if (targetDirectory.existsSync()) { + try { + targetDirectory.deleteSync(recursive: true); + } on FileSystemException catch (e) { + log(e.osError.message); + return -1; + } catch (e) { + log("An error occurred while deleteing $dir"); + log(e); + + return -1; + } + } + + return 0; } bool isCacheDirectoryValid(Artifact artifact) { @@ -149,15 +229,14 @@ Stream _toLineStream(Stream> s, Encoding encoding) => String readTokenFromKeystore(String keyName) { var env = Platform.environment; - var base = env['KOKORO_KEYSTORE_DIR']; - var id = env['FLUTTER_KEYSTORE_ID']; - var name = env[keyName]; + var base = env['KOKORO_KEYSTORE_DIR'] ?? ''; + var id = env['FLUTTER_KEYSTORE_ID'] ?? ''; + var name = env[keyName] ?? ''; - var file = File('$base/${id}_$name'); + var file = File(p.join(base, '${id}_$name')); return file.existsSync() ? file.readAsStringSync() : ''; } - int get devBuildNumber { // The dev channel is automatically refreshed weekly, so the build number // is just the number of weeks since the last stable release. @@ -182,7 +261,115 @@ String buildVersionNumber(BuildSpec spec) { String _nextRelease() { var current = - RegExp(r'release_(\d+)').matchAsPrefix(lastReleaseName).group(1); + RegExp(r'release_(\d+)').matchAsPrefix(lastReleaseName).group(1); var val = int.parse(current) + 1; return '$val.0'; } + +/// Replicates `tar` --strip-components=N behaviour +/// +/// Returns [filePath] with the first [i] components removed. +/// e.g. ('a/b/c/', 1) returns 'b/c' +String stripComponents(String filePath, int i) { + List components = p.split(filePath); + + if (i < components.length - 1) { + components.removeRange(0, i); + } else { + components.removeRange(0, components.length - 1); + } + + return p.joinAll(components); +} + +/// Returns a File from [filePath], throw an error if it doesn't exist +File getFileOrThrow(String filePath) { + var file = File(filePath); + + if (!file.existsSync()) { + throw FileSystemException("Unable to locate file '${file.path}'"); + } + + return file; +} + +/// Extract files from a tar'd [artifact.file] to [artifact.output] +/// +/// The [artifact] file location can be specified using [cwd] and +/// [targetDirectory] can be used to set a directory for the extracted files. +/// +/// An int is returned to match existing patterns of checking for an error ala +/// the CLI +int extractTar(Artifact artifact, {targetDirectory = '', cwd = ''}) { + var artifactPath = p.join(cwd, artifact.file); + var outputDir = p.join(cwd, targetDirectory, artifact.output); + + log('Extracting $artifactPath to $outputDir'); + + try { + var file = getFileOrThrow(artifactPath); + var decodedGZipContent = GZipCodec().decode(file.readAsBytesSync()); + var iStream = InputStream(decodedGZipContent); + var decodedArchive = TarDecoder().decodeBuffer(iStream); + + for (var tarFile in decodedArchive.files) { + if (!tarFile.isFile) continue; // Don't need to create empty directories + + File(p.join(outputDir, stripComponents(tarFile.name, 1))) + ..createSync(recursive: true) + ..writeAsBytesSync(tarFile.content); + } + } on FileSystemException catch (e) { + log(e.osError?.message ?? e.message); + return -1; + } catch (e) { + log('An unknown error occurred: $e'); + return -1; + } + + return 0; +} + +/// Extract files from a zipped [artifactPath] +/// +/// The [artifact] file location can be specified using [cwd] and +/// [targetDirectory] can be used to set a directory for the extracted files. +/// +/// An int is returned to match existing patterns of checking for an error ala +/// the CLI +int extractZip(String artifactPath, {targetDirectory = '', cwd = ''}) { + var filePath = p.join(cwd, artifactPath); + var outputPath = p.join(cwd, targetDirectory); + + try { + File file = getFileOrThrow(filePath); + + Archive zipArchive = ZipDecoder().decodeBytes(file.readAsBytesSync()); + + for (ArchiveFile archiveItem in zipArchive.files) { + // Don't need to create empty directories + if (!archiveItem.isFile) continue; + + File(p.join(outputPath, archiveItem.name)) + ..createSync(recursive: true) + ..writeAsBytesSync(archiveItem.content); + } + } on FileSystemException catch (e) { + log(e.osError?.message ?? e.message); + return -1; + } catch (e) { + log('An unknown error occurred: $e'); + return -1; + } + + return 0; +} + +/// Calls the platform specific gradle wrapper with the provided arguments +Future execGradleCommand(List args) async { + if (Platform.isWindows) { + return await exec('.\\gradlew.bat', args); + } else { + return await exec('./gradlew', args); + } +} diff --git a/tool/plugin/pubspec.yaml b/tool/plugin/pubspec.yaml index 2841fad205..6a8b5e08da 100644 --- a/tool/plugin/pubspec.yaml +++ b/tool/plugin/pubspec.yaml @@ -11,6 +11,8 @@ dependencies: git: ^2.0.0 markdown: ^4.0.0 path: ^1.7.0 + archive: ^3.2.2 + http: ^0.13.4 dev_dependencies: lints: ^1.0.0 diff --git a/tool/plugin/test/artifact_test.dart b/tool/plugin/test/artifact_test.dart new file mode 100644 index 0000000000..fac42759c0 --- /dev/null +++ b/tool/plugin/test/artifact_test.dart @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +// @dart = 2.10 +import 'package:path/path.dart' as p; +import 'package:plugin_tool/util.dart'; +import 'package:test/test.dart'; + +void main() { + group('Validate `stripComponents` function', () { + test('Correct components are removed', () { + var testPath = p.join('a', 'b', 'c', 'd'); + expect(stripComponents(testPath, 1), p.join('b', 'c', 'd')); + expect(stripComponents(testPath, 2), p.join('c', 'd')); + expect(stripComponents(testPath, 3), p.join('d')); + }); + + test('Last element is always returned', () { + var testPath = p.join('a', 'b', 'c', 'd'); + + expect(stripComponents(testPath, 4), p.join('d')); + expect(stripComponents(testPath, 5), p.join('d')); + }); + }); +}