diff options
59 files changed, 1731 insertions, 187 deletions
diff --git a/Auxiliary/bash-completion/cmake b/Auxiliary/bash-completion/cmake index 0a862fa..5d67b0b 100644 --- a/Auxiliary/bash-completion/cmake +++ b/Auxiliary/bash-completion/cmake @@ -96,7 +96,7 @@ _cmake() _filedir return ;; - --build) + --build|--open) _filedir -d return ;; diff --git a/Help/manual/cmake-modules.7.rst b/Help/manual/cmake-modules.7.rst index 8f4b252..9fd92ec 100644 --- a/Help/manual/cmake-modules.7.rst +++ b/Help/manual/cmake-modules.7.rst @@ -80,6 +80,7 @@ All Modules /module/ExternalData /module/ExternalProject /module/FeatureSummary + /module/FetchContent /module/FindALSA /module/FindArmadillo /module/FindASPELL diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst index 6a21683..ff8c6c7 100644 --- a/Help/manual/cmake.1.rst +++ b/Help/manual/cmake.1.rst @@ -11,6 +11,7 @@ Synopsis cmake [<options>] (<path-to-source> | <path-to-existing-build>) cmake [(-D <var>=<value>)...] -P <cmake-script-file> cmake --build <dir> [<options>...] [-- <build-tool-options>...] + cmake --open <dir> cmake -E <command> [<options>...] cmake --find-package <options>... @@ -51,6 +52,10 @@ Options ``--build <dir>`` See `Build Tool Mode`_. +``--open <dir>`` + Open the generated project in the associated application. This is + only supported by some generators. + ``-N`` View mode only. diff --git a/Help/module/FetchContent.rst b/Help/module/FetchContent.rst new file mode 100644 index 0000000..c130a6d --- /dev/null +++ b/Help/module/FetchContent.rst @@ -0,0 +1 @@ +.. cmake-module:: ../../Modules/FetchContent.cmake diff --git a/Help/release/dev/FetchContent.rst b/Help/release/dev/FetchContent.rst new file mode 100644 index 0000000..3b12977 --- /dev/null +++ b/Help/release/dev/FetchContent.rst @@ -0,0 +1,11 @@ +FetchContent +------------ + +* A new :module:`FetchContent` module was added which supports populating + content at configure time using any of the download/update methods + supported by :command:`ExternalProject_Add`. This allows the content + to be used immediately during the configure stage, such as with + :command:`add_subdirectory`, etc. Hierarchical project structures are + well supported, allowing parent projects to override the content details + of child projects and ensuring content is not populated multiple times + throughout the whole project tree. diff --git a/Help/release/dev/cmake-open.rst b/Help/release/dev/cmake-open.rst new file mode 100644 index 0000000..a8f77ae --- /dev/null +++ b/Help/release/dev/cmake-open.rst @@ -0,0 +1,6 @@ +cmake-open +---------- + +* The :manual:`cmake(1)` ``--open <dir>`` command line option can now + be used to open generated IDE projects like Visual Studio solutions + or Xcode projects. diff --git a/Modules/CMakeFindSublimeText2.cmake b/Modules/CMakeFindSublimeText2.cmake new file mode 100644 index 0000000..022d010 --- /dev/null +++ b/Modules/CMakeFindSublimeText2.cmake @@ -0,0 +1,23 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + + +# This file is included in CMakeSystemSpecificInformation.cmake if +# the Sublime Text 2 extra generator has been selected. + +find_program(CMAKE_SUBLIMETEXT_EXECUTABLE + NAMES subl3 subl sublime_text + PATHS + "/Applications/Sublime Text.app/Contents/SharedSupport/bin" + "/Applications/Sublime Text 3.app/Contents/SharedSupport/bin" + "/Applications/Sublime Text 2.app/Contents/SharedSupport/bin" + "$ENV{HOME}/Applications/Sublime Text.app/Contents/SharedSupport/bin" + "$ENV{HOME}/Applications/Sublime Text 3.app/Contents/SharedSupport/bin" + "$ENV{HOME}/Applications/Sublime Text 2.app/Contents/SharedSupport/bin" + "/opt/sublime_text" + "/opt/sublime_text_3" + DOC "The Sublime Text executable") + +if(CMAKE_SUBLIMETEXT_EXECUTABLE) + set(CMAKE_OPEN_PROJECT_COMMAND "${CMAKE_SUBLIMETEXT_EXECUTABLE} --project <PROJECT_FILE>" ) +endif() diff --git a/Modules/FetchContent.cmake b/Modules/FetchContent.cmake new file mode 100644 index 0000000..132354f --- /dev/null +++ b/Modules/FetchContent.cmake @@ -0,0 +1,914 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FetchContent +------------------ + +.. only:: html + + .. contents:: + +Overview +^^^^^^^^ + +This module enables populating content at configure time via any method +supported by the :module:`ExternalProject` module. Whereas +:command:`ExternalProject_Add` downloads at build time, the +``FetchContent`` module makes content available immediately, allowing the +configure step to use the content in commands like :command:`add_subdirectory`, +:command:`include` or :command:`file` operations. + +Content population details would normally be defined separately from the +command that performs the actual population. Projects should also +check whether the content has already been populated somewhere else in the +project hierarchy. Typical usage would look something like this: + +.. code-block:: cmake + + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.8.0 + ) + + FetchContent_GetProperties(googletest) + if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) + endif() + +When using the above pattern with a hierarchical project arrangement, +projects at higher levels in the hierarchy are able to define or override +the population details of content specified anywhere lower in the project +hierarchy. The ability to detect whether content has already been +populated ensures that even if multiple child projects want certain content +to be available, the first one to populate it wins. The other child project +can simply make use of the already available content instead of repeating +the population for itself. See the +:ref:`Examples <fetch-content-examples>` section which demonstrates +this scenario. + +The ``FetchContent`` module also supports defining and populating +content in a single call, with no check for whether the content has been +populated elsewhere in the project already. This is a more low level +operation and would not normally be the way the module is used, but it is +sometimes useful as part of implementing some higher level feature or to +populate some content in CMake's script mode. + + +Declaring Content Details +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. command:: FetchContent_Declare + + .. code-block:: cmake + + FetchContent_Declare(<name> <contentOptions>...) + + The ``FetchContent_Declare()`` function records the options that describe + how to populate the specified content, but if such details have already + been recorded earlier in this project (regardless of where in the project + hierarchy), this and all later calls for the same content ``<name>`` are + ignored. This "first to record, wins" approach is what allows hierarchical + projects to have parent projects override content details of child projects. + + The content ``<name>`` can be any string without spaces, but good practice + would be to use only letters, numbers and underscores. The name will be + treated case-insensitively and it should be obvious for the content it + represents, often being the name of the child project or the value given + to its top level :command:`project` command (if it is a CMake project). + For well-known public projects, the name should generally be the official + name of the project. Choosing an unusual name makes it unlikely that other + projects needing that same content will use the same name, leading to + the content being populated multiple times. + + The ``<contentOptions>`` can be any of the download or update/patch options + that the :command:`ExternalProject_Add` command understands. The configure, + build, install and test steps are explicitly disabled and therefore options + related to them will be ignored. In most cases, ``<contentOptions>`` will + just be a couple of options defining the download method and method-specific + details like a commit tag or archive hash. For example: + + .. code-block:: cmake + + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.8.0 + ) + + FetchContent_Declare( + myCompanyIcons + URL https://intranet.mycompany.com/assets/iconset_1.12.tar.gz + URL_HASH 5588a7b18261c20068beabfb4f530b87 + ) + + FetchContent_Declare( + myCompanyCertificates + SVN_REPOSITORY svn+ssh://svn.mycompany.com/srv/svn/trunk/certs + SVN_REVISION -r12345 + ) + +Populating The Content +^^^^^^^^^^^^^^^^^^^^^^ + +.. command:: FetchContent_Populate + + .. code-block:: cmake + + FetchContent_Populate( <name> ) + + In most cases, the only argument given to ``FetchContent_Populate()`` is the + ``<name>``. When used this way, the command assumes the content details have + been recorded by an earlier call to :command:`FetchContent_Declare`. The + details are stored in a global property, so they are unaffected by things + like variable or directory scope. Therefore, it doesn't matter where in the + project the details were previously declared, as long as they have been + declared before the call to ``FetchContent_Populate()``. Those saved details + are then used to construct a call to :command:`ExternalProject_Add` in a + private sub-build to perform the content population immediately. The + implementation of ``ExternalProject_Add()`` ensures that if the content has + already been populated in a previous CMake run, that content will be reused + rather than repopulating them again. For the common case where population + involves downloading content, the cost of the download is only paid once. + + An internal global property records when a particular content population + request has been processed. If ``FetchContent_Populate()`` is called more + than once for the same content name within a configure run, the second call + will halt with an error. Projects can and should check whether content + population has already been processed with the + :command:`FetchContent_GetProperties` command before calling + ``FetchContent_Populate()``. + + ``FetchContent_Populate()`` will set three variables in the scope of the + caller; ``<lcName>_POPULATED``, ``<lcName>_SOURCE_DIR`` and + ``<lcName>_BINARY_DIR``, where ``<lcName>`` is the lowercased ``<name>``. + ``<lcName>_POPULATED`` will always be set to ``True`` by the call. + ``<lcName>_SOURCE_DIR`` is the location where the + content can be found upon return (it will have already been populated), while + ``<lcName>_BINARY_DIR`` is a directory intended for use as a corresponding + build directory. The main use case for the two directory variables is to + call :command:`add_subdirectory` immediately after population, i.e.: + + .. code-block:: cmake + + FetchContent_Populate(FooBar ...) + add_subdirectory(${foobar_SOURCE_DIR} ${foobar_BINARY_DIR}) + + The values of the three variables can also be retrieved from anywhere in the + project hierarchy using the :command:`FetchContent_GetProperties` command. + + A number of cache variables influence the behavior of all content population + performed using details saved from a :command:`FetchContent_Declare` call: + + ``FETCHCONTENT_BASE_DIR`` + In most cases, the saved details do not specify any options relating to the + directories to use for the internal sub-build, final source and build areas. + It is generally best to leave these decisions up to the ``FetchContent`` + module to handle on the project's behalf. The ``FETCHCONTENT_BASE_DIR`` + cache variable controls the point under which all content population + directories are collected, but in most cases developers would not need to + change this. The default location is ``${CMAKE_BINARY_DIR}/_deps``, but if + developers change this value, they should aim to keep the path short and + just below the top level of the build tree to avoid running into path + length problems on Windows. + + ``FETCHCONTENT_QUIET`` + The logging output during population can be quite verbose, making the + configure stage quite noisy. This cache option (``ON`` by default) hides + all population output unless an error is encountered. If experiencing + problems with hung downloads, temporarily switching this option off may + help diagnose which content population is causing the issue. + + ``FETCHCONTENT_FULLY_DISCONNECTED`` + When this option is enabled, no attempt is made to download or update + any content. It is assumed that all content has already been populated in + a previous run or the source directories have been pointed at existing + contents the developer has provided manually (using options described + further below). When the developer knows that no changes have been made to + any content details, turning this option ``ON`` can significantly speed up + the configure stage. It is ``OFF`` by default. + + ``FETCHCONTENT_UPDATES_DISCONNECTED`` + This is a less severe download/update control compared to + ``FETCHCONTENT_FULLY_DISCONNECTED``. Instead of bypassing all download and + update logic, the ``FETCHCONTENT_UPDATES_DISCONNECTED`` only disables the + update stage. Therefore, if content has not been downloaded previously, + it will still be downloaded when this option is enabled. This can speed up + the configure stage, but not as much as + ``FETCHCONTENT_FULLY_DISCONNECTED``. It is ``OFF`` by default. + + In addition to the above cache variables, the following cache variables are + also defined for each content name (``<ucName>`` is the uppercased value of + ``<name>``): + + ``FETCHCONTENT_SOURCE_DIR_<ucName>`` + If this is set, no download or update steps are performed for the specified + content and the ``<lcName>_SOURCE_DIR`` variable returned to the caller is + pointed at this location. This gives developers a way to have a separate + checkout of the content that they can modify freely without interference + from the build. The build simply uses that existing source, but it still + defines ``<lcName>_BINARY_DIR`` to point inside its own build area. + Developers are strongly encouraged to use this mechanism rather than + editing the sources populated in the default location, as changes to + sources in the default location can be lost when content population details + are changed by the project. + + ``FETCHCONTENT_UPDATES_DISCONNECTED_<ucName>`` + This is the per-content equivalent of + ``FETCHCONTENT_UPDATES_DISCONNECTED``. If the global option or this option + is ``ON``, then updates will be disabled for the named content. + Disabling updates for individual content can be useful for content whose + details rarely change, while still leaving other frequently changing + content with updates enabled. + + + The ``FetchContent_Populate()`` command also supports a syntax allowing the + content details to be specified directly rather than using any saved + details. This is more low-level and use of this form is generally to be + avoided in favour of using saved content details as outlined above. + Nevertheless, in certain situations it can be useful to invoke the content + population as an isolated operation (typically as part of implementing some + other higher level feature or when using CMake in script mode): + + .. code-block:: cmake + + FetchContent_Populate( <name> + [QUIET] + [SUBBUILD_DIR <subBuildDir>] + [SOURCE_DIR <srcDir>] + [BINARY_DIR <binDir>] + ... + ) + + This form has a number of key differences to that where only ``<name>`` is + provided: + + - All required population details are assumed to have been provided directly + in the call to ``FetchContent_Populate()``. Any saved details for + ``<name>`` are ignored. + - No check is made for whether content for ``<name>`` has already been + populated. + - No global property is set to record that the population has occurred. + - No global properties record the source or binary directories used for the + populated content. + - The ``FETCHCONTENT_FULLY_DISCONNECTED`` and + ``FETCHCONTENT_UPDATES_DISCONNECTED`` cache variables are ignored. + + The ``<lcName>_SOURCE_DIR`` and ``<lcName>_BINARY_DIR`` variables are still + returned to the caller, but since these locations are not stored as global + properties when this form is used, they are only available to the calling + scope and below rather than the entire project hierarchy. No + ``<lcName>_POPULATED`` variable is set in the caller's scope with this form. + + The supported options for ``FetchContent_Populate()`` are the same as those + for :command:`FetchContent_Declare()`. Those few options shown just + above are either specific to ``FetchContent_Populate()`` or their behavior is + slightly modified from how :command:`ExternalProject_Add` treats them. + + ``QUIET`` + The ``QUIET`` option can be given to hide the output associated with + populating the specified content. If the population fails, the output will + be shown regardless of whether this option was given or not so that the + cause of the failure can be diagnosed. The global ``FETCHCONTENT_QUIET`` + cache variable has no effect on ``FetchContent_Populate()`` calls where the + content details are provided directly. + + ``SUBBUILD_DIR`` + The ``SUBBUILD_DIR`` argument can be provided to change the location of the + sub-build created to perform the population. The default value is + ``${CMAKE_CURRENT_BINARY_DIR}/<lcName>-subbuild`` and it would be unusual + to need to override this default. If a relative path is specified, it will + be interpreted as relative to :variable:`CMAKE_CURRENT_BINARY_DIR`. + + ``SOURCE_DIR``, ``BINARY_DIR`` + The ``SOURCE_DIR`` and ``BINARY_DIR`` arguments are supported by + :command:`ExternalProject_Add`, but different default values are used by + ``FetchContent_Populate()``. ``SOURCE_DIR`` defaults to + ``${CMAKE_CURRENT_BINARY_DIR}/<lcName>-src`` and ``BINARY_DIR`` defaults to + ``${CMAKE_CURRENT_BINARY_DIR}/<lcName>-build``. If a relative path is + specified, it will be interpreted as relative to + :variable:`CMAKE_CURRENT_BINARY_DIR`. + + In addition to the above explicit options, any other unrecognized options are + passed through unmodified to :command:`ExternalProject_Add` to perform the + download, patch and update steps. The following options are explicitly + prohibited (they are disabled by the ``FetchContent_Populate()`` command): + + - ``CONFIGURE_COMMAND`` + - ``BUILD_COMMAND`` + - ``INSTALL_COMMAND`` + - ``TEST_COMMAND`` + + If using ``FetchContent_Populate()`` within CMake's script mode, be aware + that the implementation sets up a sub-build which therefore requires a CMake + generator and build tool to be available. If these cannot be found by + default, then the :variable:`CMAKE_GENERATOR` and/or + :variable:`CMAKE_MAKE_PROGRAM` variables will need to be set appropriately + on the command line invoking the script. + + +Retrieve Population Properties +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. command:: FetchContent_GetProperties + + When using saved content details, a call to :command:`FetchContent_Populate` + records information in global properties which can be queried at any time. + This information includes the source and binary directories associated with + the content and also whether or not the content population has been processed + during the current configure run. + + .. code-block:: cmake + + FetchContent_GetProperties( <name> + [SOURCE_DIR <srcDirVar>] + [BINARY_DIR <binDirVar>] + [POPULATED <doneVar>] + ) + + The ``SOURCE_DIR``, ``BINARY_DIR`` and ``POPULATED`` options can be used to + specify which properties should be retrieved. Each option accepts a value + which is the name of the variable in which to store that property. Most of + the time though, only ``<name>`` is given, in which case the call will then + set the same variables as a call to + :command:`FetchContent_Populate(name) <FetchContent_Populate>`. This allows + the following canonical pattern to be used, which ensures that the relevant + variables will always be defined regardless of whether or not the population + has been performed elsewhere in the project already: + + .. code-block:: cmake + + FetchContent_GetProperties(foobar) + if(NOT foobar_POPULATED) + FetchContent_Populate(foobar) + + # Set any custom variables, etc. here, then + # populate the content as part of this build + + add_subdirectory(${foobar_SOURCE_DIR} ${foobar_BINARY_DIR}) + endif() + + The above pattern allows other parts of the overall project hierarchy to + re-use the same content and ensure that it is only populated once. + + +.. _`fetch-content-examples`: + +Examples +^^^^^^^^ + +Consider a project hierarchy where ``projA`` is the top level project and it +depends on projects ``projB`` and ``projC``. Both ``projB`` and ``projC`` +can be built standalone and they also both depend on another project +``projD``. For simplicity, this example will assume that all four projects +are available on a company git server. The ``CMakeLists.txt`` of each project +might have sections like the following: + +*projA*: + +.. code-block:: cmake + + include(FetchContent) + FetchContent_Declare( + projB + GIT_REPOSITORY git@mycompany.com/git/projB.git + GIT_TAG 4a89dc7e24ff212a7b5167bef7ab079d + ) + FetchContent_Declare( + projC + GIT_REPOSITORY git@mycompany.com/git/projC.git + GIT_TAG 4ad4016bd1d8d5412d135cf8ceea1bb9 + ) + FetchContent_Declare( + projD + GIT_REPOSITORY git@mycompany.com/git/projD.git + GIT_TAG origin/integrationBranch + ) + + FetchContent_GetProperties(projB) + if(NOT projb_POPULATED) + FetchContent_Populate(projB) + add_subdirectory(${projb_SOURCE_DIR} ${projb_BINARY_DIR}) + endif() + + FetchContent_GetProperties(projC) + if(NOT projc_POPULATED) + FetchContent_Populate(projC) + add_subdirectory(${projc_SOURCE_DIR} ${projc_BINARY_DIR}) + endif() + +*projB*: + +.. code-block:: cmake + + include(FetchContent) + FetchContent_Declare( + projD + GIT_REPOSITORY git@mycompany.com/git/projD.git + GIT_TAG 20b415f9034bbd2a2e8216e9a5c9e632 + ) + + FetchContent_GetProperties(projD) + if(NOT projd_POPULATED) + FetchContent_Populate(projD) + add_subdirectory(${projd_SOURCE_DIR} ${projd_BINARY_DIR}) + endif() + + +*projC*: + +.. code-block:: cmake + + include(FetchContent) + FetchContent_Declare( + projD + GIT_REPOSITORY git@mycompany.com/git/projD.git + GIT_TAG 7d9a17ad2c962aa13e2fbb8043fb6b8a + ) + + FetchContent_GetProperties(projD) + if(NOT projd_POPULATED) + FetchContent_Populate(projD) + add_subdirectory(${projd_SOURCE_DIR} ${projd_BINARY_DIR}) + endif() + +A few key points should be noted in the above: + +- ``projB`` and ``projC`` define different content details for ``projD``, + but ``projA`` also defines a set of content details for ``projD`` and + because ``projA`` will define them first, the details from ``projB`` and + ``projC`` will not be used. The override details defined by ``projA`` + are not required to match either of those from ``projB`` or ``projC``, but + it is up to the higher level project to ensure that the details it does + define still make sense for the child projects. +- While ``projA`` defined content details for ``projD``, it did not need + to explicitly call ``FetchContent_Populate(projD)`` itself. Instead, it + leaves that to a child project to do (in this case it will be ``projB`` + since it is added to the build ahead of ``projC``). If ``projA`` needed to + customize how the ``projD`` content was brought into the build as well + (e.g. define some CMake variables before calling + :command:`add_subdirectory` after populating), it would do the call to + ``FetchContent_Populate()``, etc. just as it did for the ``projB`` and + ``projC`` content. For higher level projects, it is usually enough to + just define the override content details and leave the actual population + to the child projects. This saves repeating the same thing at each level + of the project hierarchy unnecessarily. +- Even though ``projA`` is the top level project in this example, it still + checks whether ``projB`` and ``projC`` have already been populated before + going ahead to do those populations. This makes ``projA`` able to be more + easily incorporated as a child of some other higher level project in the + future if required. Always protect a call to + :command:`FetchContent_Populate` with a check to + :command:`FetchContent_GetProperties`, even in what may be considered a top + level project at the time. + + +The following example demonstrates how one might download and unpack a +firmware tarball using CMake's :manual:`script mode <cmake(1)>`. The call to +:command:`FetchContent_Populate` specifies all the content details and the +unpacked firmware will be placed in a ``firmware`` directory below the +current working directory. + +*getFirmware.cmake*: + +.. code-block:: cmake + + # NOTE: Intended to be run in script mode with cmake -P + include(FetchContent) + FetchContent_Populate( + firmware + URL https://mycompany.com/assets/firmware-1.23-arm.tar.gz + URL_HASH MD5=68247684da89b608d466253762b0ff11 + SOURCE_DIR firmware + ) + +#]=======================================================================] + + +set(__FetchContent_privateDir "${CMAKE_CURRENT_LIST_DIR}/FetchContent") + +#======================================================================= +# Recording and retrieving content details for later population +#======================================================================= + +# Internal use, projects must not call this directly. It is +# intended for use by FetchContent_Declare() only. +# +# Sets a content-specific global property (not meant for use +# outside of functions defined here in this file) which can later +# be retrieved using __FetchContent_getSavedDetails() with just the +# same content name. If there is already a value stored in the +# property, it is left unchanged and this call has no effect. +# This allows parent projects to define the content details, +# overriding anything a child project may try to set (properties +# are not cached between runs, so the first thing to set it in a +# build will be in control). +function(__FetchContent_declareDetails contentName) + + string(TOLOWER ${contentName} contentNameLower) + set(propertyName "_FetchContent_${contentNameLower}_savedDetails") + get_property(alreadyDefined GLOBAL PROPERTY ${propertyName} DEFINED) + if(NOT alreadyDefined) + define_property(GLOBAL PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} ${ARGN}) + endif() + +endfunction() + + +# Internal use, projects must not call this directly. It is +# intended for use by the FetchContent_Declare() function. +# +# Retrieves details saved for the specified content in an +# earlier call to __FetchContent_declareDetails(). +function(__FetchContent_getSavedDetails contentName outVar) + + string(TOLOWER ${contentName} contentNameLower) + set(propertyName "_FetchContent_${contentNameLower}_savedDetails") + get_property(alreadyDefined GLOBAL PROPERTY ${propertyName} DEFINED) + if(NOT alreadyDefined) + message(FATAL_ERROR "No content details recorded for ${contentName}") + endif() + get_property(propertyValue GLOBAL PROPERTY ${propertyName}) + set(${outVar} "${propertyValue}" PARENT_SCOPE) + +endfunction() + + +# Saves population details of the content, sets defaults for the +# SOURCE_DIR and BUILD_DIR. +function(FetchContent_Declare contentName) + + set(options "") + set(oneValueArgs SVN_REPOSITORY) + set(multiValueArgs "") + + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + unset(srcDirSuffix) + unset(svnRepoArgs) + if(ARG_SVN_REPOSITORY) + # Add a hash of the svn repository URL to the source dir. This works + # around the problem where if the URL changes, the download would + # fail because it tries to checkout/update rather than switch the + # old URL to the new one. We limit the hash to the first 7 characters + # so that the source path doesn't get overly long (which can be a + # problem on windows due to path length limits). + string(SHA1 urlSHA ${ARG_SVN_REPOSITORY}) + string(SUBSTRING ${urlSHA} 0 7 urlSHA) + set(srcDirSuffix "-${urlSHA}") + set(svnRepoArgs SVN_REPOSITORY ${ARG_SVN_REPOSITORY}) + endif() + + string(TOLOWER ${contentName} contentNameLower) + __FetchContent_declareDetails( + ${contentNameLower} + SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src${srcDirSuffix}" + BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build" + ${svnRepoArgs} + # List these last so they can override things we set above + ${ARG_UNPARSED_ARGUMENTS} + ) + +endfunction() + + +#======================================================================= +# Set/get whether the specified content has been populated yet. +# The setter also records the source and binary dirs used. +#======================================================================= + +# Internal use, projects must not call this directly. It is +# intended for use by the FetchContent_Populate() function to +# record when FetchContent_Populate() is called for a particular +# content name. +function(__FetchContent_setPopulated contentName sourceDir binaryDir) + + string(TOLOWER ${contentName} contentNameLower) + set(prefix "_FetchContent_${contentNameLower}") + + set(propertyName "${prefix}_sourceDir") + define_property(GLOBAL PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} ${sourceDir}) + + set(propertyName "${prefix}_binaryDir") + define_property(GLOBAL PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} ${binaryDir}) + + set(propertyName "${prefix}_populated") + define_property(GLOBAL PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} True) + +endfunction() + + +# Set variables in the calling scope for any of the retrievable +# properties. If no specific properties are requested, variables +# will be set for all retrievable properties. +# +# This function is intended to also be used by projects as the canonical +# way to detect whether they should call FetchContent_Populate() +# and pull the populated source into the build with add_subdirectory(), +# if they are using the populated content in that way. +function(FetchContent_GetProperties contentName) + + string(TOLOWER ${contentName} contentNameLower) + + set(options "") + set(oneValueArgs SOURCE_DIR BINARY_DIR POPULATED) + set(multiValueArgs "") + + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT ARG_SOURCE_DIR AND + NOT ARG_BINARY_DIR AND + NOT ARG_POPULATED) + # No specific properties requested, provide them all + set(ARG_SOURCE_DIR ${contentNameLower}_SOURCE_DIR) + set(ARG_BINARY_DIR ${contentNameLower}_BINARY_DIR) + set(ARG_POPULATED ${contentNameLower}_POPULATED) + endif() + + set(prefix "_FetchContent_${contentNameLower}") + + if(ARG_SOURCE_DIR) + set(propertyName "${prefix}_sourceDir") + get_property(value GLOBAL PROPERTY ${propertyName}) + if(value) + set(${ARG_SOURCE_DIR} ${value} PARENT_SCOPE) + endif() + endif() + + if(ARG_BINARY_DIR) + set(propertyName "${prefix}_binaryDir") + get_property(value GLOBAL PROPERTY ${propertyName}) + if(value) + set(${ARG_BINARY_DIR} ${value} PARENT_SCOPE) + endif() + endif() + + if(ARG_POPULATED) + set(propertyName "${prefix}_populated") + get_property(value GLOBAL PROPERTY ${propertyName} DEFINED) + set(${ARG_POPULATED} ${value} PARENT_SCOPE) + endif() + +endfunction() + + +#======================================================================= +# Performing the population +#======================================================================= + +# The value of contentName will always have been lowercased by the caller. +# All other arguments are assumed to be options that are understood by +# ExternalProject_Add(), except for QUIET and SUBBUILD_DIR. +function(__FetchContent_directPopulate contentName) + + set(options + QUIET + ) + set(oneValueArgs + SUBBUILD_DIR + SOURCE_DIR + BINARY_DIR + # Prevent the following from being passed through + CONFIGURE_COMMAND + BUILD_COMMAND + INSTALL_COMMAND + TEST_COMMAND + ) + set(multiValueArgs "") + + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT ARG_SUBBUILD_DIR) + message(FATAL_ERROR "Internal error: SUBBUILD_DIR not set") + elseif(NOT IS_ABSOLUTE "${ARG_SUBBUILD_DIR}") + set(ARG_SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_SUBBUILD_DIR}") + endif() + + if(NOT ARG_SOURCE_DIR) + message(FATAL_ERROR "Internal error: SOURCE_DIR not set") + elseif(NOT IS_ABSOLUTE "${ARG_SOURCE_DIR}") + set(ARG_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_SOURCE_DIR}") + endif() + + if(NOT ARG_BINARY_DIR) + message(FATAL_ERROR "Internal error: BINARY_DIR not set") + elseif(NOT IS_ABSOLUTE "${ARG_BINARY_DIR}") + set(ARG_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_BINARY_DIR}") + endif() + + # Ensure the caller can know where to find the source and build directories + # with some convenient variables. Doing this here ensures the caller sees + # the correct result in the case where the default values are overridden by + # the content details set by the project. + set(${contentName}_SOURCE_DIR "${ARG_SOURCE_DIR}" PARENT_SCOPE) + set(${contentName}_BINARY_DIR "${ARG_BINARY_DIR}" PARENT_SCOPE) + + # The unparsed arguments may contain spaces, so build up ARG_EXTRA + # in such a way that it correctly substitutes into the generated + # CMakeLists.txt file with each argument quoted. + unset(ARG_EXTRA) + foreach(arg IN LISTS ARG_UNPARSED_ARGUMENTS) + set(ARG_EXTRA "${ARG_EXTRA} \"${arg}\"") + endforeach() + + # Hide output if requested, but save it to a variable in case there's an + # error so we can show the output upon failure. When not quiet, don't + # capture the output to a variable because the user may want to see the + # output as it happens (e.g. progress during long downloads). Combine both + # stdout and stderr in the one capture variable so the output stays in order. + if (ARG_QUIET) + set(outputOptions + OUTPUT_VARIABLE capturedOutput + ERROR_VARIABLE capturedOutput + ) + else() + set(capturedOutput) + set(outputOptions) + message(STATUS "Populating ${contentName}") + endif() + + if(CMAKE_GENERATOR) + set(generatorOpts "-G${CMAKE_GENERATOR}") + if(CMAKE_GENERATOR_PLATFORM) + list(APPEND generatorOpts "-A${CMAKE_GENERATOR_PLATFORM}") + endif() + if(CMAKE_GENERATOR_TOOLSET) + list(APPEND generatorOpts "-T${CMAKE_GENERATOR_TOOLSET}") + endif() + + list(APPEND generatorOpts "-DCMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}") + + else() + # Likely we've been invoked via CMake's script mode where no + # generator is set (and hence CMAKE_MAKE_PROGRAM could not be + # trusted even if provided). We will have to rely on being + # able to find the default generator and build tool. + unset(generatorOpts) + endif() + + # Create and build a separate CMake project to carry out the population. + # If we've already previously done these steps, they will not cause + # anything to be updated, so extra rebuilds of the project won't occur. + # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project + # has this set to something not findable on the PATH. + configure_file("${__FetchContent_privateDir}/CMakeLists.cmake.in" + "${ARG_SUBBUILD_DIR}/CMakeLists.txt") + execute_process( + COMMAND ${CMAKE_COMMAND} ${generatorOpts} . + RESULT_VARIABLE result + ${outputOptions} + WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}" + ) + if(result) + if(capturedOutput) + message("${capturedOutput}") + endif() + message(FATAL_ERROR "CMake step for ${contentName} failed: ${result}") + endif() + execute_process( + COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + ${outputOptions} + WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}" + ) + if(result) + if(capturedOutput) + message("${capturedOutput}") + endif() + message(FATAL_ERROR "Build step for ${contentName} failed: ${result}") + endif() + +endfunction() + + +option(FETCHCONTENT_FULLY_DISCONNECTED "Disables all attempts to download or update content and assumes source dirs already exist") +option(FETCHCONTENT_UPDATES_DISCONNECTED "Enables UPDATE_DISCONNECTED behavior for all content population") +option(FETCHCONTENT_QUIET "Enables QUIET option for all content population" ON) +set(FETCHCONTENT_BASE_DIR "${CMAKE_BINARY_DIR}/_deps" CACHE PATH "Directory under which to collect all populated content") + +# Populate the specified content using details stored from +# an earlier call to FetchContent_Declare(). +function(FetchContent_Populate contentName) + + if(NOT contentName) + message(FATAL_ERROR "Empty contentName not allowed for FetchContent_Populate()") + endif() + + string(TOLOWER ${contentName} contentNameLower) + + if(ARGN) + # This is the direct population form with details fully specified + # as part of the call, so we already have everything we need + __FetchContent_directPopulate( + ${contentNameLower} + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-build" + ${ARGN} # Could override any of the above ..._DIR variables + ) + + # Pass source and binary dir variables back to the caller + set(${contentNameLower}_SOURCE_DIR "${${contentNameLower}_SOURCE_DIR}" PARENT_SCOPE) + set(${contentNameLower}_BINARY_DIR "${${contentNameLower}_BINARY_DIR}" PARENT_SCOPE) + + # Don't set global properties, or record that we did this population, since + # this was a direct call outside of the normal declared details form. + # We only want to save values in the global properties for content that + # honours the hierarchical details mechanism so that projects are not + # robbed of the ability to override details set in nested projects. + return() + endif() + + # No details provided, so assume they were saved from an earlier call + # to FetchContent_Declare(). Do a check that we haven't already + # populated this content before in case the caller forgot to check. + FetchContent_GetProperties(${contentName}) + if(${contentNameLower}_POPULATED) + message(FATAL_ERROR "Content ${contentName} already populated in ${${contentNameLower}_SOURCE_DIR}") + endif() + + string(TOUPPER ${contentName} contentNameUpper) + set(FETCHCONTENT_SOURCE_DIR_${contentNameUpper} + "${FETCHCONTENT_SOURCE_DIR_${contentNameUpper}}" + CACHE PATH "When not empty, overrides where to find pre-populated content for ${contentName}") + + if(FETCHCONTENT_SOURCE_DIR_${contentNameUpper}) + # The source directory has been explicitly provided in the cache, + # so no population is required + set(${contentNameLower}_SOURCE_DIR "${FETCHCONTENT_SOURCE_DIR_${contentNameUpper}}") + set(${contentNameLower}_BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build") + + elseif(FETCHCONTENT_FULLY_DISCONNECTED) + # Bypass population and assume source is already there from a previous run + set(${contentNameLower}_SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src") + set(${contentNameLower}_BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build") + + else() + # Support both a global "disconnect all updates" and a per-content + # update test (either one being set disables updates for this content). + option(FETCHCONTENT_UPDATES_DISCONNECTED_${contentNameUpper} + "Enables UPDATE_DISCONNECTED behavior just for population of ${contentName}") + if(FETCHCONTENT_UPDATES_DISCONNECTED OR + FETCHCONTENT_UPDATES_DISCONNECTED_${contentNameUpper}) + set(disconnectUpdates True) + else() + set(disconnectUpdates False) + endif() + + if(FETCHCONTENT_QUIET) + set(quietFlag QUIET) + else() + unset(quietFlag) + endif() + + __FetchContent_getSavedDetails(${contentName} contentDetails) + if("${contentDetails}" STREQUAL "") + message(FATAL_ERROR "No details have been set for content: ${contentName}") + endif() + + __FetchContent_directPopulate( + ${contentNameLower} + ${quietFlag} + UPDATE_DISCONNECTED ${disconnectUpdates} + SUBBUILD_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-subbuild" + SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src" + BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build" + # Put the saved details last so they can override any of the + # the options we set above (this can include SOURCE_DIR or + # BUILD_DIR) + ${contentDetails} + ) + endif() + + __FetchContent_setPopulated( + ${contentName} + ${${contentNameLower}_SOURCE_DIR} + ${${contentNameLower}_BINARY_DIR} + ) + + # Pass variables back to the caller. The variables passed back here + # must match what FetchContent_GetProperties() sets when it is called + # with just the content name. + set(${contentNameLower}_SOURCE_DIR "${${contentNameLower}_SOURCE_DIR}" PARENT_SCOPE) + set(${contentNameLower}_BINARY_DIR "${${contentNameLower}_BINARY_DIR}" PARENT_SCOPE) + set(${contentNameLower}_POPULATED True PARENT_SCOPE) + +endfunction() diff --git a/Modules/FetchContent/CMakeLists.cmake.in b/Modules/FetchContent/CMakeLists.cmake.in new file mode 100644 index 0000000..9a7a771 --- /dev/null +++ b/Modules/FetchContent/CMakeLists.cmake.in @@ -0,0 +1,21 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION ${CMAKE_VERSION}) + +# We name the project and the target for the ExternalProject_Add() call +# to something that will highlight to the user what we are working on if +# something goes wrong and an error message is produced. + +project(${contentName}-populate NONE) + +include(ExternalProject) +ExternalProject_Add(${contentName}-populate + ${ARG_EXTRA} + SOURCE_DIR "${ARG_SOURCE_DIR}" + BINARY_DIR "${ARG_BINARY_DIR}" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/Modules/FindOpenCL.cmake b/Modules/FindOpenCL.cmake index b8a7d82..ba87086 100644 --- a/Modules/FindOpenCL.cmake +++ b/Modules/FindOpenCL.cmake @@ -120,9 +120,12 @@ else() NAMES OpenCL PATHS ENV AMDAPPSDKROOT + ENV CUDA_PATH PATH_SUFFIXES lib/x86_64 - lib/x64) + lib/x64 + lib + lib64) endif() set(OpenCL_LIBRARIES ${OpenCL_LIBRARY}) diff --git a/Modules/FindOpenMP.cmake b/Modules/FindOpenMP.cmake index 1d6a039..a39a234 100644 --- a/Modules/FindOpenMP.cmake +++ b/Modules/FindOpenMP.cmake @@ -211,13 +211,27 @@ function(_OPENMP_GET_FLAGS LANG FLAG_MODE OPENMP_FLAG_VAR OPENMP_LIB_NAMES_VAR) unset(_OPENMP_LIB_NAMES) foreach(_OPENMP_IMPLICIT_LIB IN LISTS OpenMP_${LANG}_IMPLICIT_LIBRARIES) - if(NOT "${_OPENMP_IMPLICIT_LIB}" IN_LIST CMAKE_${LANG}_IMPLICIT_LINK_LIBRARIES) - find_library(OpenMP_${_OPENMP_IMPLICIT_LIB}_LIBRARY - NAMES "${_OPENMP_IMPLICIT_LIB}" - HINTS ${OpenMP_${LANG}_IMPLICIT_LINK_DIRS} - ) - mark_as_advanced(OpenMP_${_OPENMP_IMPLICIT_LIB}_LIBRARY) - list(APPEND _OPENMP_LIB_NAMES ${_OPENMP_IMPLICIT_LIB}) + get_filename_component(_OPENMP_IMPLICIT_LIB_DIR "${_OPENMP_IMPLICIT_LIB}" DIRECTORY) + get_filename_component(_OPENMP_IMPLICIT_LIB_NAME "${_OPENMP_IMPLICIT_LIB}" NAME) + get_filename_component(_OPENMP_IMPLICIT_LIB_PLAIN "${_OPENMP_IMPLICIT_LIB}" NAME_WE) + string(REGEX REPLACE "([][+.*?()^$])" "\\\\\\1" _OPENMP_IMPLICIT_LIB_PLAIN_ESC "${_OPENMP_IMPLICIT_LIB_PLAIN}") + string(REGEX REPLACE "([][+.*?()^$])" "\\\\\\1" _OPENMP_IMPLICIT_LIB_PATH_ESC "${_OPENMP_IMPLICIT_LIB}") + if(NOT ( "${_OPENMP_IMPLICIT_LIB}" IN_LIST CMAKE_${LANG}_IMPLICIT_LINK_LIBRARIES + OR "${CMAKE_${LANG}_STANDARD_LIBRARIES}" MATCHES "(^| )(-Wl,)?(-l)?(${_OPENMP_IMPLICIT_LIB_PLAIN_ESC}|${_OPENMP_IMPLICIT_LIB_PATH_ESC})( |$)" + OR "${CMAKE_${LANG}_LINK_EXECUTABLE}" MATCHES "(^| )(-Wl,)?(-l)?(${_OPENMP_IMPLICIT_LIB_PLAIN_ESC}|${_OPENMP_IMPLICIT_LIB_PATH_ESC})( |$)" ) ) + if(_OPENMP_IMPLICIT_LIB_DIR) + set(OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY "${_OPENMP_IMPLICIT_LIB}" CACHE FILEPATH + "Path to the ${_OPENMP_IMPLICIT_LIB_PLAIN} library for OpenMP") + else() + find_library(OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY + NAMES "${_OPENMP_IMPLICIT_LIB_NAME}" + DOC "Path to the ${_OPENMP_IMPLICIT_LIB_PLAIN} library for OpenMP" + HINTS ${OpenMP_${LANG}_IMPLICIT_LINK_DIRS} + CMAKE_FIND_ROOT_PATH_BOTH + ) + endif() + mark_as_advanced(OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY) + list(APPEND _OPENMP_LIB_NAMES ${_OPENMP_IMPLICIT_LIB_PLAIN}) endif() endforeach() set("${OPENMP_LIB_NAMES_VAR}" "${_OPENMP_LIB_NAMES}" PARENT_SCOPE) diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake index c984aad..d0d95b4 100644 --- a/Source/CMakeVersion.cmake +++ b/Source/CMakeVersion.cmake @@ -1,5 +1,5 @@ # CMake version number components. set(CMake_VERSION_MAJOR 3) set(CMake_VERSION_MINOR 10) -set(CMake_VERSION_PATCH 20171016) +set(CMake_VERSION_PATCH 20171019) #set(CMake_VERSION_RC 1) diff --git a/Source/QtDialog/CMakeSetupDialog.cxx b/Source/QtDialog/CMakeSetupDialog.cxx index bbb2395..5be9ec3 100644 --- a/Source/QtDialog/CMakeSetupDialog.cxx +++ b/Source/QtDialog/CMakeSetupDialog.cxx @@ -188,6 +188,9 @@ CMakeSetupDialog::CMakeSetupDialog() connect(this->Output, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(doOutputContextMenu(const QPoint&))); + // disable open project button + this->OpenProjectButton->setDisabled(true); + // start the cmake worker thread this->CMakeThread = new QCMakeThread(this); QObject::connect(this->CMakeThread, SIGNAL(cmakeInitialized()), this, @@ -249,6 +252,10 @@ void CMakeSetupDialog::initialize() SIGNAL(outputMessage(QString)), this, SLOT(message(QString))); + QObject::connect(this->CMakeThread->cmakeInstance(), + SIGNAL(openPossible(bool)), this->OpenProjectButton, + SLOT(setEnabled(bool))); + QObject::connect(this->groupedCheck, SIGNAL(toggled(bool)), this, SLOT(setGroupedView(bool))); QObject::connect(this->advancedCheck, SIGNAL(toggled(bool)), this, @@ -492,24 +499,10 @@ void CMakeSetupDialog::doGenerate() this->ConfigureNeeded = true; } -QString CMakeSetupDialog::getProjectFilename() -{ - QStringList nameFilter; - nameFilter << "*.sln" - << "*.xcodeproj"; - QDir directory(this->BinaryDirectory->currentText()); - QStringList nlnFile = directory.entryList(nameFilter); - - if (nlnFile.count() == 1) { - return this->BinaryDirectory->currentText() + "/" + nlnFile.at(0); - } - - return QString(); -} - void CMakeSetupDialog::doOpenProject() { - QDesktopServices::openUrl(QUrl::fromLocalFile(this->getProjectFilename())); + QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "open", + Qt::QueuedConnection); } void CMakeSetupDialog::closeEvent(QCloseEvent* e) @@ -630,11 +623,6 @@ void CMakeSetupDialog::updateBinaryDirectory(const QString& dir) this->BinaryDirectory->setEditText(dir); this->BinaryDirectory->blockSignals(false); } - if (!this->getProjectFilename().isEmpty()) { - this->OpenProjectButton->setEnabled(true); - } else { - this->OpenProjectButton->setEnabled(false); - } } void CMakeSetupDialog::doBinaryBrowse() @@ -1039,9 +1027,6 @@ void CMakeSetupDialog::enterState(CMakeSetupDialog::State s) this->GenerateButton->setEnabled(true); this->GenerateAction->setEnabled(true); this->ConfigureButton->setEnabled(true); - if (!this->getProjectFilename().isEmpty()) { - this->OpenProjectButton->setEnabled(true); - } this->ConfigureButton->setText(tr("&Configure")); this->GenerateButton->setText(tr("&Generate")); } else if (s == ReadyGenerate) { @@ -1049,9 +1034,6 @@ void CMakeSetupDialog::enterState(CMakeSetupDialog::State s) this->GenerateButton->setEnabled(true); this->GenerateAction->setEnabled(true); this->ConfigureButton->setEnabled(true); - if (!this->getProjectFilename().isEmpty()) { - this->OpenProjectButton->setEnabled(true); - } this->ConfigureButton->setText(tr("&Configure")); this->GenerateButton->setText(tr("&Generate")); } diff --git a/Source/QtDialog/CMakeSetupDialog.h b/Source/QtDialog/CMakeSetupDialog.h index 0da28d8..7b767e5 100644 --- a/Source/QtDialog/CMakeSetupDialog.h +++ b/Source/QtDialog/CMakeSetupDialog.h @@ -31,7 +31,6 @@ protected slots: void initialize(); void doConfigure(); void doGenerate(); - QString getProjectFilename(); void doOpenProject(); void doInstallForCommandLine(); void doHelp(); diff --git a/Source/QtDialog/QCMake.cxx b/Source/QtDialog/QCMake.cxx index d473d9b..7e94a27 100644 --- a/Source/QtDialog/QCMake.cxx +++ b/Source/QtDialog/QCMake.cxx @@ -115,6 +115,8 @@ void QCMake::setBinaryDirectory(const QString& _dir) if (toolset) { this->setToolset(QString::fromLocal8Bit(toolset)); } + + checkOpenPossible(); } } @@ -183,6 +185,26 @@ void QCMake::generate() #endif emit this->generateDone(err); + checkOpenPossible(); +} + +void QCMake::open() +{ +#ifdef Q_OS_WIN + UINT lastErrorMode = SetErrorMode(0); +#endif + + InterruptFlag = 0; + cmSystemTools::ResetErrorOccuredFlag(); + + auto successful = this->CMakeInstance->Open( + this->BinaryDirectory.toLocal8Bit().data(), false); + +#ifdef Q_OS_WIN + SetErrorMode(lastErrorMode); +#endif + + emit this->openDone(successful); } void QCMake::setProperties(const QCMakePropertyList& newProps) @@ -450,3 +472,10 @@ void QCMake::setWarnUnusedMode(bool value) { this->WarnUnusedMode = value; } + +void QCMake::checkOpenPossible() +{ + auto data = this->BinaryDirectory.toLocal8Bit().data(); + auto possible = this->CMakeInstance->Open(data, true); + emit openPossible(possible); +} diff --git a/Source/QtDialog/QCMake.h b/Source/QtDialog/QCMake.h index 3b8cea7..6fae7e3 100644 --- a/Source/QtDialog/QCMake.h +++ b/Source/QtDialog/QCMake.h @@ -80,6 +80,8 @@ public slots: void configure(); /// generate the files void generate(); + /// open the project + void open(); /// set the property values void setProperties(const QCMakePropertyList&); /// interrupt the configure or generate process (if connecting, make a direct @@ -111,6 +113,8 @@ public slots: void setWarnUninitializedMode(bool value); /// set whether to run cmake with warnings about unused variables void setWarnUnusedMode(bool value); + /// check if project IDE open is possible and emit openPossible signal + void checkOpenPossible(); public: /// get the list of cache properties @@ -151,6 +155,10 @@ signals: void debugOutputChanged(bool); /// signal when the toolset changes void toolsetChanged(const QString& toolset); + /// signal when open is done + void openDone(bool successful); + /// signal when open is done + void openPossible(bool possible); protected: cmake* CMakeInstance; diff --git a/Source/cmExternalMakefileProjectGenerator.cxx b/Source/cmExternalMakefileProjectGenerator.cxx index 825ec65..fecd821 100644 --- a/Source/cmExternalMakefileProjectGenerator.cxx +++ b/Source/cmExternalMakefileProjectGenerator.cxx @@ -24,6 +24,13 @@ std::string cmExternalMakefileProjectGenerator::CreateFullGeneratorName( return fullName; } +bool cmExternalMakefileProjectGenerator::Open( + const std::string& /*bindir*/, const std::string& /*projectName*/, + bool /*dryRun*/) +{ + return false; +} + cmExternalMakefileProjectGeneratorFactory:: cmExternalMakefileProjectGeneratorFactory(const std::string& n, const std::string& doc) diff --git a/Source/cmExternalMakefileProjectGenerator.h b/Source/cmExternalMakefileProjectGenerator.h index a1734ee..7f332a8 100644 --- a/Source/cmExternalMakefileProjectGenerator.h +++ b/Source/cmExternalMakefileProjectGenerator.h @@ -55,6 +55,9 @@ public: void SetName(const std::string& n) { Name = n; } std::string GetName() const { return Name; } + virtual bool Open(const std::string& bindir, const std::string& projectName, + bool dryRun); + protected: ///! Contains the names of the global generators support by this generator. std::vector<std::string> SupportedGlobalGenerators; diff --git a/Source/cmExtraSublimeTextGenerator.cxx b/Source/cmExtraSublimeTextGenerator.cxx index 73a9c85..3d72ae3 100644 --- a/Source/cmExtraSublimeTextGenerator.cxx +++ b/Source/cmExtraSublimeTextGenerator.cxx @@ -400,3 +400,26 @@ std::string cmExtraSublimeTextGenerator::ComputeDefines( return definesString; } + +bool cmExtraSublimeTextGenerator::Open(const std::string& bindir, + const std::string& projectName, + bool dryRun) +{ + const char* sublExecutable = + this->GlobalGenerator->GetCMakeInstance()->GetCacheDefinition( + "CMAKE_SUBLIMETEXT_EXECUTABLE"); + if (!sublExecutable) { + return false; + } + if (cmSystemTools::IsNOTFOUND(sublExecutable)) { + return false; + } + + std::string filename = bindir + "/" + projectName + ".sublime-project"; + if (dryRun) { + return cmSystemTools::FileExists(filename, true); + } + + return cmSystemTools::RunSingleCommand( + { sublExecutable, "--project", filename }); +} diff --git a/Source/cmExtraSublimeTextGenerator.h b/Source/cmExtraSublimeTextGenerator.h index 7fb304e..57ba1cf 100644 --- a/Source/cmExtraSublimeTextGenerator.h +++ b/Source/cmExtraSublimeTextGenerator.h @@ -65,6 +65,9 @@ private: std::string ComputeDefines(cmSourceFile* source, cmLocalGenerator* lg, cmGeneratorTarget* gtgt); + bool Open(const std::string& bindir, const std::string& projectName, + bool dryRun) override; + bool ExcludeBuildFolder; std::string EnvSettings; }; diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 05efff3..38669c9 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -1824,6 +1824,16 @@ int cmGlobalGenerator::Build(const std::string& /*unused*/, return retVal; } +bool cmGlobalGenerator::Open(const std::string& bindir, + const std::string& projectName, bool dryRun) +{ + if (this->ExtraGenerator) { + return this->ExtraGenerator->Open(bindir, projectName, dryRun); + } + + return false; +} + std::string cmGlobalGenerator::GenerateCMakeBuildCommand( const std::string& target, const std::string& config, const std::string& native, bool ignoreErrors) diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index 18ca682..04e9dc1 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -162,6 +162,12 @@ public: std::vector<std::string> const& nativeOptions = std::vector<std::string>()); + /** + * Open a generated IDE project given the following information. + */ + virtual bool Open(const std::string& bindir, const std::string& projectName, + bool dryRun); + virtual void GenerateBuildCommand( std::vector<std::string>& makeCommand, const std::string& makeProgram, const std::string& projectName, const std::string& projectDir, diff --git a/Source/cmGlobalVisualStudioGenerator.cxx b/Source/cmGlobalVisualStudioGenerator.cxx index 0651536..99600df 100644 --- a/Source/cmGlobalVisualStudioGenerator.cxx +++ b/Source/cmGlobalVisualStudioGenerator.cxx @@ -4,7 +4,10 @@ #include "cmGlobalVisualStudioGenerator.h" #include "cmsys/Encoding.hxx" +#include <future> #include <iostream> +#include <objbase.h> +#include <shellapi.h> #include <windows.h> #include "cmAlgorithms.h" @@ -103,15 +106,6 @@ void cmGlobalVisualStudioGenerator::AddExtraIDETargets() // Configure CMake Visual Studio macros, for this user on this version // of Visual Studio. this->ConfigureCMakeVisualStudioMacros(); - - // Add CMakeLists.txt with custom command to rerun CMake. - for (std::vector<cmLocalGenerator*>::const_iterator lgi = - this->LocalGenerators.begin(); - lgi != this->LocalGenerators.end(); ++lgi) { - cmLocalVisualStudioGenerator* lg = - static_cast<cmLocalVisualStudioGenerator*>(*lgi); - lg->AddCMakeListsRules(); - } } void cmGlobalVisualStudioGenerator::ComputeTargetObjectDirectory( @@ -928,3 +922,33 @@ void cmGlobalVisualStudioGenerator::AddSymbolExportCommand( commandLines, "Auto build dll exports", "."); commands.push_back(command); } + +static bool OpenSolution(std::string sln) +{ + HRESULT comInitialized = + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + if (FAILED(comInitialized)) { + return false; + } + + HINSTANCE hi = + ShellExecuteA(NULL, "open", sln.c_str(), NULL, NULL, SW_SHOWNORMAL); + + CoUninitialize(); + + return reinterpret_cast<intptr_t>(hi) > 32; +} + +bool cmGlobalVisualStudioGenerator::Open(const std::string& bindir, + const std::string& projectName, + bool dryRun) +{ + std::string buildDir = cmSystemTools::ConvertToOutputPath(bindir.c_str()); + std::string sln = buildDir + "\\" + projectName + ".sln"; + + if (dryRun) { + return cmSystemTools::FileExists(sln, true); + } + + return std::async(std::launch::async, OpenSolution, sln).get(); +} diff --git a/Source/cmGlobalVisualStudioGenerator.h b/Source/cmGlobalVisualStudioGenerator.h index 62bfd3b..55a6813 100644 --- a/Source/cmGlobalVisualStudioGenerator.h +++ b/Source/cmGlobalVisualStudioGenerator.h @@ -131,6 +131,9 @@ public: std::vector<cmCustomCommand>& commands, std::string const& configName); + bool Open(const std::string& bindir, const std::string& projectName, + bool dryRun) override; + protected: virtual void AddExtraIDETargets(); diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index c79ee47..78943e4 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -35,6 +35,11 @@ struct cmLinkImplementation; +#if defined(CMAKE_BUILD_WITH_CMAKE) && defined(__APPLE__) +#define HAVE_APPLICATION_SERVICES +#include <ApplicationServices/ApplicationServices.h> +#endif + #if defined(CMAKE_BUILD_WITH_CMAKE) #include "cmXMLParser.h" @@ -287,6 +292,35 @@ void cmGlobalXCodeGenerator::EnableLanguage( this->ComputeArchitectures(mf); } +bool cmGlobalXCodeGenerator::Open(const std::string& bindir, + const std::string& projectName, bool dryRun) +{ + bool ret = false; + +#ifdef HAVE_APPLICATION_SERVICES + std::string url = bindir + "/" + projectName + ".xcodeproj"; + + if (dryRun) { + return cmSystemTools::FileExists(url, false); + } + + CFStringRef cfStr = CFStringCreateWithCString( + kCFAllocatorDefault, url.c_str(), kCFStringEncodingUTF8); + if (cfStr) { + CFURLRef cfUrl = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, cfStr, + kCFURLPOSIXPathStyle, true); + if (cfUrl) { + OSStatus err = LSOpenCFURLRef(cfUrl, nullptr); + ret = err == noErr; + CFRelease(cfUrl); + } + CFRelease(cfStr); + } +#endif + + return ret; +} + void cmGlobalXCodeGenerator::GenerateBuildCommand( std::vector<std::string>& makeCommand, const std::string& makeProgram, const std::string& projectName, const std::string& /*projectDir*/, @@ -404,12 +438,6 @@ void cmGlobalXCodeGenerator::AddExtraTargets( cmGeneratorTarget* allBuildGt = new cmGeneratorTarget(allbuild, root); root->AddGeneratorTarget(allBuildGt); - // Refer to the main build configuration file for easy editing. - std::string listfile = root->GetCurrentSourceDirectory(); - listfile += "/"; - listfile += "CMakeLists.txt"; - allBuildGt->AddSource(listfile); - // Add XCODE depend helper std::string dir = root->GetCurrentBinaryDirectory(); cmCustomCommandLine makeHelper; @@ -479,12 +507,6 @@ void cmGlobalXCodeGenerator::AddExtraTargets( !target->GetPropertyAsBool("EXCLUDE_FROM_ALL")) { allbuild->AddUtility(target->GetName()); } - - // Refer to the build configuration file for easy editing. - listfile = gen->GetCurrentSourceDirectory(); - listfile += "/"; - listfile += "CMakeLists.txt"; - target->AddSource(listfile); } } } @@ -962,6 +984,13 @@ bool cmGlobalXCodeGenerator::CreateXCodeTargets( if (!gtgt->GetConfigCommonSourceFiles(classes)) { return false; } + + // Add CMakeLists.txt file for user convenience. + std::string listfile = + gtgt->GetLocalGenerator()->GetCurrentSourceDirectory(); + listfile += "/CMakeLists.txt"; + classes.push_back(gtgt->Makefile->GetOrCreateSource(listfile)); + std::sort(classes.begin(), classes.end(), cmSourceFilePathCompare()); gtgt->ComputeObjectMapping(); @@ -2265,6 +2294,12 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateUtilityTarget( return nullptr; } + // Add CMakeLists.txt file for user convenience. + std::string listfile = + gtgt->GetLocalGenerator()->GetCurrentSourceDirectory(); + listfile += "/CMakeLists.txt"; + sources.push_back(gtgt->Makefile->GetOrCreateSource(listfile)); + for (std::vector<cmSourceFile*>::const_iterator i = sources.begin(); i != sources.end(); ++i) { if (!(*i)->GetPropertyAsBool("GENERATED")) { @@ -2706,6 +2741,20 @@ bool cmGlobalXCodeGenerator::CreateGroups( std::string key = GetGroupMapKeyFromPath(gtgt, source); this->GroupMap[key] = pbxgroup; } + + // Add CMakeLists.txt file for user convenience. + { + std::string listfile = + gtgt->GetLocalGenerator()->GetCurrentSourceDirectory(); + listfile += "/CMakeLists.txt"; + cmSourceFile* sf = gtgt->Makefile->GetOrCreateSource(listfile); + std::string const& source = sf->GetFullPath(); + cmSourceGroup* sourceGroup = + mf->FindSourceGroup(source.c_str(), sourceGroups); + cmXCodeObject* pbxgroup = this->CreateOrGetPBXGroup(gtgt, sourceGroup); + std::string key = GetGroupMapKeyFromPath(gtgt, source); + this->GroupMap[key] = pbxgroup; + } } } return true; diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h index e9ca91c..b758e97 100644 --- a/Source/cmGlobalXCodeGenerator.h +++ b/Source/cmGlobalXCodeGenerator.h @@ -55,6 +55,13 @@ public: */ void EnableLanguage(std::vector<std::string> const& languages, cmMakefile*, bool optional) override; + + /** + * Open a generated IDE project given the following information. + */ + bool Open(const std::string& bindir, const std::string& projectName, + bool dryRun) override; + /** * Try running cmake and building a file. This is used for dynalically * loaded commands, not as part of the usual build process. diff --git a/Source/cmLocalVisualStudio7Generator.cxx b/Source/cmLocalVisualStudio7Generator.cxx index d8030b7..beb80f2 100644 --- a/Source/cmLocalVisualStudio7Generator.cxx +++ b/Source/cmLocalVisualStudio7Generator.cxx @@ -36,6 +36,13 @@ private: cmLocalVisualStudio7Generator* LocalGenerator; }; +class cmLocalVisualStudio7Generator::AllConfigSources +{ +public: + std::vector<cmGeneratorTarget::AllConfigSource> Sources; + std::map<cmSourceFile const*, size_t> Index; +}; + extern cmVS7FlagTable cmLocalVisualStudio7GeneratorFlagTable[]; static void cmConvertToWindowsSlash(std::string& s) @@ -83,29 +90,6 @@ void cmLocalVisualStudio7Generator::Generate() this->WriteStampFiles(); } -void cmLocalVisualStudio7Generator::AddCMakeListsRules() -{ - // Create the regeneration custom rule. - if (!this->Makefile->IsOn("CMAKE_SUPPRESS_REGENERATION")) { - // Create a rule to regenerate the build system when the target - // specification source changes. - if (cmSourceFile* sf = this->CreateVCProjBuildRule()) { - // Add the rule to targets that need it. - const std::vector<cmGeneratorTarget*>& tgts = - this->GetGeneratorTargets(); - for (std::vector<cmGeneratorTarget*>::const_iterator l = tgts.begin(); - l != tgts.end(); ++l) { - if ((*l)->GetType() == cmStateEnums::GLOBAL_TARGET) { - continue; - } - if ((*l)->GetName() != CMAKE_CHECK_BUILD_SYSTEM_TARGET) { - (*l)->AddSource(sf->GetFullPath()); - } - } - } - } -} - void cmLocalVisualStudio7Generator::FixGlobalTargets() { // Visual Studio .NET 2003 Service Pack 1 will not run post-build @@ -239,19 +223,29 @@ void cmLocalVisualStudio7Generator::CreateSingleVCProj( cmSourceFile* cmLocalVisualStudio7Generator::CreateVCProjBuildRule() { + if (this->Makefile->IsOn("CMAKE_SUPPRESS_REGENERATION")) { + return nullptr; + } + + std::string makefileIn = this->GetCurrentSourceDirectory(); + makefileIn += "/"; + makefileIn += "CMakeLists.txt"; + makefileIn = cmSystemTools::CollapseFullPath(makefileIn); + if (cmSourceFile* file = this->Makefile->GetSource(makefileIn)) { + if (file->GetCustomCommand()) { + return file; + } + } + if (!cmSystemTools::FileExists(makefileIn)) { + return nullptr; + } + std::string stampName = this->GetCurrentBinaryDirectory(); stampName += "/"; stampName += cmake::GetCMakeFilesDirectoryPostSlash(); stampName += "generate.stamp"; cmCustomCommandLine commandLine; commandLine.push_back(cmSystemTools::GetCMakeCommand()); - std::string makefileIn = this->GetCurrentSourceDirectory(); - makefileIn += "/"; - makefileIn += "CMakeLists.txt"; - makefileIn = cmSystemTools::CollapseFullPath(makefileIn.c_str()); - if (!cmSystemTools::FileExists(makefileIn.c_str())) { - return 0; - } std::string comment = "Building Custom Rule "; comment += makefileIn; std::string args; @@ -275,10 +269,13 @@ cmSourceFile* cmLocalVisualStudio7Generator::CreateVCProjBuildRule() fullpathStampName.c_str(), listFiles, makefileIn.c_str(), commandLines, comment.c_str(), no_working_directory, true, false); if (cmSourceFile* file = this->Makefile->GetSource(makefileIn.c_str())) { + // Finalize the source file path now since we're adding this after + // the generator validated all project-named sources. + file->GetFullPath(); return file; } else { cmSystemTools::Error("Error adding rule for ", makefileIn.c_str()); - return 0; + return nullptr; } } @@ -1368,13 +1365,26 @@ void cmLocalVisualStudio7Generator::WriteVCProjFile(std::ostream& fout, // We may be modifying the source groups temporarily, so make a copy. std::vector<cmSourceGroup> sourceGroups = this->Makefile->GetSourceGroups(); - std::vector<cmGeneratorTarget::AllConfigSource> const& sources = - target->GetAllConfigSources(); - std::map<cmSourceFile const*, size_t> sourcesIndex; + AllConfigSources sources; + sources.Sources = target->GetAllConfigSources(); + + // Add CMakeLists.txt file with rule to re-run CMake for user convenience. + if (target->GetType() != cmStateEnums::GLOBAL_TARGET && + target->GetName() != CMAKE_CHECK_BUILD_SYSTEM_TARGET) { + if (cmSourceFile const* sf = this->CreateVCProjBuildRule()) { + cmGeneratorTarget::AllConfigSource acs; + acs.Source = sf; + acs.Kind = cmGeneratorTarget::SourceKindCustomCommand; + for (size_t ci = 0; ci < configs.size(); ++ci) { + acs.Configs.push_back(ci); + } + sources.Sources.emplace_back(std::move(acs)); + } + } - for (size_t si = 0; si < sources.size(); ++si) { - cmSourceFile const* sf = sources[si].Source; - sourcesIndex[sf] = si; + for (size_t si = 0; si < sources.Sources.size(); ++si) { + cmSourceFile const* sf = sources.Sources[si].Source; + sources.Index[sf] = si; if (!sf->GetObjectLibrary().empty()) { if (this->FortranProject) { // Intel Fortran does not support per-config source locations @@ -1400,7 +1410,7 @@ void cmLocalVisualStudio7Generator::WriteVCProjFile(std::ostream& fout, // Loop through every source group. for (unsigned int i = 0; i < sourceGroups.size(); ++i) { cmSourceGroup sg = sourceGroups[i]; - this->WriteGroup(&sg, target, fout, libName, configs, sourcesIndex); + this->WriteGroup(&sg, target, fout, libName, configs, sources); } fout << "\t</Files>\n"; @@ -1567,7 +1577,7 @@ std::string cmLocalVisualStudio7Generator::ComputeLongestObjectDirectory( bool cmLocalVisualStudio7Generator::WriteGroup( const cmSourceGroup* sg, cmGeneratorTarget* target, std::ostream& fout, const std::string& libName, std::vector<std::string> const& configs, - std::map<cmSourceFile const*, size_t> const& sourcesIndex) + AllConfigSources const& sources) { cmGlobalVisualStudio7Generator* gg = static_cast<cmGlobalVisualStudio7Generator*>(this->GlobalGenerator); @@ -1579,7 +1589,7 @@ bool cmLocalVisualStudio7Generator::WriteGroup( std::ostringstream tmpOut; for (unsigned int i = 0; i < children.size(); ++i) { if (this->WriteGroup(&children[i], target, tmpOut, libName, configs, - sourcesIndex)) { + sources)) { hasChildrenWithSources = true; } } @@ -1595,9 +1605,6 @@ bool cmLocalVisualStudio7Generator::WriteGroup( this->WriteVCProjBeginGroup(fout, name.c_str(), ""); } - std::vector<cmGeneratorTarget::AllConfigSource> const& sources = - target->GetAllConfigSources(); - // Loop through each source in the source group. for (std::vector<const cmSourceFile*>::const_iterator sf = sourceFiles.begin(); @@ -1608,10 +1615,11 @@ bool cmLocalVisualStudio7Generator::WriteGroup( target->GetType() == cmStateEnums::GLOBAL_TARGET) { // Look up the source kind and configs. std::map<cmSourceFile const*, size_t>::const_iterator map_it = - sourcesIndex.find(*sf); + sources.Index.find(*sf); // The map entry must exist because we populated it earlier. - assert(map_it != sourcesIndex.end()); - cmGeneratorTarget::AllConfigSource const& acs = sources[map_it->second]; + assert(map_it != sources.Index.end()); + cmGeneratorTarget::AllConfigSource const& acs = + sources.Sources[map_it->second]; FCInfo fcinfo(this, target, acs, configs); diff --git a/Source/cmLocalVisualStudio7Generator.h b/Source/cmLocalVisualStudio7Generator.h index 7a77574..48f2e1a 100644 --- a/Source/cmLocalVisualStudio7Generator.h +++ b/Source/cmLocalVisualStudio7Generator.h @@ -65,7 +65,6 @@ public: virtual void ReadAndStoreExternalGUID(const std::string& name, const char* path); - virtual void AddCMakeListsRules(); protected: void CreateSingleVCProj(const std::string& lname, cmGeneratorTarget* tgt); @@ -117,10 +116,11 @@ private: FCInfo& fcinfo); void WriteTargetVersionAttribute(std::ostream& fout, cmGeneratorTarget* gt); + class AllConfigSources; bool WriteGroup(const cmSourceGroup* sg, cmGeneratorTarget* target, std::ostream& fout, const std::string& libName, std::vector<std::string> const& configs, - std::map<cmSourceFile const*, size_t> const& sourcesIndex); + AllConfigSources const& sources); friend class cmLocalVisualStudio7GeneratorFCInfo; friend class cmLocalVisualStudio7GeneratorInternals; diff --git a/Source/cmLocalVisualStudioGenerator.h b/Source/cmLocalVisualStudioGenerator.h index cba24fe..ace2f89 100644 --- a/Source/cmLocalVisualStudioGenerator.h +++ b/Source/cmLocalVisualStudioGenerator.h @@ -44,8 +44,6 @@ public: virtual std::string ComputeLongestObjectDirectory( cmGeneratorTarget const*) const = 0; - virtual void AddCMakeListsRules() = 0; - virtual void ComputeObjectFilenames( std::map<cmSourceFile const*, std::string>& mapping, cmGeneratorTarget const* = 0); diff --git a/Source/cmVSSetupHelper.cxx b/Source/cmVSSetupHelper.cxx index 3c50403..ea13649 100644 --- a/Source/cmVSSetupHelper.cxx +++ b/Source/cmVSSetupHelper.cxx @@ -1,7 +1,10 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmVSSetupHelper.h" + #include "cmSystemTools.h" +#include "cmsys/Encoding.hxx" +#include "cmsys/FStream.hxx" #ifndef VSSetupConstants #define VSSetupConstants @@ -41,14 +44,19 @@ const CLSID CLSID_SetupConfiguration = { /* clang-format on */ #endif -const WCHAR* VCToolsetComponent = - L"Microsoft.VisualStudio.Component.VC.Tools.x86.x64"; const WCHAR* Win10SDKComponent = L"Microsoft.VisualStudio.Component.Windows10SDK"; const WCHAR* Win81SDKComponent = L"Microsoft.VisualStudio.Component.Windows81SDK"; const WCHAR* ComponentType = L"Component"; +std::string VSInstanceInfo::GetInstallLocation() const +{ + std::string loc = cmsys::Encoding::ToNarrow(this->VSInstallLocation); + cmSystemTools::ConvertToUnixSlashes(loc); + return loc; +} + cmVSSetupAPIHelper::cmVSSetupAPIHelper() : setupConfig(NULL) , setupConfig2(NULL) @@ -90,11 +98,11 @@ bool cmVSSetupAPIHelper::IsWin81SDKInstalled() } bool cmVSSetupAPIHelper::CheckInstalledComponent( - SmartCOMPtr<ISetupPackageReference> package, bool& bVCToolset, - bool& bWin10SDK, bool& bWin81SDK) + SmartCOMPtr<ISetupPackageReference> package, bool& bWin10SDK, + bool& bWin81SDK) { bool ret = false; - bVCToolset = bWin10SDK = bWin81SDK = false; + bWin10SDK = bWin81SDK = false; SmartBSTR bstrId; if (FAILED(package->GetId(&bstrId))) { return ret; @@ -107,11 +115,6 @@ bool cmVSSetupAPIHelper::CheckInstalledComponent( std::wstring id = std::wstring(bstrId); std::wstring type = std::wstring(bstrType); - if (id.compare(VCToolsetComponent) == 0 && - type.compare(ComponentType) == 0) { - bVCToolset = true; - ret = true; - } // Checks for any version of Win10 SDK. The version is appended at the end of // the @@ -135,7 +138,6 @@ bool cmVSSetupAPIHelper::CheckInstalledComponent( bool cmVSSetupAPIHelper::GetVSInstanceInfo( SmartCOMPtr<ISetupInstance2> pInstance, VSInstanceInfo& vsInstanceInfo) { - bool isVCToolSetInstalled = false; if (pInstance == NULL) return false; @@ -174,6 +176,23 @@ bool cmVSSetupAPIHelper::GetVSInstanceInfo( } } + // Check if a compiler is installed with this instance. + { + std::string const vcRoot = vsInstanceInfo.GetInstallLocation(); + std::string const vcToolsVersionFile = + vcRoot + "/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt"; + std::string vcToolsVersion; + cmsys::ifstream fin(vcToolsVersionFile.c_str()); + if (!fin || !cmSystemTools::GetLineFromStream(fin, vcToolsVersion)) { + return false; + } + vcToolsVersion = cmSystemTools::TrimWhitespace(vcToolsVersion); + std::string const vcToolsDir = vcRoot + "/VC/Tools/MSVC/" + vcToolsVersion; + if (!cmSystemTools::FileIsDirectory(vcToolsDir)) { + return false; + } + } + // Reboot may have been required before the product package was registered // (last). if ((eRegistered & state) == eRegistered) { @@ -199,12 +218,11 @@ bool cmVSSetupAPIHelper::GetVSInstanceInfo( package == NULL) continue; - bool vcToolsetInstalled = false, win10SDKInstalled = false, - win81SDkInstalled = false; - bool ret = CheckInstalledComponent(package, vcToolsetInstalled, - win10SDKInstalled, win81SDkInstalled); + bool win10SDKInstalled = false; + bool win81SDkInstalled = false; + bool ret = + CheckInstalledComponent(package, win10SDKInstalled, win81SDkInstalled); if (ret) { - isVCToolSetInstalled |= vcToolsetInstalled; vsInstanceInfo.IsWin10SDKInstalled |= win10SDKInstalled; vsInstanceInfo.IsWin81SDKInstalled |= win81SDkInstalled; } @@ -213,7 +231,7 @@ bool cmVSSetupAPIHelper::GetVSInstanceInfo( SafeArrayDestroy(lpsaPackages); } - return isVCToolSetInstalled; + return true; } bool cmVSSetupAPIHelper::GetVSInstanceInfo(std::string& vsInstallLocation) @@ -222,9 +240,7 @@ bool cmVSSetupAPIHelper::GetVSInstanceInfo(std::string& vsInstallLocation) bool isInstalled = this->EnumerateAndChooseVSInstance(); if (isInstalled) { - std::string str(chosenInstanceInfo.VSInstallLocation.begin(), - chosenInstanceInfo.VSInstallLocation.end()); - vsInstallLocation = str; + vsInstallLocation = chosenInstanceInfo.GetInstallLocation(); } return isInstalled; @@ -281,9 +297,7 @@ bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance() if (isInstalled) { if (!envVSCommonToolsDir.empty()) { - std::string currentVSLocation(instanceInfo.VSInstallLocation.begin(), - instanceInfo.VSInstallLocation.end()); - cmSystemTools::ConvertToUnixSlashes(currentVSLocation); + std::string currentVSLocation = instanceInfo.GetInstallLocation(); currentVSLocation += "/Common7/Tools"; if (cmSystemTools::ComparePath(currentVSLocation, envVSCommonToolsDir)) { diff --git a/Source/cmVSSetupHelper.h b/Source/cmVSSetupHelper.h index d2f514c..74a7ec0 100644 --- a/Source/cmVSSetupHelper.h +++ b/Source/cmVSSetupHelper.h @@ -116,6 +116,8 @@ struct VSInstanceInfo ullVersion = 0; IsWin10SDKInstalled = IsWin81SDKInstalled = false; } + + std::string GetInstallLocation() const; }; class cmVSSetupAPIHelper @@ -134,8 +136,7 @@ private: bool GetVSInstanceInfo(SmartCOMPtr<ISetupInstance2> instance2, VSInstanceInfo& vsInstanceInfo); bool CheckInstalledComponent(SmartCOMPtr<ISetupPackageReference> package, - bool& bVCToolset, bool& bWin10SDK, - bool& bWin81SDK); + bool& bWin10SDK, bool& bWin81SDK); int ChooseVSInstance(const std::vector<VSInstanceInfo>& vecVSInstances); bool EnumerateAndChooseVSInstance(); diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx index 0087957..1a1b1e2 100644 --- a/Source/cmVisualStudio10TargetGenerator.cxx +++ b/Source/cmVisualStudio10TargetGenerator.cxx @@ -1174,6 +1174,15 @@ void cmVisualStudio10TargetGenerator::WriteCustomCommands() si != customCommands.end(); ++si) { this->WriteCustomCommand(*si); } + + // Add CMakeLists.txt file with rule to re-run CMake for user convenience. + if (this->GeneratorTarget->GetType() != cmStateEnums::GLOBAL_TARGET && + this->GeneratorTarget->GetName() != CMAKE_CHECK_BUILD_SYSTEM_TARGET) { + if (cmSourceFile const* sf = + this->LocalGenerator->CreateVCProjBuildRule()) { + this->WriteCustomCommand(sf); + } + } } void cmVisualStudio10TargetGenerator::WriteCustomCommand( @@ -2648,6 +2657,13 @@ bool cmVisualStudio10TargetGenerator::ComputeCudaOptions( cudaOptions.AddFlag("CompileOut", "$(IntDir)%(Filename).ptx"); } + // CUDA automatically passes the proper '--machine' flag to nvcc + // for the current architecture, but does not reflect this default + // in the user-visible IDE settings. Set it explicitly. + if (this->Platform == "x64") { + cudaOptions.AddFlag("TargetMachinePlatform", "64"); + } + // Convert the host compiler options to the toolset's abstractions // using a secondary flag table. cudaOptions.ClearTables(); diff --git a/Source/cmake.cxx b/Source/cmake.cxx index fd7151f..d7ed772 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -2425,6 +2425,49 @@ int cmake::Build(const std::string& dir, const std::string& target, nativeOptions); } +bool cmake::Open(const std::string& dir, bool dryRun) +{ + this->SetHomeDirectory(""); + this->SetHomeOutputDirectory(""); + if (!cmSystemTools::FileIsDirectory(dir)) { + std::cerr << "Error: " << dir << " is not a directory\n"; + return false; + } + + std::string cachePath = FindCacheFile(dir); + if (!this->LoadCache(cachePath)) { + std::cerr << "Error: could not load cache\n"; + return false; + } + const char* genName = this->State->GetCacheEntryValue("CMAKE_GENERATOR"); + if (!genName) { + std::cerr << "Error: could not find CMAKE_GENERATOR in Cache\n"; + return false; + } + const char* extraGenName = + this->State->GetInitializedCacheValue("CMAKE_EXTRA_GENERATOR"); + std::string fullName = + cmExternalMakefileProjectGenerator::CreateFullGeneratorName( + genName, extraGenName ? extraGenName : ""); + + std::unique_ptr<cmGlobalGenerator> gen( + this->CreateGlobalGenerator(fullName)); + if (!gen.get()) { + std::cerr << "Error: could create CMAKE_GENERATOR \"" << fullName + << "\"\n"; + return false; + } + + const char* cachedProjectName = + this->State->GetCacheEntryValue("CMAKE_PROJECT_NAME"); + if (!cachedProjectName) { + std::cerr << "Error: could not find CMAKE_PROJECT_NAME in Cache\n"; + return false; + } + + return gen->Open(dir, cachedProjectName, dryRun); +} + void cmake::WatchUnusedCli(const std::string& var) { #ifdef CMAKE_BUILD_WITH_CMAKE diff --git a/Source/cmake.h b/Source/cmake.h index b31b6f5..ed3ebe0 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -401,6 +401,9 @@ public: const std::string& config, const std::vector<std::string>& nativeOptions, bool clean); + ///! run the --open option + bool Open(const std::string& dir, bool dryRun); + void UnwatchUnusedCli(const std::string& var); void WatchUnusedCli(const std::string& var); diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx index a1dfc3e..59b908a 100644 --- a/Source/cmakemain.cxx +++ b/Source/cmakemain.cxx @@ -66,6 +66,7 @@ static const char* cmDocumentationOptions[][2] = { { "-E", "CMake command mode." }, { "-L[A][H]", "List non-advanced cached variables." }, { "--build <dir>", "Build a CMake-generated project binary tree." }, + { "--open <dir>", "Open generated project in the associated application." }, { "-N", "View mode only." }, { "-P <file>", "Process script mode." }, { "--find-package", "Run in pkg-config like mode." }, @@ -100,6 +101,7 @@ static int do_command(int ac, char const* const* av) int do_cmake(int ac, char const* const* av); static int do_build(int ac, char const* const* av); +static int do_open(int ac, char const* const* av); static cmMakefile* cmakemainGetMakefile(void* clientdata) { @@ -186,6 +188,9 @@ int main(int ac, char const* const* av) if (strcmp(av[1], "--build") == 0) { return do_build(ac, av); } + if (strcmp(av[1], "--open") == 0) { + return do_open(ac, av); + } if (strcmp(av[1], "-E") == 0) { return do_command(ac, av); } @@ -423,3 +428,41 @@ static int do_build(int ac, char const* const* av) return cm.Build(dir, target, config, nativeOptions, clean); #endif } + +static int do_open(int ac, char const* const* av) +{ +#ifndef CMAKE_BUILD_WITH_CMAKE + std::cerr << "This cmake does not support --open\n"; + return -1; +#else + std::string dir; + + enum Doing + { + DoingNone, + DoingDir, + }; + Doing doing = DoingDir; + for (int i = 2; i < ac; ++i) { + switch (doing) { + case DoingDir: + dir = cmSystemTools::CollapseFullPath(av[i]); + doing = DoingNone; + break; + default: + std::cerr << "Unknown argument " << av[i] << std::endl; + dir.clear(); + break; + } + } + if (dir.empty()) { + std::cerr << "Usage: cmake --open <dir>\n"; + return 1; + } + + cmake cm(cmake::RoleInternal); + cmSystemTools::SetMessageCallback(cmakemainMessageCallback, &cm); + cm.SetProgressCallback(cmakemainProgressCallback, &cm); + return cm.Open(dir, false) ? 0 : 1; +#endif +} diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 57cc144..7eafa06 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -327,6 +327,7 @@ add_RunCMake_test(install) add_RunCMake_test(CPackConfig) add_RunCMake_test(CPackInstallProperties) add_RunCMake_test(ExternalProject) +add_RunCMake_test(FetchContent) add_RunCMake_test(CTestCommandLine) # Only run this test on unix platforms that support # symbolic links diff --git a/Tests/RunCMake/FetchContent/CMakeLists.txt b/Tests/RunCMake/FetchContent/CMakeLists.txt new file mode 100644 index 0000000..d3137f6 --- /dev/null +++ b/Tests/RunCMake/FetchContent/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.9) +project(${RunCMake_TEST} NONE) +include(${RunCMake_TEST}.cmake) diff --git a/Tests/RunCMake/FetchContent/DirOverrides.cmake b/Tests/RunCMake/FetchContent/DirOverrides.cmake new file mode 100644 index 0000000..50eef16 --- /dev/null +++ b/Tests/RunCMake/FetchContent/DirOverrides.cmake @@ -0,0 +1,46 @@ +include(FetchContent) + +# Test using saved details +FetchContent_Declare( + t1 + SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/savedSrc + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E make_directory <SOURCE_DIR> +) +FetchContent_Populate(t1) +if(NOT IS_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/savedSrc) + message(FATAL_ERROR "Saved details SOURCE_DIR override failed") +endif() + +# Test direct population +FetchContent_Populate( + t2 + SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/directSrc + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E make_directory <SOURCE_DIR> +) +if(NOT IS_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/directSrc) + message(FATAL_ERROR "Direct details SOURCE_DIR override failed") +endif() + +# Ensure setting BINARY_DIR to SOURCE_DIR works (a technique to +# prevent an unwanted separate BINARY_DIR from being created, which +# ExternalProject_Add() does whether we like it or not) +FetchContent_Declare( + t3 + SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/savedNoBuildDir + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/savedNoBuildDir + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E make_directory <SOURCE_DIR> +) +FetchContent_Populate(t3) +if(IS_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/savedNobuildDir-build) + message(FATAL_ERROR "Saved details BINARY_DIR override failed") +endif() + +FetchContent_Populate( + t4 + SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/directNoBuildDir + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/directNoBuildDir + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E make_directory <SOURCE_DIR> +) +if(IS_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/savedNobuildDir-build) + message(FATAL_ERROR "Direct details BINARY_DIR override failed") +endif() diff --git a/Tests/RunCMake/FetchContent/DirectIgnoresDetails-stdout.txt b/Tests/RunCMake/FetchContent/DirectIgnoresDetails-stdout.txt new file mode 100644 index 0000000..6fa5a57 --- /dev/null +++ b/Tests/RunCMake/FetchContent/DirectIgnoresDetails-stdout.txt @@ -0,0 +1 @@ +Local details used diff --git a/Tests/RunCMake/FetchContent/DirectIgnoresDetails.cmake b/Tests/RunCMake/FetchContent/DirectIgnoresDetails.cmake new file mode 100644 index 0000000..0731b43 --- /dev/null +++ b/Tests/RunCMake/FetchContent/DirectIgnoresDetails.cmake @@ -0,0 +1,12 @@ +include(FetchContent) + +FetchContent_Declare( + t1 + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E echo "Saved details used" +) + +# No QUIET option given, so command output will be shown +FetchContent_Populate( + t1 + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E echo "Local details used" +) diff --git a/Tests/RunCMake/FetchContent/DownloadTwice-result.txt b/Tests/RunCMake/FetchContent/DownloadTwice-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/FetchContent/DownloadTwice-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/FetchContent/DownloadTwice-stderr.txt b/Tests/RunCMake/FetchContent/DownloadTwice-stderr.txt new file mode 100644 index 0000000..96fed48 --- /dev/null +++ b/Tests/RunCMake/FetchContent/DownloadTwice-stderr.txt @@ -0,0 +1 @@ +Content t1 already populated in diff --git a/Tests/RunCMake/FetchContent/DownloadTwice.cmake b/Tests/RunCMake/FetchContent/DownloadTwice.cmake new file mode 100644 index 0000000..6863c30 --- /dev/null +++ b/Tests/RunCMake/FetchContent/DownloadTwice.cmake @@ -0,0 +1,9 @@ +include(FetchContent) + +FetchContent_Declare( + t1 + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E echo "Download command executed" +) + +FetchContent_Populate(t1) +FetchContent_Populate(t1) # Triggers error diff --git a/Tests/RunCMake/FetchContent/FirstDetailsWin-stdout.txt b/Tests/RunCMake/FetchContent/FirstDetailsWin-stdout.txt new file mode 100644 index 0000000..7a8bf18 --- /dev/null +++ b/Tests/RunCMake/FetchContent/FirstDetailsWin-stdout.txt @@ -0,0 +1 @@ +First details used diff --git a/Tests/RunCMake/FetchContent/FirstDetailsWin.cmake b/Tests/RunCMake/FetchContent/FirstDetailsWin.cmake new file mode 100644 index 0000000..208b12d --- /dev/null +++ b/Tests/RunCMake/FetchContent/FirstDetailsWin.cmake @@ -0,0 +1,16 @@ +include(FetchContent) + +# Need to see the download command output +set(FETCHCONTENT_QUIET OFF) + +FetchContent_Declare( + t1 + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E echo "First details used" +) + +FetchContent_Declare( + t1 + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E echo "Second details used" +) + +FetchContent_Populate(t1) diff --git a/Tests/RunCMake/FetchContent/GetProperties.cmake b/Tests/RunCMake/FetchContent/GetProperties.cmake new file mode 100644 index 0000000..61c99fe --- /dev/null +++ b/Tests/RunCMake/FetchContent/GetProperties.cmake @@ -0,0 +1,67 @@ +include(FetchContent) + +# First confirm properties are empty even before declare +FetchContent_GetProperties(t1) +if(t1_POPULATED) + message(FATAL_ERROR "Property says populated before doing anything") +endif() +if(t1_SOURCE_DIR) + message(FATAL_ERROR "SOURCE_DIR property not initially empty") +endif() +if(t1_BINARY_DIR) + message(FATAL_ERROR "BINARY_DIR property not initially empty") +endif() + +# Declare, but no properties should change yet +FetchContent_Declare( + t1 + SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/savedSrc + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/savedBin + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E echo "Do nothing" +) + +FetchContent_GetProperties(t1) +if(t1_POPULATED) + message(FATAL_ERROR "Property says populated after only declaring details") +endif() +if(t1_SOURCE_DIR) + message(FATAL_ERROR "SOURCE_DIR property not empty after declare") +endif() +if(t1_BINARY_DIR) + message(FATAL_ERROR "BINARY_DIR property not empty after declare") +endif() + +# Populate should make all properties non-empty/set +FetchContent_Populate(t1) + +FetchContent_GetProperties(t1) +if(NOT t1_POPULATED) + message(FATAL_ERROR "Population did not set POPULATED property") +endif() +if(NOT "${t1_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/savedSrc") + message(FATAL_ERROR "SOURCE_DIR property not correct after population: " + "${t1_SOURCE_DIR}\n" + " Expected: ${CMAKE_CURRENT_BINARY_DIR}/savedSrc") +endif() +if(NOT "${t1_BINARY_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/savedBin") + message(FATAL_ERROR "BINARY_DIR property not correct after population: " + "${t1_BINARY_DIR}\n" + " Expected: ${CMAKE_CURRENT_BINARY_DIR}/savedBin") +endif() + +# Verify we can retrieve properties individually too +FetchContent_GetProperties(t1 POPULATED varPop) +FetchContent_GetProperties(t1 SOURCE_DIR varSrc) +FetchContent_GetProperties(t1 BINARY_DIR varBin) + +if(NOT varPop) + message(FATAL_ERROR "Failed to retrieve POPULATED property") +endif() +if(NOT "${varSrc}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/savedSrc") + message(FATAL_ERROR "SOURCE_DIR property not retrieved correctly: ${varSrc}\n" + " Expected: ${CMAKE_CURRENT_BINARY_DIR}/savedSrc") +endif() +if(NOT "${varBin}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/savedBin") + message(FATAL_ERROR "BINARY_DIR property not retrieved correctly: ${varBin}\n" + " Expected: ${CMAKE_CURRENT_BINARY_DIR}/savedBin") +endif() diff --git a/Tests/RunCMake/FetchContent/MissingDetails-result.txt b/Tests/RunCMake/FetchContent/MissingDetails-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/FetchContent/MissingDetails-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/FetchContent/MissingDetails-stderr.txt b/Tests/RunCMake/FetchContent/MissingDetails-stderr.txt new file mode 100644 index 0000000..c4f1daf --- /dev/null +++ b/Tests/RunCMake/FetchContent/MissingDetails-stderr.txt @@ -0,0 +1 @@ +No content details recorded for t1 diff --git a/Tests/RunCMake/FetchContent/MissingDetails.cmake b/Tests/RunCMake/FetchContent/MissingDetails.cmake new file mode 100644 index 0000000..ba8d121 --- /dev/null +++ b/Tests/RunCMake/FetchContent/MissingDetails.cmake @@ -0,0 +1,3 @@ +include(FetchContent) + +FetchContent_Populate(t1) diff --git a/Tests/RunCMake/FetchContent/RunCMakeTest.cmake b/Tests/RunCMake/FetchContent/RunCMakeTest.cmake new file mode 100644 index 0000000..621fb8b --- /dev/null +++ b/Tests/RunCMake/FetchContent/RunCMakeTest.cmake @@ -0,0 +1,28 @@ +include(RunCMake) + +unset(RunCMake_TEST_NO_CLEAN) + +run_cmake(MissingDetails) +run_cmake(DirectIgnoresDetails) +run_cmake(FirstDetailsWin) +run_cmake(DownloadTwice) +run_cmake(SameGenerator) +run_cmake(VarDefinitions) +run_cmake(GetProperties) +run_cmake(DirOverrides) + +# We need to pass through CMAKE_GENERATOR and CMAKE_MAKE_PROGRAM +# to ensure the test can run on machines where the build tool +# isn't on the PATH. Some build slaves explicitly test with such +# an arrangement (e.g. to test with spaces in the path). We also +# pass through the platform and toolset for completeness, even +# though we don't build anything, just in case this somehow affects +# the way the build tool is invoked. +run_cmake_command(ScriptMode + ${CMAKE_COMMAND} + -DCMAKE_GENERATOR=${RunCMake_GENERATOR} + -DCMAKE_GENERATOR_PLATFORM=${RunCMake_GENERATOR_PLATFORM} + -DCMAKE_GENERATOR_TOOLSET=${RunCMake_GENERATOR_TOOLSET} + -DCMAKE_MAKE_PROGRAM=${RunCMake_MAKE_PROGRAM} + -P ${CMAKE_CURRENT_LIST_DIR}/ScriptMode.cmake +) diff --git a/Tests/RunCMake/FetchContent/SameGenerator.cmake b/Tests/RunCMake/FetchContent/SameGenerator.cmake new file mode 100644 index 0000000..58204ef --- /dev/null +++ b/Tests/RunCMake/FetchContent/SameGenerator.cmake @@ -0,0 +1,17 @@ +include(FetchContent) + +FetchContent_Declare( + t1 + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E echo "Download command executed" +) + +FetchContent_Populate(t1) + +file(STRINGS "${FETCHCONTENT_BASE_DIR}/t1-subbuild/CMakeCache.txt" + matchLine REGEX "^CMAKE_GENERATOR:.*=" + LIMIT_COUNT 1 +) +if(NOT matchLine MATCHES "${CMAKE_GENERATOR}") + message(FATAL_ERROR "Generator line mismatch: ${matchLine}\n" + " Expected type: ${CMAKE_GENERATOR}") +endif() diff --git a/Tests/RunCMake/FetchContent/ScriptMode.cmake b/Tests/RunCMake/FetchContent/ScriptMode.cmake new file mode 100644 index 0000000..0a93d62 --- /dev/null +++ b/Tests/RunCMake/FetchContent/ScriptMode.cmake @@ -0,0 +1,35 @@ +include(FetchContent) + +file(WRITE tmpFile.txt "Generated contents, not important") + +FetchContent_Populate( + t1 + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_BINARY_DIR}/tmpFile.txt + <SOURCE_DIR>/done1.txt +) +if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/t1-src/done1.txt) + message(FATAL_ERROR "Default SOURCE_DIR doesn't contain done1.txt") +endif() + +FetchContent_Populate( + t2 + SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/mysrc + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_BINARY_DIR}/tmpFile.txt + <SOURCE_DIR>/done2.txt +) +if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/mysrc/done2.txt) + message(FATAL_ERROR "Specified SOURCE_DIR doesn't contain done2.txt") +endif() + +FetchContent_Populate( + t3 + SOURCE_DIR myrelsrc + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_BINARY_DIR}/tmpFile.txt + <SOURCE_DIR>/done3.txt +) +if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/myrelsrc/done3.txt) + message(FATAL_ERROR "Relative SOURCE_DIR doesn't contain done3.txt") +endif() diff --git a/Tests/RunCMake/FetchContent/VarDefinitions.cmake b/Tests/RunCMake/FetchContent/VarDefinitions.cmake new file mode 100644 index 0000000..4d2a929 --- /dev/null +++ b/Tests/RunCMake/FetchContent/VarDefinitions.cmake @@ -0,0 +1,75 @@ +unset(FETCHCONTENT_FULLY_DISCONNECTED CACHE) +unset(FETCHCONTENT_UPDATES_DISCONNECTED CACHE) +unset(FETCHCONTENT_QUIET CACHE) +unset(FETCHCONTENT_BASE_DIR CACHE) + +include(FetchContent) + +# Each of the cache entries should be defined and have the +# expected value. Be careful to check unset separately from a +# false value, since unset also equates to false. +if(FETCHCONTENT_FULLY_DISCONNECTED STREQUAL "") + message(FATAL_ERROR "FETCHCONTENT_FULLY_DISCONNECTED not defined") +elseif(FETCHCONTENT_FULLY_DISCONNECTED) + message(FATAL_ERROR "FETCHCONTENT_FULLY_DISCONNECTED not defaulted to OFF") +endif() + +if(FETCHCONTENT_UPDATES_DISCONNECTED STREQUAL "") + message(FATAL_ERROR "FETCHCONTENT_UPDATES_DISCONNECTED not defined") +elseif(FETCHCONTENT_UPDATES_DISCONNECTED) + message(FATAL_ERROR "FETCHCONTENT_UPDATES_DISCONNECTED not defaulted to OFF") +endif() + +if(FETCHCONTENT_QUIET STREQUAL "") + message(FATAL_ERROR "FETCHCONTENT_QUIET not defined") +elseif(NOT FETCHCONTENT_QUIET) + message(FATAL_ERROR "FETCHCONTENT_QUIET not defaulted to ON") +endif() + +if(NOT FETCHCONTENT_BASE_DIR STREQUAL "${CMAKE_BINARY_DIR}/_deps") + message(FATAL_ERROR "FETCHCONTENT_BASE_DIR has default value: " + "${FETCHCONTENT_BASE_DIR}\n Expected: ${CMAKE_BINARY_DIR}/_deps") +endif() + +file(REMOVE_RECURSE ${FETCHCONTENT_BASE_DIR}/t1-subbuild) + +# Use uppercase T1 test name to confirm conversion to lowercase +# for the t1_... variable names that get set +FetchContent_Declare( + T1 + DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E echo "Download command executed" +) +FetchContent_Populate(T1) + +# Be careful to check both regular and cache variables. Since they have +# the same name, we can only confirm them separately by using get_property(). +get_property(srcRegVarSet VARIABLE PROPERTY t1_SOURCE_DIR SET) +get_property(bldRegVarSet VARIABLE PROPERTY t1_BINARY_DIR SET) + +get_property(srcCacheVarSet CACHE t1_SOURCE_DIR PROPERTY VALUE SET) +get_property(bldCacheVarSet CACHE t1_BINARY_DIR PROPERTY VALUE SET) + +if(NOT srcRegVarSet) + message(FATAL_ERROR "t1_SOURCE_DIR regular variable not set") +endif() +if(NOT bldRegVarSet) + message(FATAL_ERROR "t1_BINARY_DIR regular variable not set") +endif() +if(srcCacheVarSet) + message(FATAL_ERROR "t1_SOURCE_DIR cache variable unexpectedly set") +endif() +if(bldCacheVarSet) + message(FATAL_ERROR "t1_BINARY_DIR cache variable unexpectedly set") +endif() + +set(srcRegVar ${t1_SOURCE_DIR}) +set(bldRegVar ${t1_BINARY_DIR}) + +if(NOT srcRegVar STREQUAL "${CMAKE_BINARY_DIR}/_deps/t1-src") + message(FATAL_ERROR "Unexpected t1_SOURCE_DIR value: ${srcRegVar}\n" + " Expected: ${CMAKE_BINARY_DIR}/_deps/t1-src") +endif() +if(NOT bldRegVar STREQUAL "${CMAKE_BINARY_DIR}/_deps/t1-build") + message(FATAL_ERROR "Unexpected t1_BINARY_DIR value: ${bldRegVar}\n" + " Expected: ${CMAKE_BINARY_DIR}/_deps/t1-build") +endif() diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_PROPERTY-SOURCES-check.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_PROPERTY-SOURCES-check.cmake index f1452b5..c1a0f5b 100644 --- a/Tests/RunCMake/GeneratorExpression/TARGET_PROPERTY-SOURCES-check.cmake +++ b/Tests/RunCMake/GeneratorExpression/TARGET_PROPERTY-SOURCES-check.cmake @@ -1,8 +1,5 @@ file(READ ${RunCMake_TEST_BINARY_DIR}/foo.txt foo_sources) -# VS generators inject CMakeLists.txt as a source. Remove it. -string(REGEX REPLACE ";[^;]*CMakeLists.txt$" "" foo_sources "${foo_sources}") - set(foo_expected "empty.c;empty2.c;empty3.c") if(NOT foo_sources STREQUAL foo_expected) set(RunCMake_TEST_FAILED "foo SOURCES was:\n [[${foo_sources}]]\nbut expected:\n [[${foo_expected}]]") diff --git a/Tests/RunCMake/TargetSources/ConfigNotAllowed-stderr.txt b/Tests/RunCMake/TargetSources/ConfigNotAllowed-stderr.txt index 1de5dd7..c6b75fc 100644 --- a/Tests/RunCMake/TargetSources/ConfigNotAllowed-stderr.txt +++ b/Tests/RunCMake/TargetSources/ConfigNotAllowed-stderr.txt @@ -6,9 +6,7 @@ CMake Error in CMakeLists.txt: .*/Tests/RunCMake/TargetSources/empty_1.cpp .*/Tests/RunCMake/TargetSources/empty_2.cpp - .*/Tests/RunCMake/TargetSources/CMakeLists.txt Config "Release": .*/Tests/RunCMake/TargetSources/empty_1.cpp - .*/Tests/RunCMake/TargetSources/CMakeLists.txt diff --git a/Tests/RunCMake/TargetSources/OriginDebugIDE-result.txt b/Tests/RunCMake/TargetSources/OriginDebugIDE-result.txt deleted file mode 100644 index 573541a..0000000 --- a/Tests/RunCMake/TargetSources/OriginDebugIDE-result.txt +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/Tests/RunCMake/TargetSources/OriginDebugIDE-stderr.txt b/Tests/RunCMake/TargetSources/OriginDebugIDE-stderr.txt deleted file mode 100644 index 6fdcce7..0000000 --- a/Tests/RunCMake/TargetSources/OriginDebugIDE-stderr.txt +++ /dev/null @@ -1,40 +0,0 @@ -CMake Debug Log at OriginDebug.cmake:13 \(add_library\): - Used sources for target OriginDebug: - - \* .*Tests/RunCMake/TargetSources/empty_2.cpp - -Call Stack \(most recent call first\): - OriginDebugIDE.cmake:4 \(include\) - CMakeLists.txt:3 \(include\) -+ -CMake Debug Log at OriginDebug.cmake:16 \(set_property\): - Used sources for target OriginDebug: - - \* .*Tests/RunCMake/TargetSources/empty_3.cpp - -Call Stack \(most recent call first\): - OriginDebugIDE.cmake:4 \(include\) - CMakeLists.txt:3 \(include\) -+ -CMake Debug Log at OriginDebug.cmake:20 \(target_sources\): - Used sources for target OriginDebug: - - \* .*Tests/RunCMake/TargetSources/empty_4.cpp - -Call Stack \(most recent call first\): - OriginDebugIDE.cmake:4 \(include\) - CMakeLists.txt:3 \(include\) -+ -CMake Debug Log in CMakeLists.txt: - Used sources for target OriginDebug: - - * .*CMakeLists.txt -+ -CMake Debug Log at OriginDebug.cmake:14 \(target_link_libraries\): - Used sources for target OriginDebug: - - \* .*Tests/RunCMake/TargetSources/empty_1.cpp - -Call Stack \(most recent call first\): - OriginDebugIDE.cmake:4 \(include\) - CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/TargetSources/OriginDebugIDE.cmake b/Tests/RunCMake/TargetSources/OriginDebugIDE.cmake deleted file mode 100644 index a3cc3a8..0000000 --- a/Tests/RunCMake/TargetSources/OriginDebugIDE.cmake +++ /dev/null @@ -1,4 +0,0 @@ - -# Separate test for the IDEs, because they show the CMakeLists.txt file -# as a source file. -include(${CMAKE_CURRENT_LIST_DIR}/OriginDebug.cmake) diff --git a/Tests/RunCMake/TargetSources/RunCMakeTest.cmake b/Tests/RunCMake/TargetSources/RunCMakeTest.cmake index bb55a6e..36d01de 100644 --- a/Tests/RunCMake/TargetSources/RunCMakeTest.cmake +++ b/Tests/RunCMake/TargetSources/RunCMakeTest.cmake @@ -2,11 +2,9 @@ include(RunCMake) if(RunCMake_GENERATOR MATCHES "Visual Studio|Xcode") run_cmake(ConfigNotAllowed) - run_cmake(OriginDebugIDE) -else() - run_cmake(OriginDebug) endif() +run_cmake(OriginDebug) run_cmake(CMP0026-LOCATION) run_cmake(RelativePathInInterface) run_cmake(ExportBuild) |