Fix up Java's main path after installing
[zeroinstall/solver.git] / zeroinstall / helpers.py
blob3f097de876fb0488a237ec8af3aeeb6dc6c35606
1 """
2 Convenience routines for performing common operations.
3 @since: 0.28
4 """
6 # Copyright (C) 2009, Thomas Leonard
7 # See the README file for details, or visit http://0install.net.
9 import os, sys
10 from zeroinstall import support, SafeException, logger
11 from zeroinstall.support import tasks
13 DontUseGUI = object()
15 def get_selections_gui(iface_uri, gui_args, test_callback = None, use_gui = True):
16 """Run the GUI to choose and download a set of implementations.
17 The user may ask the GUI to submit a bug report about the program. In that case,
18 the GUI may ask us to test it. test_callback is called in that case with the implementations
19 to be tested; the callback will typically call L{zeroinstall.injector.run.test_selections} and return the result of that.
20 @param iface_uri: the required program, or None to show just the preferences dialog
21 @type iface_uri: str
22 @param gui_args: any additional arguments for the GUI itself
23 @type gui_args: [str]
24 @param test_callback: function to use to try running the program
25 @type test_callback: L{zeroinstall.injector.selections.Selections} -> str
26 @param use_gui: if True, raise a SafeException if the GUI is not available. If None, returns DontUseGUI if the GUI cannot be started. If False, returns DontUseGUI always. (since 1.11)
27 @param use_gui: bool | None
28 @return: the selected implementations
29 @rtype: L{zeroinstall.injector.selections.Selections}
30 @since: 0.28
31 """
32 if use_gui is False:
33 return DontUseGUI
35 if 'DISPLAY' not in os.environ:
36 if use_gui is None:
37 return DontUseGUI
38 else:
39 raise SafeException("Can't use GUI because $DISPLAY is not set")
41 from zeroinstall.injector import selections, qdom
42 from io import BytesIO
44 from os.path import join, dirname
45 gui_exe = join(dirname(__file__), '0launch-gui', '0launch-gui')
47 import socket
48 cli, gui = socket.socketpair()
50 try:
51 child = os.fork()
52 if child == 0:
53 # We are the child (GUI)
54 try:
55 try:
56 cli.close()
57 # We used to use pipes to support Python2.3...
58 os.dup2(gui.fileno(), 1)
59 os.dup2(gui.fileno(), 0)
60 if use_gui is True:
61 gui_args = ['-g'] + gui_args
62 if iface_uri is not None:
63 gui_args = gui_args + ['--', iface_uri]
64 os.execvp(sys.executable, [sys.executable, gui_exe] + gui_args)
65 except:
66 import traceback
67 traceback.print_exc(file = sys.stderr)
68 finally:
69 sys.stderr.flush()
70 os._exit(1)
71 # We are the parent (CLI)
72 gui.close()
73 gui = None
75 while True:
76 logger.info("Waiting for selections from GUI...")
78 reply = support.read_bytes(cli.fileno(), len('Length:') + 9, null_ok = True)
79 if reply:
80 if not reply.startswith(b'Length:'):
81 raise Exception("Expected Length:, but got %s" % repr(reply))
82 reply = reply.decode('ascii')
83 xml = support.read_bytes(cli.fileno(), int(reply.split(':', 1)[1], 16))
85 dom = qdom.parse(BytesIO(xml))
86 sels = selections.Selections(dom)
88 if dom.getAttribute('run-test'):
89 logger.info("Testing program, as requested by GUI...")
90 if test_callback is None:
91 output = b"Can't test: no test_callback was passed to get_selections_gui()\n"
92 else:
93 output = test_callback(sels)
94 logger.info("Sending results to GUI...")
95 output = ('Length:%8x\n' % len(output)).encode('utf-8') + output
96 logger.debug("Sending: %s", repr(output))
97 while output:
98 sent = cli.send(output)
99 output = output[sent:]
100 continue
101 else:
102 sels = None
104 pid, status = os.waitpid(child, 0)
105 assert pid == child
106 if status == 1 << 8:
107 logger.info("User cancelled the GUI; aborting")
108 return None # Aborted
109 elif status == 100 << 8:
110 if use_gui is None:
111 return DontUseGUI
112 else:
113 raise SafeException("No GUI available")
114 if status != 0:
115 raise Exception("Error from GUI: code = %d" % status)
116 break
117 finally:
118 for sock in [cli, gui]:
119 if sock is not None: sock.close()
121 return sels
123 def ensure_cached(uri, command = 'run', config = None):
124 """Ensure that an implementation of uri is cached.
125 If not, it downloads one. It uses the GUI if a display is
126 available, or the console otherwise.
127 @param uri: the required interface
128 @type uri: str
129 @return: the selected implementations, or None if the user cancelled
130 @rtype: L{zeroinstall.injector.selections.Selections}
132 from zeroinstall.injector.driver import Driver
134 if config is None:
135 from zeroinstall.injector.config import load_config
136 config = load_config()
138 from zeroinstall.injector.requirements import Requirements
139 requirements = Requirements(uri)
140 requirements.command = command
142 d = Driver(config, requirements)
144 if d.need_download() or not d.solver.ready:
145 sels = get_selections_gui(uri, ['--command', command], use_gui = None)
146 if sels != DontUseGUI:
147 return sels
148 done = d.solve_and_download_impls()
149 tasks.wait_for_blocker(done)
151 return d.solver.selections