| # Copyright 2016 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. |
| |
| from contextlib import contextmanager |
| import re |
| |
| DEPS = [ |
| 'build', |
| 'depot_tools/git', |
| 'depot_tools/gsutil', |
| 'depot_tools/depot_tools', |
| 'depot_tools/osx_sdk', |
| 'depot_tools/windows_sdk', |
| 'recipe_engine/buildbucket', |
| 'recipe_engine/cipd', |
| 'recipe_engine/context', |
| 'recipe_engine/file', |
| 'recipe_engine/isolated', |
| 'recipe_engine/json', |
| 'recipe_engine/path', |
| 'recipe_engine/platform', |
| 'recipe_engine/properties', |
| 'recipe_engine/python', |
| 'recipe_engine/raw_io', |
| 'recipe_engine/runtime', |
| 'recipe_engine/step', |
| 'recipe_engine/swarming', |
| 'recipe_engine/url', |
| 'zip', |
| ] |
| |
| BUCKET_NAME = 'flutter_infra' |
| PACKAGED_REF_RE = re.compile(r'^refs/heads/(dev|beta|stable)$') |
| |
| # Fuchsia globals. |
| FUCHSIA_IMAGE_NAME = 'generic-x64.tgz' |
| FUCHSIA_PACKAGES_ARCHIVE_NAME = 'generic-x64.tar.gz' |
| FUCHSIA_TEST_SCRIPT_NAME = 'run_fuchsia_tests.sh' |
| |
| |
| @contextmanager |
| def _PlatformSDK(api): |
| if api.platform.is_win: |
| with api.windows_sdk(): |
| with InstallOpenJDK(api): |
| yield |
| elif api.platform.is_mac: |
| yield |
| elif api.platform.is_linux: |
| with InstallOpenJDK(api): |
| yield |
| |
| |
| @contextmanager |
| def Install7za(api): |
| if api.platform.is_win: |
| sevenzip_cache_dir = api.path['cache'].join('builder', '7za') |
| api.cipd.ensure( |
| sevenzip_cache_dir, |
| api.cipd.EnsureFile().add_package( |
| 'flutter_internal/tools/7za/${platform}', 'version:19.00')) |
| with api.context(env_prefixes={'PATH': [sevenzip_cache_dir]}): |
| yield |
| else: |
| yield |
| |
| |
| def EnsureGoldctl(api): |
| with api.step.nest('Download goldctl'): |
| goldctl_cache_dir = api.path['cache'].join('gold') |
| api.cipd.ensure( |
| goldctl_cache_dir, |
| api.cipd.EnsureFile().add_package('skia/tools/goldctl/${platform}', |
| 'latest')) |
| return goldctl_cache_dir.join('goldctl') |
| |
| |
| def ShouldRunGoldTryjob(api): |
| """Specifies pre-submit conditions for executing gold tryjobs.""" |
| return api.properties.get('gold_tryjob', True) |
| |
| |
| @contextmanager |
| def MakeTempDir(api, label): |
| temp_dir = api.path.mkdtemp('tmp') |
| try: |
| yield temp_dir |
| finally: |
| api.file.rmtree('temp dir for %s' % label, temp_dir) |
| |
| |
| def DownloadFuchsiaSystemImageAndPackages(api, fuchsia_dir, target_dir): |
| with api.step.nest('Download Fuchsia Archives'): |
| manifest_path = fuchsia_dir.join('meta', 'manifest.json') |
| manifest_data = api.file.read_json( |
| 'Read fuchsia build manifest', manifest_path, test_data={'id': 123}) |
| build_id = manifest_data['id'] |
| bucket_name = 'fuchsia' |
| api.gsutil.download( |
| bucket_name, |
| 'development/%s/images/%s' % (build_id, FUCHSIA_IMAGE_NAME), |
| target_dir, |
| name="download fuchsia system image") |
| api.gsutil.download( |
| bucket_name, |
| 'development/%s/packages/%s' % (build_id, |
| FUCHSIA_PACKAGES_ARCHIVE_NAME), |
| target_dir, |
| name="download fuchsia companion packages") |
| |
| |
| def IsolateFuchsiaCtlDeps(api, fuchsia_ctl_wd): |
| checkout = api.path['checkout'] |
| flutter_bin = checkout.join('bin') |
| fuchsia_dir = flutter_bin.join('cache', 'artifacts', 'fuchsia') |
| fuchsia_tools = fuchsia_dir.join('tools') |
| DownloadFuchsiaSystemImageAndPackages(api, fuchsia_dir, fuchsia_ctl_wd) |
| with api.step.nest('Copy Fuchsia CTL Deps'): |
| api.file.copy('Copy test script', |
| checkout.join('dev', 'bots', FUCHSIA_TEST_SCRIPT_NAME), |
| fuchsia_ctl_wd) |
| api.file.copy('Copy dev_finder', fuchsia_tools.join('dev_finder'), |
| fuchsia_ctl_wd) |
| api.file.copy('Copy pm', fuchsia_tools.join('pm'), fuchsia_ctl_wd) |
| |
| |
| def IsolateDriverDeps(api): |
| checkout = api.path['checkout'] |
| with api.step.nest('Create Isolate Archive'): |
| with MakeTempDir(api, 'isolate_dir') as isolate_dir: |
| IsolateFuchsiaCtlDeps(api, isolate_dir) |
| isolated_flutter = isolate_dir.join('flutter') |
| api.file.copytree('Copy flutter framework', checkout, isolated_flutter) |
| isolated = api.isolated.isolated(isolate_dir) |
| isolated.add_dir(isolate_dir) |
| return isolated.archive('Archive Fuchsia Test Isolate') |
| |
| |
| def SwarmFuchsiaTests(api): |
| isolated_hash = IsolateDriverDeps(api) |
| fuchsia_ctl_package = api.cipd.EnsureFile() |
| fuchsia_ctl_package.add_package('flutter/fuchsia_ctl/${platform}', |
| api.properties.get('fuchsia_ctl_version')) |
| request = ( |
| api.swarming.task_request().with_name('flutter_fuchsia_driver_tests') |
| .with_priority(100)) |
| request = ( |
| request.with_slice( |
| 0, request[0].with_cipd_ensure_file(fuchsia_ctl_package).with_command( |
| ['./%s' % FUCHSIA_TEST_SCRIPT_NAME, |
| FUCHSIA_IMAGE_NAME]).with_dimensions(pool='luci.flutter.tests') |
| .with_isolated(isolated_hash).with_expiration_secs( |
| 3600).with_io_timeout_secs(3600).with_execution_timeout_secs( |
| 3600).with_idempotent(True).with_containment_type('AUTO'))) |
| |
| # Trigger the task request. |
| metadata = api.swarming.trigger( |
| 'Trigger Fuchsia Driver Tests', requests=[request]) |
| # Collect the result of the task by metadata. |
| fuchsia_output = api.path['cleanup'].join('fuchsia_test_output') |
| api.file.ensure_directory('swarming output', fuchsia_output) |
| results = api.swarming.collect( |
| 'collect', metadata, output_dir=fuchsia_output, timeout='30m') |
| for result in results: |
| result.analyze() |
| |
| |
| def RunFuchsiaDriverTests(api): |
| # Fuchsia driver tests are currently only run from linux hosts. |
| if not api.platform.is_linux: |
| return |
| # Note: https://github.com/flutter/flutter/issues/50500 |
| # We will only be running Fuchsia tests in experimental runs until the bots |
| # become more stable. |
| if not api.runtime.is_experimental: |
| return |
| with api.step.nest('Run Fuchsia Driver Tests'): |
| flutter_executable = 'flutter' if not api.platform.is_win else 'flutter.bat' |
| api.step('precache fuchsia artifacts', [ |
| flutter_executable, 'precache', '--fuchsia', '--no-android', '--no-ios' |
| ]) |
| api.step('precache flutter runners', [ |
| flutter_executable, 'precache', '--flutter_runner', '--no-android', |
| '--no-ios' |
| ]) |
| SwarmFuchsiaTests(api) |
| return |
| |
| |
| def InstallOpenJDK(api): |
| java_cache_dir = api.path['cache'].join('java') |
| api.cipd.ensure( |
| java_cache_dir, |
| api.cipd.EnsureFile().add_package( |
| 'flutter_internal/java/openjdk/${platform}', 'version:1.8.0u202-b08')) |
| return api.context( |
| env={'JAVA_HOME': java_cache_dir}, |
| env_prefixes={'PATH': [java_cache_dir.join('bin')]}) |
| |
| |
| def EnsureCloudKMS(api, version=None): |
| with api.step.nest('ensure_cloudkms'): |
| with api.context(infra_steps=True): |
| pkgs = api.cipd.EnsureFile() |
| pkgs.add_package('infra/tools/luci/cloudkms/${platform}', version or |
| 'latest') |
| cipd_dir = api.path['start_dir'].join('cipd', 'cloudkms') |
| api.cipd.ensure(cipd_dir, pkgs) |
| return cipd_dir.join('cloudkms') |
| |
| |
| def DecryptKMS(api, step_name, crypto_key_path, ciphertext_file, |
| plaintext_file): |
| kms_path = EnsureCloudKMS(api) |
| return api.step(step_name, [ |
| kms_path, |
| 'decrypt', |
| '-input', |
| ciphertext_file, |
| '-output', |
| plaintext_file, |
| crypto_key_path, |
| ]) |
| |
| |
| def GetCloudPath(api, git_hash, path): |
| if api.runtime.is_experimental: |
| return 'flutter/experimental/%s/%s' % (git_hash, path) |
| return 'flutter/%s/%s' % (git_hash, path) |
| |
| |
| def UploadFlutterCoverage(api): |
| """Uploads the Flutter coverage output to cloud storage and Coveralls. |
| """ |
| if not api.properties.get('upload_packages', False): |
| return |
| |
| # Upload latest coverage to cloud storage. |
| checkout = api.path['checkout'] |
| flutter_package_dir = checkout.join('packages', 'flutter') |
| coverage_path = flutter_package_dir.join('coverage', 'lcov.info') |
| api.gsutil.upload( |
| coverage_path, |
| BUCKET_NAME, |
| GetCloudPath(api, 'coverage', 'lcov.info'), |
| link_name='lcov.info', |
| name='upload coverage data') |
| |
| token_path = flutter_package_dir.join('.coveralls.yml') |
| DecryptKMS(api, 'decrypt coveralls token', |
| 'projects/flutter-infra/locations/global' \ |
| '/keyRings/luci/cryptoKeys/coveralls', |
| api.resource('coveralls-token.enc'), |
| token_path) |
| pub_executable = 'pub' if not api.platform.is_win else 'pub.exe' |
| api.step('pub global activate coveralls', [ |
| pub_executable, 'global', 'activate', 'coveralls', '5.1.0', |
| '--no-executables' |
| ]) |
| with api.context(cwd=flutter_package_dir): |
| api.step('upload to coveralls', |
| [pub_executable, 'global', 'run', 'coveralls:main', coverage_path]) |
| |
| |
| def CreateAndUploadFlutterPackage(api, git_hash, branch): |
| """Prepares, builds, and uploads an all-inclusive archive package.""" |
| # For creating the packages, we need to have the master branch version of the |
| # script, but we need to know what the revision in git_hash is first. So, we |
| # end up checking out the flutter repo twice: once on the branch we're going |
| # to package, to find out the hash to use, and again here so that we have the |
| # current version of the packaging script. |
| api.git.checkout( |
| 'https://chromium.googlesource.com/external/github.com/flutter/flutter', |
| ref='master', |
| recursive=True, |
| set_got_revision=True) |
| |
| flutter_executable = 'flutter' if not api.platform.is_win else 'flutter.bat' |
| dart_executable = 'dart' if not api.platform.is_win else 'dart.exe' |
| work_dir = api.path['start_dir'].join('archive') |
| prepare_script = api.path['checkout'].join('dev', 'bots', |
| 'prepare_package.dart') |
| api.step('flutter doctor', [flutter_executable, 'doctor']) |
| api.step('download dependencies', [flutter_executable, 'update-packages']) |
| api.file.rmtree('clean archive work directory', work_dir) |
| api.file.ensure_directory('(re)create archive work directory', work_dir) |
| with Install7za(api): |
| with api.context(cwd=api.path['start_dir']): |
| step_args = [ |
| dart_executable, prepare_script, |
| '--temp_dir=%s' % work_dir, |
| '--revision=%s' % git_hash, |
| '--branch=%s' % branch |
| ] |
| if not api.runtime.is_experimental: |
| step_args.append('--publish') |
| api.step('prepare, create and publish a flutter archive', step_args) |
| |
| |
| def RunSteps(api): |
| git_url = \ |
| 'https://chromium.googlesource.com/external/github.com/flutter/flutter' |
| git_ref = api.buildbucket.gitiles_commit.ref |
| if ('git_url' in api.properties and 'git_ref' in api.properties): |
| git_url = api.properties['git_url'] |
| git_ref = api.properties['git_ref'] |
| |
| git_hash = api.git.checkout( |
| git_url, ref=git_ref, recursive=True, set_got_revision=True, tags=True) |
| checkout = api.path['checkout'] |
| |
| dart_bin = checkout.join('bin', 'cache', 'dart-sdk', 'bin') |
| flutter_bin = checkout.join('bin') |
| |
| path_prefixes = [ |
| flutter_bin, |
| dart_bin, |
| ] |
| |
| env_prefixes = {'PATH': path_prefixes} |
| |
| # TODO(eseidel): This is named exactly '.pub-cache' as a hack around |
| # a regexp in flutter_tools analyze.dart which is in turn a hack around: |
| # https://github.com/dart-lang/sdk/issues/25722 |
| pub_cache = checkout.join('.pub-cache') |
| env = { |
| # Setup our own pub_cache to not affect other slaves on this machine, |
| # and so that the pre-populated pub cache is contained in the package. |
| 'PUB_CACHE': pub_cache, |
| # Windows Packaging script assumes this is set. |
| 'DEPOT_TOOLS': str(api.depot_tools.root), |
| # Goldctl binary for Flutter Gold, used by framework and driver tests. |
| 'GOLDCTL': EnsureGoldctl(api), |
| } |
| |
| if ShouldRunGoldTryjob(api) and git_ref: |
| # Tryjob should be run for given git_ref |
| env.update({ |
| 'GOLD_TRYJOB': git_ref, |
| }) |
| |
| |
| |
| |
| flutter_executable = 'flutter' if not api.platform.is_win else 'flutter.bat' |
| dart_executable = 'dart' if not api.platform.is_win else 'dart.exe' |
| |
| with api.context(env=env, env_prefixes=env_prefixes): |
| with api.depot_tools.on_path(): |
| if git_ref: |
| match = PACKAGED_REF_RE.match(git_ref) |
| if match: |
| branch = match.group(1) |
| CreateAndUploadFlutterPackage(api, git_hash, branch) |
| # Nothing left to do on a packaging branch. |
| return |
| |
| # The context adds dart-sdk tools to PATH and sets PUB_CACHE. |
| with api.context(env=env, env_prefixes=env_prefixes, cwd=checkout): |
| api.step('flutter doctor', [flutter_executable, 'doctor']) |
| api.step('download dependencies', [flutter_executable, 'update-packages']) |
| |
| # TODO (kaushikiska): Should we only run the tests on specific shard types? |
| with api.context(env=env, env_prefixes=env_prefixes, cwd=checkout): |
| RunFuchsiaDriverTests(api) |
| |
| with _PlatformSDK(api): |
| with api.context(env=env, env_prefixes=env_prefixes, cwd=checkout): |
| shard = api.properties['shard'] |
| shard_env = env |
| shard_env['SHARD'] = shard |
| with api.context(env=shard_env): |
| api.step('run test.dart for %s shard' % shard, |
| [dart_executable, |
| checkout.join('dev', 'bots', 'test.dart')]) |
| if shard == 'coverage': |
| UploadFlutterCoverage(api) |
| # Windows uses exclusive file locking. On LUCI, if these processes remain |
| # they will cause the build to fail because the builder won't be able to |
| # clean up. |
| # This might fail if there's not actually a process running, which is |
| # fine. |
| # If it actually fails to kill the task, the job will just fail anyway. |
| if api.platform.is_win: |
| |
| def KillAll(name, exe_name): |
| api.step( |
| name, ['taskkill', '/f', '/im', exe_name, '/t'], ok_ret='any') |
| |
| KillAll('stop gradle daemon', 'java.exe') |
| KillAll('stop dart', 'dart.exe') |
| KillAll('stop adb', 'adb.exe') |
| |
| |
| def GenTests(api): |
| for experimental in (True, False): |
| for should_upload in (True, False): |
| yield (api.test( |
| 'linux_master_coverage_%s%s' % |
| ('_experimental' if experimental else '', |
| '_upload' if should_upload else ''), |
| api.runtime(is_luci=True, is_experimental=experimental), |
| api.properties( |
| shard='coverage', |
| coveralls_lcov_version='5.1.0', |
| upload_packages=should_upload, |
| gold_tryjob=not should_upload), |
| ) + api.post_check( |
| lambda check, steps: check('Download goldctl' in steps))) |
| for platform in ('mac', 'linux', 'win'): |
| for branch in ('master', 'dev', 'beta', 'stable'): |
| git_ref = 'refs/heads/' + branch |
| test = api.test( |
| '%s_%s%s%s' % (platform, branch, '_experimental' if experimental |
| else '', '_upload' if should_upload else ''), |
| api.platform(platform, 64), |
| api.buildbucket.ci_build(git_ref=git_ref, revision=None), |
| api.properties( |
| shard='tests', |
| fuchsia_ctl_version='version:0.0.2', |
| upload_packages=should_upload, |
| gold_tryjob=not should_upload), |
| api.runtime(is_luci=True, is_experimental=experimental), |
| ) |
| yield test + api.post_check( |
| lambda check, steps: check('Download goldctl' in steps)) |
| |
| yield (api.test( |
| 'pull_request', |
| api.runtime(is_luci=True, is_experimental=True), |
| api.properties( |
| git_url='https://github.com/flutter/flutter', |
| git_ref='refs/pull/1/head', |
| shard='tests', |
| fuchsia_ctl_version='version:0.0.2', |
| should_upload=False), |
| ) + api.post_check(lambda check, steps: check('Download goldctl' in steps))) |