webruby 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/webruby/environment.rb +4 -0
  3. data/lib/webruby/rake/files.rake +2 -1
  4. data/lib/webruby/rake/mruby.rake +4 -2
  5. data/modules/emscripten/AUTHORS +1 -0
  6. data/modules/emscripten/cmake/Platform/Emscripten.cmake +2 -0
  7. data/modules/emscripten/emcc +96 -40
  8. data/modules/emscripten/emrun +301 -136
  9. data/modules/emscripten/emscripten.py +5 -45
  10. data/modules/emscripten/src/analyzer.js +11 -1
  11. data/modules/emscripten/src/compiler.js +1 -1
  12. data/modules/emscripten/src/emrun_postjs.js +2 -2
  13. data/modules/emscripten/src/emrun_prejs.js +5 -0
  14. data/modules/emscripten/src/emscripten-source-map.min.js +31 -0
  15. data/modules/emscripten/src/library.js +187 -0
  16. data/modules/emscripten/src/library_egl.js +20 -0
  17. data/modules/emscripten/src/library_sdl.js +1 -0
  18. data/modules/emscripten/src/preamble.js +4 -0
  19. data/modules/emscripten/src/relooper/Relooper.cpp +33 -15
  20. data/modules/emscripten/src/relooper/Relooper.h +20 -14
  21. data/modules/emscripten/src/relooper/fuzzer.py +6 -0
  22. data/modules/emscripten/src/relooper/test.cpp +28 -0
  23. data/modules/emscripten/src/relooper/test.txt +211 -166
  24. data/modules/emscripten/src/relooper/test2.txt +20 -20
  25. data/modules/emscripten/src/relooper/test3.txt +41 -41
  26. data/modules/emscripten/src/relooper/test4.txt +26 -26
  27. data/modules/emscripten/src/relooper/test5.txt +52 -52
  28. data/modules/emscripten/src/relooper/test6.txt +19 -19
  29. data/modules/emscripten/src/relooper/test_dead.txt +1 -1
  30. data/modules/emscripten/src/relooper/test_debug.txt +31 -31
  31. data/modules/emscripten/src/relooper/test_fuzz1.txt +50 -50
  32. data/modules/emscripten/src/relooper/test_fuzz2.txt +21 -21
  33. data/modules/emscripten/src/relooper/test_fuzz3.txt +18 -18
  34. data/modules/emscripten/src/relooper/test_fuzz4.txt +28 -28
  35. data/modules/emscripten/src/relooper/test_fuzz5.txt +61 -61
  36. data/modules/emscripten/src/relooper/test_fuzz6.txt +179 -179
  37. data/modules/emscripten/src/relooper/test_inf.txt +846 -846
  38. data/modules/emscripten/src/relooper/testit.sh +15 -15
  39. data/modules/emscripten/system/include/emscripten/emscripten.h +64 -0
  40. data/modules/emscripten/tools/eliminator/asm-eliminator-test-output.js +8 -2
  41. data/modules/emscripten/tools/eliminator/asm-eliminator-test.js +9 -1
  42. data/modules/emscripten/tools/eliminator/eliminator-test-output.js +11 -0
  43. data/modules/emscripten/tools/eliminator/eliminator-test.js +16 -1
  44. data/modules/emscripten/tools/file_packager.py +59 -49
  45. data/modules/emscripten/tools/js-optimizer.js +47 -8
  46. data/modules/emscripten/tools/shared.py +3 -3
  47. data/modules/emscripten/tools/test-js-optimizer-asm-pre-output.js +5 -3
  48. data/modules/emscripten/tools/test-js-optimizer-asm-pre.js +4 -0
  49. data/modules/mruby/INSTALL +11 -6
  50. data/modules/mruby/include/mrbconf.h +0 -3
  51. data/modules/mruby/include/mruby/khash.h +34 -36
  52. data/modules/mruby/include/mruby/string.h +3 -0
  53. data/modules/mruby/include/mruby.h +3 -3
  54. data/modules/mruby/mrblib/string.rb +3 -0
  55. data/modules/mruby/src/class.c +12 -12
  56. data/modules/mruby/src/codegen.c +18 -11
  57. data/modules/mruby/src/hash.c +12 -12
  58. data/modules/mruby/src/kernel.c +3 -3
  59. data/modules/mruby/src/load.c +29 -14
  60. data/modules/mruby/src/numeric.c +1 -1
  61. data/modules/mruby/src/object.c +14 -2
  62. data/modules/mruby/src/state.c +13 -10
  63. data/modules/mruby/src/string.c +1 -3
  64. data/modules/mruby/src/symbol.c +44 -18
  65. data/modules/mruby/src/variable.c +6 -6
  66. data/modules/mruby/test/t/class.rb +34 -0
  67. data/modules/mruby/test/t/module.rb +1 -1
  68. data/modules/mruby/test/t/syntax.rb +28 -0
  69. metadata +5 -13
  70. data/modules/emscripten/src/relooper.js +0 -11516
  71. data/modules/emscripten/src/relooper.js.raw.js +0 -11511
  72. data/modules/emscripten/tools/__init__.pyc +0 -0
  73. data/modules/emscripten/tools/cache.pyc +0 -0
  74. data/modules/emscripten/tools/gen_struct_info.pyc +0 -0
  75. data/modules/emscripten/tools/js_optimizer.pyc +0 -0
  76. data/modules/emscripten/tools/jsrun.pyc +0 -0
  77. data/modules/emscripten/tools/response_file.pyc +0 -0
  78. data/modules/emscripten/tools/shared.pyc +0 -0
  79. data/modules/emscripten/tools/tempfiles.pyc +0 -0
@@ -8,6 +8,7 @@ import os, platform, optparse, logging, re, pprint, atexit, urlparse, subprocess
8
8
  from operator import itemgetter
9
9
  from urllib import unquote
10
10
  from Queue import PriorityQueue
11
+ from threading import Thread, RLock
11
12
 
12
13
  # Populated from cmdline params
13
14
  emrun_options = None
@@ -41,6 +42,9 @@ processname_killed_atexit = ""
41
42
  # If user does not specify a --port parameter, this port is used to launch the server.
42
43
  default_webserver_port = 6931
43
44
 
45
+ # Location of Android Debug Bridge executable
46
+ ADB = ''
47
+
44
48
  # Host OS detection to autolocate browsers and other OS-specific support needs.
45
49
  WINDOWS = False
46
50
  LINUX = False
@@ -73,7 +77,7 @@ last_message_time = time.clock()
73
77
  page_start_time = time.clock()
74
78
 
75
79
  # Stores the time of most recent http page serve.
76
- page_last_served_time = time.clock()
80
+ page_last_served_time = None
77
81
 
78
82
  # Returns given log message formatted to be outputted on a HTML page.
79
83
  def format_html(msg):
@@ -82,21 +86,14 @@ def format_html(msg):
82
86
  msg = cgi.escape(msg)
83
87
  msg = msg.replace('\r\n', '<br />').replace('\n', '<br />')
84
88
  return msg
85
-
89
+
90
+ # HTTP requests are handled from separate threads - synchronize them to avoid race conditions
91
+ http_mutex = RLock()
92
+
86
93
  # Prints a log message to 'info' stdout channel. Always printed.
87
94
  def logi(msg):
88
95
  global last_message_time
89
- if emrun_options.log_html:
90
- sys.stdout.write(format_html(msg))
91
- else:
92
- print >> sys.stdout, msg
93
- sys.stdout.flush()
94
- last_message_time = time.clock()
95
-
96
- # Prints a verbose log message to stdout channel. Only shown if run with --verbose.
97
- def logv(msg):
98
- global emrun_options, last_message_time
99
- if emrun_options.verbose:
96
+ with http_mutex:
100
97
  if emrun_options.log_html:
101
98
  sys.stdout.write(format_html(msg))
102
99
  else:
@@ -104,15 +101,28 @@ def logv(msg):
104
101
  sys.stdout.flush()
105
102
  last_message_time = time.clock()
106
103
 
104
+ # Prints a verbose log message to stdout channel. Only shown if run with --verbose.
105
+ def logv(msg):
106
+ global emrun_options, last_message_time
107
+ with http_mutex:
108
+ if emrun_options.verbose:
109
+ if emrun_options.log_html:
110
+ sys.stdout.write(format_html(msg))
111
+ else:
112
+ print >> sys.stdout, msg
113
+ sys.stdout.flush()
114
+ last_message_time = time.clock()
115
+
107
116
  # Prints an error message to stderr channel.
108
117
  def loge(msg):
109
118
  global last_message_time
110
- if emrun_options.log_html:
111
- sys.stderr.write(format_html(msg))
112
- else:
113
- print >> sys.stderr, msg
114
- sys.stderr.flush()
115
- last_message_time = time.clock()
119
+ with http_mutex:
120
+ if emrun_options.log_html:
121
+ sys.stderr.write(format_html(msg))
122
+ else:
123
+ print >> sys.stderr, msg
124
+ sys.stderr.flush()
125
+ last_message_time = time.clock()
116
126
 
117
127
  def format_eol(msg):
118
128
  if WINDOWS:
@@ -122,8 +132,6 @@ def format_eol(msg):
122
132
  # Prints a message to the browser stdout output stream.
123
133
  def browser_logi(msg):
124
134
  global browser_stdout_handle
125
- if browser_stdout_handle != sys.stdout and not msg.endswith('\n'):
126
- msg += '\n'
127
135
  msg = format_eol(msg)
128
136
  print >> browser_stdout_handle, msg
129
137
  browser_stdout_handle.flush()
@@ -132,8 +140,6 @@ def browser_logi(msg):
132
140
  # Prints a message to the browser stderr output stream.
133
141
  def browser_loge(msg):
134
142
  global browser_stderr_handle
135
- if browser_stderr_handle != sys.stderr and not msg.endswith('\n'):
136
- msg += '\n'
137
143
  msg = format_eol(msg)
138
144
  print >> browser_stderr_handle, msg
139
145
  browser_stderr_handle.flush()
@@ -153,7 +159,7 @@ def is_browser_process_alive():
153
159
 
154
160
  # Kills browser_process and processname_killed_atexit.
155
161
  def kill_browser_process():
156
- global browser_process, processname_killed_atexit
162
+ global browser_process, processname_killed_atexit, emrun_options, ADB
157
163
  if browser_process:
158
164
  try:
159
165
  logv('Terminating browser process..')
@@ -161,21 +167,25 @@ def kill_browser_process():
161
167
  except:
162
168
  pass
163
169
  browser_process = None
164
-
165
170
  if len(processname_killed_atexit) > 0:
166
- logv("Terminating all processes that have string '" + processname_killed_atexit + "' in their name.")
167
- if WINDOWS:
168
- process_image = processname_killed_atexit if '.exe' in processname_killed_atexit else (processname_killed_atexit + '.exe')
169
- process = subprocess.Popen(['taskkill', '/F', '/IM', process_image, '/T'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
170
- process.communicate()
171
+ if emrun_options.android:
172
+ logv("Terminating Android app '" + processname_killed_atexit + "'.")
173
+ subprocess.call([ADB, 'shell', 'am', 'force-stop', processname_killed_atexit])
171
174
  else:
172
- try:
173
- subprocess.call(['pkill', processname_killed_atexit])
174
- except OSError, e:
175
+ logv("Terminating all processes that have string '" + processname_killed_atexit + "' in their name.")
176
+ if WINDOWS:
177
+ process_image = processname_killed_atexit if '.exe' in processname_killed_atexit else (processname_killed_atexit + '.exe')
178
+ process = subprocess.Popen(['taskkill', '/F', '/IM', process_image, '/T'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
179
+ process.communicate()
180
+ else:
175
181
  try:
176
- subprocess.call(['killall', processname_killed_atexit])
182
+ subprocess.call(['pkill', processname_killed_atexit])
177
183
  except OSError, e:
178
- loge('Both commands pkill and killall failed to clean up the spawned browser process. Perhaps neither of these utilities is available on your system?')
184
+ try:
185
+ subprocess.call(['killall', processname_killed_atexit])
186
+ except OSError, e:
187
+ loge('Both commands pkill and killall failed to clean up the spawned browser process. Perhaps neither of these utilities is available on your system?')
188
+ # Clear the process name to represent that the browser is now dead.
179
189
  processname_killed_atexit = ''
180
190
 
181
191
  # Our custom HTTP web server that will server the target page to run via .html.
@@ -190,56 +200,60 @@ class HTTPWebServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
190
200
 
191
201
  def handle_incoming_message(self, seq_num, log, data):
192
202
  global have_received_messages
193
- have_received_messages = True
194
-
195
- if self.expected_http_seq_num == -1:
196
- self.expected_http_seq_num = seq_num+1
197
- log(data)
198
- elif seq_num == -1: # Message arrived without a sequence number? Just log immediately
199
- log(data)
200
- elif seq_num == self.expected_http_seq_num:
201
- log(data)
202
- self.expected_http_seq_num += 1
203
- self.print_messages_due()
204
- elif seq_num < self.expected_http_seq_num:
205
- log(data)
206
- else:
207
- log(data)
208
- self.http_message_queue += [(seq_num, data, log)]
209
- self.http_message_queue.sort(key=itemgetter(0))
210
- if len(self.http_message_queue) > 16:
211
- self.print_next_message()
203
+ with http_mutex:
204
+ have_received_messages = True
205
+
206
+ if self.expected_http_seq_num == -1:
207
+ self.expected_http_seq_num = seq_num+1
208
+ log(data)
209
+ elif seq_num == -1: # Message arrived without a sequence number? Just log immediately
210
+ log(data)
211
+ elif seq_num == self.expected_http_seq_num:
212
+ log(data)
213
+ self.expected_http_seq_num += 1
214
+ self.print_messages_due()
215
+ elif seq_num < self.expected_http_seq_num:
216
+ log(data)
217
+ else:
218
+ self.http_message_queue += [(seq_num, data, log)]
219
+ self.http_message_queue.sort(key=itemgetter(0))
220
+ if len(self.http_message_queue) > 16:
221
+ self.print_next_message()
212
222
 
213
223
  # If it's been too long since we we got a message, prints out the oldest queued message, ignoring the proper order.
214
224
  # This ensures that if any messages are actually lost, that the message queue will be orderly flushed.
215
225
  def print_timed_out_messages(self):
216
226
  global last_message_time
217
- now = time.clock()
218
- max_message_queue_time = 5
219
- if len(self.http_message_queue) > 0 and now - last_message_time > max_message_queue_time:
220
- self.print_next_message()
227
+ with http_mutex:
228
+ now = time.clock()
229
+ max_message_queue_time = 5
230
+ if len(self.http_message_queue) > 0 and now - last_message_time > max_message_queue_time:
231
+ self.print_next_message()
221
232
 
222
233
  # Skips to printing the next message in queue now, independent of whether there was missed messages in the sequence numbering.
223
234
  def print_next_message(self):
224
- if len(self.http_message_queue) > 0:
225
- self.expected_http_seq_num = self.http_message_queue[0][0]
226
- self.print_messages_due()
235
+ with http_mutex:
236
+ if len(self.http_message_queue) > 0:
237
+ self.expected_http_seq_num = self.http_message_queue[0][0]
238
+ self.print_messages_due()
227
239
 
228
240
  # Completely flushes all out-of-order messages in the queue.
229
241
  def print_all_messages(self):
230
- while len(self.http_message_queue) > 0:
231
- self.print_next_message()
242
+ with http_mutex:
243
+ while len(self.http_message_queue) > 0:
244
+ self.print_next_message()
232
245
 
233
246
  # Prints any messages that are now due after we logged some other previous messages.
234
247
  def print_messages_due(self):
235
- while len(self.http_message_queue) > 0:
236
- msg = self.http_message_queue[0]
237
- if msg[0] == self.expected_http_seq_num:
238
- msg[2](msg[1])
239
- self.expected_http_seq_num += 1
240
- self.http_message_queue.pop(0)
241
- else:
242
- return
248
+ with http_mutex:
249
+ while len(self.http_message_queue) > 0:
250
+ msg = self.http_message_queue[0]
251
+ if msg[0] == self.expected_http_seq_num:
252
+ msg[2](msg[1])
253
+ self.expected_http_seq_num += 1
254
+ self.http_message_queue.pop(0)
255
+ else:
256
+ return
243
257
 
244
258
  def serve_forever(self, timeout=0.5):
245
259
  global emrun_options, last_message_time, page_exit_code, have_received_messages, emrun_not_enabled_nag_printed
@@ -278,10 +292,11 @@ class HTTPWebServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
278
292
  page_exit_code = emrun_options.timeout_returncode
279
293
 
280
294
  # If we detect that the page is not running with emrun enabled, print a warning message.
281
- time_since_page_serve = now - page_last_served_time
282
- if not emrun_not_enabled_nag_printed and not have_received_messages and time_since_page_serve > 5:
283
- logi('The html page you are running is not emrun-capable. Stdout, stderr and exit(returncode) capture will not work. Recompile the application with the --emrun linker flag to enable this, or pass --no_emrun_detect to emrun to hide this check.')
284
- emrun_not_enabled_nag_printed = True
295
+ if not emrun_not_enabled_nag_printed and page_last_served_time is not None:
296
+ time_since_page_serve = now - page_last_served_time
297
+ if not have_received_messages and time_since_page_serve > 10:
298
+ logi('The html page you are running is not emrun-capable. Stdout, stderr and exit(returncode) capture will not work. Recompile the application with the --emrun linker flag to enable this, or pass --no_emrun_detect to emrun to hide this check.')
299
+ emrun_not_enabled_nag_printed = True
285
300
 
286
301
  # Clean up at quit, print any leftover messages in queue.
287
302
  self.print_all_messages()
@@ -411,6 +426,19 @@ def get_cpu_infoline():
411
426
 
412
427
  return platform.machine() + ', ' + cpu_name
413
428
 
429
+ def get_android_cpu_infoline():
430
+ lines = subprocess.check_output([ADB, 'shell', 'cat', '/proc/cpuinfo']).split('\n')
431
+ processor = ''
432
+ hardware = ''
433
+ for line in lines:
434
+ if line.startswith('Processor'):
435
+ processor = line[line.find(':')+1:].strip()
436
+ elif line.startswith('Hardware'):
437
+ hardware = line[line.find(':')+1:].strip()
438
+
439
+ freq = int(subprocess.check_output([ADB, 'shell', 'cat', '/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq']).strip())/1000
440
+ return 'CPU: ' + processor + ', ' + hardware + ' @ ' + str(freq) + ' MHz'
441
+
414
442
  def win_print_gpu_info():
415
443
  gpus = []
416
444
  gpu_memory = []
@@ -575,19 +603,24 @@ def get_os_version():
575
603
  return 'Unknown OS'
576
604
 
577
605
  def get_system_memory():
606
+ global emrun_options
607
+
578
608
  try:
579
- if WINDOWS:
580
- return win32api.GlobalMemoryStatusEx()['TotalPhys']
581
- elif OSX:
582
- return int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']).strip())
583
- elif LINUX:
584
- mem = open('/proc/meminfo', 'r')
585
- lines = mem.readlines()
586
- mem.close()
609
+ if LINUX or emrun_options.android:
610
+ if emrun_options.android:
611
+ lines = subprocess.check_output([ADB, 'shell', 'cat', '/proc/meminfo']).split('\n')
612
+ else:
613
+ mem = open('/proc/meminfo', 'r')
614
+ lines = mem.readlines()
615
+ mem.close()
587
616
  for i in lines:
588
617
  sline = i.split()
589
618
  if str(sline[0]) == 'MemTotal:':
590
619
  return int(sline[1]) * 1024
620
+ elif WINDOWS:
621
+ return win32api.GlobalMemoryStatusEx()['TotalPhys']
622
+ elif OSX:
623
+ return int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']).strip())
591
624
  except:
592
625
  return -1
593
626
 
@@ -698,6 +731,75 @@ def find_browser(name):
698
731
 
699
732
  return None # Could not find the browser
700
733
 
734
+ def get_android_model():
735
+ global ADB
736
+ manufacturer = subprocess.check_output([ADB, 'shell', 'getprop', 'ro.product.manufacturer']).strip()
737
+ brand = subprocess.check_output([ADB, 'shell', 'getprop', 'ro.product.brand']).strip()
738
+ model = subprocess.check_output([ADB, 'shell', 'getprop', 'ro.product.model']).strip()
739
+ board = subprocess.check_output([ADB, 'shell', 'getprop', 'ro.product.board']).strip()
740
+ device = subprocess.check_output([ADB, 'shell', 'getprop', 'ro.product.device']).strip()
741
+ name = subprocess.check_output([ADB, 'shell', 'getprop', 'ro.product.name']).strip()
742
+ return manufacturer + ' ' + brand + ' ' + model + ' ' + board + ' ' + device + ' ' + name
743
+
744
+ def get_android_os_version():
745
+ global ADB
746
+ ver = subprocess.check_output([ADB, 'shell', 'getprop', 'ro.build.version.release']).strip()
747
+ apiLevel = subprocess.check_output([ADB, 'shell', 'getprop', 'ro.build.version.sdk']).strip()
748
+ if not apiLevel:
749
+ apiLevel = subprocess.check_output([ADB, 'shell', 'getprop', 'ro.build.version.sdk_int']).strip()
750
+
751
+ os = ''
752
+ if ver:
753
+ os += 'Android ' + ver + ' '
754
+ if apiLevel:
755
+ os += 'SDK API Level ' + apiLevel + ' '
756
+ os += subprocess.check_output([ADB, 'shell', 'getprop', 'ro.build.description']).strip()
757
+ return os
758
+
759
+ def list_android_browsers():
760
+ global ADB
761
+ apps = subprocess.check_output([ADB, 'shell', 'pm', 'list', 'packages', '-f']).replace('\r\n', '\n')
762
+ browsers = []
763
+ for line in apps.split('\n'):
764
+ line = line.strip()
765
+ if line.endswith('=org.mozilla.firefox'):
766
+ browsers += ['firefox']
767
+ if line.endswith('=org.mozilla.firefox_beta'):
768
+ browsers += ['firefox_beta']
769
+ if line.endswith('=org.mozilla.fennec_aurora'):
770
+ browsers += ['firefox_aurora']
771
+ if line.endswith('=org.mozilla.fennec'):
772
+ browsers += ['firefox_nightly']
773
+ if line.endswith('=com.android.chrome'):
774
+ browsers += ['chrome']
775
+ if line.endswith('=com.chrome.beta'):
776
+ browsers += ['chrome_beta']
777
+ if line.endswith('=com.opera.browser'):
778
+ browsers += ['opera']
779
+ if line.endswith('=com.opera.mini.android'):
780
+ browsers += ['opera_mini']
781
+ if line.endswith('=mobi.mgeek.TunnyBrowser'):
782
+ browsers += ['dolphin']
783
+
784
+ browsers.sort()
785
+ logi('emrun has automatically found the following browsers on the connected Android device:')
786
+ for browser in browsers:
787
+ logi(' - ' + browser)
788
+
789
+ def list_pc_browsers():
790
+ browsers = ['firefox', 'firefox_beta', 'firefox_aurora', 'firefox_nightly', 'chrome', 'chrome_canary', 'iexplore', 'safari', 'opera']
791
+ logi('emrun has automatically found the following browsers in the default install locations on the system:')
792
+ logi('')
793
+ for browser in browsers:
794
+ browser_exe = find_browser(browser)
795
+ if type(browser_exe) == list:
796
+ browser_exe = browser_exe[0]
797
+ if browser_exe:
798
+ logi(' - ' + browser + ': ' + browser_display_name(browser_exe) + ' ' + get_executable_version(browser_exe))
799
+ logi('')
800
+ logi('You can pass the --browser <id> option to launch with the given browser above.')
801
+ logi('Even if your browser was not detected, you can use --browser /path/to/browser/executable to launch with that browser.')
802
+
701
803
  def browser_display_name(browser):
702
804
  b = browser.lower()
703
805
  if 'iexplore' in b:
@@ -718,7 +820,7 @@ def browser_display_name(browser):
718
820
  return browser
719
821
 
720
822
  def main():
721
- global browser_process, processname_killed_atexit, emrun_options, emrun_not_enabled_nag_printed
823
+ global browser_process, processname_killed_atexit, emrun_options, emrun_not_enabled_nag_printed, ADB
722
824
  usage_str = "usage: %prog [options] [optional_portnum]"
723
825
  parser = optparse.OptionParser(usage=usage_str)
724
826
 
@@ -773,6 +875,9 @@ def main():
773
875
  parser.add_option('--browser', dest='browser', default='',
774
876
  help='Specifies the browser executable to run the web page in.')
775
877
 
878
+ parser.add_option('--android', dest='android', action='store_true', default=False,
879
+ help='Launches the page in a browser of an Android device connected to an USB on the local system. (via adb)')
880
+
776
881
  parser.add_option('--system_info', dest='system_info', action='store_true',
777
882
  help='Prints information about the current system at startup.')
778
883
 
@@ -785,7 +890,13 @@ def main():
785
890
  (options, args) = parser.parse_args(sys.argv)
786
891
  emrun_options = options
787
892
 
788
- if not options.browser:
893
+ if options.android:
894
+ ADB = which('adb')
895
+ if not ADB:
896
+ loge("Could not find the adb tool. Install Android SDK and add the directory of adb to PATH.")
897
+ return 1
898
+
899
+ if not options.browser and not options.android:
789
900
  if WINDOWS:
790
901
  options.browser = 'start'
791
902
  elif LINUX:
@@ -795,19 +906,11 @@ def main():
795
906
  elif OSX:
796
907
  options.browser = 'safari'
797
908
 
798
- browsers = ['firefox', 'firefox_beta', 'firefox_aurora', 'firefox_nightly', 'chrome', 'chrome_canary', 'iexplore', 'safari', 'opera']
799
909
  if options.list_browsers:
800
- logi('emrun has automatically found the following browsers in the default install locations on the system:')
801
- logi('')
802
- for browser in browsers:
803
- browser_exe = find_browser(browser)
804
- if type(browser_exe) == list:
805
- browser_exe = browser_exe[0]
806
- if browser_exe:
807
- logi(' - ' + browser + ': ' + browser_display_name(browser_exe) + ' ' + get_executable_version(browser_exe))
808
- logi('')
809
- logi('You can pass the --browser <id> option to launch with the given browser above.')
810
- logi('Even if your browser was not detected, you can use --browser /path/to/browser/executable to launch with that browser.')
910
+ if options.android:
911
+ list_android_browsers()
912
+ else:
913
+ list_pc_browsers()
811
914
  return
812
915
 
813
916
  if len(args) < 2 and (options.system_info or options.browser_info):
@@ -827,58 +930,109 @@ def main():
827
930
  url = os.path.relpath(os.path.abspath(file_to_serve), serve_dir)
828
931
  if len(cmdlineparams) > 0:
829
932
  url += '?' + '&'.join(cmdlineparams)
830
- url = 'http://localhost:'+str(options.port)+'/'+url
933
+ server_root = 'localhost'
934
+ if options.android:
935
+ server_root = socket.gethostbyname(socket.gethostname())
936
+ url = 'http://' + server_root + ':' + str(options.port)+'/'+url
831
937
 
832
938
  os.chdir(serve_dir)
833
939
  logv('Web server root directory: ' + os.path.abspath('.'))
834
940
 
835
- browser = find_browser(str(options.browser))
836
- browser_exe = browser[0]
837
- browser_args = []
838
-
839
- if 'safari' in browser_exe.lower():
840
- # Safari has a bug that a command line 'Safari http://page.com' does not launch that page,
841
- # but instead launches 'file:///http://page.com'. To remedy this, must use the open -a command
842
- # to run Safari, but unfortunately this will end up spawning Safari process detached from emrun.
843
- if OSX:
844
- browser = ['open', '-a', 'Safari'] + (browser[1:] if len(browser) > 1 else [])
845
-
846
- processname_killed_atexit = 'Safari'
847
- elif 'chrome' in browser_exe.lower():
848
- processname_killed_atexit = 'chrome'
849
- browser_args = ['--incognito', '--enable-nacl', '--enable-pnacl', '--disable-restore-session-state', '--enable-webgl', '--no-default-browser-check', '--no-first-run', '--allow-file-access-from-files']
850
- # if options.no_server:
851
- # browser_args += ['--disable-web-security']
852
- elif 'firefox' in browser_exe.lower():
853
- processname_killed_atexit = 'firefox'
854
- elif 'iexplore' in browser_exe.lower():
855
- processname_killed_atexit = 'iexplore'
856
- browser_args = ['-private']
857
- elif 'opera' in browser_exe.lower():
858
- processname_killed_atexit = 'opera'
859
-
860
- # In Windows cmdline, & character delimits multiple commmands, so must use ^ to escape them.
861
- if browser_exe == 'cmd':
862
- url = url.replace('&', '^&')
863
- browser += browser_args + [url]
941
+ if options.android:
942
+ if not options.no_browser:
943
+ if not options.browser:
944
+ loge("Running on Android requires that you explicitly specify the browser to run with --browser <id>. Run emrun --android --list_browsers to obtain a list of installed browsers you can use.")
945
+ return 1
946
+ elif options.browser == 'firefox':
947
+ browser_app = 'org.mozilla.firefox/.App'
948
+ elif options.browser == 'firefox_beta':
949
+ browser_app = 'org.mozilla.firefox_beta/.App'
950
+ elif options.browser == 'firefox_aurora' or options.browser == 'fennec_aurora':
951
+ browser_app = 'org.mozilla.fennec_aurora/.App'
952
+ elif options.browser == 'firefox_nightly' or options.browser == 'fennec':
953
+ browser_app = 'org.mozilla.fennec/.App'
954
+ elif options.browser == 'chrome':
955
+ browser_app = 'com.android.chrome/.Main'
956
+ elif options.browser == 'chrome_beta' or options.browser == 'chrome_canary': # There is no Chrome Canary for Android, but Play store has 'Chrome Beta' instead.
957
+ browser_app = 'com.chrome.beta/com.android.chrome.Main'
958
+ elif options.browser == 'opera':
959
+ browser_app = 'com.opera.browser/com.opera.Opera'
960
+ elif options.browser == 'opera_mini': # Launching the URL works, but page seems to never load (Fails with 'Network problem' even when other browsers work)
961
+ browser_app = 'com.opera.mini.android/.Browser'
962
+ elif options.browser =='dolphin': # Current stable Dolphin as of 12/2013 does not have WebGL support.
963
+ browser_app = 'mobi.mgeek.TunnyBrowser/.BrowserActivity'
964
+ else:
965
+ loge("Don't know how to launch browser " + options.browser + ' on Android!')
966
+ return 1
967
+ # To add support for a new Android browser in the list above:
968
+ # 1. Install the browser to Android phone, connect it via adb to PC.
969
+ # 2. Type 'adb shell pm list packages -f' to locate the package name of that application.
970
+ # 3. Type 'adb pull <packagename>.apk' to copy the apk of that application to PC.
971
+ # 4. Type 'aapt d xmltree <packagename>.apk AndroidManifest.xml > manifest.txt' to extract the manifest from the package.
972
+ # 5. Locate the name of the main activity for the browser in manifest.txt and add an entry to above list in form 'appname/mainactivityname'
973
+
974
+ if WINDOWS:
975
+ url = url.replace('&', '\\&')
976
+ browser = [ADB, 'shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-n', browser_app, '-d', url]
977
+ processname_killed_atexit = browser_app[:browser_app.find('/')]
978
+ else: #Launching a web page on local system.
979
+ browser = find_browser(str(options.browser))
980
+ browser_exe = browser[0]
981
+ browser_args = []
982
+
983
+ if 'safari' in browser_exe.lower():
984
+ # Safari has a bug that a command line 'Safari http://page.com' does not launch that page,
985
+ # but instead launches 'file:///http://page.com'. To remedy this, must use the open -a command
986
+ # to run Safari, but unfortunately this will end up spawning Safari process detached from emrun.
987
+ if OSX:
988
+ browser = ['open', '-a', 'Safari'] + (browser[1:] if len(browser) > 1 else [])
989
+
990
+ processname_killed_atexit = 'Safari'
991
+ elif 'chrome' in browser_exe.lower():
992
+ processname_killed_atexit = 'chrome'
993
+ browser_args = ['--incognito', '--enable-nacl', '--enable-pnacl', '--disable-restore-session-state', '--enable-webgl', '--no-default-browser-check', '--no-first-run', '--allow-file-access-from-files']
994
+ # if options.no_server:
995
+ # browser_args += ['--disable-web-security']
996
+ elif 'firefox' in browser_exe.lower():
997
+ processname_killed_atexit = 'firefox'
998
+ elif 'iexplore' in browser_exe.lower():
999
+ processname_killed_atexit = 'iexplore'
1000
+ browser_args = ['-private']
1001
+ elif 'opera' in browser_exe.lower():
1002
+ processname_killed_atexit = 'opera'
1003
+
1004
+ # In Windows cmdline, & character delimits multiple commmands, so must use ^ to escape them.
1005
+ if browser_exe == 'cmd':
1006
+ url = url.replace('&', '^&')
1007
+ browser += browser_args + [url]
864
1008
 
865
1009
  if options.kill_on_start:
1010
+ pname = processname_killed_atexit
866
1011
  kill_browser_process()
1012
+ processname_killed_atexit = pname
867
1013
 
868
1014
  if options.system_info:
869
1015
  logi('Time of run: ' + time.strftime("%x %X"))
870
- logi('Computer name: ' + socket.gethostname()) # http://stackoverflow.com/questions/799767/getting-name-of-windows-computer-running-python-script
871
- logi('OS: ' + get_os_version() + ' with ' + str(get_system_memory()/1024/1024) + ' MB of System RAM')
872
- logi('CPU: ' + get_cpu_infoline())
873
- print_gpu_infolines()
1016
+ if options.android:
1017
+ logi('Model: ' + get_android_model())
1018
+ logi('OS: ' + get_android_os_version() + ' with ' + str(get_system_memory()/1024/1024) + ' MB of System RAM')
1019
+ logi('CPU: ' + get_android_cpu_infoline())
1020
+ else:
1021
+ logi('Computer name: ' + socket.gethostname()) # http://stackoverflow.com/questions/799767/getting-name-of-windows-computer-running-python-script
1022
+ logi('OS: ' + get_os_version() + ' with ' + str(get_system_memory()/1024/1024) + ' MB of System RAM')
1023
+ logi('CPU: ' + get_cpu_infoline())
1024
+ print_gpu_infolines()
874
1025
  if options.browser_info:
875
- logi('Browser: ' + browser_display_name(browser[0]) + ' ' + get_executable_version(browser_exe))
1026
+ if options.android:
1027
+ logi('Browser: Android ' + browser_app)
1028
+ else:
1029
+ logi('Browser: ' + browser_display_name(browser[0]) + ' ' + get_executable_version(browser_exe))
876
1030
 
877
1031
  # Suppress run warning if requested.
878
1032
  if options.no_emrun_detect:
879
1033
  emrun_not_enabled_nag_printed = True
880
1034
 
881
- global browser_stdout_handle, browser_stderr_handle
1035
+ global browser_stdout_handle, browser_stderr_handle
882
1036
  if options.log_stdout:
883
1037
  browser_stdout_handle = open(options.log_stdout, 'ab')
884
1038
  if options.log_stderr:
@@ -887,6 +1041,10 @@ def main():
887
1041
  else:
888
1042
  browser_stderr_handle = open(options.log_stderr, 'ab')
889
1043
 
1044
+ if not options.no_server:
1045
+ logv('Starting web server in port ' + str(options.port))
1046
+ httpd = HTTPWebServer(('', options.port), HTTPHandler)
1047
+
890
1048
  if not options.no_browser:
891
1049
  logv("Executing %s" % ' '.join(browser))
892
1050
  if browser[0] == 'cmd':
@@ -894,10 +1052,15 @@ def main():
894
1052
  browser_process = subprocess.Popen(browser)
895
1053
  if options.kill_on_exit:
896
1054
  atexit.register(kill_browser_process)
1055
+ # For Android automation, we execute adb, so this process does not represent a browser and no point killing it.
1056
+ if options.android:
1057
+ browser_process = None
897
1058
 
1059
+ if browser_process and browser_process.poll() == None:
1060
+ options.serve_after_close = True
1061
+ logv('Warning: emrun got detached from the target browser process. Cannot detect when user closes the browser. Behaving as if --serve_after_close was passed in.')
1062
+
898
1063
  if not options.no_server:
899
- logv('Starting web server in port ' + str(options.port))
900
- httpd = HTTPWebServer(('', options.port), HTTPHandler)
901
1064
  try:
902
1065
  httpd.serve_forever()
903
1066
  except KeyboardInterrupt:
@@ -915,4 +1078,6 @@ def main():
915
1078
  return page_exit_code
916
1079
 
917
1080
  if __name__ == '__main__':
918
- sys.exit(main())
1081
+ returncode = main()
1082
+ logv('emrun quitting with process exit code ' + str(returncode))
1083
+ sys.exit(returncode)