Support parallel build and install in on-device apk scripts.
authorJarkko Pöyry <jpoyry@google.com>
Fri, 12 Dec 2014 02:22:05 +0000 (18:22 -0800)
committerJarkko Pöyry <jpoyry@google.com>
Wed, 24 Dec 2014 03:09:18 +0000 (19:09 -0800)
Add -p flag to build.py to build native libs in parallel. Add -p
flag to install.py to install to multiple devices in parallel.

Change-Id: I042de5eae321e61455d330949c7f07577725ba15

android/scripts/build.py
android/scripts/common.py
android/scripts/install.py

index 3e42371..1fb54e8 100644 (file)
@@ -53,9 +53,8 @@ def buildNative (buildRoot, libTargetDir, nativeLib, buildType):
        # Make build directory if necessary
        if not os.path.exists(buildDir):
                os.makedirs(buildDir)
-               os.chdir(buildDir)
                toolchainFile = '%s/framework/delibs/cmake/toolchain-android-%s.cmake' % (deqpDir, common.ANDROID_NDK_TOOLCHAIN_VERSION)
-               common.execArgs([
+               common.execArgsInDirectory([
                                'cmake',
                                '-G%s' % common.CMAKE_GENERATOR,
                                '-DCMAKE_TOOLCHAIN_FILE=%s' % toolchainFile,
@@ -66,10 +65,9 @@ def buildNative (buildRoot, libTargetDir, nativeLib, buildType):
                                '-DCMAKE_BUILD_TYPE=%s' % buildType,
                                '-DDEQP_TARGET=android',
                                deqpDir
-                       ])
+                       ], buildDir)
 
-       os.chdir(buildDir)
-       common.execArgs(['cmake', '--build', '.'] + common.EXTRA_BUILD_ARGS)
+       common.execArgsInDirectory(['cmake', '--build', '.'] + common.EXTRA_BUILD_ARGS, buildDir)
 
        if not os.path.exists(libsDir):
                os.makedirs(libsDir)
@@ -138,7 +136,7 @@ def signApp (keystore, keyname, storepass, keypass):
                        'bin/dEQP-release.apk'
                ])
 
-def build (buildRoot=common.ANDROID_DIR, isRelease=False, nativeBuildType="Release", javaApi=common.ANDROID_JAVA_API):
+def build (buildRoot=common.ANDROID_DIR, isRelease=False, nativeBuildType="Release", javaApi=common.ANDROID_JAVA_API, doParallelBuild=False):
        curDir = os.getcwd()
 
        try:
@@ -159,8 +157,11 @@ def build (buildRoot=common.ANDROID_DIR, isRelease=False, nativeBuildType="Relea
                        shutil.rmtree(libTargetDir)
 
                # Build native code
-               for lib in common.NATIVE_LIBS:
-                       buildNative(buildRoot, libTargetDir, lib, nativeBuildType)
+               nativeBuildArgs = [(buildRoot, libTargetDir, nativeLib, nativeBuildType) for nativeLib in common.NATIVE_LIBS]
+               if doParallelBuild:
+                       common.parallelApply(buildNative, nativeBuildArgs)
+               else:
+                       common.serialApply(buildNative, nativeBuildArgs)
 
                # Copy assets
                if os.path.exists(assetsSrcDir):
@@ -186,10 +187,11 @@ if __name__ == "__main__":
        parser.add_argument('--build-root', dest='buildRoot', default=common.ANDROID_DIR, help="Root directory for storing build results.")
        parser.add_argument('--dump-config', dest='dumpConfig', action='store_true', help="Print out all configurations variables")
        parser.add_argument('--java-api', dest='javaApi', default=common.ANDROID_JAVA_API, help="Set the API signature for the java build.")
+       parser.add_argument('-p', '--parallel-build', dest='parallelBuild', action="store_true", help="Build native libraries in parallel.")
 
        args = parser.parse_args()
 
        if args.dumpConfig:
                dumpConfig()
 
-       build(buildRoot=os.path.abspath(args.buildRoot), isRelease=args.isRelease, nativeBuildType=args.nativeBuildType, javaApi=args.javaApi)
+       build(buildRoot=os.path.abspath(args.buildRoot), isRelease=args.isRelease, nativeBuildType=args.nativeBuildType, javaApi=args.javaApi, doParallelBuild=args.parallelBuild)
index 674d818..d8e5b1b 100644 (file)
@@ -8,6 +8,11 @@ import subprocess
 import multiprocessing
 import string
 
+try:
+       import threading
+except ImportError:
+       import dummy_threading as threading
+
 class NativeLib:
        def __init__ (self, apiVersion, abiVersion):
                self.apiVersion = apiVersion
@@ -82,7 +87,43 @@ def execArgs (args):
        sys.stdout.flush()
        retcode = subprocess.call(args)
        if retcode != 0:
-               raise Exception("Failed to execute '%s', got %d" % (str(args), retcode))
+               raise Exception("Failed to execute '%s', got %d" % (str(args), retcode))
+
+def execArgsInDirectory (args, cwd):
+       # Make sure previous stdout prints have been written out.
+       sys.stdout.flush()
+       process = subprocess.Popen(args, cwd=cwd)
+       retcode = process.wait()
+       if retcode != 0:
+               raise Exception("Failed to execute '%s', got %d" % (str(args), retcode))
+
+def serialApply(f, argsList):
+       for args in argsList:
+               f(*args)
+
+def parallelApply(f, argsList):
+       class ErrorCode:
+               def __init__ (self):
+                       self.error = None;
+
+       def applyAndCaptureError (func, args, errorCode):
+               try:
+                       func(*args)
+               except:
+                       errorCode.error = sys.exc_info()
+
+       errorCode = ErrorCode()
+       jobs = []
+       for args in argsList:
+               job = threading.Thread(target=applyAndCaptureError, args=(f, args, errorCode))
+               job.start()
+               jobs.append(job)
+
+       for job in jobs:
+               job.join()
+
+       if errorCode.error:
+               raise errorCode.error[0], errorCode.error[1], errorCode.error[2]
 
 class Device:
        def __init__(self, serial, product, model, device):
@@ -99,7 +140,7 @@ def getDevices (adb):
        (stdout, stderr) = proc.communicate()
 
        if proc.returncode != 0:
-               raise Exception("adb devices -l failed, got %d" % retcode)
+               raise Exception("adb devices -l failed, got %d" % proc.returncode)
 
        ptrn = re.compile(r'^([a-zA-Z0-9]+)\s+.*product:([^\s]+)\s+model:([^\s]+)\s+device:([^\s]+)')
        devices = []
index 2ef02cd..317dd84 100644 (file)
@@ -7,43 +7,39 @@ import string
 
 import common
 
-def install (extraArgs = []):
-       curDir = os.getcwd()
-       try:
-               os.chdir(common.ANDROID_DIR)
-
-               print "Removing old dEQP Package..."
-               common.execArgs([common.ADB_BIN] + extraArgs + [
-                               'uninstall',
-                               'com.drawelements.deqp'
-                       ])
-               print ""
-
-               print "Installing dEQP Package..."
-               common.execArgs([common.ADB_BIN] + extraArgs + [
-                               'install',
-                               '-r',
-                               'package/bin/dEQP-debug.apk'
-                       ])
-               print ""
-
-       finally:
-               # Restore working dir
-               os.chdir(curDir)
-
-def installToDevice (device):
-       print "Installing to %s (%s)..." % (device.serial, device.model)
-       install(['-s', device.serial])
-
-def installToAllDevices ():
+def install (extraArgs = [], printPrefix=""):
+       print printPrefix + "Removing old dEQP Package...\n",
+       common.execArgsInDirectory([common.ADB_BIN] + extraArgs + [
+                       'uninstall',
+                       'com.drawelements.deqp'
+               ], common.ANDROID_DIR)
+       print printPrefix + "Remove complete\n",
+
+       print printPrefix + "Installing dEQP Package...\n",
+       common.execArgsInDirectory([common.ADB_BIN] + extraArgs + [
+                       'install',
+                       '-r',
+                       'package/bin/dEQP-debug.apk'
+               ], common.ANDROID_DIR)
+       print printPrefix + "Install complete\n",
+
+def installToDevice (device, printPrefix=""):
+       print printPrefix + "Installing to %s (%s)...\n" % (device.serial, device.model),
+       install(['-s', device.serial], printPrefix)
+
+def installToAllDevices (doParallel):
        devices = common.getDevices(common.ADB_BIN)
-       for device in devices:
-               installToDevice(device)
+       padLen = max([len(device.model) for device in devices])+1
+       if doParallel:
+               common.parallelApply(installToDevice, [(device, ("(%s):%s" % (device.model, ' ' * (padLen - len(device.model))))) for device in devices]);
+       else:
+               common.serialApply(installToDevice, [(device, ) for device in devices]);
 
 if __name__ == "__main__":
        if len(sys.argv) > 1:
                if sys.argv[1] == '-a':
-                       installToAllDevices()
+                       doParallel = '-p' in sys.argv[1:]
+                       installToAllDevices(doParallel)
                else:
                        install(sys.argv[1:])
        else: