summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndoni Morales Alastruey <ylatuya@gmail.com>2012-06-25 11:57:14 +0200
committerAndoni Morales Alastruey <ylatuya@gmail.com>2012-06-25 12:27:34 +0200
commit344fbc9721a2e721e831a3c5895e911977ba7ed1 (patch)
tree9938af87cc254a61804e32bd104b18c00d47e568
parentbb092276471acd4cb1b08a26749c62df456b2795 (diff)
Add initial support for application packages
-rw-r--r--cerbero/packages/osx/bundles.py181
-rw-r--r--cerbero/packages/osx/packager.py96
-rw-r--r--cerbero/packages/package.py106
-rw-r--r--cerbero/packages/packagesstore.py5
-rw-r--r--test/test_build_common.py1
-rw-r--r--test/test_cerbero_packages_package.py49
-rw-r--r--test/test_packages_common.py19
7 files changed, 447 insertions, 10 deletions
diff --git a/cerbero/packages/osx/bundles.py b/cerbero/packages/osx/bundles.py
new file mode 100644
index 00000000..32165fd0
--- /dev/null
+++ b/cerbero/packages/osx/bundles.py
@@ -0,0 +1,181 @@
+# cerbero - a multi-platform build system for Open Source software
+# Copyright (C) 2012 Andoni Morales Alastruey <ylatuya@gmail.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import os
+import tempfile
+import shutil
+from stat import S_IXUSR, S_IXGRP, S_IXOTH
+
+from cerbero.packages import PackagerBase
+from cerbero.packages.osx.packagemaker import PackageMaker
+from cerbero.packages.osx.info_plist import FrameworkPlist, ApplicationPlist
+from cerbero.utils import shell
+
+
+class BundlePackagerBase(PackagerBase):
+ '''
+ Creates a package with the basic structure of a bundle, to be included
+ in a MetaPackage.
+ '''
+
+ name = ''
+ title = ''
+
+ def __init__(self, config, package, store):
+ PackagerBase.__init__(self, config, package, store)
+
+ def pack(self, output_dir):
+ output_dir = os.path.realpath(output_dir)
+ if not os.path.exists(output_dir):
+ os.makedirs(output_dir)
+
+ self.install_dir = self.package.get_install_dir()
+
+ path = self._create_package(output_dir, self.package.get_install_dir(),
+ self.package.version)
+ return [path, None]
+
+
+ def _create_package(self, output_dir, install_dir, version):
+ output_file = os.path.join(output_dir, '%s-%s-%s.pkg' %
+ (self.name, self.package.version,
+ self.config.target_arch))
+ root = self.create_bundle()
+ packagemaker = PackageMaker()
+ packagemaker.create_package(root, self.package_name,
+ self.package.version, self.title, output_file,
+ install_dir, target=None)
+ return output_file
+
+ def get_install_dir(self):
+ '''
+ Get the installation directory
+ '''
+ raise NotImplemented('Subclasses should implement get_install_dir')
+
+ def create_bundle(self):
+ '''
+ Creates the bundle structure
+ '''
+ raise NotImplemented('Subclasses should implement create_bundle')
+
+
+class FrameworkBundlePackager(BundlePackagerBase):
+ '''
+ Creates a package with the basic structure of a framework bundle,
+ adding links for Headears, Libraries, Commands, and Current Versions,
+ and the Framework info.
+ '''
+
+ name = 'osx-framework'
+ title = 'Framework Bundle'
+
+ def __init__(self, config, package, store):
+ BundlePackagerBase.__init__(self, config, package, store)
+
+ def get_install_dir(self):
+ return os.path.join(self.install_dir, 'Versions',
+ self.package.version, self.config.target_arch)
+
+ def create_bundle(self):
+ '''
+ Creates the bundle structure
+
+ Commands -> Versions/Current/bin
+ Headers -> Versions/Current/Headers
+ Librarires -> Versions/Current/lib
+ Home -> Versions/Current
+ Resources -> Versions/Current/Resources
+ Versions/Current -> Version/$VERSION/$ARCH
+ '''
+ tmp = tempfile.mkdtemp()
+
+ vdir = 'Versions/%s/%s' % (self.package.version,
+ self.config.target_arch)
+ rdir = '%s/Resources/' % vdir
+ shell.call ('mkdir -p %s' % rdir, tmp)
+ links = {'Versions/Current': '../%s' % vdir,
+ 'Resources': 'Versions/Current/Resources',
+ 'Commands': 'Versions/Current/bin',
+ 'Headers': 'Versions/Current/Headers',
+ 'Libraries': 'Versions/Current/lib'}
+ framework_plist = FrameworkPlist(self.package.name,
+ self.package.org, self.package.version, self.package.shortdesc)
+ framework_plist.save(os.path.join(tmp, rdir, 'Info.plist'))
+ for dest, src in links.iteritems():
+ shell.call ('ln -s %s %s' % (src, dest), tmp)
+ if self.package.osx_framework_library is not None:
+ name, link = self.package.osx_framework_library
+ link = os.path.join('Versions', 'Current', link)
+ shell.call ('ln -s %s %s' % (link, name), tmp)
+ return tmp
+
+
+class ApplicationBundlePackager(BundlePackagerBase):
+ '''
+ Creates a package with the basic structure of an Application bundle.
+ '''
+
+ def __init__(self, config, package, store):
+ BundlePackagerBase.__init__(self, config, package, store)
+
+ def get_install_dir(self):
+ return self.install_dir
+
+ def create_bundle(self, tmp=None):
+ '''
+ Creates the Application bundle structure
+
+ Contents/MacOS/MainExectuable -> Contents/Home/bin/main-executable
+ Contents/Info.plist
+ '''
+ tmp = tmp or tempfile.mkdtemp()
+
+ contents = os.path.join(tmp, 'Contents')
+ macos = os.path.join(contents, 'MacOS')
+ resources = os.path.join(contents, 'Resources')
+ for p in [contents, macos, resources]:
+ if not os.path.exists(p):
+ os.makedirs(p)
+
+ # Create Contents/Info.plist
+ framework_plist = ApplicationPlist(self.package.name,
+ self.package.org, self.package.version, self.package.shortdesc,
+ os.path.basename(self.package.resources_icon_icns))
+ framework_plist.save(os.path.join(contents, 'Info.plist'))
+
+ # Copy app icon to Resources
+ shutil.copy(self.package.resources_icon_icns, resources)
+
+ # Link or create a wrapper for the executables in Contents/MacOS
+ for name, path, use_wrapper, wrapper in self.package.get_commands():
+ filename = os.path.join(macos, name)
+ if use_wrapper:
+ wrapper = self.package.get_wrapper(path, wrapper)
+ if not wrapper:
+ continue
+ with open(filename, 'w') as f:
+ f.write(wrapper)
+ shell.call('chmod +x %s' % filename)
+ else:
+ # FIXME: We need to copy the binary instead of linking, because
+ # beeing a different path, @executable_path will be different
+ # and it we will need to set a different relative path with
+ # install_name_tool
+ shutil.copy(os.path.join(contents, 'Home', path), macos)
+ return tmp
diff --git a/cerbero/packages/osx/packager.py b/cerbero/packages/osx/packager.py
index 2ccdea20..85f9ebfe 100644
--- a/cerbero/packages/osx/packager.py
+++ b/cerbero/packages/osx/packager.py
@@ -23,10 +23,12 @@ import shutil
from cerbero.ide.pkgconfig import PkgConfig
from cerbero.errors import EmptyPackageError
from cerbero.packages import PackagerBase, PackageType
-from cerbero.packages.package import Package, PackageBase
+from cerbero.packages.package import Package, MetaPackage, App, PackageBase
from cerbero.packages.osx.pmdoc import PMDoc
-from cerbero.packages.osx.bundles import FrameworkBundlePackager
+from cerbero.packages.osx.bundles import FrameworkBundlePackager,\
+ ApplicationBundlePackager
from cerbero.packages.osx.packagemaker import PackageMaker
+from cerbero.tools.osxrelocator import OSXRelocator
from cerbero.utils import shell, _
from cerbero.utils import messages as m
@@ -302,6 +304,94 @@ class PMDocPackage(PackagerBase):
shell.call(cmd)
+class ApplicationPackage(PackagerBase):
+ '''
+ Creates an osx package from a L{cerbero.packages.package.Package}
+
+ @ivar package: package used to create the osx package
+ @type package: L{cerbero.packages.package.Package}
+ '''
+
+ def __init__(self, config, package, store):
+ PackagerBase.__init__(self, config, package, store)
+
+ def pack(self, output_dir, devel=False, force=False, keep_temp=False):
+ PackagerBase.pack(self, output_dir, devel, force, keep_temp)
+
+ self.tmp = tempfile.mkdtemp()
+ self.tmp = os.path.join(self.tmp, '%s.app' % self.package.app_name)
+
+ # copy files to the bundle. it needs to be done first because the app
+ # bundle will try to create links for the main executable
+ self._create_bundle()
+ self._create_app_bundle()
+ self._strip_binaries()
+ self._relocate_binaries()
+ dmg = self._create_dmg()
+
+ return [dmg, None]
+
+ def _create_bundle(self):
+ '''
+ Moves all the files that are going to be packaged to the bundle's
+ temporary directory
+ '''
+ out_dir = os.path.join(self.tmp, 'Contents', 'Home')
+ os.makedirs(out_dir)
+ for f in self.package.files_list():
+ in_path = os.path.join(self.config.prefix, f)
+ if not os.path.exists(in_path):
+ m.warning("File %s is missing and won't be added to the "
+ "package" % in_path)
+ continue
+ out_path = os.path.join(out_dir, f)
+ odir = os.path.split(out_path)[0]
+ if not os.path.exists(odir):
+ os.makedirs(odir)
+ shutil.copy(in_path, out_path)
+
+ def _create_app_bundle(self):
+ ''' Creates the OS X Application bundle in temporary directory '''
+ packager = ApplicationBundlePackager(self.config, self.package,
+ self.store)
+ return packager.create_bundle(self.tmp)
+
+ def _strip_binaries(self):
+ pass
+
+ def _relocate_binaries(self):
+ prefix = self.config.prefix
+ if prefix[-1] == '/':
+ prefix = prefix[:-1]
+ for path in ['bin']:
+ relocator = OSXRelocator(
+ os.path.join(self.tmp, 'Contents', 'Home', path),
+ self.config.prefix, '@executable_path/../', True)
+ relocator.relocate()
+ for path in ['lib', 'libexec']:
+ relocator = OSXRelocator(
+ os.path.join(self.tmp, 'Contents', 'Home', path),
+ self.config.prefix, '@loader_path/../', True)
+ relocator.relocate()
+ relocator = OSXRelocator(
+ os.path.join(self.tmp, 'Contents', 'MacOS', path),
+ self.config.prefix, '@executable_path/../Home/', False)
+ relocator.relocate()
+
+ def _create_dmg(self):
+ #applications_link = os.path.join(self.tmp, 'Applications')
+ #shell.call('ln -s /Applications %s' % applications_link)
+ # Create link to /Applications
+ dmg_file = os.path.join(self.output_dir, '%s-%s-%s.dmg' % (
+ self.package.app_name, self.package.version, self.config.target_arch))
+ # Create Disk Image
+ cmd = 'hdiutil create %s -volname %s -ov -srcfolder %s' % \
+ (dmg_file, self.package.app_name, self.tmp)
+ shell.call(cmd)
+ return dmg_file
+
+
+
class Packager(object):
def __new__(klass, config, package, store):
@@ -309,6 +399,8 @@ class Packager(object):
return OSXPackage(config, package, store)
elif isinstance(package, MetaPackage):
return PMDocPackage(config, package, store)
+ elif isinstance(package, App):
+ return ApplicationPackage(config, package, store)
def register():
diff --git a/cerbero/packages/package.py b/cerbero/packages/package.py
index 17f8990f..fc6b7617 100644
--- a/cerbero/packages/package.py
+++ b/cerbero/packages/package.py
@@ -55,8 +55,10 @@ class PackageBase(object):
@type resources_license: str
@cvar resources_license_rtf: filename of .rtf license file
@type resources_license_rtf: str
- @cvar resources_icon: filename of the icon image
+ @cvar resources_icon: filename of the .ico icon
@type resources_icon: str
+ @cvar resources_icon_icns: filename of the .icsn icon
+ @type resources_icon_icns: str
@cvar resources_backgound = filename of the background image
@type resources_backgound = str
'''
@@ -75,6 +77,7 @@ class PackageBase(object):
resources_license = 'license.txt'
resources_license_rtf = 'license.txt'
resources_icon = 'icon.ico'
+ resources_icon_icns = 'icon.icns'
resources_background = 'background.png'
def __init__(self, config, store):
@@ -292,8 +295,6 @@ class MetaPackage(PackageBase):
@type packages: list
@cvar platform_packages: list of platform packages
@type platform_packages: dict
- @cvar icon: filename of the package icon
- @type icon: str
@cvar root_env_var: name of the environment variable with the prefix
@type root_env_var: str
@cvar sdk_version: SDK version. This version will be used for the SDK
@@ -401,3 +402,102 @@ class InstallerPackage(MetaPackage):
def __init__(self, config, store):
MetaPackage.__init__(self, config, store)
+
+
+class App(PackageBase):
+ '''
+ Create packages for applications.
+ An App package will not include development files and binaries could
+ be stripped when required. The App packager will not create a development
+ version.
+ On linux it will work in the same way as a MetaPackage, creating a package
+ with the application's recipe files and adding packages dependencies to be
+ managed by the distribution's package manager.
+ On OS X and Windows, the dependencies could be embeded in the installer
+ itself, creating an Application bundle on OS X and main menu shortcuts on
+ Windows, relocating the binaries properly.
+
+ @cvar app_recipe: Name used for the application
+ @type app_recipe: str
+ @cvar app_recipe: recipe that builds the application project
+ @type app_recipe: str
+ @cvar deps: list of packages dependencies
+ @type deps: list
+ @cvar embed_deps: include dependencies in the final package
+ @type embed_deps: boolean
+ @cvar commands: a list of with the application commands. The first will be
+ used for the main executable
+ @type command: list
+ @cvar wrapper: suffix filename for the main executable wrapper
+ @type wrapper: str
+ '''
+
+ app_name = None
+ app_recipe = None
+ embed_deps = True
+ deps = []
+ commands = [] # list of tuples ('CommandName', path/to/binary')
+ wrapper = 'app_wrapper.tpl'
+
+ def __init__(self, config, store, cookbook):
+ PackageBase.__init__(self, config, store)
+ self.cookbook = cookbook
+ self._app_recipe = self.cookbook.get_recipe(self.app_recipe)
+
+ def recipes_dependencies(self):
+ deps = []
+ for dep in self.deps:
+ for package in self.store.get_package_deps(dep, True):
+ deps.extend(package.recipes_dependencies())
+ return list(set(deps))
+
+ def files_list(self):
+ # for each package, call the function that list files
+ files = []
+ if self.embed_deps and self.config.target_platform != Platform.LINUX:
+ packages_deps = [self.store.get_package(x) for x in self.deps]
+ for package in packages_deps:
+ packages_deps.extend(self.store.get_package_deps(package))
+ packages_deps = list(set(packages_deps))
+ for package in packages_deps:
+ files.extend(package.files_list())
+ files.extend(self._app_recipe.files_list())
+ files.sort()
+ return files
+
+ def devel_files_list(self):
+ return []
+
+ def all_files_list(self):
+ return self.files_list()
+
+ def get_wix_upgrade_code(self):
+ m = self.package_mode
+ p = self.config.target_arch
+ return self.wix_upgrade_code[m][p]
+
+ def get_commands(self):
+ return self.commands
+
+ def get_wrapper(self, cmd, wrapper=None):
+ if self.config.target_platform == Platform.WINDOWS:
+ platform = 'win'
+ else:
+ platform = 'unix'
+
+ if wrapper is not None:
+ wrapper_file = self.relative_path('%s_%s' % (platform, wrapper))
+ else:
+ wrapper_file = os.path.join(self.config.data_dir, 'templates',
+ '%s_%s' % (self.wrapper, platform))
+
+ if not os.path.exists(wrapper_file):
+ return None
+
+ with open(wrapper_file, 'r') as f:
+ content = f.read()
+ content = content % {'prefix': self.config.prefix,
+ 'py_prefix': self.config.py_prefix,
+ 'cmd': self.config.prefix}
+
+ return content
diff --git a/cerbero/packages/packagesstore.py b/cerbero/packages/packagesstore.py
index d7afc063..5279b5cd 100644
--- a/cerbero/packages/packagesstore.py
+++ b/cerbero/packages/packagesstore.py
@@ -196,8 +196,11 @@ class PackagesStore (object):
p = d['SDKPackage'](self._config, self)
elif 'InstallerPackage' in d:
p = d['InstallerPackage'](self._config, self)
+ elif 'App' in d:
+ p = d['App'](self._config, self, self.cookbook)
else:
- raise Exception('Package or MetaPackage class found')
+ raise Exception('Package, SDKPackage, InstallerPackage or App '
+ 'class not found')
p.prepare()
# reload files from package now that we called prepare that
# may have changed it
diff --git a/test/test_build_common.py b/test/test_build_common.py
index e2cfa608..7a0e08a6 100644
--- a/test/test_build_common.py
+++ b/test/test_build_common.py
@@ -92,6 +92,7 @@ def add_files(tmp):
'README2 '
'README3 '
'README4 '
+ 'README5 '
'bin/gst-launch.exe '
'bin/gst-launch '
'bin/windows.exe '
diff --git a/test/test_cerbero_packages_package.py b/test/test_cerbero_packages_package.py
index dc4e3083..7344a4a2 100644
--- a/test/test_cerbero_packages_package.py
+++ b/test/test_cerbero_packages_package.py
@@ -22,7 +22,7 @@ import tempfile
from cerbero.config import Platform, Distro, DistroVersion
from cerbero.packages import PackageType
-from test.test_packages_common import Package1, Package4, MetaPackage
+from test.test_packages_common import Package1, Package4, MetaPackage, App
from test.test_build_common import create_cookbook, add_files
from test.test_packages_common import create_store
from test.test_common import DummyConfig
@@ -111,8 +111,6 @@ class PackageTest(unittest.TestCase):
self.assertEquals(package.get_sys_deps(), ['python27'])
-
-
class TestMetaPackages(unittest.TestCase):
def setUp(self):
@@ -158,3 +156,48 @@ class TestMetaPackages(unittest.TestCase):
def testAllFilesList(self):
self._compareList('all_files_list')
+
+
+class AppPackageTest(unittest.TestCase):
+
+ def setUp(self):
+ self.tmp = tempfile.mkdtemp()
+ config = Config(self.tmp, Platform.LINUX)
+ self.store = create_store(config)
+ self.cookbook = create_cookbook(config)
+ self.app = App(config, self.store, self.cookbook)
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp)
+
+ def testListFilesWithEmbededDepsOnLinux(self):
+ self.app.embed_deps = True
+ expected = self.app._app_recipe.files_list()
+ result = self.app.files_list()
+ self.assertEquals(expected, result)
+
+ def testListFilesWithEmbededDeps(self):
+ self.app.embed_deps = True
+ self.app.config.target_platform = Platform.WINDOWS
+ files = []
+ packages_deps = [self.store.get_package(x) for x in self.app.deps]
+ for dep in self.app.deps:
+ packages_deps.extend(self.store.get_package_deps(dep))
+ packages_deps = list(set(packages_deps))
+ for package in packages_deps:
+ files.extend(package.files_list())
+ files.extend(self.app._app_recipe.files_list())
+ files = sorted(set(files))
+ self.assertEquals(files, self.app.files_list())
+
+ def testListFilesWithoutEmbededDeps(self):
+ self.app.embed_deps = False
+ expected = self.app._app_recipe.files_list()
+ result = self.app.files_list()
+ self.assertEquals(expected, result)
+
+ def testDevelFilesList(self):
+ self.assertEquals(self.app.devel_files_list(), [])
+
+ def testAllFilesList(self):
+ self.assertEquals(self.app.files_list(), self.app.all_files_list())
diff --git a/test/test_packages_common.py b/test/test_packages_common.py
index 33ff2a70..adc9d385 100644
--- a/test/test_packages_common.py
+++ b/test/test_packages_common.py
@@ -96,6 +96,23 @@ class MetaPackage(package.MetaPackage):
icon = "gstreamer.ico"
+class App(package.App):
+
+ name = "gstreamer-app"
+ shortdesc = "GStreamer sample app"
+ longdesc = "GStreamer sample app"
+ title = "GStreamer sample app"
+ url = "http://www.gstreamer.net"
+ version = '1.0'
+ uuid = '3ffe67b2-4565-411f-8287-e8faa892f853'
+ vendor = "GStreamer Project"
+ org = 'net.gstreamer'
+ app_recipe = 'recipe3'
+ deps = ['gstreamer-test1']
+ icon = "share/images/gstreamer.png"
+ embed_deps = True
+
+
class DummyConfig(object):
pass
@@ -104,7 +121,7 @@ def create_store(config):
cookbook = create_cookbook(config)
store = PackagesStore(config, False)
- for klass in [Package1, Package2, Package3, Package4]:
+ for klass in [Package1, Package2, Package3, Package4, App]:
package = klass(config, store, cookbook)
store.add_package(package)
for klass in [MetaPackage]: