oops, I left out the more complete patch. As I said,

bundleManifest.all.patch /*here*/ = (bundleMANIFEST.patch /*15K, included in
msg 3 of this thread*/ + bundleManifest.subpatch /*included in previous
message*/)
diff --git a/src/sugar/activity/bundlebuilder.py b/src/sugar/activity/bundlebuilder.py
index 1063f72..0ee0b8a 100644
--- a/src/sugar/activity/bundlebuilder.py
+++ b/src/sugar/activity/bundlebuilder.py
@@ -19,64 +19,43 @@ import os
 import zipfile
 import tarfile
 import shutil
-import subprocess
+from subprocess import Popen, PIPE
 import re
 import gettext
 from optparse import OptionParser
+import subprocess
+import logging
 
 from sugar import env
-from sugar.bundle.activitybundle import ActivityBundle
-
-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 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):
-        if source_dir:
-            self.source_dir = source_dir
-        else:
-            self.source_dir = os.getcwd()
-            
+from sugar.bundle.activitybundle import ActivityBundle, MANIFEST, list_files
 
-        bundle = ActivityBundle(self.source_dir)
+class RawActivity(object):
+    def __init__(self, source_dir=None, dist_path = ""):
+        self.source_dir = source_dir or os.getcwd()
+            
+        self.bundle = bundle = ActivityBundle(self.source_dir)
         version = bundle.get_activity_version()
-
-        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)
+        self.activity_name = bundle.get_name()
         self.bundle_id = bundle.get_bundle_id()
+        self.bundle_name = reduce(lambda x, y:x+y, self.activity_name.split())
+
+        dist_dir, dist_name = os.path.split(dist_path)
+        self.dist_dir = dist_dir or os.path.join(self.source_dir, 'dist')
+        self.xo_name = dist_name or '%s-%d.xo' % (self.bundle_name, version)
+        self.tarball_name = (dist_name or 
+                             '%s-%d.tar.bz2' % (self.bundle_name, version))
         self.bundle_root_dir = self.bundle_name + '.activity'
         self.tarball_root_dir = '%s-%d' % (self.bundle_name, version)
-
-        info_path = os.path.join(self.source_dir, 'activity', 'activity.info')
-        f = open(info_path,'r')
-        info = f.read()
-        f.close()
-        match = re.search('^name\s*=\s*(.*)$', info, flags = re.MULTILINE)
-        self.activity_name = match.group(1)
-
-class Builder(object):
-    def __init__(self, config):
-        self.config = config
-
+    
+class Builder(RawActivity):
     def build(self):
         self.build_locale()
 
     def build_locale(self):
-        po_dir = os.path.join(self.config.source_dir, 'po')
-
+        if not self.bundle._is_dir('po'):
+            logging.warn("Activity lacks a po directory for translations")
+            return
+        po_dir = os.path.join(self.source_dir, 'po')
         for f in os.listdir(po_dir):
             if not f.endswith('.po'):
                 continue
@@ -84,78 +63,92 @@ class Builder(object):
             file_name = os.path.join(po_dir, f)
             lang = f[:-3]
 
-            localedir = os.path.join(self.config.source_dir, 'locale', lang)
+            localedir = os.path.join(self.source_dir, 'locale', lang)
             mo_path = os.path.join(localedir, 'LC_MESSAGES')
             if not os.path.isdir(mo_path):
                 os.makedirs(mo_path)
 
-            mo_file = os.path.join(mo_path, "%s.mo" % self.config.bundle_id)
+            mo_file = os.path.join(mo_path, "%s.mo" % self.bundle_id)
             args = ["msgfmt", "--output-file=%s" % mo_file, file_name]
             retcode = subprocess.call(args)
             if retcode:
                 print 'ERROR - msgfmt failed with return code %i.' % retcode
 
             cat = gettext.GNUTranslations(open(mo_file, 'r'))
-            translated_name = cat.gettext(self.config.activity_name)
+            translated_name = cat.gettext(self.activity_name)
             linfo_file = os.path.join(localedir, 'activity.linfo')
             f = open(linfo_file, 'w')
             f.write('[Activity]\nname = %s\n' % translated_name)
             f.close()
 
-class Packager(object):
-    def __init__(self, config):
-        self.config = config
-        self.dist_dir = os.path.join(self.config.source_dir, 'dist')
+class Packager(RawActivity):
+    def __init__(self, *args, **kw):
+        RawActivity.__init__(self, *args, **kw)
         self.package_path = None
-
         if not os.path.exists(self.dist_dir):
             os.mkdir(self.dist_dir)
             
 
 class BuildPackager(Packager):
-    def __init__(self, config):
-        Packager.__init__(self, config)
-        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.bundle.get_files()
+    
+    def fix_manifest(self):
+        allfiles =  list_files(self.source_dir,
+                          ignore_dirs=['dist', '.git'],
+                          ignore_files=['.gitignore', 'MANIFEST', 
+                                        '*.pyc', '*~', '*.bak'])
+        for afile in allfiles:
+            if afile not in self.bundle.manifest:
+                self.bundle.manifest.append(afile)
+        manifestfile = open(os.path.join(self.source_dir,
+                                         MANIFEST),
+                            "wb")
+        for line in self.bundle.manifest:
+            manifestfile.write(line + "\n")
 
 class XOPackager(BuildPackager):
-    def __init__(self, config):
-        BuildPackager.__init__(self, config)
-        self.package_path = os.path.join(self.dist_dir, self.config.xo_name)
+    def __init__(self, *args, **kw):
+        BuildPackager.__init__(self, *args, **kw)
+        self.package_path = os.path.join(self.dist_dir,
+                                         self.xo_name)
 
     def package(self):
         bundle_zip = zipfile.ZipFile(self.package_path, 'w',
                                      zipfile.ZIP_DEFLATED)
-        
         for f in self.get_files():
-            bundle_zip.write(os.path.join(self.build_dir, f),
-                             os.path.join(self.config.bundle_root_dir, f))
-
+            bundle_zip.write(os.path.join(self.source_dir, f),
+                             os.path.join(self.bundle_root_dir, f))
         bundle_zip.close()
 
-class SourcePackager(Packager):
-    def __init__(self, config):
-        Packager.__init__(self, config)
+class SourcePackager(BuildPackager):
+    def __init__(self, *args, **kw):
+        BuildPackager.__init__(self, *args, **kw)
         self.package_path = os.path.join(self.dist_dir,
-                                         self.config.tarball_name)
+                                         self.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.source_dir)
+        if git_ls.wait():
+            #non-0 return code - failed
+            return BuildPackager.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")
         for f in self.get_files():
-            tar.add(os.path.join(self.config.source_dir, f),
-                    os.path.join(self.config.tarball_root_dir, f))
+            tar.add(os.path.join(self.source_dir, f),
+                    os.path.join(self.tarball_root_dir, f))
         tar.close()
 
-def cmd_help(config, options, args):
+def cmd_help(options, args):
     print 'Usage: \n\
 setup.py build               - build generated files \n\
 setup.py dev                 - setup for development \n\
@@ -168,11 +161,12 @@ setup.py release             - do a new release of the bundle \n\
 setup.py help                - print this message \n\
 '
 
-def cmd_dev(config, options, args):
+def cmd_dev(options, args):
+    config = RawActivity()
     bundle_path = env.get_user_activities_path()
     if not os.path.isdir(bundle_path):
         os.mkdir(bundle_path)
-    bundle_path = os.path.join(bundle_path, config.bundle_top_dir)
+    bundle_path = os.path.join(bundle_path, config.bundle_root_dir)
     try:
         os.symlink(config.source_dir, bundle_path)
     except OSError:
@@ -181,24 +175,24 @@ def cmd_dev(config, options, args):
         else:
             print 'ERROR - A bundle with the same name is already installed.'
 
-def cmd_dist_xo(config, options, args):
-    builder = Builder(config)
+def cmd_dist_xo(options, args):
+    builder = Builder()
     builder.build()
 
-    packager = XOPackager(config)
+    packager = XOPackager()
     packager.package()
 
-def cmd_dist_source(config, options, args):
-    packager = SourcePackager(config)
+def cmd_dist_source(options, args):
+    packager = SourcePackager()
     packager.package()
 
-def cmd_install(config, options, args):
+def cmd_install(options, args):
     path = args[0]
 
-    packager = XOPackager(config)
+    packager = XOPackager()
     packager.package()
 
-    root_path = os.path.join(args[0], config.bundle_root_dir)
+    root_path = os.path.join(args[0], packager.bundle_root_dir)
     if os.path.isdir(root_path):
         shutil.rmtree(root_path)
 
@@ -217,7 +211,8 @@ def cmd_install(config, options, args):
         outfile.flush()
         outfile.close()
 
-def cmd_genpot(config, options, args):
+def cmd_genpot(options, args):
+    config = RawActivity()
     po_path = os.path.join(config.source_dir, 'po')
     if not os.path.isdir(po_path):
         os.mkdir(po_path)
@@ -249,7 +244,8 @@ def cmd_genpot(config, options, args):
     if retcode:
         print 'ERROR - xgettext failed with return code %i.' % retcode
 
-def cmd_release(config, options, args):
+def cmd_release(options, args):
+    config = XOPackager()
     if not os.path.isdir('.git'):
         print 'ERROR - this command works only for git repositories'
 
@@ -326,25 +322,27 @@ def cmd_release(config, options, args):
         print 'ERROR - cannot push to git'
 
     print 'Creating the bundle...'
-    packager = XOPackager(config)
+    packager = config
     packager.package()
 
     print 'Done.'
 
-def cmd_build(config, options, args):
-    builder = Builder(config)
+def cmd_build(options, args):
+    builder = Builder()
     builder.build()
 
-def start(bundle_name):
+def start(obsolete_bundle_name = None):
+    if obsolete_bundle_name:
+        logging.warn("You do not need to pass a bundle name to bundlebuilder."
+                     " Bundle name comes from activity.info.")
+        
     parser = OptionParser()
     (options, args) = parser.parse_args()
 
-    config = Config(bundle_name)
-
     try:
-        globals()['cmd_' + args[0]](config, options, args[1:])
+        globals()['cmd_' + args[0]](options, args[1:])
     except (KeyError, IndexError):
-        cmd_help(config, options, args)
+        cmd_help(options, args)
 
 if __name__ == '__main__':
     start()
diff --git a/src/sugar/bundle/activitybundle.py b/src/sugar/bundle/activitybundle.py
index db30555..610314e 100644
--- a/src/sugar/bundle/activitybundle.py
+++ b/src/sugar/bundle/activitybundle.py
@@ -21,16 +21,32 @@ 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
 
+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,76 @@ class ActivityBundle(Bundle):
         linfo_file = self._get_linfo_file()
         if linfo_file:
             self._parse_linfo(linfo_file)
-
+        
+        #we can either do
+        #pyXXXXlint: disable-msg=W0201
+        #disables "Attribute %r defined outside __init__" msg.
+        #(remove XXXX to enable), or we can do:
+        self.manifest = None #which is meaningless but shuts pylint up.
+        self.read_manifest()
+
+    def _raw_manifest(self):
+        try:
+            f = self._get_file(MANIFEST)
+        except IOError:
+            f = None
+        if not f:
+            logging.warning("Activity directory lacks a MANIFEST file.")
+            return []
+        
+        #strip trailing \n and other whitespace
+        ret = [line.strip() for line in f.readlines()] 
+        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] = ""
+                    logging.warning("Bundle %s: duplicate entry in MANIFEST: %s"
+                                                  %(self._name,line))
+                    continue
+                if line == MANIFEST:
+                    manifestlines[num] = ""
+                    logging.warning("Bundle %s: MANIFEST includes itself: %s"
+                                                  %(self._name,line))
+                if line.endswith("/"):
+                    if not self._is_dir(line):
+                        manifestlines[num] = ""
+                        logging.warning("Bundle %s: invalid dir "
+                                        "entry in MANIFEST: %s"
+                                        %(self._name,line))
+                else:
+                    if not self._is_file(line):
+                        manifestlines[num] = ""
+                        logging.warning("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 and manifestlines[-1] == "":
+            manifestlines = manifestlines[:-1]
+        self.manifest = manifestlines
+    
+    def get_files(self, manifest = None):
+        manifest = [MANIFEST] if self._is_file(MANIFEST) else [] 
+        return manifest + [line for line in (manifest or self.manifest) 
+                             if line and not line.endswith("/")]
+    
+    def get_dirs(self):
+        return [line for line in self.manifest
+                if line.endswith("/")]
+      
     def _parse_info(self, info_file):
         cp = ConfigParser()
         cp.readfp(info_file)
@@ -116,6 +201,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 +295,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 afile in list_files(install_path):
+            if afile in manifestfiles:
+                manifestfiles.remove(file)
+            elif afile != MANIFEST:
+                logging.warning("Bundle %s: %s not in MANIFEST"%
+                                (self._name,file))
+                if strict_manifest:
+                    os.remove(os.path.join(install_path, file))
+        if manifestfiles:
+            err = ("Bundle %s: files in MANIFEST not included: %s"%
+                   (self._name,str(manifestfiles)))
+            if strict_manifest:
+                raise MalformedBundleException(err)
+            else:
+                logging.warning(err)
+        
+        #create empty directories
+        for adir in self.get_dirs():
+            dirpath = os.path.join(install_path, adir)
+            if os.path.isdir(dirpath):
+                logging.warning("Bunldle %s: non-empty dir %s in MANIFEST"%
+                                                       (self._name,dirpath))
+            else:
+                os.makedirs(dirpath)
 
         xdg_data_home = os.getenv('XDG_DATA_HOME',
                                   os.path.expanduser('~/.local/share'))
@@ -267,11 +378,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 +443,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..95756d4 100644
--- a/src/sugar/bundle/bundle.py
+++ b/src/sugar/bundle/bundle.py
@@ -114,6 +114,30 @@ 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:
+                zip_file.getinfo(path)
+                #no exception above, so:
+                return True
+            except KeyError:
+                return False
+            finally:
+                zip_file.close()
+                
 
     def get_path(self):
         """Get the bundle path."""
_______________________________________________
Sugar mailing list
[email protected]
http://lists.laptop.org/listinfo/sugar

Reply via email to