mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-27 17:58:38 +09:00
382 lines
17 KiB
Python
382 lines
17 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
"""
|
|
Command-line client to control a device
|
|
"""
|
|
|
|
import errno
|
|
import logging
|
|
import os
|
|
import posixpath
|
|
import StringIO
|
|
import sys
|
|
import mozdevice
|
|
import mozlog
|
|
import argparse
|
|
|
|
|
|
class DMCli(object):
|
|
|
|
def __init__(self):
|
|
self.commands = {'deviceroot': {'function': self.deviceroot,
|
|
'help': 'get device root directory for storing temporary '
|
|
'files'},
|
|
'install': {'function': self.install,
|
|
'args': [{'name': 'file'}],
|
|
'help': 'push this package file to the device'
|
|
' and install it'},
|
|
'uninstall': {'function': self.uninstall,
|
|
'args': [{'name': 'packagename'}],
|
|
'help': 'uninstall the named app from the device'},
|
|
'killapp': {'function': self.kill,
|
|
'args': [{'name': 'process_name', 'nargs': '*'}],
|
|
'help': 'kills any processes with name(s) on device'},
|
|
'launchapp': {'function': self.launchapp,
|
|
'args': [{'name': 'appname'},
|
|
{'name': 'activity_name'},
|
|
{'name': '--intent',
|
|
'action': 'store',
|
|
'default': 'android.intent.action.VIEW'},
|
|
{'name': '--url',
|
|
'action': 'store'},
|
|
{'name': '--no-fail-if-running',
|
|
'action': 'store_true',
|
|
'help': 'Don\'t fail if application is'
|
|
' already running'}
|
|
],
|
|
'help': 'launches application on device'},
|
|
'listapps': {'function': self.listapps,
|
|
'help': 'list applications on device'},
|
|
'push': {'function': self.push,
|
|
'args': [{'name': 'local_file'},
|
|
{'name': 'remote_file'}
|
|
],
|
|
'help': 'copy file/dir to device'},
|
|
'pull': {'function': self.pull,
|
|
'args': [{'name': 'local_file'},
|
|
{'name': 'remote_file', 'nargs': '?'}],
|
|
'help': 'copy file/dir from device'},
|
|
'shell': {'function': self.shell,
|
|
'args': [{'name': 'command', 'nargs': argparse.REMAINDER},
|
|
{'name': '--root', 'action': 'store_true',
|
|
'help': 'Run command as root'}],
|
|
'help': 'run shell command on device'},
|
|
'info': {'function': self.getinfo,
|
|
'args': [{'name': 'directive', 'nargs': '?'}],
|
|
'help': 'get information on specified '
|
|
'aspect of the device (if no argument '
|
|
'given, print all available information)'
|
|
},
|
|
'ps': {'function': self.processlist,
|
|
'help': 'get information on running processes on device'
|
|
},
|
|
'logcat': {'function': self.logcat,
|
|
'help': 'get logcat from device'
|
|
},
|
|
'ls': {'function': self.listfiles,
|
|
'args': [{'name': 'remote_dir'}],
|
|
'help': 'list files on device'
|
|
},
|
|
'rm': {'function': self.removefile,
|
|
'args': [{'name': 'remote_file'}],
|
|
'help': 'remove file from device'
|
|
},
|
|
'isdir': {'function': self.isdir,
|
|
'args': [{'name': 'remote_dir'}],
|
|
'help': 'print if remote file is a directory'
|
|
},
|
|
'mkdir': {'function': self.mkdir,
|
|
'args': [{'name': 'remote_dir'}],
|
|
'help': 'makes a directory on device'
|
|
},
|
|
'rmdir': {'function': self.rmdir,
|
|
'args': [{'name': 'remote_dir'}],
|
|
'help': 'recursively remove directory from device'
|
|
},
|
|
'screencap': {'function': self.screencap,
|
|
'args': [{'name': 'png_file'}],
|
|
'help': 'capture screenshot of device in action'
|
|
},
|
|
'sutver': {'function': self.sutver,
|
|
'help': 'SUTAgent\'s product name and version (SUT only)'
|
|
},
|
|
'clearlogcat': {'function': self.clearlogcat,
|
|
'help': 'clear the logcat'
|
|
},
|
|
'reboot': {'function': self.reboot,
|
|
'help': 'reboot the device',
|
|
'args': [{'name': '--wait',
|
|
'action': 'store_true',
|
|
'help': 'Wait for device to come back up'
|
|
' before exiting'}]
|
|
|
|
},
|
|
'isfile': {'function': self.isfile,
|
|
'args': [{'name': 'remote_file'}],
|
|
'help': 'check whether a file exists on the device'
|
|
},
|
|
'launchfennec': {'function': self.launchfennec,
|
|
'args': [{'name': 'appname'},
|
|
{'name': '--intent', 'action': 'store',
|
|
'default': 'android.intent.action.VIEW'},
|
|
{'name': '--url', 'action': 'store'},
|
|
{'name': '--extra-args', 'action': 'store'},
|
|
{'name': '--mozenv', 'action': 'store',
|
|
'help': 'Gecko environment variables to set'
|
|
' in "KEY1=VAL1 KEY2=VAL2" format'},
|
|
{'name': '--no-fail-if-running',
|
|
'action': 'store_true',
|
|
'help': 'Don\'t fail if application is '
|
|
'already running'}
|
|
],
|
|
'help': 'launch fennec'
|
|
},
|
|
'getip': {'function': self.getip,
|
|
'args': [{'name': 'interface', 'nargs': '*'}],
|
|
'help': 'get the ip address of the device'
|
|
}
|
|
}
|
|
|
|
self.parser = argparse.ArgumentParser()
|
|
self.add_options(self.parser)
|
|
self.add_commands(self.parser)
|
|
mozlog.commandline.add_logging_group(self.parser)
|
|
|
|
def run(self, args=sys.argv[1:]):
|
|
args = self.parser.parse_args()
|
|
|
|
mozlog.commandline.setup_logging(
|
|
'mozdevice', args, {'mach': sys.stdout})
|
|
|
|
if args.dmtype == "sut" and not args.host and not args.hwid:
|
|
self.parser.error("Must specify device ip in TEST_DEVICE or "
|
|
"with --host option with SUT")
|
|
|
|
self.dm = self.getDevice(dmtype=args.dmtype, hwid=args.hwid,
|
|
host=args.host, port=args.port,
|
|
verbose=args.verbose)
|
|
|
|
ret = args.func(args)
|
|
if ret is None:
|
|
ret = 0
|
|
|
|
sys.exit(ret)
|
|
|
|
def add_options(self, parser):
|
|
parser.add_argument("-v", "--verbose", action="store_true",
|
|
help="Verbose output from DeviceManager",
|
|
default=bool(os.environ.get('VERBOSE')))
|
|
parser.add_argument("--host", action="store",
|
|
help="Device hostname (only if using TCP/IP, "
|
|
"defaults to TEST_DEVICE environment "
|
|
"variable if present)",
|
|
default=os.environ.get('TEST_DEVICE'))
|
|
parser.add_argument("-p", "--port", action="store",
|
|
type=int,
|
|
help="Custom device port (if using SUTAgent or "
|
|
"adb-over-tcp)", default=None)
|
|
parser.add_argument("-m", "--dmtype", action="store",
|
|
help="DeviceManager type (adb or sut, defaults "
|
|
"to DM_TRANS environment variable, if "
|
|
"present, or adb)",
|
|
default=os.environ.get('DM_TRANS', 'adb'))
|
|
parser.add_argument("-d", "--hwid", action="store",
|
|
help="HWID", default=None)
|
|
parser.add_argument("--package-name", action="store",
|
|
help="Packagename (if using DeviceManagerADB)",
|
|
default=None)
|
|
|
|
def add_commands(self, parser):
|
|
subparsers = parser.add_subparsers(title="Commands", metavar="<command>")
|
|
for (commandname, commandprops) in sorted(self.commands.iteritems()):
|
|
subparser = subparsers.add_parser(commandname, help=commandprops['help'])
|
|
if commandprops.get('args'):
|
|
for arg in commandprops['args']:
|
|
# this is more elegant but doesn't work in python 2.6
|
|
# (which we still use on tbpl @ mozilla where we install
|
|
# this package)
|
|
# kwargs = { k: v for k,v in arg.items() if k is not 'name' }
|
|
kwargs = {}
|
|
for (k, v) in arg.items():
|
|
if k is not 'name':
|
|
kwargs[k] = v
|
|
subparser.add_argument(arg['name'], **kwargs)
|
|
subparser.set_defaults(func=commandprops['function'])
|
|
|
|
def getDevice(self, dmtype="adb", hwid=None, host=None, port=None,
|
|
packagename=None, verbose=False):
|
|
'''
|
|
Returns a device with the specified parameters
|
|
'''
|
|
logLevel = logging.ERROR
|
|
if verbose:
|
|
logLevel = logging.DEBUG
|
|
|
|
if hwid:
|
|
return mozdevice.DroidConnectByHWID(hwid, logLevel=logLevel)
|
|
|
|
if dmtype == "adb":
|
|
if host and not port:
|
|
port = 5555
|
|
return mozdevice.DroidADB(packageName=packagename,
|
|
host=host, port=port,
|
|
logLevel=logLevel)
|
|
elif dmtype == "sut":
|
|
if not host:
|
|
self.parser.error("Must specify host with SUT!")
|
|
if not port:
|
|
port = 20701
|
|
return mozdevice.DroidSUT(host=host, port=port,
|
|
logLevel=logLevel)
|
|
else:
|
|
self.parser.error("Unknown device manager type: %s" % type)
|
|
|
|
def deviceroot(self, args):
|
|
print self.dm.deviceRoot
|
|
|
|
def push(self, args):
|
|
(src, dest) = (args.local_file, args.remote_file)
|
|
if os.path.isdir(src):
|
|
self.dm.pushDir(src, dest)
|
|
else:
|
|
dest_is_dir = dest[-1] == '/' or self.dm.dirExists(dest)
|
|
dest = posixpath.normpath(dest)
|
|
if dest_is_dir:
|
|
dest = posixpath.join(dest, os.path.basename(src))
|
|
self.dm.pushFile(src, dest)
|
|
|
|
def pull(self, args):
|
|
(src, dest) = (args.local_file, args.remote_file)
|
|
if not self.dm.fileExists(src):
|
|
print 'No such file or directory'
|
|
return
|
|
if not dest:
|
|
dest = posixpath.basename(src)
|
|
if self.dm.dirExists(src):
|
|
self.dm.getDirectory(src, dest)
|
|
else:
|
|
self.dm.getFile(src, dest)
|
|
|
|
def install(self, args):
|
|
basename = os.path.basename(args.file)
|
|
app_path_on_device = posixpath.join(self.dm.deviceRoot,
|
|
basename)
|
|
self.dm.pushFile(args.file, app_path_on_device)
|
|
self.dm.installApp(app_path_on_device)
|
|
|
|
def uninstall(self, args):
|
|
self.dm.uninstallApp(args.packagename)
|
|
|
|
def launchapp(self, args):
|
|
self.dm.launchApplication(args.appname, args.activity_name,
|
|
args.intent, url=args.url,
|
|
failIfRunning=(not args.no_fail_if_running))
|
|
|
|
def listapps(self, args):
|
|
for app in self.dm.getInstalledApps():
|
|
print app
|
|
|
|
def stopapp(self, args):
|
|
self.dm.stopApplication(args.appname)
|
|
|
|
def kill(self, args):
|
|
for name in args.process_name:
|
|
self.dm.killProcess(name)
|
|
|
|
def shell(self, args):
|
|
buf = StringIO.StringIO()
|
|
self.dm.shell(args.command, buf, root=args.root)
|
|
print str(buf.getvalue()[0:-1]).rstrip()
|
|
|
|
def getinfo(self, args):
|
|
info = self.dm.getInfo(directive=args.directive)
|
|
for (infokey, infoitem) in sorted(info.iteritems()):
|
|
if infokey == "process":
|
|
pass # skip process list: get that through ps
|
|
elif args.directive is None:
|
|
print "%s: %s" % (infokey.upper(), infoitem)
|
|
else:
|
|
print infoitem
|
|
|
|
def logcat(self, args):
|
|
print ''.join(self.dm.getLogcat())
|
|
|
|
def clearlogcat(self, args):
|
|
self.dm.recordLogcat()
|
|
|
|
def reboot(self, args):
|
|
self.dm.reboot(wait=args.wait)
|
|
|
|
def processlist(self, args):
|
|
pslist = self.dm.getProcessList()
|
|
for ps in pslist:
|
|
print " ".join(str(i) for i in ps)
|
|
|
|
def listfiles(self, args):
|
|
filelist = self.dm.listFiles(args.remote_dir)
|
|
for file in filelist:
|
|
print file
|
|
|
|
def removefile(self, args):
|
|
self.dm.removeFile(args.remote_file)
|
|
|
|
def isdir(self, args):
|
|
if self.dm.dirExists(args.remote_dir):
|
|
print "TRUE"
|
|
return
|
|
|
|
print "FALSE"
|
|
return errno.ENOTDIR
|
|
|
|
def mkdir(self, args):
|
|
self.dm.mkDir(args.remote_dir)
|
|
|
|
def rmdir(self, args):
|
|
self.dm.removeDir(args.remote_dir)
|
|
|
|
def screencap(self, args):
|
|
self.dm.saveScreenshot(args.png_file)
|
|
|
|
def sutver(self, args):
|
|
if args.dmtype == 'sut':
|
|
print '%s Version %s' % (self.dm.agentProductName,
|
|
self.dm.agentVersion)
|
|
else:
|
|
print 'Must use SUT transport to get SUT version.'
|
|
|
|
def isfile(self, args):
|
|
if self.dm.fileExists(args.remote_file):
|
|
print "TRUE"
|
|
return
|
|
print "FALSE"
|
|
return errno.ENOENT
|
|
|
|
def launchfennec(self, args):
|
|
mozEnv = None
|
|
if args.mozenv:
|
|
mozEnv = {}
|
|
keyvals = args.mozenv.split()
|
|
for keyval in keyvals:
|
|
(key, _, val) = keyval.partition("=")
|
|
mozEnv[key] = val
|
|
self.dm.launchFennec(args.appname, intent=args.intent,
|
|
mozEnv=mozEnv,
|
|
extraArgs=args.extra_args, url=args.url,
|
|
failIfRunning=(not args.no_fail_if_running))
|
|
|
|
def getip(self, args):
|
|
if args.interface:
|
|
print(self.dm.getIP(args.interface))
|
|
else:
|
|
print(self.dm.getIP())
|
|
|
|
|
|
def cli(args=sys.argv[1:]):
|
|
# process the command line
|
|
cli = DMCli()
|
|
cli.run(args)
|
|
|
|
if __name__ == '__main__':
|
|
cli()
|