From d71a0e17cf2e2218e5ff049334ef9345716d3789 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Sat, 9 Mar 2019 12:39:16 -0800 Subject: [PATCH] Drop android_sdk_downloader in favor of cipd (flutter/engine#8087) * remove sdk downloader, use cipd * roll buildroot to drop android_sdk_downloader --- DEPS | 85 ++-- .../tools/android_sdk_downloader/.gitignore | 7 - .../tools/android_sdk_downloader/LICENSE | 27 -- .../tools/android_sdk_downloader/README.md | 31 -- .../analysis_options.yaml | 155 ------- .../android_sdk_downloader/lib/main.dart | 220 --------- .../lib/src/android_repository.dart | 416 ------------------ .../lib/src/checksums.dart | 31 -- .../android_sdk_downloader/lib/src/http.dart | 130 ------ .../lib/src/options.dart | 129 ------ .../android_sdk_downloader/lib/src/zip.dart | 41 -- .../tools/android_sdk_downloader/pubspec.yaml | 12 - .../flutter/tools/create_ndk_cipd_package.sh | 31 ++ .../flutter/tools/create_sdk_cipd_package.sh | 43 ++ 14 files changed, 130 insertions(+), 1228 deletions(-) delete mode 100644 engine/src/flutter/tools/android_sdk_downloader/.gitignore delete mode 100644 engine/src/flutter/tools/android_sdk_downloader/LICENSE delete mode 100644 engine/src/flutter/tools/android_sdk_downloader/README.md delete mode 100644 engine/src/flutter/tools/android_sdk_downloader/analysis_options.yaml delete mode 100644 engine/src/flutter/tools/android_sdk_downloader/lib/main.dart delete mode 100644 engine/src/flutter/tools/android_sdk_downloader/lib/src/android_repository.dart delete mode 100644 engine/src/flutter/tools/android_sdk_downloader/lib/src/checksums.dart delete mode 100644 engine/src/flutter/tools/android_sdk_downloader/lib/src/http.dart delete mode 100644 engine/src/flutter/tools/android_sdk_downloader/lib/src/options.dart delete mode 100644 engine/src/flutter/tools/android_sdk_downloader/lib/src/zip.dart delete mode 100644 engine/src/flutter/tools/android_sdk_downloader/pubspec.yaml create mode 100755 engine/src/flutter/tools/create_ndk_cipd_package.sh create mode 100755 engine/src/flutter/tools/create_sdk_cipd_package.sh diff --git a/DEPS b/DEPS index af289c6064..1b9e0071fa 100644 --- a/DEPS +++ b/DEPS @@ -122,7 +122,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + '4a12b0dfad16723b2190b697a669e3ae17b50b35', + 'src': 'https://github.com/flutter/buildroot.git' + '@' + '3f54d4f03112098e164ee62f015fcc54b19d1eda', # Fuchsia compatibility # @@ -387,6 +387,61 @@ deps = { 'src/third_party/pkg/when': Var('dart_git') + '/when.git' + '@' + '0.2.0', + + 'src/third_party/android_tools/ndk': { + 'packages': [ + { + 'package': 'flutter/android/ndk/${{platform}}', + 'version': 'version:r19b' + } + ], + 'condition': 'download_android_deps', + 'dep_type': 'cipd', + }, + + 'src/third_party/android_tools/sdk/build-tools': { + 'packages': [ + { + 'package': 'flutter/android/sdk/build-tools/${{platform}}', + 'version': 'version:28.0.3' + } + ], + 'condition': 'download_android_deps', + 'dep_type': 'cipd', + }, + + 'src/third_party/android_tools/sdk/platform-tools': { + 'packages': [ + { + 'package': 'flutter/android/sdk/platform-tools/${{platform}}', + 'version': 'version:28.0.1' + } + ], + 'condition': 'download_android_deps', + 'dep_type': 'cipd', + }, + + 'src/third_party/android_tools/sdk/platforms': { + 'packages': [ + { + 'package': 'flutter/android/sdk/platforms', + 'version': 'version:28r6' + } + ], + 'condition': 'download_android_deps', + 'dep_type': 'cipd', + }, + + 'src/third_party/android_tools/sdk/tools': { + 'packages': [ + { + 'package': 'flutter/android/sdk/tools/${{platform}}', + 'version': 'version:26.1.1' + } + ], + 'condition': 'download_android_deps', + 'dep_type': 'cipd', + }, } hooks = [ @@ -414,34 +469,6 @@ hooks = [ 'pattern': '.', 'action': ['python', 'src/tools/dart/update.py'], }, - { - 'name': 'prepare_android_downloader', - 'pattern': '.', - 'condition': 'download_android_deps', - 'cwd': 'src/flutter/tools/android_sdk_downloader', - 'action': [ - '../../../third_party/dart/tools/sdks/dart-sdk/bin/pub', # this hook _must_ be run _after_ the dart hook. - 'get' - ], - }, - { - 'name': 'download_android_tools', - 'pattern': '.', - 'condition': 'download_android_deps', - 'action': [ - 'src/third_party/dart/tools/sdks/dart-sdk/bin/dart', # this hook _must_ be run _after_ the dart hook. - '--enable-asserts', - 'src/flutter/tools/android_sdk_downloader/lib/main.dart', - '-y', # Accept licenses - '--out=src/third_party/android_tools', - '--platform=28', - '--platform-revision=6', - '--build-tools-version=28.0.3', - '--platform-tools-version=28.0.1', - '--tools-version=26.1.1', - '--ndk-version=19.1.5304403' - ], - }, { 'name': 'download_android_support', 'pattern': '.', diff --git a/engine/src/flutter/tools/android_sdk_downloader/.gitignore b/engine/src/flutter/tools/android_sdk_downloader/.gitignore deleted file mode 100644 index d6a35f97f7..0000000000 --- a/engine/src/flutter/tools/android_sdk_downloader/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.DS_Store -.atom/ -.idea -.packages -.pub/ -.dart_tool/ -pubspec.lock \ No newline at end of file diff --git a/engine/src/flutter/tools/android_sdk_downloader/LICENSE b/engine/src/flutter/tools/android_sdk_downloader/LICENSE deleted file mode 100644 index 58ab87f9b9..0000000000 --- a/engine/src/flutter/tools/android_sdk_downloader/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/engine/src/flutter/tools/android_sdk_downloader/README.md b/engine/src/flutter/tools/android_sdk_downloader/README.md deleted file mode 100644 index c705ee2142..0000000000 --- a/engine/src/flutter/tools/android_sdk_downloader/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Android SDK Downloader - -This program assists with downloading the Android SDK and NDK artifacts for -Flutter engine development. - -## Usage - -``` --r, --repository-xml Specifies the location of the Android Repository XML file. - (defaults to "https://dl.google.com/android/repository/repository2-1.xml") - --p, --platform Specifies the Android platform version, e.g. 28 - - --platform-revision Specifies the Android platform revision, e.g. 6 for 28_r06 - --o, --out The directory to write downloaded files to. - - --os The OS type to download for. Defaults to current platform. - (defaults to current platform), accepts: [windows, macos, linux] - - --build-tools-version The build-tools version to download. Must be in format of .., e.g. 28.0.3; or ..., e.g. 28.0.0.2 - - --platform-tools-version The platform-tools version to download. Must be in format of .., e.g. 28.0.1; or ..., e.g. 28.0.0.2 - - --tools-version The tools version to download. Must be in format of .., e.g. 26.1.1; or ..., e.g. 28.1.1.2 - - --ndk-version The ndk version to download. Must be in format of .., e.g. 28.0.3; or ..., e.g. 28.0.0.2 - --y, --[no-]accept-licenses Automatically accept Android SDK licenses. - --[no-]overwrite Skip download if the target directory exists. -``` \ No newline at end of file diff --git a/engine/src/flutter/tools/android_sdk_downloader/analysis_options.yaml b/engine/src/flutter/tools/android_sdk_downloader/analysis_options.yaml deleted file mode 100644 index 2023b29bda..0000000000 --- a/engine/src/flutter/tools/android_sdk_downloader/analysis_options.yaml +++ /dev/null @@ -1,155 +0,0 @@ -# Specify analysis options. -# -# Copied from https://github.com/flutter/flutter/blob/master/analysis_options.yaml - -analyzer: - strong-mode: - implicit-dynamic: false - errors: - # treat missing required parameters as a warning (not a hint) - missing_required_param: warning - # treat missing returns as a warning (not a hint) - missing_return: warning - # allow having TODOs in the code - todo: ignore - exclude: - - 'bin/cache/**' - # the following two are relative to the stocks example and the flutter package respectively - # see https://github.com/dart-lang/sdk/issues/28463 - - 'lib/i18n/stock_messages_*.dart' - - 'lib/src/http/**' - -linter: - rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # https://github.com/dart-lang/linter/blob/master/example/all.yaml - - always_declare_return_types - - always_put_control_body_on_new_line - # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 - - always_require_non_null_named_parameters - - always_specify_types - - annotate_overrides - # - avoid_annotating_with_dynamic # conflicts with always_specify_types - - avoid_as - # - avoid_bool_literals_in_conditional_expressions # not yet tested - # - avoid_catches_without_on_clauses # we do this commonly - # - avoid_catching_errors # we do this commonly - - avoid_classes_with_only_static_members - # - avoid_double_and_int_checks # only useful when targeting JS runtime - - avoid_empty_else - - avoid_field_initializers_in_const_classes - - avoid_function_literals_in_foreach_calls - # - avoid_implementing_value_types # not yet tested - - avoid_init_to_null - # - avoid_js_rounded_ints # only useful when targeting JS runtime - - avoid_null_checks_in_equality_operators - # - avoid_positional_boolean_parameters # not yet tested - # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) - - avoid_relative_lib_imports - - avoid_renaming_method_parameters - - avoid_return_types_on_setters - # - avoid_returning_null # there are plenty of valid reasons to return null - - avoid_returning_null_for_void - # - avoid_returning_this # there are plenty of valid reasons to return this - # - avoid_setters_without_getters # not yet tested - # - avoid_single_cascade_in_expression_statements # not yet tested - - avoid_slow_async_io - - avoid_types_as_parameter_names - # - avoid_types_on_closure_parameters # conflicts with always_specify_types - - avoid_unused_constructor_parameters - - avoid_void_async - - await_only_futures - - camel_case_types - - cancel_subscriptions - # - cascade_invocations # not yet tested - # - close_sinks # not reliable enough - # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 - # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 - - control_flow_in_finally - # - curly_braces_in_flow_control_structures # not yet tested - - directives_ordering - - empty_catches - - empty_constructor_bodies - - empty_statements - # - file_names # not yet tested - - flutter_style_todos - - hash_and_equals - - implementation_imports - # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 - - iterable_contains_unrelated_type - # - join_return_with_assignment # not yet tested - - library_names - - library_prefixes - # - lines_longer_than_80_chars # not yet tested - - list_remove_unrelated_type - # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 - - no_adjacent_strings_in_list - - no_duplicate_case_values - - non_constant_identifier_names - # - null_closures # not yet tested - # - omit_local_variable_types # opposite of always_specify_types - # - one_member_abstracts # too many false positives - # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 - - overridden_fields - - package_api_docs - - package_names - - package_prefixed_library_names - # - parameter_assignments # we do this commonly - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_const_literals_to_create_immutables - # - prefer_constructors_over_static_methods # not yet tested - - prefer_contains - - prefer_equal_for_default_values - # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - - prefer_final_fields - - prefer_final_locals - - prefer_foreach - # - prefer_function_declarations_over_variables # not yet tested - - prefer_generic_function_type_aliases - - prefer_initializing_formals - # - prefer_int_literals # not yet tested - # - prefer_interpolation_to_compose_strings # not yet tested - - prefer_is_empty - - prefer_is_not_empty - - prefer_iterable_whereType - # - prefer_mixin # https://github.com/dart-lang/language/issues/32 - - prefer_single_quotes - - prefer_typing_uninitialized_variables - - prefer_void_to_null - # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml - - recursive_getters - - slash_for_doc_comments - - sort_constructors_first - - sort_pub_dependencies - - sort_unnamed_constructors_first - - super_goes_last - - test_types_in_equals - - throw_in_finally - # - type_annotate_public_apis # subset of always_specify_types - - type_init_formals - # - unawaited_futures # too many false positives - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_overrides - - unnecessary_parenthesis - - unnecessary_statements - - unnecessary_this - - unrelated_type_equality_checks - - use_rethrow_when_possible - # - use_setters_to_change_properties # not yet tested - # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 - # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review - - valid_regexps - # - void_checks # not yet tested diff --git a/engine/src/flutter/tools/android_sdk_downloader/lib/main.dart b/engine/src/flutter/tools/android_sdk_downloader/lib/main.dart deleted file mode 100644 index f563889c82..0000000000 --- a/engine/src/flutter/tools/android_sdk_downloader/lib/main.dart +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2013 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 'dart:convert'; -import 'dart:io'; - -import 'package:args/args.dart'; -import 'package:path/path.dart' as path; - -import 'src/android_repository.dart'; -import 'src/checksums.dart'; -import 'src/http.dart'; -import 'src/options.dart'; -import 'src/zip.dart'; - -const String _kAndroidRepositoryXml = 'https://dl.google.com/android/repository/repository2-1.xml'; - - -Future main(List args) async { - final ArgParser argParser = ArgParser() - ..addOption( - 'repository-xml', - abbr: 'r', - help: 'Specifies the location of the Android Repository XML file.', - defaultsTo: _kAndroidRepositoryXml, - ) - ..addOption( - 'platform', - abbr: 'p', - help: 'Specifies the Android platform version, e.g. 28', - ) - ..addOption( - 'platform-revision', - help: 'Specifies the Android platform revision, e.g. 6 for 28_r06', - ) - ..addOption( - 'out', - abbr: 'o', - help: 'The directory to write downloaded files to.', - defaultsTo: '.', - ) - ..addOption( - 'os', - help: 'The OS type to download for. Defaults to current platform.', - defaultsTo: Platform.operatingSystem, - allowed: osTypeMap.keys, - ) - ..addOption( - 'build-tools-version', - help: 'The build-tools version to download. Must be in format of ' - '.., e.g. 28.0.3; ' - 'or ..., e.g. 28.0.0.2', - ) - ..addOption( - 'platform-tools-version', - help: 'The platform-tools version to download. Must be in format of ' - '.., e.g. 28.0.1; ' - 'or ..., e.g. 28.0.0.2', - ) - ..addOption( - 'tools-version', - help: 'The tools version to download. Must be in format of ' - '.., e.g. 26.1.1; ' - 'or ..., e.g. 28.1.1.2', - ) - ..addOption( - 'ndk-version', - help: 'The ndk version to download. Must be in format of ' - '.., e.g. 28.0.3; ' - 'or ..., e.g. 28.0.0.2', - ) - ..addFlag('accept-licenses', - abbr: 'y', - defaultsTo: false, - help: 'Automatically accept Android SDK licenses.') - ..addFlag( - 'overwrite', - defaultsTo: false, - help: 'Skip download if the target directory exists.', - ); - - final bool help = args.contains('-h') - || args.contains('--help') - || (args.isNotEmpty && args.first == 'help'); - if (help) { - print(argParser.usage); - return; - } - - final Options options = Options.parseAndValidate(args, argParser); - - final AndroidRepository androidRepository = await _getAndroidRepository(options.repositoryXmlUri); - assert(androidRepository.platforms.isNotEmpty); - assert(androidRepository.buildTools.isNotEmpty); - - if (!options.acceptLicenses) { - for (final AndroidRepositoryLicense license in androidRepository.licenses) { - print('================================================================================\n\n'); - print(license.text); - stdout.write('Do you accept? (Y/n): '); - final String result = stdin.readLineSync().trim().toLowerCase(); - if (result != '' && result.startsWith('y') == false) { - print('Ending.'); - exit(-1); - } - } - } - - await options.outDirectory.create(recursive: true); - - final Directory tempDir = await Directory(options.outDirectory.path).createTemp(); - await tempDir.create(recursive: true); - - final Directory ndkDir = Directory(path.join(options.outDirectory.path, 'ndk')); - final Directory sdkDir = Directory(path.join(options.outDirectory.path, 'sdk')); - final Directory platformDir = Directory(path.join(sdkDir.path, 'platforms', 'android-${options.platformApiLevel}')); - final Directory buildToolsDir = Directory(path.join(sdkDir.path, 'build-tools', options.buildToolsRevision.raw)); - final Directory platformToolsDir = Directory(path.join(sdkDir.path, 'platform-tools')); - final Directory toolsDir = Directory(path.join(sdkDir.path, 'tools')); - - final Map checksums = - await loadChecksums(options.outDirectory); - - print('Downloading Android SDK and NDK artifacts...'); - final List> futures = >[]; - - futures.add(downloadArchive( - androidRepository.platforms, - OptionsRevision(null, options.platformRevision), - options.repositoryBase, - tempDir, - checksumToSkip: options.overwrite ? null : checksums[platformDir.path], - ).then((ArchiveDownloadResult result) { - if (result != ArchiveDownloadResult.empty) { - return unzipFile(result.zipFileName, platformDir).then((_) { - checksums[platformDir.path] = result.checksum; - return writeChecksums(checksums, options.outDirectory); - }); - } - return null; - })); - futures.add(downloadArchive( - androidRepository.buildTools, - options.buildToolsRevision, - options.repositoryBase, - tempDir, - osType: options.osType, - checksumToSkip: options.overwrite ? null : checksums[buildToolsDir.path], - ).then((ArchiveDownloadResult result) { - if (result != ArchiveDownloadResult.empty) { - return unzipFile(result.zipFileName, buildToolsDir).then((_) { - checksums[buildToolsDir.path] = result.checksum; - return writeChecksums(checksums, options.outDirectory); - }); - } - return null; - })); - futures.add(downloadArchive( - androidRepository.platformTools, - options.platformToolsRevision, - options.repositoryBase, - tempDir, - osType: options.osType, - checksumToSkip: options.overwrite ? null : checksums[platformToolsDir.path], - ).then((ArchiveDownloadResult result) { - if (result != ArchiveDownloadResult.empty) { - return unzipFile(result.zipFileName, platformToolsDir).then((_) { - checksums[platformToolsDir.path] = result.checksum; - return writeChecksums(checksums, options.outDirectory); - }); - } - return null; - })); - futures.add(downloadArchive( - androidRepository.tools, - options.toolsRevision, - options.repositoryBase, - tempDir, - osType: options.osType, - checksumToSkip: options.overwrite ? null : checksums[toolsDir.path], - ).then((ArchiveDownloadResult result) { - if (result != ArchiveDownloadResult.empty) { - return unzipFile(result.zipFileName, toolsDir).then((_) { - checksums[toolsDir.path] = result.checksum; - return writeChecksums(checksums, options.outDirectory); - }); - } - return null; - })); - futures.add(downloadArchive( - androidRepository.ndkBundles, - options.ndkRevision, - options.repositoryBase, - tempDir, - osType: options.osType, - checksumToSkip: options.overwrite ? null : checksums[ndkDir.path], - ).then((ArchiveDownloadResult result) { - if (result != ArchiveDownloadResult.empty) { - return unzipFile(result.zipFileName, ndkDir).then((_) { - checksums[ndkDir.path] = result.checksum; - return writeChecksums(checksums, options.outDirectory); - }); - } - return null; - })); - await Future.wait(futures); - await tempDir.delete(recursive: true); -} - -Future _getAndroidRepository(Uri repositoryXmlUri) async { - final StringBuffer repoXmlBuffer = StringBuffer(); - Future _repositoryXmlHandler(HttpClientResponse response) async { - await response.transform(utf8.decoder).forEach(repoXmlBuffer.write); - } - - await httpGet(repositoryXmlUri, _repositoryXmlHandler); - - return parseAndroidRepositoryXml(repoXmlBuffer.toString()); -} diff --git a/engine/src/flutter/tools/android_sdk_downloader/lib/src/android_repository.dart b/engine/src/flutter/tools/android_sdk_downloader/lib/src/android_repository.dart deleted file mode 100644 index 3490d02e95..0000000000 --- a/engine/src/flutter/tools/android_sdk_downloader/lib/src/android_repository.dart +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright 2013 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 'package:xml/xml.dart' as xml show parse; -import 'package:xml/xml.dart'; - -// see https://android.googlesource.com/platform/tools/base/+/master/sdklib/src/main/java/com/android/sdklib/repository/sdk-repository-10.xsd - -const String _kXsi = 'http://www.w3.org/2001/XMLSchema-instance'; -const String _kSdk = 'http://schemas.android.com/sdk/android/repo/repository2/01'; - -void _debugCheckElement( - XmlElement element, - String name, { - String namespace, -}) { - assert(element != null); - assert(element.name.local == name, '${element.name.local} != $name'); - assert(element.name.namespaceUri == namespace, - '${element.name.namespaceUri} != $namespace'); -} - -XmlElement _firstOrDefault(Iterable list) { - if (list?.isEmpty == true) { - return null; - } - return list.first; -} - -String _getChildText( - XmlElement parent, - String childName, { - String def = '', - String namespace, -}) { - final String value = _firstOrDefault( - parent.findElements( - childName, - namespace: namespace, - ), - )?.text; - return value ?? def; -} - -OSType _parseHostType(String value) { - switch (value) { - case 'linux': - return OSType.linux; - case 'windows': - return OSType.windows; - case 'macosx': - return OSType.mac; - default: - return OSType.any; - } -} - -/// Parses a the Android SDK's https://dl.google.com/android/repository/repository2-1.xml -/// into an [AndroidRepository] object. -AndroidRepository parseAndroidRepositoryXml(String rawXml) { - final XmlDocument doc = xml.parse(rawXml); - return AndroidRepository.fromXml(doc.rootElement); -} - -XmlElement _getTypeDetails(XmlElement parent) { - final XmlElement typeDetails = - _firstOrDefault(parent.findAllElements('type-details')); - if (typeDetails == null) { - throw StateError('Missing .'); - } - return typeDetails; -} - -String _getTypeDetailsType(XmlElement typeDetails) { - return typeDetails.getAttribute('type', namespace: _kXsi); -} - -Iterable _getArchives(XmlElement parent) { - assert(parent != null); - final XmlElement archives = - _firstOrDefault(parent.findAllElements('archives')); - if (archives == null) { - return null; - } - return archives.findElements('archive'); -} - -/// Object class for https://dl.google.com/android/repository/repository2-1.xml. -class AndroidRepository { - const AndroidRepository( - this.licenses, - this.platforms, - this.buildTools, - this.platformTools, - this.tools, - this.ndkBundles, - ) : assert(licenses != null), - assert(platforms != null), - assert(buildTools != null), - assert(platformTools != null), - assert(tools != null), - assert(ndkBundles != null); - - /// Parses the `` element. - factory AndroidRepository.fromXml(XmlElement element) { - _debugCheckElement(element, 'sdk-repository', namespace: _kSdk); - final List licenses = - []; - final List platforms = - []; - final List buildTools = - []; - final List platformTools = - []; - final List tools = - []; - - final List ndkBundles = - []; - for (final XmlElement child in element.children.whereType()) { - switch (child.name.local) { - case 'license': - licenses.add(AndroidRepositoryLicense.fromXml(child)); - break; - case 'remotePackage': - final XmlElement typeDetails = _getTypeDetails(child); - switch (_getTypeDetailsType(typeDetails)) { - case 'sdk:platformDetailsType': - platforms.add( - AndroidRepositoryPlatform.fromXml(child, typeDetails), - ); - break; - case 'generic:genericDetailsType': - final String path = child.getAttribute('path'); - if (path.startsWith('build-tools;')) { - buildTools.add(AndroidRepositoryRemotePackage.fromXml(child)); - } else if (path.startsWith('platform-tools')) { - platformTools - .add(AndroidRepositoryRemotePackage.fromXml(child)); - } else if (path.startsWith('tools')) { - tools.add(AndroidRepositoryRemotePackage.fromXml(child)); - } else if (path.startsWith('ndk-bundle')) { - ndkBundles.add(AndroidRepositoryRemotePackage.fromXml(child)); - } - break; - default: - break; - } - break; - default: - break; - } - } - return AndroidRepository( - licenses, - platforms, - buildTools, - platformTools, - tools, - ndkBundles, - ); - } - - /// Licenses from the repository XML. - final List licenses; - - /// Platform information from the repository XML. - final List platforms; - - /// Build tools information from the repostiory XML. - final List buildTools; - - /// Platform tools information from the repostiory XML. - final List platformTools; - - /// Tools information from the repostiory XML. - final List tools; - - /// Tools information from the repostiory XML. - final List ndkBundles; -} - -/// Object class for the `` element in the Android repo XML. -/// -/// This node contains license information for the packages in the SDK. -class AndroidRepositoryLicense { - /// Creates a new RepositoryLicense holder. - const AndroidRepositoryLicense(this.id, this.text) - : assert(id != null), - assert(text != null); - - /// Parses a `` element. - factory AndroidRepositoryLicense.fromXml(XmlElement element) { - _debugCheckElement(element, 'license'); - return AndroidRepositoryLicense(element.getAttribute('id'), element.text); - } - - /// The identifier for this license. - final String id; - - /// The text of the license. - final String text; -} - -/// Object class for the `` nodes in the repo XML. -/// -/// These nodes contain information about where to download the zipped -/// binaries for various components of the SDK. -class AndroidRepositoryRemotePackage { - const AndroidRepositoryRemotePackage( - this.revision, - this.displayName, - this.archives, { - this.isObsolete = false, - }) : assert(revision != null), - assert(displayName != null), - assert(archives != null), - assert(isObsolete != null); - - factory AndroidRepositoryRemotePackage.fromXml(XmlElement element) { - _debugCheckElement(element, 'remotePackage'); - - return AndroidRepositoryRemotePackage( - AndroidRepositoryRevision.fromXml( - _firstOrDefault(element.findElements('revision'))), - _getChildText(element, 'display-name'), - _getArchives(element) - .map( - (XmlElement archive) => AndroidRepositoryArchive.fromXml(archive), - ) - .toList(), - isObsolete: element.getAttribute('obsolete') == 'true', - ); - } - - /// The `` element, if any. - final AndroidRepositoryRevision revision; - - /// The `` element. - final String displayName; - - /// The list of archives available for this package. - final List archives; - - /// Whether this package is marked as obsolete. - final bool isObsolete; - - @override - String toString() => '$runtimeType{revision: $revision, displayName: $displayName, archives: $archives}'; -} - -/// Object class for instances of `` elements that are for the -/// platform package. -class AndroidRepositoryPlatform extends AndroidRepositoryRemotePackage { - const AndroidRepositoryPlatform( - AndroidRepositoryRevision revision, - String displayName, - List archives, - this.apiLevel, { - bool isObsolete = false, - }) : assert(apiLevel != null), - super(revision, displayName, archives, isObsolete: isObsolete); - - /// Parses an platform from a `` element. - factory AndroidRepositoryPlatform.fromXml( - XmlElement element, - XmlElement typeDetails, - ) { - _debugCheckElement(element, 'remotePackage'); - assert(typeDetails != null); - - return AndroidRepositoryPlatform( - AndroidRepositoryRevision.fromXml( - _firstOrDefault(element.findElements('revision'))), - _getChildText(element, 'display-name'), - _getArchives(element) - .map( - (XmlElement archive) => AndroidRepositoryArchive.fromXml(archive), - ) - .toList(), - int.parse(_getChildText(typeDetails, 'api-level', def: '0')), - isObsolete: element.getAttribute('obsolete') == 'true', - ); - } - - /// The API level for this Platform. - final int apiLevel; - - @override - String toString() => '$runtimeType{revision: $revision, displayName: $displayName, archives: $archives, apiLevel: $apiLevel}'; -} - -/// The OS types supported by Android. -enum OSType { - /// Any OS is supported. - any, - - /// Suppoorts Linux only. - linux, - - /// Supports macOS only. - mac, - - /// Supports windows only. - windows, -} - -/// Object class for the `` element in the Android repo XML. -/// -/// Contains information about the size, checksum, and location of a binary -/// zip archive. Optionally contains information about what host OS is -/// supported. -class AndroidRepositoryArchive { - /// Creates a new AndroidRepositoryArchive. - const AndroidRepositoryArchive( - this.size, - this.checksum, - this.url, { - this.hostOS = OSType.any, - }) : assert(size != null), - assert(checksum != null), - assert(url != null), - assert(hostOS != null); - - /// Parses an `` element. - factory AndroidRepositoryArchive.fromXml(XmlElement element) { - _debugCheckElement(element, 'archive'); - final XmlElement complete = - _firstOrDefault(element.findElements('complete')); - if (complete == null) { - throw StateError('Found element without a node!'); - } - - return AndroidRepositoryArchive( - int.parse(_getChildText(complete, 'size', def: '0')), - _getChildText(complete, 'checksum'), - _getChildText(complete, 'url'), - hostOS: _parseHostType(_getChildText(element, 'host-os')), - ); - } - - /// The download size in bytes of the archive. - final int size; - - /// The SHA-1 checksum of the archive. - final String checksum; - - /// The absolute or relative URL of the file. - final String url; - - /// The OS type, if applicable, for this archive. - final OSType hostOS; - - @override - String toString() => '$runtimeType{size: $size, checksum: $checksum, url: $url, hostOS: $hostOS}'; -} - -/// Object class for a `` node in the Android repo XML. -/// -/// Contains information about the revision of the archive. -/// -/// In the case of the platform package, this is the revision of the platform. -/// -/// In all other cases, this basically works like semver. -class AndroidRepositoryRevision { - /// Creates a new Android repository revision object. All values are required. - const AndroidRepositoryRevision( - this.major, [ - this.minor = 0, - this.micro = 0, - this.preview = 0, - ]) : assert(major != null), - assert(minor != null), - assert(micro != null), - assert(preview != null); - - /// Parses a `` element from the Android repository XML. - factory AndroidRepositoryRevision.fromXml(XmlElement element) { - if (element == null) { - return const AndroidRepositoryRevision(0); - } - _debugCheckElement(element, 'revision'); - return AndroidRepositoryRevision( - int.tryParse(_getChildText(element, 'major', def: '0')), - int.tryParse(_getChildText(element, 'minor', def: '0')), - int.tryParse(_getChildText(element, 'micro', def: '0')), - ); - } - - /// The major revision value. - final int major; - - /// The minor revision value. - final int minor; - - /// The micro revision. - final int micro; - - /// Preview/Release candidate version. A value of 0 indicates that - /// this is not a preview. - final int preview; - - /// Whether this revision represents a preview or release. - bool get isPreview => preview > 0; - - bool matches(int major, int minor, int micro, [int preview = 0]) { - return this.major == major && - this.minor == minor && - this.micro == micro && - this.preview == preview; - } - - @override - String toString() => '$runtimeType:{$major.$minor.$micro.$preview}'; -} diff --git a/engine/src/flutter/tools/android_sdk_downloader/lib/src/checksums.dart b/engine/src/flutter/tools/android_sdk_downloader/lib/src/checksums.dart deleted file mode 100644 index 37e376217e..0000000000 --- a/engine/src/flutter/tools/android_sdk_downloader/lib/src/checksums.dart +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2013 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 'dart:convert'; -import 'dart:io'; - -import 'package:path/path.dart' as path; - -Future> loadChecksums(Directory directory) async { - final File checksumFile = File(path.join(directory.path, 'checksums.json')); - if (!checksumFile.existsSync()) { - return {}; - } - final Map result = {}; - final Map jsonResult = - json.decode(await checksumFile.readAsString()); - for (final String key in jsonResult.keys) { - result[key] = jsonResult[key]; - } - return result; -} - -Future writeChecksums( - Map checksums, - Directory directory, -) async { - final File checksumFile = File(path.join(directory.path, 'checksums.json')); - const JsonEncoder encoder = JsonEncoder.withIndent(' '); - await checksumFile.writeAsString(encoder.convert(checksums)); -} diff --git a/engine/src/flutter/tools/android_sdk_downloader/lib/src/http.dart b/engine/src/flutter/tools/android_sdk_downloader/lib/src/http.dart deleted file mode 100644 index 4b2fa6cc29..0000000000 --- a/engine/src/flutter/tools/android_sdk_downloader/lib/src/http.dart +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2013 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 'dart:io'; - -import 'package:path/path.dart' as path; - -import 'android_repository.dart'; -import 'options.dart'; - -typedef HttpResponseHandler = Future Function(HttpClientResponse); - -Future httpGet( - Uri url, - HttpResponseHandler handler, -) async { - assert(url != null); - assert(handler != null); - - final HttpClient httpClient = HttpClient(); - - try { - final HttpClientRequest request = await httpClient.getUrl(url); - final HttpClientResponse response = await request.close(); - await handler(response); - } finally { - httpClient.close(); - } -} - -class DownloadTracker { - DownloadTracker(this.name, this.total) : received = 0; - - final String name; - final int total; - int received; - - String get percent => '${((received / total) * 100).round()}'.padLeft(3) + '%'; - - @override - String toString() => '$name: $received/$total ($percent).'; -} - -final Map _downloadTrackers = - {}; -void _printDownloadTrackers() { - for (final DownloadTracker tracker in _downloadTrackers.values) { - stdout.write( - '${tracker.name.replaceAll('Android ', '')}: ${tracker.percent} '); - } - - if (_downloadTrackers.values - .every((DownloadTracker tracker) => tracker.received == tracker.total)) { - stdout.writeln(); - print('Downloads complete.'); - } else { - stdout.write('\r'); - } -} - -class ArchiveDownloadResult { - const ArchiveDownloadResult(this.zipFileName, this.checksum); - - static const ArchiveDownloadResult empty = ArchiveDownloadResult(null, null); - - final String zipFileName; - final String checksum; -} - -Future downloadArchive( - List packages, - OptionsRevision revision, - String repositoryBase, - Directory outDirectory, { - OSType osType, - int apiLevel, - String checksumToSkip, -}) async { - AndroidRepositoryRemotePackage package; - for (final AndroidRepositoryRemotePackage p in packages) { - if (apiLevel != null && p is AndroidRepositoryPlatform) { - if (p.apiLevel != apiLevel) { - continue; - } - } - if (p.revision.matches( - revision.major, revision.minor, revision.micro, revision.preview)) { - package = p; - break; - } - } - if (package == null) { - throw StateError('Could not find package matching arguments: ' - '$revision, $osType, $apiLevel'); - } - - final String displayName = package.displayName; - final AndroidRepositoryArchive archive = osType == null - ? package.archives.first - : package.archives.firstWhere( - (AndroidRepositoryArchive archive) => archive.hostOS == osType, - ); - - if (archive.checksum == checksumToSkip) { - print('Skipping $displayName, checksum matches current asset.'); - return ArchiveDownloadResult.empty; - } - - Uri uri = Uri.parse(archive.url); - if (!uri.isAbsolute) { - uri = Uri.parse(repositoryBase + archive.url); - } - - _downloadTrackers[displayName] = DownloadTracker(displayName, archive.size); - final String outFileName = path.join(outDirectory.path, archive.url); - final IOSink tempFileSink = File(outFileName).openWrite(); - - Future _handlePlatformZip(HttpClientResponse response) async { - await for (List data in response) { - _downloadTrackers[displayName].received += data.length; - tempFileSink.add(data); - _printDownloadTrackers(); - } - await tempFileSink.close(); - } - - await httpGet(uri, _handlePlatformZip); - return ArchiveDownloadResult(outFileName, archive.checksum); -} diff --git a/engine/src/flutter/tools/android_sdk_downloader/lib/src/options.dart b/engine/src/flutter/tools/android_sdk_downloader/lib/src/options.dart deleted file mode 100644 index 73e3a2f210..0000000000 --- a/engine/src/flutter/tools/android_sdk_downloader/lib/src/options.dart +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2013 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 'dart:io'; - -import 'package:args/args.dart'; -import 'package:meta/meta.dart'; - -import 'android_repository.dart'; - -const Map osTypeMap = { - 'windows': OSType.windows, - 'macos': OSType.mac, - 'linux': OSType.linux, -}; - -class OptionsRevision { - const OptionsRevision( - this.raw, [ - this.major = 0, - this.minor = 0, - this.micro = 0, - this.preview = 0, - ]); - - /// Accepted formats: 1.2.3 or 1.2.3.4 - factory OptionsRevision.fromRaw(String raw) { - final List rawParts = raw.split('.'); - if (rawParts == null || (rawParts.length != 3 && rawParts.length != 4)) { - throw ArgumentError('Invalid revision string $raw.'); - } - return OptionsRevision( - raw, - int.parse(rawParts[0]), - int.parse(rawParts[1]), - int.parse(rawParts[2]), - rawParts.length == 4 ? int.parse(rawParts[3]) : 0, - ); - } - - final String raw; - final int major; - final int minor; - final int micro; - final int preview; -} - -class Options { - const Options({ - @required this.platformApiLevel, - @required this.platformRevision, - @required this.repositoryXml, - @required this.repositoryXmlUri, - @required this.buildToolsRevision, - @required this.platformToolsRevision, - @required this.toolsRevision, - @required this.ndkRevision, - @required this.outDirectory, - @required this.repositoryBase, - @required this.osType, - this.acceptLicenses = false, - this.overwrite = false, - }); - - static Options parseAndValidate(List args, ArgParser argParser) { - final ArgResults argResults = argParser.parse(args); - final int platformApiLevel = int.parse(argResults['platform']); - final int platformRevision = int.parse(argResults['platform-revision']); - final Directory outDirectory = Directory(argResults['out']); - - final String rawRepositoryXmlUri = argResults['repository-xml']; - final Uri repositoryXmlUri = Uri.tryParse(rawRepositoryXmlUri); - final int lastSlash = rawRepositoryXmlUri.lastIndexOf('/'); - final String repositoryBase = - rawRepositoryXmlUri.substring(0, lastSlash + 1); - - if (repositoryXmlUri == null) { - throw ArgumentError( - 'Error: could not parse $rawRepositoryXmlUri as a valid URL.'); - } - - String getRawVersion(String argName) { - final String raw = argResults[argName]; - if (raw?.isEmpty == true) { - print('Could not parse required argument $argName.'); - print(argParser.usage); - exit(-1); - } - return raw; - } - - final String rawBuildToolsVersion = getRawVersion('build-tools-version'); - final String rawPlatformToolsVersion = - getRawVersion('platform-tools-version'); - final String rawToolsVersion = getRawVersion('tools-version'); - final String rawNdkVersion = getRawVersion('ndk-version'); - - return Options( - platformApiLevel: platformApiLevel, - platformRevision: platformRevision, - outDirectory: outDirectory, - repositoryXml: rawRepositoryXmlUri, - repositoryXmlUri: repositoryXmlUri, - repositoryBase: repositoryBase, - buildToolsRevision: OptionsRevision.fromRaw(rawBuildToolsVersion), - platformToolsRevision: OptionsRevision.fromRaw(rawPlatformToolsVersion), - toolsRevision: OptionsRevision.fromRaw(rawToolsVersion), - ndkRevision: OptionsRevision.fromRaw(rawNdkVersion), - osType: osTypeMap[argResults['os']], - acceptLicenses: argResults['accept-licenses'], - overwrite: argResults['overwrite'], - ); - } - - final int platformApiLevel; - final String repositoryXml; - final Uri repositoryXmlUri; - final int platformRevision; - final OptionsRevision buildToolsRevision; - final OptionsRevision platformToolsRevision; - final OptionsRevision toolsRevision; - final OptionsRevision ndkRevision; - final String repositoryBase; - final Directory outDirectory; - final OSType osType; - final bool acceptLicenses; - final bool overwrite; -} diff --git a/engine/src/flutter/tools/android_sdk_downloader/lib/src/zip.dart b/engine/src/flutter/tools/android_sdk_downloader/lib/src/zip.dart deleted file mode 100644 index 488cb9b29b..0000000000 --- a/engine/src/flutter/tools/android_sdk_downloader/lib/src/zip.dart +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2013 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 'dart:async'; -import 'dart:io'; - -// TODO(dnfield): if/when a streaming unzip routine is available for Dart, use that instead. - -Future unzipFile(String file, Directory outDir) async { - await outDir.parent.create(recursive: true); - final Directory tempDir = await outDir.parent.createTemp(); - - String command; - List args; - if (Platform.isWindows) { - command = 'powershell.exe -nologo -noprofile -command ' - '"& { ' - 'Add-Type -A \'System.IO.Compression.FileSystem\'; ' - '[IO.Compression.ZipFile]::ExtractToDirectory(\'$file\', \'${tempDir.path}\'); ' - '}"'; - args = []; - } else { - command = 'unzip'; - args = [ - file, - '-d', - tempDir.path, - ]; - } - final ProcessResult result = await Process.run(command, args); - if (result.exitCode != 0) { - throw Exception('Failed to unzip archive!'); - } - final Directory dir = await tempDir.list().first; - if (await outDir.exists()) { - await outDir.delete(recursive: true); - } - await dir.rename(outDir.path); - await tempDir.delete(); -} diff --git a/engine/src/flutter/tools/android_sdk_downloader/pubspec.yaml b/engine/src/flutter/tools/android_sdk_downloader/pubspec.yaml deleted file mode 100644 index ef1b304818..0000000000 --- a/engine/src/flutter/tools/android_sdk_downloader/pubspec.yaml +++ /dev/null @@ -1,12 +0,0 @@ -name: 'android_sdk_downloader' -publish_to: none - -environment: - # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite. - sdk: ">=2.0.0-dev.68.0 <3.0.0" - -dependencies: - args: ^1.5.1 - meta: ^1.1.6 - path: ^1.6.2 - xml: ^3.2.3 diff --git a/engine/src/flutter/tools/create_ndk_cipd_package.sh b/engine/src/flutter/tools/create_ndk_cipd_package.sh new file mode 100755 index 0000000000..7651b19573 --- /dev/null +++ b/engine/src/flutter/tools/create_ndk_cipd_package.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# This script requires depot_tools to be on path. + +print_usage () { + echo "Usage: create_ndk_cipd_package.sh " + echo " where:" + echo " - PATH_TO_NDK_ASSETS is the path to the unzipped NDK folder" + echo " - PLATFORM_NAME is one of linux-amd64, mac-amd64, or windows-amd64" + echo " - VERSION_TAG is the version of the NDK, e.g. r19b" +} + +if [[ $3 == "" ]]; then + print_usage + exit 1 +fi + +if [[ ! -d "$1" ]]; then + echo "Directory $1 not found." + print_usage + exit 1 +fi + +if [[ $2 != "linux-amd64" && $2 != "mac-amd64" && $2 != "windows-amd64" ]]; then + echo "Unsupported platform $2." + echo "Valid options are linux-amd64, mac-amd64, windows-amd64." + print_usage + exit 1 +fi + +cipd create -in $1 -name flutter/android/ndk/$2 -install-mode copy -tag version:$3 diff --git a/engine/src/flutter/tools/create_sdk_cipd_package.sh b/engine/src/flutter/tools/create_sdk_cipd_package.sh new file mode 100755 index 0000000000..7f1ed33ad9 --- /dev/null +++ b/engine/src/flutter/tools/create_sdk_cipd_package.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# This script requires depot_tools to be on path. + +print_usage () { + echo "Usage: create_ndk_cipd_package.sh " + echo " where:" + echo " - PACKAGE_TYPE is one of build-tools, platform-tools, platforms, or tools" + echo " - PATH_TO_ASSETS is the path to the unzipped asset folder" + echo " - PLATFORM_NAME is one of linux-amd64, mac-amd64, or windows-amd64" + echo " - VERSION_TAG is the version of the package, e.g. 28r6 or 28.0.3" +} + +if [[ $4 == "" ]]; then + print_usage + exit 1 +fi + +if [[ $1 != "build-tools" && $1 != "platform-tools" && $1 != "platforms" && $1 != "tools" ]]; then + echo "Unrecognized paackage type $1." + print_usage + exit 1 +fi + +if [[ ! -d "$2" ]]; then + echo "Directory $1 not found." + print_usage + exit 1 +fi + +if [[ $1 != "platforms" && $3 != "linux-amd64" && $3 != "mac-amd64" && $3 != "windows-amd64" ]]; then + echo "Unsupported platform $3." + echo "Valid options are linux-amd64, mac-amd64, windows-amd64." + print_usage + exit 1 +fi + +if [[ $1 == "platforms" ]]; then + echo "Ignoring PLATFORM_NAME - this package is cross-platform." + cipd create -in $2 -name flutter/android/sdk/$1 -install-mode copy -tag version:$4 +else + cipd create -in $2 -name flutter/android/sdk/$1/$3 -install-mode copy -tag version:$4 +fi