summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/build/fuchsia/target.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/build/fuchsia/target.py')
-rw-r--r--third_party/libwebrtc/build/fuchsia/target.py336
1 files changed, 336 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/fuchsia/target.py b/third_party/libwebrtc/build/fuchsia/target.py
new file mode 100644
index 0000000000..7a8628266a
--- /dev/null
+++ b/third_party/libwebrtc/build/fuchsia/target.py
@@ -0,0 +1,336 @@
+# Copyright 2018 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.
+
+import json
+import logging
+import os
+import subprocess
+import time
+
+import common
+import remote_cmd
+import runner_logs
+
+
+_SHUTDOWN_CMD = ['dm', 'poweroff']
+_ATTACH_RETRY_INTERVAL = 1
+_ATTACH_RETRY_SECONDS = 120
+
+# Amount of time to wait for a complete package installation, as a
+# mitigation against hangs due to pkg/network-related failures.
+_INSTALL_TIMEOUT_SECS = 10 * 60
+
+
+def _GetPackageUri(package_name):
+ """Returns the URI for the specified package name."""
+ return 'fuchsia-pkg://fuchsia.com/%s' % (package_name)
+
+
+def _GetPackageInfo(package_path):
+ """Returns a tuple with the name and version of a package."""
+
+ # Query the metadata file which resides next to the package file.
+ package_info = json.load(
+ open(os.path.join(os.path.dirname(package_path), 'package')))
+ return package_info['name'], package_info['version'],
+
+
+class _MapIsolatedPathsForPackage:
+ """Callable object which remaps /data and /tmp paths to their component-
+ specific locations, based on the package name and test realm path."""
+
+ def __init__(self, package_name, package_version, realms):
+ realms_path_fragment = '/r/'.join(['r/sys'] + realms)
+ package_sub_path = '{2}/fuchsia.com:{0}:{1}#meta:{0}.cmx/'.format(
+ package_name, package_version, realms_path_fragment)
+ self.isolated_format = '{0}' + package_sub_path + '{1}'
+
+ def __call__(self, path):
+ for isolated_directory in ['/data/' , '/tmp/']:
+ if (path+'/').startswith(isolated_directory):
+ return self.isolated_format.format(isolated_directory,
+ path[len(isolated_directory):])
+ return path
+
+
+class FuchsiaTargetException(Exception):
+ def __init__(self, message):
+ super(FuchsiaTargetException, self).__init__(message)
+
+
+class Target(object):
+ """Base class representing a Fuchsia deployment target."""
+
+ def __init__(self, out_dir, target_cpu):
+ self._out_dir = out_dir
+ self._started = False
+ self._dry_run = False
+ self._target_cpu = target_cpu
+ self._command_runner = None
+ self._ffx_path = os.path.join(common.SDK_ROOT, 'tools',
+ common.GetHostArchFromPlatform(), 'ffx')
+
+ @staticmethod
+ def CreateFromArgs(args):
+ raise NotImplementedError()
+
+ @staticmethod
+ def RegisterArgs(arg_parser):
+ pass
+
+ # Functions used by the Python context manager for teardown.
+ def __enter__(self):
+ return self
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ return
+
+ def Start(self):
+ """Handles the instantiation and connection process for the Fuchsia
+ target instance."""
+
+ def IsStarted(self):
+ """Returns True if the Fuchsia target instance is ready to accept
+ commands."""
+ return self._started
+
+ def IsNewInstance(self):
+ """Returns True if the connected target instance is newly provisioned."""
+ return True
+
+ def GetCommandRunner(self):
+ """Returns CommandRunner that can be used to execute commands on the
+ target. Most clients should prefer RunCommandPiped() and RunCommand()."""
+ self._AssertIsStarted()
+
+ if self._command_runner is None:
+ host, port = self._GetEndpoint()
+ self._command_runner = \
+ remote_cmd.CommandRunner(self._GetSshConfigPath(), host, port)
+
+ return self._command_runner
+
+ def RunCommandPiped(self, command, **kwargs):
+ """Starts a remote command and immediately returns a Popen object for the
+ command. The caller may interact with the streams, inspect the status code,
+ wait on command termination, etc.
+
+ command: A list of strings representing the command and arguments.
+ kwargs: A dictionary of parameters to be passed to subprocess.Popen().
+ The parameters can be used to override stdin and stdout, for
+ example.
+
+ Returns: a Popen object.
+
+ Note: method does not block.
+ """
+ logging.debug('running (non-blocking) \'%s\'.', ' '.join(command))
+ return self.GetCommandRunner().RunCommandPiped(command, **kwargs)
+
+ def RunCommand(self, command, silent=False, timeout_secs=None):
+ """Executes a remote command and waits for it to finish executing.
+
+ Returns the exit code of the command.
+ """
+ logging.debug('running \'%s\'.', ' '.join(command))
+ return self.GetCommandRunner().RunCommand(command, silent,
+ timeout_secs=timeout_secs)
+
+ def EnsureIsolatedPathsExist(self, for_package, for_realms):
+ """Ensures that the package's isolated /data and /tmp exist."""
+ for isolated_directory in ['/data', '/tmp']:
+ self.RunCommand([
+ 'mkdir', '-p',
+ _MapIsolatedPathsForPackage(for_package, 0,
+ for_realms)(isolated_directory)
+ ])
+
+ def PutFile(self,
+ source,
+ dest,
+ recursive=False,
+ for_package=None,
+ for_realms=()):
+ """Copies a file from the local filesystem to the target filesystem.
+
+ source: The path of the file being copied.
+ dest: The path on the remote filesystem which will be copied to.
+ recursive: If true, performs a recursive copy.
+ for_package: If specified, isolated paths in the |dest| are mapped to their
+ obsolute paths for the package, on the target. This currently
+ affects the /data and /tmp directories.
+ for_realms: If specified, identifies the sub-realm of 'sys' under which
+ isolated paths (see |for_package|) are stored.
+ """
+ assert type(source) is str
+ self.PutFiles([source], dest, recursive, for_package, for_realms)
+
+ def PutFiles(self,
+ sources,
+ dest,
+ recursive=False,
+ for_package=None,
+ for_realms=()):
+ """Copies files from the local filesystem to the target filesystem.
+
+ sources: List of local file paths to copy from, or a single path.
+ dest: The path on the remote filesystem which will be copied to.
+ recursive: If true, performs a recursive copy.
+ for_package: If specified, /data in the |dest| is mapped to the package's
+ isolated /data location.
+ for_realms: If specified, identifies the sub-realm of 'sys' under which
+ isolated paths (see |for_package|) are stored.
+ """
+ assert type(sources) is tuple or type(sources) is list
+ if for_package:
+ self.EnsureIsolatedPathsExist(for_package, for_realms)
+ dest = _MapIsolatedPathsForPackage(for_package, 0, for_realms)(dest)
+ logging.debug('copy local:%s => remote:%s', sources, dest)
+ self.GetCommandRunner().RunScp(sources, dest, remote_cmd.COPY_TO_TARGET,
+ recursive)
+
+ def GetFile(self,
+ source,
+ dest,
+ for_package=None,
+ for_realms=(),
+ recursive=False):
+ """Copies a file from the target filesystem to the local filesystem.
+
+ source: The path of the file being copied.
+ dest: The path on the local filesystem which will be copied to.
+ for_package: If specified, /data in paths in |sources| is mapped to the
+ package's isolated /data location.
+ for_realms: If specified, identifies the sub-realm of 'sys' under which
+ isolated paths (see |for_package|) are stored.
+ recursive: If true, performs a recursive copy.
+ """
+ assert type(source) is str
+ self.GetFiles([source], dest, for_package, for_realms, recursive)
+
+ def GetFiles(self,
+ sources,
+ dest,
+ for_package=None,
+ for_realms=(),
+ recursive=False):
+ """Copies files from the target filesystem to the local filesystem.
+
+ sources: List of remote file paths to copy.
+ dest: The path on the local filesystem which will be copied to.
+ for_package: If specified, /data in paths in |sources| is mapped to the
+ package's isolated /data location.
+ for_realms: If specified, identifies the sub-realm of 'sys' under which
+ isolated paths (see |for_package|) are stored.
+ recursive: If true, performs a recursive copy.
+ """
+ assert type(sources) is tuple or type(sources) is list
+ self._AssertIsStarted()
+ if for_package:
+ sources = map(_MapIsolatedPathsForPackage(for_package, 0, for_realms),
+ sources)
+ logging.debug('copy remote:%s => local:%s', sources, dest)
+ return self.GetCommandRunner().RunScp(sources, dest,
+ remote_cmd.COPY_FROM_TARGET,
+ recursive)
+
+ def _GetEndpoint(self):
+ """Returns a (host, port) tuple for the SSH connection to the target."""
+ raise NotImplementedError()
+
+ def _GetTargetSdkArch(self):
+ """Returns the Fuchsia SDK architecture name for the target CPU."""
+ if self._target_cpu == 'arm64' or self._target_cpu == 'x64':
+ return self._target_cpu
+ raise FuchsiaTargetException('Unknown target_cpu:' + self._target_cpu)
+
+ def _AssertIsStarted(self):
+ assert self.IsStarted()
+
+ def _WaitUntilReady(self):
+ logging.info('Connecting to Fuchsia using SSH.')
+
+ host, port = self._GetEndpoint()
+ end_time = time.time() + _ATTACH_RETRY_SECONDS
+ ssh_diagnostic_log = runner_logs.FileStreamFor('ssh_diagnostic_log')
+ while time.time() < end_time:
+ runner = remote_cmd.CommandRunner(self._GetSshConfigPath(), host, port)
+ ssh_proc = runner.RunCommandPiped(['true'],
+ ssh_args=['-v'],
+ stdout=ssh_diagnostic_log,
+ stderr=subprocess.STDOUT)
+ if ssh_proc.wait() == 0:
+ logging.info('Connected!')
+ self._started = True
+ return True
+ time.sleep(_ATTACH_RETRY_INTERVAL)
+
+ logging.error('Timeout limit reached.')
+
+ raise FuchsiaTargetException('Couldn\'t connect using SSH.')
+
+ def _GetSshConfigPath(self, path):
+ raise NotImplementedError()
+
+ def GetPkgRepo(self):
+ """Returns an PkgRepo instance which serves packages for this Target.
+ Callers should typically call GetPkgRepo() in a |with| statement, and
+ install and execute commands inside the |with| block, so that the returned
+ PkgRepo can teardown correctly, if necessary.
+ """
+ raise NotImplementedError()
+
+ def InstallPackage(self, package_paths):
+ """Installs a package and it's dependencies on the device. If the package is
+ already installed then it will be updated to the new version.
+
+ package_paths: Paths to the .far files to install.
+ """
+ with self.GetPkgRepo() as pkg_repo:
+ # Publish all packages to the serving TUF repository under |tuf_root|.
+ for package_path in package_paths:
+ pkg_repo.PublishPackage(package_path)
+
+ # Resolve all packages, to have them pulled into the device/VM cache.
+ for package_path in package_paths:
+ package_name, package_version = _GetPackageInfo(package_path)
+ logging.info('Resolving %s into cache.', package_name)
+ return_code = self.RunCommand(
+ ['pkgctl', 'resolve',
+ _GetPackageUri(package_name), '>/dev/null'],
+ timeout_secs=_INSTALL_TIMEOUT_SECS)
+ if return_code != 0:
+ raise Exception(
+ 'Error {} while resolving {}.'.format(return_code, package_name))
+
+ # Verify that the newly resolved versions of packages are reported.
+ for package_path in package_paths:
+ # Use pkgctl get-hash to determine which version will be resolved.
+ package_name, package_version = _GetPackageInfo(package_path)
+ pkgctl = self.RunCommandPiped(
+ ['pkgctl', 'get-hash',
+ _GetPackageUri(package_name)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ pkgctl_out, pkgctl_err = pkgctl.communicate()
+
+ # Read the expected version from the meta.far Merkel hash file alongside
+ # the package's FAR.
+ meta_far_path = os.path.join(os.path.dirname(package_path), 'meta.far')
+ meta_far_merkel = subprocess.check_output(
+ [common.GetHostToolPathFromPlatform('merkleroot'),
+ meta_far_path]).split()[0]
+ if pkgctl_out != meta_far_merkel:
+ raise Exception('Hash mismatch for %s after resolve (%s vs %s).' %
+ (package_name, pkgctl_out, meta_far_merkel))
+
+ def RunFFXCommand(self, ffx_args, **kwargs):
+ """Automatically gets the FFX path and runs FFX based on the
+ arguments provided. Extra args can be added to be used with Popen.
+
+ ffx_args: The arguments for a ffx command.
+ kwargs: A dictionary of parameters to be passed to subprocess.Popen().
+
+ Returns a Popen object for the command."""
+ command = [self._ffx_path] + ffx_args
+ return subprocess.Popen(command, **kwargs)