Fixed --arch
[0export.git] / 0export
blob19fb43ed07f91e2956ff672d200898af67a92375
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 """ + version + """
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 (os, cpu)
87 archs = [get_arch(a) for a in options.arch]
88 else:
89 archs = [None]
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 if target_arch is not None:
113 requirements.os = target_arch[0]
114 requirements.cpu = target_arch[1]
116 # Don't let us choose local devel versions
117 driver.solver.extra_restrictions = utils.NoLocalRestrictions(uris)
119 solved = driver.solve_with_downloads()
120 tasks.wait_for_blocker(solved)
122 if not driver.solver.ready:
123 if len(archs) > 1:
124 warn("Failed to select any version for %s for architecture %s" % (uri, target_arch))
125 continue
126 versions += 1
128 feeds_used = feeds_used | driver.solver.feeds_used
129 iface_cache = config.iface_cache
131 if not only_feeds:
132 for iface, impl in driver.solver.selections.items():
133 if impl.id.startswith('package:'):
134 debug('Skip package implementation %r', impl)
135 continue
136 print " %-10s : %s (%s)" % (iface.get_name(), impl.get_version(), impl.arch or 'any arch')
137 assert impl.digests, "Implementation %s has no digests!" % impl
138 implementations_used[impl.digests[0]] = impl
140 downloads = driver.download_uncached_implementations()
141 if downloads:
142 print "Downloading implementations..."
143 tasks.wait_for_blocker(downloads)
145 uri_impls[uri] = driver.solver.selections[iface_cache.get_interface(uri)].id
147 for iface, impl in driver.solver.selections.items():
148 interfaces_used.add(iface.uri)
150 if not versions:
151 raise SafeException("Failed to select a set of versions for %s" % uri)
153 for iface_uri in interfaces_used:
154 if iface_uri.startswith('/'): continue
155 iface = iface_cache.get_interface(iface_uri)
156 icon = iface_cache.get_icon_path(iface)
157 if icon is None:
158 download_icon = config.fetcher.download_icon(iface)
159 if download_icon:
160 print "Downloading icon..."
161 tasks.wait_for_blocker(download_icon)
163 return (feeds_used, implementations_used, uri_impls)
165 bootstrap_uris = [options.injector]
167 try:
168 (bootstrap_feeds_used, bootstrap_implementations_used, bootstrap_uri_impls) = choose_implementations(bootstrap_uris, False)
169 (feeds_used, implementations_used, uri_impls) = choose_implementations(uris, options.net_install)
171 keys_used = set()
173 print "Building package..."
175 tmp = tempfile.mkdtemp(prefix = '0export-')
176 bootstrap_tmp = tempfile.mkdtemp(prefix = '0export-')
177 try:
178 # Add feeds...
179 utils.export_feeds(bootstrap_tmp, bootstrap_feeds_used | feeds_used, keys_used)
181 # Add implementations...
182 utils.export_impls(bootstrap_tmp, bootstrap_implementations_used)
184 os.symlink(os.path.join('implementations', bootstrap_uri_impls[options.injector]), os.path.join(bootstrap_tmp, 'zeroinstall'))
186 # Add keys...
187 keys_dir = os.path.join(bootstrap_tmp, 'keys')
188 os.mkdir(keys_dir)
189 for key in keys_used:
190 utils.export_key(key, keys_dir)
192 # Add installer...
193 mydir = os.path.dirname(os.path.abspath(sys.argv[0]))
195 install_code = file(os.path.join(mydir, 'install.py')).read()
196 install_file = file(os.path.join(bootstrap_tmp, 'install.py'), 'w')
197 install_file.write(install_code)
198 install_file.close()
200 # Record the toplevel interfaces (not those brought in only as dependencies)
201 # These are the programs the installer will offer to run
202 toplevels = file(os.path.join(bootstrap_tmp, 'toplevel_uris'), 'w')
203 for uri in uris:
204 toplevels.write(uri + '\n')
205 toplevels.close()
207 # Create an archive with bootstrap data
208 bootstrap_path = os.path.join(tmp, 'bootstrap.tar.bz2')
209 ts = tarfile.open(bootstrap_path, 'w|bz2')
210 ts.add(bootstrap_tmp, '.')
211 ts.close()
213 # Collect all other implementations in separate archives in the archive
214 impl_files = []
215 for impl in set(implementations_used) - set(bootstrap_implementations_used):
216 impltmp = tempfile.mkdtemp(prefix = '0export-')
217 try:
218 utils.export_impls(impltmp, {impl : implementations_used[impl]})
219 implpath = os.path.join(tmp, impl + '.tar.bz2')
220 ts = tarfile.open(implpath, 'w|bz2')
221 ts.add(str(os.path.join(impltmp, 'implementations', impl)), '.')
222 ts.close()
223 finally:
224 support.ro_rmtree(impltmp)
225 impl_files.append(implpath)
227 extract_header = extract_header.replace('@INSTALLER_MODE@', str(options.net_install))
228 extract_header = extract_header.replace('00000', "%05d" % len(extract_header))
229 setup_stream = file(setup_file, 'wb')
230 setup_stream.write(extract_header)
231 setup_tar = tarfile.open(setup_file, 'w|', setup_stream)
232 setup_tar.add(bootstrap_path, os.path.basename(bootstrap_path))
233 for impl in impl_files:
234 setup_tar.add(impl, str('implementations/' + os.path.basename(impl)))
235 setup_tar.close()
236 setup_stream.close()
237 os.chmod(setup_file, (os.stat(setup_file).st_mode & 0777) | 0111)
238 finally:
239 support.ro_rmtree(bootstrap_tmp)
240 support.ro_rmtree(tmp)
241 except BadDigest, ex:
242 print >>sys.stderr, str(ex)
243 if ex.detail:
244 print >>sys.stderr, ex.detail
245 sys.exit(1)
246 except SafeException, ex:
247 print >>sys.stderr, str(ex)
248 sys.exit(1)