1 # Copyright (C) 2006, Thomas Leonard
2 # See http://0install.net/0compile.html
4 import sys
, os
, __main__
5 import pygtk
; pygtk
.require('2.0')
8 from zeroinstall
.injector
import reader
, writer
, model
9 from zeroinstall
.injector
.iface_cache
import iface_cache
11 from support
import BuildEnv
, _
18 action_responses
= [RESPONSE_SETUP
, RESPONSE_BUILD
, RESPONSE_PUBLISH
, RESPONSE_REGISTER
]
20 main_path
= os
.path
.abspath(__main__
.__file
__)
22 class CompileBox(gtk
.Dialog
):
25 def __init__(self
, interface
):
27 gtk
.Dialog
.__init
__(self
, _("Compile '%s'") % interface
.split('/')[-1]) # No rsplit on Python 2.3
28 self
.set_has_separator(False)
29 self
.set_default_size(gtk
.gdk
.screen_width() / 2, gtk
.gdk
.screen_height() / 2)
31 def add_action(stock
, name
, resp
):
32 if not hasattr(gtk
, stock
):
34 button
= ButtonMixed(getattr(gtk
, stock
), name
)
35 button
.set_flags(gtk
.CAN_DEFAULT
)
36 self
.add_action_widget(button
, resp
)
39 add_action('STOCK_PROPERTIES', '_Setup', RESPONSE_SETUP
)
40 add_action('STOCK_CONVERT', '_Build', RESPONSE_BUILD
)
41 add_action('STOCK_ADD', '_Register', RESPONSE_REGISTER
)
42 add_action('STOCK_NETWORK', '_Publish', RESPONSE_PUBLISH
)
44 self
.add_button(gtk
.STOCK_CLOSE
, gtk
.RESPONSE_CANCEL
)
46 self
.set_default_response(RESPONSE_BUILD
)
48 self
.buffer = gtk
.TextBuffer()
49 self
.tv
= gtk
.TextView(self
.buffer)
50 self
.tv
.set_left_margin(4)
51 self
.tv
.set_right_margin(4)
52 self
.tv
.set_wrap_mode(gtk
.WRAP_WORD
)
53 swin
= gtk
.ScrolledWindow()
54 swin
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_ALWAYS
)
56 swin
.set_shadow_type(gtk
.SHADOW_IN
)
57 self
.vscroll
= swin
.get_vadjustment()
58 self
.tv
.set_editable(False)
59 self
.tv
.set_cursor_visible(False)
60 self
.vbox
.pack_start(swin
, True, True, 0)
64 self
.connect('delete-event', lambda box
, dev
: True)
66 def response(box
, resp
):
67 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 os
.path
.isabs(iface
.uri
):
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 self
.add_msg("Running: " + ' '.join(command
) + "\n")
181 self
.child
= os
.fork()
190 os
.setpgrp() # Become group leader
191 os
.execvp(command
[0], command
)
194 traceback
.print_exc()
203 for resp
in action_responses
:
204 self
.set_response_sensitive(resp
, False)
207 gobject
.io_add_watch(r
, gobject
.IO_IN | gobject
.IO_HUP
, self
.got_data
)
209 def set_responses_sensitive(self
):
210 self
.set_response_sensitive(RESPONSE_SETUP
, True)
211 self
.set_response_sensitive(RESPONSE_BUILD
, True)
213 buildenv
= BuildEnv()
214 have_binary
= os
.path
.exists(buildenv
.local_iface_file
)
215 self
.set_response_sensitive(RESPONSE_REGISTER
, have_binary
)
216 self
.set_response_sensitive(RESPONSE_PUBLISH
, have_binary
)
218 def insert_at_end_and_scroll(self
, data
, *tags
):
219 near_end
= self
.vscroll
.upper
- self
.vscroll
.page_size
* 1.5 < self
.vscroll
.value
220 end
= self
.buffer.get_end_iter()
221 self
.buffer.insert_with_tags(end
, data
, *tags
)
223 cursor
= self
.buffer.get_insert()
224 self
.buffer.move_mark(cursor
, end
)
225 self
.tv
.scroll_to_mark(cursor
, 0, False, 0, 0)
227 def got_data(self
, src
, cond
):
228 data
= os
.read(src
, 100)
230 # TODO: only insert complete UTF-8 sequences, not half sequences
231 self
.insert_at_end_and_scroll(data
)
234 pid
, status
= os
.waitpid(self
.child
, 0)
235 assert pid
== self
.child
238 self
.set_responses_sensitive()
240 if os
.WIFEXITED(status
) and os
.WEXITSTATUS(status
) == 0:
243 self
.add_msg("\nCommand terminated at user's request.")
245 self
.add_msg("\nCommand failed.")
251 def choose_dir(title
, default
):
252 sel
= gtk
.FileSelection(title
)
253 sel
.set_has_separator(False)
254 sel
.set_filename(default
)
257 if resp
== gtk
.RESPONSE_OK
:
258 build_dir
= sel
.get_filename()
259 if not os
.path
.exists(build_dir
):
262 alert(sel
, _("'%s' already exists") % build_dir
)
267 def alert(parent
, msg
):
268 d
= gtk
.MessageDialog(parent
,
276 class ButtonMixed(gtk
.Button
):
277 """A button with a standard stock icon, but any label. This is useful
278 when you want to express a concept similar to one of the stock ones."""
279 def __init__(self
, stock
, message
):
280 """Specify the icon and text for the new button. The text
281 may specify the mnemonic for the widget by putting a _ before
283 button = ButtonMixed(gtk.STOCK_DELETE, '_Delete message')."""
284 gtk
.Button
.__init
__(self
)
286 label
= gtk
.Label('')
287 label
.set_text_with_mnemonic(message
)
288 label
.set_mnemonic_widget(self
)
290 image
= gtk
.image_new_from_stock(stock
, gtk
.ICON_SIZE_BUTTON
)
291 box
= gtk
.HBox(False, 2)
292 align
= gtk
.Alignment(0.5, 0.5, 0.0, 0.0)
294 box
.pack_start(image
, False, False, 0)
295 box
.pack_end(label
, False, False, 0)
301 def get_build_options(parent
, message
):
302 box
= gtk
.MessageDialog(parent
, 0, gtk
.MESSAGE_QUESTION
,
303 gtk
.BUTTONS_CANCEL
, message
)
305 button
= ButtonMixed(gtk
.STOCK_GO_FORWARD
, 'Force')
307 box
.add_action_widget(button
, 2)
309 button
= ButtonMixed(gtk
.STOCK_CLEAR
, 'Clean')
310 button
.set_flags(gtk
.CAN_DEFAULT
)
312 box
.add_action_widget(button
, 1)
314 box
.set_position(gtk
.WIN_POS_CENTER
)
315 box
.set_title(_('Confirm:'))
316 box
.set_default_response(1)
326 class PublishBox(gtk
.MessageDialog
):
327 def __init__(self
, parent
, buildenv
):
328 gtk
.MessageDialog
.__init
__(self
, parent
,
329 gtk
.DIALOG_MODAL | gtk
.DIALOG_DESTROY_WITH_PARENT
,
330 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_OK_CANCEL
,
331 'Enter the directory on your HTTP or FTP server to which '
332 'the archive file will be uploaded:')
333 vbox
= gtk
.VBox(True, 4)
334 self
.vbox
.pack_start(vbox
, False, True, 0)
335 vbox
.set_border_width(8)
337 self
.archive_dir
= gtk
.Entry()
338 self
.archive_dir
.set_activates_default(True)
339 self
.set_default_response(gtk
.RESPONSE_OK
)
341 if buildenv
.download_base_url
:
342 self
.archive_dir
.set_text(buildenv
.download_base_url
)
344 self
.archive_dir
.set_text('http://myserver.com/archives')
346 vbox
.pack_start(self
.archive_dir
, False, True, 0)
349 instructions
= """Instructions
351 Compiling a program takes the program's human readable source code, and generates a binary which a computer can run.
353 To choose a different version of the source code, or the versions of any other programs needed to compile, use Setup.
355 To compile the chosen source code into a binary, use Build.
357 To add the new binary to the list of available versions for the program, use Register.
359 To publish the binary on the web, so that other people can run it, use Publish.
361 For further information, including details of how to make changes to the source code before compiling, visit: http://0install.net/0compile.html