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