emscripten-core/emscripten

Don't Provide `-DCMAKE_TOOLCHAIN_FILE` in emcmake Unless Configuring with CMake.

Open

#14.546 geöffnet am 28. Juni 2021

Auf GitHub ansehen
 (5 Kommentare) (0 Reaktionen) (0 zugewiesene Personen)C++ (3.519 Forks)batch import
good first bughelp wanted

Repository-Metriken

Stars
 (27.361 Stars)
PR-Merge-Metriken
 (Durchschn. Merge 19T 10h) (147 gemergte PRs in 30 T)

Beschreibung

Background

CMake provides numerous commands 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:

emcmake cmake ..
emcmake cmake --build . --config Release

The second command fails, with the following error output:

 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 <dir> [options] [-- [native-options]]
Options:
  <dir>          = Project binary directory to be built.
  --parallel [<jobs>], -j [<jobs>]
                 = Build in parallel using the given number of jobs. 
                   If <jobs> 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 <tgt>..., -t <tgt>... 
                 = Build <tgt> instead of default targets.
  --config <cfg> = For multi-configuration tools, choose <cfg>.
  --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) 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:

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 ... <source>
    #   cmake ... <build>
    #   cmake ... -S <source> -B <build>
    # 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