1 # Copyright (C) 2006, Thomas Leonard
2 # See http://0install.net/0compile.html
6 from os
.path
import join
7 from logging
import info
10 from zeroinstall
.injector
import model
, selections
, qdom
, arch
11 from zeroinstall
.injector
.arch
import canonicalize_os
, canonicalize_machine
13 from zeroinstall
.injector
.iface_cache
import iface_cache
14 from zeroinstall
import SafeException
15 from zeroinstall
.zerostore
import Store
, NotStored
20 # This is An os.uname() substitute that uses as much of ZI's
21 # arch._uname as is available and yet has all four elements one
22 # normally expects from os.uname() on Posix (on Windows, arch._uname
23 # has only two elements).
25 uname
= arch
._uname
+ platform
.uname()[len(arch
._uname
):]
27 ENV_FILE
= '0compile.properties'
28 XMLNS_0COMPILE
= 'http://zero-install.sourceforge.net/2006/namespaces/0compile'
30 zeroinstall_dir
= os
.environ
.get('0COMPILE_ZEROINSTALL', None)
32 # XXX: we're assuming that, if installed through 0install, 0launch requires
33 # the same version of Python as 0compile. This is currently needed for Arch
34 # Linux, but long-term we need to use the <runner>.
35 install_prog
= [sys
.executable
, os
.path
.join(zeroinstall_dir
, '0install')]
36 if not os
.path
.exists(install_prog
[1]):
37 # For the Windows version...
38 install_prog
[1] = os
.path
.join(zeroinstall_dir
, 'zeroinstall', 'scripts', 'install.py')
40 install_prog
= ['0install']
42 if os
.path
.isdir('dependencies'):
43 dep_dir
= os
.path
.realpath('dependencies')
44 iface_cache
.stores
.stores
.append(Store(dep_dir
))
45 install_prog
.append('--with-store='+ dep_dir
)
52 def is_package_impl(impl
):
53 return impl
.id.startswith("package:")
55 def lookup(impl_or_sel
):
57 if id.startswith('package:'):
59 local_path
= impl_or_sel
.local_path
60 if local_path
is not None:
61 if os
.path
.isdir(local_path
):
63 raise SafeException("Directory '%s' no longer exists. Try '0compile setup'" % local_path
)
65 return iface_cache
.stores
.lookup_any(impl_or_sel
.digests
)
67 raise NotStored(str(ex
) + "\nHint: try '0compile setup'")
69 def ensure_dir(d
, clean
= False):
77 raise SafeException("'%s' exists, but is not a directory!" % d
)
80 def find_in_path(prog
):
81 for d
in os
.environ
['PATH'].split(':'):
82 path
= os
.path
.join(d
, prog
)
83 if os
.path
.isfile(path
):
87 def spawn_and_check(prog
, args
):
88 status
= os
.spawnv(os
.P_WAIT
, prog
, [prog
] + args
)
90 raise SafeException("Program '%s' failed with exit code %d" % (prog
, status
))
92 raise SafeException("Program '%s' failed with signal %d" % (prog
, -status
))
94 def spawn_and_check_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
):
95 child
= spawn_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
)
98 raise SafeException('Command failed with exit status %d' % status
)
100 raise SafeException('Command failed with signal %d' % -status
)
102 def spawn_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
):
103 """spawn prog, with (only) the 'writable' directories writable if sandboxing is available.
104 The readable directories will be readable, as well as various standard locations.
105 If no sandbox is available, run without a sandbox."""
107 USE_PLASH
= 'USE_PLASH_0COMPILE'
109 assert os
.path
.isabs(prog
)
110 _pola_run
= find_in_path('pola-run')
112 if _pola_run
is None:
113 #print "Not using sandbox (plash not installed)"
116 use_plash
= os
.environ
.get(USE_PLASH
, '').lower() or 'not set'
117 if use_plash
in ('not set', 'false'):
118 print "Not using plash: $%s is %s" % (USE_PLASH
, use_plash
)
120 elif use_plash
== 'true':
123 raise Exception('$%s must be "true" or "false", not "%s"' % (USE_PLASH
, use_plash
))
126 return subprocess
.Popen([prog
] + args
)
128 print "Using plash to sandbox the build..."
130 # We have pola-shell :-)
131 pola_args
= ['--prog', prog
, '-B']
133 pola_args
+= ['-a', a
]
135 pola_args
+= ['-f', r
]
137 pola_args
+= ['-fw', w
]
138 pola_args
+= ['-tw', '/tmp', tmpdir
]
139 os
.environ
['TMPDIR'] = '/tmp'
140 return subprocess
.Popen([_pola_run
] + pola_args
)
143 target_os
= canonicalize_os(uname
[0])
144 target_machine
= canonicalize_machine(uname
[4])
145 if target_os
== 'Darwin' and target_machine
== 'i386':
146 # this system detection shell script comes from config.guess (20090918):
147 CC
= os
.getenv("CC_FOR_BUILD") or os
.getenv("CC") or os
.getenv("HOST_CC") or "cc"
148 process
= subprocess
.Popen("(echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | " +
149 "(CCOPTS= %s -E - 2>/dev/null) | " % CC
+
150 "grep IS_64BIT_ARCH >/dev/null", stdout
=subprocess
.PIPE
, shell
=True)
151 output
, error
= process
.communicate()
152 retcode
= process
.poll()
154 target_machine
='x86_64'
155 if target_machine
in ('i585', 'i686'):
156 target_machine
= 'i486' # (sensible default)
157 return target_os
+ '-' + target_machine
160 def __init__(self
, need_config
= True):
161 if need_config
and not os
.path
.isfile(ENV_FILE
):
162 raise SafeException("Run 0compile from a directory containing a '%s' file" % ENV_FILE
)
164 self
.config
= ConfigParser
.RawConfigParser()
165 self
.config
.add_section('compile')
166 self
.config
.set('compile', 'download-base-url', '')
167 self
.config
.set('compile', 'version-modifier', '')
168 self
.config
.set('compile', 'interface', '')
169 self
.config
.set('compile', 'selections', '')
170 self
.config
.set('compile', 'metadir', '0install')
171 self
.config
.set('compile', 'distdir', '')
173 self
.config
.read(ENV_FILE
)
175 self
._selections
= None
180 def iface_name(self
):
181 iface_name
= os
.path
.basename(self
.interface
)
182 if iface_name
.endswith('.xml'):
183 iface_name
= iface_name
[:-4]
184 iface_name
= iface_name
.replace(' ', '-')
185 if iface_name
.endswith('-src'):
186 iface_name
= iface_name
[:-4]
189 interface
= property(lambda self
: model
.canonical_iface_uri(self
.config
.get('compile', 'interface')))
193 distdir_name
= self
.config
.get('compile', 'distdir')
195 arch
= self
.target_arch
.replace('*', 'any')
196 distdir_name
= self
.iface_name
.lower()
197 distdir_name
+= '-' + arch
.lower()
198 assert os
.path
.dirname(distdir_name
) == ''
199 return os
.path
.realpath(distdir_name
)
201 def get_binary_template(self
):
202 """Find the <compile:implementation> element for the selected compile command, if any"""
203 sels
= self
.get_selections()
205 for elem
in sels
.commands
[0].qdom
.childNodes
:
206 if elem
.uri
== XMLNS_0COMPILE
and elem
.name
== 'implementation':
212 metadir
= self
.config
.get('compile', 'metadir')
213 assert not os
.path
.isabs(metadir
)
214 return join(self
.distdir
, metadir
)
217 def local_iface_file(self
):
218 return join(self
.metadir
, 'feed.xml')
221 def target_arch(self
):
222 temp
= self
.get_binary_template()
223 arch
= temp
and temp
.getAttribute('arch')
224 return arch
or get_arch_name()
227 def version_modifier(self
):
228 vm
= self
.config
.get('compile', 'version-modifier')
235 def archive_stem(self
):
236 # Use the version that we actually built, not the version we would build now
237 feed
= self
.load_built_feed()
238 assert len(feed
.implementations
) == 1
239 version
= feed
.implementations
.values()[0].get_version()
241 # Don't use the feed's name, as it may contain the version number
242 name
= feed
.get_name().lower().replace(' ', '-')
243 arch
= self
.target_arch
.lower().replace('*-*', 'bin').replace('*', 'any')
245 return '%s-%s-%s' % (name
, arch
, version
)
247 def load_built_feed(self
):
248 path
= self
.local_iface_file
251 feed
= model
.ZeroInstallFeed(qdom
.parse(stream
), local_path
= path
)
256 def load_built_selections(self
):
257 path
= join(self
.metadir
, 'build-environment.xml')
258 if os
.path
.exists(path
):
261 return selections
.Selections(qdom
.parse(stream
))
267 def download_base_url(self
):
268 return self
.config
.get('compile', 'download-base-url')
270 def chosen_impl(self
, uri
):
271 sels
= self
.get_selections()
272 assert uri
in sels
.selections
273 return sels
.selections
[uri
]
276 def local_download_iface(self
):
277 impl
, = self
.load_built_feed().implementations
.values()
278 return '%s-%s.xml' % (self
.iface_name
, impl
.get_version())
281 stream
= file(ENV_FILE
, 'w')
283 self
.config
.write(stream
)
287 def get_selections(self
, prompt
= False):
290 return self
._selections
292 selections_file
= self
.config
.get('compile', 'selections')
295 raise SafeException("Selections are fixed by %s" % selections_file
)
296 stream
= file(selections_file
)
298 self
._selections
= selections
.Selections(qdom
.parse(stream
))
301 from zeroinstall
.injector
import handler
302 from zeroinstall
.injector
.config
import load_config
304 h
= handler
.ConsoleHandler()
306 h
= handler
.Handler()
307 config
= load_config(h
)
308 blocker
= self
._selections
.download_missing(config
)
310 print "Waiting for selected implementations to be downloaded..."
311 h
.wait_for_blocker(blocker
)
313 command
= install_prog
+ ['download', '--source', '--xml']
314 if prompt
and '--console' not in install_prog
:
317 command
.append('--gui')
318 command
.append(self
.interface
)
319 child
= subprocess
.Popen(command
, stdout
= subprocess
.PIPE
)
321 self
._selections
= selections
.Selections(qdom
.parse(child
.stdout
))
324 raise SafeException(' '.join(repr(x
) for x
in command
) + " failed (exit code %d)" % child
.returncode
)
326 self
.root_impl
= self
._selections
.selections
[self
.interface
]
328 self
.orig_srcdir
= os
.path
.realpath(lookup(self
.root_impl
))
329 self
.user_srcdir
= None
331 if os
.path
.isdir('src'):
332 self
.user_srcdir
= os
.path
.realpath('src')
333 if self
.user_srcdir
== self
.orig_srcdir
or \
334 self
.user_srcdir
.startswith(os
.path
.join(self
.orig_srcdir
, '')) or \
335 self
.orig_srcdir
.startswith(os
.path
.join(self
.user_srcdir
, '')):
336 info("Ignoring 'src' directory because it coincides with %s",
338 self
.user_srcdir
= None
340 return self
._selections
342 def get_build_changes(self
):
343 sels
= self
.get_selections()
344 old_sels
= self
.load_built_selections()
347 # See if things have changed since the last build
348 all_ifaces
= set(sels
.selections
) |
set(old_sels
.selections
)
350 old_impl
= old_sels
.selections
.get(x
, no_impl
)
351 new_impl
= sels
.selections
.get(x
, no_impl
)
352 if old_impl
.version
!= new_impl
.version
:
353 changes
.append("Version change for %s: %s -> %s" % (x
, old_impl
.version
, new_impl
.version
))
354 elif old_impl
.id != new_impl
.id:
355 changes
.append("Version change for %s: %s -> %s" % (x
, old_impl
.id, new_impl
.id))
359 root
= node
.ownerDocument
.documentElement
361 while node
and node
is not root
:
362 node
= node
.parentNode
367 if s
== 'true': return True
368 if s
== 'false': return False
369 raise SafeException('Expected "true" or "false" but got "%s"' % s
)
372 # Copied from 0launch 0.54 (remove once 0.54 is released)
373 def __init__(self
, default_ns
):
375 self
.default_ns
= default_ns
378 prefix
= self
.prefixes
.get(ns
, None)
381 prefix
= 'ns%d' % len(self
.prefixes
)
382 self
.prefixes
[ns
] = prefix
385 def setAttributeNS(self
, elem
, uri
, localName
, value
):
387 elem
.setAttributeNS(None, localName
, value
)
389 elem
.setAttributeNS(uri
, self
.get(uri
) + ':' + localName
, value
)
391 def createElementNS(self
, doc
, uri
, localName
):
392 if uri
== self
.default_ns
:
393 return doc
.createElementNS(uri
, localName
)
395 return doc
.createElementNS(uri
, self
.get(uri
) + ':' + localName
)