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':
209 # XXX: hack for 0launch < 0.54 which messes up the namespace
210 if elem
.name
== 'implementation':
216 metadir
= self
.config
.get('compile', 'metadir')
217 assert not os
.path
.isabs(metadir
)
218 return join(self
.distdir
, metadir
)
221 def local_iface_file(self
):
222 return join(self
.metadir
, self
.iface_name
+ '.xml')
225 def target_arch(self
):
226 temp
= self
.get_binary_template()
227 arch
= temp
and temp
.getAttribute('arch')
228 return arch
or get_arch_name()
231 def version_modifier(self
):
232 vm
= self
.config
.get('compile', 'version-modifier')
239 def archive_stem(self
):
240 # Use the version that we actually built, not the version we would build now
241 feed
= self
.load_built_feed()
242 assert len(feed
.implementations
) == 1
243 version
= feed
.implementations
.values()[0].get_version()
245 # Don't use the feed's name, as it may contain the version number
246 name
= feed
.get_name().lower().replace(' ', '-')
247 arch
= self
.target_arch
.lower().replace('*-*', 'bin').replace('*', 'any')
249 return '%s-%s-%s' % (name
, arch
, version
)
251 def load_built_feed(self
):
252 path
= self
.local_iface_file
255 feed
= model
.ZeroInstallFeed(qdom
.parse(stream
), local_path
= path
)
260 def load_built_selections(self
):
261 path
= join(self
.metadir
, 'build-environment.xml')
262 if os
.path
.exists(path
):
265 return selections
.Selections(qdom
.parse(stream
))
271 def download_base_url(self
):
272 return self
.config
.get('compile', 'download-base-url')
274 def chosen_impl(self
, uri
):
275 sels
= self
.get_selections()
276 assert uri
in sels
.selections
277 return sels
.selections
[uri
]
280 def local_download_iface(self
):
281 impl
, = self
.load_built_feed().implementations
.values()
282 return '%s-%s.xml' % (self
.iface_name
, impl
.get_version())
285 stream
= file(ENV_FILE
, 'w')
287 self
.config
.write(stream
)
291 def get_selections(self
, prompt
= False):
294 return self
._selections
296 selections_file
= self
.config
.get('compile', 'selections')
299 raise SafeException("Selections are fixed by %s" % selections_file
)
300 stream
= file(selections_file
)
302 self
._selections
= selections
.Selections(qdom
.parse(stream
))
305 from zeroinstall
.injector
import handler
, policy
307 h
= handler
.ConsoleHandler()
309 h
= handler
.Handler()
310 config
= policy
.load_config(h
)
311 blocker
= self
._selections
.download_missing(config
)
313 print "Waiting for selected implementations to be downloaded..."
314 h
.wait_for_blocker(blocker
)
316 command
= install_prog
+ ['download', '--source', '--xml']
317 if prompt
and '--console' not in install_prog
:
320 command
.append('--gui')
321 command
.append(self
.interface
)
322 child
= subprocess
.Popen(command
, stdout
= subprocess
.PIPE
)
324 self
._selections
= selections
.Selections(qdom
.parse(child
.stdout
))
327 raise SafeException(' '.join(repr(x
) for x
in command
) + " failed (exit code %d)" % child
.returncode
)
329 self
.root_impl
= self
._selections
.selections
[self
.interface
]
331 self
.orig_srcdir
= os
.path
.realpath(lookup(self
.root_impl
))
332 self
.user_srcdir
= None
334 if os
.path
.isdir('src'):
335 self
.user_srcdir
= os
.path
.realpath('src')
336 if self
.user_srcdir
== self
.orig_srcdir
or \
337 self
.user_srcdir
.startswith(os
.path
.join(self
.orig_srcdir
, '')) or \
338 self
.orig_srcdir
.startswith(os
.path
.join(self
.user_srcdir
, '')):
339 info("Ignoring 'src' directory because it coincides with %s",
341 self
.user_srcdir
= None
343 return self
._selections
345 def get_build_changes(self
):
346 sels
= self
.get_selections()
347 old_sels
= self
.load_built_selections()
350 # See if things have changed since the last build
351 all_ifaces
= set(sels
.selections
) |
set(old_sels
.selections
)
353 old_impl
= old_sels
.selections
.get(x
, no_impl
)
354 new_impl
= sels
.selections
.get(x
, no_impl
)
355 if old_impl
.version
!= new_impl
.version
:
356 changes
.append("Version change for %s: %s -> %s" % (x
, old_impl
.version
, new_impl
.version
))
357 elif old_impl
.id != new_impl
.id:
358 changes
.append("Version change for %s: %s -> %s" % (x
, old_impl
.id, new_impl
.id))
362 root
= node
.ownerDocument
.documentElement
364 while node
and node
is not root
:
365 node
= node
.parentNode
370 if s
== 'true': return True
371 if s
== 'false': return False
372 raise SafeException('Expected "true" or "false" but got "%s"' % s
)
375 # Copied from 0launch 0.54 (remove once 0.54 is released)
376 def __init__(self
, default_ns
):
378 self
.default_ns
= default_ns
381 prefix
= self
.prefixes
.get(ns
, None)
384 prefix
= 'ns%d' % len(self
.prefixes
)
385 self
.prefixes
[ns
] = prefix
388 def setAttributeNS(self
, elem
, uri
, localName
, value
):
390 elem
.setAttributeNS(None, localName
, value
)
392 elem
.setAttributeNS(uri
, self
.get(uri
) + ':' + localName
, value
)
394 def createElementNS(self
, doc
, uri
, localName
):
395 if uri
== self
.default_ns
:
396 return doc
.createElementNS(uri
, localName
)
398 return doc
.createElementNS(uri
, self
.get(uri
) + ':' + localName
)