Add compile:pin-components attribute to version tag of implementation template
[0compile.git] / build.py
blob4c1d10dd95b5f25060e42dedc54da19c68dd6c34
1 # Copyright (C) 2006, Thomas Leonard
2 # See http://0install.net/0compile.html
4 import sys, os, __main__, time, shutil, glob, codecs, subprocess
5 from os.path import join
6 from logging import info, warn
7 from xml.dom import minidom, XMLNS_NAMESPACE
8 from optparse import OptionParser
9 import tempfile
10 import itertools
12 from zeroinstall import SafeException
13 from zeroinstall.injector import model, namespaces, run
14 from zeroinstall.injector.iface_cache import iface_cache
16 from support import BuildEnv, ensure_dir, XMLNS_0COMPILE, is_package_impl, parse_bool, depth, uname
17 from support import spawn_and_check, find_in_path, ENV_FILE, lookup, spawn_and_check_maybe_sandboxed, Prefixes
19 # If we have to modify any pkg-config files, we put the new versions in $TMPDIR/PKG_CONFIG_OVERRIDES
20 PKG_CONFIG_OVERRIDES = 'pkg-config-overrides'
22 def env(name, value):
23 os.environ[name] = value
24 print "%s=%s" % (name, value)
26 def do_env_binding(binding, path):
27 if binding.insert is not None and path is None:
28 # Skip insert bindings for package implementations
29 return
30 env(binding.name, binding.get_value(path, os.environ.get(binding.name, None)))
32 def correct_for_64bit(base, rel_path):
33 """If rel_path starts lib or usr/lib and doesn't exist, try with lib64 instead."""
34 if os.path.exists(os.path.join(base, rel_path)):
35 return rel_path
37 if rel_path.startswith('lib/') or rel_path.startswith('usr/lib/'):
38 new_rel_path = rel_path.replace('lib/', 'lib64/', 1)
39 if os.path.exists(os.path.join(base, new_rel_path)):
40 return new_rel_path
42 return rel_path
44 def write_pc(name, lines):
45 overrides_dir = os.path.join(os.environ['TMPDIR'], PKG_CONFIG_OVERRIDES)
46 if not os.path.isdir(overrides_dir):
47 os.mkdir(overrides_dir)
48 stream = open(os.path.join(overrides_dir, name), 'w')
49 stream.write(''.join(lines))
50 stream.close()
52 def do_pkg_config_binding(binding, impl):
53 if impl.id.startswith('package:'):
54 return # No bindings needed for native packages
55 feed_name = impl.feed.split('/')[-1]
56 path = lookup(impl)
57 new_insert = correct_for_64bit(path, binding.insert)
58 if new_insert != binding.insert:
59 print "PKG_CONFIG_PATH dir <%s>/%s not found; using %s instead" % (feed_name, binding.insert, new_insert)
60 binding = model.EnvironmentBinding(binding.name,
61 new_insert,
62 binding.default,
63 binding.mode)
65 orig_path = os.path.join(path, binding.insert)
66 if os.path.isdir(orig_path):
67 for pc in os.listdir(orig_path):
68 stream = open(os.path.join(orig_path, pc))
69 lines = stream.readlines()
70 stream.close()
71 for i, line in enumerate(lines):
72 if '=' not in line: continue
73 name, value = [x.strip() for x in line.split('=', 1)]
74 if name == 'prefix' and os.path.isabs(value):
75 print "Absolute prefix=%s in %s; overriding..." % (value, feed_name)
76 lines[i] = 'prefix=' + os.path.join(
77 path, os.path.splitdrive(value)[1][1:]) +'\n'
78 write_pc(pc, lines)
79 break
80 do_env_binding(binding, path)
82 def shorten_dynamic_library_install_name(dylib_file):
83 # Only need to change actual library, not links to it
84 if os.path.islink(dylib_file):
85 return
86 otool_args = ['/usr/bin/otool', '-D', dylib_file]
87 process = subprocess.Popen(otool_args, stdout=subprocess.PIPE)
88 output, error = process.communicate()
89 retcode = process.poll()
90 for line in output.split('\n'):
91 if not line.endswith(':'):
92 value = line.strip()
93 print "Absolute install name=%s in %s; fixing..." % (value, dylib_file)
94 break
95 shortname = os.path.basename(dylib_file)
96 subprocess.check_call(['install_name_tool', '-id', shortname, dylib_file])
98 # After doing a build, remove the (dist) directory component from dynamic libraries
99 def shorten_dynamic_library_install_names():
100 for root, dirs, files in os.walk(os.environ['DISTDIR']):
101 if os.path.basename(root) == 'lib':
102 for f in files:
103 if f.endswith('.dylib'):
104 info("Checking dynamic library '%s'", f)
105 shorten_dynamic_library_install_name(os.path.join(root, f))
107 def fixup_generated_pkgconfig_file(pc_file):
108 stream = open(pc_file)
109 lines = stream.readlines()
110 stream.close()
111 for i, line in enumerate(lines):
112 if '=' not in line: continue
113 name, value = [x.strip() for x in line.split('=', 1)]
114 if name == 'prefix' and os.path.isabs(value):
115 print "Absolute prefix=%s in %s; fixing..." % (value, pc_file)
116 rel_path = os.path.relpath(value, os.path.dirname(pc_file))
117 lines[i] = 'prefix=' + os.path.join(
118 '${pcfiledir}', rel_path) + '\n'
119 write_pc(pc_file, lines)
120 break
122 # After doing a build, check that we didn't generate pkgconfig files with absolute paths
123 # Rewrite if so
124 def fixup_generated_pkgconfig_files():
125 for root, dirs, files in os.walk(os.environ['DISTDIR']):
126 if os.path.basename(root) == 'pkgconfig':
127 for f in files:
128 if f.endswith('.pc'):
129 info("Checking generated pkgconfig file '%s'", f)
130 fixup_generated_pkgconfig_file(os.path.join(root, f))
132 def remove_la_file(path):
133 # Read the contents...
134 stream = open(path)
135 data = stream.read()
136 stream.close()
138 # Check it really is a libtool archive...
139 if 'Please DO NOT delete this file' not in data:
140 warn("Ignoring %s; doesn't look like a libtool archive", path)
141 return
143 os.unlink(path)
144 print "Removed %s (.la files contain absolute paths)" % path
146 # libtool archives contain hard-coded paths. Lucky, modern systems don't need them, so remove
147 # them.
148 def remove_la_files():
149 for root, dirs, files in os.walk(os.environ['DISTDIR']):
150 if os.path.basename(root) == 'lib':
151 for f in files:
152 if f.endswith('.la'):
153 remove_la_file(os.path.join(root, f))
154 if f.endswith('.a'):
155 warn("Found static archive '%s'; maybe build with --disable-static?", f)
157 class CompileSetup(run.Setup):
158 def do_binding(self, impl, b, iface):
159 if isinstance(b, model.EnvironmentBinding):
160 if b.name == 'PKG_CONFIG_PATH':
161 do_pkg_config_binding(b, impl)
162 else:
163 do_env_binding(b, lookup(impl))
164 else:
165 run.Setup.do_binding(self, impl, b, iface)
167 def do_build_internal(options, args):
168 """build-internal"""
169 # If a sandbox is being used, we're in it now.
170 import getpass, socket
172 buildenv = BuildEnv()
173 sels = buildenv.get_selections()
175 builddir = os.path.realpath('build')
176 ensure_dir(buildenv.metadir)
178 build_env_xml = join(buildenv.metadir, 'build-environment.xml')
180 buildenv_doc = sels.toDOM()
182 # Create build-environment.xml file
183 root = buildenv_doc.documentElement
184 info = buildenv_doc.createElementNS(XMLNS_0COMPILE, 'build-info')
185 root.appendChild(info)
186 info.setAttributeNS(None, 'time', time.strftime('%Y-%m-%d %H:%M').strip())
187 info.setAttributeNS(None, 'host', socket.getfqdn())
188 info.setAttributeNS(None, 'user', getpass.getuser())
189 info.setAttributeNS(None, 'arch', '%s-%s' % (uname[0], uname[4]))
190 stream = file(build_env_xml, 'w')
191 buildenv_doc.writexml(stream, addindent=" ", newl="\n")
192 stream.close()
194 # Create local binary interface file.
195 # We use the main feed for the interface as the template for the name,
196 # summary, etc (note: this is not necessarily the feed that contained
197 # the source code).
198 master_feed = iface_cache.get_feed(buildenv.interface)
199 src_impl = buildenv.chosen_impl(buildenv.interface)
200 write_sample_feed(buildenv, master_feed, src_impl)
202 # Check 0compile is new enough
203 min_version = model.parse_version(src_impl.attrs.get(XMLNS_0COMPILE + ' min-version', None))
204 if min_version and min_version > model.parse_version(__main__.version):
205 raise SafeException("%s-%s requires 0compile >= %s, but we are only version %s" %
206 (master_feed.get_name(), src_impl.version, model.format_version(min_version), __main__.version))
208 # Create the patch
209 patch_file = join(buildenv.metadir, 'from-%s.patch' % src_impl.version)
210 if buildenv.user_srcdir:
211 with open(patch_file, 'w') as stream:
212 # (ignore errors; will already be shown on stderr)
213 try:
214 subprocess.call(["diff", "-urN", buildenv.orig_srcdir, 'src'], stdout = stream)
215 except OSError as ex:
216 print >>sys.stderr, "WARNING: Failed to run 'diff': ", ex
217 if os.path.getsize(patch_file) == 0:
218 os.unlink(patch_file)
219 elif os.path.exists(patch_file):
220 os.unlink(patch_file)
222 env('BUILDDIR', builddir)
223 env('DISTDIR', buildenv.distdir)
224 env('SRCDIR', buildenv.user_srcdir or buildenv.orig_srcdir)
225 env('BINARYFEED', buildenv.local_iface_file)
226 os.chdir(builddir)
227 print "cd", builddir
229 setup = CompileSetup(iface_cache.stores, sels)
230 setup.prepare_env()
232 # These mappings are needed when mixing Zero Install -dev packages with
233 # native package binaries.
234 mappings = {}
235 for impl in sels.selections.values():
236 # Add mappings that have been set explicitly...
237 new_mappings = impl.attrs.get(XMLNS_0COMPILE + ' lib-mappings', '')
238 if new_mappings:
239 new_mappings = new_mappings.split(' ')
240 for mapping in new_mappings:
241 assert ':' in mapping, "lib-mappings missing ':' in '%s' from '%s'" % (mapping, impl.feed)
242 name, major_version = mapping.split(':', 1)
243 assert '/' not in mapping, "lib-mappings '%s' contains a / in the version number (from '%s')!" % (mapping, impl.feed)
244 if sys.platform == 'darwin':
245 mappings[name] = 'lib%s.%s.dylib' % (name, major_version)
246 else:
247 mappings[name] = 'lib%s.so.%s' % (name, major_version)
248 # Auto-detect required mappings where possible...
249 # (if the -dev package is native, the symlinks will be OK)
250 if not is_package_impl(impl):
251 impl_path = lookup(impl)
252 for libdirname in ['lib', 'usr/lib', 'lib64', 'usr/lib64']:
253 libdir = os.path.join(impl_path, libdirname)
254 if os.path.isdir(libdir):
255 find_broken_version_symlinks(libdir, mappings)
257 if mappings:
258 set_up_mappings(mappings)
260 overrides_dir = os.path.join(os.environ['TMPDIR'], PKG_CONFIG_OVERRIDES)
261 if os.path.isdir(overrides_dir):
262 add_overrides = model.EnvironmentBinding('PKG_CONFIG_PATH', PKG_CONFIG_OVERRIDES)
263 do_env_binding(add_overrides, os.environ['TMPDIR'])
265 # Some programs want to put temporary build files in the source directory.
266 # Make a copy of the source if needed.
267 dup_src_type = src_impl.attrs.get(XMLNS_0COMPILE + ' dup-src', None)
268 if dup_src_type == 'true':
269 dup_src(copy_file)
270 env('SRCDIR', builddir)
271 elif dup_src_type:
272 raise Exception("Unknown dup-src value '%s'" % dup_src_type)
274 if options.shell:
275 spawn_and_check(find_in_path('cmd' if os.name == 'nt' else 'sh'), [])
276 else:
277 command = sels.commands[0].qdom.attrs.get('shell-command', None)
278 if command is None:
279 # New style <command>
280 prog_args = setup.build_command(sels.interface, sels.command) + args
281 else:
282 # Old style shell-command='...'
283 if os.name == 'nt':
284 prog_args = [os.environ['0COMPILE_BASH'], '-eux', '-c', command] + args
285 else:
286 prog_args = ['/bin/sh', '-c', command + ' "$@"', '-'] + args
287 assert len(sels.commands) == 1
289 # Remove any existing log files
290 for log in ['build.log', 'build-success.log', 'build-failure.log']:
291 if os.path.exists(log):
292 os.unlink(log)
294 # Run the command, copying output to a new log
295 with open('build.log', 'w') as log:
296 print >>log, "Build log for %s-%s" % (master_feed.get_name(),
297 src_impl.version)
298 print >>log, "\nBuilt using 0compile-%s" % __main__.version
299 print >>log, "\nBuild system: " + ', '.join(uname)
300 print >>log, "\n%s:\n" % ENV_FILE
301 with open(os.path.join(os.pardir, ENV_FILE)) as properties_file:
302 shutil.copyfileobj(properties_file, log)
304 log.write('\n')
306 if os.path.exists(patch_file):
307 print >>log, "\nPatched with:\n"
308 shutil.copyfileobj(file(patch_file), log)
309 log.write('\n')
311 if command:
312 print "Executing: " + command, args
313 print >>log, "Executing: " + command, args
314 else:
315 print "Executing: " + str(prog_args)
316 print >>log, "Executing: " + str(prog_args)
318 # Tee the output to the console and to the log
319 child = subprocess.Popen(prog_args, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
320 while True:
321 data = os.read(child.stdout.fileno(), 100)
322 if not data: break
323 sys.stdout.write(data)
324 log.write(data)
325 status = child.wait()
326 failure = None
327 if status == 0:
328 print >>log, "Build successful"
329 shorten_dynamic_library_install_names()
330 fixup_generated_pkgconfig_files()
331 remove_la_files()
332 elif status > 0:
333 failure = "Build failed with exit code %d" % status
334 else:
335 failure = "Build failure: exited due to signal %d" % (-status)
336 if failure:
337 print >>log, failure
339 if failure:
340 os.rename('build.log', 'build-failure.log')
341 raise SafeException("Command '%s': %s" % (prog_args, failure))
342 else:
343 os.rename('build.log', 'build-success.log')
345 def do_build(args):
346 """build [ --no-sandbox ] [ --shell | --force | --clean ]"""
347 buildenv = BuildEnv()
348 sels = buildenv.get_selections()
350 parser = OptionParser(usage="usage: %prog build [options]")
352 parser.add_option('', "--no-sandbox", help="disable use of sandboxing", action='store_true')
353 parser.add_option("-s", "--shell", help="run a shell instead of building", action='store_true')
354 parser.add_option("-c", "--clean", help="remove the build directories", action='store_true')
355 parser.add_option("-f", "--force", help="build even if dependencies have changed", action='store_true')
357 parser.disable_interspersed_args()
359 (options, args2) = parser.parse_args(args)
361 builddir = os.path.realpath('build')
363 changes = buildenv.get_build_changes()
364 if changes:
365 if not (options.force or options.clean):
366 raise SafeException("Build dependencies have changed:\n" +
367 '\n'.join(changes) + "\n\n" +
368 "To build anyway, use: 0compile build --force\n" +
369 "To do a clean build: 0compile build --clean")
370 if not options.no_sandbox:
371 print "Build dependencies have changed:\n" + '\n'.join(changes)
373 ensure_dir(builddir, options.clean)
374 ensure_dir(buildenv.distdir, options.clean)
376 if options.no_sandbox:
377 return do_build_internal(options, args2)
379 tmpdir = tempfile.mkdtemp(prefix = '0compile-')
380 try:
381 my_dir = os.path.dirname(__file__)
382 readable = ['.', my_dir]
383 writable = ['build', buildenv.distdir, tmpdir]
384 env('TMPDIR', tmpdir)
386 for selection in sels.selections.values():
387 if not is_package_impl(selection):
388 readable.append(lookup(selection))
390 options = []
391 if __main__.options.verbose:
392 options.append('--verbose')
394 readable.append('/etc') # /etc/ld.*
396 spawn_and_check_maybe_sandboxed(readable, writable, tmpdir, sys.executable, ['-u', sys.argv[0]] + options + ['build', '--no-sandbox'] + args)
397 finally:
398 info("Deleting temporary directory '%s'" % tmpdir)
399 shutil.rmtree(tmpdir)
401 def find_feed_for(master_feed):
402 """Determine the <feed-for> interface for the new binary's feed.
403 remote feed (http://...) => the binary is a feed for the interface with this URI
404 local feed (/feed.xml) => copy <feed-for> from feed.xml (e.g. for a Git clone)
405 local copy of remote feed (no feed-for) => feed's uri attribute
407 if hasattr(master_feed, 'local_path'):
408 is_local = master_feed.local_path is not None # 0install >= 1.7
409 else:
410 is_local = os.path.isabs(master_feed.url)
412 uri = master_feed.url
414 if is_local:
415 print "Note: source %s is a local feed" % uri
416 for feed_uri in master_feed.feed_for or []:
417 uri = feed_uri
418 print "Will use <feed-for interface='%s'> instead..." % uri
419 break
420 else:
421 master_feed = minidom.parse(uri).documentElement
422 if master_feed.hasAttribute('uri'):
423 uri = master_feed.getAttribute('uri')
424 print "Will use <feed-for interface='%s'> instead..." % uri
426 return uri
428 def write_sample_feed(buildenv, master_feed, src_impl):
429 path = buildenv.local_iface_file
431 old_path = os.path.join(buildenv.metadir, buildenv.iface_name + '.xml')
432 if os.path.exists(old_path):
433 warn("Removing old %s file: use %s instead now", old_path, path)
434 os.unlink(old_path)
436 impl = minidom.getDOMImplementation()
438 XMLNS_IFACE = namespaces.XMLNS_IFACE
440 doc = impl.createDocument(XMLNS_IFACE, "interface", None)
442 root = doc.documentElement
443 root.setAttributeNS(XMLNS_NAMESPACE, 'xmlns', XMLNS_IFACE)
444 prefixes = Prefixes(XMLNS_IFACE)
446 def addSimple(parent, name, text = None):
447 elem = doc.createElementNS(XMLNS_IFACE, name)
449 parent.appendChild(doc.createTextNode('\n' + ' ' * (1 + depth(parent))))
450 parent.appendChild(elem)
451 if text:
452 elem.appendChild(doc.createTextNode(text))
453 return elem
455 def close(element):
456 element.appendChild(doc.createTextNode('\n' + ' ' * depth(element)))
458 addSimple(root, 'name', master_feed.name)
459 addSimple(root, 'summary', master_feed.summary)
460 addSimple(root, 'description', master_feed.description)
461 feed_for = addSimple(root, 'feed-for')
463 feed_for.setAttributeNS(None, 'interface', find_feed_for(master_feed))
465 group = addSimple(root, 'group')
466 main = src_impl.attrs.get(XMLNS_0COMPILE + ' binary-main', None)
467 if main:
468 group.setAttributeNS(None, 'main', main)
470 lib_mappings = src_impl.attrs.get(XMLNS_0COMPILE + ' binary-lib-mappings', None)
471 if lib_mappings:
472 prefixes.setAttributeNS(group, XMLNS_0COMPILE, 'lib-mappings', lib_mappings)
474 for d in src_impl.dependencies:
475 if parse_bool(d.metadata.get(XMLNS_0COMPILE + ' include-binary', 'false')):
476 requires = d.qdom.toDOM(doc, prefixes)
477 requires.removeAttributeNS(XMLNS_0COMPILE, 'include-binary')
478 group.appendChild(requires)
479 set_arch = True
481 impl_elem = addSimple(group, 'implementation')
482 impl_template = buildenv.get_binary_template()
483 if impl_template:
484 # Copy attributes from template
485 for fullname, value in impl_template.attrs.iteritems():
486 if fullname == 'arch':
487 set_arch = False
488 if value == '*-*':
489 continue
490 if ' ' in fullname:
491 ns, localName = fullname.split(' ', 1)
492 else:
493 ns, localName = None, fullname
494 prefixes.setAttributeNS(impl_elem, ns, localName, value)
495 # Copy child nodes
496 for child in impl_template.childNodes:
497 impl_elem.appendChild(child.toDOM(doc, prefixes))
498 if impl_template.content:
499 impl_elem.appendChild(doc.createTextNode(impl_template.content))
501 for version_elem in itertools.chain(
502 impl_elem.getElementsByTagName('version'),
504 pin_components = version_elem.getAttributeNS(XMLNS_0COMPILE, "pin-components")
505 if pin_components:
506 pin_components = int(pin_components)
507 iface = version_elem.parentNode.getAttribute("interface")
508 assert iface
509 dep_impl = buildenv.chosen_impl(iface)
510 impl_version = model.parse_version(dep_impl.attrs.get('version'))
512 pinned_components = [impl_version[0][:pin_components]]
513 # (for -pre versions)
514 min_version = min(pinned_components, impl_version)
516 # clone and increment
517 max_version = [pinned_components[0][:]]
518 max_version[0][-1] += 1
520 version_elem.setAttribute("not-before", model.format_version(min_version))
521 version_elem.setAttribute("before", model.format_version(max_version))
523 if set_arch:
524 group.setAttributeNS(None, 'arch', buildenv.target_arch)
526 impl_elem.setAttributeNS(None, 'version', src_impl.version)
528 if not impl_elem.hasAttribute('license'):
529 license = src_impl.attrs.get('license')
530 if license:
531 impl_elem.setAttributeNS(None, 'license', license)
533 version_modifier = buildenv.version_modifier
534 if version_modifier:
535 impl_elem.setAttributeNS(None, 'version-modifier', version_modifier)
537 impl_elem.setAttributeNS(None, 'id', '..')
538 impl_elem.setAttributeNS(None, 'released', time.strftime('%Y-%m-%d'))
539 close(group)
540 close(root)
542 for ns, prefix in prefixes.prefixes.items():
543 root.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:' + prefix, ns)
545 stream = codecs.open(path, 'w', encoding = 'utf-8')
546 try:
547 doc.writexml(stream)
548 finally:
549 stream.close()
551 def find_broken_version_symlinks(libdir, mappings):
552 """libdir may be a legacy -devel package containing lib* symlinks whose
553 targets would be provided by the corresponding runtime package. If so,
554 create fixed symlinks under $TMPDIR with the real location."""
555 prefix = 'lib'
556 if sys.platform == 'darwin':
557 extension = '.dylib'
558 else:
559 extension = '.so'
561 for x in os.listdir(libdir):
562 if x.startswith(prefix) and x.endswith(extension):
563 path = os.path.join(libdir, x)
564 if os.path.islink(path):
565 target = os.readlink(path)
566 if '/' not in target and not os.path.exists(os.path.join(libdir, target)):
567 print "Broken link %s -> %s; will relocate..." % (x, target)
568 mappings[x[len(prefix):-len(extension)]] = target
570 def set_up_mappings(mappings):
571 """Create a temporary directory with symlinks for each of the library mappings."""
572 libdirs = []
573 if sys.platform == 'darwin':
574 LD_LIBRARY_PATH='DYLD_LIBRARY_PATH'
575 else:
576 LD_LIBRARY_PATH='LD_LIBRARY_PATH'
577 for d in os.environ.get(LD_LIBRARY_PATH, '').split(':'):
578 if d: libdirs.append(d)
579 libdirs += ['/lib', '/usr/lib']
581 def add_ldconf(config_file):
582 if not os.path.isfile(config_file):
583 return
584 for line in file(config_file):
585 d = line.strip()
586 if d.startswith('include '):
587 glob_pattern = d.split(' ', 1)[1]
588 for conf in glob.glob(glob_pattern):
589 add_ldconf(conf)
590 elif d and not d.startswith('#'):
591 libdirs.append(d)
592 add_ldconf('/etc/ld.so.conf')
594 def find_library(name, wanted):
595 # Takes a short-name and target name of a library and returns
596 # the full path of the library.
597 for d in libdirs:
598 path = os.path.join(d, wanted)
599 if os.path.exists(path):
600 return path
601 print "WARNING: library '%s' not found (searched '%s')!" % (wanted, libdirs)
602 return None
604 mappings_dir = os.path.join(os.environ['TMPDIR'], 'lib-mappings')
605 os.mkdir(mappings_dir)
607 old_path = os.environ.get('LIBRARY_PATH', '')
608 if old_path: old_path = ':' + old_path
609 os.environ['LIBRARY_PATH'] = mappings_dir + old_path
611 if sys.platform == 'darwin':
612 soext='.dylib'
613 else:
614 soext='.so'
615 for name, wanted in mappings.items():
616 target = find_library(name, wanted)
617 if target:
618 print "Adding mapping lib%s%s -> %s" % (name, soext, target)
619 os.symlink(target, os.path.join(mappings_dir, 'lib' + name + soext))
621 def copy_file(src, target):
622 if os.path.islink(src):
623 os.symlink(os.readlink(src), target)
624 else:
625 shutil.copy2(src, target)
627 def dup_src(fn):
628 srcdir = os.path.join(os.environ['SRCDIR'], '')
629 builddir = os.environ['BUILDDIR']
631 build_in_src = srcdir + 'build' == builddir
633 for root, dirs, files in os.walk(srcdir):
634 assert root.startswith(srcdir)
635 reldir = root[len(srcdir):]
637 if reldir == '.git' or (reldir == 'build' and build_in_src) or \
638 ('0compile.properties' in files and not build_in_src):
639 print "dup-src: skipping", reldir
640 dirs[:] = []
641 continue
643 for f in files:
644 target = os.path.join(reldir, f)
645 #print "Copy %s -> %s" % (os.path.join(root, f), target)
646 if os.path.exists(target):
647 os.unlink(target)
648 fn(os.path.join(root, f), target)
649 for d in dirs:
650 target = os.path.join(reldir, d)
651 if not os.path.isdir(target):
652 os.mkdir(target)
654 __main__.commands.append(do_build)