From 28071f3f92e6a47378585ab5155ea04b74828344 Mon Sep 17 00:00:00 2001 From: SineStriker <55847490+SineStriker@users.noreply.github.com> Date: Tue, 22 Apr 2025 18:54:21 +0800 Subject: [PATCH 1/8] find_package: optimize --- .../package/manager/cmake/find_package.lua | 376 ++++++++++-------- 1 file changed, 213 insertions(+), 163 deletions(-) diff --git a/xmake/modules/package/manager/cmake/find_package.lua b/xmake/modules/package/manager/cmake/find_package.lua index 4c71291d791..63a4cefa452 100644 --- a/xmake/modules/package/manager/cmake/find_package.lua +++ b/xmake/modules/package/manager/cmake/find_package.lua @@ -24,6 +24,54 @@ import("core.base.hashset") import("core.project.target") import("lib.detect.find_tool") +local _cmake_internal_flags_variables = { + -- c flags + "CMAKE_C_FLAGS", + "CMAKE_C_FLAGS_DEBUG", + "CMAKE_C_FLAGS_RELEASE", + "CMAKE_C_FLAGS_MINSIZEREL", + "CMAKE_C_FLAGS_RELWITHDEBINFO", + "CMAKE_C_FLAGS_INIT", + "CMAKE_C_FLAGS_DEBUG_INIT", + "CMAKE_C_FLAGS_RELEASE_INIT", + "CMAKE_C_FLAGS_MINSIZEREL_INIT", + "CMAKE_C_FLAGS_RELWITHDEBINFO_INIT", + "CMAKE_C_FLAGS", + + -- c++ flags + "CMAKE_CXX_FLAGS", + "CMAKE_CXX_FLAGS_DEBUG", + "CMAKE_CXX_FLAGS_RELEASE", + "CMAKE_CXX_FLAGS_MINSIZEREL", + "CMAKE_CXX_FLAGS_RELWITHDEBINFO", + "CMAKE_CXX_FLAGS_INIT", + "CMAKE_CXX_FLAGS_DEBUG_INIT", + "CMAKE_CXX_FLAGS_RELEASE_INIT", + "CMAKE_CXX_FLAGS_MINSIZEREL_INIT", + "CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT", + + -- linker flags + "CMAKE_EXE_LINKER_FLAGS", + "CMAKE_EXE_LINKER_FLAGS_DEBUG", + "CMAKE_EXE_LINKER_FLAGS_RELEASE", + "CMAKE_EXE_LINKER_FLAGS_MINSIZEREL", + "CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO", + "CMAKE_EXE_LINKER_FLAGS_INIT", + "CMAKE_EXE_LINKER_FLAGS_DEBUG_INIT", + "CMAKE_EXE_LINKER_FLAGS_RELEASE_INIT", + "CMAKE_EXE_LINKER_FLAGS_MINSIZEREL_INIT", + "CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO_INIT", + + -- msvc + "CMAKE_MSVC_RUNTIME_LIBRARY", + + -- windows + -- https://github.com/Kitware/CMake/blob/e66e0b2cfefaf61fa995a0aa117df31e680b1c7e/Source/cmLocalGenerator.cxx#L1604 + -- https://github.com/Kitware/CMake/blob/e66e0b2cfefaf61fa995a0aa117df31e680b1c7e/Modules/Platform/Windows-MSVC.cmake#L404 + "CMAKE_CXX_CREATE_WIN32_EXE", + "CMAKE_CXX_CREATE_CONSOLE_EXE" +} + -- exclude cmake internal definitions https://github.com/xmake-io/xmake/issues/5217 function _should_exclude(define) local name = define:split("=")[1] @@ -46,7 +94,31 @@ function _find_package(cmake, name, opt) local workdir = os.tmpfile() .. ".dir" os.tryrm(workdir) os.mkdir(workdir) - io.writefile(path.join(workdir, "test.cpp"), "") + + -- generate fake ninja + local ninja_version = "1.10.2" + local ninjascript + if os.host() == "windows" then + ninjascript = path.join(workdir, "ninja.bat") + io.writefile(ninjascript, "@echo off\necho " .. ninja_version) + else + ninjascript = path.join(workdir, "ninja.sh") + io.writefile(ninjascript, "#!/bin/sh\necho " .. ninja_version) + os.vrunv("chmod", {"+x", ninjascript}, {curdir = workdir, envs = envs}) + end + + -- generate main.cpp + io.writefile(path.join(workdir, "main.cpp"), "") + + -- generate main-info.cmake.in + local info_cmake_in_file = io.open(path.join(workdir, "main-info.cmake.in"), "w") + info_cmake_in_file:print("set(INFO_INCLUDE_DIRS @INFO_INCLUDE_DIRS@)") + info_cmake_in_file:print("set(INFO_LINK_DIRS @INFO_LINK_DIRS@)") + info_cmake_in_file:print("set(INFO_COMPILE_DEFINITIONS @INFO_COMPILE_DEFINITIONS@)") + info_cmake_in_file:print("set(INFO_COMPILE_OPTIONS @INFO_COMPILE_OPTIONS@)") + info_cmake_in_file:print("set(INFO_LINK_OPTIONS @INFO_LINK_OPTIONS@)") + info_cmake_in_file:print("set(INFO_LINK_LIBRARIES @INFO_LINK_LIBRARIES@)") + info_cmake_in_file:close() -- generate CMakeLists.txt local filepath = path.join(workdir, "CMakeLists.txt") @@ -54,7 +126,12 @@ function _find_package(cmake, name, opt) if cmake.version then cmakefile:print("cmake_minimum_required(VERSION %s)", cmake.version) end - cmakefile:print("project(find_package)") + cmakefile:print("project(main)") + + -- unset CMake internal flags variables + for _, var in ipairs(_cmake_internal_flags_variables) do + cmakefile:print("set(%s \"\")", var) + end -- e.g. OpenCV 4.1.1, Boost COMPONENTS regex system local requirestr = name @@ -62,12 +139,14 @@ function _find_package(cmake, name, opt) if opt.require_version and opt.require_version ~= "latest" then requirestr = requirestr .. " " .. opt.require_version end + -- set search mode, e.g. config, module -- it will be both mode if do not set this config. -- e.g. https://cmake.org/cmake/help/latest/command/find_package.html#id4 if configs.search_mode then requirestr = requirestr .. " " .. configs.search_mode:upper() end + -- use opt.components is for backward compatibility local componentstr = "" local components = configs.components or opt.components @@ -102,10 +181,18 @@ function _find_package(cmake, name, opt) end end end - local testname = "test_" .. name - cmakefile:print("find_package(%s REQUIRED %s)", requirestr, componentstr) - cmakefile:print("if(%s_FOUND)", name) - cmakefile:print(" add_executable(%s test.cpp)", testname) + + -- find package manually + local find_script = configs.find_script or opt.find_script + if find_script then + cmakefile:print(find_script) + else + cmakefile:print("find_package(%s REQUIRED %s)", requirestr, componentstr) + end + + -- add executable target + cmakefile:print("add_executable(${PROJECT_NAME} main.cpp)") + -- setup include directories local includedirs = "" if configs.include_directories then @@ -114,10 +201,11 @@ function _find_package(cmake, name, opt) includedirs = ("${%s_INCLUDE_DIR} ${%s_INCLUDE_DIRS}"):format(name, name) includedirs = includedirs .. (" ${%s_INCLUDE_DIR} ${%s_INCLUDE_DIRS}"):format(name:upper(), name:upper()) end - cmakefile:print(" target_include_directories(%s PRIVATE %s)", testname, includedirs) + cmakefile:print("target_include_directories(${PROJECT_NAME} PRIVATE %s)", includedirs) + -- reserved for backword compatibility - cmakefile:print(" target_include_directories(%s PRIVATE ${%s_CXX_INCLUDE_DIRS})", - testname, name) + cmakefile:print("target_include_directories(${PROJECT_NAME} PRIVATE ${%s_CXX_INCLUDE_DIRS})", name) + -- setup link library/target local linklibs = "" if configs.link_libraries then @@ -126,8 +214,24 @@ function _find_package(cmake, name, opt) linklibs = ("${%s_LIBRARY} ${%s_LIBRARIES} ${%s_LIBS}"):format(name, name, name) linklibs = linklibs .. (" ${%s_LIBRARY} ${%s_LIBRARIES} ${%s_LIBS}"):format(name:upper(), name:upper(), name:upper()) end - cmakefile:print(" target_link_libraries(%s PRIVATE %s)", testname, linklibs) - cmakefile:print("endif(%s_FOUND)", name) + cmakefile:print("target_link_libraries(${PROJECT_NAME} PRIVATE %s)", linklibs) + + -- link target manually + local link_script = configs.link_script or opt.link_script + if link_script then + cmakefile:print(link_script) + end + + -- setup generating information + cmakefile:print("set(INFO_INCLUDE_DIRS $)") + cmakefile:print("set(INFO_LINK_DIRS $)") + cmakefile:print("set(INFO_COMPILE_DEFINITIONS $)") + cmakefile:print("set(INFO_COMPILE_OPTIONS $)") + cmakefile:print("set(INFO_LINK_OPTIONS $)") + cmakefile:print("set(INFO_LINK_LIBRARIES $)") + cmakefile:print("configure_file(\"main-info.cmake.in\" \"${CMAKE_BINARY_DIR}/main-info.cmake.tmp\")") + cmakefile:print("file(GENERATE OUTPUT \"${CMAKE_BINARY_DIR}/main-info.cmake\" INPUT \"${CMAKE_BINARY_DIR}/main-info.cmake.tmp\")") + cmakefile:close() if option.get("diagnosis") then local cmakedata = io.readfile(filepath) @@ -137,163 +241,108 @@ function _find_package(cmake, name, opt) -- run cmake local envs = configs.envs or opt.envs or {} envs.CMAKE_BUILD_TYPE = envs.CMAKE_BUILD_TYPE or _cmake_mode(opt.mode or "release") - try {function() return os.vrunv(cmake.program, {workdir}, {curdir = workdir, envs = envs}) end} - - -- pares defines and includedirs for macosx/linux - local links - local linkdirs - local libfiles - local defines - local includedirs - local ldflags - local flagsfile = path.join(workdir, "CMakeFiles", testname .. ".dir", "flags.make") - if os.isfile(flagsfile) then - local flagsdata = io.readfile(flagsfile) - if flagsdata then - if option.get("diagnosis") then - cprint("finding includes from %s\n${dim}%s", flagsfile, flagsdata) - end - for _, line in ipairs(flagsdata:split("\n", {plain = true})) do - if line:find("CXX_INCLUDES =", 1, true) then - local has_include = false - local flags = os.argv(line:split("=", {plain = true})[2]:trim()) - for _, flag in ipairs(flags) do - if has_include or (flag:startswith("-I") and #flag > 2) then - local includedir = has_include and flag or flag:sub(3) - if includedir and os.isdir(includedir) then - includedirs = includedirs or {} - table.insert(includedirs, includedir) - end - has_include = false - elseif flag == "-isystem" or flag == "-I" then - has_include = true - end - end - elseif line:find("CXX_DEFINES =", 1, true) then - defines = defines or {} - local flags = os.argv(line:split("=", {plain = true})[2]:trim()) - for _, flag in ipairs(flags) do - if flag:startswith("-D") and #flag > 2 then - local define = flag:sub(3) - if define and not _should_exclude(define) then - table.insert(defines, define) - end - end - end - end - end + + local builddir = path.join(workdir, "build") + try { + function() + return os.vrunv(cmake.program, + { + workdir, + "-S", + workdir, + "-B", + builddir, + "-G", + "Ninja", + "-DCMAKE_MAKE_PROGRAM=" .. ninjascript, + }, + { + curdir = workdir, + envs = envs + } + ) end - end + } - -- parse links and linkdirs for macosx/linux - local linkfile = path.join(workdir, "CMakeFiles", testname .. ".dir", "link.txt") - if os.isfile(linkfile) then - local linkdata = io.readfile(linkfile) - if linkdata then - if option.get("diagnosis") then - cprint("finding links from %s\n${dim}%s", linkfile, linkdata) - end - for _, line in ipairs(os.argv(linkdata)) do - local is_ldflags = false - local is_library = false - for _, suffix in ipairs({".so", ".dylib", ".dylib", ".tbd", ".lib"}) do - if line:startswith("-Wl,") then - is_ldflags = true - break - elseif line:find(suffix, 1, true) then - is_library = true - break - end - end - if is_ldflags then - ldflags = ldflags or {} - table.insert(ldflags, line) - elseif is_library then - -- strip library version suffix, e.g. libxxx.so.1.1 -> libxxx.so - if line:find(".so", 1, true) then - line = line:gsub("lib(.-)%.so%..+$", "lib%1.so") - end - - -- get libfiles - if os.isfile(line) then - libfiles = libfiles or {} - table.insert(libfiles, line) - end - - -- get links and linkdirs - local linkdir = path.directory(line) - if linkdir ~= "." then - linkdirs = linkdirs or {} - table.insert(linkdirs, linkdir) - end - local link = target.linkname(path.filename(line)) - if link then - links = links or {} - table.insert(links, link) - end - -- is link? e.g. -lxxx - elseif line:startswith("-l") then - local link = line:sub(3):trim() - links = links or {} - table.insert(links, link) - end - end - end - end + -- generate strip-info.cmake + local stripscriptpath = path.join(workdir, "strip-info.cmake") + local stripscript = io.open(stripscriptpath, "w") + stripscript:print("include(${INPUT_FILE})") + stripscript:print("function(_strip_genex _var)") + stripscript:print(" string(GENEX_STRIP \"${${_var}}\" _var_no_genex)") + stripscript:print(" if(NOT \"${_var_no_genex}\" STREQUAL \"${${_var}}\")") + stripscript:print(" unset(${_var} PARENT_SCOPE)") + stripscript:print(" endif()") + stripscript:print("endfunction()") - -- pares includedirs and links/linkdirs for windows - local vcprojfile = path.join(workdir, testname .. ".vcxproj") - if os.isfile(vcprojfile) then - local vcprojdata = io.readfile(vcprojfile) - local vs_mode = envs.CMAKE_BUILD_TYPE or _cmake_mode(opt.mode or "release") - vcprojdata = vcprojdata:match("(.-)") - - if vcprojdata then - for _, line in ipairs(vcprojdata:split("\n", {plain = true})) do - local values = line:match("(.+);%%%(AdditionalIncludeDirectories%)") - if values then - includedirs = includedirs or {} - table.join2(includedirs, path.splitenv(values)) - end - - values = line:match("(.+)") - if values then - for _, library in ipairs(path.splitenv(values)) do - -- get libfiles - if os.isfile(library) then - libfiles = libfiles or {} - table.insert(libfiles, library) - end - - -- get links and linkdirs - local linkdir = path.directory(library) - linkdir = path.translate(linkdir) - if linkdir ~= "." and not linkdir:startswith(workdir) then - linkdirs = linkdirs or {} - table.insert(linkdirs, linkdir) - local link = target.linkname(path.filename(library)) - if link then - links = links or {} - table.insert(links, link) - end - end - end - end - - values = line:match("%%%(PreprocessorDefinitions%);(.+)") - if values then - defines = defines or {} - values = path.splitenv(values) - for _, value in ipairs(values) do - if not _should_exclude(value) then - table.insert(defines, value) - end - end - end - end + stripscript:print("function(_list_to_table _list _table)") + stripscript:print(" if(_list AND (NOT \"${${_list}}\" STREQUAL \"\"))") + stripscript:print(" string(REPLACE \";\" \"\\\",\\n \\\"\" _result \"${${_list}}\")") + stripscript:print(" set(_result \"{\n \\\"${_result}\\\" \\n }\")") + stripscript:print(" set(${_table} ${_result} PARENT_SCOPE)") + stripscript:print(" return()") + stripscript:print(" endif()") + stripscript:print(" set(${_table} \"nil\" PARENT_SCOPE)") + stripscript:print("endfunction()") + + stripscript:print("_strip_genex(INFO_INCLUDE_DIRS)") + stripscript:print("_strip_genex(INFO_LINK_DIRS)") + stripscript:print("_strip_genex(INFO_COMPILE_DEFINITIONS)") + stripscript:print("_strip_genex(INFO_COMPILE_OPTIONS)") + stripscript:print("_strip_genex(INFO_LINK_OPTIONS)") + stripscript:print("_strip_genex(INFO_LINK_LIBRARIES)") + + stripscript:print("_list_to_table(INFO_INCLUDE_DIRS INFO_INCLUDE_DIRS_TABLE)") + stripscript:print("_list_to_table(INFO_LINK_DIRS INFO_LINK_DIRS_TABLE)") + stripscript:print("_list_to_table(INFO_COMPILE_DEFINITIONS INFO_COMPILE_DEFINITIONS_TABLE)") + stripscript:print("_list_to_table(INFO_COMPILE_OPTIONS INFO_COMPILE_OPTIONS_TABLE)") + stripscript:print("_list_to_table(INFO_LINK_OPTIONS INFO_LINK_OPTIONS_TABLE)") + stripscript:print("_list_to_table(INFO_LINK_LIBRARIES INFO_LINK_LIBRARIES_TABLE)") + + stripscript:print("set(_content \"{") + stripscript:print(" INCLUDE_DIRS = ${INFO_INCLUDE_DIRS_TABLE},") + stripscript:print(" LINK_DIRS = ${INFO_LINK_DIRS_TABLE},") + stripscript:print(" COMPILE_DEFINITIONS = ${INFO_COMPILE_DEFINITIONS_TABLE},") + stripscript:print(" COMPILE_OPTIONS = ${INFO_COMPILE_OPTIONS_TABLE},") + stripscript:print(" LINK_OPTIONS = ${INFO_LINK_OPTIONS_TABLE},") + stripscript:print(" LINK_LIBRARIES = ${INFO_LINK_LIBRARIES_TABLE}") + stripscript:print("}\")") + + stripscript:print("file(WRITE ${OUTPUT_FILE} ${_content})") + + stripscript:close() + + -- run strip-genex script + local infopath = path.join(builddir, "main-info.txt") + try { + function() + return os.vrunv(cmake.program, + { + "-DINPUT_FILE=" .. path.join(builddir, "main-info.cmake"), + "-DOUTPUT_FILE=" .. infopath, + "-P", + stripscriptpath, + }, + { + curdir = workdir + } + ) end - end + } + + -- get main-info.txt + local info = io.load(infopath) + local includedirs = info.INCLUDE_DIRS + local linkdirs = info.LINK_DIRS + local defines = info.COMPILE_DEFINITIONS + local flags = info.COMPILE_OPTIONS + local ldflags = info.LINK_OPTIONS + local links = info.LINK_LIBRARIES + local libfiles = info.LINK_LIBRARIES + -- get build.ninja + -- TODO + -- remove work directory os.tryrm(workdir) @@ -305,6 +354,7 @@ function _find_package(cmake, name, opt) results.linkdirs = table.unique(linkdirs) results.defines = table.unique(defines) results.libfiles = table.unique(libfiles) + results.flags = table.unique(flags) results.includedirs = table.unique(includedirs) return results end From ccabd1089a7ae28e1bcddbe76a250993faeaff4c Mon Sep 17 00:00:00 2001 From: SineStriker <55847490+SineStriker@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:04:27 +0800 Subject: [PATCH 2/8] find_package: parse ninja file --- .../package/manager/cmake/find_package.lua | 115 +++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/xmake/modules/package/manager/cmake/find_package.lua b/xmake/modules/package/manager/cmake/find_package.lua index 63a4cefa452..2ca75af5ad7 100644 --- a/xmake/modules/package/manager/cmake/find_package.lua +++ b/xmake/modules/package/manager/cmake/find_package.lua @@ -229,7 +229,7 @@ function _find_package(cmake, name, opt) cmakefile:print("set(INFO_COMPILE_OPTIONS $)") cmakefile:print("set(INFO_LINK_OPTIONS $)") cmakefile:print("set(INFO_LINK_LIBRARIES $)") - cmakefile:print("configure_file(\"main-info.cmake.in\" \"${CMAKE_BINARY_DIR}/main-info.cmake.tmp\")") + cmakefile:print("configure_file(\"main-info.cmake.in\" \"${CMAKE_BINARY_DIR}/main-info.cmake.tmp\" @ONLY)") cmakefile:print("file(GENERATE OUTPUT \"${CMAKE_BINARY_DIR}/main-info.cmake\" INPUT \"${CMAKE_BINARY_DIR}/main-info.cmake.tmp\")") cmakefile:close() @@ -341,7 +341,118 @@ function _find_package(cmake, name, opt) local libfiles = info.LINK_LIBRARIES -- get build.ninja - -- TODO + local ninjafile = path.join(builddir, "build.ninja") + local ninjabuilds = nil + if os.isfile(ninjafile) then + local ninjadata = io.readfile(ninjafile) + if ninjadata then + if option.get("diagnosis") then + cprint("finding links from %s\n${dim}%s", ninjafile, ninjadata) + end + + local results = {} + local current_build = nil + local current_vars = nil + for _, line in ipairs(ninjadata:split("\n")) do + -- https://ninja-build.org/manual.html#_build_statements + -- match build statement + -- e.g. + -- build CMakeFiles/main.dir/main.cpp.obj: ... + -- build main.exe: ... + local buildfile = line:match("^build%s+([^:]+):") + if buildfile then + -- save previous statement if exists + if current_build and current_vars then + table.insert(results, { build = current_build, vars = current_vars }) + end + + if path.basename(buildfile):startswith("main") then + -- save current statement + current_build = buildfile + current_vars = {} + else + -- ignore unrelated build statements + current_build = nil + current_vars = nil + end + elseif current_build then + -- match variable assignments + -- e.g. + -- ^ CONFIG = Debug + -- ^ DEFINES = -DXXX=YYY + -- ^ INCLUDES = -isystem /path/to/include + local var = line:match("^%s+([%w_]+)%s*=") + if var then + -- save current variable + current_vars[var] = line:sub(line:find("=") + 1):gsub("^%s+", "") + elseif line == "" then + -- encounter empty line, save current statement and start a new one + table.insert(results, { build = current_build, vars = current_vars }) + current_build = nil + current_vars = nil + end + end + end + + -- save the last statement if exists + if current_build and current_vars then + table.insert(results, { build = current_build, vars = current_vars }) + end + + ninjabuilds = results + end + end + + if ninjabuilds then + local ninja_defines = {} + local ninja_includes = {} + local ninja_flags = {} + local ninja_links = {} + local ninja_linkdirs = {} + local ninja_linkflags = {} + for _, builditem in pairs(ninjabuilds) do + for key, value in pairs(builditem.vars) do + if key == "DEFINES" then + ninja_defines = table.join(ninja_defines, os.argv(value)) + elseif key == "INCLUDES" then + ninja_includes = table.join(ninja_includes, os.argv(value)) + elseif key == "FLAGS" then + ninja_flags = table.join(ninja_flags, os.argv(value)) + elseif key == "LINK_LIBRARIES" then + ninja_links = table.join(ninja_links, os.argv(value)) + elseif key == "LINK_FLAGS" then + ninja_linkflags = table.join(ninja_linkflags, os.argv(value)) + elseif key == "LINK_PATH" then + ninja_linkdirs = table.join(ninja_linkdirs, os.argv(value)) + end + end + end + + -- TODO: strip command line prefix for `ninja_xxx` variables + -- e.g. -isystem -> + -- -DXXX=YYY -> XXX=YYY + -- -I/path/to/include -> /path/to/include + -- -L/path/to/lib -> /path/to/lib + -- -lxxx -> xxx + -- -framework xxx -> ? + + links = ninja_links + if not ldflags then + ldflags = ninja_linkflags + end + if not linkdirs then + linkdirs = ninja_linkdirs + end + if not defines then + defines = ninja_defines + end + if not flags then + flags = ninja_flags + end + if not includedirs then + includedirs = ninja_includes + end + end -- remove work directory os.tryrm(workdir) From 78f16fce38cff13e4611c502ecbfc4c2db08fe78 Mon Sep 17 00:00:00 2001 From: SineStriker <55847490+SineStriker@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:37:50 +0800 Subject: [PATCH 3/8] minor tweaks --- xmake/modules/package/manager/cmake/find_package.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/xmake/modules/package/manager/cmake/find_package.lua b/xmake/modules/package/manager/cmake/find_package.lua index 2ca75af5ad7..2004e8fef15 100644 --- a/xmake/modules/package/manager/cmake/find_package.lua +++ b/xmake/modules/package/manager/cmake/find_package.lua @@ -99,11 +99,11 @@ function _find_package(cmake, name, opt) local ninja_version = "1.10.2" local ninjascript if os.host() == "windows" then - ninjascript = path.join(workdir, "ninja.bat") + ninjascript = path.join(workdir, "ninja1.bat") io.writefile(ninjascript, "@echo off\necho " .. ninja_version) else - ninjascript = path.join(workdir, "ninja.sh") - io.writefile(ninjascript, "#!/bin/sh\necho " .. ninja_version) + ninjascript = path.join(workdir, "ninja1.sh") + io.writefile(ninjascript, "#!/bin/bash\necho " .. ninja_version) os.vrunv("chmod", {"+x", ninjascript}, {curdir = workdir, envs = envs}) end @@ -246,8 +246,7 @@ function _find_package(cmake, name, opt) try { function() return os.vrunv(cmake.program, - { - workdir, + { "-S", workdir, "-B", From 4836aa0a3e38be6b930ab9148a543b8b08042e1e Mon Sep 17 00:00:00 2001 From: SineStriker <55847490+SineStriker@users.noreply.github.com> Date: Wed, 23 Apr 2025 00:12:39 +0800 Subject: [PATCH 4/8] find_package: strip cmdline prefix --- .../package/manager/cmake/find_package.lua | 91 +++++++++++++++---- 1 file changed, 75 insertions(+), 16 deletions(-) diff --git a/xmake/modules/package/manager/cmake/find_package.lua b/xmake/modules/package/manager/cmake/find_package.lua index 2004e8fef15..f91d1b00da4 100644 --- a/xmake/modules/package/manager/cmake/find_package.lua +++ b/xmake/modules/package/manager/cmake/find_package.lua @@ -336,8 +336,20 @@ function _find_package(cmake, name, opt) local defines = info.COMPILE_DEFINITIONS local flags = info.COMPILE_OPTIONS local ldflags = info.LINK_OPTIONS - local links = info.LINK_LIBRARIES - local libfiles = info.LINK_LIBRARIES + local links = nil + local libfiles = nil + + -- get CMakeCache.txt + local isMSVC = false + for line in io.lines(path.join(builddir, "CMakeCache.txt")) do + if line:startswith("CMAKE_CXX_COMPILER") then + local compiler = line:sub(line:find("=") + 1):trim() + if path.basename(compiler):lower() == "cl" then + isMSVC = true + end + break + end + end -- get build.ninja local ninjafile = path.join(builddir, "build.ninja") @@ -383,7 +395,7 @@ function _find_package(cmake, name, opt) local var = line:match("^%s+([%w_]+)%s*=") if var then -- save current variable - current_vars[var] = line:sub(line:find("=") + 1):gsub("^%s+", "") + current_vars[var] = line:sub(line:find("=") + 1):trim() elseif line == "" then -- encounter empty line, save current statement and start a new one table.insert(results, { build = current_build, vars = current_vars }) @@ -409,33 +421,80 @@ function _find_package(cmake, name, opt) local ninja_links = {} local ninja_linkdirs = {} local ninja_linkflags = {} + + -- strip command line prefix for _, builditem in pairs(ninjabuilds) do for key, value in pairs(builditem.vars) do if key == "DEFINES" then - ninja_defines = table.join(ninja_defines, os.argv(value)) + -- strip -D, /D + for _, flag in ipairs(os.argv(value)) do + local define = flag:match("^-D(.+)$") or (isMSVC and flag:match("^/D(.+)$")) + if define and not _should_exclude(define) then + table.insert(ninja_defines, define) + end + end elseif key == "INCLUDES" then - ninja_includes = table.join(ninja_includes, os.argv(value)) + local has_include = false + for _, flag in ipairs(os.argv(value)) do + if has_include then + local includedir = flag + table.insert(ninja_includes, includedir) + has_include = false + elseif flag == "-isystem" or flag == "-idirafter" or flag == "-I" or (isMSVC and flag == "/I") then + has_include = true + else + -- strip -I, /I, -external:I, /external:I + local includedir = flag:match("^-I(.+)$") or ( + isMSVC and ( + flag:match("^/I(.+)$") or + flag:match("^-external:I(.+)$") or + flag:match("^/external:I(.+)$") + ) + ) + if includedir then + table.insert(ninja_includes, includedir) + end + end + end elseif key == "FLAGS" then ninja_flags = table.join(ninja_flags, os.argv(value)) elseif key == "LINK_LIBRARIES" then - ninja_links = table.join(ninja_links, os.argv(value)) + for _, flag in ipairs(os.argv(value)) do + -- strip -l + if flag:startswith("-l") then + if #flag > 2 then + local link = flag:sub(3):trim() + table.insert(ninja_links, link) + end + else + table.insert(ninja_links, flag) + end + end + + elseif key == "LINK_PATH" then + for _, flag in ipairs(os.argv(value)) do + -- strip -L, /LIBPATH, /libpath, -LIBPATH, -libpath + local linkdir + if isMSVC then + linkdir = flag:match("^/LIBPATH:(.+)$") or + flag:match("^/libpath:(.+)$") or + flag:match("^-LIBPATH:(.+)$") or + flag:match("^-libpath:(.+)$") + else + linkdir = flag:match("^-L(.+)$") + end + if linkdir then + table.insert(ninja_linkdirs, linkdir) + end + end elseif key == "LINK_FLAGS" then ninja_linkflags = table.join(ninja_linkflags, os.argv(value)) - elseif key == "LINK_PATH" then - ninja_linkdirs = table.join(ninja_linkdirs, os.argv(value)) end end end - -- TODO: strip command line prefix for `ninja_xxx` variables - -- e.g. -isystem -> - -- -DXXX=YYY -> XXX=YYY - -- -I/path/to/include -> /path/to/include - -- -L/path/to/lib -> /path/to/lib - -- -lxxx -> xxx - -- -framework xxx -> ? - links = ninja_links + libfiles = ninja_links if not ldflags then ldflags = ninja_linkflags end From d79c0b9cc391765608276db8c167ef2309ebb866 Mon Sep 17 00:00:00 2001 From: SineStriker <55847490+SineStriker@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:07:19 +0800 Subject: [PATCH 5/8] find_package: use regex to match kv pairs --- .../package/manager/cmake/find_package.lua | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/xmake/modules/package/manager/cmake/find_package.lua b/xmake/modules/package/manager/cmake/find_package.lua index f91d1b00da4..485e4377cc4 100644 --- a/xmake/modules/package/manager/cmake/find_package.lua +++ b/xmake/modules/package/manager/cmake/find_package.lua @@ -392,10 +392,10 @@ function _find_package(cmake, name, opt) -- ^ CONFIG = Debug -- ^ DEFINES = -DXXX=YYY -- ^ INCLUDES = -isystem /path/to/include - local var = line:match("^%s+([%w_]+)%s*=") - if var then + local key, value = line:match("^%s+([%w_]+)%s*=%s*(.+)$") + if key and value then -- save current variable - current_vars[var] = line:sub(line:find("=") + 1):trim() + current_vars[key] = value elseif line == "" then -- encounter empty line, save current statement and start a new one table.insert(results, { build = current_build, vars = current_vars }) @@ -493,25 +493,27 @@ function _find_package(cmake, name, opt) end end - links = ninja_links - libfiles = ninja_links - if not ldflags then + if #ninja_links > 0 then + links = ninja_links + libfiles = links + end + if #ninja_flags >0 and not ldflags then ldflags = ninja_linkflags end - if not linkdirs then + if #ninja_linkdirs >0 and not linkdirs then linkdirs = ninja_linkdirs end - if not defines then + if #ninja_defines >0 and not defines then defines = ninja_defines end - if not flags then + if #ninja_flags >0 and not flags then flags = ninja_flags end - if not includedirs then + if #ninja_includes >0 and not includedirs then includedirs = ninja_includes end end - + -- remove work directory os.tryrm(workdir) From de152ebe7021a989da090c24b2a39e3729bc8928 Mon Sep 17 00:00:00 2001 From: SineStriker <55847490+SineStriker@users.noreply.github.com> Date: Wed, 23 Apr 2025 13:16:03 +0800 Subject: [PATCH 6/8] find_package: add shared and static target types --- .../package/manager/cmake/find_package.lua | 78 +++++++++++++++---- 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/xmake/modules/package/manager/cmake/find_package.lua b/xmake/modules/package/manager/cmake/find_package.lua index 485e4377cc4..9d2a01fa772 100644 --- a/xmake/modules/package/manager/cmake/find_package.lua +++ b/xmake/modules/package/manager/cmake/find_package.lua @@ -61,6 +61,26 @@ local _cmake_internal_flags_variables = { "CMAKE_EXE_LINKER_FLAGS_RELEASE_INIT", "CMAKE_EXE_LINKER_FLAGS_MINSIZEREL_INIT", "CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO_INIT", + "CMAKE_SHARED_LINKER_FLAGS", + "CMAKE_SHARED_LINKER_FLAGS_DEBUG", + "CMAKE_SHARED_LINKER_FLAGS_RELEASE", + "CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL", + "CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO", + "CMAKE_SHARED_LINKER_FLAGS_INIT", + "CMAKE_SHARED_LINKER_FLAGS_DEBUG_INIT", + "CMAKE_SHARED_LINKER_FLAGS_RELEASE_INIT", + "CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL_INIT", + "CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO_INIT", + "CMAKE_STATIC_LINKER_FLAGS", + "CMAKE_STATIC_LINKER_FLAGS_DEBUG", + "CMAKE_STATIC_LINKER_FLAGS_RELEASE", + "CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL", + "CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO", + "CMAKE_STATIC_LINKER_FLAGS_INIT", + "CMAKE_STATIC_LINKER_FLAGS_DEBUG_INIT", + "CMAKE_STATIC_LINKER_FLAGS_RELEASE_INIT", + "CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL_INIT", + "CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO_INIT", -- msvc "CMAKE_MSVC_RUNTIME_LIBRARY", @@ -89,6 +109,7 @@ end -- find package function _find_package(cmake, name, opt) + local is_windows = os.host() == "windows" -- get work directory local workdir = os.tmpfile() .. ".dir" @@ -98,7 +119,7 @@ function _find_package(cmake, name, opt) -- generate fake ninja local ninja_version = "1.10.2" local ninjascript - if os.host() == "windows" then + if is_windows then ninjascript = path.join(workdir, "ninja1.bat") io.writefile(ninjascript, "@echo off\necho " .. ninja_version) else @@ -190,8 +211,26 @@ function _find_package(cmake, name, opt) cmakefile:print("find_package(%s REQUIRED %s)", requirestr, componentstr) end - -- add executable target - cmakefile:print("add_executable(${PROJECT_NAME} main.cpp)") + -- add target + local target_type = configs.target_type or opt.target_type + local is_executable + if not target_type then + is_executable = true + cmakefile:print("add_executable(${PROJECT_NAME})") + elseif target_type == "shared" then + cmakefile:print("add_library(${PROJECT_NAME} SHARED)") + elseif target_type == "static" then + cmakefile:print("add_library(${PROJECT_NAME} STATIC)") + else + is_executable = true + cmakefile:print("add_executable(${PROJECT_NAME})") + end + cmakefile:print("target_sources(${PROJECT_NAME} PRIVATE main.cpp)") + + -- set target properties + if is_executable and is_windows and (configs.win32 or opt.win32) then + cmakefile:print("set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE TRUE)") + end -- setup include directories local includedirs = "" @@ -340,12 +379,12 @@ function _find_package(cmake, name, opt) local libfiles = nil -- get CMakeCache.txt - local isMSVC = false + local is_msvc = false for line in io.lines(path.join(builddir, "CMakeCache.txt")) do if line:startswith("CMAKE_CXX_COMPILER") then local compiler = line:sub(line:find("=") + 1):trim() if path.basename(compiler):lower() == "cl" then - isMSVC = true + is_msvc = true end break end @@ -370,14 +409,16 @@ function _find_package(cmake, name, opt) -- e.g. -- build CMakeFiles/main.dir/main.cpp.obj: ... -- build main.exe: ... - local buildfile = line:match("^build%s+([^:]+):") - if buildfile then + local buildfiles = line:match("^build%s+([^:]+):") + if buildfiles then -- save previous statement if exists if current_build and current_vars then table.insert(results, { build = current_build, vars = current_vars }) end - if path.basename(buildfile):startswith("main") then + local buildfile = os.argv(buildfiles)[1] + local basename = path.basename(buildfile) + if basename == "main" or basename == "libmain" or basename == "main.cpp" then -- save current statement current_build = buildfile current_vars = {} @@ -428,7 +469,7 @@ function _find_package(cmake, name, opt) if key == "DEFINES" then -- strip -D, /D for _, flag in ipairs(os.argv(value)) do - local define = flag:match("^-D(.+)$") or (isMSVC and flag:match("^/D(.+)$")) + local define = flag:match("^-D(.+)$") or (is_msvc and flag:match("^/D(.+)$")) if define and not _should_exclude(define) then table.insert(ninja_defines, define) end @@ -440,12 +481,12 @@ function _find_package(cmake, name, opt) local includedir = flag table.insert(ninja_includes, includedir) has_include = false - elseif flag == "-isystem" or flag == "-idirafter" or flag == "-I" or (isMSVC and flag == "/I") then + elseif flag == "-isystem" or flag == "-idirafter" or flag == "-I" or (is_msvc and flag == "/I") then has_include = true else -- strip -I, /I, -external:I, /external:I local includedir = flag:match("^-I(.+)$") or ( - isMSVC and ( + is_msvc and ( flag:match("^/I(.+)$") or flag:match("^-external:I(.+)$") or flag:match("^/external:I(.+)$") @@ -475,7 +516,7 @@ function _find_package(cmake, name, opt) for _, flag in ipairs(os.argv(value)) do -- strip -L, /LIBPATH, /libpath, -LIBPATH, -libpath local linkdir - if isMSVC then + if is_msvc then linkdir = flag:match("^/LIBPATH:(.+)$") or flag:match("^/libpath:(.+)$") or flag:match("^-LIBPATH:(.+)$") or @@ -552,8 +593,17 @@ end -- configs = { -- components = {"regex", "system"}, -- moduledirs = "xxx", --- presets = {Boost_USE_STATIC_LIB = true}, --- envs = {CMAKE_PREFIX_PATH = "xxx"}}) +-- presets = { +-- Boost_USE_STATIC_LIB = true +-- }, +-- envs = { +-- CMAKE_PREFIX_PATH = "xxx", +-- }, +-- find_script = "find_package(Foo CONFIG REQUIRED)", +-- link_script = "target_link_libraries(${PROJECT_NAME} PRIVATE Foo::Foo)", +-- target_type = "shared", +-- win32 = true +-- }} -- function main(name, opt) opt = opt or {} From 7529351f958d4a5c8575de1433b1880b55692fbc Mon Sep 17 00:00:00 2001 From: SineStriker <55847490+SineStriker@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:18:06 +0800 Subject: [PATCH 7/8] find_package: add more CMake implicit variables --- .../package/manager/cmake/find_package.lua | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/xmake/modules/package/manager/cmake/find_package.lua b/xmake/modules/package/manager/cmake/find_package.lua index 9d2a01fa772..6e88bd038e0 100644 --- a/xmake/modules/package/manager/cmake/find_package.lua +++ b/xmake/modules/package/manager/cmake/find_package.lua @@ -25,6 +25,20 @@ import("core.project.target") import("lib.detect.find_tool") local _cmake_internal_flags_variables = { + -- default libraries + "CMAKE_C_STANDARD_LIBRARIES", + "CMAKE_CXX_STANDARD_LIBRARIES", + + -- implicit directories + "CMAKE_C_IMPLICIT_LINK_LIBRARIES", + "CMAKE_C_IMPLICIT_LINK_DIRECTORIES", + "CMAKE_C_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES", + "CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES", + "CMAKE_CXX_IMPLICIT_LINK_LIBRARIES", + "CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES", + "CMAKE_CXX_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES", + "CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES", + -- c flags "CMAKE_C_FLAGS", "CMAKE_C_FLAGS_DEBUG", @@ -81,6 +95,16 @@ local _cmake_internal_flags_variables = { "CMAKE_STATIC_LINKER_FLAGS_RELEASE_INIT", "CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL_INIT", "CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO_INIT", + "CMAKE_MODULE_LINKER_FLAGS", + "CMAKE_MODULE_LINKER_FLAGS_DEBUG", + "CMAKE_MODULE_LINKER_FLAGS_RELEASE", + "CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL", + "CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO", + "CMAKE_MODULE_LINKER_FLAGS_INIT", + "CMAKE_MODULE_LINKER_FLAGS_DEBUG_INIT", + "CMAKE_MODULE_LINKER_FLAGS_RELEASE_INIT", + "CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL_INIT", + "CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO_INIT", -- msvc "CMAKE_MSVC_RUNTIME_LIBRARY", From e33f185b402c7ae847ba1d425a1c0683144f5a78 Mon Sep 17 00:00:00 2001 From: SineStriker <55847490+SineStriker@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:20:33 +0800 Subject: [PATCH 8/8] update --- xmake/modules/package/manager/cmake/find_package.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmake/modules/package/manager/cmake/find_package.lua b/xmake/modules/package/manager/cmake/find_package.lua index 6e88bd038e0..18d8598c68b 100644 --- a/xmake/modules/package/manager/cmake/find_package.lua +++ b/xmake/modules/package/manager/cmake/find_package.lua @@ -133,7 +133,7 @@ end -- find package function _find_package(cmake, name, opt) - local is_windows = os.host() == "windows" + local is_windows = is_host("windows") -- get work directory local workdir = os.tmpfile() .. ".dir"