Skip to content

Commit 461212d

Browse files
committed
feat(analyze,robot): add configurable load_library_timeout with CLI flag and robot.toml support
Add configurable library/variable load timeout. Sources (precedence): analyze code: --load-library-timeout / --no-load-library-timeout VS Code setting: robotcode.analysis.robot.loadLibraryTimeout robot.toml / pyproject: [tool.robotcode-analyze] load-library-timeout Env: ROBOTCODE_LOAD_LIBRARY_TIMEOUT Default: 10 Replaces previous hard-coded timeout, adds validation (>0), clearer docs, and examples. Signed-off-by: Daniel Biehl <dbiehl@live.de>
1 parent d019a60 commit 461212d

File tree

7 files changed

+171
-6
lines changed

7 files changed

+171
-6
lines changed

docs/03_reference/config.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3118,3 +3118,48 @@ option is specified.
31183118
corresponds to the `-x --xunit file` option of _robot_
31193119

31203120

3121+
## tool.robotcode-analyze.global-library-search-order
3122+
3123+
Type: `list[str] | None`
3124+
3125+
Specifies a global search order for libraries and resources used during static analysis.
3126+
This helps disambiguate keywords with identical names coming from multiple sources.
3127+
RobotCode cannot infer dynamic changes done at runtime via `Set Library Search Order`, so
3128+
define a deterministic order here if needed.
3129+
3130+
Examples:
3131+
3132+
```toml
3133+
[tool.robotcode-analyze]
3134+
global_library_search_order = ["MyLib", "BuiltIn", "Collections"]
3135+
```
3136+
3137+
## tool.robotcode-analyze.load-library-timeout
3138+
3139+
Type: `int | None`
3140+
3141+
Specifies the timeout in seconds for loading (importing) libraries and variable files during analysis. Increase this if your libraries perform heavy initialization (network calls, large dependency graphs, model loading, etc.).
3142+
3143+
Must be > 0 when set. If you omit this key, RobotCode will instead look for the environment variable `ROBOTCODE_LOAD_LIBRARY_TIMEOUT`; otherwise it will use the internal default `10`.
3144+
3145+
Examples:
3146+
3147+
```toml
3148+
[tool.robotcode-analyze]
3149+
# Fast fail if libraries normally import in < 2s
3150+
load_library_timeout = 5
3151+
```
3152+
3153+
```toml
3154+
[tool.robotcode-analyze]
3155+
# Allow heavy bootstrap (e.g. Selenium + large resource trees)
3156+
load_library_timeout = 30
3157+
```
3158+
3159+
```toml
3160+
[tool.robotcode-analyze]
3161+
# Omit to use default
3162+
# load_library_timeout = 15
3163+
```
3164+
3165+

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,14 @@
10281028
"markdownDescription": "Specifies a global [search order](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#specifying-explicit-priority-between-libraries-and-resources) for libraries and resources. This is usefull when you have libraries containing keywords with the same name. **RobotCode** is unable to analyze the library search order in a file specified with [`Set Library Search Order`](https://robotframework.org/robotframework/latest/libraries/BuiltIn.html#Set%20Library%20Search%20Order), so you can define a global order here. Just make sure to call the `Set Library Search Order` keyword somewhere in your robot file or internally in your library.",
10291029
"scope": "resource"
10301030
},
1031+
"robotcode.analysis.robot.loadLibraryTimeout": {
1032+
"type": ["number", "null"],
1033+
"default": null,
1034+
"minimum": 1,
1035+
"maximum": 3600,
1036+
"markdownDescription": "Specifies the timeout in seconds for loading libraries and variable files during analysis. Use this when libraries initialize slowly (network calls, heavy imports, large models).\n\n**Must be > 0 when set.**\n\n**Warning:** Defining a value here overrides both the configuration file value and the environment variable.\n\nIf you change this setting, you may need to run the command `RobotCode: Clear Cache and Restart Language Servers`.",
1037+
"scope": "resource"
1038+
},
10311039
"robotcode.analysis.diagnosticModifiers.ignore": {
10321040
"type": "array",
10331041
"default": [],
@@ -2051,6 +2059,7 @@
20512059
"userDescription": "Gets informations about the Robot Framework Environment .",
20522060
"canBeReferencedInPrompt": true,
20532061
"tags": [
2062+
20542063
"robot",
20552064
"robotframework",
20562065
"robotcode"

packages/analyze/src/robotcode/analyze/code/cli.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@ def _split_comma(ctx: click.Context, param: click.Option, value: Optional[List[s
114114
return result
115115

116116

117+
def _validate_load_library_timeout(ctx: click.Context, param: click.Option, value: Optional[int]) -> Optional[int]:
118+
"""Validate --load-library-timeout (>0) or pass through None."""
119+
if value is None:
120+
return None
121+
if value <= 0:
122+
raise click.BadParameter("must be > 0")
123+
return value
124+
125+
117126
@click.command(
118127
add_help_option=True,
119128
)
@@ -216,6 +225,18 @@ def _split_comma(ctx: click.Context, param: click.Option, value: Optional[List[s
216225
help="Extend the exit code mask with the specified values. This appends to the default mask, defined in the config"
217226
" file.",
218227
)
228+
@click.option(
229+
"--load-library-timeout",
230+
type=int,
231+
callback=_validate_load_library_timeout,
232+
metavar="SECONDS",
233+
show_envvar=True,
234+
envvar="ROBOTCODE_LOAD_LIBRARY_TIMEOUT",
235+
help=(
236+
"Timeout (in seconds) for loading libraries and variable files during analysis. "
237+
"Must be > 0. Overrides config file and environment variable when set."
238+
),
239+
)
219240
@click.argument(
220241
"paths", nargs=-1, type=click.Path(exists=True, dir_okay=True, file_okay=True, readable=True, path_type=Path)
221242
)
@@ -234,6 +255,7 @@ def code(
234255
exit_code_mask: ExitCodeMask,
235256
extend_exit_code_mask: ExitCodeMask,
236257
paths: Tuple[Path],
258+
load_library_timeout: Optional[int],
237259
) -> None:
238260
"""\
239261
Performs static code analysis to identify potential issues in the specified *PATHS*. The analysis detects syntax
@@ -335,6 +357,12 @@ def code(
335357
)
336358
mask = default_mask | extend_exit_code_mask
337359

360+
if load_library_timeout is not None:
361+
analyzer_config.load_library_timeout = load_library_timeout
362+
363+
app.verbose(f"Using analyzer_config: {analyzer_config}")
364+
app.verbose(f"Using exit code mask: {mask}")
365+
338366
statistics = Statistic(mask)
339367
for e in CodeAnalyzer(
340368
app=app,

packages/analyze/src/robotcode/analyze/config.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,37 @@ class AnalyzeConfig(BaseOptions):
318318
description="Extend the global library search order setting."
319319
)
320320

321+
load_library_timeout: Optional[int] = field(
322+
description="""\
323+
Specifies the timeout in seconds for loading (importing) libraries and variable files during
324+
analysis. Increase this if your libraries perform heavy initialization (network calls, large
325+
dependency graphs, model loading, etc.).
326+
327+
Must be > 0 when set. If you omit this key, RobotCode will instead look for the environment
328+
variable `ROBOTCODE_LOAD_LIBRARY_TIMEOUT`; otherwise it will use the internal default `10`.
329+
330+
Examples:
331+
332+
```toml
333+
[tool.robotcode-analyze]
334+
# Fast fail if libraries normally import in < 2s
335+
load_library_timeout = 5
336+
```
337+
338+
```toml
339+
[tool.robotcode-analyze]
340+
# Allow heavy bootstrap (e.g. Selenium + large resource trees)
341+
load_library_timeout = 30
342+
```
343+
344+
```toml
345+
[tool.robotcode-analyze]
346+
# Omit to use default
347+
# load_library_timeout = 15
348+
```
349+
""",
350+
)
351+
321352
def to_workspace_analysis_config(self) -> WorkspaceAnalysisConfig:
322353
return WorkspaceAnalysisConfig(
323354
exclude_patterns=self.exclude_patterns or [],
@@ -331,7 +362,10 @@ def to_workspace_analysis_config(self) -> WorkspaceAnalysisConfig:
331362
if self.cache is not None
332363
else WorkspaceCacheConfig()
333364
),
334-
robot=AnalysisRobotConfig(global_library_search_order=self.global_library_search_order or []),
365+
robot=AnalysisRobotConfig(
366+
global_library_search_order=self.global_library_search_order or [],
367+
load_library_timeout=self.load_library_timeout,
368+
),
335369
modifiers=(
336370
AnalysisDiagnosticModifiersConfig(
337371
ignore=self.modifiers.ignore or [],

packages/robot/src/robotcode/robot/diagnostics/document_cache_helper.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,11 @@ def create_imports_manager(self, root_uri: Uri) -> ImportsManager:
532532
self.analysis_config.cache.ignore_arguments_for_library + cache_config.ignore_arguments_for_library,
533533
self.analysis_config.robot.global_library_search_order + analysis_config.global_library_search_order,
534534
cache_base_path,
535+
load_library_timeout=(
536+
analysis_config.load_library_timeout
537+
if analysis_config.load_library_timeout is not None
538+
else self.analysis_config.robot.load_library_timeout
539+
),
535540
)
536541

537542
result.libraries_changed.add(self._on_libraries_changed)

packages/robot/src/robotcode/robot/diagnostics/imports_manager.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@
9696
REST_EXTENSIONS = (".rst", ".rest")
9797

9898

99-
LOAD_LIBRARY_TIMEOUT: int = int(os.environ.get("ROBOTCODE_LOAD_LIBRARY_TIMEOUT", 10))
99+
DEFAULT_LOAD_LIBRARY_TIMEOUT: int = 10
100+
ENV_LOAD_LIBRARY_TIMEOUT_VAR = "ROBOTCODE_LOAD_LIBRARY_TIMEOUT"
100101
COMPLETE_LIBRARY_IMPORT_TIMEOUT = COMPLETE_RESOURCE_IMPORT_TIMEOUT = COMPLETE_VARIABLES_IMPORT_TIMEOUT = 5
101102

102103

@@ -514,6 +515,7 @@ def __init__(
514515
ignore_arguments_for_library: List[str],
515516
global_library_search_order: List[str],
516517
cache_base_path: Optional[Path],
518+
load_library_timeout: Optional[int] = None,
517519
) -> None:
518520
super().__init__()
519521

@@ -590,6 +592,45 @@ def __init__(
590592

591593
self._diagnostics: List[Diagnostic] = []
592594

595+
# precedence: explicit config (arg) > environment variable > default
596+
if load_library_timeout is None:
597+
env_value = os.environ.get(ENV_LOAD_LIBRARY_TIMEOUT_VAR)
598+
if env_value is not None:
599+
try:
600+
load_library_timeout = int(env_value)
601+
except ValueError:
602+
self._logger.warning(
603+
lambda: (
604+
"Invalid value for "
605+
f"{ENV_LOAD_LIBRARY_TIMEOUT_VAR}={env_value!r}, using default "
606+
f"{DEFAULT_LOAD_LIBRARY_TIMEOUT}"
607+
),
608+
context_name="imports",
609+
)
610+
load_library_timeout = DEFAULT_LOAD_LIBRARY_TIMEOUT
611+
else:
612+
load_library_timeout = DEFAULT_LOAD_LIBRARY_TIMEOUT
613+
614+
# enforce sane lower bound
615+
if load_library_timeout <= 0:
616+
self._logger.warning(
617+
lambda: (
618+
"Configured load_library_timeout "
619+
f"{load_library_timeout} is not > 0, fallback to {DEFAULT_LOAD_LIBRARY_TIMEOUT}"
620+
),
621+
context_name="imports",
622+
)
623+
load_library_timeout = DEFAULT_LOAD_LIBRARY_TIMEOUT
624+
625+
self.load_library_timeout = load_library_timeout
626+
627+
self._logger.critical(lambda: f"Using LoadLibrary timeout of {self.load_library_timeout} seconds")
628+
629+
self._logger.trace(
630+
lambda: f"Using load_library_timeout={self.load_library_timeout} (config/env/default)",
631+
context_name="imports",
632+
)
633+
593634
def __del__(self) -> None:
594635
try:
595636
if self._executor is not None:
@@ -1266,12 +1307,12 @@ def _get_library_libdoc(
12661307
base_dir,
12671308
self.get_resolvable_command_line_variables(),
12681309
variables,
1269-
).result(LOAD_LIBRARY_TIMEOUT)
1310+
).result(self.load_library_timeout)
12701311

12711312
except TimeoutError as e:
12721313
raise RuntimeError(
12731314
f"Loading library {name!r} with args {args!r} (working_dir={working_dir!r}, base_dir={base_dir!r}) "
1274-
f"timed out after {LOAD_LIBRARY_TIMEOUT} seconds. "
1315+
f"timed out after {self.load_library_timeout} seconds. "
12751316
"The library may be slow or blocked during import. "
12761317
"If required, increase the timeout by setting the ROBOTCODE_LOAD_LIBRARY_TIMEOUT "
12771318
"environment variable."
@@ -1444,13 +1485,13 @@ def _get_variables_libdoc(
14441485
base_dir,
14451486
self.get_resolvable_command_line_variables() if resolve_command_line_vars else None,
14461487
variables,
1447-
).result(LOAD_LIBRARY_TIMEOUT)
1488+
).result(self.load_library_timeout)
14481489

14491490
except TimeoutError as e:
14501491
raise RuntimeError(
14511492
f"Loading variables {name!r} with args {args!r} (working_dir={working_dir!r}, "
14521493
f"base_dir={base_dir!r}) "
1453-
f"timed out after {LOAD_LIBRARY_TIMEOUT} seconds. "
1494+
f"timed out after {self.load_library_timeout} seconds. "
14541495
"The variables may be slow or blocked during import. "
14551496
"If required, increase the timeout by setting the ROBOTCODE_LOAD_LIBRARY_TIMEOUT "
14561497
"environment variable."

packages/robot/src/robotcode/robot/diagnostics/workspace_config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ class CacheConfig(ConfigBase):
5555
@dataclass
5656
class AnalysisRobotConfig(ConfigBase):
5757
global_library_search_order: List[str] = field(default_factory=list)
58+
# Timeout in seconds for loading libraries and variable files during analysis. If None, fallback to env var
59+
# ROBOTCODE_LOAD_LIBRARY_TIMEOUT or default (10).
60+
load_library_timeout: Optional[int] = None
5861

5962

6063
@config_section("robotcode.analysis.diagnosticModifiers")

0 commit comments

Comments
 (0)