emscripten-core/emscripten

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

Open

#14,546 建立於 2021年6月28日

在 GitHub 查看
 (5 留言) (0 反應) (0 負責人)C++ (3,519 fork)batch import
good first bughelp wanted

倉庫指標

Star
 (27,361 star)
PR 合併指標
 (平均合併 19天 10小時) (30 天內合併 147 個 PR)

描述

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.

貢獻者指南