Skip to content

Commit

Permalink
Support case sensitive folders on Windows. Fixes microsoft#200
Browse files Browse the repository at this point in the history
  • Loading branch information
fabioz committed Sep 17, 2020
1 parent d3cb68f commit 4d2b5f7
Show file tree
Hide file tree
Showing 32 changed files with 5,032 additions and 4,917 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import zlib
import base64
import os
from pydevd_file_utils import _NormFile
from pydevd_file_utils import canonical_normalized_path
import pytest
import sys
import time
Expand Down Expand Up @@ -32,7 +32,7 @@ def _load_filters():
# may need to filter with a resolved path too.
new_dct = {}
for filename, value in py_test_accept_filter.items():
new_dct[_NormFile(str(Path(filename).resolve()))] = value
new_dct[canonical_normalized_path(str(Path(filename).resolve()))] = value

py_test_accept_filter.update(new_dct)

Expand Down Expand Up @@ -110,7 +110,7 @@ def pytest_collection_modifyitems(session, config, items):

new_items = []
for item in items:
f = _NormFile(str(item.parent.fspath))
f = canonical_normalized_path(str(item.parent.fspath))
name = item.name

if f not in py_test_accept_filter:
Expand Down
110 changes: 64 additions & 46 deletions src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ def filename_to_str(self, filename):

def filename_to_server(self, filename):
filename = self.filename_to_str(filename)
return pydevd_file_utils.norm_file_to_server(filename)
filename = pydevd_file_utils.map_file_to_server(filename)
return filename

class _DummyFrame(object):
'''
Expand Down Expand Up @@ -336,10 +337,10 @@ def __init__(self, translated_filename, translated_line):
self.translated_line = translated_line

def add_breakpoint(
self, py_db, filename, breakpoint_type, breakpoint_id, line, condition, func_name,
self, py_db, original_filename, breakpoint_type, breakpoint_id, line, condition, func_name,
expression, suspend_policy, hit_condition, is_logpoint, adjust_line=False):
'''
:param str filename:
:param str original_filename:
Note: must be sent as it was received in the protocol. It may be translated in this
function and its final value will be available in the returned _AddBreakpointResult.
Expand Down Expand Up @@ -377,41 +378,45 @@ def add_breakpoint(
:return _AddBreakpointResult:
'''
assert filename.__class__ == str, 'Expected str, found: %s' % (filename.__class__,) # i.e.: bytes on py2 and str on py3
assert original_filename.__class__ == str, 'Expected str, found: %s' % (original_filename.__class__,) # i.e.: bytes on py2 and str on py3

original_filename = filename
pydev_log.debug('Request for breakpoint in: %s line: %s', original_filename, line)
# Parameters to reapply breakpoint.
api_add_breakpoint_params = (filename, breakpoint_type, breakpoint_id, line, condition, func_name,
api_add_breakpoint_params = (original_filename, breakpoint_type, breakpoint_id, line, condition, func_name,
expression, suspend_policy, hit_condition, is_logpoint)

filename = self.filename_to_server(filename) # Apply user path mapping.
translated_filename = self.filename_to_server(original_filename) # Apply user path mapping.
pydev_log.debug('Breakpoint (after path translation) in: %s line: %s', translated_filename, line)
func_name = self.to_str(func_name)

assert filename.__class__ == str # i.e.: bytes on py2 and str on py3
assert translated_filename.__class__ == str # i.e.: bytes on py2 and str on py3
assert func_name.__class__ == str # i.e.: bytes on py2 and str on py3

# Apply source mapping (i.e.: ipython).
new_filename, new_line, multi_mapping_applied = py_db.source_mapping.map_to_server(filename, line)

py_db.api_received_breakpoints[(original_filename, breakpoint_id)] = (new_filename, api_add_breakpoint_params)

pydev_log.debug('Breakpoint (after path/source mapping) in: %s line: %s', new_filename, new_line)
source_mapped_filename, new_line, multi_mapping_applied = py_db.source_mapping.map_to_server(
translated_filename, line)

if multi_mapping_applied:
pydev_log.debug('Breakpoint (after source mapping) in: %s line: %s', source_mapped_filename, new_line)
# Note that source mapping is internal and does not change the resulting filename nor line
# (we want the outside world to see the line in the original file and not in the ipython
# cell, otherwise the editor wouldn't be correct as the returned line is the line to
# which the breakpoint will be moved in the editor).
result = self._AddBreakpointResult(filename, line)
filename = new_filename
result = self._AddBreakpointResult(original_filename, line)

# If a multi-mapping was applied, consider it the canonical / source mapped version (translated to ipython cell).
translated_absolute_filename = source_mapped_filename
canonical_normalized_filename = pydevd_file_utils.normcase(source_mapped_filename)
line = new_line

else:
if adjust_line and not filename.startswith('<'):
translated_absolute_filename = pydevd_file_utils.absolute_path(translated_filename)
canonical_normalized_filename = pydevd_file_utils.canonical_normalized_path(translated_filename)

if adjust_line and not translated_absolute_filename.startswith('<'):
# Validate breakpoints and adjust their positions.
try:
lines = sorted(_get_code_lines(filename))
lines = sorted(_get_code_lines(translated_absolute_filename))
except Exception:
pass
else:
Expand All @@ -421,19 +426,21 @@ def add_breakpoint(
if idx > 0:
line = lines[idx - 1]

result = self._AddBreakpointResult(filename, line)
result = self._AddBreakpointResult(original_filename, line)

py_db.api_received_breakpoints[(original_filename, breakpoint_id)] = (canonical_normalized_filename, api_add_breakpoint_params)

if not filename.startswith('<'):
if not translated_absolute_filename.startswith('<'):
# Note: if a mapping pointed to a file starting with '<', don't validate.

if not pydevd_file_utils.exists(filename):
if not pydevd_file_utils.exists(translated_absolute_filename):
result.error_code = self.ADD_BREAKPOINT_FILE_NOT_FOUND
return result

if (
py_db.is_files_filter_enabled and
not py_db.get_require_module_for_filters() and
py_db.apply_files_filter(self._DummyFrame(filename), filename, False)
py_db.apply_files_filter(self._DummyFrame(translated_absolute_filename), translated_absolute_filename, False)
):
# Note that if `get_require_module_for_filters()` returns False, we don't do this check.
# This is because we don't have the module name given a file at this point (in
Expand All @@ -456,7 +463,9 @@ def add_breakpoint(
add_plugin_breakpoint_result = None
plugin = py_db.get_plugin_lazy_init()
if plugin is not None:
add_plugin_breakpoint_result = plugin.add_breakpoint('add_line_breakpoint', py_db, breakpoint_type, filename, line, condition, expression, func_name, hit_condition=hit_condition, is_logpoint=is_logpoint)
add_plugin_breakpoint_result = plugin.add_breakpoint(
'add_line_breakpoint', py_db, breakpoint_type, canonical_normalized_filename, line, condition, expression, func_name, hit_condition=hit_condition, is_logpoint=is_logpoint)

if add_plugin_breakpoint_result is not None:
supported_type = True
added_breakpoint, breakpoints = add_plugin_breakpoint_result
Expand All @@ -468,15 +477,15 @@ def add_breakpoint(
raise NameError(breakpoint_type)

if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
pydev_log.debug('Added breakpoint:%s - line:%s - func_name:%s\n', filename, line, func_name)
pydev_log.debug('Added breakpoint:%s - line:%s - func_name:%s\n', canonical_normalized_filename, line, func_name)

if filename in file_to_id_to_breakpoint:
id_to_pybreakpoint = file_to_id_to_breakpoint[filename]
if canonical_normalized_filename in file_to_id_to_breakpoint:
id_to_pybreakpoint = file_to_id_to_breakpoint[canonical_normalized_filename]
else:
id_to_pybreakpoint = file_to_id_to_breakpoint[filename] = {}
id_to_pybreakpoint = file_to_id_to_breakpoint[canonical_normalized_filename] = {}

id_to_pybreakpoint[breakpoint_id] = added_breakpoint
py_db.consolidate_breakpoints(filename, id_to_pybreakpoint, breakpoints)
py_db.consolidate_breakpoints(canonical_normalized_filename, id_to_pybreakpoint, breakpoints)
if py_db.plugin is not None:
py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks()

Expand All @@ -488,6 +497,7 @@ def reapply_breakpoints(self, py_db):
Reapplies all the received breakpoints as they were received by the API (so, new
translations are applied).
'''
pydev_log.debug('Reapplying breakpoints.')
items = dict_items(py_db.api_received_breakpoints) # Create a copy with items to reapply.
self.remove_all_breakpoints(py_db, '*')
for _key, val in items:
Expand Down Expand Up @@ -527,25 +537,25 @@ def remove_all_breakpoints(self, py_db, filename):
items = dict_items(py_db.api_received_breakpoints) # Create a copy to remove items.
translated_filenames = []
for key, val in items:
key_filename, _breakpoint_id = key
if key_filename == filename:
new_filename, _api_add_breakpoint_params = val
original_filename, _breakpoint_id = key
if original_filename == filename:
canonical_normalized_filename, _api_add_breakpoint_params = val
# Note: there can be actually 1:N mappings due to source mapping (i.e.: ipython).
translated_filenames.append(new_filename)
translated_filenames.append(canonical_normalized_filename)
del py_db.api_received_breakpoints[key]

for filename in translated_filenames:
for canonical_normalized_filename in translated_filenames:
for file_to_id_to_breakpoint in lst:
if filename in file_to_id_to_breakpoint:
del file_to_id_to_breakpoint[filename]
if canonical_normalized_filename in file_to_id_to_breakpoint:
file_to_id_to_breakpoint.pop(canonical_normalized_filename, None)
changed = True

if changed:
py_db.on_breakpoints_changed(removed=True)

def remove_breakpoint(self, py_db, filename, breakpoint_type, breakpoint_id):
def remove_breakpoint(self, py_db, received_filename, breakpoint_type, breakpoint_id):
'''
:param str filename:
:param str received_filename:
Note: must be sent as it was received in the protocol. It may be translated in this
function.
Expand All @@ -557,15 +567,16 @@ def remove_breakpoint(self, py_db, filename, breakpoint_type, breakpoint_id):
for key, val in dict_items(py_db.api_received_breakpoints):
original_filename, existing_breakpoint_id = key
_new_filename, _api_add_breakpoint_params = val
if filename == original_filename and existing_breakpoint_id == breakpoint_id:
if received_filename == original_filename and existing_breakpoint_id == breakpoint_id:
del py_db.api_received_breakpoints[key]
break
else:
pydev_log.info(
'Did not find breakpoint to remove: %s (breakpoint id: %s)', filename, breakpoint_id)
'Did not find breakpoint to remove: %s (breakpoint id: %s)', received_filename, breakpoint_id)

file_to_id_to_breakpoint = None
filename = self.filename_to_server(filename)
received_filename = self.filename_to_server(received_filename)
canonical_normalized_filename = pydevd_file_utils.canonical_normalized_path(received_filename)

if breakpoint_type == 'python-line':
breakpoints = py_db.breakpoints
Expand All @@ -582,20 +593,20 @@ def remove_breakpoint(self, py_db, filename, breakpoint_type, breakpoint_id):

else:
try:
id_to_pybreakpoint = file_to_id_to_breakpoint.get(filename, {})
id_to_pybreakpoint = file_to_id_to_breakpoint.get(canonical_normalized_filename, {})
if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
existing = id_to_pybreakpoint[breakpoint_id]
pydev_log.info('Removed breakpoint:%s - line:%s - func_name:%s (id: %s)\n' % (
filename, existing.line, existing.func_name.encode('utf-8'), breakpoint_id))
canonical_normalized_filename, existing.line, existing.func_name.encode('utf-8'), breakpoint_id))

del id_to_pybreakpoint[breakpoint_id]
py_db.consolidate_breakpoints(filename, id_to_pybreakpoint, breakpoints)
py_db.consolidate_breakpoints(canonical_normalized_filename, id_to_pybreakpoint, breakpoints)
if py_db.plugin is not None:
py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks()

except KeyError:
pydev_log.info("Error removing breakpoint: Breakpoint id not found: %s id: %s. Available ids: %s\n",
filename, breakpoint_id, dict_keys(id_to_pybreakpoint))
canonical_normalized_filename, breakpoint_id, dict_keys(id_to_pybreakpoint))

py_db.on_breakpoints_changed(removed=True)

Expand Down Expand Up @@ -784,6 +795,10 @@ def request_change_variable_json(self, py_db, request, thread_id):
thread_id, internal_change_variable_json, request)

def set_dont_trace_start_end_patterns(self, py_db, start_patterns, end_patterns):
# Note: start/end patterns normalized internally.
start_patterns = tuple(pydevd_file_utils.normcase(x) for x in start_patterns)
end_patterns = tuple(pydevd_file_utils.normcase(x) for x in end_patterns)

# After it's set the first time, we can still change it, but we need to reset the
# related caches.
reset_caches = False
Expand All @@ -804,7 +819,8 @@ def set_dont_trace_start_end_patterns(self, py_db, start_patterns, end_patterns)
reset_caches = True

def custom_dont_trace_external_files(abs_path):
return abs_path.startswith(start_patterns) or abs_path.endswith(end_patterns)
normalized_abs_path = pydevd_file_utils.normcase(abs_path)
return normalized_abs_path.startswith(start_patterns) or normalized_abs_path.endswith(end_patterns)

custom_dont_trace_external_files.start_patterns = start_patterns
custom_dont_trace_external_files.end_patterns = end_patterns
Expand All @@ -831,6 +847,7 @@ def set_source_mapping(self, py_db, source_filename, mapping):
'''
:param str source_filename:
The filename for the source mapping (bytes on py2 and str on py3).
This filename will be made absolute in this function.
:param list(SourceMappingEntry) mapping:
A list with the source mapping entries to be applied to the given filename.
Expand All @@ -840,9 +857,10 @@ def set_source_mapping(self, py_db, source_filename, mapping):
everything is ok.
'''
source_filename = self.filename_to_server(source_filename)
absolute_source_filename = pydevd_file_utils.absolute_path(source_filename)
for map_entry in mapping:
map_entry.source_filename = source_filename
error_msg = py_db.source_mapping.set_source_mapping(source_filename, mapping)
map_entry.source_filename = absolute_source_filename
error_msg = py_db.source_mapping.set_source_mapping(absolute_source_filename, mapping)
if error_msg:
return error_msg

Expand Down
Loading

0 comments on commit 4d2b5f7

Please sign in to comment.