Skip to content

Fix msvc/intel/snippet detect when temp path contains spaces - #7523

Merged
waruqi merged 2 commits into
xmake-io:devfrom
DavidWang19:fix/detect-bat-path-with-spaces
May 3, 2026
Merged

Fix msvc/intel/snippet detect when temp path contains spaces#7523
waruqi merged 2 commits into
xmake-io:devfrom
DavidWang19:fix/detect-bat-path-with-spaces

Conversation

@DavidWang19

@DavidWang19 DavidWang19 commented May 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • MSVC, Intel oneAPI (icl/icx/ifort/ifx) and tryrun C/C++/Fortran snippet detection fail on Windows when the user profile name contains a space (e.g. C:\Users\Some Name\...).
  • With xmake f --toolchain=msvc (or --toolchain=clang-cl) the user gets a "toolchain not found" error; with plain xmake f xmake silently falls back to MinGW and prints no warning.
  • Fix: at every affected call site, switch from os.run / os.iorun / os.vrun to the corresponding *v variant, so the temp file path is no longer parsed by os.argv.

Root cause

The detection scripts in xmake/modules/detect/sdks/ write a generated .bat under os.tmpfile() (which on Windows lives under the user profile) and run it via os.iorun(genvcvars_bat) or os.run(...). These wrappers parse their cmd through os.argv, a POSIX-shell-style word splitter (core/src/xmake/os/argv.c:83). Without surrounding quotes the path gets shredded:

"C:\Users\Some Name\AppData\Local\Temp\.xmake\...\_genvcvars.bat"
    -> ["C:\Users\Some", "Name\AppData\...\_genvcvars.bat"]

CreateProcess then runs against the directory C:\Users\Some and fails with ERROR_BAD_EXE_FORMAT (193). In find_vstudio the failure is swallowed by the surrounding try {} and turned into an empty vcvarsall = {}. The same root cause hits Intel oneAPI detection on Windows (find_iccenv / find_icxenv / find_ifortenv / find_ifxenv) and any tryrun feature probe in check_cxsnippets / check_fcsnippets (cross-platform but rarely tripped on POSIX).

Fix

Switch every site that passes a single bare path to its *v variant. xmake's C-side process._openv already knows how to launch a .bat directly, so no cmd.exe wrapping is needed.

- local outdata, errdata = try {function () return os.iorun(genvcvars_bat) end}
+ local outdata, errdata = try {function () return os.iorunv(genvcvars_bat) end}
- os.run(geniclvars_bat)
+ os.runv(geniclvars_bat)
- local output = os.iorun(binaryfile)
+ local output = os.iorunv(binaryfile)
...
- os.vrun(binaryfile)
+ os.vrunv(binaryfile)

A short @note comment was added at each site so the choice does not get reverted later.

Test plan

Verified on Windows 11 with a user profile path containing a space, with VS 2026 Community installed.

xmake lua detect.sdks.find_vstudio after the fix:

{
  "2026" = {
    version = "18.0",
    vcvarsall_bat = "C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvarsall.bat",
    vcvarsall = {
      arm64   = { VCToolsVersion = "14.50.35717", PATH = "...", LIB = "...", INCLUDE = "...", ... },
      arm64ec = { ... },
      x64     = { ... },
      x86     = { ... },
      arm     = { ... }
    }
  }
}

Before the fix xmake lua detect.sdks.find_vstudio shows

{
  "2026" = {
    version = "18.0",
    vcvarsall_bat = "C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvarsall.bat",
    vcvarsall = { }
  }
}

Pass the temp bat / test binary via the *v variants of os.run / iorun /
vrun so os.argv does not split the path on whitespace.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates several SDK detection and snippet checking modules to use vector-based process execution functions (e.g., os.runv, os.iorunv) to prevent path splitting on whitespace, which is common on Windows. However, the implementation introduces several bugs because the return signature of os.iorunv differs from os.iorun. Specifically, os.iorunv returns a boolean success status as its first value, causing subsequent string operations on the captured output to fail. These return values need to be correctly unpacked to avoid runtime errors.

local outdata, errdata = try {function () return os.iorun(genvcvars_bat) end}
-- @note we use iorunv here so the bat path is not split on whitespace by os.argv,
-- which breaks detection when the temp file lives under a path with spaces.
local outdata, errdata = try {function () return os.iorunv(genvcvars_bat, {}) end}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The return values of os.iorunv are ok, outdata, errdata, errors. By assigning them to local outdata, errdata, outdata receives the boolean success status and errdata receives the stdout string. This causes errdata to be printed as an error message on line 301 (if stdout is not empty), and more importantly, outdata:split("\n") on line 309 will fail because outdata is a boolean. You should capture the boolean status separately and align the variables with the expected output strings.

Comment on lines 262 to 263
local output = os.iorunv(binaryfile, {})
if output then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The first return value of os.iorunv is a boolean indicating success, not the output string. Assigning it to output will cause output:trim() to fail on line 264 because output is a boolean. You should capture the status and the output string separately.

                    local ok, output = os.iorunv(binaryfile, {})
                    if ok and output then

Comment on lines 121 to 122
local output = os.iorunv(binaryfile, {})
if output then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The first return value of os.iorunv is a boolean indicating success, not the output string. Assigning it to output will cause output:trim() to fail on line 124 because output is a boolean. You should capture the status and the output string separately.

                    local ok, output = os.iorunv(binaryfile, {})
                    if ok and output then

@waruqi

waruqi commented May 3, 2026

Copy link
Copy Markdown
Member

?

os.run(geniclvars_bat)
-- @note we use runv here so the bat path is not split on whitespace by os.argv,
-- which breaks detection when the temp file lives under a path with spaces.
os.runv(geniclvars_bat, {})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

, {} is optional

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Refactored.

@waruqi waruqi added this to the v3.0.9 milestone May 3, 2026
@waruqi
waruqi merged commit 8bbbb28 into xmake-io:dev May 3, 2026
37 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.

2 participants