1 # Copyright (C) 2006, Thomas Leonard
2 # See http://0install.net/0compile.html
4 import sys
, os
, __main__
, popen2
5 import pygtk
; pygtk
.require('2.0')
7 from zeroinstall
.injector
import reader
, writer
16 action_responses
= [RESPONSE_SETUP
, RESPONSE_BUILD
, RESPONSE_PUBLISH
, RESPONSE_REGISTER
]
18 main_path
= os
.path
.abspath(__main__
.__file
__)
20 class CompileBox(gtk
.Dialog
):
23 def __init__(self
, interface
):
25 gtk
.Dialog
.__init
__(self
, _("Compile '%s'") % interface
.split('/')[-1]) # No rsplit on Python 2.3
26 self
.set_has_separator(False)
27 self
.set_default_size(gtk
.gdk
.screen_width() / 2, gtk
.gdk
.screen_height() / 2)
29 def add_action(stock
, name
, resp
):
30 if not hasattr(gtk
, stock
):
32 button
= ButtonMixed(getattr(gtk
, stock
), name
)
33 button
.set_flags(gtk
.CAN_DEFAULT
)
34 self
.add_action_widget(button
, resp
)
37 add_action('STOCK_PROPERTIES', '_Setup', RESPONSE_SETUP
)
38 add_action('STOCK_CONVERT', '_Build', RESPONSE_BUILD
)
39 add_action('STOCK_ADD', '_Register', RESPONSE_REGISTER
)
40 add_action('STOCK_NETWORK', '_Publish', RESPONSE_PUBLISH
)
42 self
.add_button(gtk
.STOCK_CLOSE
, gtk
.RESPONSE_CANCEL
)
44 self
.set_default_response(RESPONSE_BUILD
)
46 self
.buffer = gtk
.TextBuffer()
47 self
.tv
= gtk
.TextView(self
.buffer)
48 self
.tv
.set_left_margin(4)
49 self
.tv
.set_right_margin(4)
50 self
.tv
.set_wrap_mode(gtk
.WRAP_WORD
)
51 swin
= gtk
.ScrolledWindow()
52 swin
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_ALWAYS
)
54 swin
.set_shadow_type(gtk
.SHADOW_IN
)
55 self
.vscroll
= swin
.get_vadjustment()
56 self
.tv
.set_editable(False)
57 self
.tv
.set_cursor_visible(False)
58 self
.vbox
.pack_start(swin
, True, True, 0)
62 self
.connect('delete-event', lambda box
, dev
: True)
64 def response(box
, resp
):
65 if resp
== RESPONSE_SETUP
:
69 self
.add_msg('Now use Build to compile the chosen source code.')
70 self
.run_command((sys
.executable
, main_path
, 'setup'), done_setup
)
71 elif resp
== RESPONSE_BUILD
:
73 self
.add_msg('\nBuild successful. Now register or publish the build.')
75 self
.add_msg('\nIf the messages displayed above indicate a missing dependency (e.g. no C compiler '
76 "or a library that isn't available through Zero Install) then install it using your "
77 'normal package manager and click on Build again. Note that for libraries you often '
78 'need the -dev version of the package. '
79 '\nOtherwise, please notify the developers of this problem (this will transmit '
80 'the contents of the build/build-failure.log file):')
81 end
= self
.buffer.get_end_iter()
82 anchor
= self
.buffer.create_child_anchor(end
)
83 align
= gtk
.Alignment(0.0, 0.0, 1.0, 1.0)
84 button
= ButtonMixed(gtk
.STOCK_YES
, 'Notify developers')
86 align
.set_padding(8, 8, 8, 8)
88 self
.tv
.add_child_at_anchor(align
, anchor
)
90 def report_bug(button
):
92 self
.add_msg("\nReport sent. Thank you! (note: you won't get a reply, as "
93 "no contact details were sent; write to the project's mailing "
94 "list if you want to discuss the problem)")
95 self
.run_command((sys
.executable
, main_path
, 'report-bug'), done_notify
)
96 button
.connect('clicked', report_bug
)
98 changes
= buildenv
.get_build_changes()
100 options
= get_build_options(box
, '\n'.join(changes
) + '\n\nIt would be best to do a clean (full) build.')
103 if options
is not None:
104 box
.run_command([sys
.executable
, main_path
, 'build'] + options
, done_build
, build_failed
)
105 elif resp
== RESPONSE_REGISTER
:
106 buildenv
= BuildEnv()
108 iface
= iface_cache
.get_interface(interface
)
109 reader
.update_from_cache(iface
)
110 feed
= buildenv
.local_iface_file
111 for f
in iface
.feeds
or []:
113 self
.add_msg("Feed '%s' is already registered for interface '%s'!\n" % (feed
, iface
.uri
))
115 box
.buffer.insert_at_cursor("Registering feed '%s'\n" % feed
)
116 iface
.extra_feeds
.append(model
.Feed(feed
, arch
= None, user_override
= True))
117 writer
.save_interface(iface
)
118 box
.buffer.insert_at_cursor("Done. You can now close this window.\n")
119 elif resp
== RESPONSE_PUBLISH
:
120 buildenv
= BuildEnv()
121 box
= PublishBox(self
, buildenv
)
124 if resp
== gtk
.RESPONSE_OK
:
126 self
.add_msg("\nYou can use '0publish --local' to add this "
127 "into the main feed. If you don't have a main feed then this "
128 "will create one. See "
129 "http://0install.net/injector-packagers.html for more information.")
130 self
.run_command((sys
.executable
, main_path
,
131 'publish', box
.archive_dir
.get_text()), done_publish
)
132 elif resp
== gtk
.RESPONSE_CANCEL
or resp
== gtk
.RESPONSE_DELETE_EVENT
:
133 if self
.kill_child(): return
136 self
.add_msg('Unknown response: %s' % resp
)
138 self
.connect('response', response
)
140 self
.system_tag
= self
.buffer.create_tag('system', foreground
= 'blue', background
= 'white')
141 self
.add_msg(instructions
)
142 self
.set_responses_sensitive()
144 def kill_child(self
):
145 if self
.child
is None: return False
149 self
.add_msg('\nSending SIGTERM to process...')
150 os
.kill(-self
.child
, signal
.SIGTERM
)
153 def add_msg(self
, msg
):
154 self
.insert_at_end_and_scroll(msg
+ '\n', self
.system_tag
)
156 """Run command in a sub-process.
157 Calls success() if the command exits with status zero.
158 Calls failure() if it fails for other reasons.
159 (neither is called if the user aborts the command)"""
160 def run_command(self
, command
, success
, failure
= None):
161 assert self
.child
is None
163 self
.success
= success
164 self
.failure
= failure
165 if isinstance(command
, basestring
):
166 self
.add_msg("Running: " + command
+ "\n")
168 self
.add_msg("Running: " + ' '.join(command
) + "\n")
173 self
.child
= os
.fork()
182 os
.setpgrp() # Become group leader
183 os
.execvp(command
[0], command
)
186 traceback
.print_exc()
195 for resp
in action_responses
:
196 self
.set_response_sensitive(resp
, False)
199 gobject
.io_add_watch(r
, gobject
.IO_IN | gobject
.IO_HUP
, self
.got_data
)
201 def set_responses_sensitive(self
):
202 self
.set_response_sensitive(RESPONSE_SETUP
, True)
203 self
.set_response_sensitive(RESPONSE_BUILD
, True)
205 buildenv
= BuildEnv()
206 have_binary
= os
.path
.exists(buildenv
.local_iface_file
)
207 self
.set_response_sensitive(RESPONSE_REGISTER
, have_binary
)
208 self
.set_response_sensitive(RESPONSE_PUBLISH
, have_binary
)
210 def insert_at_end_and_scroll(self
, data
, *tags
):
211 near_end
= self
.vscroll
.upper
- self
.vscroll
.page_size
* 1.5 < self
.vscroll
.value
212 end
= self
.buffer.get_end_iter()
213 self
.buffer.insert_with_tags(end
, data
, *tags
)
215 cursor
= self
.buffer.get_insert()
216 self
.buffer.move_mark(cursor
, end
)
217 self
.tv
.scroll_to_mark(cursor
, 0, False, 0, 0)
219 def got_data(self
, src
, cond
):
220 data
= os
.read(src
, 100)
222 # TODO: only insert complete UTF-8 sequences, not half sequences
223 self
.insert_at_end_and_scroll(data
)
226 pid
, status
= os
.waitpid(self
.child
, 0)
227 assert pid
== self
.child
230 self
.set_responses_sensitive()
232 if os
.WIFEXITED(status
) and os
.WEXITSTATUS(status
) == 0:
235 self
.add_msg("\nCommand terminated at user's request.")
237 self
.add_msg("\nCommand failed.")
243 def choose_dir(title
, default
):
244 sel
= gtk
.FileSelection(title
)
245 sel
.set_has_separator(False)
246 sel
.set_filename(default
)
249 if resp
== gtk
.RESPONSE_OK
:
250 build_dir
= sel
.get_filename()
251 if not os
.path
.exists(build_dir
):
254 alert(sel
, _("'%s' already exists") % build_dir
)
259 def alert(parent
, msg
):
260 d
= gtk
.MessageDialog(parent
,
268 class ButtonMixed(gtk
.Button
):
269 """A button with a standard stock icon, but any label. This is useful
270 when you want to express a concept similar to one of the stock ones."""
271 def __init__(self
, stock
, message
):
272 """Specify the icon and text for the new button. The text
273 may specify the mnemonic for the widget by putting a _ before
275 button = ButtonMixed(gtk.STOCK_DELETE, '_Delete message')."""
276 gtk
.Button
.__init
__(self
)
278 label
= gtk
.Label('')
279 label
.set_text_with_mnemonic(message
)
280 label
.set_mnemonic_widget(self
)
282 image
= gtk
.image_new_from_stock(stock
, gtk
.ICON_SIZE_BUTTON
)
283 box
= gtk
.HBox(False, 2)
284 align
= gtk
.Alignment(0.5, 0.5, 0.0, 0.0)
286 box
.pack_start(image
, False, False, 0)
287 box
.pack_end(label
, False, False, 0)
293 def get_build_options(parent
, message
):
294 box
= gtk
.MessageDialog(parent
, 0, gtk
.MESSAGE_QUESTION
,
295 gtk
.BUTTONS_CANCEL
, message
)
297 button
= ButtonMixed(gtk
.STOCK_GO_FORWARD
, 'Force')
299 box
.add_action_widget(button
, 2)
301 button
= ButtonMixed(gtk
.STOCK_CLEAR
, 'Clean')
302 button
.set_flags(gtk
.CAN_DEFAULT
)
304 box
.add_action_widget(button
, 1)
306 box
.set_position(gtk
.WIN_POS_CENTER
)
307 box
.set_title(_('Confirm:'))
308 box
.set_default_response(1)
318 class PublishBox(gtk
.MessageDialog
):
319 def __init__(self
, parent
, buildenv
):
320 gtk
.MessageDialog
.__init
__(self
, parent
,
321 gtk
.DIALOG_MODAL | gtk
.DIALOG_DESTROY_WITH_PARENT
,
322 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_OK_CANCEL
,
323 'Enter the directory on your HTTP or FTP server to which '
324 'the archive file will be uploaded:')
325 vbox
= gtk
.VBox(True, 4)
326 self
.vbox
.pack_start(vbox
, False, True, 0)
327 vbox
.set_border_width(8)
329 self
.archive_dir
= gtk
.Entry()
330 self
.archive_dir
.set_activates_default(True)
331 self
.set_default_response(gtk
.RESPONSE_OK
)
333 if buildenv
.download_base_url
:
334 self
.archive_dir
.set_text(buildenv
.download_base_url
)
336 self
.archive_dir
.set_text('http://myserver.com/archives')
338 vbox
.pack_start(self
.archive_dir
, False, True, 0)
341 instructions
= """Instructions
343 Compiling a program takes the program's human readable source code, and generates a binary which a computer can run.
345 To choose a different version of the source code, or the versions of any other programs needed to compile, use Setup.
347 To compile the chosen source code into a binary, use Build.
349 To add the new binary to the list of available versions for the program, use Register.
351 To publish the binary on the web, so that other people can run it, use Publish.
353 For further information, including details of how to make changes to the source code before compiling, visit: http://0install.net/0compile.html