Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix powershell ArgumentList bug #11659

Merged
merged 3 commits into from
Jan 8, 2025

Conversation

jkrumbiegel
Copy link
Contributor

@jkrumbiegel jkrumbiegel commented Dec 11, 2024

Should fix #10111 with the technique reported to work in #10489 (comment) which seems to match one of the syntax options mentioned in the official docs https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-7.4#example-7-specifying-arguments-to-the-process

I don't have a Windows machine so I could not separately test this. For a test of the bug, one of the passed paths would need a space inside it which I can't control easily on CI. The only path amenable to that would be the project path which can be controlled with the ENV variable QUARTO_JULIA_PROJECT (but only if no server process is running yet, and the order of tests seems to be automatically decided by the CI script, so I'm not sure how to control that).

@cderv
Copy link
Collaborator

cderv commented Dec 11, 2024

I don't have a Windows machine so I could not separately test this.

I have so I'll try to test this. Thanks !

@cderv cderv self-assigned this Dec 11, 2024
@cderv cderv self-requested a review December 11, 2024 10:09
@jkrumbiegel
Copy link
Contributor Author

Bump :)

Would be nice to get this in as this bug completely precludes some Windows users from using the native julia engine.

@cderv
Copy link
Collaborator

cderv commented Jan 6, 2025

Great timing ! Yeah I was working on that today and just saw your message while I was gonna report.

Something is not working on my windows with this PR. No more errors, but not transport file created.

❯ quarto render index.qmd --execute-debug
- Transport file C:\Users\chris\AppData\Local\quarto\julia\julia_transport.txt doesn't exist
Starting julia control server process. This might take a while...
- Custom julia project set via QUARTO_JULIA_PROJECT="C:\Users\chris\Documents\test-dir with space\". Checking if QuartoNotebookRunner can be loaded.
The latest version of Julia in the `1.10` channel is 1.10.7+0.x64.w64.mingw32. You currently have `1.10.5+0.x64.w64.mingw32` installed. Run:

  juliaup update

in your terminal shell to install Julia 1.10.7+0.x64.w64.mingw32 and update the `1.10` channel to that version.
- QuartoNotebookRunner could be loaded successfully.
- Starting detached julia server through powershell, once transport file exists, server should be running.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
ERROR: Error
    at renderFiles (file:///C:/Users/chris/Documents/DEV_R/quarto-cli/src/command/render/render-files.ts:350:23)
    at eventLoopTick (ext:core/01_core.js:214:9)
    at async renderProject (file:///C:/Users/chris/Documents/DEV_R/quarto-cli/src/command/render/project.ts:464:23)
    at async Command.actionHandler (file:///C:/Users/chris/Documents/DEV_R/quarto-cli/src/command/render/cmd.ts:248:26)
    at async Command.execute (https://deno.land/x/cliffy@v1.0.0-rc.3/command/command.ts:1948:7)
    at async Command.parseCommand (https://deno.land/x/cliffy@v1.0.0-rc.3/command/command.ts:1780:14)
    at async quarto (file:///C:/Users/chris/Documents/DEV_R/quarto-cli/src/quarto.ts:190:5)
    at async file:///C:/Users/chris/Documents/DEV_R/quarto-cli/src/quarto.ts:219:5
    at async mainRunner (file:///C:/Users/chris/Documents/DEV_R/quarto-cli/src/core/main.ts:36:5)
    at async file:///C:/Users/chris/Documents/DEV_R/quarto-cli/src/quarto.ts:209:3

Unsetting QUARTO_JULIA_PROJECT="C:\Users\chris\Documents\test-dir with space\" works - so it is realted to the space probably

❯ quarto render index.qmd --execute-debug
- Transport file C:\Users\chris\AppData\Local\quarto\julia\julia_transport.txt doesn't exist
Starting julia control server process. This might take a while...
The latest version of Julia in the `1.10` channel is 1.10.7+0.x64.w64.mingw32. You currently have `1.10.5+0.x64.w64.mingw32` installed. Run:

  juliaup update

in your terminal shell to install Julia 1.10.7+0.x64.w64.mingw32 and update the `1.10` channel to that version.
- Starting detached julia server through powershell, once transport file exists, server should be running.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file read successfully.
Julia server process started.
- Connecting to server at port 8000, pid 35260
- write command "isready" to socket server
- received server response
- Transport file read successfully.
- write command "run" to socket server
- received server response
- received progress update response, listening for further responses
Running [1/1] at line 6:  2+1
- received server response
pandoc
  to: html
  output-file: index.html
  standalone: true
  section-divs: true
  html-math-method: mathjax
  wrap: none
  default-image-extension: png

metadata
  document-css: false
  link-citations: true
  date-format: long
  lang: en
  title: test

Output created: index.html

So I think I'll merge main into this PR and see if I can fix this

@jkrumbiegel
Copy link
Contributor Author

jkrumbiegel commented Jan 6, 2025

Thanks @cderv! In the state of this PR, debugging problems with server startup is annoying because you never see errors happening in the julia server process. One thing you could try is to checkout #11803, then merge the changes from this one into it (I added one arg, the logfile, to the julia invocation which you'd have to add here too). Then maybe it prints out something useful after the transport file lookup times out. If the error is before that, like some syntax error with PowerShell, then that wouldn't help, though.

@cderv
Copy link
Collaborator

cderv commented Jan 6, 2025

One nice thing of being on windows is that I have access to powershell and can try Powershell command directly. I believe there is still quoting problem

❯ Powershell -Command Start-Process julia -ArgumentList '"--startup-file=no" "--project=C:/Users/chris/Documents/test-dir/" "C:\\Users\\chris\\Documents\\DEV_R\\quarto-cli\\src\\resources\\julia\\quartonotebookrunner.jl" "C:\\Users\\chris\\AppData\\Local\\quarto\\julia\\julia_transport.txt"'
Start-Process : Impossible de trouver un paramètre positionnel acceptant l'argument «
--project=C:/Users/chris/Documents/test-dir/».
Au caractère Ligne:1 : 1
+ Start-Process julia -ArgumentList "--startup-file=no" "--project=C:/U ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument : (:) [Start-Process], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.StartProcessCommand

So something is not yet right in how the command is created when juliaProject is set using env var. Space or no Space in it. I'll try a few thing and suggest the change

@cderv
Copy link
Collaborator

cderv commented Jan 6, 2025

in the official docs learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-7.4#example-7-specifying-arguments-to-the-process

About this, just so we are on the same page, by calling PowerShell command, it will be PowerShell 5.1 called, not version 7. So the official doc will be this one
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-5.1#example-7-specifying-arguments-to-the-process

Probably not that many difference for the call we want to make. Quoting in Powershell is really painfull, and here we need to take extra care because

  • Start-Process takes -ArgumentList which requires careful quoting
  • this call is wrapped in a Powershell -Command call, which will also required carefull quoting.

I am convinced this is the issue - just writing the correct Powershell Syntax for this Deno.Command() call.

Copy link
Collaborator

@cderv cderv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I get it to work by doing these adjustment below.

However, I am trying to test a few scenarios locally. It seems behavior is not the same between

  • Backslash for path separator
     $env:QUARTO_JULIA_PROJECT="C:\Users\chris\Documents\test-dir\" 
    
  • Forwardslash for path separator
     $env:QUARTO_JULIA_PROJECT="C:/Users/chris/Documents/test-dir/" 
    

And so adding space like $env:QUARTO_JULIA_PROJECT="C:/Users/chris/Documents/test-dir with space/" also impact.

Also I noticed something I don't know why exactly but it seems the space does not cause issue at
https://github.com/cderv/quarto-cli/blob/7b89d103ce5ca128ad74568304b9fe618858186d/src/execute/julia.ts#L279-L282

I also wanted to do --project="${juliaProject}" but then Package QuartoNotebookRunner not found in current path.

as it seems Deno or something else do a modification that lead Julia to this Pkg.project().path

'C:\\Users\\chris\\Documents\\test-dir\\"\\Project.toml
'

So it is quite fragile right now depending on how project path is.

Also is this expected that --no-execute-daemon or --execute-daemon-restart does not remove the transport file ?

Is there a way we could trigger a new server for our test while running quarto render ?

It would help test different scenarios.

Comment on lines +251 to +255
function powershell_argument_list_to_string(...args: string[]): string {
// formats as '"arg 1" "arg 2" "arg 3"'
const inner = args.map((arg) => `"${arg}"`).join(" ");
return `'${inner}'`;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that we are double quoting every args here but it is not necessary.

Especially argument like --startup-file=no shouldn't be quoted

Suggested change
function powershell_argument_list_to_string(...args: string[]): string {
// formats as '"arg 1" "arg 2" "arg 3"'
const inner = args.map((arg) => `"${arg}"`).join(" ");
return `'${inner}'`;
}
function powershell_argument_list_to_string(...args: string[]): string {
// formats as 'arg1 arg2 arg3'
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-5.1#example-7-specifying-arguments-to-the-process
const inner = args.join(" ");
return `'${inner}'`;
}

This only seems to fix the problem I see.

It will generate this argument

'--startup-file=no --project=C:/Users/chris/Documents/test-dir/ C:\\Users\\chris\\Documents\\DEV_R\\quarto-cli\\src\\resources\\julia\\quartonotebookrunner.jl C:\\Users\\chris\\AppData\\Local\\quarto\\julia\\julia_transport.txt'

Each args that needs quoting should be when calling the function, specifically each path that could have space I think.

Comment on lines +319 to +324
powershell_argument_list_to_string(
"--startup-file=no",
`--project=${juliaProject}`,
resourcePath("julia/quartonotebookrunner.jl"),
transportFile,
),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quotes should be set for each path that could have space in them

Suggested change
powershell_argument_list_to_string(
"--startup-file=no",
`--project=${juliaProject}`,
resourcePath("julia/quartonotebookrunner.jl"),
transportFile,
),
powershell_argument_list_to_string(
"--startup-file=no",
`--project="${juliaProject}"`,
`"${resourcePath("julia/quartonotebookrunner.jl")}"`,
`"${transportFile}"`,
),

@jkrumbiegel
Copy link
Contributor Author

Especially argument like --startup-file=no shouldn't be quoted

I set up a github actions script to download deno and start a tmate session, then ssh'd into that and tried some things out. I found that multiple things could work, and they all don't require that args without spaces are unquoted. So now I'm a bit confused, one of them also looked pretty much like what I have in the PR

Here's the output from part of my deno repl session where I tested this, you could check against your system, too:

One concatenated Start-Process

> let command_args = 'Start-Process julia -ArgumentList """--project=./dir with spaces"" ""--threads=auto"" ""-E 1+1""" -Wait -NoNewWindow'
undefined
> let command = new Deno.Command("PowerShell", {args: ["-Command", command_args]})
undefined
> let result = command.outputSync()
undefined
> new TextDecoder().decode(result.stdout)
"2\n"

All args separated out, quoting with '"

> let command = new Deno.Command("PowerShell", {args: ["-Command", "Start-Process", "julia", "-ArgumentList", `'"--project=./dir with spaces" "--threads=auto" "-E 1+1"'`, "-Wait", "-NoNewWindow"]})
undefined
> let result = command.outputSync()
undefined
> new TextDecoder().decode(result.stdout)
"2\n"

All args separated out, quoting with """

> let command = new Deno.Command("PowerShell", {args: ["-Command", "Start-Process", "julia", "-ArgumentList", '"""--project=./dir with spaces"" ""--threads=auto"" ""-E 1+1"""', "-Wait", "-NoNewWindow"]})
undefined
> let result = command.outputSync()
undefined
> new TextDecoder().decode(result.stdout)
"2\n"

@cderv
Copy link
Collaborator

cderv commented Jan 7, 2025

All the above works for me locally to. This is indeed confusing... I'll start fresh on this to understand better.

@jkrumbiegel
Copy link
Contributor Author

What I noticed when trying things out first was that it was even harder to try from bash or whatever the default shell on the runner was. Because bash already removes one layer of quoting. So maybe what you thought you tried locally wasn't exactly corresponding to what the Deno code does. As this seems to be fine, maybe the bug is in the script that starts the server, as you didn't get a valid transport file out so it must have crashed at some point.

@cderv
Copy link
Collaborator

cderv commented Jan 7, 2025

As this seems to be fine, maybe the bug is in the script that starts the server, as you didn't get a valid transport file out so it must have crashed at some point.

I'll follow your advice on merging this in #11803 to see if I get more information then.

@jkrumbiegel
Copy link
Contributor Author

I tried your QUARTO_JULIA_PROJECT with a space with this branch and that worked (main branch did not, just for sanity checking):

runneradmin@fv-az801-487 MINGW64 /d/a/JuliaGithubActionsSandbox/JuliaGithubActionsSandbox/quarto-cli
# git checkout jk/powershell-argumentlist-fix
branch 'jk/powershell-argumentlist-fix' set up to track 'fork/jk/powershell-argumentlist-fix'.
Switched to a new branch 'jk/powershell-argumentlist-fix'

runneradmin@fv-az801-487 MINGW64 /d/a/JuliaGithubActionsSandbox/JuliaGithubActionsSandbox/quarto-cli
# QUARTO_JULIA_PROJECT="../Quarto Notebook Runner" ./package/dist/bin/quarto.cmd render test.qmd --execute-debug
Check file:///D:/a/JuliaGithubActionsSandbox/JuliaGithubActionsSandbox/quarto-cli/src/quarto.ts
- Transport file C:\Users\runneradmin\AppData\Local\quarto\julia\julia_transport.txt doesn't exist
Starting julia control server process. This might take a while...
- Custom julia project set via QUARTO_JULIA_PROJECT="../Quarto Notebook Runner". Checking if QuartoNotebookRunner can be loaded.
- QuartoNotebookRunner could be loaded successfully.
- Starting detached julia server through powershell, once transport file exists, server should be running.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file did not exist, yet.
- Transport file read successfully.
Julia server process started.
- Connecting to server at port 8000, pid 2172
- write command "isready" to socket server
- received server response
- Transport file read successfully.
- write command "isopen" to socket server
- received server response
- write command "run" to socket server
- received server response
- received progress update response, listening for further responses
Running [1/1] at line 5:  1 + 1
- received server response
- write command "close" to socket server
- received server response
pandoc
  to: html
  output-file: test.html
  standalone: true
  section-divs: true
  html-math-method: mathjax
  wrap: none
  default-image-extension: png

metadata
  document-css: false
  link-citations: true
  date-format: long
  lang: en

Output created: test.html

I also tried with a qmd file with space and that also worked.

@cderv
Copy link
Collaborator

cderv commented Jan 7, 2025

I'll start fresh on this to understand better.

So what I did to test your PR without change

  • Simple test document
--- 
title: "Julia test"
format: html
engine: julia
---

```{julia}
1 + 1
```                 
  • Checkout PR gh pr checkout 11659
  • Create a new clean project to install QuartoNotebookRunner
    • mkdir "C:\Users\chris\Documents\DEV_OTHER\00-TESTS\testDir with space"
    • julia --project="C:\Users\chris\Documents\DEV_OTHER\00-TESTS\testDir with space"
    • ]
    • add QuartoNotebookRunner
  • Configure environment variable. I test two ways (for each sucessful test I manually deleted julia_transport.txt if created as --execute-daemon-restart does not do it seems)
  1. Using \ as separator as Windows Powerwhell autocomplete
    • $env:QUARTO_JULIA_PROJECT='C:\Users\chris\Documents\DEV_OTHER\00-TESTS\testDir with space\'
    • In that case, no transport file after 20 attempts.
  2. Using / as separator
    • $env:QUARTO_JULIA_PROJECT='C:/Users/chris/Documents/DEV_OTHER/00-TESTS/testDir with space/'
    • In that case, transport file is created after 6 attempt.

So we could say
* This PR does fix the powershell syntax problem
* There is something else happening, possibly in Julia script or QuartoNotebookRunner

So I tried a simple fix for this doing this patch

diff --git a/src/execute/julia.ts b/src/execute/julia.ts
index 280925851..0e3b6893e 100644
--- a/src/execute/julia.ts
+++ b/src/execute/julia.ts
@@ -34,7 +34,7 @@ import {
 } from "../config/format.ts";
 import { resourcePath } from "../core/resources.ts";
 import { quartoRuntimeDir } from "../core/appdirs.ts";
-import { normalizePath } from "../core/path.ts";
+import { normalizePath, pathWithForwardSlashes } from "../core/path.ts";
 import { isInteractiveSession } from "../core/platform.ts";
 import { runningInCI } from "../core/ci-info.ts";
 import { sleep } from "../core/async.ts";
@@ -318,7 +318,7 @@ async function startOrReuseJuliaServer(
             "-ArgumentList",
             powershell_argument_list_to_string(
               "--startup-file=no",
-              `--project=${juliaProject}`,
+              `--project=${pathWithForwardSlashes(juliaProject)}`,
               resourcePath("julia/quartonotebookrunner.jl"),
               transportFile,
             ),

and now both
* $env:QUARTO_JULIA_PROJECT='C:\Users\chris\Documents\DEV_OTHER\00-TESTS\testDir with space\'
* $env:QUARTO_JULIA_PROJECT='C:/Users/chris/Documents/DEV_OTHER/00-TESTS/testDir with space/'

Maybe this pathWithForwardSlashes() transformation should even be done before to normalize the path from the user defined env var

diff --git a/src/execute/julia.ts b/src/execute/julia.ts
index 280925851..ff18b2e7f 100644
--- a/src/execute/julia.ts
+++ b/src/execute/julia.ts
@@ -34,7 +34,7 @@ import {
 } from "../config/format.ts";
 import { resourcePath } from "../core/resources.ts";
 import { quartoRuntimeDir } from "../core/appdirs.ts";
-import { normalizePath } from "../core/path.ts";
+import { normalizePath, pathWithForwardSlashes } from "../core/path.ts";
 import { isInteractiveSession } from "../core/platform.ts";
 import { runningInCI } from "../core/ci-info.ts";
 import { sleep } from "../core/async.ts";
@@ -271,6 +271,7 @@ async function startOrReuseJuliaServer(
       await ensureQuartoNotebookRunnerEnvironment(options);
       juliaProject = juliaRuntimeDir();
     } else {
+      juliaProject = pathWithForwardSlashes(juliaProject);
       trace(
         options,
         `Custom julia project set via QUARTO_JULIA_PROJECT="${juliaProject}". Checking if QuartoNotebookRunner can be loaded.`,

With this change I think the PR is ok (unless we can fix the --no-execute-daemon problem and add test that will be sure to remove transport file ?)

What I noticed when trying things out first was that it was even harder to try from bash or whatever the default shell on the runner was. Because bash already removes one layer of quoting. So maybe what you thought you tried locally wasn't exactly corresponding to what the Deno code does.

I probably got lost in this problem where Deno.Command() is doing something different from using shell directly. Thanks for getting me out of this ! 😄

@jkrumbiegel
Copy link
Contributor Author

Ah yes, backslashes, that's not good :) And I never tried with those because the runner terminal didn't autocomplete to them. Great that we found where the disparity was.

With this change I think the PR is ok (unless we can fix the --no-execute-daemon problem and add test that will be sure to remove transport file ?)

The daemon in the native julia engine refers to the worker process for a given file. The server process always exists after the first start and only automatically times out a couple minutes after no more workers exist. So that behavior is as intended (this is different from how jupyter kernels work). #11803 (comment) adds a kill command to forcefully remove the server, it basically just calls kill pid where pid is the process id you can see when you have --execute-debug enabled.

@cderv
Copy link
Collaborator

cderv commented Jan 7, 2025

Oh I see. We get a new concept with this server process that no current flag covers... 🤔
This is to be discussed in #11803 then. This will be a good addition.

I let you do the change for insuring forward slash and I'll create manual test procedure for now that we can run if necessary. We'll add automated test when we have a way to kill the server.

@jkrumbiegel
Copy link
Contributor Author

Ok I've added the backslash replacement, although I could not reproduce the transport file failure on the CI runner with a path with backslashes (Julia should in principle handle those ok on Windows). Well, it can't hurt to do this, and if it fixes things for you locally 🤷‍♂️

@cderv
Copy link
Collaborator

cderv commented Jan 8, 2025

All works now ok with this PR while changing QUARTO_JULIA_PROJECT to different path. Could you add changelog item and than I'll merge ?

THanks

@jkrumbiegel
Copy link
Contributor Author

Thanks, I've added a changelog entry in the same style as the 1.6 log had

@jkrumbiegel
Copy link
Contributor Author

The errors seem unrelated, I had the same ones on a different PR earlier and they went away again without intervention

@cderv cderv merged commit 8ad252e into quarto-dev:main Jan 8, 2025
47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

"Start-Process" fails on Windows with julia engine
2 participants