On Sun, Jun 8, 2008 at 6:27 PM, Marco Pesenti Gritti <[EMAIL PROTECTED]>
wrote:

> Some quic
>
> * The patch does not apply cleanly to sugar-toolkit master.


I'll be more careful with my git diff.

>
> * Do you actually need to move the imports in activitybundle for
> Develop or is it just a cleanup?


No, removed.


>
>
> +IGNORE_DIRS=['dist', '.git'],
> +IGNORE_FILES=['.gitignore', 'MANIFEST', '*.pyc', '*~', '*.bak']
> +
>
> Keep these in the class they are used by.
>

check


>
> -
> +
>
> You are adding spaces there, please remove.
>
>         for f in files:
> -            if ignore_files and f not in ignore_files:
> +            if not (ignore_files and
> +                    [True for pat in ignore_files if fnmatch(f,pat)]):
> +                #not (result matches a pattern in ignore_files, ignore it)
>
> This was really hard to parse for me. It should be simpler if you just
> build the list of files with something like "files = [n for n in names
> if fnmatch(n, pattern)]" and then iterate over it.


check. I could also change the "for ..:.. append" to a "..+= [..for..] if
you want


>
>
>                     dirs.remove(ignore)
> -
>     return result
>
> No need to remove the \n  there.


>
> +    def __init__(self, source_dir=None, dist_path = ""):
>
> The dist_path split is really confusing, better to pass in both a
> dist_dir and xo_name.
> In the implementation please use an if instead of the or (like I did
> for source_dir). The or is more compact but imo makes the thing harder
> to read.


check.


>
>
> +        #list files
> +        allfiles = list_files(source_dir, ignore_dirs=IGNORE_DIRS,
> +                              ignore_files=IGNORE_FILES)
> +
> +        #add them to internal copy of MANIFEST
> +        #note: duplicates/invalids already removed when creating bundle
> object
> +        for path in allfiles:
> +            if path not in manifest:
> +                manifest.append(path)
> +
> +        #write internal copy to disk
> +        manifestfile = open(os.path.join(source_dir,"MANIFEST"),"wb")
> +        for line in manifest:
> +            manifestfile.write(line + "\n")
>
> All the comments in this block are redundant, the code is clear enough.


The thing about duplicate/invalid actually matters, leaving just that.


>
>
> +        if git_ls.wait():
> +            #fall back to MANIFEST
> +            return BuildPackager.get_files(self)
>
> Fallback to MANIFEST doesn't make sense. We should fallback to the
> list_files which you are removing instead.


check


>
>
> +        #cleanup and return
>
> Please avoid this kind of comments, they don't add anything.


check.

>
>
> +        return [file.strip() for file in files if not
> file.startswith('.')]
>
> What's the reason to skip  .* ?


This is the logic from the old version of bundlebuilder. No preference,
myself. I suspect that it is for .gitignore .

>
>
> +        #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()
>
> Setting it to None is fine, remove the comments.


I left just one comment.


>
>
> +        try:
> +            f = self.get_file("MANIFEST")
> +        except IOError:
> +            f = None
> +        if not f:
> +            logging.warning("Activity directory lacks a MANIFEST file.")
> +            return []
>
> This should be a None check... see previous review for the reasoning.


check.


>
>
> +        for num, line in enumerate(manifestlines):
> +            if line:
>
> s/manifestlines/lines, it's clear from the context.


check.


>
>
> Can line ever be None there? I haven't checked by I'd not expect
> readlines() to return Nones.


Not None; "", which is also false.


>
>
> +                #check...
> +                if line.endswith("/"):
> +                    #...dirs...
> +                    if not self.is_dir(line):
> +                        manifestlines[num] = ""
> +                        logging.warning("Bundle %s: invalid dir "
> +                                        "entry in MANIFEST: %s"
> +                                        %(self._name,line))
>
> Do we require a trailing / for directories?
>
> +        #remove trailing newlines - unlike internal newlines,
> +        #  they do not help keep absolute position
> +        while manifestlines and manifestlines[-1] == "":
> +            manifestlines = manifestlines[:-1]
> +        self.manifest = manifestlines
>
> Can you explain the absolute position comment?


It is for version control.
Version x: ["activity/activity.info", "main.py", "extra.py", "sillyname.py"]
...
rm extra.py; mv sillyname.py bettername.py
...
version x+1: ["activity/activity.info", "main.py", "", "bettername.py"]

thus the "" in the middle is useful. "" on the end, however, is not.


>
>
> +    def unpack(self, install_dir, strict_manifest=False):
>
> What is the use case of strict_manifest?


Intended to be turned on by default later, once current activities are
fixed. It will encourages proper use of MANIFEST. The principle is SPOT -
single point of truth - and it will reduce the amount of fix_manifest I have
to do in Develop to avoid data loss. fix_manifest too often defeats the
purpose of MANIFEST, we might as well be including implicitly.


>
>
> +        #check installed files against the MANIFEST
> +        manifestfiles = self.get_files(self._raw_manifest())
> +        paths  = []
> +        for root, dirs, files in os.walk(install_path):
> +            for f in files:
> +                rel_path = root[len(base_dir) + 1:]
> +                paths.append(os.path.join(rel_path, f))
> +        for path in paths:
> +            if path in manifestfiles:
> +                manifestfiles.remove(path)
> +            elif path != "MANIFEST":
> +                logging.warning("Bundle %s: %s not in MANIFEST"%
> +                                (self._name,path))
> +                if strict_manifest:
> +                    os.remove(os.path.join(install_path, path))
>
> Some \n please :)
>
> +        #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)
>
> Can you explain the reason of this?


On second thought, there is no good reason. Removed.

> Also, you left this in.
>
> +        print path

fixed

Here is the new version of the patch. Tell me if there are any problems
applying it cleanly, and I will fix.

Jameson
diff --git a/src/sugar/activity/bundlebuilder.py b/src/sugar/activity/bundlebuilder.py
index 7a89ab4..d7e4bfd 100644
--- a/src/sugar/activity/bundlebuilder.py
+++ b/src/sugar/activity/bundlebuilder.py
@@ -23,6 +23,8 @@ import subprocess
 import re
 import gettext
 from optparse import OptionParser
+import logging
+from fnmatch import fnmatch
 
 from sugar import env
 from sugar.bundle.activitybundle import ActivityBundle
@@ -31,10 +33,14 @@ def list_files(base_dir, ignore_dirs=None, ignore_files=None):
     result = []
 
     for root, dirs, files in os.walk(base_dir):
+        if ignore_files:
+            for pattern in ignore_files:
+                files = [f for f in files if not fnmatch(f, pattern)]
+                
+        rel_path = root[len(base_dir) + 1:]
         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))
+            result.append(os.path.join(rel_path, f))
+            
         if ignore_dirs and root == base_dir:
             for ignore in ignore_dirs:
                 if ignore in dirs:
@@ -43,25 +49,27 @@ def list_files(base_dir, ignore_dirs=None, ignore_files=None):
     return result
 
 class Config(object):
-    def __init__(self, bundle_name):
-        self.source_dir = os.getcwd()
-
-        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)
+    def __init__(self, source_dir=None, dist_dir = None, dist_name = None):
+        self.source_dir = source_dir or os.getcwd()
+            
+        self.bundle = bundle = ActivityBundle(self.source_dir)
+        self.version = bundle.get_activity_version()
+        self.activity_name = bundle.get_name()
         self.bundle_id = bundle.get_bundle_id()
-        self.bundle_root_dir = self.bundle_name + '.activity'
-        self.tarball_root_dir = '%s-%d' % (self.bundle_name, version)
+        self.bundle_name = reduce(lambda x, y:x+y, self.activity_name.split())
 
-        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)
+        if dist_dir:
+            self.dist_dir = dist_dir
+        else:
+            self.dist_dir = os.path.join(self.source_dir, 'dist')
+            
+        if dist_name:
+            self.xo_name = self.tar_name = dist_name
+        else:
+            self.xo_name = '%s-%d.xo' % (self.bundle_name, self.version)
+            self.tar_name = '%s-%d.tar.bz2' % (self.bundle_name, self.version)
+        self.bundle_root_dir = self.bundle_name + '.activity'
+        self.tar_root_dir = '%s-%d' % (self.bundle_name, self.version)
 
 class Builder(object):
     def __init__(self, config):
@@ -71,6 +79,10 @@ class Builder(object):
         self.build_locale()
 
     def build_locale(self):
+        if not self.config.bundle.is_dir('po'):
+            logging.warn("Activity lacks a po directory for translations")
+            return
+        
         po_dir = os.path.join(self.config.source_dir, 'po')
 
         for f in os.listdir(po_dir):
@@ -101,54 +113,74 @@ class Builder(object):
 class Packager(object):
     def __init__(self, config):
         self.config = config
-        self.dist_dir = os.path.join(self.config.source_dir, 'dist')
         self.package_path = None
 
-        if not os.path.exists(self.dist_dir):
-            os.mkdir(self.dist_dir)
+        if not os.path.exists(self.config.dist_dir):
+            os.mkdir(self.config.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.config.bundle.get_files()
+    
+    def _list_useful_files(self):
+        ignore_dirs = ['dist', '.git'],
+        ignore_files = ['.gitignore', 'MANIFEST', '*.pyc', '*~', '*.bak']
+        
+        return list_files(self.config.source_dir, ignore_dirs, ignore_files)
+        
+    def fix_manifest(self):
+        manifest = self.config.bundle.manifest
+        
+        allfiles = self._list_useful_files()
+        
+        for path in allfiles:
+            if path not in manifest:
+                manifest.append(path)
+        #note: duplicate/invalid entries already gone from bundle.manifest
+        
+        f = open(os.path.join(self.config.source_dir, "MANIFEST"), "wb")
+        for line in manifest:
+            f.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)
+        self.package_path = os.path.join(self.config.dist_dir,
+                                         self.config.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),
+            bundle_zip.write(os.path.join(self.config.source_dir, f),
                              os.path.join(self.config.bundle_root_dir, f))
 
         bundle_zip.close()
 
-class SourcePackager(Packager):
+class SourcePackager(BuildPackager):
     def __init__(self, config):
-        Packager.__init__(self, config)
-        self.package_path = os.path.join(self.dist_dir,
-                                         self.config.tarball_name)
+        BuildPackager.__init__(self, config)
+        self.package_path = os.path.join(self.config.dist_dir,
+                                         self.config.tar_name)
 
     def get_files(self):
-        return list_files(self.config.source_dir,
-                          ignore_dirs=['locale', 'dist', '.git'],
-                          ignore_files=['.gitignore'])
+        #list files in git
+        git_ls = subprocess.Popen('git-ls-files', stdout=subprocess.PIPE, 
+                                  cwd=self.config.source_dir)
+        if git_ls.wait():
+            #fall back to filtered list
+            return self._list_useful_files()
+        paths = git_ls.stdout.readlines()
+        
+        return [path.strip() for path in paths if not path.startswith('.')]
 
     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))
+                    os.path.join(self.config.tar_root_dir, f))
         tar.close()
 
 def cmd_help(config, options, args):
@@ -331,11 +363,13 @@ def cmd_build(config, options, args):
     builder = Builder(config)
     builder.build()
 
-def start(bundle_name):
+def start(bundle_name=None):
+    if bundle_name:
+        logging.warn("bundle_name deprecated, now comes from activity.info")
     parser = OptionParser()
     (options, args) = parser.parse_args()
 
-    config = Config(bundle_name)
+    config = Config()
 
     try:
         globals()['cmd_' + args[0]](config, options, args[1:])
diff --git a/src/sugar/bundle/activitybundle.py b/src/sugar/bundle/activitybundle.py
index db30555..8dba9ba 100644
--- a/src/sugar/bundle/activitybundle.py
+++ b/src/sugar/bundle/activitybundle.py
@@ -56,7 +56,7 @@ class ActivityBundle(Bundle):
         self._show_launcher = True
         self._activity_version = 0
 
-        info_file = self._get_file('activity/activity.info')
+        info_file = self.get_file('activity/activity.info')
         if info_file is None:
             raise MalformedBundleException('No activity.info file')
         self._parse_info(info_file)
@@ -65,6 +65,62 @@ class ActivityBundle(Bundle):
         if linfo_file:
             self._parse_linfo(linfo_file)
 
+        self.manifest = None #This should be replaced by following function
+        self.read_manifest()
+
+    def _raw_manifest(self):
+        f = self.get_file("MANIFEST")
+        if not f:
+            logging.warning("Activity directory lacks a MANIFEST file.")
+            return []
+        
+        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.
+        """
+        lines = self._raw_manifest()
+        for num, line in enumerate(lines):
+            if line:
+                #remove duplicates
+                if line in lines[0:num]:
+                    lines[num] = ""
+                    logging.warning("Bundle %s: duplicate entry in MANIFEST: %s"
+                                    %(self._name,line))
+                    continue
+                
+                #remove MANIFEST
+                if line == "MANIFEST":
+                    lines[num] = ""
+                    logging.warning("Bundle %s: MANIFEST includes itself: %s"
+                                    %(self._name,line))
+                    
+                #check files
+                if not self.is_file(line):
+                    lines[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 lines and lines[-1] == "":
+            lines = lines[:-1]
+        self.manifest = lines
+    
+    def get_files(self, manifest = None):
+        manifestpath = ["MANIFEST"] if self.is_file("MANIFEST") else []
+        return manifestpath + [line for line in (manifest or self.manifest) 
+                               if line]
+      
     def _parse_info(self, info_file):
         cp = ConfigParser()
         cp.readfp(info_file)
@@ -123,12 +179,12 @@ class ActivityBundle(Bundle):
             return None
 
         linfo_path = os.path.join('locale', lang, 'activity.linfo')
-        linfo_file = self._get_file(linfo_path)
+        linfo_file = self.get_file(linfo_path)
         if linfo_file is not None:
             return linfo_file
 
         linfo_path = os.path.join('locale', lang[:2], 'activity.linfo')
-        linfo_file = self._get_file(linfo_path)
+        linfo_file = self.get_file(linfo_path)
         if linfo_file is not None:
             return linfo_file
 
@@ -180,7 +236,7 @@ class ActivityBundle(Bundle):
         if self._unpacked:
             return os.path.join(self._path, icon_path)
         else:
-            icon_data = self._get_file(icon_path).read()
+            icon_data = self.get_file(icon_path).read()
             temp_file, temp_file_path = tempfile.mkstemp(self._icon)
             os.write(temp_file, icon_data)
             os.close(temp_file)
@@ -220,17 +276,38 @@ class ActivityBundle(Bundle):
             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)
+        
+        #list installed files
+        manifestfiles = self.get_files(self._raw_manifest())
+        paths  = []
+        for root, dirs, files in os.walk(install_path):
+            rel_path = root[len(install_path) + 1:]
+            for f in files:
+                paths.append(os.path.join(rel_path, f))
+                
+        #check list against the MANIFEST
+        for path in paths:
+            if path in manifestfiles:
+                manifestfiles.remove(path)
+            elif path != "MANIFEST":
+                logging.warning("Bundle %s: %s not in MANIFEST"%
+                                (self._name,path))
+                if strict_manifest:
+                    os.remove(os.path.join(install_path, path))
+                    
+        #Is anything in MANIFEST left over after accounting for all files?
+        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)
 
         xdg_data_home = os.getenv('XDG_DATA_HOME',
                                   os.path.expanduser('~/.local/share'))
@@ -267,11 +344,21 @@ class ActivityBundle(Bundle):
                     os.symlink(info_file,
                                os.path.join(installed_icons_dir,
                                             os.path.basename(info_file)))
+        return install_path
 
+    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()
+        install_path = self.unpack(install_dir)
+        
         if not activity.get_registry().add_bundle(install_path):
             raise RegistrationException
 
-    def uninstall(self, force=False):
+    def uninstall(self, force=False):        
         if self._unpacked:
             install_path = self._path
         else:
diff --git a/src/sugar/bundle/bundle.py b/src/sugar/bundle/bundle.py
index 47d08b2..90b511b 100644
--- a/src/sugar/bundle/bundle.py
+++ b/src/sugar/bundle/bundle.py
@@ -96,13 +96,15 @@ class Bundle:
                     'All files in the bundle must be inside a single ' +
                     'top-level directory')
 
-    def _get_file(self, filename):
+    def get_file(self, filename):
         f = None
 
         if self._unpacked:
             path = os.path.join(self._path, filename)
-            if os.path.isfile(path):
-                f = open(path)
+            try:
+                f = open(path,"rb")
+            except IOError:
+                return None
         else:
             zip_file = zipfile.ZipFile(self._path)
             path = os.path.join(self._zip_root_dir, filename)
@@ -114,6 +116,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