Show More
@@ -0,0 +1,11 b'' | |||||
|
1 | The default tracebackmode will now skip frames that are marked with | |||
|
2 | ``__tracebackhide__ = True`` and show how many traceback frames have been | |||
|
3 | skipped. This can be toggled by using :magic:`xmode` with the ``--show`` or | |||
|
4 | ``--hide`` attribute. It will have no effect on non verbose traceback modes. | |||
|
5 | ||||
|
6 | The ipython debugger also now understand ``__tracebackhide__`` as well and will | |||
|
7 | skip hidden frames when displaying. Movement up and down the stack will skip the | |||
|
8 | hidden frames and will show how many frames were hidden. Internal IPython frames | |||
|
9 | are also now hidden by default. The behavior can be changed with the | |||
|
10 | ``skip_hidden`` command and accepts "yes", "no", "true" and "false" case | |||
|
11 | insensitive parameters. |
@@ -280,26 +280,31 b' class Pdb(OldPdb):' | |||||
280 |
|
280 | |||
281 | # Set the prompt - the default prompt is '(Pdb)' |
|
281 | # Set the prompt - the default prompt is '(Pdb)' | |
282 | self.prompt = prompt |
|
282 | self.prompt = prompt | |
|
283 | self.skip_hidden = True | |||
283 |
|
284 | |||
284 | def set_colors(self, scheme): |
|
285 | def set_colors(self, scheme): | |
285 | """Shorthand access to the color table scheme selector method.""" |
|
286 | """Shorthand access to the color table scheme selector method.""" | |
286 | self.color_scheme_table.set_active_scheme(scheme) |
|
287 | self.color_scheme_table.set_active_scheme(scheme) | |
287 | self.parser.style = scheme |
|
288 | self.parser.style = scheme | |
288 |
|
289 | |||
|
290 | ||||
|
291 | def hidden_frames(self, stack): | |||
|
292 | """ | |||
|
293 | Given an index in the stack return wether it should be skipped. | |||
|
294 | ||||
|
295 | This is used in up/down and where to skip frames. | |||
|
296 | """ | |||
|
297 | ip_hide = [s[0].f_locals.get("__tracebackhide__", False) for s in stack] | |||
|
298 | ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"] | |||
|
299 | if ip_start: | |||
|
300 | ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)] | |||
|
301 | return ip_hide | |||
|
302 | ||||
289 | def interaction(self, frame, traceback): |
|
303 | def interaction(self, frame, traceback): | |
290 | try: |
|
304 | try: | |
291 | OldPdb.interaction(self, frame, traceback) |
|
305 | OldPdb.interaction(self, frame, traceback) | |
292 | except KeyboardInterrupt: |
|
306 | except KeyboardInterrupt: | |
293 |
self.stdout.write( |
|
307 | self.stdout.write("\n" + self.shell.get_exception_only()) | |
294 |
|
||||
295 | def new_do_up(self, arg): |
|
|||
296 | OldPdb.do_up(self, arg) |
|
|||
297 | do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up) |
|
|||
298 |
|
||||
299 | def new_do_down(self, arg): |
|
|||
300 | OldPdb.do_down(self, arg) |
|
|||
301 |
|
||||
302 | do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down) |
|
|||
303 |
|
308 | |||
304 | def new_do_frame(self, arg): |
|
309 | def new_do_frame(self, arg): | |
305 | OldPdb.do_frame(self, arg) |
|
310 | OldPdb.do_frame(self, arg) | |
@@ -320,6 +325,8 b' class Pdb(OldPdb):' | |||||
320 | return self.do_quit(arg) |
|
325 | return self.do_quit(arg) | |
321 |
|
326 | |||
322 | def print_stack_trace(self, context=None): |
|
327 | def print_stack_trace(self, context=None): | |
|
328 | Colors = self.color_scheme_table.active_colors | |||
|
329 | ColorsNormal = Colors.Normal | |||
323 | if context is None: |
|
330 | if context is None: | |
324 | context = self.context |
|
331 | context = self.context | |
325 | try: |
|
332 | try: | |
@@ -329,8 +336,21 b' class Pdb(OldPdb):' | |||||
329 | except (TypeError, ValueError) as e: |
|
336 | except (TypeError, ValueError) as e: | |
330 | raise ValueError("Context must be a positive integer") from e |
|
337 | raise ValueError("Context must be a positive integer") from e | |
331 | try: |
|
338 | try: | |
332 | for frame_lineno in self.stack: |
|
339 | skipped = 0 | |
|
340 | for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack): | |||
|
341 | if hidden and self.skip_hidden: | |||
|
342 | skipped += 1 | |||
|
343 | continue | |||
|
344 | if skipped: | |||
|
345 | print( | |||
|
346 | f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" | |||
|
347 | ) | |||
|
348 | skipped = 0 | |||
333 | self.print_stack_entry(frame_lineno, context=context) |
|
349 | self.print_stack_entry(frame_lineno, context=context) | |
|
350 | if skipped: | |||
|
351 | print( | |||
|
352 | f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" | |||
|
353 | ) | |||
334 | except KeyboardInterrupt: |
|
354 | except KeyboardInterrupt: | |
335 | pass |
|
355 | pass | |
336 |
|
356 | |||
@@ -485,6 +505,16 b' class Pdb(OldPdb):' | |||||
485 | except KeyboardInterrupt: |
|
505 | except KeyboardInterrupt: | |
486 | pass |
|
506 | pass | |
487 |
|
507 | |||
|
508 | def do_skip_hidden(self, arg): | |||
|
509 | """ | |||
|
510 | Change whether or not we should skip frames with the | |||
|
511 | __tracebackhide__ attribute. | |||
|
512 | """ | |||
|
513 | if arg.strip().lower() in ("true", "yes"): | |||
|
514 | self.skip_hidden = True | |||
|
515 | elif arg.strip().lower() in ("false", "no"): | |||
|
516 | self.skip_hidden = False | |||
|
517 | ||||
488 | def do_list(self, arg): |
|
518 | def do_list(self, arg): | |
489 | """Print lines of code from the current stack frame |
|
519 | """Print lines of code from the current stack frame | |
490 | """ |
|
520 | """ | |
@@ -631,6 +661,109 b' class Pdb(OldPdb):' | |||||
631 |
|
661 | |||
632 | do_w = do_where |
|
662 | do_w = do_where | |
633 |
|
663 | |||
|
664 | def stop_here(self, frame): | |||
|
665 | hidden = False | |||
|
666 | if self.skip_hidden: | |||
|
667 | hidden = frame.f_locals.get("__tracebackhide__", False) | |||
|
668 | if hidden: | |||
|
669 | Colors = self.color_scheme_table.active_colors | |||
|
670 | ColorsNormal = Colors.Normal | |||
|
671 | print(f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n") | |||
|
672 | ||||
|
673 | return super().stop_here(frame) | |||
|
674 | ||||
|
675 | def do_up(self, arg): | |||
|
676 | """u(p) [count] | |||
|
677 | Move the current frame count (default one) levels up in the | |||
|
678 | stack trace (to an older frame). | |||
|
679 | ||||
|
680 | Will skip hidden frames. | |||
|
681 | """ | |||
|
682 | ## modified version of upstream that skips | |||
|
683 | # frames with __tracebackide__ | |||
|
684 | if self.curindex == 0: | |||
|
685 | self.error("Oldest frame") | |||
|
686 | return | |||
|
687 | try: | |||
|
688 | count = int(arg or 1) | |||
|
689 | except ValueError: | |||
|
690 | self.error("Invalid frame count (%s)" % arg) | |||
|
691 | return | |||
|
692 | skipped = 0 | |||
|
693 | if count < 0: | |||
|
694 | _newframe = 0 | |||
|
695 | else: | |||
|
696 | _newindex = self.curindex | |||
|
697 | counter = 0 | |||
|
698 | hidden_frames = self.hidden_frames(self.stack) | |||
|
699 | for i in range(self.curindex - 1, -1, -1): | |||
|
700 | frame = self.stack[i][0] | |||
|
701 | if hidden_frames[i] and self.skip_hidden: | |||
|
702 | skipped += 1 | |||
|
703 | continue | |||
|
704 | counter += 1 | |||
|
705 | if counter >= count: | |||
|
706 | break | |||
|
707 | else: | |||
|
708 | # if no break occured. | |||
|
709 | self.error("all frames above hidden") | |||
|
710 | return | |||
|
711 | ||||
|
712 | Colors = self.color_scheme_table.active_colors | |||
|
713 | ColorsNormal = Colors.Normal | |||
|
714 | _newframe = i | |||
|
715 | self._select_frame(_newframe) | |||
|
716 | if skipped: | |||
|
717 | print( | |||
|
718 | f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" | |||
|
719 | ) | |||
|
720 | ||||
|
721 | def do_down(self, arg): | |||
|
722 | """d(own) [count] | |||
|
723 | Move the current frame count (default one) levels down in the | |||
|
724 | stack trace (to a newer frame). | |||
|
725 | ||||
|
726 | Will skip hidden frames. | |||
|
727 | """ | |||
|
728 | if self.curindex + 1 == len(self.stack): | |||
|
729 | self.error("Newest frame") | |||
|
730 | return | |||
|
731 | try: | |||
|
732 | count = int(arg or 1) | |||
|
733 | except ValueError: | |||
|
734 | self.error("Invalid frame count (%s)" % arg) | |||
|
735 | return | |||
|
736 | if count < 0: | |||
|
737 | _newframe = len(self.stack) - 1 | |||
|
738 | else: | |||
|
739 | _newindex = self.curindex | |||
|
740 | counter = 0 | |||
|
741 | skipped = 0 | |||
|
742 | hidden_frames = self.hidden_frames(self.stack) | |||
|
743 | for i in range(self.curindex + 1, len(self.stack)): | |||
|
744 | frame = self.stack[i][0] | |||
|
745 | if hidden_frames[i] and self.skip_hidden: | |||
|
746 | skipped += 1 | |||
|
747 | continue | |||
|
748 | counter += 1 | |||
|
749 | if counter >= count: | |||
|
750 | break | |||
|
751 | else: | |||
|
752 | self.error("all frames bellow hidden") | |||
|
753 | return | |||
|
754 | ||||
|
755 | Colors = self.color_scheme_table.active_colors | |||
|
756 | ColorsNormal = Colors.Normal | |||
|
757 | if skipped: | |||
|
758 | print( | |||
|
759 | f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" | |||
|
760 | ) | |||
|
761 | _newframe = i | |||
|
762 | ||||
|
763 | self._select_frame(_newframe) | |||
|
764 | ||||
|
765 | do_d = do_down | |||
|
766 | do_u = do_up | |||
634 |
|
767 | |||
635 | class InterruptiblePdb(Pdb): |
|
768 | class InterruptiblePdb(Pdb): | |
636 | """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" |
|
769 | """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" |
@@ -2211,11 +2211,20 b' class InteractiveShell(SingletonConfigurable):' | |||||
2211 | with self.builtin_trap: |
|
2211 | with self.builtin_trap: | |
2212 | return self.Completer.complete(text, line, cursor_pos) |
|
2212 | return self.Completer.complete(text, line, cursor_pos) | |
2213 |
|
2213 | |||
2214 | def set_custom_completer(self, completer, pos=0): |
|
2214 | def set_custom_completer(self, completer, pos=0) -> None: | |
2215 | """Adds a new custom completer function. |
|
2215 | """Adds a new custom completer function. | |
2216 |
|
2216 | |||
2217 | The position argument (defaults to 0) is the index in the completers |
|
2217 | The position argument (defaults to 0) is the index in the completers | |
2218 |
list where you want the completer to be inserted. |
|
2218 | list where you want the completer to be inserted. | |
|
2219 | ||||
|
2220 | `completer` should have the following signature:: | |||
|
2221 | ||||
|
2222 | def completion(self: Completer, text: string) -> List[str]: | |||
|
2223 | raise NotImplementedError | |||
|
2224 | ||||
|
2225 | It will be bound to the current Completer instance and pass some text | |||
|
2226 | and return a list with current completions to suggest to the user. | |||
|
2227 | """ | |||
2219 |
|
2228 | |||
2220 | newcomp = types.MethodType(completer, self.Completer) |
|
2229 | newcomp = types.MethodType(completer, self.Completer) | |
2221 | self.Completer.custom_matchers.insert(pos,newcomp) |
|
2230 | self.Completer.custom_matchers.insert(pos,newcomp) | |
@@ -3310,6 +3319,9 b' class InteractiveShell(SingletonConfigurable):' | |||||
3310 | False : successful execution. |
|
3319 | False : successful execution. | |
3311 | True : an error occurred. |
|
3320 | True : an error occurred. | |
3312 | """ |
|
3321 | """ | |
|
3322 | # special value to say that anything above is IPython and should be | |||
|
3323 | # hidden. | |||
|
3324 | __tracebackhide__ = "__ipython_bottom__" | |||
3313 | # Set our own excepthook in case the user code tries to call it |
|
3325 | # Set our own excepthook in case the user code tries to call it | |
3314 | # directly, so that the IPython crash handler doesn't get triggered |
|
3326 | # directly, so that the IPython crash handler doesn't get triggered | |
3315 | old_excepthook, sys.excepthook = sys.excepthook, self.excepthook |
|
3327 | old_excepthook, sys.excepthook = sys.excepthook, self.excepthook |
@@ -364,13 +364,25 b' Currently the magic system has the following functions:""",' | |||||
364 |
|
364 | |||
365 | Valid modes: Plain, Context, Verbose, and Minimal. |
|
365 | Valid modes: Plain, Context, Verbose, and Minimal. | |
366 |
|
366 | |||
367 |
If called without arguments, acts as a toggle. |
|
367 | If called without arguments, acts as a toggle. | |
|
368 | ||||
|
369 | When in verbose mode the value --show (and --hide) | |||
|
370 | will respectively show (or hide) frames with ``__tracebackhide__ = | |||
|
371 | True`` value set. | |||
|
372 | """ | |||
368 |
|
373 | |||
369 | def xmode_switch_err(name): |
|
374 | def xmode_switch_err(name): | |
370 | warn('Error changing %s exception modes.\n%s' % |
|
375 | warn('Error changing %s exception modes.\n%s' % | |
371 | (name,sys.exc_info()[1])) |
|
376 | (name,sys.exc_info()[1])) | |
372 |
|
377 | |||
373 | shell = self.shell |
|
378 | shell = self.shell | |
|
379 | if parameter_s.strip() == "--show": | |||
|
380 | shell.InteractiveTB.skip_hidden = False | |||
|
381 | return | |||
|
382 | if parameter_s.strip() == "--hide": | |||
|
383 | shell.InteractiveTB.skip_hidden = True | |||
|
384 | return | |||
|
385 | ||||
374 | new_mode = parameter_s.strip().capitalize() |
|
386 | new_mode = parameter_s.strip().capitalize() | |
375 | try: |
|
387 | try: | |
376 | shell.InteractiveTB.set_mode(mode=new_mode) |
|
388 | shell.InteractiveTB.set_mode(mode=new_mode) |
@@ -4,20 +4,24 b'' | |||||
4 | # Copyright (c) IPython Development Team. |
|
4 | # Copyright (c) IPython Development Team. | |
5 | # Distributed under the terms of the Modified BSD License. |
|
5 | # Distributed under the terms of the Modified BSD License. | |
6 |
|
6 | |||
|
7 | import bdb | |||
|
8 | import builtins | |||
|
9 | import os | |||
7 | import signal |
|
10 | import signal | |
|
11 | import subprocess | |||
8 | import sys |
|
12 | import sys | |
9 | import time |
|
13 | import time | |
10 | import warnings |
|
14 | import warnings | |
|
15 | from subprocess import PIPE, CalledProcessError, check_output | |||
11 | from tempfile import NamedTemporaryFile |
|
16 | from tempfile import NamedTemporaryFile | |
12 | from subprocess import check_output, CalledProcessError, PIPE |
|
17 | from textwrap import dedent | |
13 | import subprocess |
|
|||
14 | from unittest.mock import patch |
|
18 | from unittest.mock import patch | |
15 | import builtins |
|
|||
16 | import bdb |
|
|||
17 |
|
19 | |||
18 | import nose.tools as nt |
|
20 | import nose.tools as nt | |
19 |
|
21 | |||
20 | from IPython.core import debugger |
|
22 | from IPython.core import debugger | |
|
23 | from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE | |||
|
24 | from IPython.testing.decorators import skip_win32 | |||
21 |
|
25 | |||
22 | #----------------------------------------------------------------------------- |
|
26 | #----------------------------------------------------------------------------- | |
23 | # Helper classes, from CPython's Pdb test suite |
|
27 | # Helper classes, from CPython's Pdb test suite | |
@@ -251,3 +255,69 b' def test_interruptible_core_debugger():' | |||||
251 | # implementation would involve a subprocess, but that adds issues with |
|
255 | # implementation would involve a subprocess, but that adds issues with | |
252 | # interrupting subprocesses that are rather complex, so it's simpler |
|
256 | # interrupting subprocesses that are rather complex, so it's simpler | |
253 | # just to do it this way. |
|
257 | # just to do it this way. | |
|
258 | ||||
|
259 | @skip_win32 | |||
|
260 | def test_xmode_skip(): | |||
|
261 | """that xmode skip frames | |||
|
262 | ||||
|
263 | Not as a doctest as pytest does not run doctests. | |||
|
264 | """ | |||
|
265 | import pexpect | |||
|
266 | env = os.environ.copy() | |||
|
267 | env["IPY_TEST_SIMPLE_PROMPT"] = "1" | |||
|
268 | ||||
|
269 | child = pexpect.spawn( | |||
|
270 | sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env | |||
|
271 | ) | |||
|
272 | child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE | |||
|
273 | ||||
|
274 | child.expect("IPython") | |||
|
275 | child.expect("\n") | |||
|
276 | child.expect_exact("In [1]") | |||
|
277 | ||||
|
278 | block = dedent( | |||
|
279 | """ | |||
|
280 | def f(): | |||
|
281 | __tracebackhide__ = True | |||
|
282 | g() | |||
|
283 | ||||
|
284 | def g(): | |||
|
285 | raise ValueError | |||
|
286 | ||||
|
287 | f() | |||
|
288 | """ | |||
|
289 | ) | |||
|
290 | ||||
|
291 | for line in block.splitlines(): | |||
|
292 | child.sendline(line) | |||
|
293 | child.expect_exact(line) | |||
|
294 | child.expect_exact("skipping") | |||
|
295 | ||||
|
296 | block = dedent( | |||
|
297 | """ | |||
|
298 | def f(): | |||
|
299 | __tracebackhide__ = True | |||
|
300 | g() | |||
|
301 | ||||
|
302 | def g(): | |||
|
303 | from IPython.core.debugger import set_trace | |||
|
304 | set_trace() | |||
|
305 | ||||
|
306 | f() | |||
|
307 | """ | |||
|
308 | ) | |||
|
309 | ||||
|
310 | for line in block.splitlines(): | |||
|
311 | child.sendline(line) | |||
|
312 | child.expect_exact(line) | |||
|
313 | ||||
|
314 | child.expect("ipdb>") | |||
|
315 | child.sendline("w") | |||
|
316 | child.expect("hidden") | |||
|
317 | child.expect("ipdb>") | |||
|
318 | child.sendline("skip_hidden false") | |||
|
319 | child.sendline("w") | |||
|
320 | child.expect("__traceba") | |||
|
321 | child.expect("ipdb>") | |||
|
322 | ||||
|
323 | child.close() |
@@ -579,12 +579,15 b' class VerboseTB(TBTools):' | |||||
579 | self.check_cache = check_cache |
|
579 | self.check_cache = check_cache | |
580 |
|
580 | |||
581 | self.debugger_cls = debugger_cls or debugger.Pdb |
|
581 | self.debugger_cls = debugger_cls or debugger.Pdb | |
|
582 | self.skip_hidden = True | |||
582 |
|
583 | |||
583 | def format_record(self, frame_info): |
|
584 | def format_record(self, frame_info): | |
584 | """Format a single stack frame""" |
|
585 | """Format a single stack frame""" | |
585 | Colors = self.Colors # just a shorthand + quicker name lookup |
|
586 | Colors = self.Colors # just a shorthand + quicker name lookup | |
586 | ColorsNormal = Colors.Normal # used a lot |
|
587 | ColorsNormal = Colors.Normal # used a lot | |
587 |
|
588 | |||
|
589 | ||||
|
590 | ||||
588 | if isinstance(frame_info, stack_data.RepeatedFrames): |
|
591 | if isinstance(frame_info, stack_data.RepeatedFrames): | |
589 | return ' %s[... skipping similar frames: %s]%s\n' % ( |
|
592 | return ' %s[... skipping similar frames: %s]%s\n' % ( | |
590 | Colors.excName, frame_info.description, ColorsNormal) |
|
593 | Colors.excName, frame_info.description, ColorsNormal) | |
@@ -699,7 +702,23 b' class VerboseTB(TBTools):' | |||||
699 | head = self.prepare_header(etype, self.long_header) |
|
702 | head = self.prepare_header(etype, self.long_header) | |
700 | records = self.get_records(etb, number_of_lines_of_context, tb_offset) |
|
703 | records = self.get_records(etb, number_of_lines_of_context, tb_offset) | |
701 |
|
704 | |||
702 | frames = list(map(self.format_record, records)) |
|
705 | frames = [] | |
|
706 | skipped = 0 | |||
|
707 | for r in records: | |||
|
708 | if not isinstance(r, stack_data.RepeatedFrames) and self.skip_hidden: | |||
|
709 | if r.frame.f_locals.get("__tracebackhide__", 0): | |||
|
710 | skipped += 1 | |||
|
711 | continue | |||
|
712 | if skipped: | |||
|
713 | Colors = self.Colors # just a shorthand + quicker name lookup | |||
|
714 | ColorsNormal = Colors.Normal # used a lot | |||
|
715 | frames.append( | |||
|
716 | " %s[... skipping hidden %s frame]%s\n" | |||
|
717 | % (Colors.excName, skipped, ColorsNormal) | |||
|
718 | ) | |||
|
719 | skipped = 0 | |||
|
720 | frames.append(self.format_record(r)) | |||
|
721 | ||||
703 |
|
722 | |||
704 | formatted_exception = self.format_exception(etype, evalue) |
|
723 | formatted_exception = self.format_exception(etype, evalue) | |
705 | if records: |
|
724 | if records: |
@@ -41,6 +41,16 b' class TerminalPdb(Pdb):' | |||||
41 | global_namespace={}, |
|
41 | global_namespace={}, | |
42 | parent=self.shell, |
|
42 | parent=self.shell, | |
43 | ) |
|
43 | ) | |
|
44 | # add a completer for all the do_ methods | |||
|
45 | methods_names = [m[3:] for m in dir(self) if m.startswith("do_")] | |||
|
46 | ||||
|
47 | def gen_comp(self, text): | |||
|
48 | return [m for m in methods_names if m.startswith(text)] | |||
|
49 | import types | |||
|
50 | newcomp = types.MethodType(gen_comp, compl) | |||
|
51 | compl.custom_matchers.insert(0, newcomp) | |||
|
52 | # end add completer. | |||
|
53 | ||||
44 | self._ptcomp = IPythonPTCompleter(compl) |
|
54 | self._ptcomp = IPythonPTCompleter(compl) | |
45 |
|
55 | |||
46 | options = dict( |
|
56 | options = dict( |
@@ -4,13 +4,6 b' matrix:' | |||||
4 |
|
4 | |||
5 | environment: |
|
5 | environment: | |
6 | matrix: |
|
6 | matrix: | |
7 | - PYTHON: "C:\\Python36" |
|
|||
8 | PYTHON_VERSION: "3.6.x" |
|
|||
9 | PYTHON_ARCH: "32" |
|
|||
10 |
|
||||
11 | - PYTHON: "C:\\Python36-x64" |
|
|||
12 | PYTHON_VERSION: "3.6.x" |
|
|||
13 | PYTHON_ARCH: "64" |
|
|||
14 |
|
7 | |||
15 | - PYTHON: "C:\\Python37-x64" |
|
8 | - PYTHON: "C:\\Python37-x64" | |
16 | PYTHON_VERSION: "3.7.x" |
|
9 | PYTHON_VERSION: "3.7.x" |
General Comments 0
You need to be logged in to leave comments.
Login now