[PATCH] Bundlebuilder to use MANIFEST, fix_manifest function, some

chema none chema
Fri Jun 6 12:01:21 EDT 2008


functions
pushed upstream to bundle.py and activitybundle.py
---
 src/sugar/activity/bundlebuilder.py |   69 ++++++++++--------
 src/sugar/bundle/activitybundle.py  |  140
++++++++++++++++++++++++++++++++---
 src/sugar/bundle/bundle.py          |   23 ++++++
 3 files changed, 189 insertions(+), 43 deletions(-)

diff --git a/src/sugar/activity/bundlebuilder.py
b/src/sugar/activity/bundlebuilder.py
index 1063f72..72c246c 100644
--- a/src/sugar/activity/bundlebuilder.py
+++ b/src/sugar/activity/bundlebuilder.py
@@ -19,28 +19,16 @@ import os
 import zipfile
 import tarfile
 import shutil
-import subprocess
+from subprocess import Popen, PIPE
 import re
 import gettext
 from optparse import OptionParser
+import warnings
+import subprocess

 from sugar import env
-from sugar.bundle.activitybundle import ActivityBundle
-
-def list_files(base_dir, ignore_dirs=None, ignore_files=None):
-    result = []
+from sugar.bundle.activitybundle import ActivityBundle,MANIFEST,list_files

-    for root, dirs, files in os.walk(base_dir):
-        for f in files:
-            if ignore_files and f not in ignore_files:
-                rel_path = root[len(base_dir) + 1:]
-                result.append(os.path.join(rel_path, f))
-        if ignore_dirs and root == base_dir:
-            for ignore in ignore_dirs:
-                if ignore in dirs:
-                    dirs.remove(ignore)
-
-    return result

 class Config(object):
     def __init__(self, bundle_name, source_dir=None):
@@ -53,6 +41,7 @@ class Config(object):
         bundle = ActivityBundle(self.source_dir)
         version = bundle.get_activity_version()

+        self.bundle = bundle
         self.bundle_name = bundle_name
         self.xo_name = '%s-%d.xo' % (self.bundle_name, version)
         self.tarball_name = '%s-%d.tar.bz2' % (self.bundle_name, version)
@@ -103,9 +92,9 @@ class Builder(object):
             f.close()

 class Packager(object):
-    def __init__(self, config):
+    def __init__(self, config, dist_dir = None):
         self.config = config
-        self.dist_dir = os.path.join(self.config.source_dir, 'dist')
+        self.dist_dir = dist_dir or os.path.join(self.config.source_dir,
'dist')
         self.package_path = None

         if not os.path.exists(self.dist_dir):
@@ -113,19 +102,28 @@ class Packager(object):


 class BuildPackager(Packager):
-    def __init__(self, config):
-        Packager.__init__(self, config)
+    def __init__(self, config,dist_dir = None):
+        Packager.__init__(self, config,dist_dir )
         self.build_dir = self.config.source_dir

     def get_files(self):
-        return list_files(self.build_dir,
-                          ignore_dirs=['po', 'dist', '.git'],
-                          ignore_files=['.gitignore'])
+        return self.config.bundle.get_files()
+
+    def fix_manifest(self):
+        allfiles =  list_files(self.build_dir,
+                          ignore_dirs=['dist', '.git'],
+                          ignore_files=['.gitignore', 'MANIFEST', '*.pyc',
'*~', '*.bak'])
+        for file in allfiles:
+            if file not in self.config.bundle.manifest:
+                self.config.bundle.manifest.append(file)
+        manifestfile =
open(os.path.join(self.config.source_dir,MANIFEST),"wb")
+        for line in self.config.bundle.manifest:
+            manifestfile.write(line + "\n")

 class XOPackager(BuildPackager):
-    def __init__(self, config):
+    def __init__(self, config,dist_dir = None,dist_name = None):
         BuildPackager.__init__(self, config)
-        self.package_path = os.path.join(self.dist_dir,
self.config.xo_name)
+        self.package_path = os.path.join(self.dist_dir, dist_name or
self.config.xo_name)

     def package(self):
         bundle_zip = zipfile.ZipFile(self.package_path, 'w',
@@ -137,16 +135,25 @@ class XOPackager(BuildPackager):

         bundle_zip.close()

-class SourcePackager(Packager):
-    def __init__(self, config):
-        Packager.__init__(self, config)
+class SourcePackager(BuildPackager):
+    def __init__(self, config,dist_dir = None):
+        BuildPackager.__init__(self, config,dist_dir)
         self.package_path = os.path.join(self.dist_dir,
                                          self.config.tarball_name)

     def get_files(self):
-        return list_files(self.config.source_dir,
-                          ignore_dirs=['locale', 'dist', '.git'],
-                          ignore_files=['.gitignore'])
+        git_ls =
Popen('git-ls-files',stdout=PIPE,cwd=self.config.source_dir)
+        if git_ls.wait():
+            #non-0 return code - failed
+            return Packager.get_files(self)
+        f = git_ls.stdout
+        files = []
+        for line in f.readlines():
+            filename = line.strip()
+            if not filename.startswith('.'):
+                files.append(filename)
+        f.close()
+        return files

     def package(self):
         tar = tarfile.open(self.package_path, "w")
diff --git a/src/sugar/bundle/activitybundle.py
b/src/sugar/bundle/activitybundle.py
index db30555..1ff7d39 100644
--- a/src/sugar/bundle/activitybundle.py
+++ b/src/sugar/bundle/activitybundle.py
@@ -21,15 +21,31 @@ from ConfigParser import ConfigParser
 import locale
 import os
 import tempfile
+from fnmatch import fnmatch

 from sugar.bundle.bundle import Bundle, MalformedBundleException, \
     AlreadyInstalledException, RegistrationException, \
     NotInstalledException

-from sugar import activity
-from sugar import env
-
 import logging
+import warnings
+
+MANIFEST = "MANIFEST"
+
+def list_files(base_dir, ignore_dirs=None, ignore_files=None):
+    result = []
+
+    for root, dirs, files in os.walk(base_dir):
+        for f in files:
+            if not (ignore_files and [True for pat in ignore_files if
fnmatch(f,pat)]):
+                #result matches a pattern in ignore_files, ignore it
+                rel_path = root[len(base_dir) + 1:]
+                result.append(os.path.join(rel_path, f))
+        if ignore_dirs and root == base_dir:
+            for ignore in ignore_dirs:
+                if ignore in dirs:
+                    dirs.remove(ignore)
+    return result

 class ActivityBundle(Bundle):
     """A Sugar activity bundle
@@ -64,7 +80,62 @@ class ActivityBundle(Bundle):
         linfo_file = self._get_linfo_file()
         if linfo_file:
             self._parse_linfo(linfo_file)
-
+
+        self.read_manifest()
+
+    def _raw_manifest(self):
+        try:
+            f = self._get_file(MANIFEST)
+        except IOError:
+            f = none
+        if not f:
+            warnings.warn(MalformedBundleException("Activity directory
lacks a MANIFEST file."))
+            return []
+        ret = map(lambda x:x.strip(),f.readlines()) #strip trailing \n and
other whitespace
+        f.close()
+        return ret
+
+    def read_manifest(self):
+        """read_manifest: sets self.manifest to list of lines in MANIFEST,
+        with invalid lines replaced by empty lines.
+
+        Since absolute order carries information on file history, it should
be preserved.
+        For instance, when renaming a file, you should leave the new name
on the same line
+        as the old one.
+        """
+        manifestlines = self._raw_manifest()
+        for num,line in enumerate(manifestlines):
+            if line:
+                if line in manifestlines[0:num]:
+                    manifestlines[num] = ""
+                    warnings.warn(MalformedBundleException("Bundle %s:
duplicate entry in MANIFEST: %s"
+                                                  %(self._name,line)))
+                    continue
+                if line == MANIFEST:
+                    manifestlines[num] = ""
+                    warnings.warn(MalformedBundleException("Bundle %s:
MANIFEST includes itself: %s"
+                                                  %(self._name,line)))
+                if line.endswith("/"):
+                    if not self._is_dir(line):
+                        manifestlines[num] = ""
+                        warnings.warn(MalformedBundleException("Bundle %s:
invalid dir entry in MANIFEST: %s"
+                                                      %(self._name,line)))
+                else:
+                    if not self._is_file(line):
+                        manifestlines[num] = ""
+                        warnings.warn(MalformedBundleException("Bundle %s:
invalid entry in MANIFEST: %s"
+                                                      %(self._name,line)))
+        #remove trailing newlines - unlike internal newlines, they do not
help keep absolute position
+        while manifestlines[-1]=="":
+            manifestlines = manifestlines[:-1]
+        self.manifest = manifestlines
+
+    def get_files(self,manifest = None):
+        return [MANIFEST] + filter(lambda line: line and not
line.endswith("/"),manifest or self.manifest)
+
+    def get_dirs(self):
+        return filter(lambda line: line and
line.endswith("/"),self.manifest)
+
     def _parse_info(self, info_file):
         cp = ConfigParser()
         cp.readfp(info_file)
@@ -116,6 +187,7 @@ class ActivityBundle(Bundle):
                 raise MalformedBundleException(
                     'Activity bundle %s has invalid version number %s' %
                     (self._path, version))
+

     def _get_linfo_file(self):
         lang = locale.getdefaultlocale()[0]
@@ -209,28 +281,53 @@ class ActivityBundle(Bundle):
         return self._show_launcher

     def is_installed(self):
+        from sugar import activity
+
         if activity.get_registry().get_activity(self._bundle_id):
             return True
         else:
             return False

     def need_upgrade(self):
+        from sugar import activity
+
         act = activity.get_registry().get_activity(self._bundle_id)
         if act is None or act.version != self._activity_version:
             return True
         else:
             return False
-
-    def install(self):
-        activities_path = env.get_user_activities_path()
-        act = activity.get_registry().get_activity(self._bundle_id)
-        if act is not None and act.path.startswith(activities_path):
-            raise AlreadyInstalledException
-
-        install_dir = env.get_user_activities_path()
+
+    def unpack(self,install_dir,strict_manifest=False):
         self._unzip(install_dir)

         install_path = os.path.join(install_dir, self._zip_root_dir)
+
+        #check installed files against the MANIFEST
+        manifestfiles = self.get_files(self._raw_manifest())
+        for file in list_files(install_path):
+            if file in manifestfiles:
+                manifestfiles.remove(file)
+            elif file != MANIFEST:
+                #warnings.warn(MalformedBundleException("Bundle %s: %s not
in MANIFEST"%
+                #                                       (self._name,file)))
+                if strict_manifest:
+                    os.remove(os.path.join(install_path,file))
+        if manifestfiles:
+            err = MalformedBundleException("Bundle %s: files in MANIFEST
not included: %s"%
+
(self._name,str(manifestfiles)))
+            if strict_manifest:
+                raise err
+            else:
+                warnings.warn(err)
+
+        #create empty directories
+        for dir in self.get_dirs():
+            dirpath = os.path.join(install_path,dir)
+            if os.path.isdir(dirpath):
+                warnings.warn(MalformedBundleException("Bunldle %s:
non-empty dir %s in MANIFEST"%
+
(self._name,dirpath)))
+            else:
+                os.makedirs(os.path.join(install_path,dir))

         xdg_data_home = os.getenv('XDG_DATA_HOME',
                                   os.path.expanduser('~/.local/share'))
@@ -267,11 +364,27 @@ class ActivityBundle(Bundle):
                     os.symlink(info_file,
                                os.path.join(installed_icons_dir,
                                             os.path.basename(info_file)))
+        return install_path

+    def install(self):
+        from sugar import activity
+        from sugar import env
+
+        activities_path = env.get_user_activities_path()
+        act = activity.get_registry().get_activity(self._bundle_id)
+        if act is not None and act.path.startswith(activities_path):
+            raise AlreadyInstalledException
+
+        install_dir = env.get_user_activities_path()
+        install_path = self.unpack(install_dir)
+
         if not activity.get_registry().add_bundle(install_path):
             raise RegistrationException

     def uninstall(self, force=False):
+        from sugar import activity
+        from sugar import env
+
         if self._unpacked:
             install_path = self._path
         else:
@@ -316,6 +429,9 @@ class ActivityBundle(Bundle):
             raise RegistrationException

     def upgrade(self):
+        from sugar import activity
+        from sugar import env
+
         act = activity.get_registry().get_activity(self._bundle_id)
         if act is None:
             logging.warning('Activity not installed')
diff --git a/src/sugar/bundle/bundle.py b/src/sugar/bundle/bundle.py
index 47d08b2..44e30b0 100644
--- a/src/sugar/bundle/bundle.py
+++ b/src/sugar/bundle/bundle.py
@@ -114,6 +114,29 @@ class Bundle:
             zip_file.close()

         return f
+
+    def _is_dir(self, filename):
+        if self._unpacked:
+            path = os.path.join(self._path, filename)
+            return os.path.isdir(path)
+        else:
+            return True #zip files contain all dirs you care about!
+
+    def _is_file(self, filename):
+        if self._unpacked:
+            path = os.path.join(self._path, filename)
+            return os.path.isfile(path)
+        else:
+            zip_file = zipfile.ZipFile(self._path)
+            path = os.path.join(self._zip_root_dir, filename)
+            try:
+                data = zip_file.getinfo(path)
+                return True
+            except KeyError:
+                return False
+            finally:
+                zip_file.close()
+

     def get_path(self):
         """Get the bundle path."""
-- 
1.5.2.5

------=_Part_21853_28463409.1212768784608
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

Note: this patch does not expose the fix_manifest function when running bun=
dlebuilder as a script. That would be a 2 or 3 line patch, seperately.<br><=
br>From 9ab619888a9d4ef40ca3e984d4485c5a22558105 Mon Sep 17 00:00:00 2001<b=
r>
From: chema &lt;chema at chema-laptop.(none)&gt;<br>Date: Fri, 6 Jun 2008 10:0=
1:21 -0600<br>Subject: [PATCH] Bundlebuilder to use MANIFEST, fix_manifest =
function, some functions<br>pushed upstream to bundle.py and activitybundle=
.py<br>
---<br>&nbsp;src/sugar/activity/bundlebuilder.py |&nbsp;&nbsp; 69 +++++++++=
+--------<br>&nbsp;src/sugar/bundle/activitybundle.py&nbsp; |&nbsp; 140 +++=
+++++++++++++++++++++++++++++---<br>&nbsp;src/sugar/bundle/bundle.py&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; 23 ++++++<br>=
&nbsp;3 files changed, 189 insertions(+), 43 deletions(-)<br>
<br>diff --git a/src/sugar/activity/bundlebuilder.py b/src/sugar/activity/b=
undlebuilder.py<br>index 1063f72..72c246c 100644<br>--- a/src/sugar/activit=
y/bundlebuilder.py<br>+++ b/src/sugar/activity/bundlebuilder.py<br>@@ -19,2=
8 +19,16 @@ import os<br>
&nbsp;import zipfile<br>&nbsp;import tarfile<br>&nbsp;import shutil<br>-imp=
ort subprocess<br>+from subprocess import Popen, PIPE<br>&nbsp;import re<br=
>&nbsp;import gettext<br>&nbsp;from optparse import OptionParser<br>+import=
 warnings<br>+import subprocess<br>
&nbsp;<br>&nbsp;from sugar import env<br>-from sugar.bundle.activitybundle =
import ActivityBundle<br>-<br>-def list_files(base_dir, ignore_dirs=3DNone,=
 ignore_files=3DNone):<br>-&nbsp;&nbsp;&nbsp; result =3D []<br>+from sugar.=
bundle.activitybundle import ActivityBundle,MANIFEST,list_files<br>
&nbsp;<br>-&nbsp;&nbsp;&nbsp; for root, dirs, files in os.walk(base_dir):<b=
r>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for f in files:<br>-&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ignore_files a=
nd f not in ignore_files:<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rel_path =3D root[len(base_di=
r) + 1:]<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp; result.append(os.path.join(rel_path, f))<br>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ignore_dirs and root =3D=3D =
base_dir:<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp; for ignore in ignore_dirs:<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ignore in dirs:<br>=
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dirs.remove(ignore)<br>-<br>-&nbsp=
;&nbsp;&nbsp; return result<br>&nbsp;<br>&nbsp;class Config(object):<br>
&nbsp;&nbsp;&nbsp;&nbsp; def __init__(self, bundle_name, source_dir=3DNone)=
:<br>@@ -53,6 +41,7 @@ class Config(object):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp; bundle =3D ActivityBundle(self.source_dir)<br>&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; version =3D bundle.get_activity_v=
ersion()<br>&nbsp;<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.bund=
le =3D bundle<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.bundle_name =3D bundl=
e_name<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.xo_name =3D=
 &#39;%s-%d.xo&#39; % (self.bundle_name, version)<br>&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp; self.tarball_name =3D &#39;%s-%d.tar.bz2&#39; % =
(self.bundle_name, version)<br>@@ -103,9 +92,9 @@ class Builder(object):<br=
>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.=
close()<br>&nbsp;<br>&nbsp;class Packager(object):<br>-&nbsp;&nbsp;&nbsp; d=
ef __init__(self, config):<br>+&nbsp;&nbsp;&nbsp; def __init__(self, config=
, dist_dir =3D None):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s=
elf.config =3D config<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.d=
ist_dir =3D os.path.join(self.config.source_dir, &#39;dist&#39;)<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.dist_dir =3D dist_dir or o=
s.path.join(self.config.source_dir, &#39;dist&#39;)<br>&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp; self.package_path =3D None<br>&nbsp;<br>&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not os.path.exists(self.dist_=
dir):<br>@@ -113,19 +102,28 @@ class Packager(object):<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b=
r>&nbsp;<br>&nbsp;class BuildPackager(Packager):<br>-&nbsp;&nbsp;&nbsp; def=
 __init__(self, config):<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Pac=
kager.__init__(self, config)<br>+&nbsp;&nbsp;&nbsp; def __init__(self, conf=
ig,dist_dir =3D None):<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Packa=
ger.__init__(self, config,dist_dir )<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.build_dir =3D self.co=
nfig.source_dir<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; def get_files(self):<=
br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return list_files(self.build=
_dir,<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp; ignore_dirs=3D[&#39;po&#39;, &#39;dist&#39;, &#39;.git&#39;],<br=
>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
; ignore_files=3D[&#39;.gitignore&#39;])<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp; return self.config.bundle.get_files()<br>+&nbsp;&nbsp;&nbsp; <=
br>+&nbsp;&nbsp;&nbsp; def fix_manifest(self):<br>+&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp; allfiles =3D&nbsp; list_files(self.build_dir,<br>+&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ignor=
e_dirs=3D[&#39;dist&#39;, &#39;.git&#39;],<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
; ignore_files=3D[&#39;.gitignore&#39;, &#39;MANIFEST&#39;, &#39;*.pyc&#39;=
, &#39;*~&#39;, &#39;*.bak&#39;])<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp; for file in allfiles:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp; if file not in self.config.bundle.manifest:<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp; self.config.bundle.manifest.append(file)<br>+&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp; manifestfile =3D open(os.path.join(self.confi=
g.source_dir,MANIFEST),&quot;wb&quot;)<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp; for line in self.config.bundle.manifest:<br>+&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; manifestfile.write(line + &=
quot;\n&quot;)<br>
&nbsp;<br>&nbsp;class XOPackager(BuildPackager):<br>-&nbsp;&nbsp;&nbsp; def=
 __init__(self, config):<br>+&nbsp;&nbsp;&nbsp; def __init__(self, config,d=
ist_dir =3D None,dist_name =3D None):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp; BuildPackager.__init__(self, config)<br>-&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp; self.package_path =3D os.path.join(self.dist_dir, s=
elf.config.xo_name)<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.package_path =3D os.path.j=
oin(self.dist_dir, dist_name or self.config.xo_name)<br>&nbsp;<br>&nbsp;&nb=
sp;&nbsp;&nbsp; def package(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp; bundle_zip =3D zipfile.ZipFile(self.package_path, &#39;w&#39;,<=
br>@@ -137,16 +135,25 @@ class XOPackager(BuildPackager):<br>
&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bundle_zip.close=
()<br>&nbsp;<br>-class SourcePackager(Packager):<br>-&nbsp;&nbsp;&nbsp; def=
 __init__(self, config):<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Pac=
kager.__init__(self, config)<br>+class SourcePackager(BuildPackager):<br>+&=
nbsp;&nbsp;&nbsp; def __init__(self, config,dist_dir =3D None):<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BuildPackager.__init__(self, co=
nfig,dist_dir)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.pac=
kage_path =3D os.path.join(self.dist_dir,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.con=
fig.tarball_name)<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; def get_files(self)=
:<br>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return list_files(self.config.s=
ource_dir,<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp; ignore_dirs=3D[&#39;locale&#39;, &#39;dist&#39;, &#39;.git&=
#39;],<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp; ignore_files=3D[&#39;.gitignore&#39;])<br>+&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp; git_ls =3D Popen(&#39;git-ls-files&#39;,stdout=3DPIP=
E,cwd=3Dself.config.source_dir)<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if git_ls.wait():<br>+&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #non-0 return cod=
e - failed<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp; return Packager.get_files(self)<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp; f =3D git_ls.stdout<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p; files =3D []<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for line in =
f.readlines():<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp; filename =3D line.strip()<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not =
filename.startswith(&#39;.&#39;):<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; files.append(filename=
)<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.close()<br>+&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return files<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;=
&nbsp; def package(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p; tar =3D tarfile.open(self.package_path, &quot;w&quot;)<br>
diff --git a/src/sugar/bundle/activitybundle.py b/src/sugar/bundle/activity=
bundle.py<br>index db30555..1ff7d39 100644<br>--- a/src/sugar/bundle/activi=
tybundle.py<br>+++ b/src/sugar/bundle/activitybundle.py<br>@@ -21,15 +21,31=
 @@ from ConfigParser import ConfigParser<br>
&nbsp;import locale<br>&nbsp;import os<br>&nbsp;import tempfile<br>+from fn=
match import fnmatch<br>&nbsp;<br>&nbsp;from sugar.bundle.bundle import Bun=
dle, MalformedBundleException, \<br>&nbsp;&nbsp;&nbsp;&nbsp; AlreadyInstall=
edException, RegistrationException, \<br>
&nbsp;&nbsp;&nbsp;&nbsp; NotInstalledException<br>&nbsp;<br>-from sugar imp=
ort activity<br>-from sugar import env<br>-<br>&nbsp;import logging<br>+imp=
ort warnings<br>+<br>+MANIFEST =3D &quot;MANIFEST&quot;<br>+<br>+def list_f=
iles(base_dir, ignore_dirs=3DNone, ignore_files=3DNone):<br>
+&nbsp;&nbsp;&nbsp; result =3D []<br>+&nbsp;&nbsp;&nbsp; <br>+&nbsp;&nbsp;&=
nbsp; for root, dirs, files in os.walk(base_dir):<br>+&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp; for f in files:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not (ignore_files and [True for pat in=
 ignore_files if fnmatch(f,pat)]):<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #result matches a pa=
ttern in ignore_files, ignore it<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp; rel_path =3D root[len(base_dir) + 1:]<br>+&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =
result.append(os.path.join(rel_path, f))<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp; if ignore_dirs and root =3D=3D base_dir:<br>+&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for ignore in ignore_dirs=
:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp; if ignore in dirs:<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dirs.remove(ignore)<br>+&nbsp;&nbs=
p;&nbsp; return result<br>&nbsp;<br>&nbsp;class ActivityBundle(Bundle):<br>=
&nbsp;&nbsp;&nbsp;&nbsp; &quot;&quot;&quot;A Sugar activity bundle<br>@@ -6=
4,7 +80,62 @@ class ActivityBundle(Bundle):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp; linfo_file =3D self._get_linfo_file()<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if linfo_file:<br>&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self._pars=
e_linfo(linfo_file)<br>-<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br=
>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.read_manifest()<br>+<br>+=
&nbsp;&nbsp;&nbsp; def _raw_manifest(self):<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp; try:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp; f =3D self._get_file(MANIFEST)<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; except IOError:<br>+&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f =3D none<br>+&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not f:<br>+&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; warnings.warn(MalformedBundleE=
xception(&quot;Activity directory lacks a MANIFEST file.&quot;))<br>+&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return []<br>+=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret =3D map(lambda x:x.strip(),f=
.readlines()) #strip trailing \n and other whitespace<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.close()<br>+&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp; return ret<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp; <br>+&nbsp;&nbsp;&nbsp; def read_manifest(self):<br>+&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;&quot;&quot;read_manifest: sets self.ma=
nifest to list of lines in MANIFEST, <br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp; with invalid lines replaced by empty lines.<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>+&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp; Since absolute order carries information on file history, =
it should be preserved.<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; For =
instance, when renaming a file, you should leave the new name on the same l=
ine<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; as the old one.<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;&quot;&quot;<br>+&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; manifestlines =3D self._raw_manifest()<b=
r>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for num,line in enumerate(man=
ifestlines):<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp; if line:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if line in manifestlines[0:num]:<br>+=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; manifestlines[num] =3D &quot;&quot;=
<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; warnings.warn(MalformedBundleExcep=
tion(&quot;Bundle %s: duplicate entry in MANIFEST: %s&quot;<br>+&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; %(self._name,lin=
e)))<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp; if line =3D=3D MANIFEST:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp; manifestlines[num] =3D &quot;&quot;<br>+&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp; warnings.warn(MalformedBundleException(&quot;Bundle %s: MAN=
IFEST includes itself: %s&quot;<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; %(self._name,line)))<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp; if line.endswith(&quot;/&quot;):<br>+&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp; if not self._is_dir(line):<br>+&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; manifestlines[num] =3D &quot;&quot;<=
br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; warning=
s.warn(MalformedBundleException(&quot;Bundle %s: invalid dir entry in MANIF=
EST: %s&quot;<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp; %(self._name,line)))<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>+&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not self._is_file(line):<br>+&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; manifestlines[num]=
 =3D &quot;&quot;<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp; warnings.warn(MalformedBundleException(&quot;Bundle %s: invalid =
entry in MANIFEST: %s&quot;<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp; %(self._name,line)))<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp; #remove trailing newlines - unlike internal newlines, they do =
not help keep absolute position<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp; while manifestlines[-1]=3D=3D&quot;&quot;:<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; manifes=
tlines =3D manifestlines[:-1]<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
; self.manifest =3D manifestlines<br>+&nbsp;&nbsp;&nbsp; <br>+&nbsp;&nbsp;&=
nbsp; def get_files(self,manifest =3D None):<br>+&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp; return [MANIFEST] + filter(lambda line: line and not line.=
endswith(&quot;/&quot;),manifest or self.manifest)<br>
+&nbsp;&nbsp;&nbsp; <br>+&nbsp;&nbsp;&nbsp; def get_dirs(self):<br>+&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return filter(lambda line: line and lin=
e.endswith(&quot;/&quot;),self.manifest)<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
 <br>&nbsp;&nbsp;&nbsp;&nbsp; def _parse_info(self, info_file):<br>&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cp =3D ConfigParser()<br>&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cp.readfp(info_file)<br>
@@ -116,6 +187,7 @@ class ActivityBundle(Bundle):<br>&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =
raise MalformedBundleException(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp; &#39;Activity bundle %s has invalid version number %s&#39; %<br>&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (self._path, version))<br>
+&nbsp;&nbsp;&nbsp; <br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; def _get_linfo_f=
ile(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lang =3D loc=
ale.getdefaultlocale()[0]<br>@@ -209,28 +281,53 @@ class ActivityBundle(Bun=
dle):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return self._show=
_launcher<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; def is_installed(self):<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; from sugar import activity<br>+=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp; if activity.get_registry().get_activity(self._bundle_i=
d):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp; return True<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<=
br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
 return False<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; def need_upgrade(self):=
<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; from sugar import activity<br>+=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp; act =3D activity.get_registry().get_activity(self._bun=
dle_id)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if act is None =
or act.version !=3D self._activity_version:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return True<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return False<br>-<b=
r>-&nbsp;&nbsp;&nbsp; def install(self):<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp; activities_path =3D env.get_user_activities_path()<br>-&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; act =3D activity.get_registry().get_act=
ivity(self._bundle_id)<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ac=
t is not None and act.path.startswith(activities_path):<br>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; raise A=
lreadyInstalledException<br>-<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
; install_dir =3D env.get_user_activities_path()<br>+&nbsp;&nbsp;&nbsp; <br=
>+&nbsp;&nbsp;&nbsp; def unpack(self,install_dir,strict_manifest=3DFalse):<=
br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self._unzip(install_dir=
)<br>&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; install_path =3D os.path.j=
oin(install_dir, self._zip_root_dir)<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp; <br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #check installed f=
iles against the MANIFEST<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ma=
nifestfiles =3D self.get_files(self._raw_manifest())<br>+&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp; for file in list_files(install_path):<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if file=
 in manifestfiles:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; manifestfiles.remove(file)<br>+&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elif file !=
=3D MANIFEST:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #warnings.warn(MalformedBundleException(&=
quot;Bundle %s: %s not in MANIFEST&quot;%<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp; #&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp; (self._name,file)))<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if strict_man=
ifest:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.remove(os.path.join(i=
nstall_path,file))<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if manife=
stfiles:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp; err =3D MalformedBundleException(&quot;Bundle %s: files in MANIFEST no=
t included: %s&quot;%<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp; (self._name,str(manifestfiles)))<br>+&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if strict_manifest:<=
br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp; raise err<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; warnings.warn(err)<br>+=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #create empty directories<br>+&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for dir in self.get_dirs():<br>+&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dirpath =
=3D os.path.join(install_path,dir)<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if os.path.isdir(dirpath):<br>+&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p; warnings.warn(MalformedBundleException(&quot;Bunldle %s: non-empty dir %=
s in MANIFEST&quot;%<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp; (self._name,dirpath)))<br>+&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>+&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.m=
akedirs(os.path.join(install_path,dir))<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp; xdg_data_home =3D os.getenv(&#39;XDG_DATA_HOME&#=
39;,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.path.expanduser(&=
#39;~/.local/share&#39;))<br>@@ -267,11 +364,27 @@ class ActivityBundle(Bun=
dle):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.symlink(info_file=
,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.path.join(installed_icons_dir,=
<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.path.basename(info_file)))<br>+&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return install_path<br>&nbsp;<br>=
+&nbsp;&nbsp;&nbsp; def install(self):<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp; from sugar import activity<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp; from sugar import env<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp; <br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; activities_path =3D env.get_use=
r_activities_path()<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; act =3D =
activity.get_registry().get_activity(self._bundle_id)<br>+&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp; if act is not None and act.path.startswith(activi=
ties_path):<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp; raise AlreadyInstalledException<br>
+<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; install_dir =3D env.get_us=
er_activities_path()<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; install=
_path =3D self.unpack(install_dir)<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not activity=
.get_registry().add_bundle(install_path):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; raise RegistrationException<br>
&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; def uninstall(self, force=3DFalse):<br>+=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; from sugar import activity<br>+&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; from sugar import env<br>+&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp; if self._unpacked:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; install_path =3D self._path<br>&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>
@@ -316,6 +429,9 @@ class ActivityBundle(Bundle):<br>&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; raise RegistrationExcept=
ion<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; def upgrade(self):<br>+&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; from sugar import activity<br>+&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; from sugar import env<br>+&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp; act =3D activity.get_registry().get_activity(self._bundle_id)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if act is None:<br>&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; logging.w=
arning(&#39;Activity not installed&#39;)<br>diff --git a/src/sugar/bundle/b=
undle.py b/src/sugar/bundle/bundle.py<br>index 47d08b2..44e30b0 100644<br>-=
-- a/src/sugar/bundle/bundle.py<br>
+++ b/src/sugar/bundle/bundle.py<br>@@ -114,6 +114,29 @@ class Bundle:<br>&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; zip=
_file.close()<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
 return f<br>+&nbsp;&nbsp;&nbsp; <br>+&nbsp;&nbsp;&nbsp; def _is_dir(self, =
filename):<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self._unpacked=
:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pa=
th =3D os.path.join(self._path, filename)<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return =
os.path.isdir(path)<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br=
>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return=
 True #zip files contain all dirs you care about!<br>+&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>+&nbsp;&nbsp;&nbsp; def _=
is_file(self, filename):<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if =
self._unpacked:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp; path =3D os.path.join(self._path, filename)<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return =
os.path.isfile(path)<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<b=
r>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; zip_f=
ile =3D zipfile.ZipFile(self._path)<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; path =3D os.path.join(self._zip_root_dir, f=
ilename)<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp; try:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp; data =3D zip_file.getinfo(path)<br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp; return True<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp; except KeyError:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return False<=
br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fina=
lly:<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp; zip_file.close()<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;<br=
>&nbsp;&nbsp;&nbsp;&nbsp; def get_path(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp; &quot;&quot;&quot;Get the bundle path.&quot;&quot;&q=
uot;<br>
-- <br><a href=3D"http://1.5.2.5">1.5.2.5</a><br><br><br>

------=_Part_21853_28463409.1212768784608--

------=_Part_21852_24326587.1212768784608
Content-Type: text/x-diff;
 name=0001-Bundlebuilder-to-use-MANIFEST-fix_manifest-function.patch
Content-Transfer-Encoding: base64
X-Attachment-Id: f_fh4z1n1z1
Content-Disposition: attachment;
 filename=0001-Bundlebuilder-to-use-MANIFEST-fix_manifest-function.patch

RnJvbSA5YWI2MTk4ODhhOWQ0ZWY0MGNhM2U5ODRkNDQ4NWM1YTIyNTU4MTA1IE1vbiBTZXAgMTcg
MDA6MDA6MDAgMjAwMQpGcm9tOiBjaGVtYSA8Y2hlbWFAY2hlbWEtbGFwdG9wLihub25lKT4KRGF0
ZTogRnJpLCA2IEp1biAyMDA4IDEwOjAxOjIxIC0wNjAwClN1YmplY3Q6IFtQQVRDSF0gQnVuZGxl
YnVpbGRlciB0byB1c2UgTUFOSUZFU1QsIGZpeF9tYW5pZmVzdCBmdW5jdGlvbiwgc29tZSBmdW5j
dGlvbnMKcHVzaGVkIHVwc3RyZWFtIHRvIGJ1bmRsZS5weSBhbmQgYWN0aXZpdHlidW5kbGUucHkK
LS0tCiBzcmMvc3VnYXIvYWN0aXZpdHkvYnVuZGxlYnVpbGRlci5weSB8ICAgNjkgKysrKysrKysr
Ky0tLS0tLS0tCiBzcmMvc3VnYXIvYnVuZGxlL2FjdGl2aXR5YnVuZGxlLnB5ICB8ICAxNDAgKysr
KysrKysrKysrKysrKysrKysrKysrKysrKysrKystLS0KIHNyYy9zdWdhci9idW5kbGUvYnVuZGxl
LnB5ICAgICAgICAgIHwgICAyMyArKysrKysKIDMgZmlsZXMgY2hhbmdlZCwgMTg5IGluc2VydGlv
bnMoKyksIDQzIGRlbGV0aW9ucygtKQoKZGlmZiAtLWdpdCBhL3NyYy9zdWdhci9hY3Rpdml0eS9i
dW5kbGVidWlsZGVyLnB5IGIvc3JjL3N1Z2FyL2FjdGl2aXR5L2J1bmRsZWJ1aWxkZXIucHkKaW5k
ZXggMTA2M2Y3Mi4uNzJjMjQ2YyAxMDA2NDQKLS0tIGEvc3JjL3N1Z2FyL2FjdGl2aXR5L2J1bmRs
ZWJ1aWxkZXIucHkKKysrIGIvc3JjL3N1Z2FyL2FjdGl2aXR5L2J1bmRsZWJ1aWxkZXIucHkKQEAg
LTE5LDI4ICsxOSwxNiBAQCBpbXBvcnQgb3MKIGltcG9ydCB6aXBmaWxlCiBpbXBvcnQgdGFyZmls
ZQogaW1wb3J0IHNodXRpbAotaW1wb3J0IHN1YnByb2Nlc3MKK2Zyb20gc3VicHJvY2VzcyBpbXBv
cnQgUG9wZW4sIFBJUEUKIGltcG9ydCByZQogaW1wb3J0IGdldHRleHQKIGZyb20gb3B0cGFyc2Ug
aW1wb3J0IE9wdGlvblBhcnNlcgoraW1wb3J0IHdhcm5pbmdzCitpbXBvcnQgc3VicHJvY2Vzcwog
CiBmcm9tIHN1Z2FyIGltcG9ydCBlbnYKLWZyb20gc3VnYXIuYnVuZGxlLmFjdGl2aXR5YnVuZGxl
IGltcG9ydCBBY3Rpdml0eUJ1bmRsZQotCi1kZWYgbGlzdF9maWxlcyhiYXNlX2RpciwgaWdub3Jl
X2RpcnM9Tm9uZSwgaWdub3JlX2ZpbGVzPU5vbmUpOgotICAgIHJlc3VsdCA9IFtdCitmcm9tIHN1
Z2FyLmJ1bmRsZS5hY3Rpdml0eWJ1bmRsZSBpbXBvcnQgQWN0aXZpdHlCdW5kbGUsTUFOSUZFU1Qs
bGlzdF9maWxlcwogCi0gICAgZm9yIHJvb3QsIGRpcnMsIGZpbGVzIGluIG9zLndhbGsoYmFzZV9k
aXIpOgotICAgICAgICBmb3IgZiBpbiBmaWxlczoKLSAgICAgICAgICAgIGlmIGlnbm9yZV9maWxl
cyBhbmQgZiBub3QgaW4gaWdub3JlX2ZpbGVzOgotICAgICAgICAgICAgICAgIHJlbF9wYXRoID0g
cm9vdFtsZW4oYmFzZV9kaXIpICsgMTpdCi0gICAgICAgICAgICAgICAgcmVzdWx0LmFwcGVuZChv
cy5wYXRoLmpvaW4ocmVsX3BhdGgsIGYpKQotICAgICAgICBpZiBpZ25vcmVfZGlycyBhbmQgcm9v
dCA9PSBiYXNlX2RpcjoKLSAgICAgICAgICAgIGZvciBpZ25vcmUgaW4gaWdub3JlX2RpcnM6Ci0g
ICAgICAgICAgICAgICAgaWYgaWdub3JlIGluIGRpcnM6Ci0gICAgICAgICAgICAgICAgICAgIGRp
cnMucmVtb3ZlKGlnbm9yZSkKLQotICAgIHJldHVybiByZXN1bHQKIAogY2xhc3MgQ29uZmlnKG9i
amVjdCk6CiAgICAgZGVmIF9faW5pdF9fKHNlbGYsIGJ1bmRsZV9uYW1lLCBzb3VyY2VfZGlyPU5v
bmUpOgpAQCAtNTMsNiArNDEsNyBAQCBjbGFzcyBDb25maWcob2JqZWN0KToKICAgICAgICAgYnVu
ZGxlID0gQWN0aXZpdHlCdW5kbGUoc2VsZi5zb3VyY2VfZGlyKQogICAgICAgICB2ZXJzaW9uID0g
YnVuZGxlLmdldF9hY3Rpdml0eV92ZXJzaW9uKCkKIAorICAgICAgICBzZWxmLmJ1bmRsZSA9IGJ1
bmRsZQogICAgICAgICBzZWxmLmJ1bmRsZV9uYW1lID0gYnVuZGxlX25hbWUKICAgICAgICAgc2Vs
Zi54b19uYW1lID0gJyVzLSVkLnhvJyAlIChzZWxmLmJ1bmRsZV9uYW1lLCB2ZXJzaW9uKQogICAg
ICAgICBzZWxmLnRhcmJhbGxfbmFtZSA9ICclcy0lZC50YXIuYnoyJyAlIChzZWxmLmJ1bmRsZV9u
YW1lLCB2ZXJzaW9uKQpAQCAtMTAzLDkgKzkyLDkgQEAgY2xhc3MgQnVpbGRlcihvYmplY3QpOgog
ICAgICAgICAgICAgZi5jbG9zZSgpCiAKIGNsYXNzIFBhY2thZ2VyKG9iamVjdCk6Ci0gICAgZGVm
IF9faW5pdF9fKHNlbGYsIGNvbmZpZyk6CisgICAgZGVmIF9faW5pdF9fKHNlbGYsIGNvbmZpZywg
ZGlzdF9kaXIgPSBOb25lKToKICAgICAgICAgc2VsZi5jb25maWcgPSBjb25maWcKLSAgICAgICAg
c2VsZi5kaXN0X2RpciA9IG9zLnBhdGguam9pbihzZWxmLmNvbmZpZy5zb3VyY2VfZGlyLCAnZGlz
dCcpCisgICAgICAgIHNlbGYuZGlzdF9kaXIgPSBkaXN0X2RpciBvciBvcy5wYXRoLmpvaW4oc2Vs
Zi5jb25maWcuc291cmNlX2RpciwgJ2Rpc3QnKQogICAgICAgICBzZWxmLnBhY2thZ2VfcGF0aCA9
IE5vbmUKIAogICAgICAgICBpZiBub3Qgb3MucGF0aC5leGlzdHMoc2VsZi5kaXN0X2Rpcik6CkBA
IC0xMTMsMTkgKzEwMiwyOCBAQCBjbGFzcyBQYWNrYWdlcihvYmplY3QpOgogICAgICAgICAgICAg
CiAKIGNsYXNzIEJ1aWxkUGFja2FnZXIoUGFja2FnZXIpOgotICAgIGRlZiBfX2luaXRfXyhzZWxm
LCBjb25maWcpOgotICAgICAgICBQYWNrYWdlci5fX2luaXRfXyhzZWxmLCBjb25maWcpCisgICAg
ZGVmIF9faW5pdF9fKHNlbGYsIGNvbmZpZyxkaXN0X2RpciA9IE5vbmUpOgorICAgICAgICBQYWNr
YWdlci5fX2luaXRfXyhzZWxmLCBjb25maWcsZGlzdF9kaXIgKQogICAgICAgICBzZWxmLmJ1aWxk
X2RpciA9IHNlbGYuY29uZmlnLnNvdXJjZV9kaXIKIAogICAgIGRlZiBnZXRfZmlsZXMoc2VsZik6
Ci0gICAgICAgIHJldHVybiBsaXN0X2ZpbGVzKHNlbGYuYnVpbGRfZGlyLAotICAgICAgICAgICAg
ICAgICAgICAgICAgICBpZ25vcmVfZGlycz1bJ3BvJywgJ2Rpc3QnLCAnLmdpdCddLAotICAgICAg
ICAgICAgICAgICAgICAgICAgICBpZ25vcmVfZmlsZXM9WycuZ2l0aWdub3JlJ10pCisgICAgICAg
IHJldHVybiBzZWxmLmNvbmZpZy5idW5kbGUuZ2V0X2ZpbGVzKCkKKyAgICAKKyAgICBkZWYgZml4
X21hbmlmZXN0KHNlbGYpOgorICAgICAgICBhbGxmaWxlcyA9ICBsaXN0X2ZpbGVzKHNlbGYuYnVp
bGRfZGlyLAorICAgICAgICAgICAgICAgICAgICAgICAgICBpZ25vcmVfZGlycz1bJ2Rpc3QnLCAn
LmdpdCddLAorICAgICAgICAgICAgICAgICAgICAgICAgICBpZ25vcmVfZmlsZXM9WycuZ2l0aWdu
b3JlJywgJ01BTklGRVNUJywgJyoucHljJywgJyp+JywgJyouYmFrJ10pCisgICAgICAgIGZvciBm
aWxlIGluIGFsbGZpbGVzOgorICAgICAgICAgICAgaWYgZmlsZSBub3QgaW4gc2VsZi5jb25maWcu
YnVuZGxlLm1hbmlmZXN0OgorICAgICAgICAgICAgICAgIHNlbGYuY29uZmlnLmJ1bmRsZS5tYW5p
ZmVzdC5hcHBlbmQoZmlsZSkKKyAgICAgICAgbWFuaWZlc3RmaWxlID0gb3Blbihvcy5wYXRoLmpv
aW4oc2VsZi5jb25maWcuc291cmNlX2RpcixNQU5JRkVTVCksIndiIikKKyAgICAgICAgZm9yIGxp
bmUgaW4gc2VsZi5jb25maWcuYnVuZGxlLm1hbmlmZXN0OgorICAgICAgICAgICAgbWFuaWZlc3Rm
aWxlLndyaXRlKGxpbmUgKyAiXG4iKQogCiBjbGFzcyBYT1BhY2thZ2VyKEJ1aWxkUGFja2FnZXIp
OgotICAgIGRlZiBfX2luaXRfXyhzZWxmLCBjb25maWcpOgorICAgIGRlZiBfX2luaXRfXyhzZWxm
LCBjb25maWcsZGlzdF9kaXIgPSBOb25lLGRpc3RfbmFtZSA9IE5vbmUpOgogICAgICAgICBCdWls
ZFBhY2thZ2VyLl9faW5pdF9fKHNlbGYsIGNvbmZpZykKLSAgICAgICAgc2VsZi5wYWNrYWdlX3Bh
dGggPSBvcy5wYXRoLmpvaW4oc2VsZi5kaXN0X2Rpciwgc2VsZi5jb25maWcueG9fbmFtZSkKKyAg
ICAgICAgc2VsZi5wYWNrYWdlX3BhdGggPSBvcy5wYXRoLmpvaW4oc2VsZi5kaXN0X2RpciwgZGlz
dF9uYW1lIG9yIHNlbGYuY29uZmlnLnhvX25hbWUpCiAKICAgICBkZWYgcGFja2FnZShzZWxmKToK
ICAgICAgICAgYnVuZGxlX3ppcCA9IHppcGZpbGUuWmlwRmlsZShzZWxmLnBhY2thZ2VfcGF0aCwg
J3cnLApAQCAtMTM3LDE2ICsxMzUsMjUgQEAgY2xhc3MgWE9QYWNrYWdlcihCdWlsZFBhY2thZ2Vy
KToKIAogICAgICAgICBidW5kbGVfemlwLmNsb3NlKCkKIAotY2xhc3MgU291cmNlUGFja2FnZXIo
UGFja2FnZXIpOgotICAgIGRlZiBfX2luaXRfXyhzZWxmLCBjb25maWcpOgotICAgICAgICBQYWNr
YWdlci5fX2luaXRfXyhzZWxmLCBjb25maWcpCitjbGFzcyBTb3VyY2VQYWNrYWdlcihCdWlsZFBh
Y2thZ2VyKToKKyAgICBkZWYgX19pbml0X18oc2VsZiwgY29uZmlnLGRpc3RfZGlyID0gTm9uZSk6
CisgICAgICAgIEJ1aWxkUGFja2FnZXIuX19pbml0X18oc2VsZiwgY29uZmlnLGRpc3RfZGlyKQog
ICAgICAgICBzZWxmLnBhY2thZ2VfcGF0aCA9IG9zLnBhdGguam9pbihzZWxmLmRpc3RfZGlyLAog
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxmLmNvbmZpZy50YXJi
YWxsX25hbWUpCiAKICAgICBkZWYgZ2V0X2ZpbGVzKHNlbGYpOgotICAgICAgICByZXR1cm4gbGlz
dF9maWxlcyhzZWxmLmNvbmZpZy5zb3VyY2VfZGlyLAotICAgICAgICAgICAgICAgICAgICAgICAg
ICBpZ25vcmVfZGlycz1bJ2xvY2FsZScsICdkaXN0JywgJy5naXQnXSwKLSAgICAgICAgICAgICAg
ICAgICAgICAgICAgaWdub3JlX2ZpbGVzPVsnLmdpdGlnbm9yZSddKQorICAgICAgICBnaXRfbHMg
PSBQb3BlbignZ2l0LWxzLWZpbGVzJyxzdGRvdXQ9UElQRSxjd2Q9c2VsZi5jb25maWcuc291cmNl
X2RpcikKKyAgICAgICAgaWYgZ2l0X2xzLndhaXQoKToKKyAgICAgICAgICAgICNub24tMCByZXR1
cm4gY29kZSAtIGZhaWxlZAorICAgICAgICAgICAgcmV0dXJuIFBhY2thZ2VyLmdldF9maWxlcyhz
ZWxmKQorICAgICAgICBmID0gZ2l0X2xzLnN0ZG91dAorICAgICAgICBmaWxlcyA9IFtdCisgICAg
ICAgIGZvciBsaW5lIGluIGYucmVhZGxpbmVzKCk6CisgICAgICAgICAgICBmaWxlbmFtZSA9IGxp
bmUuc3RyaXAoKQorICAgICAgICAgICAgaWYgbm90IGZpbGVuYW1lLnN0YXJ0c3dpdGgoJy4nKToK
KyAgICAgICAgICAgICAgICBmaWxlcy5hcHBlbmQoZmlsZW5hbWUpCisgICAgICAgIGYuY2xvc2Uo
KQorICAgICAgICByZXR1cm4gZmlsZXMKIAogICAgIGRlZiBwYWNrYWdlKHNlbGYpOgogICAgICAg
ICB0YXIgPSB0YXJmaWxlLm9wZW4oc2VsZi5wYWNrYWdlX3BhdGgsICJ3IikKZGlmZiAtLWdpdCBh
L3NyYy9zdWdhci9idW5kbGUvYWN0aXZpdHlidW5kbGUucHkgYi9zcmMvc3VnYXIvYnVuZGxlL2Fj
dGl2aXR5YnVuZGxlLnB5CmluZGV4IGRiMzA1NTUuLjFmZjdkMzkgMTAwNjQ0Ci0tLSBhL3NyYy9z
dWdhci9idW5kbGUvYWN0aXZpdHlidW5kbGUucHkKKysrIGIvc3JjL3N1Z2FyL2J1bmRsZS9hY3Rp
dml0eWJ1bmRsZS5weQpAQCAtMjEsMTUgKzIxLDMxIEBAIGZyb20gQ29uZmlnUGFyc2VyIGltcG9y
dCBDb25maWdQYXJzZXIKIGltcG9ydCBsb2NhbGUKIGltcG9ydCBvcwogaW1wb3J0IHRlbXBmaWxl
Citmcm9tIGZubWF0Y2ggaW1wb3J0IGZubWF0Y2gKIAogZnJvbSBzdWdhci5idW5kbGUuYnVuZGxl
IGltcG9ydCBCdW5kbGUsIE1hbGZvcm1lZEJ1bmRsZUV4Y2VwdGlvbiwgXAogICAgIEFscmVhZHlJ
bnN0YWxsZWRFeGNlcHRpb24sIFJlZ2lzdHJhdGlvbkV4Y2VwdGlvbiwgXAogICAgIE5vdEluc3Rh
bGxlZEV4Y2VwdGlvbgogCi1mcm9tIHN1Z2FyIGltcG9ydCBhY3Rpdml0eQotZnJvbSBzdWdhciBp
bXBvcnQgZW52Ci0KIGltcG9ydCBsb2dnaW5nCitpbXBvcnQgd2FybmluZ3MKKworTUFOSUZFU1Qg
PSAiTUFOSUZFU1QiCisKK2RlZiBsaXN0X2ZpbGVzKGJhc2VfZGlyLCBpZ25vcmVfZGlycz1Ob25l
LCBpZ25vcmVfZmlsZXM9Tm9uZSk6CisgICAgcmVzdWx0ID0gW10KKyAgICAKKyAgICBmb3Igcm9v
dCwgZGlycywgZmlsZXMgaW4gb3Mud2FsayhiYXNlX2Rpcik6CisgICAgICAgIGZvciBmIGluIGZp
bGVzOgorICAgICAgICAgICAgaWYgbm90IChpZ25vcmVfZmlsZXMgYW5kIFtUcnVlIGZvciBwYXQg
aW4gaWdub3JlX2ZpbGVzIGlmIGZubWF0Y2goZixwYXQpXSk6CisgICAgICAgICAgICAgICAgI3Jl
c3VsdCBtYXRjaGVzIGEgcGF0dGVybiBpbiBpZ25vcmVfZmlsZXMsIGlnbm9yZSBpdAorICAgICAg
ICAgICAgICAgIHJlbF9wYXRoID0gcm9vdFtsZW4oYmFzZV9kaXIpICsgMTpdCisgICAgICAgICAg
ICAgICAgcmVzdWx0LmFwcGVuZChvcy5wYXRoLmpvaW4ocmVsX3BhdGgsIGYpKQorICAgICAgICBp
ZiBpZ25vcmVfZGlycyBhbmQgcm9vdCA9PSBiYXNlX2RpcjoKKyAgICAgICAgICAgIGZvciBpZ25v
cmUgaW4gaWdub3JlX2RpcnM6CisgICAgICAgICAgICAgICAgaWYgaWdub3JlIGluIGRpcnM6Cisg
ICAgICAgICAgICAgICAgICAgIGRpcnMucmVtb3ZlKGlnbm9yZSkKKyAgICByZXR1cm4gcmVzdWx0
CiAKIGNsYXNzIEFjdGl2aXR5QnVuZGxlKEJ1bmRsZSk6CiAgICAgIiIiQSBTdWdhciBhY3Rpdml0
eSBidW5kbGUKQEAgLTY0LDcgKzgwLDYyIEBAIGNsYXNzIEFjdGl2aXR5QnVuZGxlKEJ1bmRsZSk6
CiAgICAgICAgIGxpbmZvX2ZpbGUgPSBzZWxmLl9nZXRfbGluZm9fZmlsZSgpCiAgICAgICAgIGlm
IGxpbmZvX2ZpbGU6CiAgICAgICAgICAgICBzZWxmLl9wYXJzZV9saW5mbyhsaW5mb19maWxlKQot
CisgICAgICAgIAorICAgICAgICBzZWxmLnJlYWRfbWFuaWZlc3QoKQorCisgICAgZGVmIF9yYXdf
bWFuaWZlc3Qoc2VsZik6CisgICAgICAgIHRyeToKKyAgICAgICAgICAgIGYgPSBzZWxmLl9nZXRf
ZmlsZShNQU5JRkVTVCkKKyAgICAgICAgZXhjZXB0IElPRXJyb3I6CisgICAgICAgICAgICBmID0g
bm9uZQorICAgICAgICBpZiBub3QgZjoKKyAgICAgICAgICAgIHdhcm5pbmdzLndhcm4oTWFsZm9y
bWVkQnVuZGxlRXhjZXB0aW9uKCJBY3Rpdml0eSBkaXJlY3RvcnkgbGFja3MgYSBNQU5JRkVTVCBm
aWxlLiIpKQorICAgICAgICAgICAgcmV0dXJuIFtdCisgICAgICAgIHJldCA9IG1hcChsYW1iZGEg
eDp4LnN0cmlwKCksZi5yZWFkbGluZXMoKSkgI3N0cmlwIHRyYWlsaW5nIFxuIGFuZCBvdGhlciB3
aGl0ZXNwYWNlCisgICAgICAgIGYuY2xvc2UoKQorICAgICAgICByZXR1cm4gcmV0CisgICAgICAg
IAorICAgIGRlZiByZWFkX21hbmlmZXN0KHNlbGYpOgorICAgICAgICAiIiJyZWFkX21hbmlmZXN0
OiBzZXRzIHNlbGYubWFuaWZlc3QgdG8gbGlzdCBvZiBsaW5lcyBpbiBNQU5JRkVTVCwgCisgICAg
ICAgIHdpdGggaW52YWxpZCBsaW5lcyByZXBsYWNlZCBieSBlbXB0eSBsaW5lcy4KKyAgICAgICAg
CisgICAgICAgIFNpbmNlIGFic29sdXRlIG9yZGVyIGNhcnJpZXMgaW5mb3JtYXRpb24gb24gZmls
ZSBoaXN0b3J5LCBpdCBzaG91bGQgYmUgcHJlc2VydmVkLgorICAgICAgICBGb3IgaW5zdGFuY2Us
IHdoZW4gcmVuYW1pbmcgYSBmaWxlLCB5b3Ugc2hvdWxkIGxlYXZlIHRoZSBuZXcgbmFtZSBvbiB0
aGUgc2FtZSBsaW5lCisgICAgICAgIGFzIHRoZSBvbGQgb25lLgorICAgICAgICAiIiIKKyAgICAg
ICAgbWFuaWZlc3RsaW5lcyA9IHNlbGYuX3Jhd19tYW5pZmVzdCgpCisgICAgICAgIGZvciBudW0s
bGluZSBpbiBlbnVtZXJhdGUobWFuaWZlc3RsaW5lcyk6CisgICAgICAgICAgICBpZiBsaW5lOgor
ICAgICAgICAgICAgICAgIGlmIGxpbmUgaW4gbWFuaWZlc3RsaW5lc1swOm51bV06CisgICAgICAg
ICAgICAgICAgICAgIG1hbmlmZXN0bGluZXNbbnVtXSA9ICIiCisgICAgICAgICAgICAgICAgICAg
IHdhcm5pbmdzLndhcm4oTWFsZm9ybWVkQnVuZGxlRXhjZXB0aW9uKCJCdW5kbGUgJXM6IGR1cGxp
Y2F0ZSBlbnRyeSBpbiBNQU5JRkVTVDogJXMiCisgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICUoc2VsZi5fbmFtZSxsaW5lKSkpCisgICAgICAgICAgICAg
ICAgICAgIGNvbnRpbnVlCisgICAgICAgICAgICAgICAgaWYgbGluZSA9PSBNQU5JRkVTVDoKKyAg
ICAgICAgICAgICAgICAgICAgbWFuaWZlc3RsaW5lc1tudW1dID0gIiIKKyAgICAgICAgICAgICAg
ICAgICAgd2FybmluZ3Mud2FybihNYWxmb3JtZWRCdW5kbGVFeGNlcHRpb24oIkJ1bmRsZSAlczog
TUFOSUZFU1QgaW5jbHVkZXMgaXRzZWxmOiAlcyIKKyAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgJShzZWxmLl9uYW1lLGxpbmUpKSkKKyAgICAgICAgICAg
ICAgICBpZiBsaW5lLmVuZHN3aXRoKCIvIik6CisgICAgICAgICAgICAgICAgICAgIGlmIG5vdCBz
ZWxmLl9pc19kaXIobGluZSk6CisgICAgICAgICAgICAgICAgICAgICAgICBtYW5pZmVzdGxpbmVz
W251bV0gPSAiIgorICAgICAgICAgICAgICAgICAgICAgICAgd2FybmluZ3Mud2FybihNYWxmb3Jt
ZWRCdW5kbGVFeGNlcHRpb24oIkJ1bmRsZSAlczogaW52YWxpZCBkaXIgZW50cnkgaW4gTUFOSUZF
U1Q6ICVzIgorICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgJShzZWxmLl9uYW1lLGxpbmUpKSkKKyAgICAgICAgICAgICAgICBlbHNlOgorICAgICAg
ICAgICAgICAgICAgICBpZiBub3Qgc2VsZi5faXNfZmlsZShsaW5lKToKKyAgICAgICAgICAgICAg
ICAgICAgICAgIG1hbmlmZXN0bGluZXNbbnVtXSA9ICIiCisgICAgICAgICAgICAgICAgICAgICAg
ICB3YXJuaW5ncy53YXJuKE1hbGZvcm1lZEJ1bmRsZUV4Y2VwdGlvbigiQnVuZGxlICVzOiBpbnZh
bGlkIGVudHJ5IGluIE1BTklGRVNUOiAlcyIKKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICUoc2VsZi5fbmFtZSxsaW5lKSkpCisgICAgICAgICNy
ZW1vdmUgdHJhaWxpbmcgbmV3bGluZXMgLSB1bmxpa2UgaW50ZXJuYWwgbmV3bGluZXMsIHRoZXkg
ZG8gbm90IGhlbHAga2VlcCBhYnNvbHV0ZSBwb3NpdGlvbgorICAgICAgICB3aGlsZSBtYW5pZmVz
dGxpbmVzWy0xXT09IiI6CisgICAgICAgICAgICBtYW5pZmVzdGxpbmVzID0gbWFuaWZlc3RsaW5l
c1s6LTFdCisgICAgICAgIHNlbGYubWFuaWZlc3QgPSBtYW5pZmVzdGxpbmVzCisgICAgCisgICAg
ZGVmIGdldF9maWxlcyhzZWxmLG1hbmlmZXN0ID0gTm9uZSk6CisgICAgICAgIHJldHVybiBbTUFO
SUZFU1RdICsgZmlsdGVyKGxhbWJkYSBsaW5lOiBsaW5lIGFuZCBub3QgbGluZS5lbmRzd2l0aCgi
LyIpLG1hbmlmZXN0IG9yIHNlbGYubWFuaWZlc3QpCisgICAgCisgICAgZGVmIGdldF9kaXJzKHNl
bGYpOgorICAgICAgICByZXR1cm4gZmlsdGVyKGxhbWJkYSBsaW5lOiBsaW5lIGFuZCBsaW5lLmVu
ZHN3aXRoKCIvIiksc2VsZi5tYW5pZmVzdCkKKyAgICAgIAogICAgIGRlZiBfcGFyc2VfaW5mbyhz
ZWxmLCBpbmZvX2ZpbGUpOgogICAgICAgICBjcCA9IENvbmZpZ1BhcnNlcigpCiAgICAgICAgIGNw
LnJlYWRmcChpbmZvX2ZpbGUpCkBAIC0xMTYsNiArMTg3LDcgQEAgY2xhc3MgQWN0aXZpdHlCdW5k
bGUoQnVuZGxlKToKICAgICAgICAgICAgICAgICByYWlzZSBNYWxmb3JtZWRCdW5kbGVFeGNlcHRp
b24oCiAgICAgICAgICAgICAgICAgICAgICdBY3Rpdml0eSBidW5kbGUgJXMgaGFzIGludmFsaWQg
dmVyc2lvbiBudW1iZXIgJXMnICUKICAgICAgICAgICAgICAgICAgICAgKHNlbGYuX3BhdGgsIHZl
cnNpb24pKQorICAgIAogCiAgICAgZGVmIF9nZXRfbGluZm9fZmlsZShzZWxmKToKICAgICAgICAg
bGFuZyA9IGxvY2FsZS5nZXRkZWZhdWx0bG9jYWxlKClbMF0KQEAgLTIwOSwyOCArMjgxLDUzIEBA
IGNsYXNzIEFjdGl2aXR5QnVuZGxlKEJ1bmRsZSk6CiAgICAgICAgIHJldHVybiBzZWxmLl9zaG93
X2xhdW5jaGVyCiAKICAgICBkZWYgaXNfaW5zdGFsbGVkKHNlbGYpOgorICAgICAgICBmcm9tIHN1
Z2FyIGltcG9ydCBhY3Rpdml0eQorICAgICAgICAKICAgICAgICAgaWYgYWN0aXZpdHkuZ2V0X3Jl
Z2lzdHJ5KCkuZ2V0X2FjdGl2aXR5KHNlbGYuX2J1bmRsZV9pZCk6CiAgICAgICAgICAgICByZXR1
cm4gVHJ1ZQogICAgICAgICBlbHNlOgogICAgICAgICAgICAgcmV0dXJuIEZhbHNlCiAKICAgICBk
ZWYgbmVlZF91cGdyYWRlKHNlbGYpOgorICAgICAgICBmcm9tIHN1Z2FyIGltcG9ydCBhY3Rpdml0
eQorICAgICAgICAKICAgICAgICAgYWN0ID0gYWN0aXZpdHkuZ2V0X3JlZ2lzdHJ5KCkuZ2V0X2Fj
dGl2aXR5KHNlbGYuX2J1bmRsZV9pZCkKICAgICAgICAgaWYgYWN0IGlzIE5vbmUgb3IgYWN0LnZl
cnNpb24gIT0gc2VsZi5fYWN0aXZpdHlfdmVyc2lvbjoKICAgICAgICAgICAgIHJldHVybiBUcnVl
CiAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICByZXR1cm4gRmFsc2UKLQotICAgIGRlZiBpbnN0
YWxsKHNlbGYpOgotICAgICAgICBhY3Rpdml0aWVzX3BhdGggPSBlbnYuZ2V0X3VzZXJfYWN0aXZp
dGllc19wYXRoKCkKLSAgICAgICAgYWN0ID0gYWN0aXZpdHkuZ2V0X3JlZ2lzdHJ5KCkuZ2V0X2Fj
dGl2aXR5KHNlbGYuX2J1bmRsZV9pZCkKLSAgICAgICAgaWYgYWN0IGlzIG5vdCBOb25lIGFuZCBh
Y3QucGF0aC5zdGFydHN3aXRoKGFjdGl2aXRpZXNfcGF0aCk6Ci0gICAgICAgICAgICByYWlzZSBB
bHJlYWR5SW5zdGFsbGVkRXhjZXB0aW9uCi0KLSAgICAgICAgaW5zdGFsbF9kaXIgPSBlbnYuZ2V0
X3VzZXJfYWN0aXZpdGllc19wYXRoKCkKKyAgICAKKyAgICBkZWYgdW5wYWNrKHNlbGYsaW5zdGFs
bF9kaXIsc3RyaWN0X21hbmlmZXN0PUZhbHNlKToKICAgICAgICAgc2VsZi5fdW56aXAoaW5zdGFs
bF9kaXIpCiAKICAgICAgICAgaW5zdGFsbF9wYXRoID0gb3MucGF0aC5qb2luKGluc3RhbGxfZGly
LCBzZWxmLl96aXBfcm9vdF9kaXIpCisgICAgICAgIAorICAgICAgICAjY2hlY2sgaW5zdGFsbGVk
IGZpbGVzIGFnYWluc3QgdGhlIE1BTklGRVNUCisgICAgICAgIG1hbmlmZXN0ZmlsZXMgPSBzZWxm
LmdldF9maWxlcyhzZWxmLl9yYXdfbWFuaWZlc3QoKSkKKyAgICAgICAgZm9yIGZpbGUgaW4gbGlz
dF9maWxlcyhpbnN0YWxsX3BhdGgpOgorICAgICAgICAgICAgaWYgZmlsZSBpbiBtYW5pZmVzdGZp
bGVzOgorICAgICAgICAgICAgICAgIG1hbmlmZXN0ZmlsZXMucmVtb3ZlKGZpbGUpCisgICAgICAg
ICAgICBlbGlmIGZpbGUgIT0gTUFOSUZFU1Q6CisgICAgICAgICAgICAgICAgI3dhcm5pbmdzLndh
cm4oTWFsZm9ybWVkQnVuZGxlRXhjZXB0aW9uKCJCdW5kbGUgJXM6ICVzIG5vdCBpbiBNQU5JRkVT
VCIlCisgICAgICAgICAgICAgICAgIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgIChzZWxmLl9uYW1lLGZpbGUpKSkKKyAgICAgICAgICAgICAgICBpZiBzdHJpY3RfbWFuaWZl
c3Q6CisgICAgICAgICAgICAgICAgICAgIG9zLnJlbW92ZShvcy5wYXRoLmpvaW4oaW5zdGFsbF9w
YXRoLGZpbGUpKQorICAgICAgICBpZiBtYW5pZmVzdGZpbGVzOgorICAgICAgICAgICAgZXJyID0g
TWFsZm9ybWVkQnVuZGxlRXhjZXB0aW9uKCJCdW5kbGUgJXM6IGZpbGVzIGluIE1BTklGRVNUIG5v
dCBpbmNsdWRlZDogJXMiJQorICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgIChzZWxmLl9uYW1lLHN0cihtYW5pZmVzdGZpbGVzKSkpCisgICAgICAg
ICAgICBpZiBzdHJpY3RfbWFuaWZlc3Q6CisgICAgICAgICAgICAgICAgcmFpc2UgZXJyCisgICAg
ICAgICAgICBlbHNlOgorICAgICAgICAgICAgICAgIHdhcm5pbmdzLndhcm4oZXJyKQorICAgICAg
ICAKKyAgICAgICAgI2NyZWF0ZSBlbXB0eSBkaXJlY3RvcmllcworICAgICAgICBmb3IgZGlyIGlu
IHNlbGYuZ2V0X2RpcnMoKToKKyAgICAgICAgICAgIGRpcnBhdGggPSBvcy5wYXRoLmpvaW4oaW5z
dGFsbF9wYXRoLGRpcikKKyAgICAgICAgICAgIGlmIG9zLnBhdGguaXNkaXIoZGlycGF0aCk6Cisg
ICAgICAgICAgICAgICAgd2FybmluZ3Mud2FybihNYWxmb3JtZWRCdW5kbGVFeGNlcHRpb24oIkJ1
bmxkbGUgJXM6IG5vbi1lbXB0eSBkaXIgJXMgaW4gTUFOSUZFU1QiJQorICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChzZWxmLl9uYW1lLGRpcnBh
dGgpKSkKKyAgICAgICAgICAgIGVsc2U6CisgICAgICAgICAgICAgICAgb3MubWFrZWRpcnMob3Mu
cGF0aC5qb2luKGluc3RhbGxfcGF0aCxkaXIpKQogCiAgICAgICAgIHhkZ19kYXRhX2hvbWUgPSBv
cy5nZXRlbnYoJ1hER19EQVRBX0hPTUUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgIG9zLnBhdGguZXhwYW5kdXNlcignfi8ubG9jYWwvc2hhcmUnKSkKQEAgLTI2NywxMSArMzY0
LDI3IEBAIGNsYXNzIEFjdGl2aXR5QnVuZGxlKEJ1bmRsZSk6CiAgICAgICAgICAgICAgICAgICAg
IG9zLnN5bWxpbmsoaW5mb19maWxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9z
LnBhdGguam9pbihpbnN0YWxsZWRfaWNvbnNfZGlyLAogICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICBvcy5wYXRoLmJhc2VuYW1lKGluZm9fZmlsZSkpKQorICAgICAg
ICByZXR1cm4gaW5zdGFsbF9wYXRoCiAKKyAgICBkZWYgaW5zdGFsbChzZWxmKToKKyAgICAgICAg
ZnJvbSBzdWdhciBpbXBvcnQgYWN0aXZpdHkKKyAgICAgICAgZnJvbSBzdWdhciBpbXBvcnQgZW52
CisgICAgICAgIAorICAgICAgICBhY3Rpdml0aWVzX3BhdGggPSBlbnYuZ2V0X3VzZXJfYWN0aXZp
dGllc19wYXRoKCkKKyAgICAgICAgYWN0ID0gYWN0aXZpdHkuZ2V0X3JlZ2lzdHJ5KCkuZ2V0X2Fj
dGl2aXR5KHNlbGYuX2J1bmRsZV9pZCkKKyAgICAgICAgaWYgYWN0IGlzIG5vdCBOb25lIGFuZCBh
Y3QucGF0aC5zdGFydHN3aXRoKGFjdGl2aXRpZXNfcGF0aCk6CisgICAgICAgICAgICByYWlzZSBB
bHJlYWR5SW5zdGFsbGVkRXhjZXB0aW9uCisKKyAgICAgICAgaW5zdGFsbF9kaXIgPSBlbnYuZ2V0
X3VzZXJfYWN0aXZpdGllc19wYXRoKCkKKyAgICAgICAgaW5zdGFsbF9wYXRoID0gc2VsZi51bnBh
Y2soaW5zdGFsbF9kaXIpCisgICAgICAgIAogICAgICAgICBpZiBub3QgYWN0aXZpdHkuZ2V0X3Jl
Z2lzdHJ5KCkuYWRkX2J1bmRsZShpbnN0YWxsX3BhdGgpOgogICAgICAgICAgICAgcmFpc2UgUmVn
aXN0cmF0aW9uRXhjZXB0aW9uCiAKICAgICBkZWYgdW5pbnN0YWxsKHNlbGYsIGZvcmNlPUZhbHNl
KToKKyAgICAgICAgZnJvbSBzdWdhciBpbXBvcnQgYWN0aXZpdHkKKyAgICAgICAgZnJvbSBzdWdh
ciBpbXBvcnQgZW52CisgICAgICAgIAogICAgICAgICBpZiBzZWxmLl91bnBhY2tlZDoKICAgICAg
ICAgICAgIGluc3RhbGxfcGF0aCA9IHNlbGYuX3BhdGgKICAgICAgICAgZWxzZToKQEAgLTMxNiw2
ICs0MjksOSBAQCBjbGFzcyBBY3Rpdml0eUJ1bmRsZShCdW5kbGUpOgogICAgICAgICAgICAgcmFp
c2UgUmVnaXN0cmF0aW9uRXhjZXB0aW9uCiAKICAgICBkZWYgdXBncmFkZShzZWxmKToKKyAgICAg
ICAgZnJvbSBzdWdhciBpbXBvcnQgYWN0aXZpdHkKKyAgICAgICAgZnJvbSBzdWdhciBpbXBvcnQg
ZW52CisgICAgICAgIAogICAgICAgICBhY3QgPSBhY3Rpdml0eS5nZXRfcmVnaXN0cnkoKS5nZXRf
YWN0aXZpdHkoc2VsZi5fYnVuZGxlX2lkKQogICAgICAgICBpZiBhY3QgaXMgTm9uZToKICAgICAg
ICAgICAgIGxvZ2dpbmcud2FybmluZygnQWN0aXZpdHkgbm90IGluc3RhbGxlZCcpCmRpZmYgLS1n
aXQgYS9zcmMvc3VnYXIvYnVuZGxlL2J1bmRsZS5weSBiL3NyYy9zdWdhci9idW5kbGUvYnVuZGxl
LnB5CmluZGV4IDQ3ZDA4YjIuLjQ0ZTMwYjAgMTAwNjQ0Ci0tLSBhL3NyYy9zdWdhci9idW5kbGUv
YnVuZGxlLnB5CisrKyBiL3NyYy9zdWdhci9idW5kbGUvYnVuZGxlLnB5CkBAIC0xMTQsNiArMTE0
LDI5IEBAIGNsYXNzIEJ1bmRsZToKICAgICAgICAgICAgIHppcF9maWxlLmNsb3NlKCkKIAogICAg
ICAgICByZXR1cm4gZgorICAgIAorICAgIGRlZiBfaXNfZGlyKHNlbGYsIGZpbGVuYW1lKToKKyAg
ICAgICAgaWYgc2VsZi5fdW5wYWNrZWQ6CisgICAgICAgICAgICBwYXRoID0gb3MucGF0aC5qb2lu
KHNlbGYuX3BhdGgsIGZpbGVuYW1lKQorICAgICAgICAgICAgcmV0dXJuIG9zLnBhdGguaXNkaXIo
cGF0aCkKKyAgICAgICAgZWxzZToKKyAgICAgICAgICAgIHJldHVybiBUcnVlICN6aXAgZmlsZXMg
Y29udGFpbiBhbGwgZGlycyB5b3UgY2FyZSBhYm91dCEKKyAgICAgICAgICAgIAorICAgIGRlZiBf
aXNfZmlsZShzZWxmLCBmaWxlbmFtZSk6CisgICAgICAgIGlmIHNlbGYuX3VucGFja2VkOgorICAg
ICAgICAgICAgcGF0aCA9IG9zLnBhdGguam9pbihzZWxmLl9wYXRoLCBmaWxlbmFtZSkKKyAgICAg
ICAgICAgIHJldHVybiBvcy5wYXRoLmlzZmlsZShwYXRoKQorICAgICAgICBlbHNlOgorICAgICAg
ICAgICAgemlwX2ZpbGUgPSB6aXBmaWxlLlppcEZpbGUoc2VsZi5fcGF0aCkKKyAgICAgICAgICAg
IHBhdGggPSBvcy5wYXRoLmpvaW4oc2VsZi5femlwX3Jvb3RfZGlyLCBmaWxlbmFtZSkKKyAgICAg
ICAgICAgIHRyeToKKyAgICAgICAgICAgICAgICBkYXRhID0gemlwX2ZpbGUuZ2V0aW5mbyhwYXRo
KQorICAgICAgICAgICAgICAgIHJldHVybiBUcnVlCisgICAgICAgICAgICBleGNlcHQgS2V5RXJy
b3I6CisgICAgICAgICAgICAgICAgcmV0dXJuIEZhbHNlCisgICAgICAgICAgICBmaW5hbGx5Ogor
ICAgICAgICAgICAgICAgIHppcF9maWxlLmNsb3NlKCkKKyAgICAgICAgICAgICAgICAKIAogICAg
IGRlZiBnZXRfcGF0aChzZWxmKToKICAgICAgICAgIiIiR2V0IHRoZSBidW5kbGUgcGF0aC4iIiIK
LS0gCjEuNS4yLjUKCg==
------=_Part_21852_24326587.1212768784608--



More information about the Sugar-devel mailing list