emscripten-core / emscripten-core/emscripten
Don't Provide `-DCMAKE_TOOLCHAIN_FILE` in emcmake Unless Configuring with CMake.
- Dominant language
- C++
- Stars
- 27.6k
- Forks
- 3.6k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 105
Description
# Background
CMake provides numerous [commands](https://cmake.org/cmake/help/latest/manual/cmake.1.html) in addition to merely configuring projects, which can be useful for portability when using different generators. Examples of commands include:
- `--build`
- `--install`
- `--open`
- `-E` (run a command)
- `--find-package`
Unfortunately, none of these accept `-DCMAKE_TOOLCHAIN_FILE` as a valid argument.
# Example
A sample workflow would be:
```bash
emcmake cmake ..
emcmake cmake --build . --config Release
```
The second command fails, with the following error output:
```bash
emcmake cmake --build . --config Release
configure: cmake --build . --config Release -DCMAKE_TOOLCHAIN_FILE=/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_CROSSCOMPILING_EMULATOR=/emsdk/node/14.15.5_64bit/bin/node
Unknown argument -DCMAKE_TOOLCHAIN_FILE=/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake
Unknown argument -DCMAKE_CROSSCOMPILING_EMULATOR=/emsdk/node/14.15.5_64bit/bin/node
Usage: cmake --build [options] [-- [native-options]]
Options:
= Project binary directory to be built.
--parallel [], -j []
= Build in parallel using the given number of jobs.
If is omitted the native build tool's
default number is used.
The CMAKE_BUILD_PARALLEL_LEVEL environment variable
specifies a default parallel level when this option
is not given.
--target ..., -t ...
= Build instead of default targets.
--config = For multi-configuration tools, choose .
--clean-first = Build target 'clean' first, then build.
(To clean only, use --target 'clean'.)
--verbose, -v = Enable verbose output - if supported - including
the build commands to be executed.
-- = Pass remaining options to the native tool.
emcmake: error: 'cmake --build . --config Release -DCMAKE_TOOLCHAIN_FILE=/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_CROSSCOMPILING_EMULATOR=/emsdk/node/14.15.5_64bit/bin/node' failed (returned 1)
```
The lines creating the issue are here:
https://github.com/emscripten-core/emscripten/blob/660dcc00598495f5cf353771995d77a13735c0c9/emcmake.py#L35-L36
# Solution
Luckily, all of the commands besides `-P` (run a [script](https://cmake.org/cmake/help/latest/manual/cmake.1.html#run-a-script)) only accept the command as the next argument. It must be `cmake --build . --config Release`, it cannot be `cmake --config Release --build .`, which simplifies the process dramatically.
There's then 3 scenarios:
1. cmake is followed directly by a command not compatible with `-DCMAKE_TOOLCHAIN_FILE=...`
2. cmake is following by `-D...` defines, and then `-P`, making it incompatible with `-DCMAKE_TOOLCHAIN_FILE=...`
3. everything else, which works out-of-the-box
For example, `cmake --build .` works but `cmake -DX=1 --build .` does not.
# Solution
In Python pseudocode, we can therefore fix this logic as follows:
```python
import sys
# Get all arguments after the initial command, emcmake.
argv = sys.argv[1:]
# Do the normal logic to verify `cmake` is the first argument
# ...
def should_add_toolchain(args):
'''Check if we should add a toolchain file.'''
unsupported = {
'--build',
'--install',
'--open',
'-E',
'--find-package',
'--help',
}
if not args:
# called as `cmake`, does nothing, do not add a toolchain.
return False
if args[0] in unsupported:
# called where a toolchain is unsupported, do not add it.
return False
# skip all arguments leading with `-D`, to see if the command
# is a script argument.
index = 0
while index < len(args) and args[index].startswith('-D'):
index += 1
if index < len(args) and args[index].startswith('-P'):
return False
# this is a configuration case, and can be called as:
# cmake ...
# cmake ...
# cmake ... -S -B
# can safely as a toolchain to the end if not provided.
return not any(i.startswith('-DCMAKE_TOOLCHAIN_FILE=') for i in argv)
if should_add_toolchain(argv[1:]):
argv.append('-DCMAKE_TOOLCHAIN_FILE=...')
shared.check_call(argv, ...)
```
Although calling `cmake` without `emcmake` works as expected after configuration, considering that `emccmake` is meant to be a helper for `cmake`, it would be nice to have it work as expected even if called in other contexts.
Contributor guide
Assessment
This issue has not been assessed yet.