def do_diff(args): """diff""" if args: raise __main__.UsageError() buildenv = BuildEnv() if not os.path.isdir('src'): raise SafeException('No local src directory to diff against!') new_src = os.path.realpath('src') src_impl = buildenv.chosen_impl(buildenv.interface) assert src_impl prog = find_in_path('diff') args = ['-ur', lookup(src_impl), new_src] status = os.spawnv(os.P_WAIT, prog, [prog] + args) if status == 0: return False elif status == 1: return True elif status > 1: raise SafeException("Program '%s' failed with exit code %d" % (prog, status)) elif status < 0: raise SafeException("Program '%s' failed with signal %d" % (prog, -status))
def do_copy_src(args): """copy-src""" if args: raise __main__.UsageError() buildenv = BuildEnv() src_impl = buildenv.chosen_impl(buildenv.interface) assert src_impl path = lookup(src_impl) assert path new_src = os.path.realpath('src') # Just for better messages if os.path.exists(new_src): raise SafeException("Directory '%s' already exists!" % new_src) shutil.copytree(path, 'src', symlinks=True) # Make all files writable by the owner for root, dirs, files in os.walk('src'): os.chmod(root, os.stat(root).st_mode | 0200) for f in files: path = os.path.join(root, f) if not os.path.islink(path): os.chmod(path, os.stat(path).st_mode | 0200) print "Copied as '%s'" % new_src
def do_copy_src(args): """copy-src""" if args: raise __main__.UsageError() buildenv = BuildEnv() src_impl = buildenv.chosen_impl(buildenv.interface) assert src_impl path = lookup(src_impl) assert path new_src = os.path.realpath('src') # Just for better messages if os.path.exists(new_src): raise SafeException("Directory '%s' already exists!" % new_src) shutil.copytree(path, 'src', symlinks = True) # Make all files writable by the owner for root, dirs, files in os.walk('src'): os.chmod(root, os.stat(root).st_mode | 0200) for f in files: path = os.path.join(root, f) if not os.path.islink(path): os.chmod(path, os.stat(path).st_mode | 0200) print "Copied as '%s'" % new_src
def do_include_deps(args): """include-deps""" buildenv = BuildEnv() depdir = os.path.realpath('dependencies') ensure_dir(depdir) dirs_to_copy = [] sels = buildenv.get_selections() for needed_iface in sels.selections: impl = buildenv.chosen_impl(needed_iface) assert impl if impl.local_path is not None: raise SafeException("Can't export '%s' as it's a local implementation (not supported yet; sorry)" % impl) if not impl.id.startswith('package:'): dirs_to_copy.append(lookup(impl)) copied = 0 for cached in dirs_to_copy: required_digest = os.path.basename(cached) target_impl_dir = os.path.join(depdir, required_digest) if not os.path.isdir(target_impl_dir): if required_digest.startswith('sha1='): shutil.copytree(cached, target_impl_dir) else: manifest_data = file(os.path.join(cached, '.manifest')).read() manifest.copy_tree_with_verify(cached, depdir, manifest_data, required_digest) copied += 1 print "Copied %d dependencies to %s (%d already there)" % (copied, depdir, len(dirs_to_copy) - copied)
def do_include_deps(args): """include-deps""" buildenv = BuildEnv() depdir = os.path.realpath('dependencies') ensure_dir(depdir) dirs_to_copy = [] sels = buildenv.get_selections() for needed_iface in sels.selections: impl = buildenv.chosen_impl(needed_iface) assert impl if impl.local_path is not None: raise SafeException("Can't export '%s' as it's a local implementation (not supported yet; sorry)" % impl) if not impl.id.startswith('package:'): dirs_to_copy.append(lookup(impl)) copied = 0 for cached in dirs_to_copy: required_digest = os.path.basename(cached) target_impl_dir = os.path.join(depdir, required_digest) if not os.path.isdir(target_impl_dir): if required_digest.startswith('sha1='): shutil.copytree(cached, target_impl_dir) else: with open(os.path.join(cached, '.manifest'), 'rb') as stream: manifest_data = stream.read() manifest.copy_tree_with_verify(cached, depdir, manifest_data, required_digest) copied += 1 print("Copied %d dependencies to %s (%d already there)" % (copied, depdir, len(dirs_to_copy) - copied))
def do_build(args): """build [ --no-sandbox ] [ --shell | --force | --clean ]""" buildenv = BuildEnv() sels = buildenv.get_selections() parser = OptionParser(usage="usage: %prog build [options]") parser.add_option('', "--no-sandbox", help="disable use of sandboxing", action='store_true') parser.add_option("-s", "--shell", help="run a shell instead of building", action='store_true') parser.add_option("-c", "--clean", help="remove the build directories", action='store_true') parser.add_option("-f", "--force", help="build even if dependencies have changed", action='store_true') parser.disable_interspersed_args() (options, args2) = parser.parse_args(args) builddir = os.path.realpath('build') changes = buildenv.get_build_changes() if changes: if not (options.force or options.clean): raise SafeException("Build dependencies have changed:\n" + '\n'.join(changes) + "\n\n" + "To build anyway, use: 0compile build --force\n" + "To do a clean build: 0compile build --clean") if not options.no_sandbox: print("Build dependencies have changed:\n" + '\n'.join(changes)) ensure_dir(builddir, options.clean) ensure_dir(buildenv.distdir, options.clean) if options.no_sandbox: return do_build_internal(options, args2) tmpdir = tempfile.mkdtemp(prefix = '0compile-') try: my_dir = os.path.dirname(__file__) readable = ['.', my_dir] writable = ['build', buildenv.distdir, tmpdir] env('TMPDIR', tmpdir) for selection in list(sels.selections.values()): if not is_package_impl(selection): readable.append(lookup(selection)) options = [] if __main__.options.verbose: options.append('--verbose') readable.append('/etc') # /etc/ld.* spawn_and_check_maybe_sandboxed(readable, writable, tmpdir, sys.executable, ['-u', sys.argv[0]] + options + ['build', '--no-sandbox'] + args) finally: info("Deleting temporary directory '%s'" % tmpdir) shutil.rmtree(tmpdir)
def do_build(args): """build [ --no-sandbox ] [ --shell | --force | --clean ]""" buildenv = BuildEnv() sels = buildenv.get_selections() parser = OptionParser(usage="usage: %prog build [options]") parser.add_option('', "--no-sandbox", help="disable use of sandboxing", action='store_true') parser.add_option("-s", "--shell", help="run a shell instead of building", action='store_true') parser.add_option("-c", "--clean", help="remove the build directories", action='store_true') parser.add_option("-f", "--force", help="build even if dependencies have changed", action='store_true') parser.disable_interspersed_args() (options, args2) = parser.parse_args(args) builddir = os.path.realpath('build') changes = buildenv.get_build_changes() if changes: if not (options.force or options.clean): raise SafeException("Build dependencies have changed:\n" + '\n'.join(changes) + "\n\n" + "To build anyway, use: 0compile build --force\n" + "To do a clean build: 0compile build --clean") if not options.no_sandbox: print "Build dependencies have changed:\n" + '\n'.join(changes) ensure_dir(builddir, options.clean) ensure_dir(buildenv.distdir, options.clean) if options.no_sandbox: return do_build_internal(options, args2) tmpdir = tempfile.mkdtemp(prefix = '0compile-') try: my_dir = os.path.dirname(__file__) readable = ['.', my_dir] writable = ['build', buildenv.distdir, tmpdir] env('TMPDIR', tmpdir) for selection in sels.selections.values(): if not is_package_impl(selection): readable.append(lookup(selection)) options = [] if __main__.options.verbose: options.append('--verbose') readable.append('/etc') # /etc/ld.* spawn_and_check_maybe_sandboxed(readable, writable, tmpdir, sys.executable, ['-u', sys.argv[0]] + options + ['build', '--no-sandbox'] + args) finally: info("Deleting temporary directory '%s'" % tmpdir) shutil.rmtree(tmpdir)
def do_publish(args): """publish [ DOWNLOAD-BASE-URL ]""" parser = OptionParser(usage="usage: %prog publish [options] [ DOWNLOAD-BASE-URL ]") parser.add_option('', "--target-feed", help="name of output feed file to create", metavar='FILE') (options, args2) = parser.parse_args(args) buildenv = BuildEnv() if len(args2) == 0: if not buildenv.download_base_url: raise SafeException("No download base set. Give the URL for a remote directory.") elif len(args2) == 1: buildenv.config.set('compile', 'download-base-url', args2[0]) buildenv.save() info("Using download base URL: %s", buildenv.download_base_url) if not os.path.isdir(buildenv.distdir): raise SafeException("Directory '%s' does not exist. Try 'compile build'." % buildenv.distdir) distdir = os.path.basename(buildenv.distdir) archive_name = buildenv.archive_stem + '.tar.bz2' # Make all directories in the archive user writable for main, dirs, files in os.walk(distdir): os.chmod(main, os.stat(main).st_mode | 0200) import tarfile archive = tarfile.open(archive_name, mode = 'w:bz2') archive.add(distdir, buildenv.archive_stem) archive.close() target_feed = options.target_feed or buildenv.local_download_iface download_url = os.path.join(buildenv.download_base_url, archive_name) shutil.copyfile(buildenv.local_iface_file, target_feed) # XXX: we're assuming that 0publish requires the same version of Python as # 0compile. This is currently needed for Arch Linux, but long-term we need to # use the <runner>. spawn_and_check(sys.executable, [ pubish_command, target_feed, '--archive-url', download_url, '--archive-extract', buildenv.archive_stem]) if options.target_feed is None: # If --target-feed is used this is probably a script, so don't print # out hints. print "Now upload '%s' as:\n%s\n" % (archive_name, download_url) print "Once uploaded, you can download and run with:" print "$ 0launch %s" % target_feed
def do_report_bug(args): """report-bug""" buildenv = BuildEnv() log_name = join('build', 'build-failure.log') build_log = codecs.open(log_name, 'r', 'utf-8') log_text = build_log.read() build_log.close() build_env_xml_file = join(buildenv.metadir, 'build-environment.xml') if os.path.exists(build_env_xml_file): with open(build_env_xml_file, 'r') as build_env_xml: log_text += '\n\nSelected versions:\n' + build_env_xml.read() else: log_text += '\n\n"%s" file not found' % build_env_xml_file log_text = codecs.encode(log_text, 'utf-8') import urllib.request, urllib.parse, urllib.error from urllib.request import urlopen print("Sending contents of %s file to default bug reporting site..." % log_name) stream = urlopen( 'http://api.0install.net/api/report-bug/', urllib.parse.urlencode({ 'uri': buildenv.interface, 'body': log_text }).encode()) print(stream.read()) stream.close()
def set_responses_sensitive(self): self.set_response_sensitive(RESPONSE_SETUP, True) self.set_response_sensitive(RESPONSE_BUILD, True) buildenv = BuildEnv() have_binary = os.path.exists(buildenv.local_iface_file) self.set_response_sensitive(RESPONSE_REGISTER, have_binary) self.set_response_sensitive(RESPONSE_PUBLISH, have_binary)
def do_setup(args, get_dir_callback = None): "setup [ SOURCE-URI [ DIR ] ]" if len(args) == 0: assert get_dir_callback is None buildenv = BuildEnv() interface = buildenv.interface assert interface create_dir = None buildenv.get_selections(prompt = True) else: buildenv = BuildEnv(need_config = False) interface = args[0] if get_dir_callback: assert len(args) == 1 if len(args) == 1: create_dir = os.path.basename(interface) if create_dir.endswith('.xml'): create_dir = create_dir[:-4] if create_dir.startswith('alias:'): create_dir = create_dir.split(':', 1)[1] assert os.path.dirname(create_dir) == '' assert create_dir != os.path.curdir if get_dir_callback: create_dir = get_dir_callback(create_dir) elif len(args) == 2: create_dir = args[1] if create_dir == '.': create_dir = None else: raise __main__.UsageError() iface_uri = model.canonical_iface_uri(args[0]) if os.path.isabs(iface_uri): root = qdom.parse(file(iface_uri)) if root.uri == namespaces.XMLNS_IFACE and root.name == 'selections': # Looks like this is a selections file, not an interface. buildenv.config.set('compile', 'selections', iface_uri) iface_uri = root.getAttribute('interface') buildenv.config.set('compile', 'interface', iface_uri) if create_dir and os.path.exists(create_dir): raise SafeException("Directory '%s' already exists." % create_dir) buildenv.get_selections() if create_dir: try: os.mkdir(create_dir) except: print >>sys.stderr, "Failed to create new directory '%s'" % os.path.abspath(create_dir) raise os.chdir(create_dir) print "Created directory %s" % create_dir buildenv.save()
def do_clean(args): """clean""" if args: raise __main__.UsageError() buildenv = BuildEnv() for x in ['build', buildenv.distdir]: if os.path.exists(x): print "Removing '%s'" % os.path.basename(x) shutil.rmtree(x)
def do_gui(args): "gui [--no-prompt] [SOURCE-URI]" if args and args[0] == '--no-prompt': del args[0] # This option no longer has any effect, since it is the default. # However, old versions of 0launch's GUI pass it (< 0.52) from zeroinstall.gtkui import pygtkcompat pygtkcompat.enable() pygtkcompat.enable_gtk(version='3.0') import gui_support import gtk try: if len(args) == 0: pass elif len(args) == 1: import setup def get_dir_callback(default_dir): compile_dir = gui_support.choose_dir( _('Create build directory'), default_dir) if compile_dir: return compile_dir raise SafeException("Cancelled at user's request") setup.do_setup(args, get_dir_callback) else: raise SafeException("usage: 0compile gui URI") buildenv = BuildEnv() box = gui_support.CompileBox(buildenv.interface) box.connect('destroy', lambda b: gtk.main_quit()) box.show() gtk.main() except KeyboardInterrupt: pass except SafeException as ex: gui_support.alert(None, '%s' % ex) sys.exit(1) except Exception as ex: import traceback traceback.print_exc() gui_support.alert(None, '%s: %s' % (ex.__class__, ex)) sys.exit(1)
def do_setup(args, get_dir_callback=None): "setup [ SOURCE-URI [ DIR ] ]" if len(args) == 0: assert get_dir_callback is None buildenv = BuildEnv() interface = buildenv.interface assert interface create_dir = None buildenv.get_selections(prompt=True) else: buildenv = BuildEnv(need_config=False) interface = args[0] if get_dir_callback: assert len(args) == 1 if len(args) == 1: create_dir = os.path.basename(interface) if create_dir.endswith('.xml'): create_dir = create_dir[:-4] if create_dir.startswith('alias:'): create_dir = create_dir.split(':', 1)[1] assert os.path.dirname(create_dir) == '' assert create_dir != os.path.curdir if get_dir_callback: create_dir = get_dir_callback(create_dir) elif len(args) == 2: create_dir = args[1] if create_dir == '.': create_dir = None else: raise __main__.UsageError() iface_uri = model.canonical_iface_uri(args[0]) if os.path.isabs(iface_uri): # Use a relative path if the feed is inside the current directory. # This is useful if the properties file is shared with other users. rel_iface_uri = os.path.relpath(iface_uri, create_dir or ".") if not rel_iface_uri.startswith("."): iface_uri = rel_iface_uri root = qdom.parse(file(iface_uri)) if root.uri == namespaces.XMLNS_IFACE and root.name == 'selections': # Looks like this is a selections file, not an interface. buildenv.config.set('compile', 'selections', iface_uri) iface_uri = root.getAttribute('interface') buildenv.config.set('compile', 'interface', iface_uri) if create_dir and os.path.exists(create_dir): raise SafeException("Directory '%s' already exists." % create_dir) buildenv.get_selections() if create_dir: try: os.mkdir(create_dir) except: print >> sys.stderr, "Failed to create new directory '%s'" % os.path.abspath( create_dir) raise os.chdir(create_dir) print "Created directory %s" % create_dir buildenv.save()
def do_build_internal(options, args): """build-internal""" # If a sandbox is being used, we're in it now. import getpass, socket buildenv = BuildEnv() sels = buildenv.get_selections() builddir = os.path.realpath('build') ensure_dir(buildenv.metadir) build_env_xml = join(buildenv.metadir, 'build-environment.xml') buildenv_doc = sels.toDOM() # Create build-environment.xml file root = buildenv_doc.documentElement info = buildenv_doc.createElementNS(XMLNS_0COMPILE, 'build-info') root.appendChild(info) info.setAttributeNS(None, 'time', time.strftime('%Y-%m-%d %H:%M').strip()) info.setAttributeNS(None, 'host', socket.getfqdn()) info.setAttributeNS(None, 'user', getpass.getuser()) info.setAttributeNS(None, 'arch', '%s-%s' % (uname[0], uname[4])) stream = file(build_env_xml, 'w') buildenv_doc.writexml(stream, addindent=" ", newl="\n") stream.close() # Create local binary interface file. # We use the main feed for the interface as the template for the name, # summary, etc (note: this is not necessarily the feed that contained # the source code). master_feed = iface_cache.get_feed(buildenv.interface) src_impl = buildenv.chosen_impl(buildenv.interface) write_sample_feed(buildenv, master_feed, src_impl) # Check 0compile is new enough min_version = model.parse_version( src_impl.attrs.get(XMLNS_0COMPILE + ' min-version', None)) if min_version and min_version > model.parse_version(__main__.version): raise SafeException( "%s-%s requires 0compile >= %s, but we are only version %s" % (master_feed.get_name(), src_impl.version, model.format_version(min_version), __main__.version)) # Create the patch patch_file = join(buildenv.metadir, 'from-%s.patch' % src_impl.version) if buildenv.user_srcdir: with open(patch_file, 'w') as stream: # (ignore errors; will already be shown on stderr) try: subprocess.call(["diff", "-urN", buildenv.orig_srcdir, 'src'], stdout=stream) except OSError as ex: print >> sys.stderr, "WARNING: Failed to run 'diff': ", ex if os.path.getsize(patch_file) == 0: os.unlink(patch_file) elif os.path.exists(patch_file): os.unlink(patch_file) env('BUILDDIR', builddir) env('DISTDIR', buildenv.distdir) env('SRCDIR', buildenv.user_srcdir or buildenv.orig_srcdir) env('BINARYFEED', buildenv.local_iface_file) os.chdir(builddir) print "cd", builddir setup = CompileSetup(iface_cache.stores, sels) setup.prepare_env() # These mappings are needed when mixing Zero Install -dev packages with # native package binaries. mappings = {} for impl in sels.selections.values(): # Add mappings that have been set explicitly... new_mappings = impl.attrs.get(XMLNS_0COMPILE + ' lib-mappings', '') if new_mappings: new_mappings = new_mappings.split(' ') for mapping in new_mappings: assert ':' in mapping, "lib-mappings missing ':' in '%s' from '%s'" % ( mapping, impl.feed) name, major_version = mapping.split(':', 1) assert '/' not in mapping, "lib-mappings '%s' contains a / in the version number (from '%s')!" % ( mapping, impl.feed) if sys.platform == 'darwin': mappings[name] = 'lib%s.%s.dylib' % (name, major_version) else: mappings[name] = 'lib%s.so.%s' % (name, major_version) # Auto-detect required mappings where possible... # (if the -dev package is native, the symlinks will be OK) if not is_package_impl(impl): impl_path = lookup(impl) for libdirname in ['lib', 'usr/lib', 'lib64', 'usr/lib64']: libdir = os.path.join(impl_path, libdirname) if os.path.isdir(libdir): find_broken_version_symlinks(libdir, mappings) if mappings: set_up_mappings(mappings) overrides_dir = os.path.join(os.environ['TMPDIR'], PKG_CONFIG_OVERRIDES) if os.path.isdir(overrides_dir): add_overrides = model.EnvironmentBinding('PKG_CONFIG_PATH', PKG_CONFIG_OVERRIDES) do_env_binding(add_overrides, os.environ['TMPDIR']) # Some programs want to put temporary build files in the source directory. # Make a copy of the source if needed. dup_src_type = src_impl.attrs.get(XMLNS_0COMPILE + ' dup-src', None) if dup_src_type == 'true': dup_src(copy_file) env('SRCDIR', builddir) elif dup_src_type: raise Exception("Unknown dup-src value '%s'" % dup_src_type) if options.shell: spawn_and_check(find_in_path('cmd' if os.name == 'nt' else 'sh'), []) else: command = sels.commands[0].qdom.attrs.get('shell-command', None) if command is None: # New style <command> prog_args = setup.build_command(sels.interface, sels.command) + args else: # Old style shell-command='...' if os.name == 'nt': prog_args = [ os.environ['0COMPILE_BASH'], '-eux', '-c', command ] + args else: prog_args = ['/bin/sh', '-c', command + ' "$@"', '-'] + args assert len(sels.commands) == 1 # Remove any existing log files for log in ['build.log', 'build-success.log', 'build-failure.log']: if os.path.exists(log): os.unlink(log) # Run the command, copying output to a new log with open('build.log', 'w') as log: print >> log, "Build log for %s-%s" % (master_feed.get_name(), src_impl.version) print >> log, "\nBuilt using 0compile-%s" % __main__.version print >> log, "\nBuild system: " + ', '.join(uname) print >> log, "\n%s:\n" % ENV_FILE with open(os.path.join(os.pardir, ENV_FILE)) as properties_file: shutil.copyfileobj(properties_file, log) log.write('\n') if os.path.exists(patch_file): print >> log, "\nPatched with:\n" shutil.copyfileobj(file(patch_file), log) log.write('\n') if command: print "Executing: " + command, args print >> log, "Executing: " + command, args else: print "Executing: " + str(prog_args) print >> log, "Executing: " + str(prog_args) # Tee the output to the console and to the log child = subprocess.Popen(prog_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: data = os.read(child.stdout.fileno(), 100) if not data: break sys.stdout.write(data) log.write(data) status = child.wait() failure = None if status == 0: print >> log, "Build successful" shorten_dynamic_library_install_names() fixup_generated_pkgconfig_files() remove_la_files() elif status > 0: failure = "Build failed with exit code %d" % status else: failure = "Build failure: exited due to signal %d" % (-status) if failure: print >> log, failure if failure: os.rename('build.log', 'build-failure.log') raise SafeException("Command '%s': %s" % (prog_args, failure)) else: os.rename('build.log', 'build-success.log')
def compile_and_register(self, sels, forced_iface_uri = None): """If forced_iface_uri, register as an implementation of this interface, ignoring the any <feed-for>, etc.""" buildenv = BuildEnv(need_config = False) buildenv.config.set('compile', 'interface', sels.interface) buildenv.config.set('compile', 'selections', 'selections.xml') # Download any required packages now, so we can use the GUI to request confirmation, etc download_missing = sels.download_missing(self.config, include_packages = True) if download_missing: yield download_missing tasks.check(download_missing) tmpdir = tempfile.mkdtemp(prefix = '0compile-') try: os.chdir(tmpdir) # Write configuration for build... buildenv.save() sel_file = open('selections.xml', 'w') try: doc = sels.toDOM() doc.writexml(sel_file) sel_file.write('\n') finally: sel_file.close() # Do the build... build = self.spawn_build(buildenv.iface_name) if build: yield build tasks.check(build) # Register the result... dom = minidom.parse(buildenv.local_iface_file) feed_for_elem, = dom.getElementsByTagNameNS(namespaces.XMLNS_IFACE, 'feed-for') claimed_iface = feed_for_elem.getAttribute('interface') if forced_iface_uri is not None: if forced_iface_uri != claimed_iface: self.note("WARNING: registering as feed for {forced}, though feed claims to be for {claimed}".format( forced = forced_iface_uri, claimed = claimed_iface)) else: forced_iface_uri = claimed_iface # (the top-level interface being built) version = sels.selections[sels.interface].version site_package_versions_dir = basedir.save_data_path('0install.net', 'site-packages', *model.escape_interface_uri(forced_iface_uri)) leaf = '%s-%s' % (version, uname[4]) site_package_dir = os.path.join(site_package_versions_dir, leaf) self.note("Storing build in %s" % site_package_dir) # 1. Copy new version in under a temporary name. Names starting with '.' are ignored by 0install. tmp_distdir = os.path.join(site_package_versions_dir, '.new-' + leaf) shutil.copytree(buildenv.distdir, tmp_distdir, symlinks = True) # 2. Rename the previous build to .old-VERSION (deleting that if it already existed) if os.path.exists(site_package_dir): self.note("(moving previous build out of the way)") previous_build_dir = os.path.join(site_package_versions_dir, '.old-' + leaf) if os.path.exists(previous_build_dir): shutil.rmtree(previous_build_dir) os.rename(site_package_dir, previous_build_dir) else: previous_build_dir = None # 3. Rename the new version immediately after renaming away the old one to minimise time when there's # no version. os.rename(tmp_distdir, site_package_dir) # 4. Delete the old version. if previous_build_dir: self.note("(deleting previous build)") shutil.rmtree(previous_build_dir) local_feed = os.path.join(site_package_dir, '0install', 'feed.xml') assert os.path.exists(local_feed), "Feed %s not found!" % local_feed # Reload - our 0install will detect the new feed automatically iface = self.config.iface_cache.get_interface(forced_iface_uri) reader.update_from_cache(iface, iface_cache = self.config.iface_cache) self.config.iface_cache.get_feed(local_feed, force = True) # Write it out - 0install will add the feed so that older 0install versions can find it writer.save_interface(iface) except: self.note("\nBuild failed: leaving build directory %s for inspection...\n" % tmpdir) raise else: # Can't delete current directory on Windows, so move to parent first os.chdir(os.path.join(tmpdir, os.path.pardir)) ro_rmtree(tmpdir)
def do_publish(args): """publish [ DOWNLOAD-BASE-URL ]""" parser = OptionParser( usage="usage: %prog publish [options] [ DOWNLOAD-BASE-URL ]") parser.add_option('', "--target-feed", help="name of output feed file to create", metavar='FILE') (options, args2) = parser.parse_args(args) buildenv = BuildEnv() if len(args2) == 1: buildenv.config.set('compile', 'download-base-url', args2[0]) buildenv.save() elif len(args2) > 1: parser.print_help() sys.exit(1) download_base_url = buildenv.download_base_url or None if download_base_url: info("Using download base URL: %s", download_base_url) if not os.path.isdir(buildenv.distdir): raise SafeException( "Directory '%s' does not exist. Try 'compile build'." % buildenv.distdir) distdir = os.path.basename(buildenv.distdir) archive_name = buildenv.archive_stem + '.tar.bz2' # Make all directories in the archive user writable for main, dirs, files in os.walk(distdir): os.chmod(main, os.stat(main).st_mode | 0o200) import tarfile archive = tarfile.open(archive_name, mode='w:bz2') archive.add(distdir, buildenv.archive_stem) archive.close() target_feed = options.target_feed or buildenv.local_download_iface if download_base_url: download_url = os.path.join(download_base_url, archive_name) else: download_url = archive_name shutil.copyfile(buildenv.local_iface_file, target_feed) # XXX: we're assuming that 0publish requires the same version of Python as # 0compile. This is currently needed for Arch Linux, but long-term we need to # use the <runner>. spawn_and_check(sys.executable, [ pubish_command, target_feed, '--archive-url', download_url, '--archive-extract', buildenv.archive_stem ]) if options.target_feed is None: # If --target-feed is used this is probably a script, so don't print # out hints. if download_base_url: print("Now upload '%s' as:\n%s\n" % (archive_name, download_url)) print("Once uploaded, you can download and run with:") print("0launch %s" % target_feed) else: print("Generated archive '%s' and feed '%s'." % (archive_name, target_feed)) print("Upload it to a repository with:") print("0repo add %s" % target_feed)
def compile_and_register(self, sels, forced_iface_uri=None): """If forced_iface_uri, register as an implementation of this interface, ignoring the any <feed-for>, etc.""" buildenv = BuildEnv(need_config=False) buildenv.config.set('compile', 'interface', sels.interface) buildenv.config.set('compile', 'selections', 'selections.xml') # Download any required packages now, so we can use the GUI to request confirmation, etc download_missing = sels.download_missing(self.config, include_packages=True) if download_missing: yield download_missing tasks.check(download_missing) tmpdir = tempfile.mkdtemp(prefix='0compile-') try: os.chdir(tmpdir) # Write configuration for build... buildenv.save() sel_file = open('selections.xml', 'w') try: doc = sels.toDOM() doc.writexml(sel_file) sel_file.write('\n') finally: sel_file.close() # Do the build... build = self.spawn_build(buildenv.iface_name) if build: yield build tasks.check(build) # Register the result... dom = minidom.parse(buildenv.local_iface_file) feed_for_elem, = dom.getElementsByTagNameNS( namespaces.XMLNS_IFACE, 'feed-for') claimed_iface = feed_for_elem.getAttribute('interface') if forced_iface_uri is not None: if forced_iface_uri != claimed_iface: self.note( "WARNING: registering as feed for {forced}, though feed claims to be for {claimed}" .format(forced=forced_iface_uri, claimed=claimed_iface)) else: forced_iface_uri = claimed_iface # (the top-level interface being built) version = sels.selections[sels.interface].version site_package_versions_dir = basedir.save_data_path( '0install.net', 'site-packages', *model.escape_interface_uri(forced_iface_uri)) leaf = '%s-%s' % (version, build_target_machine_type) site_package_dir = os.path.join(site_package_versions_dir, leaf) self.note("Storing build in %s" % site_package_dir) # 1. Copy new version in under a temporary name. Names starting with '.' are ignored by 0install. tmp_distdir = os.path.join(site_package_versions_dir, '.new-' + leaf) shutil.copytree(buildenv.distdir, tmp_distdir, symlinks=True) # 2. Rename the previous build to .old-VERSION (deleting that if it already existed) if os.path.exists(site_package_dir): self.note("(moving previous build out of the way)") previous_build_dir = os.path.join(site_package_versions_dir, '.old-' + leaf) if os.path.exists(previous_build_dir): shutil.rmtree(previous_build_dir) os.rename(site_package_dir, previous_build_dir) else: previous_build_dir = None # 3. Rename the new version immediately after renaming away the old one to minimise time when there's # no version. os.rename(tmp_distdir, site_package_dir) # 4. Delete the old version. if previous_build_dir: self.note("(deleting previous build)") shutil.rmtree(previous_build_dir) local_feed = os.path.join(site_package_dir, '0install', 'feed.xml') assert os.path.exists( local_feed), "Feed %s not found!" % local_feed # Reload - our 0install will detect the new feed automatically iface = self.config.iface_cache.get_interface(forced_iface_uri) reader.update_from_cache(iface, iface_cache=self.config.iface_cache) self.config.iface_cache.get_feed(local_feed, force=True) # Write it out - 0install will add the feed so that older 0install versions can find it writer.save_interface(iface) seen_key = (forced_iface_uri, sels.selections[sels.interface].id) assert seen_key not in self.seen, seen_key self.seen[seen_key] = site_package_dir except: self.note( "\nBuild failed: leaving build directory %s for inspection...\n" % tmpdir) raise else: # Can't delete current directory on Windows, so move to parent first os.chdir(os.path.join(tmpdir, os.path.pardir)) ro_rmtree(tmpdir)
def response(box, resp): if resp == RESPONSE_SETUP: def done_setup(): self.add_msg('Now use Build to compile the chosen source code.') self.run_command((sys.executable, main_path, 'setup'), done_setup) elif resp == RESPONSE_BUILD: def done_build(): self.add_msg('\nBuild successful. Now register or publish the build.') def build_failed(): self.add_msg('\nIf the messages displayed above indicate a missing dependency (e.g. no C compiler ' "or a library that isn't available through Zero Install) then install it using your " 'normal package manager and click on Build again. Note that for libraries you often ' 'need the -dev version of the package. ' '\nOtherwise, please notify the developers of this problem (this will transmit ' 'the contents of the build/build-failure.log file):') end = self.buffer.get_end_iter() anchor = self.buffer.create_child_anchor(end) align = gtk.Alignment(0.0, 0.0, 1.0, 1.0) button = ButtonMixed(gtk.STOCK_YES, 'Notify developers') align.add(button) align.set_padding(8, 8, 8, 8) align.show_all() self.tv.add_child_at_anchor(align, anchor) self.add_msg('\n') def report_bug(button): def done_notify(): self.add_msg("\nReport sent. Thank you! (note: you won't get a reply, as " "no contact details were sent; write to the project's mailing " "list if you want to discuss the problem)") self.run_command((sys.executable, main_path, 'report-bug'), done_notify) button.connect('clicked', report_bug) buildenv = BuildEnv() changes = buildenv.get_build_changes() if changes: options = get_build_options(box, '\n'.join(changes) + '\n\nIt would be best to do a clean (full) build.') else: options = [] if options is not None: box.run_command([sys.executable, main_path, 'build'] + options, done_build, build_failed) elif resp == RESPONSE_REGISTER: buildenv = BuildEnv() iface = iface_cache.get_interface(interface) reader.update_from_cache(iface) # Register using the feed-for, if available real_iface = iface for uri in iface.feed_for or []: real_iface = iface_cache.get_interface(uri) self.add_msg("Registering as a feed for %s" % real_iface.uri) break else: if os.path.isabs(iface.uri): self.add_msg("Warning: no <feed-for> in local feed %s!" % iface.uri) feed = buildenv.local_iface_file for f in real_iface.feeds or []: if f.uri == feed: self.add_msg("Feed '%s' is already registered for interface '%s'!\n" % (feed, real_iface.uri)) return box.buffer.insert_at_cursor("Registering feed '%s'\n" % feed) real_iface.extra_feeds.append(model.Feed(feed, arch = None, user_override = True)) writer.save_interface(real_iface) box.buffer.insert_at_cursor("Done. You can now close this window.\n") elif resp == RESPONSE_PUBLISH: buildenv = BuildEnv() box = PublishBox(self, buildenv) resp = box.run() box.destroy() if resp == gtk.RESPONSE_OK: def done_publish(): self.add_msg("\nYou can use '0publish --local' to add this " "into the main feed. If you don't have a main feed then this " "will create one. See " "http://0install.net/injector-packagers.html for more information.") self.run_command((sys.executable, main_path, 'publish', box.archive_dir.get_text()), done_publish) elif resp == gtk.RESPONSE_CANCEL or resp == gtk.RESPONSE_DELETE_EVENT: if self.kill_child(): return self.destroy() else: self.add_msg('Unknown response: %s' % resp)
def do_build_internal(options, args): """build-internal""" # If a sandbox is being used, we're in it now. import getpass, socket buildenv = BuildEnv() sels = buildenv.get_selections() builddir = os.path.realpath('build') ensure_dir(buildenv.metadir) build_env_xml = join(buildenv.metadir, 'build-environment.xml') buildenv_doc = sels.toDOM() # Create build-environment.xml file root = buildenv_doc.documentElement info = buildenv_doc.createElementNS(XMLNS_0COMPILE, 'build-info') root.appendChild(info) info.setAttributeNS(None, 'time', time.strftime('%Y-%m-%d %H:%M').strip()) info.setAttributeNS(None, 'host', socket.getfqdn()) info.setAttributeNS(None, 'user', getpass.getuser()) info.setAttributeNS(None, 'arch', '%s-%s' % (uname[0], uname[4])) stream = file(build_env_xml, 'w') buildenv_doc.writexml(stream, addindent=" ", newl="\n") stream.close() # Create local binary interface file. # We use the main feed for the interface as the template for the name, # summary, etc (note: this is not necessarily the feed that contained # the source code). master_feed = iface_cache.get_feed(buildenv.interface) src_impl = buildenv.chosen_impl(buildenv.interface) write_sample_feed(buildenv, master_feed, src_impl) # Check 0compile is new enough min_version = model.parse_version(src_impl.attrs.get(XMLNS_0COMPILE + ' min-version', None)) if min_version and min_version > model.parse_version(__main__.version): raise SafeException("%s-%s requires 0compile >= %s, but we are only version %s" % (master_feed.get_name(), src_impl.version, model.format_version(min_version), __main__.version)) # Create the patch patch_file = join(buildenv.metadir, 'from-%s.patch' % src_impl.version) if buildenv.user_srcdir: with open(patch_file, 'w') as stream: # (ignore errors; will already be shown on stderr) try: subprocess.call(["diff", "-urN", buildenv.orig_srcdir, 'src'], stdout = stream) except OSError as ex: print >>sys.stderr, "WARNING: Failed to run 'diff': ", ex if os.path.getsize(patch_file) == 0: os.unlink(patch_file) elif os.path.exists(patch_file): os.unlink(patch_file) env('BUILDDIR', builddir) env('DISTDIR', buildenv.distdir) env('SRCDIR', buildenv.user_srcdir or buildenv.orig_srcdir) env('BINARYFEED', buildenv.local_iface_file) os.chdir(builddir) print "cd", builddir setup = CompileSetup(iface_cache.stores, sels) setup.prepare_env() # These mappings are needed when mixing Zero Install -dev packages with # native package binaries. mappings = {} for impl in sels.selections.values(): # Add mappings that have been set explicitly... new_mappings = impl.attrs.get(XMLNS_0COMPILE + ' lib-mappings', '') if new_mappings: new_mappings = new_mappings.split(' ') for mapping in new_mappings: assert ':' in mapping, "lib-mappings missing ':' in '%s' from '%s'" % (mapping, impl.feed) name, major_version = mapping.split(':', 1) assert '/' not in mapping, "lib-mappings '%s' contains a / in the version number (from '%s')!" % (mapping, impl.feed) if sys.platform == 'darwin': mappings[name] = 'lib%s.%s.dylib' % (name, major_version) else: mappings[name] = 'lib%s.so.%s' % (name, major_version) # Auto-detect required mappings where possible... # (if the -dev package is native, the symlinks will be OK) if not is_package_impl(impl): impl_path = lookup(impl) for libdirname in ['lib', 'usr/lib', 'lib64', 'usr/lib64']: libdir = os.path.join(impl_path, libdirname) if os.path.isdir(libdir): find_broken_version_symlinks(libdir, mappings) if mappings: set_up_mappings(mappings) overrides_dir = os.path.join(os.environ['TMPDIR'], PKG_CONFIG_OVERRIDES) if os.path.isdir(overrides_dir): add_overrides = model.EnvironmentBinding('PKG_CONFIG_PATH', PKG_CONFIG_OVERRIDES) do_env_binding(add_overrides, os.environ['TMPDIR']) # Some programs want to put temporary build files in the source directory. # Make a copy of the source if needed. dup_src_type = src_impl.attrs.get(XMLNS_0COMPILE + ' dup-src', None) if dup_src_type == 'true': dup_src(shutil.copy2) env('SRCDIR', builddir) elif dup_src_type: raise Exception("Unknown dup-src value '%s'" % dup_src_type) if options.shell: spawn_and_check(find_in_path('cmd' if os.name == 'nt' else 'sh'), []) else: command = sels.commands[0].qdom.attrs.get('shell-command', None) if command is None: # New style <command> prog_args = setup.build_command(sels.interface, sels.command) + args else: # Old style shell-command='...' if os.name == 'nt': prog_args = ['cmd', '/c', command] + args else: prog_args = ['/bin/sh', '-c', command + ' "$@"', '-'] + args assert len(sels.commands) == 1 # Remove any existing log files for log in ['build.log', 'build-success.log', 'build-failure.log']: if os.path.exists(log): os.unlink(log) # Run the command, copying output to a new log with open('build.log', 'w') as log: print >>log, "Build log for %s-%s" % (master_feed.get_name(), src_impl.version) print >>log, "\nBuilt using 0compile-%s" % __main__.version print >>log, "\nBuild system: " + ', '.join(uname) print >>log, "\n%s:\n" % ENV_FILE with open(os.path.join(os.pardir, ENV_FILE)) as properties_file: shutil.copyfileobj(properties_file, log) log.write('\n') if os.path.exists(patch_file): print >>log, "\nPatched with:\n" shutil.copyfileobj(file(patch_file), log) log.write('\n') if command: print "Executing: " + command, args print >>log, "Executing: " + command, args else: print "Executing: " + str(prog_args) print >>log, "Executing: " + str(prog_args) # Tee the output to the console and to the log child = subprocess.Popen(prog_args, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) while True: data = os.read(child.stdout.fileno(), 100) if not data: break sys.stdout.write(data) log.write(data) status = child.wait() failure = None if status == 0: print >>log, "Build successful" shorten_dynamic_library_install_names() fixup_generated_pkgconfig_files() remove_la_files() elif status > 0: failure = "Build failed with exit code %d" % status else: failure = "Build failure: exited due to signal %d" % (-status) if failure: print >>log, failure if failure: os.rename('build.log', 'build-failure.log') raise SafeException("Command '%s': %s" % (prog_args, failure)) else: os.rename('build.log', 'build-success.log')