Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issues when running without debugging and debugged code terminates #249

Merged
merged 27 commits into from
Nov 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ecc1ca9
Fix Microsoft/vscode#37627 (#1368)
octref Nov 3, 2017
7c5778c
Version 0.7.0 of extension (#1381)
DonJayamanne Nov 9, 2017
9d1bf82
Update README.md
DonJayamanne Nov 9, 2017
ffba179
Update README.md
DonJayamanne Nov 9, 2017
905c713
sync fork with upstream
DonJayamanne Nov 10, 2017
acc2109
fix readme
DonJayamanne Nov 10, 2017
d470523
Merge branch 'master' of https://github.com/Microsoft/vscode-python
DonJayamanne Nov 16, 2017
d32a546
run without debugging
DonJayamanne Nov 15, 2017
3f14d0a
send terminated event when process ends and fix linters
DonJayamanne Nov 15, 2017
f37803f
gracefully handle program termination
DonJayamanne Nov 16, 2017
d69940e
use vscode infrastructure to launch code in terminals
DonJayamanne Nov 16, 2017
d392e8b
merged upstream
DonJayamanne Nov 16, 2017
11c9514
merged
DonJayamanne Nov 16, 2017
38befc0
ensure to check if launchArgs is not null
DonJayamanne Nov 16, 2017
bea91fc
ensure to check if launchArgs is not null
DonJayamanne Nov 16, 2017
0bb9054
ensure to check if launchArgs is not null
DonJayamanne Nov 16, 2017
a105a20
fix code review comments
DonJayamanne Nov 17, 2017
8b2fd7c
fixed code review comments
DonJayamanne Nov 17, 2017
5a7d21e
Merge branch 'master' into RunWithoutDebug
DonJayamanne Nov 17, 2017
acd90c5
Merge branch 'master' into RunWithoutDebug
DonJayamanne Nov 17, 2017
09b3359
fixes #250, do not copy all env variables
DonJayamanne Nov 17, 2017
9616eef
bug fix, launch using vscode and then fallback
DonJayamanne Nov 17, 2017
f9fe2a8
fix linter issue
DonJayamanne Nov 17, 2017
79aa7c1
fix code review issues
DonJayamanne Nov 22, 2017
984602e
running without debugging without workspaces
DonJayamanne Nov 22, 2017
d65eece
Merge branch 'master' into RunWithoutDebug
DonJayamanne Nov 22, 2017
650ec17
Merge branch 'master' into RunWithoutDebug
DonJayamanne Nov 22, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,200 changes: 1,572 additions & 1,628 deletions package.json

Large diffs are not rendered by default.

146 changes: 146 additions & 0 deletions pythonFiles/PythonTools/visualstudio_py_launcher_nodebug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"""Run a block of code or Python file."""

"""Run a block of code or Python file."""

import sys
import os.path
import traceback
import time
import socket
try:
import visualstudio_py_util as _vspu
except:
traceback.print_exc()
print("""Internal error detected. Please copy the above traceback and report at
https://github.com/Microsoft/vscode-python/issues""")
sys.exit(1)

LAST = _vspu.to_bytes('LAST')
OUTP = _vspu.to_bytes('OUTP')
LOAD = _vspu.to_bytes('LOAD')

def parse_argv():
"""Parses arguments for use with the launcher.
Arguments are:
1. Working directory.
2. VS debugger port to connect to.
3. GUID for the debug session.
4. Debug options (not used).
5. '-m' or '-c' to override the default run-as mode. [optional].
6. Startup script name.
7. Script arguments.
"""

# Change to directory we expected to start from.
os.chdir(sys.argv[1])

port_num = int(sys.argv[2])
debug_id = sys.argv[3]

del sys.argv[:5]

# Set run_as mode appropriately
run_as = 'script'
if sys.argv and sys.argv[0] == '-m':
run_as = 'module'
del sys.argv[0]
elif sys.argv and sys.argv[0] == '-c':
run_as = 'code'
del sys.argv[0]

# Preserve filename before we del sys.
filename = sys.argv[0]

# Fix sys.path to be the script file dir.
sys.path[0] = ''

pid = os.getpid()

return (filename, port_num, debug_id, pid, run_as)

def run(file, port_num, debug_id, pid, run_as='script'):
attach_process(port_num, pid, debug_id)

# Now execute main file.
globals_obj = {'__name__': '__main__'}

try:
if run_as == 'module':
_vspu.exec_module(file, globals_obj)
elif run_as == 'code':
_vspu.exec_code(file, '<string>', globals_obj)
else:
_vspu.exec_file(file, globals_obj)
except:
exc_type, exc_value, exc_tb = sys.exc_info()
handle_exception(exc_type, exc_value, exc_tb)

_vspu.write_bytes(conn, LAST)
# Wait for message to be received by debugger.
time.sleep(0.5)


def attach_process(port_num, pid, debug_id):
global conn
for i in xrange(50):
try:
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn.connect(('127.0.0.1', port_num))
# Initial handshake.
_vspu.write_string(conn, debug_id)
_vspu.write_int(conn, 0)
_vspu.write_int(conn, pid)

# Notify debugger that process has launched.
_vspu.write_bytes(conn, LOAD)
_vspu.write_int(conn, 0)
break
except:
time.sleep(50./1000)
else:
raise Exception('failed to attach')


def handle_exception(exc_type, exc_value, exc_tb):
# Specifies list of files not to display in stack trace.
do_not_debug = [__file__, _vspu.__file__]
if sys.version_info >= (3, 3):
do_not_debug.append('<frozen importlib._bootstrap>')
if sys.version_info >= (3, 5):
do_not_debug.append('<frozen importlib._bootstrap_external>')

# Remove debugger frames from the top and bottom of the traceback.
tb = traceback.extract_tb(exc_tb)
for i in [0, -1]:
while tb:
frame_file = path.normcase(tb[i][0])
if not any(is_same_py_file(frame_file, f) for f in do_not_debug):
break
del tb[i]

# Print the traceback.
if tb:
sys.stderr.write('Traceback (most recent call last):')
for out in traceback.format_list(tb):
sys.stderr.write(out)
sys.stderr.flush()

# Print the exception.
for out in traceback.format_exception_only(exc_type, exc_value):
sys.stderr.write(out)
sys.stderr.flush()


def is_same_py_file(file_1, file_2):
"""Compares 2 filenames accounting for .pyc files."""
if file_1.endswith('.pyc') or file_1.endswith('.pyo'):
file_1 = file_1[:-1]
if file_2.endswith('.pyc') or file_2.endswith('.pyo'):
file_2 = file_2[:-1]

return file_1 == file_2

if __name__ == '__main__':
filename, port_num, debug_id, pid, run_as = parse_argv()
run(filename, port_num, debug_id, pid, run_as)
57 changes: 37 additions & 20 deletions src/client/common/envFileParser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as fs from 'fs';
import * as path from 'path';

export function parseEnvFile(envFile: string): any {
type EnvVars = Object & { [key: string]: string };

export function parseEnvFile(envFile: string, mergeWithProcessEnvVars: boolean = true): EnvVars {
const buffer = fs.readFileSync(envFile, 'utf8');
const env = {};
buffer.split('\n').forEach(line => {
Expand All @@ -14,28 +16,43 @@ export function parseEnvFile(envFile: string): any {
env[r[1]] = value.replace(/(^['"]|['"]$)/g, '');
}
});
return mergeEnvVariables(env);
return mergeWithProcessEnvVars ? mergeEnvVariables(env, process.env) : mergePythonPath(env, process.env.PYTHONPATH);
}

export function mergeEnvVariables(newVariables: { [key: string]: string }, mergeWith: any = process.env): any {
for (let setting in mergeWith) {
if (setting === 'PYTHONPATH') {
let PYTHONPATH: string = newVariables['PYTHONPATH'];
if (typeof PYTHONPATH !== 'string') {
PYTHONPATH = '';
}
if (mergeWith['PYTHONPATH']) {
PYTHONPATH += (PYTHONPATH.length > 0 ? path.delimiter : '') + mergeWith['PYTHONPATH'];
}
if (PYTHONPATH.length > 0) {
newVariables[setting] = PYTHONPATH;
}
continue;
}
if (!newVariables[setting]) {
newVariables[setting] = mergeWith[setting];
/**
* Merge the target environment variables into the source.
* Note: The source variables are modified and returned (i.e. it modifies value passed in).
* @export
* @param {EnvVars} targetEnvVars target environment variables.
* @param {EnvVars} [sourceEnvVars=process.env] source environment variables (defaults to current process variables).
* @returns {EnvVars}
*/
export function mergeEnvVariables(targetEnvVars: EnvVars, sourceEnvVars: EnvVars = process.env): EnvVars {
Object.keys(sourceEnvVars).forEach(setting => {
if (targetEnvVars[setting] === undefined) {
targetEnvVars[setting] = sourceEnvVars[setting];
}
});
return mergePythonPath(targetEnvVars, sourceEnvVars.PYTHONPATH);
}

/**
* Merge the target PYTHONPATH value into the env variables passed.
* Note: The env variables passed in are modified and returned (i.e. it modifies value passed in).
* @export
* @param {EnvVars} env target environment variables.
* @param {string | undefined} [currentPythonPath] PYTHONPATH value.
* @returns {EnvVars}
*/
export function mergePythonPath(env: EnvVars, currentPythonPath: string | undefined): EnvVars {
if (typeof currentPythonPath !== 'string' || currentPythonPath.length === 0) {
return env;
}

return newVariables;
if (typeof env.PYTHONPATH === 'string' && env.PYTHONPATH.length > 0) {
env.PYTHONPATH = env.PYTHONPATH + path.delimiter + currentPythonPath;
} else {
env.PYTHONPATH = currentPythonPath;
}
return env;
}
5 changes: 3 additions & 2 deletions src/client/debugger/Common/Contracts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use strict";
import * as net from "net";
import { ChildProcess } from 'child_process';
import { DebugProtocol } from "vscode-debugprotocol";
import { OutputEvent } from "vscode-debugadapter";

Expand Down Expand Up @@ -49,7 +50,6 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum
stopOnEntry?: boolean;
args: string[];
applicationType?: string;
externalConsole?: boolean;
cwd?: string;
debugOptions?: string[];
env?: Object;
Expand Down Expand Up @@ -103,8 +103,9 @@ export enum PythonEvaluationResultFlags {
}

export interface IPythonProcess extends NodeJS.EventEmitter {
Connect(buffer: Buffer, socket: net.Socket, isRemoteProcess: boolean);
Connect(buffer: Buffer, socket: net.Socket, isRemoteProcess: boolean): boolean;
HandleIncomingData(buffer: Buffer);
attach(proc: ChildProcess): void;
Detach();
Kill();
SendStepInto(threadId: number);
Expand Down
74 changes: 34 additions & 40 deletions src/client/debugger/Common/Utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use strict";
'use strict';

import { IPythonProcess, IPythonThread, IPythonModule, IPythonEvaluationResult } from "./Contracts";
import * as path from "path";
import * as fs from 'fs';
import * as child_process from 'child_process';
import { mergeEnvVariables, parseEnvFile } from '../../common/envFileParser';
import * as fs from 'fs';
import * as path from 'path';
import * as untildify from 'untildify';
import { mergeEnvVariables, mergePythonPath, parseEnvFile } from '../../common/envFileParser';
import { IPythonEvaluationResult, IPythonModule, IPythonProcess, IPythonThread } from './Contracts';

export const IS_WINDOWS = /^win/.test(process.platform);
export const PATH_VARIABLE_NAME = IS_WINDOWS ? 'Path' : 'PATH';
Expand Down Expand Up @@ -38,7 +38,7 @@ export function validatePathSync(filePath: string): boolean {
return exists;
}

export function CreatePythonThread(id: number, isWorker: boolean, process: IPythonProcess, name: string = ""): IPythonThread {
export function CreatePythonThread(id: number, isWorker: boolean, process: IPythonProcess, name: string = ''): IPythonThread {
return {
IsWorkerThread: isWorker,
Process: process,
Expand All @@ -50,15 +50,13 @@ export function CreatePythonThread(id: number, isWorker: boolean, process: IPyth

export function CreatePythonModule(id: number, fileName: string): IPythonModule {
let name = fileName;
if (typeof fileName === "string") {
if (typeof fileName === 'string') {
try {
name = path.basename(fileName);
}
catch (ex) {
}
}
else {
name = "";
// tslint:disable-next-line:no-empty
} catch { }
} else {
name = '';
}

return {
Expand All @@ -74,7 +72,7 @@ export function FixupEscapedUnicodeChars(value: string): string {

export function getPythonExecutable(pythonPath: string): string {
pythonPath = untildify(pythonPath);
// If only 'python'
// If only 'python'.
if (pythonPath === 'python' ||
pythonPath.indexOf(path.sep) === -1 ||
path.basename(pythonPath) === path.dirname(pythonPath)) {
Expand All @@ -84,21 +82,20 @@ export function getPythonExecutable(pythonPath: string): string {
if (isValidPythonPath(pythonPath)) {
return pythonPath;
}
// Keep python right on top, for backwards compatibility
// Keep python right on top, for backwards compatibility.
const KnownPythonExecutables = ['python', 'python4', 'python3.6', 'python3.5', 'python3', 'python2.7', 'python2'];

for (let executableName of KnownPythonExecutables) {
// Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'
// Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'.
if (IS_WINDOWS) {
executableName = executableName + '.exe';
executableName = `${executableName}.exe`;
if (isValidPythonPath(path.join(pythonPath, executableName))) {
return path.join(pythonPath, executableName);
}
if (isValidPythonPath(path.join(pythonPath, 'scripts', executableName))) {
return path.join(pythonPath, 'scripts', executableName);
}
}
else {
} else {
if (isValidPythonPath(path.join(pythonPath, executableName))) {
return path.join(pythonPath, executableName);
}
Expand All @@ -113,39 +110,36 @@ export function getPythonExecutable(pythonPath: string): string {

function isValidPythonPath(pythonPath): boolean {
try {
let output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' });
const output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' });
return output.startsWith('1234');
}
catch (ex) {
} catch {
return false;
}
}

type EnvVars = Object & { [key: string]: string };

export function getCustomEnvVars(envVars: any, envFile: string): any {
let envFileVars = null;
export function getCustomEnvVars(envVars: Object, envFile: string, mergeWithProcessEnvVars: boolean = true): EnvVars {
let envFileVars: EnvVars = null;
if (typeof envFile === 'string' && envFile.length > 0 && fs.existsSync(envFile)) {
try {
envFileVars = parseEnvFile(envFile);
}
catch (ex) {
envFileVars = parseEnvFile(envFile, mergeWithProcessEnvVars);
} catch (ex) {
console.error('Failed to load env file');
console.error(ex);
}
}
let configVars = null;
if (envVars && Object.keys(envVars).length > 0 && envFileVars) {
configVars = mergeEnvVariables(envVars, envFileVars);
}
if (envVars && Object.keys(envVars).length > 0) {
configVars = envVars;
}
if (envFileVars) {
configVars = envFileVars;
if (envFileVars && Object.keys(envFileVars).length > 0) {
if (!envVars || Object.keys(envVars).length === 0) {
return envFileVars;
} else {
envVars = envVars || {};
return mergeEnvVariables(envVars as EnvVars, envFileVars);
}
}
if (configVars && typeof configVars === 'object' && Object.keys(configVars).length > 0) {
return configVars;
if (!envVars || Object.keys(envVars).length === 0) {
return null;
}

return null;
}
return mergePythonPath(envVars as EnvVars, process.env.PYTHONPATH);
}
2 changes: 1 addition & 1 deletion src/client/debugger/DebugClients/DebugClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export enum DebugType {
}
export abstract class DebugClient extends EventEmitter {
protected debugSession: DebugSession;
constructor(args: any, debugSession: DebugSession) {
constructor(protected args: any, debugSession: DebugSession) {
super();
this.debugSession = debugSession;
}
Expand Down
Loading