-
Notifications
You must be signed in to change notification settings - Fork 6k
Add an option to use a prebuilt Dart SDK #26931
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
Signature: b9105737a36020f19487fe9854f4be92 | ||
Signature: 6f6132591430df787d08771477f3374d | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
#!/usr/bin/env python3 | ||
# 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. | ||
|
||
# When the environment variable in FLUTTER_PREBUILTS_ENV_VAR below is defined | ||
# and is not '0' or 'false', this script downloads the Dart SDK that matches the | ||
# version in the source tree and puts it in prebuilts/. | ||
# | ||
# The return code of this script will always be 0, even if there is an error, | ||
# unless the --fail-loudly flag is passed. | ||
|
||
# TODO(zra): Eliminate this script and download through the DEPS file if/when | ||
# the Dart SDKs pulled by this script are uploaded to cipd. | ||
|
||
import argparse | ||
import os | ||
import multiprocessing | ||
import platform | ||
import re | ||
import shutil | ||
import subprocess | ||
import sys | ||
import zipfile | ||
|
||
|
||
SRC_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||
FLUTTER_DIR = os.path.join(SRC_ROOT, 'flutter') | ||
FLUTTER_PREBUILTS_DIR = os.path.join(FLUTTER_DIR, 'prebuilts') | ||
DART_DIR = os.path.join(SRC_ROOT, 'third_party', 'dart') | ||
DART_VERSION = os.path.join(DART_DIR, 'tools', 'VERSION') | ||
FLUTTER_PREBUILTS_ENV_VAR = 'FLUTTER_PREBUILT_DART_SDK' | ||
|
||
|
||
# The Dart SDK script is the source of truth about the sematic version. | ||
sys.path.append(os.path.join(DART_DIR, 'tools')) | ||
import utils | ||
|
||
|
||
# Prints to stderr. | ||
def eprint(*args, **kwargs): | ||
print(*args, file=sys.stderr, **kwargs) | ||
|
||
|
||
# Try to guess the host operating system. | ||
def GuessOS(): | ||
os_name = utils.GuessOS() | ||
if os_name == 'win32': | ||
os_name = 'windows' | ||
if os_name not in ['linux', 'macos', 'windows']: | ||
eprint('Could not determine the OS: "%s"' % os_name) | ||
return None | ||
return os_name | ||
|
||
|
||
# For os `os_name` return a list of architectures for which prebuilts are | ||
# supported. Kepp in sync with `can_use_prebuilt_dart` in //flutter/tools/gn. | ||
def ArchitecturesForOS(os_name): | ||
if os_name == 'linux': | ||
return ['x64', 'arm64'] | ||
elif os_name == 'macos': | ||
return ['x64', 'arm64'] | ||
elif os_name =='windows': | ||
return ['x64'] | ||
|
||
eprint('Could not determine architectures for os "%s"' % os_name) | ||
return None | ||
|
||
|
||
# Downloads a Dart SDK to //flutter/prebuilts. | ||
def DownloadDartSDK(channel, version, os_name, arch): | ||
file = 'dartsdk-{}-{}-release.zip'.format(os_name, arch) | ||
url = 'https://storage.googleapis.com/dart-archive/channels/{}/raw/{}/sdk/{}'.format( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be recreating the functionality of CIPD hooks in the DEPS file. Can we move these artifacts to CIPD instead? Stuff like needing curl on the host and not performing hash consistency checks may lead to unnecessary hassles on CI. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. Sorry for not being more explicit about the plan. I want to see how much this can improve build times on CI. If there's a reasonably good improvement, then I'll open a thread with Dart EngProd to request that these artifacts are uploaded to cipd, and this script will go away. If the improvement on CI is not significant, then I will back all this out. |
||
channel, version, file, | ||
) | ||
dest = os.path.join(FLUTTER_PREBUILTS_DIR, file) | ||
|
||
stamp_file = '{}.stamp'.format(dest) | ||
version_stamp = None | ||
try: | ||
with open(stamp_file) as fd: | ||
version_stamp = fd.read() | ||
except: | ||
version_stamp = 'none' | ||
|
||
if version == version_stamp: | ||
# The prebuilt Dart SDK is already up-to-date. Indicate that the download | ||
# should be skipped by returning the empty string. | ||
return '' | ||
|
||
if os.path.isfile(dest): | ||
os.unlink(dest) | ||
|
||
curl_command = ['curl', url, '-o', dest] | ||
curl_result = subprocess.run( | ||
curl_command, | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
universal_newlines=True, | ||
) | ||
if curl_result.returncode != 0: | ||
eprint('Failed to download: stdout:\n{}\nstderr:\n{}'.format( | ||
curl_result.stdout, curl_result.stderr, | ||
)) | ||
return None | ||
|
||
return dest | ||
|
||
|
||
# A custom ZipFile class that preserves file permissions. | ||
class ZipFileWithPermissions(zipfile.ZipFile): | ||
def _extract_member(self, member, targetpath, pwd): | ||
if not isinstance(member, zipfile.ZipInfo): | ||
member = self.getinfo(member) | ||
|
||
targetpath = super()._extract_member(member, targetpath, pwd) | ||
|
||
attr = member.external_attr >> 16 | ||
if attr != 0: | ||
os.chmod(targetpath, attr) | ||
return targetpath | ||
|
||
|
||
# Extracts a Dart SDK in //fluter/prebuilts | ||
def ExtractDartSDK(archive, os_name, arch): | ||
os_arch = '{}-{}'.format(os_name, arch) | ||
dart_sdk = os.path.join(FLUTTER_PREBUILTS_DIR, os_arch, 'dart-sdk') | ||
if os.path.isdir(dart_sdk): | ||
shutil.rmtree(dart_sdk) | ||
|
||
extract_dest = os.path.join(FLUTTER_PREBUILTS_DIR, os_arch) | ||
os.makedirs(extract_dest, exist_ok=True) | ||
|
||
with ZipFileWithPermissions(archive, "r") as z: | ||
z.extractall(extract_dest) | ||
|
||
|
||
def DownloadAndExtract(channel, version, os_name, arch): | ||
archive = DownloadDartSDK(channel, version, os_name, arch) | ||
if archive == None: | ||
return 1 | ||
if archive == '': | ||
return 0 | ||
ExtractDartSDK(archive, os_name, arch) | ||
try: | ||
stamp_file = '{}.stamp'.format(archive) | ||
with open(stamp_file, "w") as fd: | ||
fd.write(version) | ||
except Exception as e: | ||
eprint('Failed to write Dart SDK version stamp file:\n{}'.format(e)) | ||
return 1 | ||
return 0 | ||
|
||
|
||
def Main(): | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument( | ||
'--fail-loudly', | ||
action='store_true', | ||
default=False, | ||
help="Return an error code if a prebuilt couldn't be fetched and extracted") | ||
args = parser.parse_args() | ||
fail_loudly = 1 if args.fail_loudly else 0 | ||
|
||
prebuilt_enabled = os.environ.get(FLUTTER_PREBUILTS_ENV_VAR, 'false') | ||
if prebuilt_enabled == '0' or prebuilt_enabled.lower() == 'false': | ||
return 0 | ||
|
||
os.makedirs(FLUTTER_PREBUILTS_DIR, exist_ok=True) | ||
|
||
# Read //third_party/dart/tools/VERSION to extract information about the | ||
# Dart SDK version. | ||
version = utils.ReadVersionFile() | ||
if version == None: | ||
return fail_loudly | ||
channel = version.channel | ||
|
||
# A short Dart SDK version string used in the download url. | ||
if channel == 'be': | ||
dart_git_rev = utils.GetGitRevision() | ||
semantic_version = 'hash/{}'.format(dart_git_rev) | ||
semantic_version = utils.GetSemanticSDKVersion() | ||
|
||
os_name = GuessOS() | ||
if os_name == None: | ||
return fail_loudly | ||
|
||
architectures = ArchitecturesForOS(os_name) | ||
if architectures == None: | ||
return fail_loudly | ||
|
||
# Download and extract variants in parallel | ||
pool = multiprocessing.Pool() | ||
tasks = [(channel, semantic_version, os_name, arch) for arch in architectures] | ||
async_results = [pool.apply_async(DownloadAndExtract, t) for t in tasks] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fancy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't thank me, thank stackoverflow! |
||
success = True | ||
for async_result in async_results: | ||
result = async_result.get() | ||
success = success and (result == 0) | ||
|
||
return 0 if success else fail_loudly | ||
|
||
|
||
if __name__ == '__main__': | ||
sys.exit(Main()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we guard this with some kind of variable? Will downloading this always be necessary - in particular, will there be any builders that still use --full-dart-sdk and so should skip this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. The API in the recipes appears to use env vars, so I added a guard on one in the python script. Next step is to enable in recipes and see how much things get better there.