robotcode-robot 0.68.2__py3-none-any.whl → 0.68.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- robotcode/robot/__version__.py +1 -1
- robotcode/robot/config/model.py +63 -51
- robotcode/robot/diagnostics/document_cache_helper.py +523 -0
- robotcode/robot/diagnostics/entities.py +2 -1
- robotcode/robot/diagnostics/errors.py +33 -0
- robotcode/robot/diagnostics/imports_manager.py +1499 -0
- robotcode/robot/diagnostics/library_doc.py +3 -2
- robotcode/robot/diagnostics/model_helper.py +799 -0
- robotcode/robot/diagnostics/namespace.py +2165 -0
- robotcode/robot/diagnostics/namespace_analyzer.py +1121 -0
- robotcode/robot/diagnostics/workspace_config.py +50 -0
- robotcode/robot/utils/ast.py +6 -5
- robotcode/robot/utils/stubs.py +17 -1
- {robotcode_robot-0.68.2.dist-info → robotcode_robot-0.68.5.dist-info}/METADATA +2 -2
- robotcode_robot-0.68.5.dist-info/RECORD +29 -0
- robotcode_robot-0.68.2.dist-info/RECORD +0 -22
- {robotcode_robot-0.68.2.dist-info → robotcode_robot-0.68.5.dist-info}/WHEEL +0 -0
- {robotcode_robot-0.68.2.dist-info → robotcode_robot-0.68.5.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,1499 @@
|
|
1
|
+
import ast
|
2
|
+
import itertools
|
3
|
+
import multiprocessing as mp
|
4
|
+
import os
|
5
|
+
import shutil
|
6
|
+
import sys
|
7
|
+
import threading
|
8
|
+
import weakref
|
9
|
+
import zlib
|
10
|
+
from abc import ABC, abstractmethod
|
11
|
+
from collections import OrderedDict
|
12
|
+
from concurrent.futures import ProcessPoolExecutor
|
13
|
+
from dataclasses import dataclass
|
14
|
+
from pathlib import Path
|
15
|
+
from typing import (
|
16
|
+
TYPE_CHECKING,
|
17
|
+
Any,
|
18
|
+
Callable,
|
19
|
+
Dict,
|
20
|
+
List,
|
21
|
+
Mapping,
|
22
|
+
Optional,
|
23
|
+
Set,
|
24
|
+
Tuple,
|
25
|
+
final,
|
26
|
+
)
|
27
|
+
|
28
|
+
from robotcode.core.concurrent import run_as_task
|
29
|
+
from robotcode.core.documents_manager import DocumentsManager
|
30
|
+
from robotcode.core.event import event
|
31
|
+
from robotcode.core.filewatcher import FileWatcherEntry, FileWatcherManagerBase, FileWatcherManagerDummy
|
32
|
+
from robotcode.core.language import language_id
|
33
|
+
from robotcode.core.lsp.types import DocumentUri, FileChangeType, FileEvent
|
34
|
+
from robotcode.core.text_document import TextDocument
|
35
|
+
from robotcode.core.uri import Uri
|
36
|
+
from robotcode.core.utils.caching import SimpleLRUCache
|
37
|
+
from robotcode.core.utils.dataclasses import as_json, from_json
|
38
|
+
from robotcode.core.utils.glob_path import Pattern, iter_files
|
39
|
+
from robotcode.core.utils.logging import LoggingDescriptor
|
40
|
+
from robotcode.core.utils.path import path_is_relative_to
|
41
|
+
|
42
|
+
from ..__version__ import __version__
|
43
|
+
from ..utils import get_robot_version, get_robot_version_str
|
44
|
+
from ..utils.robot_path import find_file_ex
|
45
|
+
from .entities import (
|
46
|
+
CommandLineVariableDefinition,
|
47
|
+
VariableDefinition,
|
48
|
+
)
|
49
|
+
from .library_doc import (
|
50
|
+
ROBOT_LIBRARY_PACKAGE,
|
51
|
+
CompleteResult,
|
52
|
+
LibraryDoc,
|
53
|
+
ModuleSpec,
|
54
|
+
VariablesDoc,
|
55
|
+
complete_library_import,
|
56
|
+
complete_resource_import,
|
57
|
+
complete_variables_import,
|
58
|
+
find_file,
|
59
|
+
find_library,
|
60
|
+
find_variables,
|
61
|
+
get_library_doc,
|
62
|
+
get_model_doc,
|
63
|
+
get_module_spec,
|
64
|
+
get_variables_doc,
|
65
|
+
is_library_by_path,
|
66
|
+
is_variables_by_path,
|
67
|
+
resolve_args,
|
68
|
+
resolve_variable,
|
69
|
+
)
|
70
|
+
|
71
|
+
if TYPE_CHECKING:
|
72
|
+
from .document_cache_helper import DocumentsCacheHelper
|
73
|
+
from .namespace import Namespace
|
74
|
+
|
75
|
+
|
76
|
+
RESOURCE_EXTENSIONS = (
|
77
|
+
{".resource", ".robot", ".txt", ".tsv", ".rst", ".rest", ".json", ".rsrc"}
|
78
|
+
if get_robot_version() >= (6, 1)
|
79
|
+
else {".resource", ".robot", ".txt", ".tsv", ".rst", ".rest"}
|
80
|
+
)
|
81
|
+
REST_EXTENSIONS = (".rst", ".rest")
|
82
|
+
|
83
|
+
|
84
|
+
LOAD_LIBRARY_TIME_OUT = 30
|
85
|
+
FIND_FILE_TIME_OUT = 10
|
86
|
+
COMPLETE_LIBRARY_IMPORT_TIME_OUT = COMPLETE_RESOURCE_IMPORT_TIME_OUT = COMPLETE_VARIABLES_IMPORT_TIME_OUT = 10
|
87
|
+
|
88
|
+
|
89
|
+
class _EntryKey:
|
90
|
+
pass
|
91
|
+
|
92
|
+
|
93
|
+
@dataclass()
|
94
|
+
class _LibrariesEntryKey(_EntryKey):
|
95
|
+
name: str
|
96
|
+
args: Tuple[Any, ...]
|
97
|
+
|
98
|
+
def __hash__(self) -> int:
|
99
|
+
return hash((self.name, self.args))
|
100
|
+
|
101
|
+
|
102
|
+
class _ImportEntry(ABC):
|
103
|
+
def __init__(self, parent: "ImportsManager") -> None:
|
104
|
+
self.parent = parent
|
105
|
+
self.references: weakref.WeakSet[Any] = weakref.WeakSet()
|
106
|
+
self.file_watchers: List[FileWatcherEntry] = []
|
107
|
+
self._lock = threading.RLock()
|
108
|
+
|
109
|
+
def __del__(self) -> None:
|
110
|
+
try:
|
111
|
+
self._remove_file_watcher()
|
112
|
+
except RuntimeError:
|
113
|
+
pass
|
114
|
+
|
115
|
+
def _remove_file_watcher(self) -> None:
|
116
|
+
if self.file_watchers:
|
117
|
+
for watcher in self.file_watchers:
|
118
|
+
self.parent.file_watcher_manager.remove_file_watcher_entry(watcher)
|
119
|
+
self.file_watchers = []
|
120
|
+
|
121
|
+
@abstractmethod
|
122
|
+
def check_file_changed(self, changes: List[FileEvent]) -> Optional[FileChangeType]:
|
123
|
+
...
|
124
|
+
|
125
|
+
@final
|
126
|
+
def invalidate(self) -> None:
|
127
|
+
with self._lock:
|
128
|
+
self._invalidate()
|
129
|
+
|
130
|
+
@abstractmethod
|
131
|
+
def _invalidate(self) -> None:
|
132
|
+
...
|
133
|
+
|
134
|
+
@abstractmethod
|
135
|
+
def _update(self) -> None:
|
136
|
+
...
|
137
|
+
|
138
|
+
@abstractmethod
|
139
|
+
def is_valid(self) -> bool:
|
140
|
+
...
|
141
|
+
|
142
|
+
|
143
|
+
class _LibrariesEntry(_ImportEntry):
|
144
|
+
def __init__(
|
145
|
+
self,
|
146
|
+
parent: "ImportsManager",
|
147
|
+
name: str,
|
148
|
+
args: Tuple[Any, ...],
|
149
|
+
working_dir: str,
|
150
|
+
base_dir: str,
|
151
|
+
get_libdoc_coroutine: Callable[[str, Tuple[Any, ...], str, str], LibraryDoc],
|
152
|
+
ignore_reference: bool = False,
|
153
|
+
) -> None:
|
154
|
+
super().__init__(parent)
|
155
|
+
self.name = name
|
156
|
+
self.args = args
|
157
|
+
self.working_dir = working_dir
|
158
|
+
self.base_dir = base_dir
|
159
|
+
self._get_libdoc_coroutine = get_libdoc_coroutine
|
160
|
+
self._lib_doc: Optional[LibraryDoc] = None
|
161
|
+
self.ignore_reference = ignore_reference
|
162
|
+
|
163
|
+
def __repr__(self) -> str:
|
164
|
+
return (
|
165
|
+
f"{type(self).__qualname__}(name={self.name!r}, "
|
166
|
+
f"args={self.args!r}, file_watchers={self.file_watchers!r}, id={id(self)!r}"
|
167
|
+
)
|
168
|
+
|
169
|
+
def check_file_changed(self, changes: List[FileEvent]) -> Optional[FileChangeType]:
|
170
|
+
with self._lock:
|
171
|
+
if self._lib_doc is None:
|
172
|
+
return None
|
173
|
+
|
174
|
+
for change in changes:
|
175
|
+
uri = Uri(change.uri)
|
176
|
+
if uri.scheme != "file":
|
177
|
+
continue
|
178
|
+
|
179
|
+
path = uri.to_path()
|
180
|
+
if self._lib_doc is not None and (
|
181
|
+
(
|
182
|
+
self._lib_doc.module_spec is not None
|
183
|
+
and self._lib_doc.module_spec.submodule_search_locations is not None
|
184
|
+
and any(
|
185
|
+
path_is_relative_to(path, Path(e).resolve())
|
186
|
+
for e in self._lib_doc.module_spec.submodule_search_locations
|
187
|
+
)
|
188
|
+
)
|
189
|
+
or (
|
190
|
+
self._lib_doc.module_spec is not None
|
191
|
+
and self._lib_doc.module_spec.origin is not None
|
192
|
+
and path_is_relative_to(path, Path(self._lib_doc.module_spec.origin).parent)
|
193
|
+
)
|
194
|
+
or (self._lib_doc.source and path_is_relative_to(path, Path(self._lib_doc.source).parent))
|
195
|
+
or (
|
196
|
+
self._lib_doc.module_spec is None
|
197
|
+
and not self._lib_doc.source
|
198
|
+
and self._lib_doc.python_path
|
199
|
+
and any(path_is_relative_to(path, Path(e).resolve()) for e in self._lib_doc.python_path)
|
200
|
+
)
|
201
|
+
):
|
202
|
+
self._invalidate()
|
203
|
+
|
204
|
+
return change.type
|
205
|
+
|
206
|
+
return None
|
207
|
+
|
208
|
+
def _update(self) -> None:
|
209
|
+
self._lib_doc = self._get_libdoc_coroutine(self.name, self.args, self.working_dir, self.base_dir)
|
210
|
+
|
211
|
+
source_or_origin = (
|
212
|
+
self._lib_doc.source
|
213
|
+
if self._lib_doc.source is not None
|
214
|
+
else self._lib_doc.module_spec.origin
|
215
|
+
if self._lib_doc.module_spec is not None
|
216
|
+
else None
|
217
|
+
)
|
218
|
+
|
219
|
+
# we are a module, so add the module path into file watchers
|
220
|
+
if self._lib_doc.module_spec is not None and self._lib_doc.module_spec.submodule_search_locations is not None:
|
221
|
+
self.file_watchers.append(
|
222
|
+
self.parent.file_watcher_manager.add_file_watchers(
|
223
|
+
self.parent.did_change_watched_files,
|
224
|
+
[
|
225
|
+
str(Path(location).resolve().joinpath("**"))
|
226
|
+
for location in self._lib_doc.module_spec.submodule_search_locations
|
227
|
+
],
|
228
|
+
)
|
229
|
+
)
|
230
|
+
|
231
|
+
if source_or_origin is not None and Path(source_or_origin).parent in [
|
232
|
+
Path(loc).resolve() for loc in self._lib_doc.module_spec.submodule_search_locations
|
233
|
+
]:
|
234
|
+
return
|
235
|
+
|
236
|
+
# we are a file, so put the parent path to filewatchers
|
237
|
+
if source_or_origin is not None:
|
238
|
+
self.file_watchers.append(
|
239
|
+
self.parent.file_watcher_manager.add_file_watchers(
|
240
|
+
self.parent.did_change_watched_files,
|
241
|
+
[str(Path(source_or_origin).parent.joinpath("**"))],
|
242
|
+
)
|
243
|
+
)
|
244
|
+
|
245
|
+
return
|
246
|
+
|
247
|
+
# we are not found, so put the pythonpath to filewatchers
|
248
|
+
if self._lib_doc.python_path is not None:
|
249
|
+
self.file_watchers.append(
|
250
|
+
self.parent.file_watcher_manager.add_file_watchers(
|
251
|
+
self.parent.did_change_watched_files,
|
252
|
+
[str(Path(s).joinpath("**")) for s in self._lib_doc.python_path],
|
253
|
+
)
|
254
|
+
)
|
255
|
+
|
256
|
+
def _invalidate(self) -> None:
|
257
|
+
if self._lib_doc is None and len(self.file_watchers) == 0:
|
258
|
+
return
|
259
|
+
|
260
|
+
self._remove_file_watcher()
|
261
|
+
self._lib_doc = None
|
262
|
+
|
263
|
+
def is_valid(self) -> bool:
|
264
|
+
with self._lock:
|
265
|
+
return self._lib_doc is not None
|
266
|
+
|
267
|
+
def get_libdoc(self) -> LibraryDoc:
|
268
|
+
with self._lock:
|
269
|
+
if self._lib_doc is None:
|
270
|
+
self._update()
|
271
|
+
|
272
|
+
assert self._lib_doc is not None
|
273
|
+
|
274
|
+
return self._lib_doc
|
275
|
+
|
276
|
+
|
277
|
+
@dataclass()
|
278
|
+
class _ResourcesEntryKey(_EntryKey):
|
279
|
+
name: str
|
280
|
+
|
281
|
+
def __hash__(self) -> int:
|
282
|
+
return hash(self.name)
|
283
|
+
|
284
|
+
|
285
|
+
class _ResourcesEntry(_ImportEntry):
|
286
|
+
def __init__(
|
287
|
+
self,
|
288
|
+
name: str,
|
289
|
+
parent: "ImportsManager",
|
290
|
+
get_document_callback: Callable[[], TextDocument],
|
291
|
+
) -> None:
|
292
|
+
super().__init__(parent)
|
293
|
+
self.name = name
|
294
|
+
self._get_document_callback = get_document_callback
|
295
|
+
self._document: Optional[TextDocument] = None
|
296
|
+
self._lib_doc: Optional[LibraryDoc] = None
|
297
|
+
|
298
|
+
def __repr__(self) -> str:
|
299
|
+
return f"{type(self).__qualname__}(name={self.name!r}, file_watchers={self.file_watchers!r}, id={id(self)!r}"
|
300
|
+
|
301
|
+
def check_file_changed(self, changes: List[FileEvent]) -> Optional[FileChangeType]:
|
302
|
+
with self._lock:
|
303
|
+
for change in changes:
|
304
|
+
uri = Uri(change.uri)
|
305
|
+
if uri.scheme != "file":
|
306
|
+
continue
|
307
|
+
|
308
|
+
path = uri.to_path()
|
309
|
+
if (
|
310
|
+
self._document is not None
|
311
|
+
and (path.resolve() == self._document.uri.to_path().resolve())
|
312
|
+
or self._document is None
|
313
|
+
):
|
314
|
+
self._invalidate()
|
315
|
+
|
316
|
+
return change.type
|
317
|
+
|
318
|
+
return None
|
319
|
+
|
320
|
+
def _update(self) -> None:
|
321
|
+
self._document = self._get_document_callback()
|
322
|
+
|
323
|
+
if self._document._version is None:
|
324
|
+
self.file_watchers.append(
|
325
|
+
self.parent.file_watcher_manager.add_file_watchers(
|
326
|
+
self.parent.did_change_watched_files,
|
327
|
+
[str(self._document.uri.to_path())],
|
328
|
+
)
|
329
|
+
)
|
330
|
+
|
331
|
+
def _invalidate(self) -> None:
|
332
|
+
if self._document is None and len(self.file_watchers) == 0:
|
333
|
+
return
|
334
|
+
|
335
|
+
self._remove_file_watcher()
|
336
|
+
|
337
|
+
self._document = None
|
338
|
+
self._lib_doc = None
|
339
|
+
|
340
|
+
def is_valid(self) -> bool:
|
341
|
+
with self._lock:
|
342
|
+
return self._document is not None
|
343
|
+
|
344
|
+
def get_document(self) -> TextDocument:
|
345
|
+
with self._lock:
|
346
|
+
self._get_document()
|
347
|
+
|
348
|
+
assert self._document is not None
|
349
|
+
|
350
|
+
return self._document
|
351
|
+
|
352
|
+
def _get_document(self) -> TextDocument:
|
353
|
+
if self._document is None:
|
354
|
+
self._update()
|
355
|
+
|
356
|
+
assert self._document is not None
|
357
|
+
|
358
|
+
return self._document
|
359
|
+
|
360
|
+
def get_namespace(self) -> "Namespace":
|
361
|
+
with self._lock:
|
362
|
+
return self._get_namespace()
|
363
|
+
|
364
|
+
def _get_namespace(self) -> "Namespace":
|
365
|
+
return self.parent.get_namespace_for_resource(self._get_document())
|
366
|
+
|
367
|
+
def get_libdoc(self) -> LibraryDoc:
|
368
|
+
with self._lock:
|
369
|
+
if self._lib_doc is None:
|
370
|
+
self._lib_doc = self._get_namespace().get_library_doc()
|
371
|
+
|
372
|
+
return self._lib_doc
|
373
|
+
|
374
|
+
|
375
|
+
@dataclass()
|
376
|
+
class _VariablesEntryKey(_EntryKey):
|
377
|
+
name: str
|
378
|
+
args: Tuple[Any, ...]
|
379
|
+
|
380
|
+
def __hash__(self) -> int:
|
381
|
+
return hash((self.name, self.args))
|
382
|
+
|
383
|
+
|
384
|
+
class _VariablesEntry(_ImportEntry):
|
385
|
+
def __init__(
|
386
|
+
self,
|
387
|
+
name: str,
|
388
|
+
args: Tuple[Any, ...],
|
389
|
+
working_dir: str,
|
390
|
+
base_dir: str,
|
391
|
+
parent: "ImportsManager",
|
392
|
+
get_variables_doc_coroutine: Callable[[str, Tuple[Any, ...], str, str], VariablesDoc],
|
393
|
+
) -> None:
|
394
|
+
super().__init__(parent)
|
395
|
+
self.name = name
|
396
|
+
self.args = args
|
397
|
+
self.working_dir = working_dir
|
398
|
+
self.base_dir = base_dir
|
399
|
+
self._get_variables_doc_coroutine = get_variables_doc_coroutine
|
400
|
+
self._lib_doc: Optional[VariablesDoc] = None
|
401
|
+
|
402
|
+
def __repr__(self) -> str:
|
403
|
+
return (
|
404
|
+
f"{type(self).__qualname__}(name={self.name!r}, "
|
405
|
+
f"args={self.args!r}, file_watchers={self.file_watchers!r}, id={id(self)!r}"
|
406
|
+
)
|
407
|
+
|
408
|
+
def check_file_changed(self, changes: List[FileEvent]) -> Optional[FileChangeType]:
|
409
|
+
with self._lock:
|
410
|
+
if self._lib_doc is None:
|
411
|
+
return None
|
412
|
+
|
413
|
+
for change in changes:
|
414
|
+
uri = Uri(change.uri)
|
415
|
+
if uri.scheme != "file":
|
416
|
+
continue
|
417
|
+
|
418
|
+
path = uri.to_path()
|
419
|
+
if self._lib_doc.source and path.exists() and path.samefile(Path(self._lib_doc.source)):
|
420
|
+
self._invalidate()
|
421
|
+
|
422
|
+
return change.type
|
423
|
+
|
424
|
+
return None
|
425
|
+
|
426
|
+
def _update(self) -> None:
|
427
|
+
self._lib_doc = self._get_variables_doc_coroutine(self.name, self.args, self.working_dir, self.base_dir)
|
428
|
+
|
429
|
+
if self._lib_doc is not None:
|
430
|
+
self.file_watchers.append(
|
431
|
+
self.parent.file_watcher_manager.add_file_watchers(
|
432
|
+
self.parent.did_change_watched_files,
|
433
|
+
[str(self._lib_doc.source)],
|
434
|
+
)
|
435
|
+
)
|
436
|
+
|
437
|
+
def _invalidate(self) -> None:
|
438
|
+
if self._lib_doc is None and len(self.file_watchers) == 0:
|
439
|
+
return
|
440
|
+
|
441
|
+
self._remove_file_watcher()
|
442
|
+
|
443
|
+
self._lib_doc = None
|
444
|
+
|
445
|
+
def is_valid(self) -> bool:
|
446
|
+
with self._lock:
|
447
|
+
return self._lib_doc is not None
|
448
|
+
|
449
|
+
def get_libdoc(self) -> VariablesDoc:
|
450
|
+
with self._lock:
|
451
|
+
if self._lib_doc is None:
|
452
|
+
self._update()
|
453
|
+
|
454
|
+
assert self._lib_doc is not None
|
455
|
+
|
456
|
+
return self._lib_doc
|
457
|
+
|
458
|
+
|
459
|
+
@dataclass
|
460
|
+
class LibraryMetaData:
|
461
|
+
meta_version: str
|
462
|
+
name: Optional[str]
|
463
|
+
member_name: Optional[str]
|
464
|
+
origin: Optional[str]
|
465
|
+
submodule_search_locations: Optional[List[str]]
|
466
|
+
by_path: bool
|
467
|
+
|
468
|
+
mtimes: Optional[Dict[str, int]] = None
|
469
|
+
|
470
|
+
@property
|
471
|
+
def filepath_base(self) -> str:
|
472
|
+
if self.by_path:
|
473
|
+
if self.origin is not None:
|
474
|
+
p = Path(self.origin)
|
475
|
+
|
476
|
+
return f"{zlib.adler32(str(p.parent).encode('utf-8')):08x}_{p.stem}"
|
477
|
+
else:
|
478
|
+
if self.name is not None:
|
479
|
+
return self.name.replace(".", "/") + (f".{self.member_name}" if self.member_name else "")
|
480
|
+
|
481
|
+
raise ValueError("Cannot determine filepath base.")
|
482
|
+
|
483
|
+
|
484
|
+
class ImportsManager:
|
485
|
+
_logger = LoggingDescriptor()
|
486
|
+
|
487
|
+
def __init__(
|
488
|
+
self,
|
489
|
+
documents_manager: DocumentsManager,
|
490
|
+
file_watcher_manager: Optional[FileWatcherManagerBase],
|
491
|
+
document_cache_helper: "DocumentsCacheHelper",
|
492
|
+
root_folder: Path,
|
493
|
+
variables: Dict[str, str],
|
494
|
+
variable_files: List[str],
|
495
|
+
environment: Optional[Dict[str, str]],
|
496
|
+
ignored_libraries: List[str],
|
497
|
+
ignored_variables: List[str],
|
498
|
+
cache_base_path: Optional[Path],
|
499
|
+
) -> None:
|
500
|
+
super().__init__()
|
501
|
+
|
502
|
+
self.documents_manager = documents_manager
|
503
|
+
self.documents_manager.did_create_uri.add(self.possible_imports_modified)
|
504
|
+
self.documents_manager.did_change.add(self.possible_resource_document_modified)
|
505
|
+
|
506
|
+
self.file_watcher_manager: FileWatcherManagerBase = (
|
507
|
+
file_watcher_manager if file_watcher_manager is not None else FileWatcherManagerDummy()
|
508
|
+
)
|
509
|
+
|
510
|
+
self.document_cache_helper = document_cache_helper
|
511
|
+
|
512
|
+
self.root_folder = root_folder
|
513
|
+
|
514
|
+
if cache_base_path is None:
|
515
|
+
cache_base_path = root_folder
|
516
|
+
|
517
|
+
self._logger.trace(lambda: f"use {cache_base_path} as base for caching")
|
518
|
+
|
519
|
+
self.cache_path = cache_base_path / ".robotcode_cache"
|
520
|
+
|
521
|
+
self.lib_doc_cache_path = (
|
522
|
+
self.cache_path
|
523
|
+
/ f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
524
|
+
/ get_robot_version_str()
|
525
|
+
/ "libdoc"
|
526
|
+
)
|
527
|
+
self.variables_doc_cache_path = (
|
528
|
+
self.cache_path
|
529
|
+
/ f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
530
|
+
/ get_robot_version_str()
|
531
|
+
/ "variables"
|
532
|
+
)
|
533
|
+
|
534
|
+
self.cmd_variables = variables
|
535
|
+
self.cmd_variable_files = variable_files
|
536
|
+
|
537
|
+
self.ignored_libraries_patters = [Pattern(s) for s in ignored_libraries]
|
538
|
+
self.ignored_variables_patters = [Pattern(s) for s in ignored_variables]
|
539
|
+
self._libaries_lock = threading.RLock()
|
540
|
+
self._libaries: OrderedDict[_LibrariesEntryKey, _LibrariesEntry] = OrderedDict()
|
541
|
+
self._resources_lock = threading.RLock()
|
542
|
+
self._resources: OrderedDict[_ResourcesEntryKey, _ResourcesEntry] = OrderedDict()
|
543
|
+
self._variables_lock = threading.RLock()
|
544
|
+
self._variables: OrderedDict[_VariablesEntryKey, _VariablesEntry] = OrderedDict()
|
545
|
+
self.file_watchers: List[FileWatcherEntry] = []
|
546
|
+
self._command_line_variables: Optional[List[VariableDefinition]] = None
|
547
|
+
self._command_line_variables_lock = threading.RLock()
|
548
|
+
self._resolvable_command_line_variables: Optional[Dict[str, Any]] = None
|
549
|
+
self._resolvable_command_line_variables_lock = threading.RLock()
|
550
|
+
|
551
|
+
self._environment = dict(os.environ)
|
552
|
+
if environment:
|
553
|
+
self._environment.update(environment)
|
554
|
+
|
555
|
+
self._library_files_cache = SimpleLRUCache()
|
556
|
+
self._resource_files_cache = SimpleLRUCache()
|
557
|
+
self._variables_files_cache = SimpleLRUCache()
|
558
|
+
|
559
|
+
self._executor_lock = threading.RLock()
|
560
|
+
self._executor: Optional[ProcessPoolExecutor] = None
|
561
|
+
|
562
|
+
self._resource_document_changed_timer_lock = threading.RLock()
|
563
|
+
self._resource_document_changed_timer: Optional[threading.Timer] = None
|
564
|
+
self._resource_document_changed_timer_interval = 1
|
565
|
+
self._resource_document_changed_documents: Set[TextDocument] = set()
|
566
|
+
|
567
|
+
def __del__(self) -> None:
|
568
|
+
try:
|
569
|
+
if self._executor is not None:
|
570
|
+
self._executor.shutdown(wait=False)
|
571
|
+
except RuntimeError:
|
572
|
+
pass
|
573
|
+
|
574
|
+
@property
|
575
|
+
def environment(self) -> Mapping[str, str]:
|
576
|
+
return self._environment
|
577
|
+
|
578
|
+
def get_namespace_for_resource(self, document: TextDocument) -> "Namespace":
|
579
|
+
return self.document_cache_helper.get_resource_namespace(document)
|
580
|
+
|
581
|
+
def clear_cache(self) -> None:
|
582
|
+
if self.cache_path.exists():
|
583
|
+
shutil.rmtree(self.cache_path)
|
584
|
+
self._logger.debug(lambda: f"Cleared cache {self.cache_path}")
|
585
|
+
|
586
|
+
@_logger.call
|
587
|
+
def get_command_line_variables(self) -> List[VariableDefinition]:
|
588
|
+
from robot.utils.text import split_args_from_name_or_path
|
589
|
+
|
590
|
+
with self._command_line_variables_lock:
|
591
|
+
if self._command_line_variables is None:
|
592
|
+
command_line_vars: List[VariableDefinition] = []
|
593
|
+
|
594
|
+
command_line_vars += [
|
595
|
+
CommandLineVariableDefinition(
|
596
|
+
0,
|
597
|
+
0,
|
598
|
+
0,
|
599
|
+
0,
|
600
|
+
"",
|
601
|
+
f"${{{k}}}",
|
602
|
+
None,
|
603
|
+
has_value=True,
|
604
|
+
value=v,
|
605
|
+
)
|
606
|
+
for k, v in self.cmd_variables.items()
|
607
|
+
]
|
608
|
+
|
609
|
+
for variable_file in self.cmd_variable_files:
|
610
|
+
name, args = split_args_from_name_or_path(str(variable_file))
|
611
|
+
try:
|
612
|
+
lib_doc = self.get_libdoc_for_variables_import(
|
613
|
+
name.replace("\\", "\\\\"),
|
614
|
+
tuple(args),
|
615
|
+
str(self.root_folder),
|
616
|
+
self,
|
617
|
+
resolve_variables=False,
|
618
|
+
resolve_command_line_vars=False,
|
619
|
+
)
|
620
|
+
if lib_doc is not None:
|
621
|
+
command_line_vars += [
|
622
|
+
CommandLineVariableDefinition(
|
623
|
+
line_no=e.line_no,
|
624
|
+
col_offset=e.col_offset,
|
625
|
+
end_line_no=e.end_line_no,
|
626
|
+
end_col_offset=e.end_col_offset,
|
627
|
+
source=e.source,
|
628
|
+
name=e.name,
|
629
|
+
name_token=e.name_token,
|
630
|
+
has_value=e.has_value,
|
631
|
+
resolvable=e.resolvable,
|
632
|
+
value=e.value,
|
633
|
+
value_is_native=e.value_is_native,
|
634
|
+
)
|
635
|
+
for e in lib_doc.variables
|
636
|
+
]
|
637
|
+
|
638
|
+
if lib_doc.errors:
|
639
|
+
# TODO add diagnostics
|
640
|
+
for error in lib_doc.errors:
|
641
|
+
self._logger.error(
|
642
|
+
lambda: f"{error.type_name}: {error.message} in {error.source}:{error.line_no}"
|
643
|
+
)
|
644
|
+
except (SystemExit, KeyboardInterrupt):
|
645
|
+
raise
|
646
|
+
except BaseException as e:
|
647
|
+
# TODO add diagnostics
|
648
|
+
self._logger.exception(e)
|
649
|
+
|
650
|
+
self._command_line_variables = command_line_vars
|
651
|
+
|
652
|
+
return self._command_line_variables or []
|
653
|
+
|
654
|
+
def get_resolvable_command_line_variables(self) -> Dict[str, Any]:
|
655
|
+
with self._resolvable_command_line_variables_lock:
|
656
|
+
if self._resolvable_command_line_variables is None:
|
657
|
+
self._resolvable_command_line_variables = {
|
658
|
+
v.name: v.value for v in (self.get_command_line_variables()) if v.has_value
|
659
|
+
}
|
660
|
+
|
661
|
+
return self._resolvable_command_line_variables
|
662
|
+
|
663
|
+
@event
|
664
|
+
def libraries_changed(sender, libraries: List[LibraryDoc]) -> None:
|
665
|
+
...
|
666
|
+
|
667
|
+
@event
|
668
|
+
def resources_changed(sender, resources: List[LibraryDoc]) -> None:
|
669
|
+
...
|
670
|
+
|
671
|
+
@event
|
672
|
+
def variables_changed(sender, variables: List[LibraryDoc]) -> None:
|
673
|
+
...
|
674
|
+
|
675
|
+
@event
|
676
|
+
def imports_changed(sender, uri: DocumentUri) -> None:
|
677
|
+
...
|
678
|
+
|
679
|
+
def possible_imports_modified(self, sender: Any, uri: DocumentUri) -> None:
|
680
|
+
self.imports_changed(self, uri)
|
681
|
+
|
682
|
+
@language_id("robotframework")
|
683
|
+
def possible_resource_document_modified(self, sender: Any, document: TextDocument) -> None:
|
684
|
+
with self._resource_document_changed_timer_lock:
|
685
|
+
if document in self._resource_document_changed_documents:
|
686
|
+
return
|
687
|
+
|
688
|
+
if self._resource_document_changed_timer is not None:
|
689
|
+
self._resource_document_changed_timer.cancel()
|
690
|
+
self._resource_document_changed_timer = None
|
691
|
+
|
692
|
+
self._resource_document_changed_documents.add(document)
|
693
|
+
|
694
|
+
self._resource_document_changed_timer = threading.Timer(
|
695
|
+
self._resource_document_changed_timer_interval, self.__resource_documents_changed
|
696
|
+
)
|
697
|
+
self._resource_document_changed_timer.start()
|
698
|
+
|
699
|
+
def __resource_documents_changed(self) -> None:
|
700
|
+
with self._resource_document_changed_timer_lock:
|
701
|
+
self._resource_document_changed_timer = None
|
702
|
+
|
703
|
+
documents = self._resource_document_changed_documents
|
704
|
+
self._resource_document_changed_documents = set()
|
705
|
+
|
706
|
+
for document in documents:
|
707
|
+
run_as_task(self.__resource_document_changed, document).result()
|
708
|
+
|
709
|
+
def __resource_document_changed(self, document: TextDocument) -> None:
|
710
|
+
resource_changed: List[LibraryDoc] = []
|
711
|
+
|
712
|
+
with self._resources_lock:
|
713
|
+
for r_entry in self._resources.values():
|
714
|
+
lib_doc: Optional[LibraryDoc] = None
|
715
|
+
try:
|
716
|
+
if not r_entry.is_valid():
|
717
|
+
continue
|
718
|
+
|
719
|
+
uri = r_entry.get_document().uri
|
720
|
+
result = uri == document.uri
|
721
|
+
if result:
|
722
|
+
lib_doc = r_entry.get_libdoc()
|
723
|
+
r_entry.invalidate()
|
724
|
+
|
725
|
+
except (SystemExit, KeyboardInterrupt):
|
726
|
+
raise
|
727
|
+
except BaseException:
|
728
|
+
result = True
|
729
|
+
|
730
|
+
if result and lib_doc is not None:
|
731
|
+
resource_changed.append(lib_doc)
|
732
|
+
|
733
|
+
if resource_changed:
|
734
|
+
self.resources_changed(self, resource_changed)
|
735
|
+
|
736
|
+
@_logger.call
|
737
|
+
def did_change_watched_files(self, sender: Any, changes: List[FileEvent]) -> None:
|
738
|
+
libraries_changed: List[Tuple[_LibrariesEntryKey, FileChangeType, Optional[LibraryDoc]]] = []
|
739
|
+
resource_changed: List[Tuple[_ResourcesEntryKey, FileChangeType, Optional[LibraryDoc]]] = []
|
740
|
+
variables_changed: List[Tuple[_VariablesEntryKey, FileChangeType, Optional[LibraryDoc]]] = []
|
741
|
+
|
742
|
+
lib_doc: Optional[LibraryDoc]
|
743
|
+
|
744
|
+
with self._libaries_lock:
|
745
|
+
for l_key, l_entry in self._libaries.items():
|
746
|
+
lib_doc = None
|
747
|
+
if l_entry.is_valid():
|
748
|
+
lib_doc = l_entry.get_libdoc()
|
749
|
+
result = l_entry.check_file_changed(changes)
|
750
|
+
if result is not None:
|
751
|
+
libraries_changed.append((l_key, result, lib_doc))
|
752
|
+
|
753
|
+
try:
|
754
|
+
with self._resources_lock:
|
755
|
+
for r_key, r_entry in self._resources.items():
|
756
|
+
lib_doc = None
|
757
|
+
if r_entry.is_valid():
|
758
|
+
lib_doc = r_entry.get_libdoc()
|
759
|
+
result = r_entry.check_file_changed(changes)
|
760
|
+
if result is not None:
|
761
|
+
resource_changed.append((r_key, result, lib_doc))
|
762
|
+
except BaseException as e:
|
763
|
+
self._logger.exception(e)
|
764
|
+
raise
|
765
|
+
|
766
|
+
with self._variables_lock:
|
767
|
+
for v_key, v_entry in self._variables.items():
|
768
|
+
lib_doc = None
|
769
|
+
if v_entry.is_valid():
|
770
|
+
lib_doc = v_entry.get_libdoc()
|
771
|
+
result = v_entry.check_file_changed(changes)
|
772
|
+
if result is not None:
|
773
|
+
variables_changed.append((v_key, result, lib_doc))
|
774
|
+
|
775
|
+
if libraries_changed:
|
776
|
+
for l, t, _ in libraries_changed:
|
777
|
+
if t == FileChangeType.DELETED:
|
778
|
+
self.__remove_library_entry(l, self._libaries[l], True)
|
779
|
+
|
780
|
+
self.libraries_changed(self, [v for (_, _, v) in libraries_changed if v is not None])
|
781
|
+
|
782
|
+
if resource_changed:
|
783
|
+
for r, t, _ in resource_changed:
|
784
|
+
if t == FileChangeType.DELETED:
|
785
|
+
self.__remove_resource_entry(r, self._resources[r], True)
|
786
|
+
|
787
|
+
self.resources_changed(self, [v for (_, _, v) in resource_changed if v is not None])
|
788
|
+
|
789
|
+
if variables_changed:
|
790
|
+
for v, t, _ in variables_changed:
|
791
|
+
if t == FileChangeType.DELETED:
|
792
|
+
self.__remove_variables_entry(v, self._variables[v], True)
|
793
|
+
|
794
|
+
self.variables_changed(self, [v for (_, _, v) in variables_changed if v is not None])
|
795
|
+
|
796
|
+
def __remove_library_entry(
|
797
|
+
self,
|
798
|
+
entry_key: _LibrariesEntryKey,
|
799
|
+
entry: _LibrariesEntry,
|
800
|
+
now: bool = False,
|
801
|
+
) -> None:
|
802
|
+
try:
|
803
|
+
if len(entry.references) == 0 or now:
|
804
|
+
self._logger.debug(lambda: f"Remove Library Entry {entry_key}")
|
805
|
+
with self._libaries_lock:
|
806
|
+
if len(entry.references) == 0:
|
807
|
+
e1 = self._libaries.get(entry_key, None)
|
808
|
+
if e1 == entry:
|
809
|
+
self._libaries.pop(entry_key, None)
|
810
|
+
|
811
|
+
entry.invalidate()
|
812
|
+
self._logger.debug(lambda: f"Library Entry {entry_key} removed")
|
813
|
+
finally:
|
814
|
+
self._library_files_cache.clear()
|
815
|
+
|
816
|
+
def __remove_resource_entry(
|
817
|
+
self,
|
818
|
+
entry_key: _ResourcesEntryKey,
|
819
|
+
entry: _ResourcesEntry,
|
820
|
+
now: bool = False,
|
821
|
+
) -> None:
|
822
|
+
try:
|
823
|
+
if len(entry.references) == 0 or now:
|
824
|
+
self._logger.debug(lambda: f"Remove Resource Entry {entry_key}")
|
825
|
+
with self._resources_lock:
|
826
|
+
if len(entry.references) == 0 or now:
|
827
|
+
e1 = self._resources.get(entry_key, None)
|
828
|
+
if e1 == entry:
|
829
|
+
self._resources.pop(entry_key, None)
|
830
|
+
|
831
|
+
entry.invalidate()
|
832
|
+
self._logger.debug(lambda: f"Resource Entry {entry_key} removed")
|
833
|
+
finally:
|
834
|
+
self._resource_files_cache.clear()
|
835
|
+
|
836
|
+
def __remove_variables_entry(
|
837
|
+
self,
|
838
|
+
entry_key: _VariablesEntryKey,
|
839
|
+
entry: _VariablesEntry,
|
840
|
+
now: bool = False,
|
841
|
+
) -> None:
|
842
|
+
try:
|
843
|
+
if len(entry.references) == 0 or now:
|
844
|
+
self._logger.debug(lambda: f"Remove Variables Entry {entry_key}")
|
845
|
+
with self._variables_lock:
|
846
|
+
if len(entry.references) == 0:
|
847
|
+
e1 = self._variables.get(entry_key, None)
|
848
|
+
if e1 == entry:
|
849
|
+
self._variables.pop(entry_key, None)
|
850
|
+
|
851
|
+
entry.invalidate()
|
852
|
+
self._logger.debug(lambda: f"Variables Entry {entry_key} removed")
|
853
|
+
finally:
|
854
|
+
self._variables_files_cache.clear()
|
855
|
+
|
856
|
+
def get_library_meta(
|
857
|
+
self,
|
858
|
+
name: str,
|
859
|
+
base_dir: str = ".",
|
860
|
+
variables: Optional[Dict[str, Optional[Any]]] = None,
|
861
|
+
) -> Tuple[Optional[LibraryMetaData], str]:
|
862
|
+
try:
|
863
|
+
import_name = self.find_library(name, base_dir=base_dir, variables=variables)
|
864
|
+
|
865
|
+
result: Optional[LibraryMetaData] = None
|
866
|
+
module_spec: Optional[ModuleSpec] = None
|
867
|
+
if is_library_by_path(import_name):
|
868
|
+
if (p := Path(import_name)).exists():
|
869
|
+
result = LibraryMetaData(__version__, p.stem, None, import_name, None, True)
|
870
|
+
else:
|
871
|
+
module_spec = get_module_spec(import_name)
|
872
|
+
if module_spec is not None and module_spec.origin is not None:
|
873
|
+
result = LibraryMetaData(
|
874
|
+
__version__,
|
875
|
+
module_spec.name,
|
876
|
+
module_spec.member_name,
|
877
|
+
module_spec.origin,
|
878
|
+
module_spec.submodule_search_locations,
|
879
|
+
False,
|
880
|
+
)
|
881
|
+
|
882
|
+
if result is not None:
|
883
|
+
if any(
|
884
|
+
(p.matches(result.name) if result.name is not None else False)
|
885
|
+
or (p.matches(result.origin) if result.origin is not None else False)
|
886
|
+
for p in self.ignored_libraries_patters
|
887
|
+
):
|
888
|
+
self._logger.debug(
|
889
|
+
lambda: f"Ignore library {result.name or '' if result is not None else ''}"
|
890
|
+
f" {result.origin or '' if result is not None else ''} for caching."
|
891
|
+
)
|
892
|
+
return None, import_name
|
893
|
+
|
894
|
+
if result.origin is not None:
|
895
|
+
result.mtimes = {result.origin: Path(result.origin).stat().st_mtime_ns}
|
896
|
+
|
897
|
+
if result.submodule_search_locations:
|
898
|
+
if result.mtimes is None:
|
899
|
+
result.mtimes = {}
|
900
|
+
result.mtimes.update(
|
901
|
+
{
|
902
|
+
str(f): f.stat().st_mtime_ns
|
903
|
+
for f in itertools.chain(
|
904
|
+
*(iter_files(loc, "**/*.py") for loc in result.submodule_search_locations)
|
905
|
+
)
|
906
|
+
}
|
907
|
+
)
|
908
|
+
|
909
|
+
return result, import_name
|
910
|
+
except (SystemExit, KeyboardInterrupt):
|
911
|
+
raise
|
912
|
+
except BaseException:
|
913
|
+
pass
|
914
|
+
|
915
|
+
return None, import_name
|
916
|
+
|
917
|
+
def get_variables_meta(
|
918
|
+
self,
|
919
|
+
name: str,
|
920
|
+
base_dir: str = ".",
|
921
|
+
variables: Optional[Dict[str, Optional[Any]]] = None,
|
922
|
+
resolve_variables: bool = True,
|
923
|
+
resolve_command_line_vars: bool = True,
|
924
|
+
) -> Tuple[Optional[LibraryMetaData], str]:
|
925
|
+
try:
|
926
|
+
import_name = self.find_variables(
|
927
|
+
name,
|
928
|
+
base_dir=base_dir,
|
929
|
+
variables=variables,
|
930
|
+
resolve_variables=resolve_variables,
|
931
|
+
resolve_command_line_vars=resolve_command_line_vars,
|
932
|
+
)
|
933
|
+
|
934
|
+
result: Optional[LibraryMetaData] = None
|
935
|
+
module_spec: Optional[ModuleSpec] = None
|
936
|
+
if is_variables_by_path(import_name):
|
937
|
+
if (p := Path(import_name)).exists():
|
938
|
+
result = LibraryMetaData(__version__, p.stem, None, import_name, None, True)
|
939
|
+
else:
|
940
|
+
module_spec = get_module_spec(import_name)
|
941
|
+
if module_spec is not None and module_spec.origin is not None:
|
942
|
+
result = LibraryMetaData(
|
943
|
+
__version__,
|
944
|
+
module_spec.name,
|
945
|
+
module_spec.member_name,
|
946
|
+
module_spec.origin,
|
947
|
+
module_spec.submodule_search_locations,
|
948
|
+
False,
|
949
|
+
)
|
950
|
+
|
951
|
+
if result is not None:
|
952
|
+
if any(
|
953
|
+
(p.matches(result.name) if result.name is not None else False)
|
954
|
+
or (p.matches(result.origin) if result.origin is not None else False)
|
955
|
+
for p in self.ignored_variables_patters
|
956
|
+
):
|
957
|
+
self._logger.debug(
|
958
|
+
lambda: f"Ignore Variables {result.name or '' if result is not None else ''}"
|
959
|
+
f" {result.origin or '' if result is not None else ''} for caching."
|
960
|
+
)
|
961
|
+
return None, import_name
|
962
|
+
|
963
|
+
if result.origin is not None:
|
964
|
+
result.mtimes = {result.origin: Path(result.origin).stat().st_mtime_ns}
|
965
|
+
|
966
|
+
if result.submodule_search_locations:
|
967
|
+
if result.mtimes is None:
|
968
|
+
result.mtimes = {}
|
969
|
+
result.mtimes.update(
|
970
|
+
{
|
971
|
+
str(f): f.stat().st_mtime_ns
|
972
|
+
for f in itertools.chain(
|
973
|
+
*(iter_files(loc, "**/*.py") for loc in result.submodule_search_locations)
|
974
|
+
)
|
975
|
+
}
|
976
|
+
)
|
977
|
+
|
978
|
+
return result, import_name
|
979
|
+
except (SystemExit, KeyboardInterrupt):
|
980
|
+
raise
|
981
|
+
except BaseException:
|
982
|
+
pass
|
983
|
+
|
984
|
+
return None, name
|
985
|
+
|
986
|
+
def find_library(
|
987
|
+
self,
|
988
|
+
name: str,
|
989
|
+
base_dir: str,
|
990
|
+
variables: Optional[Dict[str, Any]] = None,
|
991
|
+
) -> str:
|
992
|
+
return self._library_files_cache.get(self._find_library, name, base_dir, variables)
|
993
|
+
|
994
|
+
def _find_library(
|
995
|
+
self,
|
996
|
+
name: str,
|
997
|
+
base_dir: str,
|
998
|
+
variables: Optional[Dict[str, Any]] = None,
|
999
|
+
) -> str:
|
1000
|
+
from robot.libraries import STDLIBS
|
1001
|
+
from robot.variables.search import contains_variable
|
1002
|
+
|
1003
|
+
if contains_variable(name, "$@&%"):
|
1004
|
+
return find_library(
|
1005
|
+
name,
|
1006
|
+
str(self.root_folder),
|
1007
|
+
base_dir,
|
1008
|
+
self.get_resolvable_command_line_variables(),
|
1009
|
+
variables,
|
1010
|
+
)
|
1011
|
+
|
1012
|
+
if name in STDLIBS:
|
1013
|
+
result = ROBOT_LIBRARY_PACKAGE + "." + name
|
1014
|
+
else:
|
1015
|
+
result = name
|
1016
|
+
|
1017
|
+
if is_library_by_path(result):
|
1018
|
+
return find_file_ex(result, base_dir, "Library")
|
1019
|
+
|
1020
|
+
return result
|
1021
|
+
|
1022
|
+
def find_resource(
|
1023
|
+
self,
|
1024
|
+
name: str,
|
1025
|
+
base_dir: str,
|
1026
|
+
file_type: str = "Resource",
|
1027
|
+
variables: Optional[Dict[str, Any]] = None,
|
1028
|
+
) -> str:
|
1029
|
+
return self._resource_files_cache.get(self.__find_resource, name, base_dir, file_type, variables)
|
1030
|
+
|
1031
|
+
@_logger.call
|
1032
|
+
def __find_resource(
|
1033
|
+
self,
|
1034
|
+
name: str,
|
1035
|
+
base_dir: str,
|
1036
|
+
file_type: str = "Resource",
|
1037
|
+
variables: Optional[Dict[str, Any]] = None,
|
1038
|
+
) -> str:
|
1039
|
+
from robot.variables.search import contains_variable
|
1040
|
+
|
1041
|
+
if contains_variable(name, "$@&%"):
|
1042
|
+
return find_file(
|
1043
|
+
name,
|
1044
|
+
str(self.root_folder),
|
1045
|
+
base_dir,
|
1046
|
+
self.get_resolvable_command_line_variables(),
|
1047
|
+
variables,
|
1048
|
+
file_type,
|
1049
|
+
)
|
1050
|
+
|
1051
|
+
return str(find_file_ex(name, base_dir, file_type))
|
1052
|
+
|
1053
|
+
def find_variables(
|
1054
|
+
self,
|
1055
|
+
name: str,
|
1056
|
+
base_dir: str,
|
1057
|
+
variables: Optional[Dict[str, Any]] = None,
|
1058
|
+
resolve_variables: bool = True,
|
1059
|
+
resolve_command_line_vars: bool = True,
|
1060
|
+
) -> str:
|
1061
|
+
return self._variables_files_cache.get(
|
1062
|
+
self.__find_variables,
|
1063
|
+
name,
|
1064
|
+
base_dir,
|
1065
|
+
variables,
|
1066
|
+
resolve_command_line_vars,
|
1067
|
+
)
|
1068
|
+
|
1069
|
+
@_logger.call
|
1070
|
+
def __find_variables(
|
1071
|
+
self,
|
1072
|
+
name: str,
|
1073
|
+
base_dir: str,
|
1074
|
+
variables: Optional[Dict[str, Any]] = None,
|
1075
|
+
resolve_variables: bool = True,
|
1076
|
+
resolve_command_line_vars: bool = True,
|
1077
|
+
) -> str:
|
1078
|
+
from robot.variables.search import contains_variable
|
1079
|
+
|
1080
|
+
if resolve_variables and contains_variable(name, "$@&%"):
|
1081
|
+
return find_variables(
|
1082
|
+
name,
|
1083
|
+
str(self.root_folder),
|
1084
|
+
base_dir,
|
1085
|
+
self.get_resolvable_command_line_variables() if resolve_command_line_vars else None,
|
1086
|
+
variables,
|
1087
|
+
)
|
1088
|
+
|
1089
|
+
if get_robot_version() >= (5, 0):
|
1090
|
+
if is_variables_by_path(name):
|
1091
|
+
return str(find_file_ex(name, base_dir, "Variables"))
|
1092
|
+
|
1093
|
+
return name
|
1094
|
+
|
1095
|
+
return str(find_file_ex(name, base_dir, "Variables"))
|
1096
|
+
|
1097
|
+
@property
|
1098
|
+
def executor(self) -> ProcessPoolExecutor:
|
1099
|
+
with self._executor_lock:
|
1100
|
+
if self._executor is None:
|
1101
|
+
self._executor = ProcessPoolExecutor(mp_context=mp.get_context("spawn"))
|
1102
|
+
|
1103
|
+
return self._executor
|
1104
|
+
|
1105
|
+
@_logger.call
|
1106
|
+
def get_libdoc_for_library_import(
|
1107
|
+
self,
|
1108
|
+
name: str,
|
1109
|
+
args: Tuple[Any, ...],
|
1110
|
+
base_dir: str,
|
1111
|
+
sentinel: Any = None,
|
1112
|
+
variables: Optional[Dict[str, Any]] = None,
|
1113
|
+
) -> LibraryDoc:
|
1114
|
+
source = self.find_library(name, base_dir, variables)
|
1115
|
+
|
1116
|
+
def _get_libdoc(name: str, args: Tuple[Any, ...], working_dir: str, base_dir: str) -> LibraryDoc:
|
1117
|
+
meta, source = self.get_library_meta(name, base_dir, variables)
|
1118
|
+
|
1119
|
+
self._logger.debug(lambda: f"Load Library {source}{args!r}")
|
1120
|
+
|
1121
|
+
if meta is not None:
|
1122
|
+
meta_file = Path(self.lib_doc_cache_path, meta.filepath_base + ".meta.json")
|
1123
|
+
if meta_file.exists():
|
1124
|
+
try:
|
1125
|
+
spec_path = None
|
1126
|
+
try:
|
1127
|
+
saved_meta = from_json(meta_file.read_text("utf-8"), LibraryMetaData)
|
1128
|
+
if saved_meta == meta:
|
1129
|
+
spec_path = Path(
|
1130
|
+
self.lib_doc_cache_path,
|
1131
|
+
meta.filepath_base + ".spec.json",
|
1132
|
+
)
|
1133
|
+
return from_json(spec_path.read_text("utf-8"), LibraryDoc)
|
1134
|
+
except (SystemExit, KeyboardInterrupt):
|
1135
|
+
raise
|
1136
|
+
except BaseException as e:
|
1137
|
+
raise RuntimeError(
|
1138
|
+
f"Failed to load library meta data for library {name} from {spec_path}"
|
1139
|
+
) from e
|
1140
|
+
except (SystemExit, KeyboardInterrupt):
|
1141
|
+
raise
|
1142
|
+
except BaseException as e:
|
1143
|
+
self._logger.exception(e)
|
1144
|
+
|
1145
|
+
executor = ProcessPoolExecutor(max_workers=1, mp_context=mp.get_context("spawn"))
|
1146
|
+
try:
|
1147
|
+
result = executor.submit(
|
1148
|
+
get_library_doc,
|
1149
|
+
name,
|
1150
|
+
args,
|
1151
|
+
working_dir,
|
1152
|
+
base_dir,
|
1153
|
+
self.get_resolvable_command_line_variables(),
|
1154
|
+
variables,
|
1155
|
+
).result(LOAD_LIBRARY_TIME_OUT)
|
1156
|
+
|
1157
|
+
except (SystemExit, KeyboardInterrupt):
|
1158
|
+
raise
|
1159
|
+
except BaseException as e:
|
1160
|
+
self._logger.exception(e)
|
1161
|
+
raise
|
1162
|
+
finally:
|
1163
|
+
executor.shutdown(wait=True)
|
1164
|
+
|
1165
|
+
if result.stdout:
|
1166
|
+
self._logger.warning(lambda: f"stdout captured at loading library {name}{args!r}:\n{result.stdout}")
|
1167
|
+
try:
|
1168
|
+
if meta is not None:
|
1169
|
+
meta_file = Path(
|
1170
|
+
self.lib_doc_cache_path,
|
1171
|
+
meta.filepath_base + ".meta.json",
|
1172
|
+
)
|
1173
|
+
spec_file = Path(
|
1174
|
+
self.lib_doc_cache_path,
|
1175
|
+
meta.filepath_base + ".spec.json",
|
1176
|
+
)
|
1177
|
+
spec_file.parent.mkdir(parents=True, exist_ok=True)
|
1178
|
+
|
1179
|
+
try:
|
1180
|
+
spec_file.write_text(as_json(result), "utf-8")
|
1181
|
+
except (SystemExit, KeyboardInterrupt):
|
1182
|
+
raise
|
1183
|
+
except BaseException as e:
|
1184
|
+
raise RuntimeError(f"Cannot write spec file for library '{name}' to '{spec_file}'") from e
|
1185
|
+
|
1186
|
+
meta_file.write_text(as_json(meta), "utf-8")
|
1187
|
+
else:
|
1188
|
+
self._logger.debug(lambda: f"Skip caching library {name}{args!r}")
|
1189
|
+
except (SystemExit, KeyboardInterrupt):
|
1190
|
+
raise
|
1191
|
+
except BaseException as e:
|
1192
|
+
self._logger.exception(e)
|
1193
|
+
|
1194
|
+
return result
|
1195
|
+
|
1196
|
+
resolved_args = resolve_args(
|
1197
|
+
args,
|
1198
|
+
str(self.root_folder),
|
1199
|
+
base_dir,
|
1200
|
+
self.get_resolvable_command_line_variables(),
|
1201
|
+
variables,
|
1202
|
+
)
|
1203
|
+
entry_key = _LibrariesEntryKey(source, resolved_args)
|
1204
|
+
|
1205
|
+
with self._libaries_lock:
|
1206
|
+
if entry_key not in self._libaries:
|
1207
|
+
self._libaries[entry_key] = _LibrariesEntry(
|
1208
|
+
self,
|
1209
|
+
name,
|
1210
|
+
args,
|
1211
|
+
str(self.root_folder),
|
1212
|
+
base_dir,
|
1213
|
+
_get_libdoc,
|
1214
|
+
ignore_reference=sentinel is None,
|
1215
|
+
)
|
1216
|
+
|
1217
|
+
entry = self._libaries[entry_key]
|
1218
|
+
|
1219
|
+
if not entry.ignore_reference and sentinel is not None and sentinel not in entry.references:
|
1220
|
+
entry.references.add(sentinel)
|
1221
|
+
weakref.finalize(sentinel, self.__remove_library_entry, entry_key, entry)
|
1222
|
+
|
1223
|
+
return entry.get_libdoc()
|
1224
|
+
|
1225
|
+
@_logger.call
|
1226
|
+
def get_libdoc_from_model(
|
1227
|
+
self,
|
1228
|
+
model: ast.AST,
|
1229
|
+
source: str,
|
1230
|
+
model_type: str = "RESOURCE",
|
1231
|
+
scope: str = "GLOBAL",
|
1232
|
+
append_model_errors: bool = True,
|
1233
|
+
) -> LibraryDoc:
|
1234
|
+
return get_model_doc(
|
1235
|
+
model=model,
|
1236
|
+
source=source,
|
1237
|
+
model_type=model_type,
|
1238
|
+
scope=scope,
|
1239
|
+
append_model_errors=append_model_errors,
|
1240
|
+
)
|
1241
|
+
|
1242
|
+
@_logger.call
|
1243
|
+
def get_libdoc_for_variables_import(
|
1244
|
+
self,
|
1245
|
+
name: str,
|
1246
|
+
args: Tuple[Any, ...],
|
1247
|
+
base_dir: str,
|
1248
|
+
sentinel: Any = None,
|
1249
|
+
variables: Optional[Dict[str, Any]] = None,
|
1250
|
+
resolve_variables: bool = True,
|
1251
|
+
resolve_command_line_vars: bool = True,
|
1252
|
+
) -> VariablesDoc:
|
1253
|
+
source = self.find_variables(
|
1254
|
+
name,
|
1255
|
+
base_dir,
|
1256
|
+
variables,
|
1257
|
+
resolve_variables=resolve_variables,
|
1258
|
+
resolve_command_line_vars=resolve_command_line_vars,
|
1259
|
+
)
|
1260
|
+
|
1261
|
+
def _get_libdoc(name: str, args: Tuple[Any, ...], working_dir: str, base_dir: str) -> VariablesDoc:
|
1262
|
+
meta, source = self.get_variables_meta(
|
1263
|
+
name,
|
1264
|
+
base_dir,
|
1265
|
+
variables,
|
1266
|
+
resolve_command_line_vars=resolve_command_line_vars,
|
1267
|
+
)
|
1268
|
+
|
1269
|
+
self._logger.debug(lambda: f"Load variables {source}{args!r}")
|
1270
|
+
if meta is not None:
|
1271
|
+
meta_file = Path(
|
1272
|
+
self.variables_doc_cache_path,
|
1273
|
+
meta.filepath_base + ".meta.json",
|
1274
|
+
)
|
1275
|
+
if meta_file.exists():
|
1276
|
+
try:
|
1277
|
+
spec_path = None
|
1278
|
+
try:
|
1279
|
+
saved_meta = from_json(meta_file.read_text("utf-8"), LibraryMetaData)
|
1280
|
+
if saved_meta == meta:
|
1281
|
+
spec_path = Path(
|
1282
|
+
self.variables_doc_cache_path,
|
1283
|
+
meta.filepath_base + ".spec.json",
|
1284
|
+
)
|
1285
|
+
return from_json(spec_path.read_text("utf-8"), VariablesDoc)
|
1286
|
+
except (SystemExit, KeyboardInterrupt):
|
1287
|
+
raise
|
1288
|
+
except BaseException as e:
|
1289
|
+
raise RuntimeError(
|
1290
|
+
f"Failed to load library meta data for library {name} from {spec_path}"
|
1291
|
+
) from e
|
1292
|
+
except (SystemExit, KeyboardInterrupt):
|
1293
|
+
raise
|
1294
|
+
except BaseException as e:
|
1295
|
+
self._logger.exception(e)
|
1296
|
+
|
1297
|
+
executor = ProcessPoolExecutor(max_workers=1, mp_context=mp.get_context("spawn"))
|
1298
|
+
try:
|
1299
|
+
result = executor.submit(
|
1300
|
+
get_variables_doc,
|
1301
|
+
name,
|
1302
|
+
args,
|
1303
|
+
str(self.root_folder),
|
1304
|
+
base_dir,
|
1305
|
+
self.get_resolvable_command_line_variables() if resolve_command_line_vars else None,
|
1306
|
+
variables,
|
1307
|
+
).result(LOAD_LIBRARY_TIME_OUT)
|
1308
|
+
except (SystemExit, KeyboardInterrupt):
|
1309
|
+
raise
|
1310
|
+
except BaseException as e:
|
1311
|
+
self._logger.exception(e)
|
1312
|
+
raise
|
1313
|
+
finally:
|
1314
|
+
executor.shutdown(True)
|
1315
|
+
|
1316
|
+
if result.stdout:
|
1317
|
+
self._logger.warning(lambda: f"stdout captured at loading variables {name}{args!r}:\n{result.stdout}")
|
1318
|
+
|
1319
|
+
try:
|
1320
|
+
if meta is not None:
|
1321
|
+
meta_file = Path(
|
1322
|
+
self.variables_doc_cache_path,
|
1323
|
+
meta.filepath_base + ".meta.json",
|
1324
|
+
)
|
1325
|
+
spec_file = Path(
|
1326
|
+
self.variables_doc_cache_path,
|
1327
|
+
meta.filepath_base + ".spec.json",
|
1328
|
+
)
|
1329
|
+
spec_file.parent.mkdir(parents=True, exist_ok=True)
|
1330
|
+
|
1331
|
+
try:
|
1332
|
+
spec_file.write_text(as_json(result), "utf-8")
|
1333
|
+
except (SystemExit, KeyboardInterrupt):
|
1334
|
+
raise
|
1335
|
+
except BaseException as e:
|
1336
|
+
raise RuntimeError(f"Cannot write spec file for variables '{name}' to '{spec_file}'") from e
|
1337
|
+
meta_file.write_text(as_json(meta), "utf-8")
|
1338
|
+
else:
|
1339
|
+
self._logger.debug(lambda: f"Skip caching variables {name}{args!r}")
|
1340
|
+
except (SystemExit, KeyboardInterrupt):
|
1341
|
+
raise
|
1342
|
+
except BaseException as e:
|
1343
|
+
self._logger.exception(e)
|
1344
|
+
|
1345
|
+
return result
|
1346
|
+
|
1347
|
+
resolved_args = resolve_args(
|
1348
|
+
args,
|
1349
|
+
str(self.root_folder),
|
1350
|
+
base_dir,
|
1351
|
+
self.get_resolvable_command_line_variables() if resolve_command_line_vars else None,
|
1352
|
+
variables,
|
1353
|
+
)
|
1354
|
+
entry_key = _VariablesEntryKey(source, resolved_args)
|
1355
|
+
|
1356
|
+
with self._variables_lock:
|
1357
|
+
if entry_key not in self._variables:
|
1358
|
+
self._variables[entry_key] = _VariablesEntry(
|
1359
|
+
name,
|
1360
|
+
resolved_args,
|
1361
|
+
str(self.root_folder),
|
1362
|
+
base_dir,
|
1363
|
+
self,
|
1364
|
+
_get_libdoc,
|
1365
|
+
)
|
1366
|
+
|
1367
|
+
entry = self._variables[entry_key]
|
1368
|
+
|
1369
|
+
if sentinel is not None and sentinel not in entry.references:
|
1370
|
+
entry.references.add(sentinel)
|
1371
|
+
weakref.finalize(sentinel, self.__remove_variables_entry, entry_key, entry)
|
1372
|
+
|
1373
|
+
return entry.get_libdoc()
|
1374
|
+
|
1375
|
+
@_logger.call
|
1376
|
+
def _get_entry_for_resource_import(
|
1377
|
+
self,
|
1378
|
+
name: str,
|
1379
|
+
base_dir: str,
|
1380
|
+
sentinel: Any = None,
|
1381
|
+
variables: Optional[Dict[str, Any]] = None,
|
1382
|
+
) -> _ResourcesEntry:
|
1383
|
+
source = self.find_resource(name, base_dir, variables=variables)
|
1384
|
+
|
1385
|
+
def _get_document() -> TextDocument:
|
1386
|
+
self._logger.debug(lambda: f"Load resource {name} from source {source}")
|
1387
|
+
|
1388
|
+
source_path = Path(source).resolve()
|
1389
|
+
extension = source_path.suffix
|
1390
|
+
if extension.lower() not in RESOURCE_EXTENSIONS:
|
1391
|
+
raise ImportError(
|
1392
|
+
f"Invalid resource file extension '{extension}'. "
|
1393
|
+
f"Supported extensions are {', '.join(repr(s) for s in RESOURCE_EXTENSIONS)}."
|
1394
|
+
)
|
1395
|
+
|
1396
|
+
return self.documents_manager.get_or_open_document(source_path)
|
1397
|
+
|
1398
|
+
entry_key = _ResourcesEntryKey(source)
|
1399
|
+
|
1400
|
+
with self._resources_lock:
|
1401
|
+
if entry_key not in self._resources:
|
1402
|
+
self._resources[entry_key] = _ResourcesEntry(name, self, _get_document)
|
1403
|
+
|
1404
|
+
entry = self._resources[entry_key]
|
1405
|
+
|
1406
|
+
if sentinel is not None and sentinel not in entry.references:
|
1407
|
+
entry.references.add(sentinel)
|
1408
|
+
weakref.finalize(sentinel, self.__remove_resource_entry, entry_key, entry)
|
1409
|
+
|
1410
|
+
return entry
|
1411
|
+
|
1412
|
+
def get_namespace_and_libdoc_for_resource_import(
|
1413
|
+
self,
|
1414
|
+
name: str,
|
1415
|
+
base_dir: str,
|
1416
|
+
sentinel: Any = None,
|
1417
|
+
variables: Optional[Dict[str, Any]] = None,
|
1418
|
+
) -> Tuple["Namespace", LibraryDoc]:
|
1419
|
+
entry = self._get_entry_for_resource_import(name, base_dir, sentinel, variables)
|
1420
|
+
|
1421
|
+
return entry.get_namespace(), entry.get_libdoc()
|
1422
|
+
|
1423
|
+
def get_namespace_for_resource_import(
|
1424
|
+
self,
|
1425
|
+
name: str,
|
1426
|
+
base_dir: str,
|
1427
|
+
sentinel: Any = None,
|
1428
|
+
variables: Optional[Dict[str, Any]] = None,
|
1429
|
+
) -> "Namespace":
|
1430
|
+
entry = self._get_entry_for_resource_import(name, base_dir, sentinel, variables)
|
1431
|
+
|
1432
|
+
return entry.get_namespace()
|
1433
|
+
|
1434
|
+
def get_libdoc_for_resource_import(
|
1435
|
+
self,
|
1436
|
+
name: str,
|
1437
|
+
base_dir: str,
|
1438
|
+
sentinel: Any = None,
|
1439
|
+
variables: Optional[Dict[str, Any]] = None,
|
1440
|
+
) -> LibraryDoc:
|
1441
|
+
entry = self._get_entry_for_resource_import(name, base_dir, sentinel, variables)
|
1442
|
+
|
1443
|
+
return entry.get_libdoc()
|
1444
|
+
|
1445
|
+
def complete_library_import(
|
1446
|
+
self,
|
1447
|
+
name: Optional[str],
|
1448
|
+
base_dir: str = ".",
|
1449
|
+
variables: Optional[Dict[str, Any]] = None,
|
1450
|
+
) -> List[CompleteResult]:
|
1451
|
+
return complete_library_import(
|
1452
|
+
name,
|
1453
|
+
str(self.root_folder),
|
1454
|
+
base_dir,
|
1455
|
+
self.get_resolvable_command_line_variables(),
|
1456
|
+
variables,
|
1457
|
+
)
|
1458
|
+
|
1459
|
+
def complete_resource_import(
|
1460
|
+
self,
|
1461
|
+
name: Optional[str],
|
1462
|
+
base_dir: str = ".",
|
1463
|
+
variables: Optional[Dict[str, Any]] = None,
|
1464
|
+
) -> Optional[List[CompleteResult]]:
|
1465
|
+
return complete_resource_import(
|
1466
|
+
name,
|
1467
|
+
str(self.root_folder),
|
1468
|
+
base_dir,
|
1469
|
+
self.get_resolvable_command_line_variables(),
|
1470
|
+
variables,
|
1471
|
+
)
|
1472
|
+
|
1473
|
+
def complete_variables_import(
|
1474
|
+
self,
|
1475
|
+
name: Optional[str],
|
1476
|
+
base_dir: str = ".",
|
1477
|
+
variables: Optional[Dict[str, Any]] = None,
|
1478
|
+
) -> Optional[List[CompleteResult]]:
|
1479
|
+
return complete_variables_import(
|
1480
|
+
name,
|
1481
|
+
str(self.root_folder),
|
1482
|
+
base_dir,
|
1483
|
+
self.get_resolvable_command_line_variables(),
|
1484
|
+
variables,
|
1485
|
+
)
|
1486
|
+
|
1487
|
+
def resolve_variable(
|
1488
|
+
self,
|
1489
|
+
name: str,
|
1490
|
+
base_dir: str = ".",
|
1491
|
+
variables: Optional[Dict[str, Any]] = None,
|
1492
|
+
) -> Any:
|
1493
|
+
return resolve_variable(
|
1494
|
+
name,
|
1495
|
+
str(self.root_folder),
|
1496
|
+
base_dir,
|
1497
|
+
self.get_resolvable_command_line_variables(),
|
1498
|
+
variables,
|
1499
|
+
)
|