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