Skip to content

Commit

Permalink
Merge "Add python fastboot package"
Browse files Browse the repository at this point in the history
am: 1fac3f7

Change-Id: Ia14e6f6447e1cb9bcc3771b0ce9272044a1b4d72
  • Loading branch information
drosen-google authored and android-build-merger committed Aug 17, 2016
2 parents 7d5963c + 1fac3f7 commit b3406c9
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 0 deletions.
2 changes: 2 additions & 0 deletions python-packages/fastboot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
This library provides access to the fastboot utility.
For fastboot bootloader tests, see platform/system/extra/tests/bootloader
17 changes: 17 additions & 0 deletions python-packages/fastboot/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from __future__ import absolute_import
from .device import * # pylint: disable=wildcard-import
235 changes: 235 additions & 0 deletions python-packages/fastboot/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Provides functionality to interact with a device via `fastboot`."""

import os
import re
import subprocess


class FastbootError(Exception):
"""Something went wrong interacting with fastboot."""


class FastbootDevice(object):
"""Class to interact with a fastboot device."""

# Prefix for INFO-type messages when printed by fastboot. If we want
# to parse the output from an INFO message we need to strip this off.
INFO_PREFIX = '(bootloader) '

def __init__(self, path='fastboot'):
"""Initialization.
Args:
path: path to the fastboot executable to test with.
Raises:
FastbootError: Failed to find a device in fastboot mode.
"""
self.path = path

# Make sure the fastboot executable is available.
try:
_subprocess_check_output([self.path, '--version'])
except OSError:
raise FastbootError('Could not execute `{}`'.format(self.path))

# Make sure exactly 1 fastboot device is available if <specific device>
# was not given as an argument. Do not try to find an adb device and
# put it in fastboot mode, it would be too easy to accidentally
# download to the wrong device.
if not self._check_single_device():
raise FastbootError('Requires exactly 1 device in fastboot mode')

def _check_single_device(self):
"""Returns True if there is exactly one fastboot device attached.
When ANDROID_SERIAL is set it checks that the device is available.
"""

if 'ANDROID_SERIAL' in os.environ:
try:
self.getvar('product')
return True
except subprocess.CalledProcessError:
return False
devices = _subprocess_check_output([self.path, 'devices']).splitlines()
return len(devices) == 1 and devices[0].split()[1] == 'fastboot'

def getvar(self, name):
"""Calls `fastboot getvar`.
To query all variables (fastboot getvar all) use getvar_all()
instead.
Args:
name: variable name to access.
Returns:
String value of variable |name| or None if not found.
"""
try:
output = _subprocess_check_output([self.path, 'getvar', name],
stderr=subprocess.STDOUT).splitlines()
except subprocess.CalledProcessError:
return None
# Output format is <name>:<whitespace><value>.
out = 0
if output[0] == "< waiting for any device >":
out = 1
result = re.search(r'{}:\s*(.*)'.format(name), output[out])
if result:
return result.group(1)
else:
return None

def getvar_all(self):
"""Calls `fastboot getvar all`.
Returns:
A {name, value} dictionary of variables.
"""
output = _subprocess_check_output([self.path, 'getvar', 'all'],
stderr=subprocess.STDOUT).splitlines()
all_vars = {}
for line in output:
result = re.search(r'(.*):\s*(.*)', line)
if result:
var_name = result.group(1)

# `getvar all` works by sending one INFO message per variable
# so we need to strip out the info prefix string.
if var_name.startswith(self.INFO_PREFIX):
var_name = var_name[len(self.INFO_PREFIX):]

# In addition to returning all variables the bootloader may
# also think it's supposed to query a return a variable named
# "all", so ignore this line if so. Fastboot also prints a
# summary line that we want to ignore.
if var_name != 'all' and 'total time' not in var_name:
all_vars[var_name] = result.group(2)
return all_vars

def flashall(self, wipe_user=True, slot=None, skip_secondary=False, quiet=True):
"""Calls `fastboot [-w] flashall`.
Args:
wipe_user: whether to set the -w flag or not.
slot: slot to flash if device supports A/B, otherwise default will be used.
skip_secondary: on A/B devices, flashes only the primary images if true.
quiet: True to hide output, false to send it to stdout.
"""
func = (_subprocess_check_output if quiet else subprocess.check_call)
command = [self.path, 'flashall']
if slot:
command.extend(['--slot', slot])
if skip_secondary:
command.append("--skip-secondary")
if wipe_user:
command.append('-w')
func(command, stderr=subprocess.STDOUT)

def flash(self, partition='cache', img=None, slot=None, quiet=True):
"""Calls `fastboot flash`.
Args:
partition: which partition to flash.
img: path to .img file, otherwise the default will be used.
slot: slot to flash if device supports A/B, otherwise default will be used.
quiet: True to hide output, false to send it to stdout.
"""
func = (_subprocess_check_output if quiet else subprocess.check_call)
command = [self.path, 'flash', partition]
if img:
command.append(img)
if slot:
command.extend(['--slot', slot])
if skip_secondary:
command.append("--skip-secondary")
func(command, stderr=subprocess.STDOUT)

def reboot(self, bootloader=False):
"""Calls `fastboot reboot [bootloader]`.
Args:
bootloader: True to reboot back to the bootloader.
"""
command = [self.path, 'reboot']
if bootloader:
command.append('bootloader')
_subprocess_check_output(command, stderr=subprocess.STDOUT)

def set_active(self, slot):
"""Calls `fastboot set_active <slot>`.
Args:
slot: The slot to set as the current slot."""
command = [self.path, 'set_active', slot]
_subprocess_check_output(command, stderr=subprocess.STDOUT)

# If necessary, modifies subprocess.check_output() or subprocess.Popen() args
# to run the subprocess via Windows PowerShell to work-around an issue in
# Python 2's subprocess class on Windows where it doesn't support Unicode.
def _get_subprocess_args(args):
# Only do this slow work-around if Unicode is in the cmd line on Windows.
# PowerShell takes 600-700ms to startup on a 2013-2014 machine, which is
# very slow.
if os.name != 'nt' or all(not isinstance(arg, unicode) for arg in args[0]):
return args

def escape_arg(arg):
# Escape for the parsing that the C Runtime does in Windows apps. In
# particular, this will take care of double-quotes.
arg = subprocess.list2cmdline([arg])
# Escape single-quote with another single-quote because we're about
# to...
arg = arg.replace(u"'", u"''")
# ...put the arg in a single-quoted string for PowerShell to parse.
arg = u"'" + arg + u"'"
return arg

# Escape command line args.
argv = map(escape_arg, args[0])
# Cause script errors (such as adb not found) to stop script immediately
# with an error.
ps_code = u'$ErrorActionPreference = "Stop"\r\n'
# Add current directory to the PATH var, to match cmd.exe/CreateProcess()
# behavior.
ps_code += u'$env:Path = ".;" + $env:Path\r\n'
# Precede by &, the PowerShell call operator, and separate args by space.
ps_code += u'& ' + u' '.join(argv)
# Make the PowerShell exit code the exit code of the subprocess.
ps_code += u'\r\nExit $LastExitCode'
# Encode as UTF-16LE (without Byte-Order-Mark) which Windows natively
# understands.
ps_code = ps_code.encode('utf-16le')

# Encode the PowerShell command as base64 and use the special
# -EncodedCommand option that base64 decodes. Base64 is just plain ASCII,
# so it should have no problem passing through Win32 CreateProcessA()
# (which python erroneously calls instead of CreateProcessW()).
return (['powershell.exe', '-NoProfile', '-NonInteractive',
'-EncodedCommand', base64.b64encode(ps_code)],) + args[1:]

# Call this instead of subprocess.check_output() to work-around issue in Python
# 2's subprocess class on Windows where it doesn't support Unicode.
def _subprocess_check_output(*args, **kwargs):
try:
return subprocess.check_output(*_get_subprocess_args(args), **kwargs)
except subprocess.CalledProcessError as e:
# Show real command line instead of the powershell.exe command line.
raise subprocess.CalledProcessError(e.returncode, args[0],
output=e.output)
32 changes: 32 additions & 0 deletions python-packages/fastboot/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from distutils.core import setup


setup(
name='fastboot',
version='0.0.1',
description='A Python interface to the Fastboot utility.',
license='Apache 2.0',
keywords='fastboot android',
package_dir={'fastboot': ''},
packages=['fastboot'],
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
]
)

0 comments on commit b3406c9

Please sign in to comment.