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

The placeholder %OUTDIR% on Windows #2010

Closed
tamuratak opened this issue Mar 29, 2020 · 18 comments · Fixed by #2015
Closed

The placeholder %OUTDIR% on Windows #2010

tamuratak opened this issue Mar 29, 2020 · 18 comments · Fixed by #2015
Labels
dev Development discussions

Comments

@tamuratak
Copy link
Contributor

tamuratak commented Mar 29, 2020

@jlelong I ask you a question on the placeholder, %OUTDIR%, on Windows. In the current master branch, backward slashes are used in %OUTDIR% on Windows since path.normalize replace forward slashes with backward slashes on Windows. Is this an intended behavior?

return path.normalize(out.split(path.sep).join('/'))

We can see the behavior of path.normalize on Windows by calling path.win32.normalize on other platforms.

path.win32.normalize('C:////temp\\\\/\\/\\/foo/bar');
// Returns: 'C:\\temp\\foo\\bar'

Related to #1897, #1904, and #1947.

@tamuratak tamuratak added the dev Development discussions label Mar 29, 2020
@jlelong
Copy link
Collaborator

jlelong commented Mar 29, 2020

I guess there is something wrong here, the line should instead read

return path.normalize(out).split(path.sep).join('/')

We do want output paths to always use / even on Windows.

@tamuratak
Copy link
Contributor Author

With the commit, 9ca0c06, %OUTDIR% cannot be used with cmd.exe in recipes on Windows as far as I tried. For example, the following recipe does not work. I do not have a specific opinion that it should work.

{
    "latex-workshop.latex.recipes": [
        {
          "name": "latexmk_copy",
          "tools": ["latexmk", "copyPDF"]
        }
    ],
    "latex-workshop.latex.tools": [
        {
          "name": "latexmk",
          "command": "latexmk",
          "args": [
            "-synctex=1",
            "-interaction=nonstopmode",
            "-file-line-error",
            "-pdf",
            "-outdir=%OUTDIR%",
            "%DOC%"
          ],
          "env": {}
        },
        {
          "name": "copyPDF",
          "command": "cmd.exe",
          "args": ["/C", "copy", "\"%OUTDIR%/%DOCFILE%.pdf\"",  "\"%OUTDIR%/b.pdf\""],
          "env": {}
        }
    ]
}

@tamuratak
Copy link
Contributor Author

@jlelong
Copy link
Collaborator

jlelong commented Mar 30, 2020

I missed that point. This is indeed an issue. Short summary

  • some LaTeX compilers such as lualatex require the use of /. This can be achieved by setting $MSWin_back_slash=0 in latexmk. Since latexmk-4.69 (only 4.67 is on CTAN), there is even a command line option for that -MSWinBackSlash-.
  • When %OUTDIR% dir is ./, we cannot use back slash as .\ causes errors.
  • The use of \ breaks cmd.exe

It seems to me that %DOC%always uses `/``

replaceArgumentPlaceholders(rootFile: string, tmpDir: string): (arg: string) => string {
return (arg: string) => {
const docker = vscode.workspace.getConfiguration('latex-workshop').get('docker.enabled')
const doc = rootFile.replace(/\.tex$/, '').split(path.sep).join('/')
const docfile = path.basename(rootFile, '.tex').split(path.sep).join('/')
const outDir = this.extension.manager.getOutDir(rootFile)
return arg.replace(/%DOC%/g, docker ? docfile : doc)
.replace(/%DOCFILE%/g, docfile)
.replace(/%DIR%/g, path.dirname(rootFile).split(path.sep).join('/'))
.replace(/%TMPDIR%/g, tmpDir)
.replace(/%OUTDIR%/g, outDir)
}
}
}

So I would be tempted to do the same for %OUTDIR%.

@jlelong jlelong reopened this Mar 30, 2020
@tamuratak
Copy link
Contributor Author

tamuratak commented Mar 30, 2020

As far as I tried, on the command prompt of Windows, the following works well with forward slashes (double quotes are necessary):

cmd.exe /C copy "C:/path/a.pdf" "C:/path/b.pdf"

I do not know why the recipe does not work.

@tamuratak
Copy link
Contributor Author

An easy solution is providing placeholders with backward slashes, %OUTDIR_W32%, %DOC_W32%, ... and placeholders with forward slashes, %OUTDIR%, %DOC%, ... at the same time on Windows.

@jlelong
Copy link
Collaborator

jlelong commented Mar 30, 2020

That's for sure. It might be related to nodejs/node-v0.x-archive#25895 (comment)

@jlelong
Copy link
Collaborator

jlelong commented Mar 30, 2020

I think the explanation is nodejs/node#5060 (comment)
We need to pass {windowsVerbatimArguments: true} to prevent node from escaping all quotes.
See the corresponding test https://github.com/jlelong/LaTeX-Workshop/runs/545593932?check_suite_focus=true

All this is related to the way arguments to cmd.exe should be quoted. Using cross-spawn, we can make everything work by simply dropping cmd.exe and using copy as the command. If the cmd does not exist, cross-spawn automatically wraps up the command and its argument and pass the whole to cmd.exe with the appropriate escaping. See https://github.com/jlelong/LaTeX-Workshop/runs/545688242?check_suite_focus=true

https://github.com/jlelong/LaTeX-Workshop/blob/c8a4d3ff79bc4e5ffb2db3630db58f10991742ad/test/fixtures/build/fixture100/.vscode/settings.json#L22-L27

https://github.com/jlelong/LaTeX-Workshop/blob/49f68155c4b658d9af2ac4ddb2451d9398529e88/src/components/builder.ts#L270

where cs is cross-spawn.

As a conclusion of all this, I would be tempted to use cross-spawn everywhere. We already use cross-spawn to run latexindent following #1873 because escaping issues with spaces.

@jlelong
Copy link
Collaborator

jlelong commented Mar 31, 2020

@tamuratak What do you think of this?

@tamuratak
Copy link
Contributor Author

tamuratak commented Mar 31, 2020

My main concern is that there might be a case cmd.exe does not work well with forward slashes. See https://stackoverflow.com/questions/10523708/why-does-the-cmd-exe-shell-on-windows-fail-with-paths-using-a-forward-slash. However, we can add %OUTDIR_W32%, %DOC_W32%, ..., later if users request them.

So, if cross-spawn works well with typical recipes other than default ones, I think it is definitely ok. We might need some more tests.

@tamuratak
Copy link
Contributor Author

tamuratak commented Mar 31, 2020

One example I am considering is a recipe using a quote,q/.../, with latexmk. See https://texwiki.texjp.org/?Visual%20Studio%20Code%2FLaTeX#c465cb18. The usage of / might be problematic.

@jlelong
Copy link
Collaborator

jlelong commented Mar 31, 2020

OK to later add _W32 placeholders with backward slashes.

cross-spawn is just a wrapper around child_process so it should not hurt. I will make a new branch to test it intensively. Btw, many thanks for your test framework. It is awesome!

@tamuratak
Copy link
Contributor Author

I have a question. Do users who are already using recipes with cmd.exe have to rewrite their recipes by dropping cmd.exe? Is there any other case that a user has to rewrite the recipe?

@jlelong
Copy link
Collaborator

jlelong commented Mar 31, 2020

The use of q/.../ might be an issue. I will have a look at it using cross-spawn.

Do users who are already using recipes with cmd.exe have to rewrite their recipes by dropping cmd.exe? Is there any other case that a user has to rewrite the recipe?

No I do not think so. My understanding is that everything that already works with child_process will still work with cross-spawn. It is designed as a drop-in replacement. It only avoids resorting to tricky options or escaping.

@jlelong
Copy link
Collaborator

jlelong commented Mar 31, 2020

It seems that everything works well with cross-spawn and q/.../.
I considered

{
    "latex-workshop.latex.recipes": [
        {
          "name": "latexmk_copy",
          "tools": ["latexmk", "copyPDF"]
        }
    ],
    "latex-workshop.latex.tools": [
        {
          "name": "latexmk",
          "command": "latexmk",
          "args": [
            "-e",
            "$latex=q/pdflatex %O -synctex=1 -interaction=nonstopmode -file-line-error %S/",
            "-e",
            "$pdflatex=q/pdflatex %O -synctex=1 -interaction=nonstopmode -file-line-error %S/",
            "-outdir=%OUTDIR%",
            "-pdf",
            "%DOC%"
          ],
          "env": {}
        },
        {
          "name": "copyPDF",
          "command": "copy",
          "args": ["%OUTDIR%/t.pdf", "%OUTDIR%/b.pdf"],
          "env": {}
        }
    ]
}

https://github.com/jlelong/LaTeX-Workshop/actions/runs/67671244 passed

If you have a look at master...jlelong:test-cmd-exe, you will see that the only meaningful changes to make all this work are

  • the direct use of copy as a real command instead of going through `cmd.exe``
  • to spawn the recipes using cross-spawn instead of child_process.

jlelong added a commit that referenced this issue Mar 31, 2020
Also use cross-spawn for calling texdoc.
Related to #2010
@jlelong
Copy link
Collaborator

jlelong commented Apr 6, 2020

See https://github.com/James-Yu/LaTeX-Workshop/wiki/Compile#placeholders for the definitions of the new placeholders.

@tamuratak
Copy link
Contributor Author

tamuratak commented Apr 8, 2020

Should we use cross-spawn also in viewer.ts? If there is not a specific reason, we had better leave it unchanged since we do not test external SyncTeX and those recipes are tricky.

import * as cs from 'cross-spawn'

[Edit] I have found that viewer.ts has nothing to do with external SyncTeX. So it will not cause problems.

jlelong added a commit that referenced this issue Apr 8, 2020
@jlelong
Copy link
Collaborator

jlelong commented Apr 8, 2020

Correct, this is only used to launch the external viewer. I do not know if this feature is much used but cross-spawn should help in some cases.

Indeed, we still use child_process for external synctex

syncTeXExternal(line: number, pdfFile: string, rootFile: string) {
if (!vscode.window.activeTextEditor) {
return
}
const texFile = vscode.window.activeTextEditor.document.uri.fsPath
const configuration = vscode.workspace.getConfiguration('latex-workshop')
const command = configuration.get('view.pdf.external.synctex.command') as string
let args = configuration.get('view.pdf.external.synctex.args') as string[]
if (args) {
args = args.map(arg => {
return replaceArgumentPlaceholders(rootFile, this.extension.builder.tmpDir)(arg)
.replace(/%PDF%/g, pdfFile)
.replace(/%LINE%/g, line.toString())
.replace(/%TEX%/g, texFile)
})
}
this.extension.manager.setEnvVar()
cp.spawn(command, args)
this.extension.logger.addLogMessage(`Open external viewer for syncTeX from ${pdfFile}`)
}

Repository owner locked as resolved and limited conversation to collaborators May 16, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
dev Development discussions
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants