[Bug] macOS framework issues
Brief Issue Summary
This is related to: https://github.com/microsoft/vscode-cpptools/issues/13993 I assumed this was an issue in cpptools, but it might be an issue with CMake Tools.
In a simple example that explicitly specifies a “framework search path” using a -F (or -iframework) arg to target_compile_options, everything seems to work fine. i.e.
cmake_minimum_required(VERSION 3.16)
project(HelloFrameworkApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
add_executable(hello main.cpp)
target_compile_options(hello PRIVATE "-F/path/to/MyFrameworksRoot")
In that case, I can see the -F (or -iframework) simply being passed through to cpptools as a compiler arg and handled correctly. Note that these are "framework search paths" (a root containing other directories with .framework extensions), and not individual frameworks (just the name of the framework, such as passed to the -framework linker arg) or framework header paths (i.e. /path/to/MyFramework.framework/Headers). The later two are not needed by cpptools, as include paths or otherwise. For macOS frameworks, we need only the "framework search path" variety (-F, -iframework or iframeworkwithsysroot, not -framework).
However, with a simple Qt example:
cmake_minimum_required(VERSION 3.16)
project(HelloQt LANGUAGES CXX)
set(Qt6_DIR "/Users/colen/Qt/6.9.3/macos/lib/cmake/Qt6")
find_package(Qt6 REQUIRED COMPONENTS Widgets)
add_executable(HelloQt main.cpp)
target_link_libraries(HelloQt PRIVATE Qt6::Widgets)
set_target_properties(HelloQt PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_GUI_IDENTIFIER com.example.helloqt
MACOSX_BUNDLE_BUNDLE_NAME "HelloQt"
)
... we're not getting that arg passed through for some reason, despite it being present (-iframework) on the build command line. In this case, I see the path appear in the set of include paths. I'm guessing that is due to the following code:
const frameworkPaths = Array.from(new Set<string>((fileGroup.frameworks ?? []).map(f => path.dirname(f.path))));
const includePath = (fileGroup.includePath ? fileGroup.includePath.map(p => p.path) : target.includePath || []).concat(frameworkPaths);
Is there a reason the behavior differs between these scenarios? Passing through as argument would be preferred as it's less ambiguous.
If this is a work-around due to frameworks potentially being handled by CMake in multiple ways, it might be preferrable to compose new -F or -iframework (if isSystem is true) args out of them, than to make the contents of the include paths array ambiguous. (Only clang and gcc support macOS frameworks, so the args should be reliable)
CMake Tools Diagnostics
n/a
Debug Log
n/a
Additional Information
No response
I open two issues one for cpptools https://github.com/microsoft/vscode-cpptools/issues/13993 one for cmake-tools https://github.com/microsoft/vscode-cmake-tools/issues/4597
I also feedback to cmake https://gitlab.kitware.com/cmake/cmake/-/issues/19897
because this bug need fix together by cmake 、 cmake-tools 、cpptools
I suggest that cmake fileapi does not separate the framework directory from includes into frameworks, so that includes contains all directories, including those included by -I, -F, -iframework, -isystem, etc., because this can retain the priority information, so that when cmake-tools delivers these includes to cpptools, cpptools can insert other directory settings in the compilation command into the correct priority position. If frameworks and includes are processed separately, even if cmake-tools sets the correct parameters to cpptools.cpptools may not know that there is a framework directory with a high priority -F setting in frameworks. Putting the directories together can not only retain the priority information, but cpptools can also determine whether the directory suffix is ".framework" to confirm whether it is a framework directory. Cmake itself also determines whether the directory is a framework in this way, so that cpptools can find the framework header file in a standard way (#include <frameworkname/headername.h>), rather than adding a directory like this (/path/to/MyFramework.framework/Headers) to includes.
Thus, CMake's separation is incorrect; it should be combined, and the directories should be tracked, including system and framework directories (identifiable by the suffix), iframework directories, and regular directories. Therefore, the handling described in https://gitlab.kitware.com/cmake/cmake/-/issues/19897 is incorrect, and CMake-Tools's direct addition of frameworks to includes is even more incorrect. CppTools also needs improvement, as it's unaware of the high-priority framework directory and the system directory within includes, and thus doesn't know how to insert the directories specified later in the command into the correct locations.
Technically, to most accurately reflect the order/priorities of -I, -sysroot, -F, -iframework, and several other arguments for which order is significant, it would actually be better if CMake Tools were to provide the entire command line used to compile the file, directly to cpptools (not using any other fields at all, in favor of just a single compilerFragment entry). It's unclear to me whether that would be feasible, given the interfaces involved between CMake and CMake Tools.
As a work-around, using CMake to generate a compile_commands.json, and configuring cpptools to use that, would result in the best possible interpretation (by cpptools) of those arguments to configure IntelliSense. Perhaps, if using Ninja generator, CMake Tools could itself ensure CMAKE_EXPORT_COMPILE_COMMANDS is set and simply dole out command lines from the generated compile_commands.json file.
Technically, to most accurately reflect the order/priorities of -I, -sysroot, -F, -iframework, and several other arguments for which order is significant, it would actually be better if CMake Tools were to provide the entire command line used to compile the file, directly to cpptools (not using any other fields at all, in favor of just a single
compilerFragmententry). It's unclear to me whether that would be feasible, given the interfaces involved between CMake and CMake Tools.As a work-around, using CMake to generate a
compile_commands.json, and configuring cpptools to use that, would result in the best possible interpretation (by cpptools) of those arguments to configure IntelliSense. Perhaps, if using Ninja generator, CMake Tools could itself ensureCMAKE_EXPORT_COMPILE_COMMANDSis set and simply dole out command lines from the generatedcompile_commands.jsonfile.
Yes, it's better this way. The interface between cmake, cmake-tools and cpptools needs to be coordinated.
Hi @Colengms and @debugee , thank you for reporting this issue. We have verified this issue in a Mac OS darwin environment with CMake version 4.1.0 + VS Code 1.105.1 + CMake tools 1.22.16. The project generated the cache successfully and built successfully, but there are IntelliSense errors showing that header files cannot be opened. Could you help check if our repro steps are correct?? If we missed something, please let us know. Thank you.
Hi @Colengms and @debugee , thank you for reporting this issue. We have verified this issue in a Mac OS darwin environment with CMake version 4.1.0 + VS Code 1.105.1 + CMake tools 1.22.16. The project generated the cache successfully and built successfully, but there are IntelliSense errors showing that header files cannot be opened. Could you help check if our repro steps are correct?? If we missed something, please let us know. Thank you.
你能看懂中文吗?问题我已经在https://github.com/microsoft/vscode-cmake-tools/issues/4597中描述的非常清楚了, 虽然你这个例子可以触发bug,但是这个里面可能包含多个bug,因为没有后缀的头文件goto进去之后translate unit变化了,所以这里有红色的波浪线,qt的cmake文件会添加QtWidgets.framework/Headers,所以你可以goto到QMessageBox里面去,然后是qmessagebox.h里面也可以进去,但是在qmessagebox.h里面的#include <QtWidgets/qtwidgetsglobal.h>就不行了,因为translate unit变化了, 然后还有个问题就是直接在main.cpp里面添加#include <QtWidgets/qtwidgetsglobal.h>也是进不去的.这是2个问题. 你之所以可以进入QMessageBox是因为
你可以这样触发问题,但是如果没分析清楚问题,修复的人会搞错
Thanks @debugee for the detailed information. Following the steps, we can reproduce the issue (Mac OS darwin environment with CMake version 4.1.0 + VS Code 1.105.1 + CMake tools 1.22.16). Repro Steps:
- Download the attached project(HelloQt.zip) and open it. The project generated the cache successfully and built successfully.
- Right click on "#include <QMessageBox>", click on "Go To Definition", it works fine. Right click on "#include <qmessagebox.h>", click on "Go To Definition", it works fine. Right click on "#include <QtWidgets/qtwidgetsglobal.h>", click on "Go To Definition", it doesn't work and shows "No definition for 'qtwidgetsglobal.h' ".
- Open main.cpp, add "#include <QtWidgets/qtwidgetsglobal.h>", right click on it and click "Go To Definition", it doesn't work and shows "No definition for 'qtwidgetsglobal.h' ".
Repro Video:
FYI: @gcampbell-msft
Adding some notes from: https://github.com/microsoft/vscode-cpptools/issues/13993#issuecomment-3513839912
Currently, in the API that CMake Tools is using, it's ambiguous (based on the contents) whether an include path should be considered an include path or a framework search path. ( https://github.com/microsoft/vscode-cpptools-api/blob/85704728ae422631f85ae36dd65a8fc6037b2bc4/api.ts#L153 )
However, the API does provide a way for CMake Tools to be unambiguous about these paths.
... in order to accurately reflect the compile-time search priority of all of the various possible types of include path (and macOS framework search path) compiler arguments (
-I,-iquote,-I-,-isysroot,-F,-iframework,-idirafter,-cxx-isystem,-stdlib++-isystem) it would be necessary for all of those arguments to be provided together in a single list, in which each type is clearly distinguished. For this to be handled properly, a config provider must specify these as compiler arguments, in the proper order, in the compiler args or compiler fragments field and provide none of them in the (flat, overly-simplified) include paths field.
Or, as noted in this issue, providing the entire command line used to compile a file would also successfully address this type of issue.
Thank you for the additional information.