##// END OF EJS Templates
Add rsvp to setupbase
Jonathan Frederic -
Show More
@@ -1,740 +1,747
1 1 //
2 2 // Utility functions for the HTML notebook's CasperJS tests.
3 3 //
4 4 casper.get_notebook_server = function () {
5 5 // Get the URL of a notebook server on which to run tests.
6 6 var port = casper.cli.get("port");
7 7 port = (typeof port === 'undefined') ? '8888' : port;
8 8 return casper.cli.get("url") || ('http://127.0.0.1:' + port);
9 9 };
10 10
11 11 casper.open_new_notebook = function () {
12 12 // Create and open a new notebook.
13 13 var baseUrl = this.get_notebook_server();
14 14 this.start(baseUrl);
15 15 this.waitFor(this.page_loaded);
16 16 this.thenClick('button#new_notebook');
17 17 this.waitForPopup('');
18 18
19 19 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
20 20 this.then(function () {
21 21 this.open(this.popups[0].url);
22 22 });
23 23 this.waitFor(this.page_loaded);
24 24
25 25 // Hook the log and error methods of the console, forcing them to
26 26 // serialize their arguments before printing. This allows the
27 27 // Objects to cross into the phantom/slimer regime for display.
28 28 this.thenEvaluate(function(){
29 29 var serialize_arguments = function(f, context) {
30 30 return function() {
31 31 var pretty_arguments = [];
32 32 for (var i = 0; i < arguments.length; i++) {
33 33 var value = arguments[i];
34 34 if (value instanceof Object) {
35 pretty_arguments.push(JSON.stringify(value, null, ' '));
35 var name = value.name || 'Object';
36 // Print a JSON string representation of the object.
37 // If we don't do this, [Object object] gets printed
38 // by casper, which is useless. The long regular
39 // expression reduces the verbosity of the JSON.
40 pretty_arguments.push(name + ' {' + JSON.stringify(value, null, ' ')
41 .replace(/(\s+)?({)?(\s+)?(}(\s+)?,?)?(\s+)?(\s+)?\n/g, '\n')
42 .replace(/\n(\s+)?\n/g, '\n'));
36 43 } else {
37 44 pretty_arguments.push(value);
38 45 }
39 46 }
40 47 f.apply(context, pretty_arguments);
41 48 };
42 49 };
43 50 console.log = serialize_arguments(console.log, console);
44 51 console.error = serialize_arguments(console.error, console);
45 52 });
46 53
47 54 // Make sure the kernel has started
48 55 this.waitFor(this.kernel_running);
49 56 // track the IPython busy/idle state
50 57 this.thenEvaluate(function () {
51 58 require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
52 59
53 60 events.on('kernel_idle.Kernel',function () {
54 61 IPython._status = 'idle';
55 62 });
56 63 events.on('kernel_busy.Kernel',function () {
57 64 IPython._status = 'busy';
58 65 });
59 66 });
60 67 });
61 68
62 69 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
63 70 // sure the notebook has actually been loaded into the IPython namespace
64 71 // before running any tests.
65 72 this.waitFor(function() {
66 73 return this.evaluate(function () {
67 74 return IPython.notebook;
68 75 });
69 76 });
70 77 };
71 78
72 79 casper.page_loaded = function() {
73 80 // Return whether or not the kernel is running.
74 81 return this.evaluate(function() {
75 82 return typeof IPython !== "undefined" &&
76 83 IPython.page !== undefined;
77 84 });
78 85 };
79 86
80 87 casper.kernel_running = function() {
81 88 // Return whether or not the kernel is running.
82 89 return this.evaluate(function() {
83 90 return IPython &&
84 91 IPython.notebook &&
85 92 IPython.notebook.kernel &&
86 93 IPython.notebook.kernel.is_connected();
87 94 });
88 95 };
89 96
90 97 casper.kernel_disconnected = function() {
91 98 return this.evaluate(function() {
92 99 return IPython.notebook.kernel.is_fully_disconnected();
93 100 });
94 101 };
95 102
96 103 casper.wait_for_kernel_ready = function () {
97 104 this.waitFor(this.kernel_running);
98 105 this.thenEvaluate(function () {
99 106 IPython._kernel_ready = false;
100 107 IPython.notebook.kernel.kernel_info(
101 108 function () {
102 109 IPython._kernel_ready = true;
103 110 });
104 111 });
105 112 this.waitFor(function () {
106 113 return this.evaluate(function () {
107 114 return IPython._kernel_ready;
108 115 });
109 116 });
110 117 };
111 118
112 119 casper.shutdown_current_kernel = function () {
113 120 // Shut down the current notebook's kernel.
114 121 this.thenEvaluate(function() {
115 122 IPython.notebook.session.delete();
116 123 });
117 124 // We close the page right after this so we need to give it time to complete.
118 125 this.wait(1000);
119 126 };
120 127
121 128 casper.delete_current_notebook = function () {
122 129 // Delete created notebook.
123 130
124 131 // For some unknown reason, this doesn't work?!?
125 132 this.thenEvaluate(function() {
126 133 IPython.notebook.delete();
127 134 });
128 135 };
129 136
130 137 casper.wait_for_busy = function () {
131 138 // Waits for the notebook to enter a busy state.
132 139 this.waitFor(function () {
133 140 return this.evaluate(function () {
134 141 return IPython._status == 'busy';
135 142 });
136 143 });
137 144 };
138 145
139 146 casper.wait_for_idle = function () {
140 147 // Waits for the notebook to idle.
141 148 this.waitFor(function () {
142 149 return this.evaluate(function () {
143 150 return IPython._status == 'idle';
144 151 });
145 152 });
146 153 };
147 154
148 155 casper.wait_for_output = function (cell_num, out_num) {
149 156 // wait for the nth output in a given cell
150 157 this.wait_for_idle();
151 158 out_num = out_num || 0;
152 159 this.then(function() {
153 160 this.waitFor(function (c, o) {
154 161 return this.evaluate(function get_output(c, o) {
155 162 var cell = IPython.notebook.get_cell(c);
156 163 return cell.output_area.outputs.length > o;
157 164 },
158 165 // pass parameter from the test suite js to the browser code js
159 166 {c : cell_num, o : out_num});
160 167 });
161 168 },
162 169 function then() { },
163 170 function timeout() {
164 171 this.echo("wait_for_output timed out!");
165 172 });
166 173 };
167 174
168 175 casper.wait_for_widget = function (widget_info) {
169 176 // wait for a widget msg que to reach 0
170 177 //
171 178 // Parameters
172 179 // ----------
173 180 // widget_info : object
174 181 // Object which contains info related to the widget. The model_id property
175 182 // is used to identify the widget.
176 183 this.waitFor(function () {
177 184 var pending = this.evaluate(function (m) {
178 185 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
179 186 }, {m: widget_info.model_id});
180 187
181 188 if (pending === 0) {
182 189 return true;
183 190 } else {
184 191 return false;
185 192 }
186 193 });
187 194 };
188 195
189 196 casper.get_output_cell = function (cell_num, out_num) {
190 197 // return an output of a given cell
191 198 out_num = out_num || 0;
192 199 var result = casper.evaluate(function (c, o) {
193 200 var cell = IPython.notebook.get_cell(c);
194 201 return cell.output_area.outputs[o];
195 202 },
196 203 {c : cell_num, o : out_num});
197 204 if (!result) {
198 205 var num_outputs = casper.evaluate(function (c) {
199 206 var cell = IPython.notebook.get_cell(c);
200 207 return cell.output_area.outputs.length;
201 208 },
202 209 {c : cell_num});
203 210 this.test.assertTrue(false,
204 211 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
205 212 );
206 213 } else {
207 214 return result;
208 215 }
209 216 };
210 217
211 218 casper.get_cells_length = function () {
212 219 // return the number of cells in the notebook
213 220 var result = casper.evaluate(function () {
214 221 return IPython.notebook.get_cells().length;
215 222 });
216 223 return result;
217 224 };
218 225
219 226 casper.set_cell_text = function(index, text){
220 227 // Set the text content of a cell.
221 228 this.evaluate(function (index, text) {
222 229 var cell = IPython.notebook.get_cell(index);
223 230 cell.set_text(text);
224 231 }, index, text);
225 232 };
226 233
227 234 casper.get_cell_text = function(index){
228 235 // Get the text content of a cell.
229 236 return this.evaluate(function (index) {
230 237 var cell = IPython.notebook.get_cell(index);
231 238 return cell.get_text();
232 239 }, index);
233 240 };
234 241
235 242 casper.insert_cell_at_bottom = function(cell_type){
236 243 // Inserts a cell at the bottom of the notebook
237 244 // Returns the new cell's index.
238 245 return this.evaluate(function (cell_type) {
239 246 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
240 247 return IPython.notebook.find_cell_index(cell);
241 248 }, cell_type);
242 249 };
243 250
244 251 casper.append_cell = function(text, cell_type) {
245 252 // Insert a cell at the bottom of the notebook and set the cells text.
246 253 // Returns the new cell's index.
247 254 var index = this.insert_cell_at_bottom(cell_type);
248 255 if (text !== undefined) {
249 256 this.set_cell_text(index, text);
250 257 }
251 258 return index;
252 259 };
253 260
254 261 casper.execute_cell = function(index, expect_failure){
255 262 // Asynchronously executes a cell by index.
256 263 // Returns the cell's index.
257 264
258 265 if (expect_failure === undefined) expect_failure = false;
259 266 var that = this;
260 267 this.then(function(){
261 268 that.evaluate(function (index) {
262 269 var cell = IPython.notebook.get_cell(index);
263 270 cell.execute();
264 271 }, index);
265 272 });
266 273 this.wait_for_idle();
267 274
268 275 this.then(function () {
269 276 var error = that.evaluate(function (index) {
270 277 var cell = IPython.notebook.get_cell(index);
271 278 var outputs = cell.output_area.outputs;
272 279 for (var i = 0; i < outputs.length; i++) {
273 280 if (outputs[i].output_type == 'error') {
274 281 return outputs[i];
275 282 }
276 283 }
277 284 return false;
278 285 }, index);
279 286 if (error === null) {
280 287 this.test.fail("Failed to check for error output");
281 288 }
282 289 if (expect_failure && error === false) {
283 290 this.test.fail("Expected error while running cell");
284 291 } else if (!expect_failure && error !== false) {
285 292 this.test.fail("Error running cell:\n" + error.traceback.join('\n'));
286 293 }
287 294 });
288 295 return index;
289 296 };
290 297
291 298 casper.execute_cell_then = function(index, then_callback, expect_failure) {
292 299 // Synchronously executes a cell by index.
293 300 // Optionally accepts a then_callback parameter. then_callback will get called
294 301 // when the cell has finished executing.
295 302 // Returns the cell's index.
296 303 var return_val = this.execute_cell(index, expect_failure);
297 304
298 305 this.wait_for_idle();
299 306
300 307 var that = this;
301 308 this.then(function(){
302 309 if (then_callback!==undefined) {
303 310 then_callback.apply(that, [index]);
304 311 }
305 312 });
306 313
307 314 return return_val;
308 315 };
309 316
310 317 casper.cell_element_exists = function(index, selector){
311 318 // Utility function that allows us to easily check if an element exists
312 319 // within a cell. Uses JQuery selector to look for the element.
313 320 return casper.evaluate(function (index, selector) {
314 321 var $cell = IPython.notebook.get_cell(index).element;
315 322 return $cell.find(selector).length > 0;
316 323 }, index, selector);
317 324 };
318 325
319 326 casper.cell_element_function = function(index, selector, function_name, function_args){
320 327 // Utility function that allows us to execute a jQuery function on an
321 328 // element within a cell.
322 329 return casper.evaluate(function (index, selector, function_name, function_args) {
323 330 var $cell = IPython.notebook.get_cell(index).element;
324 331 var $el = $cell.find(selector);
325 332 return $el[function_name].apply($el, function_args);
326 333 }, index, selector, function_name, function_args);
327 334 };
328 335
329 336 casper.validate_notebook_state = function(message, mode, cell_index) {
330 337 // Validate the entire dual mode state of the notebook. Make sure no more than
331 338 // one cell is selected, focused, in edit mode, etc...
332 339
333 340 // General tests.
334 341 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
335 342 message + '; keyboard and notebook modes match');
336 343 // Is the selected cell the only cell that is selected?
337 344 if (cell_index!==undefined) {
338 345 this.test.assert(this.is_only_cell_selected(cell_index),
339 346 message + '; cell ' + cell_index + ' is the only cell selected');
340 347 }
341 348
342 349 // Mode specific tests.
343 350 if (mode==='command') {
344 351 // Are the notebook and keyboard manager in command mode?
345 352 this.test.assertEquals(this.get_keyboard_mode(), 'command',
346 353 message + '; in command mode');
347 354 // Make sure there isn't a single cell in edit mode.
348 355 this.test.assert(this.is_only_cell_edit(null),
349 356 message + '; all cells in command mode');
350 357 this.test.assert(this.is_cell_editor_focused(null),
351 358 message + '; no cell editors are focused while in command mode');
352 359
353 360 } else if (mode==='edit') {
354 361 // Are the notebook and keyboard manager in edit mode?
355 362 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
356 363 message + '; in edit mode');
357 364 if (cell_index!==undefined) {
358 365 // Is the specified cell the only cell in edit mode?
359 366 this.test.assert(this.is_only_cell_edit(cell_index),
360 367 message + '; cell ' + cell_index + ' is the only cell in edit mode');
361 368 // Is the specified cell the only cell with a focused code mirror?
362 369 this.test.assert(this.is_cell_editor_focused(cell_index),
363 370 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
364 371 }
365 372
366 373 } else {
367 374 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
368 375 }
369 376 };
370 377
371 378 casper.select_cell = function(index) {
372 379 // Select a cell in the notebook.
373 380 this.evaluate(function (i) {
374 381 IPython.notebook.select(i);
375 382 }, {i: index});
376 383 };
377 384
378 385 casper.click_cell_editor = function(index) {
379 386 // Emulate a click on a cell's editor.
380 387
381 388 // Code Mirror does not play nicely with emulated brower events.
382 389 // Instead of trying to emulate a click, here we run code similar to
383 390 // the code used in Code Mirror that handles the mousedown event on a
384 391 // region of codemirror that the user can focus.
385 392 this.evaluate(function (i) {
386 393 var cm = IPython.notebook.get_cell(i).code_mirror;
387 394 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
388 395 cm.display.input.focus();
389 396 }, {i: index});
390 397 };
391 398
392 399 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
393 400 // Set the Code Mirror instance cursor's location.
394 401 this.evaluate(function (i, l, c) {
395 402 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
396 403 }, {i: index, l: line_index, c: char_index});
397 404 };
398 405
399 406 casper.focus_notebook = function() {
400 407 // Focus the notebook div.
401 408 this.evaluate(function (){
402 409 $('#notebook').focus();
403 410 }, {});
404 411 };
405 412
406 413 casper.trigger_keydown = function() {
407 414 // Emulate a keydown in the notebook.
408 415 for (var i = 0; i < arguments.length; i++) {
409 416 this.evaluate(function (k) {
410 417 var element = $(document);
411 418 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
412 419 element.trigger(event);
413 420 }, {k: arguments[i]});
414 421 }
415 422 };
416 423
417 424 casper.get_keyboard_mode = function() {
418 425 // Get the mode of the keyboard manager.
419 426 return this.evaluate(function() {
420 427 return IPython.keyboard_manager.mode;
421 428 }, {});
422 429 };
423 430
424 431 casper.get_notebook_mode = function() {
425 432 // Get the mode of the notebook.
426 433 return this.evaluate(function() {
427 434 return IPython.notebook.mode;
428 435 }, {});
429 436 };
430 437
431 438 casper.get_cell = function(index) {
432 439 // Get a single cell.
433 440 //
434 441 // Note: Handles to DOM elements stored in the cell will be useless once in
435 442 // CasperJS context.
436 443 return this.evaluate(function(i) {
437 444 var cell = IPython.notebook.get_cell(i);
438 445 if (cell) {
439 446 return cell;
440 447 }
441 448 return null;
442 449 }, {i : index});
443 450 };
444 451
445 452 casper.is_cell_editor_focused = function(index) {
446 453 // Make sure a cell's editor is the only editor focused on the page.
447 454 return this.evaluate(function(i) {
448 455 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
449 456 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
450 457 if (i === null) {
451 458 return focused_textarea.length === 0;
452 459 } else {
453 460 var cell = IPython.notebook.get_cell(i);
454 461 if (cell) {
455 462 return cell.code_mirror.getInputField() == focused_textarea[0];
456 463 }
457 464 }
458 465 return false;
459 466 }, {i : index});
460 467 };
461 468
462 469 casper.is_only_cell_selected = function(index) {
463 470 // Check if a cell is the only cell selected.
464 471 // Pass null as the index to check if no cells are selected.
465 472 return this.is_only_cell_on(index, 'selected', 'unselected');
466 473 };
467 474
468 475 casper.is_only_cell_edit = function(index) {
469 476 // Check if a cell is the only cell in edit mode.
470 477 // Pass null as the index to check if all of the cells are in command mode.
471 478 return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
472 479 };
473 480
474 481 casper.is_only_cell_on = function(i, on_class, off_class) {
475 482 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
476 483 // All of the other cells are checked for the `off_class` DOM class.
477 484 // Pass null as the index to check if all of the cells have the `off_class`.
478 485 var cells_length = this.get_cells_length();
479 486 for (var j = 0; j < cells_length; j++) {
480 487 if (j === i) {
481 488 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
482 489 return false;
483 490 }
484 491 } else {
485 492 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
486 493 return false;
487 494 }
488 495 }
489 496 }
490 497 return true;
491 498 };
492 499
493 500 casper.cell_has_class = function(index, classes) {
494 501 // Check if a cell has a class.
495 502 return this.evaluate(function(i, c) {
496 503 var cell = IPython.notebook.get_cell(i);
497 504 if (cell) {
498 505 return cell.element.hasClass(c);
499 506 }
500 507 return false;
501 508 }, {i : index, c: classes});
502 509 };
503 510
504 511 casper.is_cell_rendered = function (index) {
505 512 return this.evaluate(function(i) {
506 513 return !!IPython.notebook.get_cell(i).rendered;
507 514 }, {i:index});
508 515 };
509 516
510 517 casper.assert_colors_equal = function (hex_color, local_color, msg) {
511 518 // Tests to see if two colors are equal.
512 519 //
513 520 // Parameters
514 521 // hex_color: string
515 522 // Hexadecimal color code, with or without preceeding hash character.
516 523 // local_color: string
517 524 // Local color representation. Can either be hexadecimal (default for
518 525 // phantom) or rgb (default for slimer).
519 526
520 527 // Remove parentheses, hashes, semi-colons, and space characters.
521 528 hex_color = hex_color.replace(/[\(\); #]/, '');
522 529 local_color = local_color.replace(/[\(\); #]/, '');
523 530
524 531 // If the local color is rgb, clean it up and replace
525 532 if (local_color.substr(0,3).toLowerCase() == 'rgb') {
526 533 components = local_color.substr(3).split(',');
527 534 local_color = '';
528 535 for (var i = 0; i < components.length; i++) {
529 536 var part = parseInt(components[i]).toString(16);
530 537 while (part.length < 2) part = '0' + part;
531 538 local_color += part;
532 539 }
533 540 }
534 541
535 542 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
536 543 };
537 544
538 545 casper.notebook_test = function(test) {
539 546 // Wrap a notebook test to reduce boilerplate.
540 547 this.open_new_notebook();
541 548
542 549 // Echo whether or not we are running this test using SlimerJS
543 550 if (this.evaluate(function(){
544 551 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
545 552 })) {
546 553 console.log('This test is running in SlimerJS.');
547 554 this.slimerjs = true;
548 555 }
549 556
550 557 // Make sure to remove the onbeforeunload callback. This callback is
551 558 // responsible for the "Are you sure you want to quit?" type messages.
552 559 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
553 560 this.then(function(){
554 561 this.evaluate(function(){
555 562 window.onbeforeunload = function(){};
556 563 });
557 564 });
558 565
559 566 this.then(test);
560 567
561 568 // Kill the kernel and delete the notebook.
562 569 this.shutdown_current_kernel();
563 570 // This is still broken but shouldn't be a problem for now.
564 571 // this.delete_current_notebook();
565 572
566 573 // This is required to clean up the page we just finished with. If we don't call this
567 574 // casperjs will leak file descriptors of all the open WebSockets in that page. We
568 575 // have to set this.page=null so that next time casper.start runs, it will create a
569 576 // new page from scratch.
570 577 this.then(function () {
571 578 this.page.close();
572 579 this.page = null;
573 580 });
574 581
575 582 // Run the browser automation.
576 583 this.run(function() {
577 584 this.test.done();
578 585 });
579 586 };
580 587
581 588 casper.wait_for_dashboard = function () {
582 589 // Wait for the dashboard list to load.
583 590 casper.waitForSelector('.list_item');
584 591 };
585 592
586 593 casper.open_dashboard = function () {
587 594 // Start casper by opening the dashboard page.
588 595 var baseUrl = this.get_notebook_server();
589 596 this.start(baseUrl);
590 597 this.waitFor(this.page_loaded);
591 598 this.wait_for_dashboard();
592 599 };
593 600
594 601 casper.dashboard_test = function (test) {
595 602 // Open the dashboard page and run a test.
596 603 this.open_dashboard();
597 604 this.then(test);
598 605
599 606 this.then(function () {
600 607 this.page.close();
601 608 this.page = null;
602 609 });
603 610
604 611 // Run the browser automation.
605 612 this.run(function() {
606 613 this.test.done();
607 614 });
608 615 };
609 616
610 617 // note that this will only work for UNIQUE events -- if you want to
611 618 // listen for the same event twice, this will not work!
612 619 casper.event_test = function (name, events, action, timeout) {
613 620
614 621 // set up handlers to listen for each of the events
615 622 this.thenEvaluate(function (events) {
616 623 var make_handler = function (event) {
617 624 return function () {
618 625 IPython._events_triggered.push(event);
619 626 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
620 627 delete IPython._event_handlers[event];
621 628 };
622 629 };
623 630 IPython._event_handlers = {};
624 631 IPython._events_triggered = [];
625 632 for (var i=0; i < events.length; i++) {
626 633 IPython._event_handlers[events[i]] = make_handler(events[i]);
627 634 IPython.notebook.events.on(events[i], IPython._event_handlers[events[i]]);
628 635 }
629 636 }, [events]);
630 637
631 638 // execute the requested action
632 639 this.then(action);
633 640
634 641 // wait for all the events to be triggered
635 642 this.waitFor(function () {
636 643 return this.evaluate(function (events) {
637 644 return IPython._events_triggered.length >= events.length;
638 645 }, [events]);
639 646 }, undefined, undefined, timeout);
640 647
641 648 // test that the events were triggered in the proper order
642 649 this.then(function () {
643 650 var triggered = this.evaluate(function () {
644 651 return IPython._events_triggered;
645 652 });
646 653 var handlers = this.evaluate(function () {
647 654 return Object.keys(IPython._event_handlers);
648 655 });
649 656 this.test.assertEquals(triggered.length, events.length, name + ': ' + events.length + ' events were triggered');
650 657 this.test.assertEquals(handlers.length, 0, name + ': all handlers triggered');
651 658 for (var i=0; i < events.length; i++) {
652 659 this.test.assertEquals(triggered[i], events[i], name + ': ' + events[i] + ' was triggered');
653 660 }
654 661 });
655 662
656 663 // turn off any remaining event listeners
657 664 this.thenEvaluate(function () {
658 665 for (var event in IPython._event_handlers) {
659 666 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
660 667 delete IPython._event_handlers[event];
661 668 }
662 669 });
663 670 };
664 671
665 672 casper.options.waitTimeout=10000;
666 673 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
667 674 this.echo("Timeout for " + casper.get_notebook_server());
668 675 this.echo("Is the notebook server running?");
669 676 });
670 677
671 678 casper.print_log = function () {
672 679 // Pass `console.log` calls from page JS to casper.
673 680 this.on('remote.message', function(msg) {
674 681 this.echo('Remote message caught: ' + msg);
675 682 });
676 683 };
677 684
678 685 casper.on("page.error", function onError(msg, trace) {
679 686 // show errors in the browser
680 687 this.echo("Page Error");
681 688 this.echo(" Message: " + msg.split('\n').join('\n '));
682 689 this.echo(" Call stack:");
683 690 var local_path = this.get_notebook_server();
684 691 for (var i = 0; i < trace.length; i++) {
685 692 var frame = trace[i];
686 693 var file = frame.file;
687 694 // shorten common phantomjs evaluate url
688 695 // this will have a different value on slimerjs
689 696 if (file === "phantomjs://webpage.evaluate()") {
690 697 file = "evaluate";
691 698 }
692 699 // remove the version tag from the path
693 700 file = file.replace(/(\?v=[0-9abcdef]+)/, '');
694 701 // remove the local address from the beginning of the path
695 702 if (file.indexOf(local_path) === 0) {
696 703 file = file.substr(local_path.length);
697 704 }
698 705 var frame_text = (frame.function.length > 0) ? " in " + frame.function : "";
699 706 this.echo(" line " + frame.line + " of " + file + frame_text);
700 707 }
701 708 });
702 709
703 710
704 711 casper.capture_log = function () {
705 712 // show captured errors
706 713 var captured_log = [];
707 714 var seen_errors = 0;
708 715 this.on('remote.message', function(msg) {
709 716 captured_log.push(msg);
710 717 });
711 718
712 719 var that = this;
713 720 this.test.on("test.done", function (result) {
714 721 // test.done runs per-file,
715 722 // but suiteResults is per-suite (directory)
716 723 var current_errors;
717 724 if (this.suiteResults) {
718 725 // casper 1.1 has suiteResults
719 726 current_errors = this.suiteResults.countErrors() + this.suiteResults.countFailed();
720 727 } else {
721 728 // casper 1.0 has testResults instead
722 729 current_errors = this.testResults.failed;
723 730 }
724 731
725 732 if (current_errors > seen_errors && captured_log.length > 0) {
726 733 casper.echo("\nCaptured console.log:");
727 734 for (var i = 0; i < captured_log.length; i++) {
728 735 var output = String(captured_log[i]).split('\n');
729 736 for (var j = 0; j < output.length; j++) {
730 737 casper.echo(" " + output[j]);
731 738 }
732 739 }
733 740 }
734 741
735 742 seen_errors = current_errors;
736 743 captured_log = [];
737 744 });
738 745 };
739 746
740 747 casper.capture_log();
@@ -1,754 +1,755
1 1 # encoding: utf-8
2 2 """
3 3 This module defines the things that are used in setup.py for building IPython
4 4
5 5 This includes:
6 6
7 7 * The basic arguments to setup
8 8 * Functions for finding things like packages, package data, etc.
9 9 * A function for checking dependencies.
10 10 """
11 11
12 12 # Copyright (c) IPython Development Team.
13 13 # Distributed under the terms of the Modified BSD License.
14 14
15 15 from __future__ import print_function
16 16
17 17 import errno
18 18 import os
19 19 import sys
20 20
21 21 from distutils import log
22 22 from distutils.command.build_py import build_py
23 23 from distutils.command.build_scripts import build_scripts
24 24 from distutils.command.install import install
25 25 from distutils.command.install_scripts import install_scripts
26 26 from distutils.cmd import Command
27 27 from fnmatch import fnmatch
28 28 from glob import glob
29 29 from subprocess import check_call
30 30
31 31 from setupext import install_data_ext
32 32
33 33 #-------------------------------------------------------------------------------
34 34 # Useful globals and utility functions
35 35 #-------------------------------------------------------------------------------
36 36
37 37 # A few handy globals
38 38 isfile = os.path.isfile
39 39 pjoin = os.path.join
40 40 repo_root = os.path.dirname(os.path.abspath(__file__))
41 41
42 42 def oscmd(s):
43 43 print(">", s)
44 44 os.system(s)
45 45
46 46 # Py3 compatibility hacks, without assuming IPython itself is installed with
47 47 # the full py3compat machinery.
48 48
49 49 try:
50 50 execfile
51 51 except NameError:
52 52 def execfile(fname, globs, locs=None):
53 53 locs = locs or globs
54 54 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
55 55
56 56 # A little utility we'll need below, since glob() does NOT allow you to do
57 57 # exclusion on multiple endings!
58 58 def file_doesnt_endwith(test,endings):
59 59 """Return true if test is a file and its name does NOT end with any
60 60 of the strings listed in endings."""
61 61 if not isfile(test):
62 62 return False
63 63 for e in endings:
64 64 if test.endswith(e):
65 65 return False
66 66 return True
67 67
68 68 #---------------------------------------------------------------------------
69 69 # Basic project information
70 70 #---------------------------------------------------------------------------
71 71
72 72 # release.py contains version, authors, license, url, keywords, etc.
73 73 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
74 74
75 75 # Create a dict with the basic information
76 76 # This dict is eventually passed to setup after additional keys are added.
77 77 setup_args = dict(
78 78 name = name,
79 79 version = version,
80 80 description = description,
81 81 long_description = long_description,
82 82 author = author,
83 83 author_email = author_email,
84 84 url = url,
85 85 download_url = download_url,
86 86 license = license,
87 87 platforms = platforms,
88 88 keywords = keywords,
89 89 classifiers = classifiers,
90 90 cmdclass = {'install_data': install_data_ext},
91 91 )
92 92
93 93
94 94 #---------------------------------------------------------------------------
95 95 # Find packages
96 96 #---------------------------------------------------------------------------
97 97
98 98 def find_packages():
99 99 """
100 100 Find all of IPython's packages.
101 101 """
102 102 excludes = ['deathrow', 'quarantine']
103 103 packages = []
104 104 for dir,subdirs,files in os.walk('IPython'):
105 105 package = dir.replace(os.path.sep, '.')
106 106 if any(package.startswith('IPython.'+exc) for exc in excludes):
107 107 # package is to be excluded (e.g. deathrow)
108 108 continue
109 109 if '__init__.py' not in files:
110 110 # not a package
111 111 continue
112 112 packages.append(package)
113 113 return packages
114 114
115 115 #---------------------------------------------------------------------------
116 116 # Find package data
117 117 #---------------------------------------------------------------------------
118 118
119 119 def find_package_data():
120 120 """
121 121 Find IPython's package_data.
122 122 """
123 123 # This is not enough for these things to appear in an sdist.
124 124 # We need to muck with the MANIFEST to get this to work
125 125
126 126 # exclude components and less from the walk;
127 127 # we will build the components separately
128 128 excludes = [
129 129 pjoin('static', 'components'),
130 130 pjoin('static', '*', 'less'),
131 131 ]
132 132
133 133 # walk notebook resources:
134 134 cwd = os.getcwd()
135 135 os.chdir(os.path.join('IPython', 'html'))
136 136 static_data = []
137 137 for parent, dirs, files in os.walk('static'):
138 138 if any(fnmatch(parent, pat) for pat in excludes):
139 139 # prevent descending into subdirs
140 140 dirs[:] = []
141 141 continue
142 142 for f in files:
143 143 static_data.append(pjoin(parent, f))
144 144
145 145 components = pjoin("static", "components")
146 146 # select the components we actually need to install
147 147 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
148 148 static_data.extend([
149 149 pjoin(components, "backbone", "backbone-min.js"),
150 150 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
151 151 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
152 152 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
153 153 pjoin(components, "es6-promise", "*.js"),
154 154 pjoin(components, "font-awesome", "fonts", "*.*"),
155 155 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
156 156 pjoin(components, "jquery", "jquery.min.js"),
157 157 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
158 158 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
159 159 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
160 160 pjoin(components, "marked", "lib", "marked.js"),
161 161 pjoin(components, "requirejs", "require.js"),
162 pjoin(components, "rsvp", "rsvp.js"),
162 163 pjoin(components, "underscore", "underscore-min.js"),
163 164 pjoin(components, "moment", "moment.js"),
164 165 pjoin(components, "moment", "min", "moment.min.js"),
165 166 pjoin(components, "term.js", "src", "term.js"),
166 167 pjoin(components, "text-encoding", "lib", "encoding.js"),
167 168 ])
168 169
169 170 # Ship all of Codemirror's CSS and JS
170 171 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
171 172 for f in files:
172 173 if f.endswith(('.js', '.css')):
173 174 static_data.append(pjoin(parent, f))
174 175
175 176 os.chdir(os.path.join('tests',))
176 177 js_tests = glob('*.js') + glob('*/*.js')
177 178
178 179 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
179 180 nbconvert_templates = [os.path.join(dirpath, '*.*')
180 181 for dirpath, _, _ in os.walk('templates')]
181 182
182 183 os.chdir(cwd)
183 184
184 185 package_data = {
185 186 'IPython.config.profile' : ['README*', '*/*.py'],
186 187 'IPython.core.tests' : ['*.png', '*.jpg'],
187 188 'IPython.lib.tests' : ['*.wav'],
188 189 'IPython.testing.plugin' : ['*.txt'],
189 190 'IPython.html' : ['templates/*'] + static_data,
190 191 'IPython.html.tests' : js_tests,
191 192 'IPython.qt.console' : ['resources/icon/*.svg'],
192 193 'IPython.nbconvert' : nbconvert_templates +
193 194 [
194 195 'tests/files/*.*',
195 196 'exporters/tests/files/*.*',
196 197 'preprocessors/tests/files/*.*',
197 198 ],
198 199 'IPython.nbconvert.filters' : ['marked.js'],
199 200 'IPython.nbformat' : [
200 201 'tests/*.ipynb',
201 202 'v3/nbformat.v3.schema.json',
202 203 'v4/nbformat.v4.schema.json',
203 204 ]
204 205 }
205 206
206 207 return package_data
207 208
208 209
209 210 def check_package_data(package_data):
210 211 """verify that package_data globs make sense"""
211 212 print("checking package data")
212 213 for pkg, data in package_data.items():
213 214 pkg_root = pjoin(*pkg.split('.'))
214 215 for d in data:
215 216 path = pjoin(pkg_root, d)
216 217 if '*' in path:
217 218 assert len(glob(path)) > 0, "No files match pattern %s" % path
218 219 else:
219 220 assert os.path.exists(path), "Missing package data: %s" % path
220 221
221 222
222 223 def check_package_data_first(command):
223 224 """decorator for checking package_data before running a given command
224 225
225 226 Probably only needs to wrap build_py
226 227 """
227 228 class DecoratedCommand(command):
228 229 def run(self):
229 230 check_package_data(self.package_data)
230 231 command.run(self)
231 232 return DecoratedCommand
232 233
233 234
234 235 #---------------------------------------------------------------------------
235 236 # Find data files
236 237 #---------------------------------------------------------------------------
237 238
238 239 def make_dir_struct(tag,base,out_base):
239 240 """Make the directory structure of all files below a starting dir.
240 241
241 242 This is just a convenience routine to help build a nested directory
242 243 hierarchy because distutils is too stupid to do this by itself.
243 244
244 245 XXX - this needs a proper docstring!
245 246 """
246 247
247 248 # we'll use these a lot below
248 249 lbase = len(base)
249 250 pathsep = os.path.sep
250 251 lpathsep = len(pathsep)
251 252
252 253 out = []
253 254 for (dirpath,dirnames,filenames) in os.walk(base):
254 255 # we need to strip out the dirpath from the base to map it to the
255 256 # output (installation) path. This requires possibly stripping the
256 257 # path separator, because otherwise pjoin will not work correctly
257 258 # (pjoin('foo/','/bar') returns '/bar').
258 259
259 260 dp_eff = dirpath[lbase:]
260 261 if dp_eff.startswith(pathsep):
261 262 dp_eff = dp_eff[lpathsep:]
262 263 # The output path must be anchored at the out_base marker
263 264 out_path = pjoin(out_base,dp_eff)
264 265 # Now we can generate the final filenames. Since os.walk only produces
265 266 # filenames, we must join back with the dirpath to get full valid file
266 267 # paths:
267 268 pfiles = [pjoin(dirpath,f) for f in filenames]
268 269 # Finally, generate the entry we need, which is a pari of (output
269 270 # path, files) for use as a data_files parameter in install_data.
270 271 out.append((out_path, pfiles))
271 272
272 273 return out
273 274
274 275
275 276 def find_data_files():
276 277 """
277 278 Find IPython's data_files.
278 279
279 280 Just man pages at this point.
280 281 """
281 282
282 283 manpagebase = pjoin('share', 'man', 'man1')
283 284
284 285 # Simple file lists can be made by hand
285 286 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
286 287 if not manpages:
287 288 # When running from a source tree, the manpages aren't gzipped
288 289 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
289 290
290 291 # And assemble the entire output list
291 292 data_files = [ (manpagebase, manpages) ]
292 293
293 294 return data_files
294 295
295 296
296 297 def make_man_update_target(manpage):
297 298 """Return a target_update-compliant tuple for the given manpage.
298 299
299 300 Parameters
300 301 ----------
301 302 manpage : string
302 303 Name of the manpage, must include the section number (trailing number).
303 304
304 305 Example
305 306 -------
306 307
307 308 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
308 309 ('docs/man/ipython.1.gz',
309 310 ['docs/man/ipython.1'],
310 311 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
311 312 """
312 313 man_dir = pjoin('docs', 'man')
313 314 manpage_gz = manpage + '.gz'
314 315 manpath = pjoin(man_dir, manpage)
315 316 manpath_gz = pjoin(man_dir, manpage_gz)
316 317 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
317 318 locals() )
318 319 return (manpath_gz, [manpath], gz_cmd)
319 320
320 321 # The two functions below are copied from IPython.utils.path, so we don't need
321 322 # to import IPython during setup, which fails on Python 3.
322 323
323 324 def target_outdated(target,deps):
324 325 """Determine whether a target is out of date.
325 326
326 327 target_outdated(target,deps) -> 1/0
327 328
328 329 deps: list of filenames which MUST exist.
329 330 target: single filename which may or may not exist.
330 331
331 332 If target doesn't exist or is older than any file listed in deps, return
332 333 true, otherwise return false.
333 334 """
334 335 try:
335 336 target_time = os.path.getmtime(target)
336 337 except os.error:
337 338 return 1
338 339 for dep in deps:
339 340 dep_time = os.path.getmtime(dep)
340 341 if dep_time > target_time:
341 342 #print "For target",target,"Dep failed:",dep # dbg
342 343 #print "times (dep,tar):",dep_time,target_time # dbg
343 344 return 1
344 345 return 0
345 346
346 347
347 348 def target_update(target,deps,cmd):
348 349 """Update a target with a given command given a list of dependencies.
349 350
350 351 target_update(target,deps,cmd) -> runs cmd if target is outdated.
351 352
352 353 This is just a wrapper around target_outdated() which calls the given
353 354 command if target is outdated."""
354 355
355 356 if target_outdated(target,deps):
356 357 os.system(cmd)
357 358
358 359 #---------------------------------------------------------------------------
359 360 # Find scripts
360 361 #---------------------------------------------------------------------------
361 362
362 363 def find_entry_points():
363 364 """Defines the command line entry points for IPython
364 365
365 366 This always uses setuptools-style entry points. When setuptools is not in
366 367 use, our own build_scripts_entrypt class below parses these and builds
367 368 command line scripts.
368 369
369 370 Each of our entry points gets both a plain name, e.g. ipython, and one
370 371 suffixed with the Python major version number, e.g. ipython3.
371 372 """
372 373 ep = [
373 374 'ipython%s = IPython:start_ipython',
374 375 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
375 376 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
376 377 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
377 378 'iptest%s = IPython.testing.iptestcontroller:main',
378 379 ]
379 380 suffix = str(sys.version_info[0])
380 381 return [e % '' for e in ep] + [e % suffix for e in ep]
381 382
382 383 script_src = """#!{executable}
383 384 # This script was automatically generated by setup.py
384 385 if __name__ == '__main__':
385 386 from {mod} import {func}
386 387 {func}()
387 388 """
388 389
389 390 class build_scripts_entrypt(build_scripts):
390 391 """Build the command line scripts
391 392
392 393 Parse setuptools style entry points and write simple scripts to run the
393 394 target functions.
394 395
395 396 On Windows, this also creates .cmd wrappers for the scripts so that you can
396 397 easily launch them from a command line.
397 398 """
398 399 def run(self):
399 400 self.mkpath(self.build_dir)
400 401 outfiles = []
401 402 for script in find_entry_points():
402 403 name, entrypt = script.split('=')
403 404 name = name.strip()
404 405 entrypt = entrypt.strip()
405 406 outfile = os.path.join(self.build_dir, name)
406 407 outfiles.append(outfile)
407 408 print('Writing script to', outfile)
408 409
409 410 mod, func = entrypt.split(':')
410 411 with open(outfile, 'w') as f:
411 412 f.write(script_src.format(executable=sys.executable,
412 413 mod=mod, func=func))
413 414
414 415 if sys.platform == 'win32':
415 416 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
416 417 # command line
417 418 cmd_file = os.path.join(self.build_dir, name + '.cmd')
418 419 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
419 420 python=sys.executable, script=name)
420 421 log.info("Writing %s wrapper script" % cmd_file)
421 422 with open(cmd_file, 'w') as f:
422 423 f.write(cmd)
423 424
424 425 return outfiles, outfiles
425 426
426 427 class install_lib_symlink(Command):
427 428 user_options = [
428 429 ('install-dir=', 'd', "directory to install to"),
429 430 ]
430 431
431 432 def initialize_options(self):
432 433 self.install_dir = None
433 434
434 435 def finalize_options(self):
435 436 self.set_undefined_options('symlink',
436 437 ('install_lib', 'install_dir'),
437 438 )
438 439
439 440 def run(self):
440 441 if sys.platform == 'win32':
441 442 raise Exception("This doesn't work on Windows.")
442 443 pkg = os.path.join(os.getcwd(), 'IPython')
443 444 dest = os.path.join(self.install_dir, 'IPython')
444 445 if os.path.islink(dest):
445 446 print('removing existing symlink at %s' % dest)
446 447 os.unlink(dest)
447 448 print('symlinking %s -> %s' % (pkg, dest))
448 449 os.symlink(pkg, dest)
449 450
450 451 class unsymlink(install):
451 452 def run(self):
452 453 dest = os.path.join(self.install_lib, 'IPython')
453 454 if os.path.islink(dest):
454 455 print('removing symlink at %s' % dest)
455 456 os.unlink(dest)
456 457 else:
457 458 print('No symlink exists at %s' % dest)
458 459
459 460 class install_symlinked(install):
460 461 def run(self):
461 462 if sys.platform == 'win32':
462 463 raise Exception("This doesn't work on Windows.")
463 464
464 465 # Run all sub-commands (at least those that need to be run)
465 466 for cmd_name in self.get_sub_commands():
466 467 self.run_command(cmd_name)
467 468
468 469 # 'sub_commands': a list of commands this command might have to run to
469 470 # get its work done. See cmd.py for more info.
470 471 sub_commands = [('install_lib_symlink', lambda self:True),
471 472 ('install_scripts_sym', lambda self:True),
472 473 ]
473 474
474 475 class install_scripts_for_symlink(install_scripts):
475 476 """Redefined to get options from 'symlink' instead of 'install'.
476 477
477 478 I love distutils almost as much as I love setuptools.
478 479 """
479 480 def finalize_options(self):
480 481 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
481 482 self.set_undefined_options('symlink',
482 483 ('install_scripts', 'install_dir'),
483 484 ('force', 'force'),
484 485 ('skip_build', 'skip_build'),
485 486 )
486 487
487 488 #---------------------------------------------------------------------------
488 489 # Verify all dependencies
489 490 #---------------------------------------------------------------------------
490 491
491 492 def check_for_dependencies():
492 493 """Check for IPython's dependencies.
493 494
494 495 This function should NOT be called if running under setuptools!
495 496 """
496 497 from setupext.setupext import (
497 498 print_line, print_raw, print_status,
498 499 check_for_sphinx, check_for_pygments,
499 500 check_for_nose, check_for_pexpect,
500 501 check_for_pyzmq, check_for_readline,
501 502 check_for_jinja2, check_for_tornado
502 503 )
503 504 print_line()
504 505 print_raw("BUILDING IPYTHON")
505 506 print_status('python', sys.version)
506 507 print_status('platform', sys.platform)
507 508 if sys.platform == 'win32':
508 509 print_status('Windows version', sys.getwindowsversion())
509 510
510 511 print_raw("")
511 512 print_raw("OPTIONAL DEPENDENCIES")
512 513
513 514 check_for_sphinx()
514 515 check_for_pygments()
515 516 check_for_nose()
516 517 if os.name == 'posix':
517 518 check_for_pexpect()
518 519 check_for_pyzmq()
519 520 check_for_tornado()
520 521 check_for_readline()
521 522 check_for_jinja2()
522 523
523 524 #---------------------------------------------------------------------------
524 525 # VCS related
525 526 #---------------------------------------------------------------------------
526 527
527 528 # utils.submodule has checks for submodule status
528 529 execfile(pjoin('IPython','utils','submodule.py'), globals())
529 530
530 531 class UpdateSubmodules(Command):
531 532 """Update git submodules
532 533
533 534 IPython's external javascript dependencies live in a separate repo.
534 535 """
535 536 description = "Update git submodules"
536 537 user_options = []
537 538
538 539 def initialize_options(self):
539 540 pass
540 541
541 542 def finalize_options(self):
542 543 pass
543 544
544 545 def run(self):
545 546 failure = False
546 547 try:
547 548 self.spawn('git submodule init'.split())
548 549 self.spawn('git submodule update --recursive'.split())
549 550 except Exception as e:
550 551 failure = e
551 552 print(e)
552 553
553 554 if not check_submodule_status(repo_root) == 'clean':
554 555 print("submodules could not be checked out")
555 556 sys.exit(1)
556 557
557 558
558 559 def git_prebuild(pkg_dir, build_cmd=build_py):
559 560 """Return extended build or sdist command class for recording commit
560 561
561 562 records git commit in IPython.utils._sysinfo.commit
562 563
563 564 for use in IPython.utils.sysinfo.sys_info() calls after installation.
564 565
565 566 Also ensures that submodules exist prior to running
566 567 """
567 568
568 569 class MyBuildPy(build_cmd):
569 570 ''' Subclass to write commit data into installation tree '''
570 571 def run(self):
571 572 build_cmd.run(self)
572 573 # this one will only fire for build commands
573 574 if hasattr(self, 'build_lib'):
574 575 self._record_commit(self.build_lib)
575 576
576 577 def make_release_tree(self, base_dir, files):
577 578 # this one will fire for sdist
578 579 build_cmd.make_release_tree(self, base_dir, files)
579 580 self._record_commit(base_dir)
580 581
581 582 def _record_commit(self, base_dir):
582 583 import subprocess
583 584 proc = subprocess.Popen('git rev-parse --short HEAD',
584 585 stdout=subprocess.PIPE,
585 586 stderr=subprocess.PIPE,
586 587 shell=True)
587 588 repo_commit, _ = proc.communicate()
588 589 repo_commit = repo_commit.strip().decode("ascii")
589 590
590 591 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
591 592 if os.path.isfile(out_pth) and not repo_commit:
592 593 # nothing to write, don't clobber
593 594 return
594 595
595 596 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
596 597
597 598 # remove to avoid overwriting original via hard link
598 599 try:
599 600 os.remove(out_pth)
600 601 except (IOError, OSError):
601 602 pass
602 603 with open(out_pth, 'w') as out_file:
603 604 out_file.writelines([
604 605 '# GENERATED BY setup.py\n',
605 606 'commit = u"%s"\n' % repo_commit,
606 607 ])
607 608 return require_submodules(MyBuildPy)
608 609
609 610
610 611 def require_submodules(command):
611 612 """decorator for instructing a command to check for submodules before running"""
612 613 class DecoratedCommand(command):
613 614 def run(self):
614 615 if not check_submodule_status(repo_root) == 'clean':
615 616 print("submodules missing! Run `setup.py submodule` and try again")
616 617 sys.exit(1)
617 618 command.run(self)
618 619 return DecoratedCommand
619 620
620 621 #---------------------------------------------------------------------------
621 622 # bdist related
622 623 #---------------------------------------------------------------------------
623 624
624 625 def get_bdist_wheel():
625 626 """Construct bdist_wheel command for building wheels
626 627
627 628 Constructs py2-none-any tag, instead of py2.7-none-any
628 629 """
629 630 class RequiresWheel(Command):
630 631 description = "Dummy command for missing bdist_wheel"
631 632 user_options = []
632 633
633 634 def initialize_options(self):
634 635 pass
635 636
636 637 def finalize_options(self):
637 638 pass
638 639
639 640 def run(self):
640 641 print("bdist_wheel requires the wheel package")
641 642 sys.exit(1)
642 643
643 644 if 'setuptools' not in sys.modules:
644 645 return RequiresWheel
645 646 else:
646 647 try:
647 648 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
648 649 except ImportError:
649 650 return RequiresWheel
650 651
651 652 class bdist_wheel_tag(bdist_wheel):
652 653
653 654 def add_requirements(self, metadata_path):
654 655 """transform platform-dependent requirements"""
655 656 pkg_info = read_pkg_info(metadata_path)
656 657 # pkg_info is an email.Message object (?!)
657 658 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
658 659 # and transform them to conditionals
659 660 requires = pkg_info.get_all('Requires-Dist')
660 661 del pkg_info['Requires-Dist']
661 662 def _remove_startswith(lis, prefix):
662 663 """like list.remove, but with startswith instead of =="""
663 664 found = False
664 665 for idx, item in enumerate(lis):
665 666 if item.startswith(prefix):
666 667 found = True
667 668 break
668 669 if found:
669 670 lis.pop(idx)
670 671
671 672 for pkg in ("gnureadline", "pyreadline", "mock"):
672 673 _remove_startswith(requires, pkg)
673 674 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
674 675 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
675 676 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
676 677 requires.append("mock; extra == 'test' and python_version < '3.3'")
677 678 for r in requires:
678 679 pkg_info['Requires-Dist'] = r
679 680 write_pkg_info(metadata_path, pkg_info)
680 681
681 682 return bdist_wheel_tag
682 683
683 684 #---------------------------------------------------------------------------
684 685 # Notebook related
685 686 #---------------------------------------------------------------------------
686 687
687 688 class CompileCSS(Command):
688 689 """Recompile Notebook CSS
689 690
690 691 Regenerate the compiled CSS from LESS sources.
691 692
692 693 Requires various dev dependencies, such as invoke and lessc.
693 694 """
694 695 description = "Recompile Notebook CSS"
695 696 user_options = [
696 697 ('minify', 'x', "minify CSS"),
697 698 ('force', 'f', "force recompilation of CSS"),
698 699 ]
699 700
700 701 def initialize_options(self):
701 702 self.minify = False
702 703 self.force = False
703 704
704 705 def finalize_options(self):
705 706 self.minify = bool(self.minify)
706 707 self.force = bool(self.force)
707 708
708 709 def run(self):
709 710 cmd = ['invoke', 'css']
710 711 if self.minify:
711 712 cmd.append('--minify')
712 713 if self.force:
713 714 cmd.append('--force')
714 715 check_call(cmd, cwd=pjoin(repo_root, "IPython", "html"))
715 716
716 717
717 718 class JavascriptVersion(Command):
718 719 """write the javascript version to notebook javascript"""
719 720 description = "Write IPython version to javascript"
720 721 user_options = []
721 722
722 723 def initialize_options(self):
723 724 pass
724 725
725 726 def finalize_options(self):
726 727 pass
727 728
728 729 def run(self):
729 730 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
730 731 with open(nsfile) as f:
731 732 lines = f.readlines()
732 733 with open(nsfile, 'w') as f:
733 734 for line in lines:
734 735 if line.startswith("IPython.version"):
735 736 line = 'IPython.version = "{0}";\n'.format(version)
736 737 f.write(line)
737 738
738 739
739 740 def css_js_prerelease(command, strict=True):
740 741 """decorator for building js/minified css prior to a release"""
741 742 class DecoratedCommand(command):
742 743 def run(self):
743 744 self.distribution.run_command('jsversion')
744 745 css = self.distribution.get_command_obj('css')
745 746 css.minify = True
746 747 try:
747 748 self.distribution.run_command('css')
748 749 except Exception as e:
749 750 if strict:
750 751 raise
751 752 else:
752 753 log.warn("Failed to build css sourcemaps: %s" % e)
753 754 command.run(self)
754 755 return DecoratedCommand
General Comments 0
You need to be logged in to leave comments. Login now