1 # Copyright (C) 2006, Thomas Leonard
2 # See http://0install.net/0compile.html
4 import sys
, os
, __main__
, time
, shutil
, glob
, codecs
5 from os
.path
import join
6 from logging
import info
7 from xml
.dom
import minidom
, XMLNS_NAMESPACE
8 from optparse
import OptionParser
13 os
.environ
[name
] = value
14 print "%s=%s" % (name
, value
)
16 def do_env_binding(binding
, path
):
17 env(binding
.name
, binding
.get_value(path
, os
.environ
.get(binding
.name
, None)))
19 def do_build_internal(options
, args
):
21 # If a sandbox is being used, we're in it now.
22 import getpass
, socket
, time
25 sels
= buildenv
.get_selections()
27 builddir
= os
.path
.realpath('build')
28 ensure_dir(buildenv
.metadir
)
30 build_env_xml
= join(buildenv
.metadir
, 'build-environment.xml')
32 buildenv_doc
= buildenv
.get_selections().toDOM()
34 # Create build-environment.xml file
35 root
= buildenv_doc
.documentElement
36 info
= buildenv_doc
.createElementNS(XMLNS_0COMPILE
, 'build-info')
37 root
.appendChild(info
)
38 info
.setAttributeNS(None, 'time', time
.strftime('%Y-%m-%d %H:%M').strip())
39 info
.setAttributeNS(None, 'host', socket
.getfqdn())
40 info
.setAttributeNS(None, 'user', getpass
.getuser())
42 info
.setAttributeNS(None, 'arch', '%s-%s' % (uname
[0], uname
[4]))
43 stream
= file(build_env_xml
, 'w')
44 buildenv_doc
.writexml(stream
, addindent
=" ", newl
="\n")
47 # Create local binary interface file
48 src_iface
= iface_cache
.get_interface(buildenv
.interface
)
49 src_impl
= buildenv
.chosen_impl(buildenv
.interface
)
50 write_sample_interface(buildenv
, src_iface
, src_impl
)
52 # Check 0compile is new enough
53 min_version
= parse_version(src_impl
.attrs
.get(XMLNS_0COMPILE
+ ' min-version', None))
54 if min_version
and min_version
> parse_version(__main__
.version
):
55 raise SafeException("%s-%s requires 0compile >= %s, but we are only version %s" %
56 (src_iface
.get_name(), src_impl
.version
, format_version(min_version
), __main__
.version
))
59 patch_file
= join(buildenv
.metadir
, 'from-%s.patch' % src_impl
.version
)
60 if buildenv
.user_srcdir
:
61 # (ignore errors; will already be shown on stderr)
62 os
.system("diff -urN '%s' src > %s" %
63 (buildenv
.orig_srcdir
.replace('\\', '\\\\').replace("'", "\\'"),
65 if os
.path
.getsize(patch_file
) == 0:
67 elif os
.path
.exists(patch_file
):
70 env('BUILDDIR', builddir
)
71 env('DISTDIR', buildenv
.distdir
)
72 env('SRCDIR', buildenv
.user_srcdir
or buildenv
.orig_srcdir
)
76 for needed_iface
in sels
.selections
:
77 impl
= buildenv
.chosen_impl(needed_iface
)
79 for dep
in impl
.dependencies
:
80 dep_iface
= sels
.selections
[dep
.interface
]
81 for b
in dep
.bindings
:
82 if isinstance(b
, EnvironmentBinding
):
83 dep_impl
= buildenv
.chosen_impl(dep
.interface
)
84 do_env_binding(b
, lookup(dep_impl
.id))
87 for impl
in sels
.selections
.values():
88 new_mappings
= impl
.attrs
.get(XMLNS_0COMPILE
+ ' lib-mappings', '')
90 new_mappings
= new_mappings
.split(' ')
91 for mapping
in new_mappings
:
92 assert ':' in mapping
, "lib-mappings missing ':' in '%s' from '%s'" % (mapping
, impl
.feed
)
93 name
, major_version
= mapping
.split(':', 1)
94 assert '/' not in mapping
, "lib-mappings '%s' contains a / in the version number (from '%s')!" % (mapping
, impl
.feed
)
95 mappings
.append((name
, major_version
))
98 set_up_mappings(mappings
)
100 # Some programs want to put temporary build files in the source directory.
101 # Make a copy of the source if needed.
102 dup_src_type
= src_impl
.attrs
.get(XMLNS_0COMPILE
+ ' dup-src', None)
103 if dup_src_type
== 'true':
104 dup_src(shutil
.copy2
)
105 env('SRCDIR', builddir
)
107 raise Exception("Unknown dup-src value '%s'" % dup_src_type
)
110 spawn_and_check(find_in_path('sh'), [])
112 command
= src_impl
.attrs
[XMLNS_0COMPILE
+ ' command']
114 # Remove any existing log files
115 for log
in ['build.log', 'build-success.log', 'build-failure.log']:
116 if os
.path
.exists(log
):
119 # Run the command, copying output to a new log
120 log
= file('build.log', 'w')
122 print >>log
, "Build log for %s-%s" % (src_iface
.get_name(),
124 print >>log
, "\nBuilt using 0compile-%s" % __main__
.version
125 print >>log
, "\nBuild system: " + ', '.join(uname
)
126 print >>log
, "\n%s:\n" % ENV_FILE
127 shutil
.copyfileobj(file("../" + ENV_FILE
), log
)
131 if os
.path
.exists(patch_file
):
132 print >>log
, "\nPatched with:\n"
133 shutil
.copyfileobj(file(patch_file
), log
)
136 print "Executing: " + command
137 print >>log
, "Executing: " + command
139 # Tee the output to the console and to the log
140 from popen2
import Popen4
141 child
= Popen4(command
)
142 child
.tochild
.close()
144 data
= os
.read(child
.fromchild
.fileno(), 100)
146 sys
.stdout
.write(data
)
148 status
= child
.wait()
150 if os
.WIFEXITED(status
):
151 exit_code
= os
.WEXITSTATUS(status
)
153 print >>log
, "Build successful"
155 failure
= "Build failed with exit code %d" % exit_code
157 failure
= "Build failure: exited due to signal %d" % os
.WTERMSIG(status
)
160 os
.rename('build.log', 'build-failure.log')
161 raise SafeException("Command '%s': %s" % (command
, failure
))
163 os
.rename('build.log', 'build-success.log')
168 """build [ --no-sandbox ] [ --shell | --force | --clean ]"""
169 buildenv
= BuildEnv()
170 sels
= buildenv
.get_selections()
172 parser
= OptionParser(usage
="usage: %prog build [options]")
174 parser
.add_option('', "--no-sandbox", help="disable use of sandboxing", action
='store_true')
175 parser
.add_option("-s", "--shell", help="run a shell instead of building", action
='store_true')
176 parser
.add_option("-c", "--clean", help="remove the build directories", action
='store_true')
177 parser
.add_option("-f", "--force", help="build even if dependencies have changed", action
='store_true')
179 parser
.disable_interspersed_args()
181 (options
, args2
) = parser
.parse_args(args
)
183 builddir
= os
.path
.realpath('build')
185 changes
= buildenv
.get_build_changes()
187 if not (options
.force
or options
.clean
):
188 raise SafeException("Build dependencies have changed:\n" +
189 '\n'.join(changes
) + "\n\n" +
190 "To build anyway, use: 0compile build --force\n" +
191 "To do a clean build: 0compile build --clean")
192 if not options
.no_sandbox
:
193 print "Build dependencies have changed:\n" + '\n'.join(changes
)
195 ensure_dir(builddir
, options
.clean
)
196 ensure_dir(buildenv
.distdir
, options
.clean
)
198 if options
.no_sandbox
:
199 return do_build_internal(options
, args2
)
201 tmpdir
= tempfile
.mkdtemp(prefix
= '0compile-')
203 my_dir
= os
.path
.dirname(__file__
)
204 readable
= ['.', my_dir
]
205 writable
= ['build', buildenv
.distdir
, tmpdir
]
206 env('TMPDIR', tmpdir
)
208 for selection
in sels
.selections
.values():
209 readable
.append(lookup(selection
.id))
212 if __main__
.options
.verbose
:
213 options
.append('--verbose')
215 readable
.append('/etc') # /etc/ld.*
217 spawn_maybe_sandboxed(readable
, writable
, tmpdir
, sys
.executable
, [sys
.argv
[0]] + options
+ ['build', '--no-sandbox'] + args
)
219 info("Deleting temporary directory '%s'" % tmpdir
)
220 shutil
.rmtree(tmpdir
)
222 def write_sample_interface(buildenv
, iface
, src_impl
):
223 path
= buildenv
.local_iface_file
224 target_arch
= buildenv
.target_arch
226 impl
= minidom
.getDOMImplementation()
228 XMLNS_IFACE
= namespaces
.XMLNS_IFACE
230 doc
= impl
.createDocument(XMLNS_IFACE
, "interface", None)
232 root
= doc
.documentElement
233 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
235 def addSimple(parent
, name
, text
= None):
236 elem
= doc
.createElementNS(XMLNS_IFACE
, name
)
238 parent
.appendChild(doc
.createTextNode('\n' + ' ' * (1 + depth(parent
))))
239 parent
.appendChild(elem
)
241 elem
.appendChild(doc
.createTextNode(text
))
245 element
.appendChild(doc
.createTextNode('\n' + ' ' * depth(element
)))
247 addSimple(root
, 'name', iface
.name
)
248 addSimple(root
, 'summary', iface
.summary
)
249 addSimple(root
, 'description', iface
.description
)
250 feed_for
= addSimple(root
, 'feed-for')
253 if uri
.startswith('/') and iface
.feed_for
:
254 for uri
in iface
.feed_for
:
255 print "Note: source %s is a local feed" % iface
.uri
256 print "Will use <feed-for interface='%s'> instead..." % uri
259 feed_for
.setAttributeNS(None, 'interface', uri
)
261 group
= addSimple(root
, 'group')
262 main
= src_impl
.attrs
.get(XMLNS_0COMPILE
+ ' binary-main', None)
264 group
.setAttributeNS(None, 'main', main
)
266 lib_mappings
= src_impl
.attrs
.get(XMLNS_0COMPILE
+ ' binary-lib-mappings', None)
268 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:compile', XMLNS_0COMPILE
)
269 group
.setAttributeNS(XMLNS_0COMPILE
, 'compile:lib-mappings', lib_mappings
)
271 for d
in src_impl
.dependencies
:
272 # 0launch < 0.32 messed up the namespace...
273 if parse_bool(d
.metadata
.get('include-binary', 'false')) or \
274 parse_bool(d
.metadata
.get(XMLNS_0COMPILE
+ ' include-binary', 'false')):
275 requires
= addSimple(group
, 'requires')
276 requires
.setAttributeNS(None, 'interface', d
.interface
)
278 if isinstance(b
, model
.EnvironmentBinding
):
279 env_elem
= addSimple(requires
, 'environment')
280 env_elem
.setAttributeNS(None, 'name', b
.name
)
281 env_elem
.setAttributeNS(None, 'insert', b
.insert
)
283 env_elem
.setAttributeNS(None, 'default', b
.default
)
285 raise Exception('Unknown binding type ' + b
)
288 group
.setAttributeNS(None, 'arch', target_arch
)
289 impl_elem
= addSimple(group
, 'implementation')
290 impl_elem
.setAttributeNS(None, 'version', src_impl
.version
)
292 version_modifier
= buildenv
.version_modifier
294 impl_elem
.setAttributeNS(None, 'version-modifier', version_modifier
)
296 impl_elem
.setAttributeNS(None, 'id', '..')
297 impl_elem
.setAttributeNS(None, 'released', time
.strftime('%Y-%m-%d'))
301 stream
= codecs
.open(path
, 'w', encoding
= 'utf-8')
307 def set_up_mappings(mappings
):
308 """Create a temporary directory with symlinks for each of the library mappings."""
309 # The find_library function takes a short-name and major version of a library and
310 # returns the full path of the library.
311 libdirs
= ['/lib', '/usr/lib']
312 for d
in os
.environ
.get('LD_LIBRARY_PATH', '').split(':'):
313 if d
: libdirs
.append(d
)
315 def add_ldconf(config_file
):
316 if not os
.path
.isfile(config_file
):
318 for line
in file(config_file
):
320 if d
.startswith('include '):
321 glob_pattern
= d
.split(' ', 1)[1]
322 for conf
in glob
.glob(glob_pattern
):
324 elif d
and not d
.startswith('#'):
326 add_ldconf('/etc/ld.so.conf')
328 def find_library(name
, major
):
329 wanted
= 'lib%s.so.%s' % (name
, major
)
331 path
= os
.path
.join(d
, wanted
)
332 if os
.path
.exists(path
):
334 print "WARNING: library '%s' not found (searched '%s')!" % (wanted
, libdirs
)
337 mappings_dir
= os
.path
.join(os
.environ
['TMPDIR'], 'lib-mappings')
338 os
.mkdir(mappings_dir
)
340 old_path
= os
.environ
.get('LIBRARY_PATH', '')
341 if old_path
: old_path
= ':' + old_path
342 os
.environ
['LIBRARY_PATH'] = mappings_dir
+ old_path
344 for name
, major_version
in mappings
:
345 target
= find_library(name
, major_version
)
347 print "Adding mapping lib%s.so -> %s" % (name
, target
)
348 os
.symlink(target
, os
.path
.join(mappings_dir
, 'lib' + name
+ '.so'))
351 srcdir
= os
.environ
['SRCDIR'] + '/'
352 for root
, dirs
, files
in os
.walk(srcdir
):
353 assert root
.startswith(srcdir
)
354 reldir
= root
[len(srcdir
):]
356 target
= os
.path
.join(reldir
, f
)
357 #print "Copy %s -> %s" % (os.path.join(root, f), target)
358 if os
.path
.exists(target
):
360 fn(os
.path
.join(root
, f
), target
)
362 target
= os
.path
.join(reldir
, d
)
363 if not os
.path
.isdir(target
):
366 __main__
.commands
.append(do_build
)