robotcode 2.6.2__py3-none-any.whl → 2.7.0__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/cli/__init__.py CHANGED
@@ -2,6 +2,9 @@ import json
2
2
  import logging
3
3
  import logging.config
4
4
  import os
5
+ import shlex
6
+ import subprocess
7
+ import sys
5
8
  from pathlib import Path
6
9
  from typing import Any, List, Literal, Optional
7
10
 
@@ -18,6 +21,7 @@ from robotcode.plugin import (
18
21
  from robotcode.plugin._agent_detection import is_running_in_ai_agent
19
22
  from robotcode.plugin.click_helper.aliases import AliasedGroup
20
23
  from robotcode.plugin.click_helper.types import EnumChoice
24
+ from robotcode.plugin.click_helper.wrappable import WRAPPER_APPLIED_ENV, is_wrappable
21
25
  from robotcode.plugin.manager import PluginManager
22
26
 
23
27
  from .__version__ import __version__
@@ -46,6 +50,99 @@ class RobotCodeFormatter(logging.Formatter):
46
50
  super().__init__(*args, defaults=defaults, **kwargs)
47
51
 
48
52
 
53
+ def _maybe_reexec_under_wrapper(
54
+ ctx: click.Context, app: Application, cli_wrapper: Optional[str] = None, no_wrapper: bool = False
55
+ ) -> None:
56
+ """Re-execute this process through a configured ``wrapper`` command.
57
+
58
+ When a ``wrapper`` is configured - either via ``--wrapper`` on the command
59
+ line (``cli_wrapper``) or the selected profile's ``wrapper`` (e.g. to run
60
+ the tests inside a specific X11/Wayland session) - and the invoked command
61
+ is ``@wrappable`` (i.e. it executes Robot Framework), the process
62
+ re-executes itself **once** through that command before Robot Framework
63
+ starts. ``--wrapper`` overrides the profile; ``--no-wrapper`` disables
64
+ wrapping for this run. The guard env var prevents infinite recursion and
65
+ lets an outer layer suppress the re-exec by setting it itself.
66
+ """
67
+ if no_wrapper:
68
+ if cli_wrapper:
69
+ app.warning("Ignoring --wrapper because --no-wrapper was given.")
70
+ return
71
+ if os.environ.get(WRAPPER_APPLIED_ENV):
72
+ return
73
+
74
+ sub_name = ctx.invoked_subcommand
75
+ if not sub_name:
76
+ return
77
+ # The command itself opts in via @wrappable; aliases resolve to the same
78
+ # command object, so this is alias-safe and needs no central name list.
79
+ sub_cmd = ctx.command.get_command(ctx, sub_name) if isinstance(ctx.command, click.Group) else None
80
+ if sub_cmd is None or not is_wrappable(sub_cmd):
81
+ if cli_wrapper:
82
+ app.warning(f"Ignoring --wrapper: '{sub_name}' does not execute Robot Framework.")
83
+ return
84
+
85
+ from robotcode.robot.config.loader import load_robot_config_from_path
86
+ from robotcode.robot.config.utils import get_config_files
87
+
88
+ profile_wrapper = None
89
+ try:
90
+ config_files, _, _ = get_config_files(
91
+ config_files=app.config.config_files,
92
+ root_folder=app.config.root,
93
+ no_vcs=app.config.no_vcs,
94
+ verbose_callback=app.verbose,
95
+ )
96
+ # `evaluated_with_env` also applies the profile's `env` to `os.environ`,
97
+ # so the wrapper command can rely on it.
98
+ profile = (
99
+ load_robot_config_from_path(*config_files, verbose_callback=app.verbose)
100
+ .combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error)
101
+ .evaluated_with_env(verbose_callback=app.verbose, error_callback=app.error)
102
+ )
103
+ profile_wrapper = profile.wrapper
104
+ except Exception as e:
105
+ # Don't fail early on config problems here; the actual command loads the
106
+ # config again and reports errors properly. An explicit --wrapper is
107
+ # still honored below.
108
+ message = str(e)
109
+ app.verbose(lambda: f"Skipping profile wrapper detection: {message}")
110
+
111
+ # `--wrapper` overrides the profile's `wrapper`. The evaluated profile turns
112
+ # StringExpression entries into plain strings.
113
+ if cli_wrapper:
114
+ wrapper = shlex.split(cli_wrapper)
115
+ elif profile_wrapper:
116
+ wrapper = [str(w) for w in profile_wrapper]
117
+ else:
118
+ return
119
+
120
+ # Rebuild the invocation the way this process was actually started, so the
121
+ # re-exec finds robotcode even when it runs from a bundled path (VS Code) and
122
+ # is not installed in the interpreter. `launcher_script` is set only when we
123
+ # were started through a bundled entry (its `__main__` sets it) or an explicit
124
+ # `--launcher-script`; otherwise robotcode is importable directly and
125
+ # `-m robotcode.cli` is correct. We deliberately do NOT consult the
126
+ # ROBOTCODE_BUNDLED_ROBOTCODE_MAIN env var: VS Code sets it in every terminal,
127
+ # so a directly started robotcode would be diverted to the bundled copy.
128
+ # Mirrors the debug launcher's `debugger_script` handling.
129
+ entry = [str(app.config.launcher_script)] if app.config.launcher_script else ["-m", "robotcode.cli"]
130
+ command = [*wrapper, sys.executable, *entry, *sys.argv[1:]]
131
+ app.verbose(lambda: "Running under wrapper: " + " ".join(command))
132
+
133
+ # Run robotcode again under the wrapper; the guard env stops the spawned
134
+ # robotcode from wrapping a second time. On POSIX we replace this process with
135
+ # execvp (no extra process); Windows has no such exec, so there we spawn the
136
+ # command, wait, and forward its exit code.
137
+ os.environ[WRAPPER_APPLIED_ENV] = "1"
138
+ try:
139
+ if sys.platform == "win32":
140
+ sys.exit(subprocess.run(command).returncode)
141
+ os.execvp(command[0], command)
142
+ except OSError as e:
143
+ raise click.ClickException(f"Failed to execute wrapper command {wrapper!r}: {e}") from e
144
+
145
+
49
146
  @click.group(
50
147
  cls=AliasedGroup,
51
148
  context_settings={"auto_envvar_prefix": "ROBOTCODE"},
@@ -129,6 +226,20 @@ class RobotCodeFormatter(logging.Formatter):
129
226
  help="Enables verbose mode.",
130
227
  show_envvar=True,
131
228
  )
229
+ @click.option(
230
+ "--wrapper",
231
+ type=str,
232
+ default=None,
233
+ show_envvar=True,
234
+ help='Command prefix to run the test execution through, e.g. `--wrapper "xvfb-run -a"`. '
235
+ "Split like a shell command. Applies only to commands that execute Robot Framework "
236
+ "(run/debug/repl). Overrides the profile's `wrapper`.",
237
+ )
238
+ @click.option(
239
+ "--no-wrapper",
240
+ is_flag=True,
241
+ help="Disable any configured `wrapper` (profile or --wrapper) for this run.",
242
+ )
132
243
  @click.option("--log", is_flag=True, help="Enables logging.", show_envvar=True)
133
244
  @click.option(
134
245
  "--log-level",
@@ -230,7 +341,9 @@ class RobotCodeFormatter(logging.Formatter):
230
341
  )
231
342
  @click.version_option(version=__version__, prog_name="robotcode")
232
343
  @pass_application
344
+ @click.pass_context
233
345
  def robotcode(
346
+ ctx: click.Context,
234
347
  app: Application,
235
348
  config_files: Optional[List[Path]],
236
349
  profiles: Optional[List[str]],
@@ -249,6 +362,8 @@ def robotcode(
249
362
  log_calls: bool,
250
363
  log_config: Optional[Path],
251
364
  default_path: Optional[List[str]],
365
+ wrapper: Optional[str] = None,
366
+ no_wrapper: bool = False,
252
367
  launcher_script: Optional[str] = None,
253
368
  debugpy: bool = False,
254
369
  debugpy_port: int = 5678,
@@ -295,6 +410,8 @@ def robotcode(
295
410
  app.config.log_level = log_level
296
411
  app.config.log_calls = log_calls
297
412
 
413
+ _maybe_reexec_under_wrapper(ctx, app, wrapper, no_wrapper)
414
+
298
415
  if log_config:
299
416
  if log_calls:
300
417
  LoggingDescriptor.set_call_tracing(True)
@@ -1 +1 @@
1
- __version__ = "2.6.2"
1
+ __version__ = "2.7.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotcode
3
- Version: 2.6.2
3
+ Version: 2.7.0
4
4
  Summary: Command line interface for RobotCode
5
5
  Project-URL: Homepage, https://robotcode.io
6
6
  Project-URL: Donate, https://opencollective.com/robotcode
@@ -33,35 +33,35 @@ Classifier: Topic :: Text Editors :: Integrated Development Environments (IDE)
33
33
  Classifier: Topic :: Utilities
34
34
  Classifier: Typing :: Typed
35
35
  Requires-Python: >=3.10
36
- Requires-Dist: robotcode-core==2.6.2
37
- Requires-Dist: robotcode-plugin==2.6.2
38
- Requires-Dist: robotcode-robot==2.6.2
36
+ Requires-Dist: robotcode-core==2.7.0
37
+ Requires-Dist: robotcode-plugin==2.7.0
38
+ Requires-Dist: robotcode-robot==2.7.0
39
39
  Provides-Extra: all
40
40
  Requires-Dist: docutils; extra == 'all'
41
41
  Requires-Dist: pyyaml>=5.4; extra == 'all'
42
- Requires-Dist: robotcode-analyze==2.6.2; extra == 'all'
43
- Requires-Dist: robotcode-debugger==2.6.2; extra == 'all'
44
- Requires-Dist: robotcode-language-server==2.6.2; extra == 'all'
45
- Requires-Dist: robotcode-repl-server==2.6.2; extra == 'all'
46
- Requires-Dist: robotcode-repl==2.6.2; extra == 'all'
47
- Requires-Dist: robotcode-runner==2.6.2; extra == 'all'
42
+ Requires-Dist: robotcode-analyze==2.7.0; extra == 'all'
43
+ Requires-Dist: robotcode-debugger==2.7.0; extra == 'all'
44
+ Requires-Dist: robotcode-language-server==2.7.0; extra == 'all'
45
+ Requires-Dist: robotcode-repl-server==2.7.0; extra == 'all'
46
+ Requires-Dist: robotcode-repl==2.7.0; extra == 'all'
47
+ Requires-Dist: robotcode-runner==2.7.0; extra == 'all'
48
48
  Requires-Dist: robotframework-robocop>=6.0.0; extra == 'all'
49
49
  Provides-Extra: analyze
50
- Requires-Dist: robotcode-analyze==2.6.2; extra == 'analyze'
50
+ Requires-Dist: robotcode-analyze==2.7.0; extra == 'analyze'
51
51
  Provides-Extra: debugger
52
- Requires-Dist: robotcode-debugger==2.6.2; extra == 'debugger'
52
+ Requires-Dist: robotcode-debugger==2.7.0; extra == 'debugger'
53
53
  Provides-Extra: languageserver
54
- Requires-Dist: robotcode-language-server==2.6.2; extra == 'languageserver'
54
+ Requires-Dist: robotcode-language-server==2.7.0; extra == 'languageserver'
55
55
  Provides-Extra: lint
56
56
  Requires-Dist: robotframework-robocop>=2.0.0; extra == 'lint'
57
57
  Provides-Extra: repl
58
- Requires-Dist: robotcode-repl==2.6.2; extra == 'repl'
58
+ Requires-Dist: robotcode-repl==2.7.0; extra == 'repl'
59
59
  Provides-Extra: replserver
60
- Requires-Dist: robotcode-repl-server==2.6.2; extra == 'replserver'
60
+ Requires-Dist: robotcode-repl-server==2.7.0; extra == 'replserver'
61
61
  Provides-Extra: rest
62
62
  Requires-Dist: docutils; extra == 'rest'
63
63
  Provides-Extra: runner
64
- Requires-Dist: robotcode-runner==2.6.2; extra == 'runner'
64
+ Requires-Dist: robotcode-runner==2.7.0; extra == 'runner'
65
65
  Provides-Extra: yaml
66
66
  Requires-Dist: pyyaml>=5.4; extra == 'yaml'
67
67
  Description-Content-Type: text/markdown
@@ -0,0 +1,12 @@
1
+ robotcode/cli/__init__.py,sha256=qvIUqcHP8K1FNyu3whFDe6M_Wyeo_SKCqwBxtPeQfy0,15608
2
+ robotcode/cli/__main__.py,sha256=hX3nwROMTnsYGT1KS0rXUYrslu9sFzctYdAh66Rcckw,153
3
+ robotcode/cli/__version__.py,sha256=EtKWW0Hnl5oWglRNH0HZigvcDT2FEs58ek8buJdwW1E,22
4
+ robotcode/cli/py.typed,sha256=bWew9mHgMy8LqMu7RuqQXFXLBxh2CRx0dUbSx-3wE48,27
5
+ robotcode/cli/commands/__init__.py,sha256=XJHRt_YwMO2Ni2EfL2aj4jkJXMVG6NGFTpzvSVgIRnQ,92
6
+ robotcode/cli/commands/config.py,sha256=0Jmf7HDIo7IIaOMU9M_bYi7PdHAfcae6x3SW93NA3EA,10481
7
+ robotcode/cli/commands/profiles.py,sha256=5H9lvSVCMggWXf0IH54QtgDgqupEEvpcUrEsTZvZsNM,6127
8
+ robotcode-2.7.0.dist-info/METADATA,sha256=nJWf0wXHOE5LlTfA-cTYK3NsBzv6tGYnOfKj8XZSt9M,14582
9
+ robotcode-2.7.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
10
+ robotcode-2.7.0.dist-info/entry_points.txt,sha256=Pb4DKVVdJb5PboVl48njwk3DkKQHBJOL1A8KkpemqA8,58
11
+ robotcode-2.7.0.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
12
+ robotcode-2.7.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.30.1
2
+ Generator: hatchling 1.31.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,12 +0,0 @@
1
- robotcode/cli/__init__.py,sha256=-zPNouLiGkJg8KKz6gVzyVm47dXHqM-zv2gJV3r-n7Y,10254
2
- robotcode/cli/__main__.py,sha256=hX3nwROMTnsYGT1KS0rXUYrslu9sFzctYdAh66Rcckw,153
3
- robotcode/cli/__version__.py,sha256=53Sii4w6BIWn-1RhaTyqUO46gDe4nDCRQDAcpsWFH24,22
4
- robotcode/cli/py.typed,sha256=bWew9mHgMy8LqMu7RuqQXFXLBxh2CRx0dUbSx-3wE48,27
5
- robotcode/cli/commands/__init__.py,sha256=XJHRt_YwMO2Ni2EfL2aj4jkJXMVG6NGFTpzvSVgIRnQ,92
6
- robotcode/cli/commands/config.py,sha256=0Jmf7HDIo7IIaOMU9M_bYi7PdHAfcae6x3SW93NA3EA,10481
7
- robotcode/cli/commands/profiles.py,sha256=5H9lvSVCMggWXf0IH54QtgDgqupEEvpcUrEsTZvZsNM,6127
8
- robotcode-2.6.2.dist-info/METADATA,sha256=OV5izStIuVLT677FLY1vM3MUTPLaRqb7ESS-cm3LkXU,14582
9
- robotcode-2.6.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
10
- robotcode-2.6.2.dist-info/entry_points.txt,sha256=Pb4DKVVdJb5PboVl48njwk3DkKQHBJOL1A8KkpemqA8,58
11
- robotcode-2.6.2.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
12
- robotcode-2.6.2.dist-info/RECORD,,