Add --gui-script flag for running Python scripts with pythonw.exe on … (#9152)
Addresses #6805 ## Summary This PR adds a `--gui-script` flag to `uv run` that allows running Python scripts with `pythonw.exe` on Windows, regardless of file extension. This solves the issue where users need to maintain duplicate `.py` and `.pyw` files to run the same script with and without a console window. The implementation follows the pattern established by the existing `--script` flag, but uses `pythonw.exe` instead of `python.exe` on Windows. On non-Windows platforms, the flag is present but returns an error indicating it's Windows-only functionality. Changes: - Added `--gui-script` flag (Windows-only) - Added Windows test to verify GUI script behavior - Added non-Windows test to verify proper error message - Updated CLI documentation ## Test Plan The changes are tested through: 1. New Windows-specific test that verifies: - Script runs successfully with `pythonw.exe` when using `--gui-script` - Console output is suppressed in GUI mode but visible in regular mode - Same script can be run both ways without modification 2. New non-Windows test that verifies: - Appropriate error message when `--gui-script` is used on non-Windows platforms 3. Documentation updates to clearly indicate Windows-only functionality --------- Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
committed by
GitHub
parent
761dafd0d1
commit
f6f9179a5a
@@ -2702,7 +2702,7 @@ pub struct RunArgs {
|
||||
/// Run a Python module.
|
||||
///
|
||||
/// Equivalent to `python -m <module>`.
|
||||
#[arg(short, long, conflicts_with = "script")]
|
||||
#[arg(short, long, conflicts_with_all = ["script", "gui_script"])]
|
||||
pub module: bool,
|
||||
|
||||
/// Only include the development dependency group.
|
||||
@@ -2801,9 +2801,16 @@ pub struct RunArgs {
|
||||
///
|
||||
/// Using `--script` will attempt to parse the path as a PEP 723 script,
|
||||
/// irrespective of its extension.
|
||||
#[arg(long, short, conflicts_with = "module")]
|
||||
#[arg(long, short, conflicts_with_all = ["module", "gui_script"])]
|
||||
pub script: bool,
|
||||
|
||||
/// Run the given path as a Python GUI script.
|
||||
///
|
||||
/// Using `--gui-script` will attempt to parse the path as a PEP 723 script and run it with pythonw.exe,
|
||||
/// irrespective of its extension. Only available on Windows.
|
||||
#[arg(long, conflicts_with_all = ["script", "module"])]
|
||||
pub gui_script: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
pub installer: ResolverInstallerArgs,
|
||||
|
||||
|
||||
@@ -1344,10 +1344,12 @@ impl std::fmt::Display for RunCommand {
|
||||
|
||||
impl RunCommand {
|
||||
/// Determine the [`RunCommand`] for a given set of arguments.
|
||||
#[allow(clippy::fn_params_excessive_bools)]
|
||||
pub(crate) async fn from_args(
|
||||
command: &ExternalCommand,
|
||||
module: bool,
|
||||
script: bool,
|
||||
gui_script: bool,
|
||||
connectivity: Connectivity,
|
||||
native_tls: bool,
|
||||
allow_insecure_host: &[TrustedHost],
|
||||
@@ -1401,6 +1403,11 @@ impl RunCommand {
|
||||
return Ok(Self::PythonModule(target.clone(), args.to_vec()));
|
||||
} else if script {
|
||||
return Ok(Self::PythonScript(target.clone().into(), args.to_vec()));
|
||||
} else if gui_script {
|
||||
if cfg!(windows) {
|
||||
return Ok(Self::PythonGuiScript(target.clone().into(), args.to_vec()));
|
||||
}
|
||||
anyhow::bail!("`--gui-script` is only supported on Windows. Did you mean `--script`?");
|
||||
}
|
||||
|
||||
let metadata = target_path.metadata();
|
||||
|
||||
@@ -141,6 +141,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||
command: Some(command),
|
||||
module,
|
||||
script,
|
||||
gui_script,
|
||||
..
|
||||
}) = &mut **command
|
||||
{
|
||||
@@ -150,6 +151,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||
command,
|
||||
*module,
|
||||
*script,
|
||||
*gui_script,
|
||||
settings.connectivity,
|
||||
settings.native_tls,
|
||||
&settings.allow_insecure_host,
|
||||
|
||||
@@ -296,6 +296,7 @@ impl RunSettings {
|
||||
only_dev,
|
||||
no_editable,
|
||||
script: _,
|
||||
gui_script: _,
|
||||
command: _,
|
||||
with,
|
||||
with_editable,
|
||||
|
||||
@@ -2893,6 +2893,68 @@ fn run_script_explicit_directory() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn run_gui_script_explicit() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let test_script = context.temp_dir.child("script");
|
||||
test_script.write_str(indoc! { r#"
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = []
|
||||
# ///
|
||||
import sys
|
||||
import os
|
||||
|
||||
executable = os.path.basename(sys.executable).lower()
|
||||
if not executable.startswith("pythonw"):
|
||||
print(f"Error: Expected pythonw.exe but got: {executable}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Using executable: {executable}", file=sys.stderr)
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("--gui-script").arg("script"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Reading inline script metadata from `script`
|
||||
Resolved in [TIME]
|
||||
Audited in [TIME]
|
||||
Using executable: pythonw.exe
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn run_gui_script_not_supported() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let test_script = context.temp_dir.child("script");
|
||||
test_script.write_str(indoc! { r#"
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = []
|
||||
# ///
|
||||
print("Hello")
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("--gui-script").arg("script"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: `--gui-script` is only supported on Windows. Did you mean `--script`?
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_remote_pep723_script() {
|
||||
let context = TestContext::new("3.12").with_filtered_python_names();
|
||||
|
||||
@@ -184,6 +184,10 @@ uv run [OPTIONS] [COMMAND]
|
||||
|
||||
<p>May be provided multiple times.</p>
|
||||
|
||||
</dd><dt><code>--gui-script</code></dt><dd><p>Run the given path as a Python GUI script.</p>
|
||||
|
||||
<p>Using <code>--gui-script</code> will attempt to parse the path as a PEP 723 script and run it with pythonw.exe, irrespective of its extension. Only available on Windows.</p>
|
||||
|
||||
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
|
||||
|
||||
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
|
||||
|
||||
Reference in New Issue
Block a user