chiark / gitweb /
Don't use generic Exception raises
[fdroidserver.git] / fdroidserver / common.py
index aa362ea8e22ec0177cc5565b35790d3ef39eaaa1..ce3acc12fa3a21bb0ac9d68ca7c9c297b55350c0 100644 (file)
@@ -36,12 +36,13 @@ import metadata
 
 config = None
 options = None
+env = None
 
 
 def get_default_config():
     return {
-        'sdk_path': os.getenv("ANDROID_HOME"),
-        'ndk_path': os.getenv("ANDROID_NDK"),
+        'sdk_path': os.getenv("ANDROID_HOME") or "",
+        'ndk_path': os.getenv("ANDROID_NDK") or "",
         'build_tools': "20.0.0",
         'ant': "ant",
         'mvn3': "mvn",
@@ -61,11 +62,11 @@ def get_default_config():
         'repo_url': "https://MyFirstFDroidRepo.org/fdroid/repo",
         'repo_name': "My First FDroid Repo Demo",
         'repo_icon': "fdroid-icon.png",
-        'repo_description':
+        'repo_description': (
             "This is a repository of apps to be used with FDroid. Applications in this "
-            "repository are either official binaries built by the original application "
-            "developers, or are binaries built from source by the admin of f-droid.org "
-            "using the tools on https://gitlab.com/u/fdroid.",
+            "repository are either official binaries built by the original application "
+            "developers, or are binaries built from source by the admin of f-droid.org "
+            + "using the tools on https://gitlab.com/u/fdroid."),
         'archive_older': 0,
     }
 
@@ -76,7 +77,7 @@ def read_config(opts, config_file='config.py'):
     The config is read from config_file, which is in the current directory when
     any of the repo management commands are used.
     """
-    global config, options
+    global config, options, env
 
     if config is not None:
         return config
@@ -121,6 +122,43 @@ def read_config(opts, config_file='config.py'):
     if not test_sdk_exists(config):
         sys.exit(3)
 
+    if not test_build_tools_exists(config):
+        sys.exit(3)
+
+    bin_paths = {
+        'aapt': [
+            os.path.join(config['sdk_path'], 'build-tools', config['build_tools'], 'aapt'),
+            ],
+        'zipalign': [
+            os.path.join(config['sdk_path'], 'tools', 'zipalign'),
+            os.path.join(config['sdk_path'], 'build-tools', config['build_tools'], 'zipalign'),
+            ],
+        'android': [
+            os.path.join(config['sdk_path'], 'tools', 'android'),
+            ],
+        'adb': [
+            os.path.join(config['sdk_path'], 'platform-tools', 'adb'),
+            ],
+        }
+
+    for b, paths in bin_paths.items():
+        config[b] = None
+        for path in paths:
+            if os.path.isfile(path):
+                config[b] = path
+                break
+        if config[b] is None:
+            logging.warn("Could not find %s in any of the following paths:\n%s" % (
+                b, '\n'.join(paths)))
+
+    # There is no standard, so just set up the most common environment
+    # variables
+    env = os.environ
+    for n in ['ANDROID_HOME', 'ANDROID_SDK']:
+        env[n] = config['sdk_path']
+    for n in ['ANDROID_NDK', 'NDK']:
+        env[n] = config['ndk_path']
+
     for k in ["keystorepass", "keypass"]:
         if k in config:
             write_password_file(k)
@@ -138,9 +176,9 @@ def read_config(opts, config_file='config.py'):
 def test_sdk_exists(c):
     if c['sdk_path'] is None:
         # c['sdk_path'] is set to the value of ANDROID_HOME by default
-        logging.critical('No Android SDK found! ANDROID_HOME is not set and sdk_path is not in config.py!')
-        logging.info('You can use ANDROID_HOME to set the path to your SDK, i.e.:')
-        logging.info('\texport ANDROID_HOME=/opt/android-sdk')
+        logging.error('No Android SDK found! ANDROID_HOME is not set and sdk_path is not in config.py!')
+        logging.error('You can use ANDROID_HOME to set the path to your SDK, i.e.:')
+        logging.error('\texport ANDROID_HOME=/opt/android-sdk')
         return False
     if not os.path.exists(c['sdk_path']):
         logging.critical('Android SDK path "' + c['sdk_path'] + '" does not exist!')
@@ -148,12 +186,11 @@ def test_sdk_exists(c):
     if not os.path.isdir(c['sdk_path']):
         logging.critical('Android SDK path "' + c['sdk_path'] + '" is not a directory!')
         return False
-    if not os.path.isdir(os.path.join(c['sdk_path'], 'build-tools')):
-        logging.critical('Android SDK path "' + c['sdk_path'] + '" does not contain "build-tools/"!')
-        return False
-    if not os.path.isdir(os.path.join(c['sdk_path'], 'build-tools', c['build_tools'])):
-        logging.critical('Configured build-tools version "' + c['build_tools'] + '" not found in the SDK!')
-        return False
+    for d in ['build-tools', 'platform-tools', 'tools']:
+        if not os.path.isdir(os.path.join(c['sdk_path'], d)):
+            logging.critical('Android SDK path "%s" does not contain "%s/"!' % (
+                c['sdk_path'], d))
+            return False
     return True
 
 
@@ -166,11 +203,6 @@ def test_build_tools_exists(c):
         logging.critical('Android Build Tools path "'
                          + versioned_build_tools + '" does not exist!')
         return False
-    if not os.path.exists(os.path.join(c['sdk_path'], 'build-tools', c['build_tools'], 'aapt')):
-        logging.critical('Android Build Tools "'
-                         + versioned_build_tools
-                         + '" does not contain "aapt"!')
-        return False
     return True
 
 
@@ -227,9 +259,9 @@ def read_app_args(args, allapps, allow_vercodes=False):
         for p in vercodes:
             if p not in allids:
                 logging.critical("No such package: %s" % p)
-        raise Exception("Found invalid app ids in arguments")
+        raise FDroidException("Found invalid app ids in arguments")
     if not apps:
-        raise Exception("No packages specified")
+        raise FDroidException("No packages specified")
 
     error = False
     for app in apps:
@@ -245,7 +277,7 @@ def read_app_args(args, allapps, allow_vercodes=False):
                     logging.critical("No such vercode %s for app %s" % (v, app['id']))
 
     if error:
-        raise Exception("Found invalid vercodes for some apps")
+        raise FDroidException("Found invalid vercodes for some apps")
 
     return apps
 
@@ -267,7 +299,7 @@ def apknameinfo(filename):
     try:
         result = (m.group(1), m.group(2))
     except AttributeError:
-        raise Exception("Invalid apk name: %s" % filename)
+        raise FDroidException("Invalid apk name: %s" % filename)
     return result
 
 
@@ -329,6 +361,7 @@ class vcs:
 
         self.remote = remote
         self.local = local
+        self.clone_failed = False
         self.refreshed = False
         self.srclib = None
 
@@ -344,6 +377,9 @@ class vcs:
     # the repo - otherwise it must specify a valid revision.
     def gotorevision(self, rev):
 
+        if self.clone_failed:
+            raise VCSException("Downloading the repository already failed once, not trying again.")
+
         # The .fdroidvcs-id file for a repo tells us what VCS type
         # and remote that directory was created from, allowing us to drop it
         # automatically if either of those things changes.
@@ -361,11 +397,12 @@ class vcs:
                 else:
                     deleterepo = True
                     logging.info(
-                        "Repository details for {0} changed - deleting"
-                        .format(self.local))
+                        "Repository details for %s changed - deleting" % (
+                            self.local))
             else:
                 deleterepo = True
-                logging.info("Repository details missing - deleting")
+                logging.info("Repository details for %s missing - deleting" % (
+                    self.local))
         if deleterepo:
             shutil.rmtree(self.local)
 
@@ -423,42 +460,49 @@ class vcs_git(vcs):
             # Brand new checkout
             p = FDroidPopen(['git', 'clone', self.remote, self.local])
             if p.returncode != 0:
-                raise VCSException("Git clone failed")
+                self.clone_failed = True
+                raise VCSException("Git clone failed", p.output)
             self.checkrepo()
         else:
             self.checkrepo()
             # Discard any working tree changes
             p = SilentPopen(['git', 'reset', '--hard'], cwd=self.local)
             if p.returncode != 0:
-                raise VCSException("Git reset failed")
+                raise VCSException("Git reset failed", p.output)
             # Remove untracked files now, in case they're tracked in the target
             # revision (it happens!)
             p = SilentPopen(['git', 'clean', '-dffx'], cwd=self.local)
             if p.returncode != 0:
-                raise VCSException("Git clean failed")
+                raise VCSException("Git clean failed", p.output)
             if not self.refreshed:
                 # Get latest commits and tags from remote
                 p = FDroidPopen(['git', 'fetch', 'origin'], cwd=self.local)
                 if p.returncode != 0:
-                    raise VCSException("Git fetch failed")
+                    raise VCSException("Git fetch failed", p.output)
                 p = SilentPopen(['git', 'fetch', '--prune', '--tags', 'origin'], cwd=self.local)
                 if p.returncode != 0:
-                    raise VCSException("Git fetch failed")
+                    raise VCSException("Git fetch failed", p.output)
                 # Recreate origin/HEAD as git clone would do it, in case it disappeared
                 p = SilentPopen(['git', 'remote', 'set-head', 'origin', '--auto'], cwd=self.local)
                 if p.returncode != 0:
-                    raise VCSException("Git remote set-head failed")
+                    lines = p.output.splitlines()
+                    if 'Multiple remote HEAD branches' not in lines[0]:
+                        raise VCSException("Git remote set-head failed", p.output)
+                    branch = lines[1].split(' ')[-1]
+                    p2 = SilentPopen(['git', 'remote', 'set-head', 'origin', branch], cwd=self.local)
+                    if p2.returncode != 0:
+                        raise VCSException("Git remote set-head failed", p.output + '\n' + p2.output)
                 self.refreshed = True
         # origin/HEAD is the HEAD of the remote, e.g. the "default branch" on
         # a github repo. Most of the time this is the same as origin/master.
-        rev = str(rev if rev else 'origin/HEAD')
+        rev = rev or 'origin/HEAD'
         p = SilentPopen(['git', 'checkout', '-f', rev], cwd=self.local)
         if p.returncode != 0:
-            raise VCSException("Git checkout of '%s' failed" % rev)
+            raise VCSException("Git checkout of '%s' failed" % rev, p.output)
         # Get rid of any uncontrolled files left behind
         p = SilentPopen(['git', 'clean', '-dffx'], cwd=self.local)
         if p.returncode != 0:
-            raise VCSException("Git clean failed")
+            raise VCSException("Git clean failed", p.output)
 
     def initsubmodules(self):
         self.checkrepo()
@@ -481,13 +525,13 @@ class vcs_git(vcs):
                 ]:
             p = SilentPopen(['git', 'submodule', 'foreach', '--recursive'] + cmd, cwd=self.local)
             if p.returncode != 0:
-                raise VCSException("Git submodule reset failed")
+                raise VCSException("Git submodule reset failed", p.output)
         p = FDroidPopen(['git', 'submodule', 'sync'], cwd=self.local)
         if p.returncode != 0:
-            raise VCSException("Git submodule sync failed")
+            raise VCSException("Git submodule sync failed", p.output)
         p = FDroidPopen(['git', 'submodule', 'update', '--init', '--force', '--recursive'], cwd=self.local)
         if p.returncode != 0:
-            raise VCSException("Git submodule update failed")
+            raise VCSException("Git submodule update failed", p.output)
 
     def gettags(self):
         self.checkrepo()
@@ -540,23 +584,25 @@ class vcs_gitsvn(vcs):
                         gitsvn_cmd += ' -b %s' % i[9:]
                 p = SilentPopen([gitsvn_cmd + " %s %s" % (remote_split[0], self.local)], shell=True)
                 if p.returncode != 0:
-                    raise VCSException("Git clone failed")
+                    self.clone_failed = True
+                    raise VCSException("Git clone failed", p.output)
             else:
                 p = SilentPopen([gitsvn_cmd + " %s %s" % (self.remote, self.local)], shell=True)
                 if p.returncode != 0:
-                    raise VCSException("Git clone failed")
+                    self.clone_failed = True
+                    raise VCSException("Git clone failed", p.output)
             self.checkrepo()
         else:
             self.checkrepo()
             # Discard any working tree changes
             p = SilentPopen(['git', 'reset', '--hard'], cwd=self.local)
             if p.returncode != 0:
-                raise VCSException("Git reset failed")
+                raise VCSException("Git reset failed", p.output)
             # Remove untracked files now, in case they're tracked in the target
             # revision (it happens!)
             p = SilentPopen(['git', 'clean', '-dffx'], cwd=self.local)
             if p.returncode != 0:
-                raise VCSException("Git clean failed")
+                raise VCSException("Git clean failed", p.output)
             if not self.refreshed:
                 # Get new commits, branches and tags from repo
                 p = SilentPopen(['%sgit svn fetch %s' % self.userargs()], cwd=self.local, shell=True)
@@ -564,10 +610,10 @@ class vcs_gitsvn(vcs):
                     raise VCSException("Git svn fetch failed")
                 p = SilentPopen(['%sgit svn rebase %s' % self.userargs()], cwd=self.local, shell=True)
                 if p.returncode != 0:
-                    raise VCSException("Git svn rebase failed")
+                    raise VCSException("Git svn rebase failed", p.output)
                 self.refreshed = True
 
-        rev = str(rev if rev else 'master')
+        rev = rev or 'master'
         if rev:
             nospaces_rev = rev.replace(' ', '%20')
             # Try finding a svn tag
@@ -576,34 +622,39 @@ class vcs_gitsvn(vcs):
                 # No tag found, normal svn rev translation
                 # Translate svn rev into git format
                 rev_split = rev.split('/')
-                if len(rev_split) > 1:
-                    treeish = 'origin/' + rev_split[0]
-                    svn_rev = rev_split[1]
 
-                else:
-                    # if no branch is specified, then assume trunk (ie. 'master'
-                    # branch):
-                    treeish = 'origin/master'
-                    svn_rev = rev
+                p = None
+                for treeish in ['origin/', '']:
+                    if len(rev_split) > 1:
+                        treeish += rev_split[0]
+                        svn_rev = rev_split[1]
 
-                p = SilentPopen(['git', 'svn', 'find-rev', 'r' + svn_rev, treeish], cwd=self.local)
-                git_rev = p.output.rstrip()
+                    else:
+                        # if no branch is specified, then assume trunk (i.e. 'master' branch):
+                        treeish += 'master'
+                        svn_rev = rev
+
+                    p = SilentPopen(['git', 'svn', 'find-rev', 'r' + svn_rev, treeish], cwd=self.local)
+                    git_rev = p.output.rstrip()
+
+                    if p.returncode == 0 and git_rev:
+                        break
 
                 if p.returncode != 0 or not git_rev:
                     # Try a plain git checkout as a last resort
                     p = SilentPopen(['git', 'checkout', rev], cwd=self.local)
                     if p.returncode != 0:
-                        raise VCSException("No git treeish found and direct git checkout of '%s' failed" % rev)
+                        raise VCSException("No git treeish found and direct git checkout of '%s' failed" % rev, p.output)
                 else:
                     # Check out the git rev equivalent to the svn rev
                     p = SilentPopen(['git', 'checkout', git_rev], cwd=self.local)
                     if p.returncode != 0:
-                        raise VCSException("Git svn checkout of '%s' failed" % rev)
+                        raise VCSException("Git svn checkout of '%s' failed" % rev, p.output)
 
         # Get rid of any uncontrolled files left behind
         p = SilentPopen(['git', 'clean', '-dffx'], cwd=self.local)
         if p.returncode != 0:
-            raise VCSException("Git clean failed")
+            raise VCSException("Git clean failed", p.output)
 
     def gettags(self):
         self.checkrepo()
@@ -633,24 +684,25 @@ class vcs_svn(vcs):
         if not os.path.exists(self.local):
             p = SilentPopen(['svn', 'checkout', self.remote, self.local] + self.userargs())
             if p.returncode != 0:
-                raise VCSException("Svn checkout of '%s' failed" % rev)
+                self.clone_failed = True
+                raise VCSException("Svn checkout of '%s' failed" % rev, p.output)
         else:
             for svncommand in (
                     'svn revert -R .',
                     r"svn status | awk '/\?/ {print $2}' | xargs rm -rf"):
                 p = SilentPopen([svncommand], cwd=self.local, shell=True)
                 if p.returncode != 0:
-                    raise VCSException("Svn reset ({0}) failed in {1}".format(svncommand, self.local))
+                    raise VCSException("Svn reset ({0}) failed in {1}".format(svncommand, self.local), p.output)
             if not self.refreshed:
                 p = SilentPopen(['svn', 'update'] + self.userargs(), cwd=self.local)
                 if p.returncode != 0:
-                    raise VCSException("Svn update failed")
+                    raise VCSException("Svn update failed", p.output)
                 self.refreshed = True
 
         revargs = list(['-r', rev] if rev else [])
         p = SilentPopen(['svn', 'update', '--force'] + revargs + self.userargs(), cwd=self.local)
         if p.returncode != 0:
-            raise VCSException("Svn update failed")
+            raise VCSException("Svn update failed", p.output)
 
     def getref(self):
         p = SilentPopen(['svn', 'info'], cwd=self.local)
@@ -669,23 +721,24 @@ class vcs_hg(vcs):
         if not os.path.exists(self.local):
             p = SilentPopen(['hg', 'clone', self.remote, self.local])
             if p.returncode != 0:
-                raise VCSException("Hg clone failed")
+                self.clone_failed = True
+                raise VCSException("Hg clone failed", p.output)
         else:
             p = SilentPopen(['hg status -uS | xargs rm -rf'], cwd=self.local, shell=True)
             if p.returncode != 0:
-                raise VCSException("Hg clean failed")
+                raise VCSException("Hg clean failed", p.output)
             if not self.refreshed:
                 p = SilentPopen(['hg', 'pull'], cwd=self.local)
                 if p.returncode != 0:
-                    raise VCSException("Hg pull failed")
+                    raise VCSException("Hg pull failed", p.output)
                 self.refreshed = True
 
-        rev = str(rev if rev else 'default')
+        rev = rev or 'default'
         if not rev:
             return
         p = SilentPopen(['hg', 'update', '-C', rev], cwd=self.local)
         if p.returncode != 0:
-            raise VCSException("Hg checkout of '%s' failed" % rev)
+            raise VCSException("Hg checkout of '%s' failed" % rev, p.output)
         p = SilentPopen(['hg', 'purge', '--all'], cwd=self.local)
         # Also delete untracked files, we have to enable purge extension for that:
         if "'purge' is provided by the following extension" in p.output:
@@ -693,9 +746,9 @@ class vcs_hg(vcs):
                 myfile.write("\n[extensions]\nhgext.purge=\n")
             p = SilentPopen(['hg', 'purge', '--all'], cwd=self.local)
             if p.returncode != 0:
-                raise VCSException("HG purge failed")
+                raise VCSException("HG purge failed", p.output)
         elif p.returncode != 0:
-            raise VCSException("HG purge failed")
+            raise VCSException("HG purge failed", p.output)
 
     def gettags(self):
         p = SilentPopen(['hg', 'tags', '-q'], cwd=self.local)
@@ -711,21 +764,22 @@ class vcs_bzr(vcs):
         if not os.path.exists(self.local):
             p = SilentPopen(['bzr', 'branch', self.remote, self.local])
             if p.returncode != 0:
-                raise VCSException("Bzr branch failed")
+                self.clone_failed = True
+                raise VCSException("Bzr branch failed", p.output)
         else:
             p = SilentPopen(['bzr', 'clean-tree', '--force', '--unknown', '--ignored'], cwd=self.local)
             if p.returncode != 0:
-                raise VCSException("Bzr revert failed")
+                raise VCSException("Bzr revert failed", p.output)
             if not self.refreshed:
                 p = SilentPopen(['bzr', 'pull'], cwd=self.local)
                 if p.returncode != 0:
-                    raise VCSException("Bzr update failed")
+                    raise VCSException("Bzr update failed", p.output)
                 self.refreshed = True
 
         revargs = list(['-r', rev] if rev else [])
         p = SilentPopen(['bzr', 'revert'] + revargs, cwd=self.local)
         if p.returncode != 0:
-            raise VCSException("Bzr revert of '%s' failed" % rev)
+            raise VCSException("Bzr revert of '%s' failed" % rev, p.output)
 
     def gettags(self):
         p = SilentPopen(['bzr', 'tags'], cwd=self.local)
@@ -829,7 +883,7 @@ def get_library_references(root_dir):
             relpath = os.path.join(root_dir, path)
             if not os.path.isdir(relpath):
                 continue
-            logging.info("Found subproject at %s" % path)
+            logging.debug("Found subproject at %s" % path)
             libraries.append(path)
     return libraries
 
@@ -847,7 +901,7 @@ def ant_subprojects(root_dir):
 
 def remove_debuggable_flags(root_dir):
     # Remove forced debuggable flags
-    logging.info("Removing debuggable flags")
+    logging.debug("Removing debuggable flags from %s" % root_dir)
     for root, dirs, files in os.walk(root_dir):
         if 'AndroidManifest.xml' in files:
             path = os.path.join(root, 'AndroidManifest.xml')
@@ -933,7 +987,7 @@ def parse_androidmanifests(paths, ignoreversions=None):
     return (max_version, max_vercode, max_package)
 
 
-class BuildException(Exception):
+class FDroidException(Exception):
     def __init__(self, value, detail=None):
         self.value = value
         self.detail = detail
@@ -955,12 +1009,12 @@ class BuildException(Exception):
         return ret
 
 
-class VCSException(Exception):
-    def __init__(self, value):
-        self.value = value
+class VCSException(FDroidException):
+    pass
 
-    def __str__(self):
-        return self.value
+
+class BuildException(FDroidException):
+    pass
 
 
 # Get the specified source library.
@@ -983,7 +1037,7 @@ def getsrclib(spec, srclib_dir, srclibpaths=[], subdir=None,
             name, subdir = name.split('/', 1)
 
     if name not in metadata.srclibs:
-        raise BuildException('srclib ' + name + ' not found.')
+        raise VCSException('srclib ' + name + ' not found.')
 
     srclib = metadata.srclibs[name]
 
@@ -1020,7 +1074,7 @@ def getsrclib(spec, srclib_dir, srclibpaths=[], subdir=None,
                     s_tuple = t
                     break
             if s_tuple is None:
-                raise BuildException('Missing recursive srclib %s for %s' % (
+                raise VCSException('Missing recursive srclib %s for %s' % (
                     lib, name))
             place_srclib(libdir, n, s_tuple[2])
             n += 1
@@ -1196,13 +1250,6 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
                          's@compileSdkVersion *[0-9]*@compileSdkVersion ' + n + '@g',
                          'build.gradle'],
                         cwd=root_dir)
-            if '@' in build['gradle']:
-                gradle_dir = os.path.join(root_dir, build['gradle'].split('@', 1)[1])
-                gradle_dir = os.path.normpath(gradle_dir)
-                FDroidPopen(['sed', '-i',
-                             's@compileSdkVersion *[0-9]*@compileSdkVersion ' + n + '@g',
-                             'build.gradle'],
-                            cwd=gradle_dir)
 
     # Remove forced debuggable flags
     remove_debuggable_flags(root_dir)
@@ -1296,9 +1343,8 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
 
     # Generate (or update) the ant build file, build.xml...
     if build['update'] and build['update'] != ['no'] and build['type'] == 'ant':
-        parms = [os.path.join(config['sdk_path'], 'tools', 'android'), 'update']
-        lparms = parms + ['lib-project']
-        parms = parms + ['project']
+        parms = [config['android'], 'update', 'lib-project']
+        lparms = [config['android'], 'update', 'project']
 
         if build['target']:
             parms += ['-t', build['target']]
@@ -1311,10 +1357,10 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
         for d in update_dirs:
             subdir = os.path.join(root_dir, d)
             if d == '.':
-                print("Updating main project")
+                logging.debug("Updating main project")
                 cmd = parms + ['-p', d]
             else:
-                print("Updating subproject %s" % d)
+                logging.debug("Updating subproject %s" % d)
                 cmd = lparms + ['-p', d]
             p = FDroidPopen(cmd, cwd=root_dir)
             # Check to see whether an error was returned without a proper exit
@@ -1601,13 +1647,15 @@ def FDroidPopen(commands, cwd=None, shell=False, output=True):
     :returns: A PopenResult.
     """
 
+    global env
+
     if cwd:
         cwd = os.path.normpath(cwd)
         logging.debug("Directory: %s" % cwd)
     logging.debug("> %s" % ' '.join(commands))
 
     result = PopenResult()
-    p = subprocess.Popen(commands, cwd=cwd, shell=shell,
+    p = subprocess.Popen(commands, cwd=cwd, shell=shell, env=env,
                          stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
 
     stdout_queue = Queue.Queue()
@@ -1618,10 +1666,10 @@ def FDroidPopen(commands, cwd=None, shell=False, output=True):
     while not stdout_reader.eof():
         while not stdout_queue.empty():
             line = stdout_queue.get()
-            if output:
+            if output and options.verbose:
                 # Output directly to console
-                sys.stdout.write(line)
-                sys.stdout.flush()
+                sys.stderr.write(line)
+                sys.stderr.flush()
             result.output += line
 
         time.sleep(0.1)