GeezerVison is showing github.com — Fix issues when running without debugging and debugged code terminate… · keyombin/vscode-python@a6b49d2
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: package.json +1,572-1,628Lines changed: 1572 additions & 1628 deletions Large diffs are not rendered by default. Collapse file Copy file name to clipboard +146Lines changed: 146 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +# Copyright (c) Microsoft Corporation. All rights reserved. | | 2 | +# Licensed under the MIT License. | | 3 | + | | 4 | +"""Run a block of code or Python file.""" | | 5 | + | | 6 | +import sys | | 7 | +import os.path | | 8 | +import traceback | | 9 | +import time | | 10 | +import socket | | 11 | +try: | | 12 | + import visualstudio_py_util as _vspu | | 13 | +except: | | 14 | + traceback.print_exc() | | 15 | + print("""Internal error detected. Please copy the above traceback and report at | | 16 | +https://github.com/Microsoft/vscode-python/issues""") | | 17 | + sys.exit(1) | | 18 | + | | 19 | +LAST = _vspu.to_bytes('LAST') | | 20 | +OUTP = _vspu.to_bytes('OUTP') | | 21 | +LOAD = _vspu.to_bytes('LOAD') | | 22 | + | | 23 | +def parse_argv(): | | 24 | + """Parses arguments for use with the launcher. | | 25 | + Arguments are: | | 26 | + 1. Working directory. | | 27 | + 2. VS debugger port to connect to. | | 28 | + 3. GUID for the debug session. | | 29 | + 4. Debug options (not used). | | 30 | + 5. '-m' or '-c' to override the default run-as mode. [optional]. | | 31 | + 6. Startup script name. | | 32 | + 7. Script arguments. | | 33 | + """ | | 34 | + | | 35 | + # Change to directory we expected to start from. | | 36 | + os.chdir(sys.argv[1]) | | 37 | + | | 38 | + port_num = int(sys.argv[2]) | | 39 | + debug_id = sys.argv[3] | | 40 | + | | 41 | + del sys.argv[:5] | | 42 | + | | 43 | + # Set run_as mode appropriately | | 44 | + run_as = 'script' | | 45 | + if sys.argv and sys.argv[0] == '-m': | | 46 | + run_as = 'module' | | 47 | + del sys.argv[0] | | 48 | + elif sys.argv and sys.argv[0] == '-c': | | 49 | + run_as = 'code' | | 50 | + del sys.argv[0] | | 51 | + | | 52 | + # Preserve filename before we del sys. | | 53 | + filename = sys.argv[0] | | 54 | + | | 55 | + # Fix sys.path to be the script file dir. | | 56 | + sys.path[0] = '' | | 57 | + | | 58 | + pid = os.getpid() | | 59 | + | | 60 | + return (filename, port_num, debug_id, pid, run_as) | | 61 | + | | 62 | +def run(file, port_num, debug_id, pid, run_as='script'): | | 63 | + attach_process(port_num, pid, debug_id) | | 64 | + | | 65 | + # Now execute main file. | | 66 | + globals_obj = {'__name__': '__main__'} | | 67 | + | | 68 | + try: | | 69 | + if run_as == 'module': | | 70 | + _vspu.exec_module(file, globals_obj) | | 71 | + elif run_as == 'code': | | 72 | + _vspu.exec_code(file, '<string>', globals_obj) | | 73 | + else: | | 74 | + _vspu.exec_file(file, globals_obj) | | 75 | + except: | | 76 | + exc_type, exc_value, exc_tb = sys.exc_info() | | 77 | + handle_exception(exc_type, exc_value, exc_tb) | | 78 | + | | 79 | + _vspu.write_bytes(conn, LAST) | | 80 | + # Wait for message to be received by debugger. | | 81 | + time.sleep(0.5) | | 82 | + | | 83 | + | | 84 | +def attach_process(port_num, pid, debug_id): | | 85 | + global conn | | 86 | + for i in xrange(50): | | 87 | + try: | | 88 | + conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | | 89 | + conn.connect(('127.0.0.1', port_num)) | | 90 | + # Initial handshake. | | 91 | + _vspu.write_string(conn, debug_id) | | 92 | + _vspu.write_int(conn, 0) | | 93 | + _vspu.write_int(conn, pid) | | 94 | + | | 95 | + # Notify debugger that process has launched. | | 96 | + _vspu.write_bytes(conn, LOAD) | | 97 | + _vspu.write_int(conn, 0) | | 98 | + break | | 99 | + except: | | 100 | + time.sleep(50./1000) | | 101 | + else: | | 102 | + raise Exception('failed to attach') | | 103 | + | | 104 | + | | 105 | +def handle_exception(exc_type, exc_value, exc_tb): | | 106 | + # Specifies list of files not to display in stack trace. | | 107 | + do_not_debug = [__file__, _vspu.__file__] | | 108 | + if sys.version_info >= (3, 3): | | 109 | + do_not_debug.append('<frozen importlib._bootstrap>') | | 110 | + if sys.version_info >= (3, 5): | | 111 | + do_not_debug.append('<frozen importlib._bootstrap_external>') | | 112 | + | | 113 | + # Remove debugger frames from the top and bottom of the traceback. | | 114 | + tb = traceback.extract_tb(exc_tb) | | 115 | + for i in [0, -1]: | | 116 | + while tb: | | 117 | + frame_file = path.normcase(tb[i][0]) | | 118 | + if not any(is_same_py_file(frame_file, f) for f in do_not_debug): | | 119 | + break | | 120 | + del tb[i] | | 121 | + | | 122 | + # Print the traceback. | | 123 | + if tb: | | 124 | + sys.stderr.write('Traceback (most recent call last):') | | 125 | + for out in traceback.format_list(tb): | | 126 | + sys.stderr.write(out) | | 127 | + sys.stderr.flush() | | 128 | + | | 129 | + # Print the exception. | | 130 | + for out in traceback.format_exception_only(exc_type, exc_value): | | 131 | + sys.stderr.write(out) | | 132 | + sys.stderr.flush() | | 133 | + | | 134 | + | | 135 | +def is_same_py_file(file_1, file_2): | | 136 | + """Compares 2 filenames accounting for .pyc files.""" | | 137 | + if file_1.endswith('.pyc') or file_1.endswith('.pyo'): | | 138 | + file_1 = file_1[:-1] | | 139 | + if file_2.endswith('.pyc') or file_2.endswith('.pyo'): | | 140 | + file_2 = file_2[:-1] | | 141 | + | | 142 | + return file_1 == file_2 | | 143 | + | | 144 | +if __name__ == '__main__': | | 145 | + filename, port_num, debug_id, pid, run_as = parse_argv() | | 146 | + run(filename, port_num, debug_id, pid, run_as) |
Collapse file Copy file name to clipboard +37-20Lines changed: 37 additions & 20 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | import * as fs from 'fs'; | 2 | 2 | import * as path from 'path'; | 3 | 3 |
| 4 | | -export function parseEnvFile(envFile: string): any { | | 4 | +type EnvVars = Object & { [key: string]: string }; | | 5 | + | | 6 | +export function parseEnvFile(envFile: string, mergeWithProcessEnvVars: boolean = true): EnvVars { | 5 | 7 | const buffer = fs.readFileSync(envFile, 'utf8'); | 6 | 8 | const env = {}; | 7 | 9 | buffer.split('\n').forEach(line => { | @@ -14,28 +16,43 @@ export function parseEnvFile(envFile: string): any { | 14 | 16 | env[r[1]] = value.replace(/(^['"]|['"]$)/g, ''); | 15 | 17 | } | 16 | 18 | }); | 17 | | - return mergeEnvVariables(env); | | 19 | + return mergeWithProcessEnvVars ? mergeEnvVariables(env, process.env) : mergePythonPath(env, process.env.PYTHONPATH); | 18 | 20 | } | 19 | 21 |
| 20 | | -export function mergeEnvVariables(newVariables: { [key: string]: string }, mergeWith: any = process.env): any { | 21 | | - for (let setting in mergeWith) { | 22 | | - if (setting === 'PYTHONPATH') { | 23 | | - let PYTHONPATH: string = newVariables['PYTHONPATH']; | 24 | | - if (typeof PYTHONPATH !== 'string') { | 25 | | - PYTHONPATH = ''; | 26 | | - } | 27 | | - if (mergeWith['PYTHONPATH']) { | 28 | | - PYTHONPATH += (PYTHONPATH.length > 0 ? path.delimiter : '') + mergeWith['PYTHONPATH']; | 29 | | - } | 30 | | - if (PYTHONPATH.length > 0) { | 31 | | - newVariables[setting] = PYTHONPATH; | 32 | | - } | 33 | | - continue; | 34 | | - } | 35 | | - if (!newVariables[setting]) { | 36 | | - newVariables[setting] = mergeWith[setting]; | | 22 | +/** | | 23 | + * Merge the target environment variables into the source. | | 24 | + * Note: The source variables are modified and returned (i.e. it modifies value passed in). | | 25 | + * @export | | 26 | + * @param {EnvVars} targetEnvVars target environment variables. | | 27 | + * @param {EnvVars} [sourceEnvVars=process.env] source environment variables (defaults to current process variables). | | 28 | + * @returns {EnvVars} | | 29 | + */ | | 30 | +export function mergeEnvVariables(targetEnvVars: EnvVars, sourceEnvVars: EnvVars = process.env): EnvVars { | | 31 | + Object.keys(sourceEnvVars).forEach(setting => { | | 32 | + if (targetEnvVars[setting] === undefined) { | | 33 | + targetEnvVars[setting] = sourceEnvVars[setting]; | 37 | 34 | } | | 35 | + }); | | 36 | + return mergePythonPath(targetEnvVars, sourceEnvVars.PYTHONPATH); | | 37 | +} | | 38 | + | | 39 | +/** | | 40 | + * Merge the target PYTHONPATH value into the env variables passed. | | 41 | + * Note: The env variables passed in are modified and returned (i.e. it modifies value passed in). | | 42 | + * @export | | 43 | + * @param {EnvVars} env target environment variables. | | 44 | + * @param {string | undefined} [currentPythonPath] PYTHONPATH value. | | 45 | + * @returns {EnvVars} | | 46 | + */ | | 47 | +export function mergePythonPath(env: EnvVars, currentPythonPath: string | undefined): EnvVars { | | 48 | + if (typeof currentPythonPath !== 'string' || currentPythonPath.length === 0) { | | 49 | + return env; | 38 | 50 | } | 39 | 51 |
| 40 | | - return newVariables; | | 52 | + if (typeof env.PYTHONPATH === 'string' && env.PYTHONPATH.length > 0) { | | 53 | + env.PYTHONPATH = env.PYTHONPATH + path.delimiter + currentPythonPath; | | 54 | + } else { | | 55 | + env.PYTHONPATH = currentPythonPath; | | 56 | + } | | 57 | + return env; | 41 | 58 | } |
Collapse file Copy file name to clipboardExpand all lines: src/client/debugger/Common/Contracts.ts +3-2Lines changed: 3 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | "use strict"; | 2 | 2 | import * as net from "net"; | | 3 | +import { ChildProcess } from 'child_process'; | 3 | 4 | import { DebugProtocol } from "vscode-debugprotocol"; | 4 | 5 | import { OutputEvent } from "vscode-debugadapter"; | 5 | 6 |
| @@ -49,7 +50,6 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum | 49 | 50 | stopOnEntry?: boolean; | 50 | 51 | args: string[]; | 51 | 52 | applicationType?: string; | 52 | | - externalConsole?: boolean; | 53 | 53 | cwd?: string; | 54 | 54 | debugOptions?: string[]; | 55 | 55 | env?: Object; | @@ -103,8 +103,9 @@ export enum PythonEvaluationResultFlags { | 103 | 103 | } | 104 | 104 |
| 105 | 105 | export interface IPythonProcess extends NodeJS.EventEmitter { | 106 | | - Connect(buffer: Buffer, socket: net.Socket, isRemoteProcess: boolean); | | 106 | + Connect(buffer: Buffer, socket: net.Socket, isRemoteProcess: boolean): boolean; | 107 | 107 | HandleIncomingData(buffer: Buffer); | | 108 | + attach(proc: ChildProcess): void; | 108 | 109 | Detach(); | 109 | 110 | Kill(); | 110 | 111 | SendStepInto(threadId: number); | |
Collapse file Copy file name to clipboardExpand all lines: src/client/debugger/Common/Utils.ts +34-40Lines changed: 34 additions & 40 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | | -"use strict"; | | 1 | +'use strict'; | 2 | 2 |
| 3 | | -import { IPythonProcess, IPythonThread, IPythonModule, IPythonEvaluationResult } from "./Contracts"; | 4 | | -import * as path from "path"; | 5 | | -import * as fs from 'fs'; | 6 | 3 | import * as child_process from 'child_process'; | 7 | | -import { mergeEnvVariables, parseEnvFile } from '../../common/envFileParser'; | | 4 | +import * as fs from 'fs'; | | 5 | +import * as path from 'path'; | 8 | 6 | import * as untildify from 'untildify'; | | 7 | +import { mergeEnvVariables, mergePythonPath, parseEnvFile } from '../../common/envFileParser'; | | 8 | +import { IPythonEvaluationResult, IPythonModule, IPythonProcess, IPythonThread } from './Contracts'; | 9 | 9 |
| 10 | 10 | export const IS_WINDOWS = /^win/.test(process.platform); | 11 | 11 | export const PATH_VARIABLE_NAME = IS_WINDOWS ? 'Path' : 'PATH'; | @@ -38,7 +38,7 @@ export function validatePathSync(filePath: string): boolean { | 38 | 38 | return exists; | 39 | 39 | } | 40 | 40 |
| 41 | | -export function CreatePythonThread(id: number, isWorker: boolean, process: IPythonProcess, name: string = ""): IPythonThread { | | 41 | +export function CreatePythonThread(id: number, isWorker: boolean, process: IPythonProcess, name: string = ''): IPythonThread { | 42 | 42 | return { | 43 | 43 | IsWorkerThread: isWorker, | 44 | 44 | Process: process, | @@ -50,15 +50,13 @@ export function CreatePythonThread(id: number, isWorker: boolean, process: IPyth | 50 | 50 |
| 51 | 51 | export function CreatePythonModule(id: number, fileName: string): IPythonModule { | 52 | 52 | let name = fileName; | 53 | | - if (typeof fileName === "string") { | | 53 | + if (typeof fileName === 'string') { | 54 | 54 | try { | 55 | 55 | name = path.basename(fileName); | 56 | | - } | 57 | | - catch (ex) { | 58 | | - } | 59 | | - } | 60 | | - else { | 61 | | - name = ""; | | 56 | + // tslint:disable-next-line:no-empty | | 57 | + } catch { } | | 58 | + } else { | | 59 | + name = ''; | 62 | 60 | } | 63 | 61 |
| 64 | 62 | return { | @@ -74,7 +72,7 @@ export function FixupEscapedUnicodeChars(value: string): string { | 74 | 72 |
| 75 | 73 | export function getPythonExecutable(pythonPath: string): string { | 76 | 74 | pythonPath = untildify(pythonPath); | 77 | | - // If only 'python' | | 75 | + // If only 'python'. | 78 | 76 | if (pythonPath === 'python' || | 79 | 77 | pythonPath.indexOf(path.sep) === -1 || | 80 | 78 | path.basename(pythonPath) === path.dirname(pythonPath)) { | @@ -84,21 +82,20 @@ export function getPythonExecutable(pythonPath: string): string { | 84 | 82 | if (isValidPythonPath(pythonPath)) { | 85 | 83 | return pythonPath; | 86 | 84 | } | 87 | | - // Keep python right on top, for backwards compatibility | | 85 | + // Keep python right on top, for backwards compatibility. | 88 | 86 | const KnownPythonExecutables = ['python', 'python4', 'python3.6', 'python3.5', 'python3', 'python2.7', 'python2']; | 89 | 87 |
| 90 | 88 | for (let executableName of KnownPythonExecutables) { | 91 | | - // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows' | | 89 | + // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'. | 92 | 90 | if (IS_WINDOWS) { | 93 | | - executableName = executableName + '.exe'; | | 91 | + executableName = `${executableName}.exe`; | 94 | 92 | if (isValidPythonPath(path.join(pythonPath, executableName))) { | 95 | 93 | return path.join(pythonPath, executableName); | 96 | 94 | } | 97 | 95 | if (isValidPythonPath(path.join(pythonPath, 'scripts', executableName))) { | 98 | 96 | return path.join(pythonPath, 'scripts', executableName); | 99 | 97 | } | 100 | | - } | 101 | | - else { | | 98 | + } else { | 102 | 99 | if (isValidPythonPath(path.join(pythonPath, executableName))) { | 103 | 100 | return path.join(pythonPath, executableName); | 104 | 101 | } | @@ -113,39 +110,36 @@ export function getPythonExecutable(pythonPath: string): string { | 113 | 110 |
| 114 | 111 | function isValidPythonPath(pythonPath): boolean { | 115 | 112 | try { | 116 | | - let output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); | | 113 | + const output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); | 117 | 114 | return output.startsWith('1234'); | 118 | | - } | 119 | | - catch (ex) { | | 115 | + } catch { | 120 | 116 | return false; | 121 | 117 | } | 122 | 118 | } | 123 | 119 |
| | 120 | +type EnvVars = Object & { [key: string]: string }; | 124 | 121 |
| 125 | | -export function getCustomEnvVars(envVars: any, envFile: string): any { | 126 | | - let envFileVars = null; | | 122 | +export function getCustomEnvVars(envVars: Object, envFile: string, mergeWithProcessEnvVars: boolean = true): EnvVars { | | 123 | + let envFileVars: EnvVars = null; | 127 | 124 | if (typeof envFile === 'string' && envFile.length > 0 && fs.existsSync(envFile)) { | 128 | 125 | try { | 129 | | - envFileVars = parseEnvFile(envFile); | 130 | | - } | 131 | | - catch (ex) { | | 126 | + envFileVars = parseEnvFile(envFile, mergeWithProcessEnvVars); | | 127 | + } catch (ex) { | 132 | 128 | console.error('Failed to load env file'); | 133 | 129 | console.error(ex); | 134 | 130 | } | 135 | 131 | } | 136 | | - let configVars = null; | 137 | | - if (envVars && Object.keys(envVars).length > 0 && envFileVars) { | 138 | | - configVars = mergeEnvVariables(envVars, envFileVars); | 139 | | - } | 140 | | - if (envVars && Object.keys(envVars).length > 0) { | 141 | | - configVars = envVars; | 142 | | - } | 143 | | - if (envFileVars) { | 144 | | - configVars = envFileVars; | | 132 | + if (envFileVars && Object.keys(envFileVars).length > 0) { | | 133 | + if (!envVars || Object.keys(envVars).length === 0) { | | 134 | + return envFileVars; | | 135 | + } else { | | 136 | + envVars = envVars || {}; | | 137 | + return mergeEnvVariables(envVars as EnvVars, envFileVars); | | 138 | + } | 145 | 139 | } | 146 | | - if (configVars && typeof configVars === 'object' && Object.keys(configVars).length > 0) { | 147 | | - return configVars; | | 140 | + if (!envVars || Object.keys(envVars).length === 0) { | | 141 | + return null; | 148 | 142 | } | 149 | 143 |
| 150 | | - return null; | 151 | | -} | | 144 | + return mergePythonPath(envVars as EnvVars, process.env.PYTHONPATH); | | 145 | +} |
Collapse file Copy file name to clipboardExpand all lines: src/client/debugger/DebugClients/DebugClient.ts +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -14,7 +14,7 @@ export enum DebugType { | 14 | 14 | } | 15 | 15 | export abstract class DebugClient extends EventEmitter { | 16 | 16 | protected debugSession: DebugSession; | 17 | | - constructor(args: any, debugSession: DebugSession) { | | 17 | + constructor(protected args: any, debugSession: DebugSession) { | 18 | 18 | super(); | 19 | 19 | this.debugSession = debugSession; | 20 | 20 | } | |
|