Fixed name of unit-test class
[0export.git] / 0export
blobfcf5668062060da653128ed1eb9f52f484fdea5c
1 #!/usr/bin/env python
2 # Copyright (C) 2010, Thomas Leonard
3 # See the README file for details, or visit http://0install.net.
5 from optparse import OptionParser
6 import os, sys, tempfile, tarfile
7 from logging import warn, info, debug
9 ZEROINSTALL_URI = "http://0install.net/2007/interfaces/ZeroInstall.xml"
11 zeroinstall_dir = os.environ.get('0EXPORT_ZEROINSTALL', None)
12 if zeroinstall_dir:
13 sys.path.insert(1, zeroinstall_dir)
15 version = '0.5'
17 extract_header = """#!/bin/sh
18 # 0export %s
19 archive_offset=00000
20 archive_format=application/x-tar
21 package_version=2
22 if [ -x /usr/bin/python2 ]; then
23 PYTHON=/usr/bin/python2
24 else
25 PYTHON=python
27 exec "$PYTHON" - "$0" "$archive_offset" "$@" << EOF
28 """ + file(os.path.join(os.path.dirname(__file__), 'unpacker.py')).read() + """
29 EOF
30 exit 1
31 """
33 parser = OptionParser(usage="usage: %prog setup.sh URI...\n"
34 "Create setup.sh self-extracting installer for program(s) URI...")
36 parser.add_option("-j", "--injector", help="use custom zeroinstall-injector url", default=ZEROINSTALL_URI)
37 parser.add_option("-n", "--net-install", help="lightweight installer mode; bundle only injector, passed interface will be downloaded at run time", action='store_true')
38 parser.add_option("-a", "--arch", help="add a target architecture (os-cpu)", metavar='CPU', action='append')
39 parser.add_option("-v", "--verbose", help="more verbose output", action='count')
40 parser.add_option("-V", "--version", help="display version information", action='store_true')
42 (options, args) = parser.parse_args()
44 if options.version:
45 print "0export (zero-install) " + version
46 print "Copyright (C) 2010 Thomas Leonard"
47 print "This program comes with ABSOLUTELY NO WARRANTY,"
48 print "to the extent permitted by law."
49 print "You may redistribute copies of this program"
50 print "under the terms of the GNU General Public License."
51 print "For more information about these matters, see the file named COPYING."
52 sys.exit(0)
54 if options.verbose:
55 import logging
56 logger = logging.getLogger()
57 if options.verbose == 1:
58 logger.setLevel(logging.INFO)
59 else:
60 logger.setLevel(logging.DEBUG)
62 if len(args) < 2:
63 parser.print_help()
64 sys.exit(1)
66 from zeroinstall import support
67 from zeroinstall.support import tasks
68 from zeroinstall.zerostore import BadDigest
69 from zeroinstall.injector import model, arch, solver
70 from zeroinstall.injector.config import load_config
71 from zeroinstall.injector.requirements import Requirements
72 from zeroinstall.injector.driver import Driver
73 from zeroinstall import SafeException
74 import utils
76 setup_file = args[0]
77 uris = [model.canonical_iface_uri(x) for x in args[1:]]
79 if options.arch:
80 def get_arch(arch_str):
81 if '-' not in arch_str:
82 raise Exception("Architecture syntax is OS-CPU (e.g. 'Linux-86_64')")
83 os, cpu = arch_str.split('-', 1)
84 if os in ("*", "any"): os = None
85 if cpu in ("*", "any"): cpu = None
86 return arch.get_architecture(os, cpu)
87 archs = [get_arch(a) for a in options.arch]
88 else:
89 archs = [arch.get_host_architecture()]
91 class ExportSolver(solver.SATSolver):
92 def get_rating(self, interface, impl, arch):
93 # Always prefer non-native packages, as only these can be included in a bundle
94 return [not impl.id.startswith('package:')] + solver.SATSolver.get_rating(self, interface, impl, arch)
96 solver.DefaultSolver = ExportSolver
98 config = load_config()
100 def choose_implementations(uris, only_feeds):
101 interfaces_used = set() # URI
102 feeds_used = set() # URI
103 implementations_used = {} # ID -> Implementation
104 uri_impls = {} # Maps top-level URIs to implementations
106 for uri in uris:
107 print "Choosing versions for %s..." % uri
108 versions = 0
109 for target_arch in archs:
110 requirements = Requirements(uri)
111 driver = Driver(config, requirements)
112 driver.target_arch = target_arch
114 # Don't let us choose local devel versions
115 driver.solver.extra_restrictions = utils.NoLocalRestrictions(uris)
117 solved = driver.solve_with_downloads()
118 tasks.wait_for_blocker(solved)
120 if not driver.solver.ready:
121 if len(archs) > 1:
122 warn("Failed to select any version for %s for architecture %s" % (uri, target_arch))
123 continue
124 versions += 1
126 feeds_used = feeds_used | driver.solver.feeds_used
127 iface_cache = config.iface_cache
129 if not only_feeds:
130 for iface, impl in driver.solver.selections.items():
131 if impl.id.startswith('package:'):
132 debug('Skip package implementation %r', impl)
133 continue
134 print " %-10s : %s (%s)" % (iface.get_name(), impl.get_version(), impl.arch or 'any arch')
135 assert impl.digests, "Implementation %s has no digests!" % impl
136 implementations_used[impl.digests[0]] = impl
138 downloads = driver.download_uncached_implementations()
139 if downloads:
140 print "Downloading implementations..."
141 tasks.wait_for_blocker(downloads)
143 uri_impls[uri] = driver.solver.selections[iface_cache.get_interface(uri)].id
145 for iface, impl in driver.solver.selections.items():
146 interfaces_used.add(iface.uri)
148 if not versions:
149 raise SafeException("Failed to select a set of versions for %s" % uri)
151 for iface_uri in interfaces_used:
152 if iface_uri.startswith('/'): continue
153 iface = iface_cache.get_interface(iface_uri)
154 icon = iface_cache.get_icon_path(iface)
155 if icon is None:
156 download_icon = config.fetcher.download_icon(iface)
157 if download_icon:
158 print "Downloading icon..."
159 tasks.wait_for_blocker(download_icon)
161 return (feeds_used, implementations_used, uri_impls)
163 bootstrap_uris = [options.injector]
165 try:
166 (bootstrap_feeds_used, bootstrap_implementations_used, bootstrap_uri_impls) = choose_implementations(bootstrap_uris, False)
167 (feeds_used, implementations_used, uri_impls) = choose_implementations(uris, options.net_install)
169 keys_used = set()
171 print "Building package..."
173 tmp = tempfile.mkdtemp(prefix = '0export-')
174 bootstrap_tmp = tempfile.mkdtemp(prefix = '0export-')
175 try:
176 # Add feeds...
177 utils.export_feeds(bootstrap_tmp, bootstrap_feeds_used | feeds_used, keys_used)
179 # Add implementations...
180 utils.export_impls(bootstrap_tmp, bootstrap_implementations_used)
182 os.symlink(os.path.join('implementations', bootstrap_uri_impls[options.injector]), os.path.join(bootstrap_tmp, 'zeroinstall'))
184 # Add keys...
185 keys_dir = os.path.join(bootstrap_tmp, 'keys')
186 os.mkdir(keys_dir)
187 for key in keys_used:
188 utils.export_key(key, keys_dir)
190 # Add installer...
191 mydir = os.path.dirname(os.path.abspath(sys.argv[0]))
193 install_code = file(os.path.join(mydir, 'install.py')).read()
194 install_code = install_code.replace('@ZEROINSTALL_URI@', options.injector)
195 install_file = file(os.path.join(bootstrap_tmp, 'install.py'), 'w')
196 install_file.write(install_code)
197 install_file.close()
199 # Record the toplevel interfaces (not those brought in only as dependencies)
200 # These are the programs the installer will offer to run
201 toplevels = file(os.path.join(bootstrap_tmp, 'toplevel_uris'), 'w')
202 for uri in uris:
203 toplevels.write(uri + '\n')
204 toplevels.close()
206 # Create an archive with bootstrap data
207 bootstrap_path = os.path.join(tmp, 'bootstrap.tar.bz2')
208 ts = tarfile.open(bootstrap_path, 'w|bz2')
209 ts.add(bootstrap_tmp, '.')
210 ts.close()
212 # Collect all other implementations in separate archives in the archive
213 impl_files = []
214 for impl in set(implementations_used) - set(bootstrap_implementations_used):
215 impltmp = tempfile.mkdtemp(prefix = '0export-')
216 try:
217 utils.export_impls(impltmp, {impl : implementations_used[impl]})
218 implpath = os.path.join(tmp, impl + '.tar.bz2')
219 ts = tarfile.open(implpath, 'w|bz2')
220 ts.add(str(os.path.join(impltmp, 'implementations', impl)), '.')
221 ts.close()
222 finally:
223 support.ro_rmtree(impltmp)
224 impl_files.append(implpath)
226 extract_header = extract_header.replace('@INSTALLER_MODE@', str(options.net_install))
227 extract_header = extract_header.replace('00000', "%05d" % len(extract_header))
228 setup_stream = file(setup_file, 'wb')
229 setup_stream.write(extract_header)
230 setup_tar = tarfile.open(setup_file, 'w|', setup_stream)
231 setup_tar.add(bootstrap_path, os.path.basename(bootstrap_path))
232 for impl in impl_files:
233 setup_tar.add(impl, str('implementations/' + os.path.basename(impl)))
234 setup_tar.close()
235 setup_stream.close()
236 os.chmod(setup_file, (os.stat(setup_file).st_mode & 0777) | 0111)
237 finally:
238 support.ro_rmtree(bootstrap_tmp)
239 support.ro_rmtree(tmp)
240 except BadDigest, ex:
241 print >>sys.stderr, str(ex)
242 if ex.detail:
243 print >>sys.stderr, ex.detail
244 sys.exit(1)
245 except SafeException, ex:
246 print >>sys.stderr, str(ex)
247 sys.exit(1)