chiark / gitweb /
Merge branch 'plural' into 'master'
[fdroidserver.git] / fdroidserver / build.py
index dff83104db74c2a44cbb2770e9b0671183bbb95d..6410226ccb4603315afb4bd75dc818b0693b3850 100644 (file)
@@ -31,7 +31,9 @@ import tempfile
 from configparser import ConfigParser
 from argparse import ArgumentParser
 import logging
+from gettext import ngettext
 
+from . import _
 from . import common
 from . import net
 from . import metadata
@@ -96,19 +98,19 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
 
         # Helper to copy the contents of a directory to the server...
         def send_dir(path):
-            root = os.path.dirname(path)
+            startroot = os.path.dirname(path)
             main = os.path.basename(path)
             ftp.mkdir(main)
-            for r, d, f in os.walk(path):
-                rr = os.path.relpath(rroot)
+            for root, dirs, files in os.walk(path):
+                rr = os.path.relpath(root, startroot)
                 ftp.chdir(rr)
-                for dd in d:
-                    ftp.mkdir(dd)
-                for ff in f:
-                    lfile = os.path.join(root, rr, ff)
+                for d in dirs:
+                    ftp.mkdir(d)
+                for f in files:
+                    lfile = os.path.join(startroot, rr, f)
                     if not os.path.islink(lfile):
-                        ftp.put(lfile, ff)
-                        ftp.chmod(ff, os.stat(lfile).st_mode)
+                        ftp.put(lfile, f)
+                        ftp.chmod(f, os.stat(lfile).st_mode)
                 for i in range(len(rr.split('/'))):
                     ftp.chdir('..')
             ftp.chdir('..')
@@ -162,7 +164,7 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
                         ftp.mkdir(d)
                     ftp.chdir(d)
                 ftp.put(libsrc, lp[-1])
-                for _ in lp[:-1]:
+                for _ignored in lp[:-1]:
                     ftp.chdir('..')
         # Copy any srclibs that are required...
         srclibpaths = []
@@ -210,23 +212,33 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
         cmdline += " %s:%s" % (app.id, build.versionCode)
         chan.exec_command('bash --login -c "' + cmdline + '"')
 
-        output = bytes()
-        output += get_android_tools_version_log(build.ndk_path()).encode()
-        while not chan.exit_status_ready():
-            while chan.recv_ready():
-                output += chan.recv(1024)
-            time.sleep(0.1)
+        # Fetch build process output ...
+        try:
+            cmd_stdout = chan.makefile('rb', 1024)
+            output = bytes()
+            output += get_android_tools_version_log(build.ndk_path()).encode()
+            while not chan.exit_status_ready():
+                line = cmd_stdout.readline()
+                if line:
+                    if options.verbose:
+                        logging.debug("buildserver > " + str(line, 'utf-8').rstrip())
+                    output += line
+                else:
+                    time.sleep(0.05)
+            for line in cmd_stdout.readlines():
+                if options.verbose:
+                    logging.debug("buildserver > " + str(line, 'utf-8').rstrip())
+                output += line
+        finally:
+            cmd_stdout.close()
+
+        # Check build process exit status ...
         logging.info("...getting exit status")
         returncode = chan.recv_exit_status()
-        while True:
-            get = chan.recv(1024)
-            if len(get) == 0:
-                break
-            output += get
         if returncode != 0:
             raise BuildException(
                 "Build.py failed on server for {0}:{1}".format(
-                    app.id, build.versionName), str(output, 'utf-8'))
+                    app.id, build.versionName), None if options.verbose else str(output, 'utf-8'))
 
         # Retreive logs...
         toolsversion_log = common.get_toolsversion_logname(app, build)
@@ -251,8 +263,8 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
                 ftp.get(tarball, os.path.join(output_dir, tarball))
         except Exception:
             raise BuildException(
-                "Build failed for %s:%s - missing output files".format(
-                    app.id, build.versionName), output)
+                "Build failed for {0}:{1} - missing output files".format(
+                    app.id, build.versionName), None if options.verbose else str(output, 'utf-8'))
         ftp.close()
 
     finally:
@@ -275,14 +287,13 @@ def force_gradle_build_tools(build_dir, build_tools):
                                path)
 
 
-def capitalize_intact(string):
-    """Like str.capitalize(), but leave the rest of the string intact without
-    switching it to lowercase."""
+def transform_first_char(string, method):
+    """Uses method() on the first character of string."""
     if len(string) == 0:
         return string
     if len(string) == 1:
-        return string.upper()
-    return string[0].upper() + string[1:]
+        return method(string)
+    return method(string[0]) + string[1:]
 
 
 def has_native_code(apkobj):
@@ -444,7 +455,7 @@ def build_local(app, build, vcs, build_dir, output_dir, log_dir, srclib_dir, ext
         if flavours == ['yes']:
             flavours = []
 
-        flavours_cmd = ''.join([capitalize_intact(flav) for flav in flavours])
+        flavours_cmd = ''.join([transform_first_char(flav, str.upper) for flav in flavours])
 
         gradletasks += ['assemble' + flavours_cmd + 'Release']
 
@@ -513,9 +524,12 @@ def build_local(app, build, vcs, build_dir, output_dir, log_dir, srclib_dir, ext
         count = scanner.scan_source(build_dir, build)
         if count > 0:
             if force:
-                logging.warn('Scanner found %d problems' % count)
+                logging.warning(ngettext('Scanner found {} problem',
+                                         'Scanner found {} problems', count).format(count))
             else:
-                raise BuildException("Can't build due to %d errors while scanning" % count)
+                raise BuildException(ngettext(
+                    "Can't build due to {} error while scanning",
+                    "Can't build due to {} errors while scanning", count).format(count))
 
     if not options.notarball:
         # Build the source tarball right before we build the release...
@@ -803,11 +817,20 @@ def build_local(app, build, vcs, build_dir, output_dir, log_dir, srclib_dir, ext
 
     elif omethod == 'gradle':
         src = None
-        for apks_dir in [
-                os.path.join(root_dir, 'build', 'outputs', 'apk', 'release'),
-                os.path.join(root_dir, 'build', 'outputs', 'apk'),
-                os.path.join(root_dir, 'build', 'apk'),
-                ]:
+        apk_dirs = [
+            # gradle plugin >= 3.0
+            os.path.join(root_dir, 'build', 'outputs', 'apk', 'release'),
+            # gradle plugin < 3.0 and >= 0.11
+            os.path.join(root_dir, 'build', 'outputs', 'apk'),
+            # really old path
+            os.path.join(root_dir, 'build', 'apk'),
+            ]
+        # If we build with gradle flavours with gradle plugin >= 3.0 the apk will be in
+        # a subdirectory corresponding to the flavour command used, but with different
+        # capitalization.
+        if flavours_cmd:
+            apk_dirs.append(os.path.join(root_dir, 'build', 'outputs', 'apk', transform_first_char(flavours_cmd, str.lower), 'release'))
+        for apks_dir in apk_dirs:
             for apkglob in ['*-release-unsigned.apk', '*-unsigned.apk', '*.apk']:
                 apks = glob.glob(os.path.join(apks_dir, apkglob))
 
@@ -977,33 +1000,33 @@ def parse_commandline():
 
     parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]")
     common.setup_global_opts(parser)
-    parser.add_argument("appid", nargs='*', help="app-id with optional versionCode in the form APPID[:VERCODE]")
+    parser.add_argument("appid", nargs='*', help=_("applicationId with optional versionCode in the form APPID[:VERCODE]"))
     parser.add_argument("-l", "--latest", action="store_true", default=False,
-                        help="Build only the latest version of each package")
+                        help=_("Build only the latest version of each package"))
     parser.add_argument("-s", "--stop", action="store_true", default=False,
-                        help="Make the build stop on exceptions")
+                        help=_("Make the build stop on exceptions"))
     parser.add_argument("-t", "--test", action="store_true", default=False,
-                        help="Test mode - put output in the tmp directory only, and always build, even if the output already exists.")
+                        help=_("Test mode - put output in the tmp directory only, and always build, even if the output already exists."))
     parser.add_argument("--server", action="store_true", default=False,
-                        help="Use build server")
+                        help=_("Use build server"))
     parser.add_argument("--resetserver", action="store_true", default=False,
-                        help="Reset and create a brand new build server, even if the existing one appears to be ok.")
+                        help=_("Reset and create a brand new build server, even if the existing one appears to be ok."))
     parser.add_argument("--on-server", dest="onserver", action="store_true", default=False,
-                        help="Specify that we're running on the build server")
+                        help=_("Specify that we're running on the build server"))
     parser.add_argument("--skip-scan", dest="skipscan", action="store_true", default=False,
-                        help="Skip scanning the source code for binaries and other problems")
+                        help=_("Skip scanning the source code for binaries and other problems"))
     parser.add_argument("--dscanner", action="store_true", default=False,
-                        help="Setup an emulator, install the apk on it and perform a drozer scan")
+                        help=_("Setup an emulator, install the apk on it and perform a drozer scan"))
     parser.add_argument("--no-tarball", dest="notarball", action="store_true", default=False,
-                        help="Don't create a source tarball, useful when testing a build")
+                        help=_("Don't create a source tarball, useful when testing a build"))
     parser.add_argument("--no-refresh", dest="refresh", action="store_false", default=True,
-                        help="Don't refresh the repository, useful when testing a build with no internet connection")
+                        help=_("Don't refresh the repository, useful when testing a build with no internet connection"))
     parser.add_argument("-f", "--force", action="store_true", default=False,
-                        help="Force build of disabled apps, and carries on regardless of scan problems. Only allowed in test mode.")
+                        help=_("Force build of disabled apps, and carries on regardless of scan problems. Only allowed in test mode."))
     parser.add_argument("-a", "--all", action="store_true", default=False,
-                        help="Build all applications available")
+                        help=_("Build all applications available"))
     parser.add_argument("-w", "--wiki", default=False, action="store_true",
-                        help="Update the wiki")
+                        help=_("Update the wiki"))
     metadata.add_metadata_arguments(parser)
     options = parser.parse_args()
     metadata.warnings_action = options.W
@@ -1158,7 +1181,7 @@ def main():
                         url = url.replace('%v', build.versionName)
                         url = url.replace('%c', str(build.versionCode))
                         logging.info("...retrieving " + url)
-                        of = common.get_release_filename(app, build) + '.binary'
+                        of = re.sub(r'.apk$', '.binary.apk', common.get_release_filename(app, build))
                         of = os.path.join(output_dir, of)
                         try:
                             net.download_file(url, local_filename=of)
@@ -1298,11 +1321,13 @@ def main():
         logging.info("Cleaning up after ourselves.")
         docker.clean()
 
-    logging.info("Finished.")
+    logging.info(_("Finished"))
     if len(build_succeeded) > 0:
-        logging.info(str(len(build_succeeded)) + ' builds succeeded')
+        logging.info(ngettext("{} build succeeded",
+                              "{} builds succeeded", len(build_succeeded)).format(len(build_succeeded)))
     if len(failed_apps) > 0:
-        logging.info(str(len(failed_apps)) + ' builds failed')
+        logging.info(ngettext("{} build failed",
+                              "{} builds failed", len(failed_apps)).format(len(failed_apps)))
 
     sys.exit(0)