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
)
111 # Register using the feed-for, if available
113 for uri
in iface
.feed_for
or []:
114 real_iface
= iface_cache
.get_interface(uri
)
115 self
.add_msg("Registering as a feed for %s" % real_iface
.uri
)
118 if iface
.uri
.startswith('/'):
119 self
.add_msg("Warning: no <feed-for> in local feed %s!" % iface
.uri
)
121 feed
= buildenv
.local_iface_file
122 for f
in real_iface
.feeds
or []:
124 self
.add_msg("Feed '%s' is already registered for interface '%s'!\n" % (feed
, real_iface
.uri
))
126 box
.buffer.insert_at_cursor("Registering feed '%s'\n" % feed
)
127 real_iface
.extra_feeds
.append(model
.Feed(feed
, arch
= None, user_override
= True))
128 writer
.save_interface(real_iface
)
129 box
.buffer.insert_at_cursor("Done. You can now close this window.\n")
130 elif resp
== RESPONSE_PUBLISH
:
131 buildenv
= BuildEnv()
132 box
= PublishBox(self
, buildenv
)
135 if resp
== gtk
.RESPONSE_OK
:
137 self
.add_msg("\nYou can use '0publish --local' to add this "
138 "into the main feed. If you don't have a main feed then this "
139 "will create one. See "
140 "http://0install.net/injector-packagers.html for more information.")
141 self
.run_command((sys
.executable
, main_path
,
142 'publish', box
.archive_dir
.get_text()), done_publish
)
143 elif resp
== gtk
.RESPONSE_CANCEL
or resp
== gtk
.RESPONSE_DELETE_EVENT
:
144 if self
.kill_child(): return
147 self
.add_msg('Unknown response: %s' % resp
)
149 self
.connect('response', response
)
151 self
.system_tag
= self
.buffer.create_tag('system', foreground
= 'blue', background
= 'white')
152 self
.add_msg(instructions
)
153 self
.set_responses_sensitive()
155 def kill_child(self
):
156 if self
.child
is None: return False
160 self
.add_msg('\nSending SIGTERM to process...')
161 os
.kill(-self
.child
, signal
.SIGTERM
)
164 def add_msg(self
, msg
):
165 self
.insert_at_end_and_scroll(msg
+ '\n', self
.system_tag
)
167 """Run command in a sub-process.
168 Calls success() if the command exits with status zero.
169 Calls failure() if it fails for other reasons.
170 (neither is called if the user aborts the command)"""
171 def run_command(self
, command
, success
, failure
= None):
172 assert self
.child
is None
174 self
.success
= success
175 self
.failure
= failure
176 if isinstance(command
, basestring
):
177 self
.add_msg("Running: " + command
+ "\n")
179 self
.add_msg("Running: " + ' '.join(command
) + "\n")
184 self
.child
= os
.fork()
193 os
.setpgrp() # Become group leader
194 os
.execvp(command
[0], command
)
197 traceback
.print_exc()
206 for resp
in action_responses
:
207 self
.set_response_sensitive(resp
, False)
210 gobject
.io_add_watch(r
, gobject
.IO_IN | gobject
.IO_HUP
, self
.got_data
)
212 def set_responses_sensitive(self
):
213 self
.set_response_sensitive(RESPONSE_SETUP
, True)
214 self
.set_response_sensitive(RESPONSE_BUILD
, True)
216 buildenv
= BuildEnv()
217 have_binary
= os
.path
.exists(buildenv
.local_iface_file
)
218 self
.set_response_sensitive(RESPONSE_REGISTER
, have_binary
)
219 self
.set_response_sensitive(RESPONSE_PUBLISH
, have_binary
)
221 def insert_at_end_and_scroll(self
, data
, *tags
):
222 near_end
= self
.vscroll
.upper
- self
.vscroll
.page_size
* 1.5 < self
.vscroll
.value
223 end
= self
.buffer.get_end_iter()
224 self
.buffer.insert_with_tags(end
, data
, *tags
)
226 cursor
= self
.buffer.get_insert()
227 self
.buffer.move_mark(cursor
, end
)
228 self
.tv
.scroll_to_mark(cursor
, 0, False, 0, 0)
230 def got_data(self
, src
, cond
):
231 data
= os
.read(src
, 100)
233 # TODO: only insert complete UTF-8 sequences, not half sequences
234 self
.insert_at_end_and_scroll(data
)
237 pid
, status
= os
.waitpid(self
.child
, 0)
238 assert pid
== self
.child
241 self
.set_responses_sensitive()
243 if os
.WIFEXITED(status
) and os
.WEXITSTATUS(status
) == 0:
246 self
.add_msg("\nCommand terminated at user's request.")
248 self
.add_msg("\nCommand failed.")
254 def choose_dir(title
, default
):
255 sel
= gtk
.FileSelection(title
)
256 sel
.set_has_separator(False)
257 sel
.set_filename(default
)
260 if resp
== gtk
.RESPONSE_OK
:
261 build_dir
= sel
.get_filename()
262 if not os
.path
.exists(build_dir
):
265 alert(sel
, _("'%s' already exists") % build_dir
)
270 def alert(parent
, msg
):
271 d
= gtk
.MessageDialog(parent
,
279 class ButtonMixed(gtk
.Button
):
280 """A button with a standard stock icon, but any label. This is useful
281 when you want to express a concept similar to one of the stock ones."""
282 def __init__(self
, stock
, message
):
283 """Specify the icon and text for the new button. The text
284 may specify the mnemonic for the widget by putting a _ before
286 button = ButtonMixed(gtk.STOCK_DELETE, '_Delete message')."""
287 gtk
.Button
.__init
__(self
)
289 label
= gtk
.Label('')
290 label
.set_text_with_mnemonic(message
)
291 label
.set_mnemonic_widget(self
)
293 image
= gtk
.image_new_from_stock(stock
, gtk
.ICON_SIZE_BUTTON
)
294 box
= gtk
.HBox(False, 2)
295 align
= gtk
.Alignment(0.5, 0.5, 0.0, 0.0)
297 box
.pack_start(image
, False, False, 0)
298 box
.pack_end(label
, False, False, 0)
304 def get_build_options(parent
, message
):
305 box
= gtk
.MessageDialog(parent
, 0, gtk
.MESSAGE_QUESTION
,
306 gtk
.BUTTONS_CANCEL
, message
)
308 button
= ButtonMixed(gtk
.STOCK_GO_FORWARD
, 'Force')
310 box
.add_action_widget(button
, 2)
312 button
= ButtonMixed(gtk
.STOCK_CLEAR
, 'Clean')
313 button
.set_flags(gtk
.CAN_DEFAULT
)
315 box
.add_action_widget(button
, 1)
317 box
.set_position(gtk
.WIN_POS_CENTER
)
318 box
.set_title(_('Confirm:'))
319 box
.set_default_response(1)
329 class PublishBox(gtk
.MessageDialog
):
330 def __init__(self
, parent
, buildenv
):
331 gtk
.MessageDialog
.__init
__(self
, parent
,
332 gtk
.DIALOG_MODAL | gtk
.DIALOG_DESTROY_WITH_PARENT
,
333 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_OK_CANCEL
,
334 'Enter the directory on your HTTP or FTP server to which '
335 'the archive file will be uploaded:')
336 vbox
= gtk
.VBox(True, 4)
337 self
.vbox
.pack_start(vbox
, False, True, 0)
338 vbox
.set_border_width(8)
340 self
.archive_dir
= gtk
.Entry()
341 self
.archive_dir
.set_activates_default(True)
342 self
.set_default_response(gtk
.RESPONSE_OK
)
344 if buildenv
.download_base_url
:
345 self
.archive_dir
.set_text(buildenv
.download_base_url
)
347 self
.archive_dir
.set_text('http://myserver.com/archives')
349 vbox
.pack_start(self
.archive_dir
, False, True, 0)
352 instructions
= """Instructions
354 Compiling a program takes the program's human readable source code, and generates a binary which a computer can run.
356 To choose a different version of the source code, or the versions of any other programs needed to compile, use Setup.
358 To compile the chosen source code into a binary, use Build.
360 To add the new binary to the list of available versions for the program, use Register.
362 To publish the binary on the web, so that other people can run it, use Publish.
364 For further information, including details of how to make changes to the source code before compiling, visit: http://0install.net/0compile.html