chiark / gitweb /
makebuildserver: add ndk r16
[fdroidserver.git] / fdroidserver / common.py
index 7e76513d84fb0a7a9df4dc0f9c92a4a82f5a0293..89c1a1d5ac84414f353f62f53d61a0a8eb04af4f 100644 (file)
@@ -80,6 +80,7 @@ default_config = {
         'r13b': None,
         'r14b': None,
         'r15c': None,
+        'r16': None,
     },
     'qt_sdk_path': None,
     'build_tools': "25.0.2",
@@ -519,7 +520,7 @@ def get_extension(filename):
 
 
 def has_extension(filename, ext):
-    _, f_ext = get_extension(filename)
+    _ignored, f_ext = get_extension(filename)
     return ext == f_ext
 
 
@@ -623,7 +624,6 @@ def setup_vcs(app):
     else:
         remote = app.Repo
     vcs = getvcs(app.RepoType, remote, build_dir)
-    logging.debug("Using %s" % vcs.clientversion())
 
     return vcs, build_dir
 
@@ -783,6 +783,37 @@ class vcs_git(vcs):
     def clientversioncmd(self):
         return ['git', '--version']
 
+    def GitFetchFDroidPopen(self, gitargs, envs=dict(), cwd=None, output=True):
+        '''Prevent git fetch/clone/submodule from hanging at the username/password prompt
+
+        While fetch/pull/clone respect the command line option flags,
+        it seems that submodule commands do not.  They do seem to
+        follow whatever is in env vars, if the version of git is new
+        enough.  So we just throw the kitchen sink at it to see what
+        sticks.
+
+        '''
+        if cwd is None:
+            cwd = self.local
+        git_config = []
+        for domain in ('bitbucket.org', 'github.com', 'gitlab.com'):
+            git_config.append('-c')
+            git_config.append('url.https://u:p@' + domain + '/.insteadOf=git@' + domain + ':')
+            git_config.append('-c')
+            git_config.append('url.https://u:p@' + domain + '.insteadOf=git://' + domain)
+            git_config.append('-c')
+            git_config.append('url.https://u:p@' + domain + '.insteadOf=https://' + domain)
+        # add helpful tricks supported in git >= 2.3
+        ssh_command = 'ssh -oBatchMode=yes -oStrictHostKeyChecking=yes'
+        git_config.append('-c')
+        git_config.append('core.sshCommand="' + ssh_command + '"')  # git >= 2.10
+        envs.update({
+            'GIT_TERMINAL_PROMPT': '0',
+            'GIT_SSH_COMMAND': ssh_command,  # git >= 2.3
+        })
+        return FDroidPopen(['git', ] + git_config + gitargs,
+                           envs=envs, cwd=cwd, output=output)
+
     def checkrepo(self):
         """If the local directory exists, but is somehow not a git repository,
         git will traverse up the directory tree until it finds one
@@ -799,7 +830,7 @@ class vcs_git(vcs):
     def gotorevisionx(self, rev):
         if not os.path.exists(self.local):
             # Brand new checkout
-            p = FDroidPopen(['git', 'clone', self.remote, self.local])
+            p = FDroidPopen(['git', 'clone', self.remote, self.local], cwd=None)
             if p.returncode != 0:
                 self.clone_failed = True
                 raise VCSException("Git clone failed", p.output)
@@ -819,10 +850,10 @@ class vcs_git(vcs):
                 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)
+                p = self.GitFetchFDroidPopen(['fetch', 'origin'])
                 if p.returncode != 0:
                     raise VCSException(_("Git fetch failed"), p.output)
-                p = FDroidPopen(['git', 'fetch', '--prune', '--tags', 'origin'], cwd=self.local, output=False)
+                p = self.GitFetchFDroidPopen(['fetch', '--prune', '--tags', 'origin'], output=False)
                 if p.returncode != 0:
                     raise VCSException(_("Git fetch failed"), p.output)
                 # Recreate origin/HEAD as git clone would do it, in case it disappeared
@@ -858,16 +889,14 @@ class vcs_git(vcs):
             lines = f.readlines()
         with open(submfile, 'w') as f:
             for line in lines:
-                if 'git@github.com' in line:
-                    line = line.replace('git@github.com:', 'https://github.com/')
-                if 'git@gitlab.com' in line:
-                    line = line.replace('git@gitlab.com:', 'https://gitlab.com/')
+                for domain in ('bitbucket.org', 'github.com', 'gitlab.com'):
+                    line = re.sub('git@' + domain + ':', 'https://u:p@' + domain + '/', line)
                 f.write(line)
 
         p = FDroidPopen(['git', 'submodule', 'sync'], cwd=self.local, output=False)
         if p.returncode != 0:
             raise VCSException(_("Git submodule sync failed"), p.output)
-        p = FDroidPopen(['git', 'submodule', 'update', '--init', '--force', '--recursive'], cwd=self.local)
+        p = self.GitFetchFDroidPopen(['submodule', 'update', '--init', '--force', '--recursive'])
         if p.returncode != 0:
             raise VCSException(_("Git submodule update failed"), p.output)
 
@@ -1584,10 +1613,11 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
             dest = os.path.join(build_dir, part)
             logging.info("Removing {0}".format(part))
             if os.path.lexists(dest):
-                if os.path.islink(dest):
-                    FDroidPopen(['unlink', dest], output=False)
+                # rmtree can only handle directories that are not symlinks, so catch anything else
+                if not os.path.isdir(dest) or os.path.islink(dest):
+                    os.remove(dest)
                 else:
-                    FDroidPopen(['rm', '-rf', dest], output=False)
+                    shutil.rmtree(dest)
             else:
                 logging.info("...but it didn't exist")
 
@@ -1745,7 +1775,7 @@ class KnownApks:
                 default_date = datetime.utcnow()
             self.apks[apkName] = (app, default_date)
             self.changed = True
-        _, added = self.apks[apkName]
+        _ignored, added = self.apks[apkName]
         return added
 
     def getapp(self, apkname):
@@ -2251,15 +2281,15 @@ def apk_strip_signatures(signed_apk, strip_manifest=False):
         os.rename(signed_apk, tmp_apk)
         with ZipFile(tmp_apk, 'r') as in_apk:
             with ZipFile(signed_apk, 'w') as out_apk:
-                for f in in_apk.infolist():
-                    if not apk_sigfile.match(f.filename):
+                for info in in_apk.infolist():
+                    if not apk_sigfile.match(info.filename):
                         if strip_manifest:
-                            if f.filename != 'META-INF/MANIFEST.MF':
-                                buf = in_apk.read(f.filename)
-                                out_apk.writestr(f.filename, buf)
+                            if info.filename != 'META-INF/MANIFEST.MF':
+                                buf = in_apk.read(info.filename)
+                                out_apk.writestr(info, buf)
                         else:
-                            buf = in_apk.read(f.filename)
-                            out_apk.writestr(f.filename, buf)
+                            buf = in_apk.read(info.filename)
+                            out_apk.writestr(info, buf)
 
 
 def apk_implant_signatures(apkpath, signaturefile, signedfile, manifest):
@@ -2272,19 +2302,21 @@ def apk_implant_signatures(apkpath, signaturefile, signedfile, manifest):
     """
     # get list of available signature files in metadata
     with tempfile.TemporaryDirectory() as tmpdir:
-        # orig_apk = os.path.join(tmpdir, 'orig.apk')
-        # os.rename(apkpath, orig_apk)
         apkwithnewsig = os.path.join(tmpdir, 'newsig.apk')
         with ZipFile(apkpath, 'r') as in_apk:
             with ZipFile(apkwithnewsig, 'w') as out_apk:
                 for sig_file in [signaturefile, signedfile, manifest]:
-                    out_apk.write(sig_file, arcname='META-INF/' +
-                                  os.path.basename(sig_file))
-                for f in in_apk.infolist():
-                    if not apk_sigfile.match(f.filename):
-                        if f.filename != 'META-INF/MANIFEST.MF':
-                            buf = in_apk.read(f.filename)
-                            out_apk.writestr(f.filename, buf)
+                    with open(sig_file, 'rb') as fp:
+                        buf = fp.read()
+                    info = zipfile.ZipInfo('META-INF/' + os.path.basename(sig_file))
+                    info.compress_type = zipfile.ZIP_DEFLATED
+                    info.create_system = 0  # "Windows" aka "FAT", what Android SDK uses
+                    out_apk.writestr(info, buf)
+                for info in in_apk.infolist():
+                    if not apk_sigfile.match(info.filename):
+                        if info.filename != 'META-INF/MANIFEST.MF':
+                            buf = in_apk.read(info.filename)
+                            out_apk.writestr(info, buf)
         os.remove(apkpath)
         p = SdkToolsPopen(['zipalign', '-v', '4', apkwithnewsig, apkpath])
         if p.returncode != 0:
@@ -2716,6 +2748,28 @@ def string_is_integer(string):
         return False
 
 
+def local_rsync(options, fromdir, todir):
+    '''Rsync method for local to local copying of things
+
+    This is an rsync wrapper with all the settings for safe use within
+    the various fdroidserver use cases. This uses stricter rsync
+    checking on all files since people using offline mode are already
+    prioritizing security above ease and speed.
+
+    '''
+    rsyncargs = ['rsync', '--recursive', '--safe-links', '--times', '--perms',
+                 '--one-file-system', '--delete', '--chmod=Da+rx,Fa-x,a+r,u+w']
+    if not options.no_checksum:
+        rsyncargs.append('--checksum')
+    if options.verbose:
+        rsyncargs += ['--verbose']
+    if options.quiet:
+        rsyncargs += ['--quiet']
+    logging.debug(' '.join(rsyncargs + [fromdir, todir]))
+    if subprocess.call(rsyncargs + [fromdir, todir]) != 0:
+        raise FDroidException()
+
+
 def get_per_app_repos():
     '''per-app repos are dirs named with the packageName of a single app'''
 
@@ -2755,3 +2809,26 @@ def is_repo_file(filename):
             b'index-v1.json',
             b'categories.txt',
         ]
+
+
+def get_examples_dir():
+    '''Return the dir where the fdroidserver example files are available'''
+    examplesdir = None
+    tmp = os.path.dirname(sys.argv[0])
+    if os.path.basename(tmp) == 'bin':
+        egg_links = glob.glob(os.path.join(tmp, '..',
+                                           'local/lib/python3.*/site-packages/fdroidserver.egg-link'))
+        if egg_links:
+            # installed from local git repo
+            examplesdir = os.path.join(open(egg_links[0]).readline().rstrip(), 'examples')
+        else:
+            # try .egg layout
+            examplesdir = os.path.dirname(os.path.dirname(__file__)) + '/share/doc/fdroidserver/examples'
+            if not os.path.exists(examplesdir):  # use UNIX layout
+                examplesdir = os.path.dirname(tmp) + '/share/doc/fdroidserver/examples'
+    else:
+        # we're running straight out of the git repo
+        prefix = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
+        examplesdir = prefix + '/examples'
+
+    return examplesdir