EMBTClang: Add support for the Embarcadero bcc64x compiler

Add a new compiler id, EMBTClang, for the Embarcadero clang-based Win64
compiler (bcc64x), as a Clang variant detected by its __CODEGEARC__ marker
on the MinGW-w64 target, and exclude that target from the Embarcadero
compiler id.

Provide Compiler/ and Platform/ modules for C, CXX, and ASM, including
feature tests, binutils discovery with the linker, resource compiler, and
LLVM binutils pinned to the toolchain, and the derived RTL and include
directories forwarded into try_compile.

Drive the WIN32_EXECUTABLE property through the toolchain's console and GUI
subsystem switches, and add an EMBT_RUNTIME_LIBRARY target property
(Static or Dynamic) implemented like WATCOM_RUNTIME_LIBRARY and
MSVC_RUNTIME_LIBRARY.

Build shared and module libraries as plain DLLs.  Search for libraries as
name.lib before the MinGW names, and do not add the GNU -Bstatic and
-Bdynamic switches.  Do not provide a C++ module dependency scanning rule:
the toolchain's clang-scan-deps does not apply the bcc64x driver's target,
standard, and include paths.

Document the new id and property, and add RunCMake and build tests covering
detection, the runtime-library property, binutils pinning, the console/GUI
subsystem macros, precompiled header reuse across target kinds, and C++20
builds without module scanning.

Closes: #26769
This commit is contained in:
Lee Cantey authored and Brad King committed 2026-10-28 14:58:31 -04:00
1 parent f29cfe2bf9
commit e739170f15
70 files changed
+1177 -17

No files matched your search

+11
View File
@@ -113,6 +113,17 @@ foreach(lang IN ITEMS C CXX)
endif()
endforeach()
# CMake's own sources are UTF-8. The Embarcadero (EMBTClang) bcc64x compiler
# defaults the narrow execution code page to the host ANSI code page, unlike
# Clang and MSVC which preserve UTF-8, so non-ASCII narrow string literals such
# as the localized regexes in Source/CTest/cmCTestBuildHandler.cxx are corrupted.
# Select UTF-8 (code page 65001) when building CMake itself with EMBTClang.
foreach(lang IN ITEMS C CXX)
if(CMAKE_${lang}_COMPILER_ID STREQUAL "EMBTClang")
string(APPEND CMAKE_${lang}_FLAGS " -fborland-exec-code-page=65001")
endif()
endforeach()
# use the ansi CXX compile flag for building cmake
if (CMAKE_ANSI_CXXFLAGS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_ANSI_CXXFLAGS}")
+1
View File
@@ -253,6 +253,7 @@ Properties on Targets
/prop_tgt/DOTNET_SDK
/prop_tgt/DOTNET_TARGET_FRAMEWORK
/prop_tgt/DOTNET_TARGET_FRAMEWORK_VERSION
/prop_tgt/EMBT_RUNTIME_LIBRARY
/prop_tgt/EchoString
/prop_tgt/ENABLE_EXPORTS
/prop_tgt/EXCLUDE_FROM_ALL
+1
View File
@@ -485,6 +485,7 @@ Variables that Control the Build
/variable/CMAKE_DEPENDS_USE_COMPILER
/variable/CMAKE_DISABLE_PRECOMPILE_HEADERS
/variable/CMAKE_DLL_NAME_WITH_SOVERSION
/variable/CMAKE_EMBT_RUNTIME_LIBRARY
/variable/CMAKE_EXECUTABLE_ENABLE_EXPORTS
/variable/CMAKE_EXE_LINKER_FLAGS
/variable/CMAKE_EXE_LINKER_FLAGS_CONFIG
+26
View File
@@ -0,0 +1,26 @@
EMBT_RUNTIME_LIBRARY
--------------------
.. versionadded:: 4.6
Select the runtime library used by the Embarcadero (``bcc64x``) toolchain.
The allowed values are:
.. include:: include/EMBT_RUNTIME_LIBRARY-VALUES.rst
Use :manual:`generator expressions <cmake-generator-expressions(7)>` to
support per-configuration specification.
For example, the code:
.. code-block:: cmake
add_library(mylib SHARED mylib.cxx)
set_property(TARGET mylib PROPERTY EMBT_RUNTIME_LIBRARY "Dynamic")
selects for the target ``mylib`` the dynamically-linked runtime library.
This property is initialized by the :variable:`CMAKE_EMBT_RUNTIME_LIBRARY`
variable if it is set when a target is created. It has no effect on
toolchains other than ``EMBTClang``.
@@ -0,0 +1,12 @@
``Static``
Compile without additional flags to use the statically-linked runtime
library.
``Dynamic``
Compile with ``-tR`` and define ``USEPACKAGES`` to use the
dynamically-linked (shared) runtime library.
The value is ignored on non-Embarcadero compilers but an unsupported value
will be rejected as an error when using the ``bcc64x`` toolchain.
If this property is not set then no runtime library selection flag is added
explicitly by CMake, which is equivalent to ``Static``.
+9
View File
@@ -0,0 +1,9 @@
embtclang-toolchain
-------------------
* Embarcadero Clang-based compilers are now supported with
:variable:`compiler id <CMAKE_<LANG>_COMPILER_ID>` ``EMBTClang``.
C++ modules are not yet supported with this compiler.
* The runtime library for the Embarcadero ``bcc64x`` toolchain may now be
selected with the :prop_tgt:`EMBT_RUNTIME_LIBRARY` target property.
@@ -0,0 +1,15 @@
CMAKE_EMBT_RUNTIME_LIBRARY
--------------------------
.. versionadded:: 4.6
Select the runtime library used by the Embarcadero (``bcc64x``) toolchain.
This variable is used to initialize the :prop_tgt:`EMBT_RUNTIME_LIBRARY`
property on all targets as they are created.
The allowed values are:
.. include:: ../prop_tgt/include/EMBT_RUNTIME_LIBRARY-VALUES.rst
If this variable is not set then the :prop_tgt:`EMBT_RUNTIME_LIBRARY` target
property is not set automatically.
+1
View File
@@ -22,6 +22,7 @@ Value Name
``CrayClang`` Cray Clang-based Compiler
``Diab`` `Wind River Systems Diab Compiler`_
``DPClang`` `Open-source DPC++ Compiler`_
``EMBTClang`` `Embarcadero`_ Clang-based Compiler
``Embarcadero``, ``Borland`` `Embarcadero`_
``Flang`` `Classic Flang Fortran Compiler`_
``LLVMFlang`` `LLVM Flang Fortran Compiler`_
+1
View File
@@ -47,6 +47,7 @@ function(compiler_id_detection outvar lang)
IntelLLVM
DPClang
PathScale
EMBTClang
Embarcadero
Borland
Watcom
+2 -2
View File
@@ -43,9 +43,9 @@ if(NOT CMAKE_ASM${ASM_DIALECT}_COMPILER)
else()
# List all default C and CXX compilers
set(CMAKE_ASM_COMPILER_LIST
${_CMAKE_TOOLCHAIN_PREFIX}cc ${_CMAKE_TOOLCHAIN_PREFIX}gcc xlc
${_CMAKE_TOOLCHAIN_PREFIX}cc ${_CMAKE_TOOLCHAIN_PREFIX}gcc bcc64x xlc
${_CMAKE_ASM_MAYBE_CL}
CC ${_CMAKE_TOOLCHAIN_PREFIX}c++ ${_CMAKE_TOOLCHAIN_PREFIX}g++ xlC)
CC ${_CMAKE_TOOLCHAIN_PREFIX}c++ ${_CMAKE_TOOLCHAIN_PREFIX}g++ bcc64x xlC)
unset(_CMAKE_ASM_MAYBE_CL)
unset(_CMAKE_ASM_REGEX_CL)
unset(_CMAKE_ASM_REGEX_MSVC)
+1 -1
View File
@@ -58,7 +58,7 @@ else()
# finally list compilers to try
if(NOT CMAKE_C_COMPILER_INIT)
set(CMAKE_C_COMPILER_LIST ${_CMAKE_TOOLCHAIN_PREFIX}cc ${_CMAKE_TOOLCHAIN_PREFIX}gcc cl bcc xlc icx clang dpclang)
set(CMAKE_C_COMPILER_LIST ${_CMAKE_TOOLCHAIN_PREFIX}cc ${_CMAKE_TOOLCHAIN_PREFIX}gcc cl bcc64x bcc xlc icx clang dpclang)
endif()
_cmake_find_compiler(C)
+1 -1
View File
@@ -57,7 +57,7 @@ else()
# finally list compilers to try
if(NOT CMAKE_CXX_COMPILER_INIT)
set(CMAKE_CXX_COMPILER_LIST CC ${_CMAKE_TOOLCHAIN_PREFIX}c++ ${_CMAKE_TOOLCHAIN_PREFIX}g++ aCC cl bcc xlC)
set(CMAKE_CXX_COMPILER_LIST CC ${_CMAKE_TOOLCHAIN_PREFIX}c++ ${_CMAKE_TOOLCHAIN_PREFIX}g++ aCC cl bcc64x bcc xlC)
if(NOT CMAKE_HOST_WIN32)
# FIXME(#24314): Add support for the GNU-like icpx compiler driver
# on Windows, first introduced by Intel oneAPI 2023.0.
+2 -1
View File
@@ -361,6 +361,7 @@ function(CMAKE_DETERMINE_COMPILER_ID lang flagvar src)
elseif("x${CMAKE_${lang}_COMPILER_ID}" STREQUAL "xGNU"
OR "x${CMAKE_${lang}_COMPILER_ID}" STREQUAL "xAdaptiveCpp"
OR "x${CMAKE_${lang}_COMPILER_ID}" STREQUAL "xAppleClang"
OR "x${CMAKE_${lang}_COMPILER_ID}" STREQUAL "xEMBTClang"
OR "x${CMAKE_${lang}_COMPILER_ID}" STREQUAL "xFujitsuClang"
OR "x${CMAKE_${lang}_COMPILER_ID}" STREQUAL "xIBMClang"
OR "x${CMAKE_${lang}_COMPILER_ID}" STREQUAL "xTIClang")
@@ -375,7 +376,7 @@ function(CMAKE_DETERMINE_COMPILER_ID lang flagvar src)
# for C++ and the GNU-frontend variant.
set(CMAKE_${lang}_COMPILER_CLANG_RESOURCE_DIR "")
if ("x${lang}" STREQUAL "xCXX" AND
CMAKE_${lang}_COMPILER_ID MATCHES "^(Clang|DPClang)$" AND
CMAKE_${lang}_COMPILER_ID MATCHES "^(Clang|DPClang|EMBTClang)$" AND
"x${CMAKE_${lang}_COMPILER_FRONTEND_VARIANT}" STREQUAL "xGNU")
execute_process(
COMMAND "${CMAKE_${lang}_COMPILER}"
+4
View File
@@ -0,0 +1,4 @@
include(Compiler/Clang-ASM)
include(Compiler/EMBTClang)
string(APPEND CMAKE_ASM_FLAGS_INIT " -q")
@@ -0,0 +1 @@
include(Compiler/Clang-C-FeatureTests)
+4
View File
@@ -0,0 +1,4 @@
include(Compiler/Clang-C)
include(Compiler/EMBTClang)
string(APPEND CMAKE_C_FLAGS_INIT " -q")
@@ -0,0 +1 @@
include(Compiler/Clang-CXX-FeatureTests)
+19
View File
@@ -0,0 +1,19 @@
# Keep a scanning rule set before enable_language() (CMake's own tests set one
# to fake scanning); drop only the one Compiler/Clang-CXX adds.
if(DEFINED CMAKE_CXX_SCANDEP_SOURCE)
set(__embt_scandep_source "${CMAKE_CXX_SCANDEP_SOURCE}")
endif()
include(Compiler/Clang-CXX)
include(Compiler/EMBTClang)
# The toolchain's clang-scan-deps does not apply the bcc64x driver's target,
# standard, and include paths, so C++ module scanning is not supported.
if(DEFINED __embt_scandep_source)
set(CMAKE_CXX_SCANDEP_SOURCE "${__embt_scandep_source}")
unset(__embt_scandep_source)
else()
unset(CMAKE_CXX_SCANDEP_SOURCE)
endif()
string(APPEND CMAKE_CXX_FLAGS_INIT " -q")
@@ -0,0 +1,4 @@
set(_compiler_id_pp_test "defined(__CODEGEARC__) && defined(__clang__) && defined(__MINGW64__)")
include("${CMAKE_CURRENT_LIST_DIR}/Clang-DetermineCompilerInternal.cmake")
@@ -0,0 +1,69 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file LICENSE.rst or https://cmake.org/licensing for details.
# Runs immediately after CMakeFindBinUtils during compiler determination.
# CMakeFindBinUtils does not match EMBTClang in its Clang/IntelLLVM/LLVMFlang
# regex, so it falls through to the generic branch and finds whatever "ar"
# happens to be on PATH (often a stray ar.exe from MSYS, MinGW, etc.).
# Override CMAKE_AR/CMAKE_RANLIB with the llvm-ar/llvm-ranlib that ship
# alongside bcc64x in the Embarcadero installation.
if(NOT DEFINED _CMAKE_PROCESSING_LANGUAGE OR _CMAKE_PROCESSING_LANGUAGE STREQUAL "")
message(FATAL_ERROR "Internal error: _CMAKE_PROCESSING_LANGUAGE is not set")
endif()
# The compiler path is fully resolved by this point in the determination
# phase, so get_filename_component yields a real directory.
get_filename_component(__embt_hint
"${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER}" REALPATH)
get_filename_component(__embt_hint "${__embt_hint}" DIRECTORY)
find_program(__embt_ar NAMES llvm-ar
HINTS "${__embt_hint}"
NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_DEFAULT_PATH)
if(__embt_ar)
set(CMAKE_AR "${__embt_ar}" CACHE FILEPATH "Archiver" FORCE)
set(CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_AR "${__embt_ar}"
CACHE FILEPATH "LLVM archiver" FORCE)
mark_as_advanced(CMAKE_AR
CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_AR)
endif()
unset(__embt_ar CACHE)
find_program(__embt_ranlib NAMES llvm-ranlib
HINTS "${__embt_hint}"
NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_DEFAULT_PATH)
if(__embt_ranlib)
set(CMAKE_RANLIB "${__embt_ranlib}" CACHE FILEPATH "Archive index tool" FORCE)
set(CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_RANLIB "${__embt_ranlib}"
CACHE FILEPATH "Generate index for LLVM archive" FORCE)
mark_as_advanced(CMAKE_RANLIB
CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_RANLIB)
endif()
unset(__embt_ranlib CACHE)
# Pin the linker and the remaining LLVM binutils that ship beside bcc64x, for the
# same reason as CMAKE_AR above: CMakeFindBinUtils leaves them unset (or, for a
# bare name, resolvable from a stray PATH entry). Linking itself goes through
# the bcc64x driver, so CMAKE_LINKER is informational (CheckLinkerFlag and the
# like consume it).
foreach(__embt_tool "LINKER=ld.lld" "NM=llvm-nm" "OBJDUMP=llvm-objdump"
"STRIP=llvm-strip" "DLLTOOL=llvm-dlltool")
string(REPLACE "=" ";" __embt_tool "${__embt_tool}")
list(GET __embt_tool 0 __embt_var)
list(GET __embt_tool 1 __embt_prog)
find_program(__embt_found NAMES ${__embt_prog}
HINTS "${__embt_hint}"
NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_DEFAULT_PATH)
if(__embt_found)
set(CMAKE_${__embt_var} "${__embt_found}" CACHE FILEPATH "${__embt_prog}" FORCE)
mark_as_advanced(CMAKE_${__embt_var})
endif()
unset(__embt_found CACHE)
endforeach()
unset(__embt_var)
unset(__embt_prog)
unset(__embt_tool)
unset(__embt_hint)
+37
View File
@@ -0,0 +1,37 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file LICENSE.rst or https://cmake.org/licensing for details.
# This module is shared by multiple languages; use include blocker.
if(__COMPILER_EMBTCLANG)
return()
endif()
set(__COMPILER_EMBTCLANG 1)
if("x${CMAKE_C_COMPILER_ID}" STREQUAL "xEMBTClang")
set(__embt_driver "${CMAKE_C_COMPILER}")
elseif("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xEMBTClang")
set(__embt_driver "${CMAKE_CXX_COMPILER}")
elseif("x${CMAKE_ASM_COMPILER_ID}" STREQUAL "xEMBTClang")
set(__embt_driver "${CMAKE_ASM_COMPILER}")
endif()
if(__embt_driver)
get_filename_component(EMBT_DRIVER_NAME "${__embt_driver}" NAME_WE)
endif()
if(WIN32 AND EMBT_DRIVER_NAME AND NOT CMAKE_RC_COMPILER_INIT AND NOT CMAKE_GENERATOR_RC)
# Resolve the resource compiler that ships beside the driver to an absolute
# path, not a bare name that would be taken from PATH at build time.
get_filename_component(__embt_bindir "${__embt_driver}" DIRECTORY)
find_program(__embt_rc NAMES ${EMBT_DRIVER_NAME}-windres
HINTS "${__embt_bindir}"
NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_DEFAULT_PATH)
if(__embt_rc)
set(CMAKE_RC_COMPILER_INIT "${__embt_rc}")
else()
set(CMAKE_RC_COMPILER_INIT ${EMBT_DRIVER_NAME}-windres)
endif()
unset(__embt_rc CACHE)
unset(__embt_bindir)
endif()
unset(__embt_driver)
@@ -1,5 +1,5 @@
set(_compiler_id_pp_test "defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__)")
set(_compiler_id_pp_test "defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__) && !defined(__MINGW64__)")
set(_compiler_id_version_compute "
# define @PREFIX@COMPILER_VERSION_MAJOR @MACRO_HEX@(__CODEGEARC_VERSION__>>24 & 0x00FF)
@@ -0,0 +1,2 @@
include(Platform/Windows-EMBTClang)
__windows_compiler_embtclang(ASM)
@@ -0,0 +1,2 @@
include(Platform/Windows-EMBTClang)
__windows_compiler_embtclang(C)
@@ -0,0 +1,2 @@
include(Platform/Windows-EMBTClang)
__windows_compiler_embtclang(CXX)
+140
View File
@@ -0,0 +1,140 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file LICENSE.rst or https://cmake.org/licensing for details.
# This module is shared by multiple languages; use include blocker.
if(__WINDOWS_EMBTCLANG)
return()
endif()
set(__WINDOWS_EMBTCLANG 1)
include(Platform/Windows-Clang)
# CMAKE_EMBT_RUNTIME_LIBRARY is forwarded into try_compile() by cmCoreTryCompile,
# so probes compile with the right runtime. The derived RTL link directory and
# RAD Studio include base are not, so forward them too: a probe for a project
# that adds -tJ to link the RAD Studio RTL, or that includes the RAD headers,
# then resolves rtl.lib/rtl.bpi/rtle.lib and <System.hpp> from these paths.
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
CMAKE_EMBT_RTL_LIBRARY_DIR CMAKE_EMBT_INCLUDE_DIR)
macro(__windows_compiler_embtclang lang)
__windows_compiler_clang(${lang})
# The GNU-driver Clang path gives Debug builds only "-g"; the RAD Studio
# headers select debug runtime behavior on _DEBUG, which "-g" does not
# define. Add it so a Debug build matches the debug runtime.
string(APPEND CMAKE_${lang}_FLAGS_DEBUG_INIT " -D_DEBUG")
# Drive the WIN32_EXECUTABLE property through the bcc64x subsystem switches
# instead of the inherited MSVC-style linker options. These are link rules.
# At compile -tC only defines __CONSOLE__, which the RAD Studio headers
# branch on, and -tW defines nothing; but -tC is recorded in precompiled
# headers, so the compile side defines the macro instead. Keep the two
# separate: a CREATE_*_EXE link rule may carry link-only syntax (LINKER:)
# that is meaningless on a compile command.
set(CMAKE_${lang}_CREATE_WIN32_EXE "-tW")
set(CMAKE_${lang}_CREATE_CONSOLE_EXE "-tC")
set(CMAKE_${lang}_COMPILE_OPTIONS_EMBT_GUI "")
set(CMAKE_${lang}_COMPILE_OPTIONS_EMBT_CONSOLE -D__CONSOLE__=1)
# The bcc64x toolchain uses MSVC-style naming (no "lib" prefix, ".lib"
# import/static, ".dll" shared), not the GNU/MinGW names inherited from
# Windows-Clang's non-MSVC path.
set(CMAKE_STATIC_LIBRARY_PREFIX "")
set(CMAKE_STATIC_LIBRARY_SUFFIX ".lib")
set(CMAKE_SHARED_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_SUFFIX ".dll")
set(CMAKE_SHARED_MODULE_PREFIX "")
set(CMAKE_SHARED_MODULE_SUFFIX ".dll")
set(CMAKE_IMPORT_LIBRARY_PREFIX "")
set(CMAKE_IMPORT_LIBRARY_SUFFIX ".lib")
# Find RAD Studio-named libraries first, then the MinGW names the toolchain's
# own libraries use: static before import, as the driver links by default.
set(CMAKE_FIND_LIBRARY_PREFIXES "" "lib")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib" ".a" ".dll.a")
# Disable the GNU link-type switches (-Bstatic/-Bdynamic) inherited above.
# Static and import libraries share the ".lib" name, so they cannot select
# between them, and the driver sets the link type itself to match the
# runtime library, which a switch left at the end of the line would undo.
foreach(__embt_type IN ITEMS EXE SHARED_LIBRARY SHARED_MODULE)
set(CMAKE_${__embt_type}_LINK_STATIC_${lang}_FLAGS "")
set(CMAKE_${__embt_type}_LINK_DYNAMIC_${lang}_FLAGS "")
endforeach()
unset(__embt_type)
set(CMAKE_${lang}_OUTPUT_EXTENSION ".o")
# Append (foo.c.o), do not replace (foo.o): replacing the source extension
# collides when a target has two sources sharing a stem (e.g. test.c +
# test.cxx), producing "multiple rules generate <stem>.o"
set(CMAKE_${lang}_OUTPUT_EXTENSION_REPLACE 0)
# The default archive command (qc) does not write a symbol index. The
# ARCHIVE_FINISH step runs ranlib to add one, but llvm-ranlib may not be
# present. Use 's' so llvm-ar embeds the index in the same pass; lld
# requires the index to resolve symbols from a static archive.
set(CMAKE_${lang}_ARCHIVE_CREATE "<CMAKE_AR> -qcDs <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_${lang}_ARCHIVE_APPEND "<CMAKE_AR> -qDs <TARGET> <LINK_FLAGS> <OBJECTS>")
# The EMBT_RUNTIME_LIBRARY property selects the runtime library the same way as
# MSVC_RUNTIME_LIBRARY / WATCOM_RUNTIME_LIBRARY: a default activates it and the
# property overrides. Static relies on the driver's -tC/-tW/-tD default, which
# links libc++ statically; Dynamic adds -tR for the shared libc++ and defines
# USEPACKAGES so the RAD Studio headers import RTL symbols.
set(CMAKE_EMBT_RUNTIME_LIBRARY_DEFAULT "Static")
set(CMAKE_${lang}_COMPILE_OPTIONS_EMBT_RUNTIME_LIBRARY_Static "")
set(CMAKE_${lang}_COMPILE_OPTIONS_EMBT_RUNTIME_LIBRARY_Dynamic -tR -DUSEPACKAGES)
# A shared or module library is a plain DLL: -tD at link. At compile -tD
# only defines __DLL__, but it is recorded in precompiled headers, which
# then cannot be reused by other target kinds; define the macro instead.
set(CMAKE_SHARED_LIBRARY_CREATE_${lang}_FLAGS "-shared -tD")
set(CMAKE_SHARED_MODULE_CREATE_${lang}_FLAGS "-shared -tD")
set(CMAKE_${lang}_COMPILE_OPTIONS_EMBT_DLL -D__DLL__=1)
# When a project adds -tJ to link against the RAD Studio RTL, the driver pulls
# the RTL import libraries (rtl.lib/rtl.bpi and rtle.lib) and startup object
# from the link search path. They sit next to the compiler at
# <compiler>/../lib/win64x/<config>. Put that directory on the standard link
# path so a user-added -tJ resolves them. Override with
# CMAKE_EMBT_RTL_LIBRARY_DIR.
if(NOT DEFINED CMAKE_EMBT_RTL_LIBRARY_DIR)
get_filename_component(__embt_bindir "${CMAKE_${lang}_COMPILER}" DIRECTORY)
# Use the debug RTL only for a Debug build -- the config that also defines
# _DEBUG, matching the debug runtime; use release otherwise, including when
# no build type is set. Keyed on CMAKE_BUILD_TYPE, so this assumes a
# single-config generator; multi-config would need a $<CONFIG:Debug> genex.
if(CMAKE_BUILD_TYPE MATCHES "^[Dd][Ee][Bb][Uu][Gg]$")
set(__embt_rtl_config debug)
else()
set(__embt_rtl_config release)
endif()
get_filename_component(CMAKE_EMBT_RTL_LIBRARY_DIR
"${__embt_bindir}/../lib/win64x/${__embt_rtl_config}" ABSOLUTE)
unset(__embt_bindir)
unset(__embt_rtl_config)
endif()
if(EXISTS "${CMAKE_EMBT_RTL_LIBRARY_DIR}")
list(APPEND CMAKE_${lang}_STANDARD_LINK_DIRECTORIES
"${CMAKE_EMBT_RTL_LIBRARY_DIR}")
endif()
# The bcc64x driver does not put the RAD Studio C++ headers on the default
# include path (the IDE adds them). Do the same so <System.hpp> and the
# framework headers resolve. They live at <compiler>/../include/windows/<set>.
# Override the base with CMAKE_EMBT_INCLUDE_DIR.
if(NOT DEFINED CMAKE_EMBT_INCLUDE_DIR)
get_filename_component(__embt_bindir "${CMAKE_${lang}_COMPILER}" DIRECTORY)
get_filename_component(CMAKE_EMBT_INCLUDE_DIR
"${__embt_bindir}/../include/windows" ABSOLUTE)
unset(__embt_bindir)
endif()
foreach(__embt_inc IN ITEMS sdk rtl vcl fmx)
if(EXISTS "${CMAKE_EMBT_INCLUDE_DIR}/${__embt_inc}")
list(APPEND CMAKE_${lang}_STANDARD_INCLUDE_DIRECTORIES
"${CMAKE_EMBT_INCLUDE_DIR}/${__embt_inc}")
endif()
endforeach()
unset(__embt_inc)
endmacro()
+1
View File
@@ -1150,6 +1150,7 @@ cm::optional<cmTryCompileResult> cmCoreTryCompile::TryCompileCode(
vars.insert("CMAKE_SYCL_DEVICE_TARGETS");
vars.emplace("CMAKE_MSVC_RUNTIME_LIBRARY"_s);
vars.emplace("CMAKE_WATCOM_RUNTIME_LIBRARY"_s);
vars.emplace("CMAKE_EMBT_RUNTIME_LIBRARY"_s);
vars.emplace("CMAKE_MSVC_DEBUG_INFORMATION_FORMAT"_s);
vars.emplace("CMAKE_MSVC_RUNTIME_CHECKS"_s);
vars.emplace("CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS"_s);
+57
View File
@@ -2318,6 +2318,7 @@ void cmLocalGenerator::AddLanguageFlags(std::string& flags,
(compilerId == "MSVC" || compilerSimulateId == "MSVC");
bool const compilerTargetsWatcomABI =
(compilerId == "OpenWatcom" || compilerSimulateId == "OpenWatcom");
bool const compilerTargetsEmbtABI = (compilerId == "EMBTClang");
if (lang == "Swift") {
target->AddSwiftTargetFlags(flags);
@@ -2419,6 +2420,62 @@ void cmLocalGenerator::AddLanguageFlags(std::string& flags,
}
}
// Add Embarcadero (bcc64x) runtime library flags. This is activated by the
// presence of a default selection whether or not it is overridden by a
// property, the same way as MSVC and Watcom above.
cmValue embtRuntimeLibraryDefault =
this->Makefile->GetDefinition("CMAKE_EMBT_RUNTIME_LIBRARY_DEFAULT");
if (cmNonempty(embtRuntimeLibraryDefault)) {
cmValue embtRuntimeLibraryValue =
target->GetProperty("EMBT_RUNTIME_LIBRARY");
if (!embtRuntimeLibraryValue) {
embtRuntimeLibraryValue = embtRuntimeLibraryDefault;
}
std::string const embtRuntimeLibrary = cmGeneratorExpression::Evaluate(
*embtRuntimeLibraryValue, this, config, target);
if (!embtRuntimeLibrary.empty()) {
if (cmValue embtRuntimeLibraryOptions = this->Makefile->GetDefinition(
cmStrCat("CMAKE_", lang, "_COMPILE_OPTIONS_EMBT_RUNTIME_LIBRARY_",
embtRuntimeLibrary))) {
this->AppendCompileOptions(flags, *embtRuntimeLibraryOptions);
} else if (compilerTargetsEmbtABI &&
!cmSystemTools::GetErrorOccurredFlag()) {
// The compiler is EMBTClang so it needs a known runtime library.
this->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("EMBT_RUNTIME_LIBRARY value '", embtRuntimeLibrary,
"' not known for this ", lang, " compiler."));
}
}
}
// Add Embarcadero (bcc64x) target-kind flags for shared and module libraries
// and the console/GUI subsystem, active only for the EMBTClang toolchain.
if (compilerId == "EMBTClang") {
// A shared or module library is a plain DLL: it is compiled with __DLL__
// for the RAD Studio headers (the platform module links it with -tD).
if (target->GetType() == cm::TargetType::SHARED_LIBRARY ||
target->GetType() == cm::TargetType::MODULE_LIBRARY) {
if (cmValue embtOptions = this->Makefile->GetDefinition(
cmStrCat("CMAKE_", lang, "_COMPILE_OPTIONS_EMBT_DLL"))) {
this->AppendCompileOptions(flags, *embtOptions);
}
}
// -tC/-tW set the subsystem at link, and a console executable is also
// compiled with __CONSOLE__, which the RAD Studio headers branch on.
// WIN32_EXECUTABLE drives the switch only at link, so apply the matching
// compile options to an executable too, via dedicated compile-option
// variables (not the CREATE_{WIN32,CONSOLE}_EXE link rules, which may
// carry link-only syntax).
if (target->GetType() == cm::TargetType::EXECUTABLE) {
if (cmValue subsystem = this->Makefile->GetDefinition(
cmStrCat("CMAKE_", lang, "_COMPILE_OPTIONS_EMBT_",
target->IsWin32Executable(config) ? "GUI" : "CONSOLE"))) {
this->AppendCompileOptions(flags, *subsystem);
}
}
}
// Add MSVC runtime checks flags. This is activated by the presence
// of a default selection whether or not it is overridden by a property.
cmValue msvcRuntimeChecksDefault =
+4
View File
@@ -362,6 +362,8 @@ TargetProperty const StaticTargetProperties[] = {
{ "VS_USE_DEBUG_LIBRARIES"_s, IC::NonImportedTarget },
// ---- OpenWatcom
{ "WATCOM_RUNTIME_LIBRARY"_s, IC::CanCompileSources },
// ---- Embarcadero
{ "EMBT_RUNTIME_LIBRARY"_s, IC::CanCompileSources },
// ---- AIX
{ "AIX_SHARED_LIBRARY_ARCHIVE"_s, IC::SharedLibraryTarget },
// -- Language
@@ -1997,6 +1999,8 @@ void cmTarget::CopyCxxModulesProperties(cmTarget const* tgt)
"VS_PLATFORM_TOOLSET",
// ---- OpenWatcom
"WATCOM_RUNTIME_LIBRARY",
// ---- Embarcadero
"EMBT_RUNTIME_LIBRARY",
// -- Language
// ---- C++
"CXX_COMPILER_LAUNCHER",
@@ -10,11 +10,11 @@ add_executable(target_compile_options
"${CMAKE_CURRENT_SOURCE_DIR}/main.cpp"
)
target_compile_options(target_compile_options
PRIVATE $<$<CXX_COMPILER_ID:AppleClang,IBMClang,CrayClang,Clang,DPClang,GNU,LCC>:-DMY_PRIVATE_DEFINE>
PRIVATE $<$<CXX_COMPILER_ID:AppleClang,IBMClang,CrayClang,EMBTClang,Clang,DPClang,GNU,LCC>:-DMY_PRIVATE_DEFINE>
PUBLIC $<$<COMPILE_LANG_AND_ID:CXX,GNU,LCC>:-DMY_PUBLIC_DEFINE>
PUBLIC $<$<COMPILE_LANG_AND_ID:CXX,GNU,LCC,Clang,DPClang,AppleClang,CrayClang,IBMClang>:-DMY_MUTLI_COMP_PUBLIC_DEFINE>
PUBLIC $<$<COMPILE_LANG_AND_ID:CXX,GNU,LCC,Clang,DPClang,AppleClang,CrayClang,EMBTClang,IBMClang>:-DMY_MUTLI_COMP_PUBLIC_DEFINE>
INTERFACE $<$<CXX_COMPILER_ID:GNU,LCC>:-DMY_INTERFACE_DEFINE>
INTERFACE $<$<CXX_COMPILER_ID:GNU,LCC,Clang,DPClang,AppleClang,CrayClang,IBMClang>:-DMY_MULTI_COMP_INTERFACE_DEFINE>
INTERFACE $<$<CXX_COMPILER_ID:GNU,LCC,Clang,DPClang,AppleClang,CrayClang,EMBTClang,IBMClang>:-DMY_MULTI_COMP_INTERFACE_DEFINE>
)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|LCC")
@@ -50,7 +50,7 @@ if(CMAKE_GENERATOR MATCHES "Visual Studio")
endif()
target_compile_options(consumer
PRIVATE $<$<CXX_COMPILER_ID:GNU,LCC,Clang,DPClang,AppleClang,CrayClang,IBMClang>:$<TARGET_PROPERTY:target_compile_options,INTERFACE_COMPILE_OPTIONS>>
PRIVATE $<$<CXX_COMPILER_ID:GNU,LCC,Clang,DPClang,AppleClang,CrayClang,EMBTClang,IBMClang>:$<TARGET_PROPERTY:target_compile_options,INTERFACE_COMPILE_OPTIONS>>
)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|LCC")
+4
View File
@@ -2142,6 +2142,10 @@ if(BUILD_TESTING)
ADD_TEST_MACRO(WatcomRuntimeLibrary)
endif()
if(CMAKE_C_COMPILER_ID MATCHES "EMBTClang" AND WIN32)
ADD_TEST_MACRO(EMBTRuntimeLibrary)
endif()
ADD_TEST_MACRO(CheckCompilerRelatedVariables CheckCompilerRelatedVariables)
if("${CMAKE_GENERATOR}" MATCHES "^Ninja$|Makefile|FASTBuild")
+66
View File
@@ -0,0 +1,66 @@
cmake_minimum_required(VERSION 3.28)
project(EMBTRuntimeLibrary C CXX)
# Verify that an EMBT_RUNTIME_LIBRARY selection compiles and defines the expected
# macros. The selection is applied both as a directory variable (which
# initializes the target property and is forwarded into try_compile) and directly
# on a target, so both paths are exercised. The VERIFY_* switches arm the #error
# guards in verify.h, making a wrong property->flag mapping a build failure.
function(verify tag lang src)
cmake_parse_arguments(PARSE_ARGV 3 A "" "PROP;VALUE;TYPE" "DEFS")
if(NOT A_TYPE)
set(A_TYPE STATIC)
endif()
# Local to this function scope, so it does not leak to sibling cases.
if(A_PROP)
set(CMAKE_${A_PROP} "${A_VALUE}")
endif()
# try_compile wants -D-prefixed definitions; target_compile_definitions wants
# bare names.
set(try_defs "")
foreach(d IN LISTS A_DEFS)
list(APPEND try_defs "-D${d}")
endforeach()
# try_compile only supports EXECUTABLE / STATIC_LIBRARY target types, so the
# shared-library case is covered by the real add_library() target below.
if(NOT A_TYPE STREQUAL "SHARED")
set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")
try_compile(${tag}_${lang}_COMPILES
${CMAKE_CURRENT_BINARY_DIR}/tc/${tag}-${lang}
${CMAKE_CURRENT_SOURCE_DIR}/${src}
COMPILE_DEFINITIONS ${try_defs}
OUTPUT_VARIABLE out)
if(${tag}_${lang}_COMPILES)
message(STATUS "try_compile ${tag} (${lang}) worked")
else()
string(REPLACE "\n" "\n " out " ${out}")
message(SEND_ERROR "try_compile ${tag} (${lang}) failed:\n${out}")
endif()
endif()
add_library(${tag}-${lang} ${A_TYPE} ${src})
if(A_PROP)
set_property(TARGET ${tag}-${lang} PROPERTY ${A_PROP} "${A_VALUE}")
endif()
target_compile_definitions(${tag}-${lang} PRIVATE ${A_DEFS})
endfunction()
foreach(lang_src "C;verify.c" "CXX;verify.cxx")
list(GET lang_src 0 lang)
list(GET lang_src 1 src)
# No selection: none of the macros are defined.
verify(default ${lang} ${src})
# Runtime library.
verify(static ${lang} ${src} PROP EMBT_RUNTIME_LIBRARY VALUE Static)
verify(dynamic ${lang} ${src} PROP EMBT_RUNTIME_LIBRARY VALUE Dynamic
DEFS VERIFY_RTLDLL VERIFY_USEPACKAGES)
# A shared library is a plain DLL: linked with -tD and compiled with __DLL__.
# A trivial DLL links without the RAD Studio runtime.
verify(dll ${lang} ${src} TYPE SHARED DEFS VERIFY_DLL)
endforeach()
+1
View File
@@ -0,0 +1 @@
#include "verify.h"
+1
View File
@@ -0,0 +1 @@
#include "verify.h"
+38
View File
@@ -0,0 +1,38 @@
/* The EMBT_RUNTIME_LIBRARY property and the target type select bcc64x driver
flags. Each flag defines characteristic preprocessor macros; assert them
here so a wrong mapping fails the build. The VERIFY_* switches are passed
by the test for the selection under test. */
/* EMBT_RUNTIME_LIBRARY=Dynamic uses the dynamic RTL (-tR), which defines
_RTLDLL. */
#ifdef VERIFY_RTLDLL
# ifndef _RTLDLL
# error "_RTLDLL not defined by the dynamic runtime selection"
# endif
#else
# ifdef _RTLDLL
# error "_RTLDLL defined without the dynamic runtime selection"
# endif
#endif
/* The dynamic RTL also defines USEPACKAGES (added by CMake). */
#ifdef VERIFY_USEPACKAGES
# ifndef USEPACKAGES
# error "USEPACKAGES not defined by the dynamic runtime selection"
# endif
#else
# ifdef USEPACKAGES
# error "USEPACKAGES defined without the dynamic runtime selection"
# endif
#endif
/* A shared library defines __DLL__. */
#ifdef VERIFY_DLL
# ifndef __DLL__
# error "__DLL__ not defined for a shared library"
# endif
#else
# ifdef __DLL__
# error "__DLL__ defined for a non-shared target"
# endif
#endif
+2 -1
View File
@@ -12,7 +12,8 @@ if(MSVC AND NOT MSVC_VERSION LESS 1400)
-P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake)
add_executable(MSManifestNone main.c)
set_property(TARGET MSManifestNone PROPERTY LINK_FLAGS "/MANIFEST:NO")
elseif(WIN32 AND CMAKE_C_COMPILER_ID MATCHES "Clang")
elseif(WIN32 AND CMAKE_C_COMPILER_ID MATCHES "Clang"
AND CMAKE_C_SIMULATE_ID STREQUAL "MSVC")
add_test(NAME MSManifest.Single COMMAND
${CMAKE_COMMAND} -Dexe=$<TARGET_FILE:MSManifest>
-P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake)
+3 -1
View File
@@ -6,7 +6,9 @@ add_executable(MSMultipleManifest main.c
${CMAKE_CURRENT_BINARY_DIR}/test_manifest2.manifest
${CMAKE_CURRENT_BINARY_DIR}/test_manifest3.manifest)
if((MSVC AND NOT MSVC_VERSION LESS 1400) OR (WIN32 AND CMAKE_C_COMPILER_ID MATCHES "Clang") )
if((MSVC AND NOT MSVC_VERSION LESS 1400)
OR (WIN32 AND CMAKE_C_COMPILER_ID MATCHES "Clang"
AND CMAKE_C_SIMULATE_ID STREQUAL "MSVC"))
add_test(NAME MSManifest.Multiple COMMAND
${CMAKE_COMMAND} -Dexe=$<TARGET_FILE:MSMultipleManifest>
-P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake)
@@ -55,7 +55,7 @@ endmacro()
# detailed features tables, not just meta-features
if (CMAKE_C_COMPILE_FEATURES)
if (NOT CMAKE_C_COMPILER_ID MATCHES "^(LCC|Cray|CrayClang|PGI|NVHPC|XL|XLClang|IBMClang|IntelLLVM|DPClang|Fujitsu|FujitsuClang|OrangeC)$")
if (NOT CMAKE_C_COMPILER_ID MATCHES "^(LCC|Cray|CrayClang|PGI|NVHPC|XL|XLClang|IBMClang|IntelLLVM|DPClang|Fujitsu|FujitsuClang|OrangeC|EMBTClang)$")
set(C_expected_features ${CMAKE_C_COMPILE_FEATURES})
list(FILTER C_expected_features EXCLUDE REGEX "^c_std_[0-9][0-9]")
endif()
@@ -98,7 +98,7 @@ if (C_expected_features)
endif()
if (CMAKE_CXX_COMPILE_FEATURES)
if (NOT CMAKE_CXX_COMPILER_ID MATCHES "^(LCC|Cray|CrayClang|PGI|NVHPC|XL|XLClang|IBMClang|IntelLLVM|DPClang|Fujitsu|FujitsuClang|OrangeC)$")
if (NOT CMAKE_CXX_COMPILER_ID MATCHES "^(LCC|Cray|CrayClang|PGI|NVHPC|XL|XLClang|IBMClang|IntelLLVM|DPClang|Fujitsu|FujitsuClang|OrangeC|EMBTClang)$")
set(CXX_expected_features ${CMAKE_CXX_COMPILE_FEATURES})
list(FILTER CXX_expected_features EXCLUDE REGEX "^cxx_std_[0-9][0-9]")
endif()
+1
View File
@@ -661,6 +661,7 @@ if(MSVC)
add_RunCMake_test(MSVCRuntimeTypeInfo)
add_RunCMake_test(MSVCDebugInformationFormat)
endif()
add_RunCMake_test(EMBTClang)
if(XCODE_VERSION)
set(ObjectLibrary_ARGS -DXCODE_VERSION=${XCODE_VERSION})
endif()
+3
View File
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.14)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)
@@ -0,0 +1,32 @@
include(RunCMake)
# Registration and initialization of the EMBT_RUNTIME_LIBRARY target property is
# generic and does not require the Embarcadero toolchain, so it runs on any host.
run_cmake(embt-properties)
# EMBT_RUNTIME_LIBRARY link selection: Dynamic links the shared libc++
# (libc++-380.dll import), Static links it statically (no import). Builds real
# exes with bcc64x and checks imports; a no-op where bcc64x is absent.
run_cmake(runtime-library)
# Compiler-id regression: bcc64x -> EMBTClang, older drivers -> Embarcadero.
# The case finds the drivers itself and is a no-op where none are installed.
run_cmake(compiler-id-drivers)
# Binutils pinning: the linker and LLVM tools resolve to distinct absolute paths
# beside the compiler. Finds bcc64x itself; a no-op where it is absent.
run_cmake(binutils-pinned)
# Subsystem macros: a console target compiles with __CONSOLE__ and a GUI target
# without it (compilation sees the macro, not just the link switch).
# Driver-gated.
run_cmake(subsystem-macros)
# Precompiled header reuse across target kinds: a static library's header is
# reused by shared and module libraries and console and GUI executables.
# Driver-gated.
run_cmake(pch-reuse-kinds)
# C++ module scanning is not supported: a C++20 target builds without being
# scanned, and C++ module sources are rejected. Driver-gated.
run_cmake(cxx-module-scanning)
@@ -0,0 +1,60 @@
# Regression guard for C4: EMBTClang-FindBinUtils must pin the linker and the
# LLVM binutils to absolute paths beside the compiler, each a distinct tool.
# The distinctness check specifically catches the find_program cache-reuse trap
# -- dropping the per-iteration unset(... CACHE) collapses every tool onto the
# first (ld.lld). Gated on bcc64x being present; a no-op where it is absent.
if(NOT WIN32)
return()
endif()
set(_hints "")
if(DEFINED ENV{TP})
list(APPEND _hints "$ENV{TP}/bin64")
endif()
find_program(EMBT_BCC64X NAMES bcc64x HINTS ${_hints})
if(NOT EMBT_BCC64X)
message(STATUS "bcc64x not found on PATH or under $TP; binutils pinning check skipped.")
return()
endif()
# Start from a clean nested build tree: compiler determination (and thus
# FindBinUtils) is cached, so a stale tree would not re-run the logic under test.
file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/build")
execute_process(
COMMAND ${CMAKE_COMMAND}
-S "${CMAKE_CURRENT_SOURCE_DIR}/binutils"
-B "${CMAKE_CURRENT_BINARY_DIR}/build"
-G "${CMAKE_GENERATOR}"
"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
"-DCMAKE_C_COMPILER=${EMBT_BCC64X}"
-DCMAKE_C_COMPILER_WORKS=1
OUTPUT_VARIABLE _out ERROR_VARIABLE _err)
# FindBinUtils pins tools found under REALPATH(compiler dir), so canonicalize
# both sides before comparing -- the work trees are reachable through junctions,
# where a literal directory would not string-match the resolved one.
get_filename_component(_bindir "${EMBT_BCC64X}" REALPATH)
get_filename_component(_bindir "${_bindir}" DIRECTORY)
set(_seen "")
foreach(_tool LINKER NM OBJDUMP STRIP DLLTOOL)
if(NOT _out MATCHES "BINUTIL ${_tool}=\\[([^]]+)\\]")
message(SEND_ERROR "no CMAKE_${_tool} reported.\n${_out}${_err}")
continue()
endif()
set(_path "${CMAKE_MATCH_1}")
if(NOT IS_ABSOLUTE "${_path}" OR NOT EXISTS "${_path}")
message(SEND_ERROR "CMAKE_${_tool} is not an existing absolute path: [${_path}]")
endif()
get_filename_component(_dir "${_path}" REALPATH)
get_filename_component(_dir "${_dir}" DIRECTORY)
if(NOT _dir STREQUAL _bindir)
message(SEND_ERROR "CMAKE_${_tool} is not beside the compiler: [${_path}]")
endif()
if(_path IN_LIST _seen)
message(SEND_ERROR
"CMAKE_${_tool} collapsed onto an already-pinned tool: [${_path}] "
"(find_program cache-reuse regression)")
endif()
list(APPEND _seen "${_path}")
endforeach()
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.14)
project(embt_binutils C)
# The parent test reads these back to assert each tool was pinned.
foreach(t LINKER NM OBJDUMP STRIP DLLTOOL)
message(STATUS "BINUTIL ${t}=[${CMAKE_${t}}]")
endforeach()
@@ -0,0 +1,56 @@
# Regression guard for the Embarcadero compiler-id detection: bcc64x must
# identify as EMBTClang while the older Clang-based drivers (bcc32x, bcc64,
# bcc32c) stay Embarcadero. bcc64x is
# discriminated by __MINGW64__ (bcc64 also defines _WIN64, so _WIN64 would not
# tell them apart), and EMBTClang is tried before Embarcadero because bcc64x
# also defines __BORLANDC__/__CODEGEARC_VERSION__.
#
# Each driver's identity is checked by a nested configure, gated on the driver
# being present: real coverage where a RAD Studio tree is installed, a no-op
# elsewhere (upstream CI has none of these compilers).
if(NOT WIN32)
return()
endif()
set(_hints "")
if(DEFINED ENV{TP})
list(APPEND _hints "$ENV{TP}/bin64" "$ENV{TP}/bin")
endif()
# driver name -> expected CMAKE_C_COMPILER_ID
set(_expect
bcc64x EMBTClang
bcc32x Embarcadero
bcc64 Embarcadero
bcc32c Embarcadero)
set(_found_any NO)
while(_expect)
list(POP_FRONT _expect _drv _want)
find_program(EMBT_DRIVER_${_drv} NAMES ${_drv} HINTS ${_hints})
if(NOT EMBT_DRIVER_${_drv})
continue()
endif()
set(_found_any YES)
execute_process(
COMMAND ${CMAKE_COMMAND}
-S "${CMAKE_CURRENT_SOURCE_DIR}/compiler-id"
-B "${CMAKE_CURRENT_BINARY_DIR}/id-${_drv}"
-G "${CMAKE_GENERATOR}"
"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
"-DCMAKE_C_COMPILER=${EMBT_DRIVER_${_drv}}"
-DCMAKE_C_COMPILER_WORKS=1
OUTPUT_VARIABLE _out ERROR_VARIABLE _err)
if(NOT _out MATCHES "DRIVER_COMPILER_ID=\\[${_want}\\]")
message(SEND_ERROR
"driver '${_drv}' should identify as '${_want}'.\n"
"----- configure output -----\n${_out}${_err}")
endif()
endwhile()
if(NOT _found_any)
message(STATUS
"No Embarcadero drivers found on PATH or under \$TP; "
"compiler-id regression check skipped.")
endif()
@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.14)
project(embt_compiler_id C)
# The parent test reads this line back to assert the detected identity.
message(STATUS "DRIVER_COMPILER_ID=[${CMAKE_C_COMPILER_ID}]")
@@ -0,0 +1,80 @@
# C++ module scanning is not supported: a C++20 target that includes standard
# headers must build without being scanned, even when clang-scan-deps is
# present beside bcc64x, and a target with C++ module sources must be rejected
# at configure time. Driver-gated: a no-op where bcc64x is not installed.
if(NOT WIN32)
return()
endif()
set(_hints "")
if(DEFINED ENV{TP})
list(APPEND _hints "$ENV{TP}/bin64" "$ENV{TP}/bin")
endif()
find_program(EMBT_BCC64X NAMES bcc64x HINTS ${_hints})
if(NOT EMBT_BCC64X)
message(STATUS "bcc64x not found; C++ module scanning check skipped.")
return()
endif()
get_filename_component(_bindir "${EMBT_BCC64X}" DIRECTORY)
function(_scan_configure case)
set(_b "${CMAKE_CURRENT_BINARY_DIR}/scan-${case}")
file(REMOVE_RECURSE "${_b}")
execute_process(
COMMAND ${CMAKE_COMMAND}
-S "${CMAKE_CURRENT_SOURCE_DIR}/cxx-module-scanning"
-B "${_b}"
-G "${CMAKE_GENERATOR}"
"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
"-DCMAKE_CXX_COMPILER=${EMBT_BCC64X}"
${ARGN}
OUTPUT_VARIABLE _out ERROR_VARIABLE _err RESULT_VARIABLE _rc)
set(_rc "${_rc}" PARENT_SCOPE)
set(_log "${_out}${_err}" PARENT_SCOPE)
set(_b "${_b}" PARENT_SCOPE)
endfunction()
# A C++20 target builds without scan steps, also when a scanner is named.
set(_cases plain)
if(EXISTS "${_bindir}/clang-scan-deps.exe")
list(APPEND _cases scanner)
endif()
foreach(_case IN LISTS _cases)
set(_opts "")
if(_case STREQUAL "scanner")
set(_opts
"-DCMAKE_CXX_COMPILER_CLANG_SCAN_DEPS=${_bindir}/clang-scan-deps.exe")
endif()
_scan_configure(${_case} ${_opts})
if(NOT _rc EQUAL 0)
message(SEND_ERROR "configure (${_case}) failed:\n${_log}")
continue()
endif()
execute_process(
COMMAND ${CMAKE_COMMAND} --build "${_b}"
OUTPUT_VARIABLE _out ERROR_VARIABLE _err RESULT_VARIABLE _rc)
if(NOT _rc EQUAL 0)
message(SEND_ERROR "build (${_case}) failed:\n${_out}${_err}")
continue()
endif()
if(CMAKE_GENERATOR MATCHES "Ninja")
file(GLOB _ninja_files "${_b}/build*.ninja")
foreach(_f IN LISTS _ninja_files)
file(STRINGS "${_f}" _steps REGEX "^build [^:]*: CXX_SCAN__")
if(_steps)
message(SEND_ERROR "sources are scanned (${_case}):\n${_steps}")
endif()
endforeach()
endif()
endforeach()
# A target with C++ module sources is rejected.
_scan_configure(module -DWITH_MODULE=ON)
set(_want "does not provide a way to discover the import graph dependencies")
string(REGEX REPLACE "[ \n]+" " " _flat "${_log}")
string(FIND "${_flat}" "${_want}" _pos)
if(_rc EQUAL 0 OR _pos EQUAL -1)
message(SEND_ERROR
"a CXX_MODULES target should be rejected with:\n ${_want}\n"
"Result: ${_rc}\n${_log}")
endif()
@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.28)
project(scantest CXX)
# CMP0155 is NEW, so a C++20 target would be scanned for module dependencies
# if the toolchain provided a scanning rule.
add_library(scantest STATIC scantest.cpp)
target_compile_features(scantest PRIVATE cxx_std_20)
if(WITH_MODULE)
add_library(modtest STATIC)
target_sources(modtest PUBLIC FILE_SET CXX_MODULES FILES modtest.cppm)
target_compile_features(modtest PUBLIC cxx_std_20)
endif()
@@ -0,0 +1,6 @@
export module modtest;
export int modtest()
{
return 0;
}
@@ -0,0 +1,7 @@
#include <iostream>
int scantest()
{
std::cout << "scantest\n";
return 0;
}
@@ -0,0 +1,18 @@
enable_language(C)
function(expect_prop tgt prop want)
get_property(got TARGET ${tgt} PROPERTY ${prop})
if(NOT "${got}" STREQUAL "${want}")
message(SEND_ERROR "${tgt} ${prop}: expected '${want}', got '${got}'")
endif()
endfunction()
# A target created before the variable is set must not inherit it.
add_library(before empty.c)
expect_prop(before EMBT_RUNTIME_LIBRARY "")
# The CMAKE_EMBT_RUNTIME_LIBRARY variable initializes the property at creation.
set(CMAKE_EMBT_RUNTIME_LIBRARY "Dynamic")
add_library(after empty.c)
expect_prop(after EMBT_RUNTIME_LIBRARY "Dynamic")
+4
View File
@@ -0,0 +1,4 @@
int embt_empty(void)
{
return 0;
}
@@ -0,0 +1,45 @@
# One precompiled header, built by a static library and reused by a shared
# library, a module library, a console executable and a GUI executable. bcc64x
# records some target-kind switches in a precompiled header and rejects a
# mismatch (E904), so they must not reach compilation; each target must still
# see its own __DLL__/__CONSOLE__ state. The sources #error on the wrong
# state, so a clean build is the assertion. Driver-gated; a no-op where bcc64x
# is absent.
if(NOT WIN32)
return()
endif()
set(_hints "")
if(DEFINED ENV{TP})
list(APPEND _hints "$ENV{TP}/bin64")
endif()
find_program(EMBT_BCC64X NAMES bcc64x HINTS ${_hints})
if(NOT EMBT_BCC64X)
message(STATUS
"bcc64x not found on PATH or under $TP; PCH reuse check skipped.")
return()
endif()
set(_bin "${CMAKE_CURRENT_BINARY_DIR}/pch-reuse-kinds-build")
file(REMOVE_RECURSE "${_bin}")
execute_process(
COMMAND ${CMAKE_COMMAND}
-S "${CMAKE_CURRENT_SOURCE_DIR}/pch-reuse-kinds"
-B "${_bin}"
-G "${CMAKE_GENERATOR}"
"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
"-DCMAKE_CXX_COMPILER=${EMBT_BCC64X}"
RESULT_VARIABLE _rc OUTPUT_VARIABLE _o ERROR_VARIABLE _e)
if(NOT _rc EQUAL 0)
message(SEND_ERROR "configure failed:\n${_o}${_e}")
return()
endif()
execute_process(
COMMAND ${CMAKE_COMMAND} --build "${_bin}"
RESULT_VARIABLE _rc OUTPUT_VARIABLE _o ERROR_VARIABLE _e)
if(NOT _rc EQUAL 0)
message(SEND_ERROR
"build failed -- a target could not reuse the precompiled header, or saw "
"the wrong __DLL__/__CONSOLE__ state:\n${_o}${_e}")
endif()
@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.23)
project(embt_pch_reuse CXX)
add_library(pch STATIC lib.cpp)
target_precompile_headers(pch PRIVATE pch.h)
add_library(dll SHARED dll.cpp)
add_library(mod MODULE dll.cpp)
add_executable(console console.cpp)
add_executable(gui WIN32 gui.cpp)
foreach(t IN ITEMS dll mod console gui)
target_precompile_headers(${t} REUSE_FROM pch)
endforeach()
@@ -0,0 +1,8 @@
// A console executable must be compiled with __CONSOLE__ and without __DLL__.
#if !defined(__CONSOLE__) || defined(__DLL__)
# error "console target compiled with the wrong __CONSOLE__/__DLL__ state"
#endif
int main()
{
return static_cast<int>(std::string().size());
}
@@ -0,0 +1,8 @@
// A shared or module library must be compiled with __DLL__.
#ifndef __DLL__
# error "DLL compiled without __DLL__"
#endif
__declspec(dllexport) int dll_value()
{
return static_cast<int>(std::string("dll").size());
}
@@ -0,0 +1,8 @@
// A GUI (WIN32) executable must be compiled without __CONSOLE__ and __DLL__.
#if defined(__CONSOLE__) || defined(__DLL__)
# error "GUI target compiled with __CONSOLE__ or __DLL__"
#endif
extern "C" int __stdcall WinMain(void*, void*, char*, int)
{
return static_cast<int>(std::string().size());
}
@@ -0,0 +1,8 @@
// A static library is neither a DLL nor a console target.
#if defined(__DLL__) || defined(__CONSOLE__)
# error "static library compiled with __DLL__ or __CONSOLE__"
#endif
std::string lib_value()
{
return "lib";
}
@@ -0,0 +1 @@
#include <string>
@@ -0,0 +1,107 @@
# EMBT_RUNTIME_LIBRARY must select the linked C++ runtime: Dynamic links the
# shared libc++ (an import of libc++-<ver>.dll), Static (and the default) link it
# statically (no such import). Build a real exe and module library for each
# value with bcc64x and inspect their imports -- a configure-only check cannot
# tell them apart, since the switches only change the link. The module library
# must also be compiled with __DLL__, seen as an export only defined under it.
# The helper project also carries a .rc source, so this guards against the
# runtime-library handling mishandling a non-C/CXX language. Driver-gated: a
# no-op where bcc64x is not installed.
if(NOT WIN32)
return()
endif()
set(_hints "")
if(DEFINED ENV{TP})
list(APPEND _hints "$ENV{TP}/bin64" "$ENV{TP}/bin")
endif()
find_program(EMBT_BCC64X NAMES bcc64x HINTS ${_hints})
if(NOT EMBT_BCC64X)
message(STATUS "bcc64x not found; EMBT_RUNTIME_LIBRARY link check skipped.")
return()
endif()
get_filename_component(_bindir "${EMBT_BCC64X}" DIRECTORY)
find_program(EMBT_OBJDUMP NAMES llvm-objdump HINTS "${_bindir}")
if(NOT EMBT_OBJDUMP)
message(STATUS
"llvm-objdump not found; EMBT_RUNTIME_LIBRARY link check skipped.")
return()
endif()
# "" exercises the default (Static); Static and Dynamic the explicit values.
foreach(_val "" Static Dynamic)
if(_val STREQUAL "")
set(_tag default)
else()
set(_tag "${_val}")
endif()
set(_b "${CMAKE_CURRENT_BINARY_DIR}/rtl-${_tag}")
execute_process(
COMMAND ${CMAKE_COMMAND}
-S "${CMAKE_CURRENT_SOURCE_DIR}/runtime-library"
-B "${_b}"
-G "${CMAKE_GENERATOR}"
"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
"-DCMAKE_CXX_COMPILER=${EMBT_BCC64X}"
"-DRTL_VALUE=${_val}"
OUTPUT_VARIABLE _out ERROR_VARIABLE _err RESULT_VARIABLE _rc)
if(NOT _rc EQUAL 0)
message(SEND_ERROR "configure (${_tag}) failed:\n${_out}${_err}")
continue()
endif()
execute_process(
COMMAND ${CMAKE_COMMAND} --build "${_b}"
OUTPUT_VARIABLE _out ERROR_VARIABLE _err RESULT_VARIABLE _rc)
if(NOT _rc EQUAL 0)
message(SEND_ERROR "build (${_tag}) failed:\n${_out}${_err}")
continue()
endif()
foreach(_art IN ITEMS rtltest.exe rtlmod.dll)
execute_process(
COMMAND "${EMBT_OBJDUMP}" -p "${_b}/${_art}"
OUTPUT_VARIABLE _imports RESULT_VARIABLE _od_rc)
if(NOT _od_rc EQUAL 0)
message(SEND_ERROR
"llvm-objdump (${_tag}, ${_art}) failed:\n${_imports}")
continue()
endif()
if(_imports MATCHES "libc\\+\\+[-0-9]*\\.dll")
set(_dynamic YES)
else()
set(_dynamic NO)
endif()
if(_val STREQUAL "Dynamic" AND NOT _dynamic)
message(SEND_ERROR
"EMBT_RUNTIME_LIBRARY=Dynamic should link the shared libc++, but "
"${_art} has no libc++ import.")
elseif(NOT _val STREQUAL "Dynamic" AND _dynamic)
message(SEND_ERROR
"EMBT_RUNTIME_LIBRARY=${_tag} should link libc++ statically, but "
"${_art} imports the shared libc++.")
endif()
if(_art STREQUAL "rtlmod.dll" AND
NOT _imports MATCHES "rtlmod_has_dll_macro")
message(SEND_ERROR
"The module library (${_tag}) was not compiled with __DLL__.")
endif()
endforeach()
endforeach()
# An unknown value is rejected when generating for the bcc64x toolchain.
execute_process(
COMMAND ${CMAKE_COMMAND}
-S "${CMAKE_CURRENT_SOURCE_DIR}/runtime-library"
-B "${CMAKE_CURRENT_BINARY_DIR}/rtl-Bogus"
-G "${CMAKE_GENERATOR}"
"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
"-DCMAKE_CXX_COMPILER=${EMBT_BCC64X}"
"-DRTL_VALUE=Bogus"
OUTPUT_VARIABLE _out ERROR_VARIABLE _err RESULT_VARIABLE _rc)
set(_want
"EMBT_RUNTIME_LIBRARY value 'Bogus' not known for this CXX compiler.")
string(FIND "${_err}" "${_want}" _pos)
if(_rc EQUAL 0 OR _pos EQUAL -1)
message(SEND_ERROR
"EMBT_RUNTIME_LIBRARY=Bogus should be rejected with:\n ${_want}\n"
"Result: ${_rc}\n${_out}${_err}")
endif()
@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.28)
project(rtltest CXX RC)
# A console exe that uses libc++ (std::string), so the runtime-library choice is
# observable in its imports. It also carries a .rc resource -- which a RAD
# Studio WIN32 app almost always has -- to guard against the runtime-library
# handling mishandling a non-C/CXX language (the RC "compiler" has no runtime
# library). RTL_VALUE is passed as Static, Dynamic, or empty (the default).
add_executable(rtltest main.cpp app.rc)
# A module library takes the same runtime-library choice, and must also be
# compiled with __DLL__ defined.
add_library(rtlmod MODULE module.cpp)
if(NOT RTL_VALUE STREQUAL "")
set_property(TARGET rtltest rtlmod
PROPERTY EMBT_RUNTIME_LIBRARY "${RTL_VALUE}")
endif()
@@ -0,0 +1,4 @@
STRINGTABLE
BEGIN
1 "EMBTClang runtime-library test"
END
@@ -0,0 +1,6 @@
#include <string>
int main()
{
std::string s = "x";
return static_cast<int>(s.size());
}
@@ -0,0 +1,14 @@
#include <string>
extern "C" __declspec(dllexport) int rtlmod_size()
{
std::string s = "x";
return static_cast<int>(s.size());
}
#ifdef __DLL__
extern "C" __declspec(dllexport) int rtlmod_has_dll_macro()
{
return 1;
}
#endif
@@ -0,0 +1,57 @@
# Regression guard for C3: a console target is compiled with __CONSOLE__, which
# -tC defines in the IDE build, not only linked with -tC, and a GUI target is
# not -- and compile uses the same per-config WIN32_EXECUTABLE determination as
# the link, so a genex-valued property cannot make the two disagree. The
# sources #error on the wrong __CONSOLE__ state, so a clean build is the
# assertion. Driver-gated; a no-op where bcc64x is absent.
if(NOT WIN32)
return()
endif()
set(_hints "")
if(DEFINED ENV{TP})
list(APPEND _hints "$ENV{TP}/bin64")
endif()
find_program(EMBT_BCC64X NAMES bcc64x HINTS ${_hints})
if(NOT EMBT_BCC64X)
message(STATUS "bcc64x not found on PATH or under $TP; __CONSOLE__ check skipped.")
return()
endif()
function(_subsystem_build case config)
set(_bin "${CMAKE_CURRENT_BINARY_DIR}/${case}")
file(REMOVE_RECURSE "${_bin}")
set(_cfg_arg "")
if(config)
set(_cfg_arg "-DCMAKE_BUILD_TYPE=${config}")
endif()
execute_process(
COMMAND ${CMAKE_COMMAND}
-S "${CMAKE_CURRENT_SOURCE_DIR}/subsystem-macros"
-B "${_bin}"
-G "${CMAKE_GENERATOR}"
"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
"-DCMAKE_CXX_COMPILER=${EMBT_BCC64X}"
"-DCASE=${case}"
${_cfg_arg}
RESULT_VARIABLE _rc OUTPUT_VARIABLE _o ERROR_VARIABLE _e)
if(NOT _rc EQUAL 0)
message(SEND_ERROR "CASE ${case}: configure failed:\n${_o}${_e}")
return()
endif()
execute_process(
COMMAND ${CMAKE_COMMAND} --build "${_bin}"
RESULT_VARIABLE _rc OUTPUT_VARIABLE _o ERROR_VARIABLE _e)
if(NOT _rc EQUAL 0)
message(SEND_ERROR
"CASE ${case}: build failed -- a target compiled with the wrong "
"__CONSOLE__ state:\n${_o}${_e}")
endif()
endfunction()
# Plain bool: console gets __CONSOLE__, GUI does not.
_subsystem_build(plain "")
# Genex WIN32_EXECUTABLE ($<CONFIG:Release>): GUI in a Release build, so it must
# not get __CONSOLE__ -- catches a non-genex read that would pick console.
_subsystem_build(genex "Release")
@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.23)
project(embt_subsystem CXX)
if(NOT DEFINED CASE OR CASE STREQUAL "plain")
add_executable(console console.cpp) # console (default subsystem)
add_executable(gui WIN32 gui.cpp) # GUI subsystem
elseif(CASE STREQUAL "genex")
# WIN32_EXECUTABLE as a per-config genex: GUI in Release, console otherwise.
# Built in Release, so it is GUI and must NOT see __CONSOLE__ (gui.cpp asserts
# that). A non-genex read of the property would pick console here and define
# __CONSOLE__, tripping the #error -- so this locks compile==link per config.
add_executable(genexgui gui.cpp)
set_target_properties(genexgui PROPERTIES WIN32_EXECUTABLE "$<CONFIG:Release>")
endif()
@@ -0,0 +1,8 @@
// A console target must be compiled with __CONSOLE__.
#ifndef __CONSOLE__
# error "console target compiled without __CONSOLE__"
#endif
int main()
{
return 0;
}
@@ -0,0 +1,8 @@
// A GUI (WIN32) target must NOT be compiled with __CONSOLE__.
#ifdef __CONSOLE__
# error "GUI target compiled with __CONSOLE__"
#endif
extern "C" int __stdcall WinMain(void*, void*, char*, int)
{
return 0;
}
+1 -1
View File
@@ -1133,7 +1133,7 @@ def gen_check_build_system_targets(c, g, inSource):
e["compileGroups"] = apple_exe_framework["compileGroups"]
e["link"] = apple_exe_framework["link"]
if args.cxx_compiler_id in ['Clang', 'AppleClang', 'LCC', 'GNU', 'Intel', 'IntelLLVM', 'DPClang', 'MSVC', 'Embarcadero', 'CrayClang', 'IBMClang'] and g["name"] != "Xcode":
if args.cxx_compiler_id in ['Clang', 'AppleClang', 'LCC', 'GNU', 'Intel', 'IntelLLVM', 'DPClang', 'MSVC', 'Embarcadero', 'CrayClang', 'EMBTClang', 'IBMClang'] and g["name"] != "Xcode":
for e in expected:
if e["name"] == "cxx_exe":
if matches(g["name"], "^(Visual Studio |Ninja Multi-Config)"):
@@ -30,6 +30,8 @@ set(properties
"VS_JUST_MY_CODE_DEBUGGING" "ON" "<SAME>"
### OpenWatcom
"WATCOM_RUNTIME_LIBRARY" "MultiThreaded" "<SAME>"
### Embarcadero
"EMBT_RUNTIME_LIBRARY" "Dynamic" "<SAME>"
## Language
### CUDA
"CUDA_SEPARABLE_COMPILATION" "ON" "<SAME>"
+5 -2
View File
@@ -6,6 +6,10 @@ endif()
project(SetLang)
# force this to be verbose so I can debug a dashboard entry
set(CMAKE_VERBOSE_MAKEFILE 1)
if(CMAKE_C_COMPILER_ID MATCHES "(GNU|LCC|Clang|MSVC|Borland|Embarcadero|Intel|TI|XL)")
set(have_explicit_language 1)
cmake_policy(SET CMP0119 NEW)
endif()
add_library(foo foo.c)
add_executable(SetLang bar.c)
set_source_files_properties(foo.c bar.c PROPERTIES LANGUAGE CXX)
@@ -23,8 +27,7 @@ if(CMAKE_GENERATOR MATCHES "^Visual Studio" AND "x${CMAKE_C_COMPILER_ID}" STREQU
set_property(TARGET stay PROPERTY COMPILE_OPTIONS "-TP")
endif()
if((CMAKE_C_COMPILER_ID MATCHES "(GNU|LCC|Clang|MSVC|Borland|Embarcadero|Intel|TI|XL)"))
cmake_policy(SET CMP0119 NEW)
if(have_explicit_language)
add_library(zoom zoom.zzz)
set_source_files_properties(zoom.zzz PROPERTIES LANGUAGE CXX)
target_link_libraries(SetLang zoom)