From 0ecab33d3bac66b61dfd3e09914eacdbc6d39c88 Mon Sep 17 00:00:00 2001 From: Eugene Talagrand Date: Mon, 16 Feb 2026 01:02:40 -0800 Subject: [PATCH 1/2] Add support for Linux cross-compilation for cswinrt.exe --- .github/workflows/ci.yml | 42 +++++ cross-mingw-toolchain.cmake | 41 ++++ src/Directory.Build.props | 1 - src/Strings.props | 204 -------------------- src/cswinrt.sln | 22 ++- src/cswinrt/CMakeLists.txt | 176 ++++++++++++++++++ src/cswinrt/cmd_reader.h | 29 ++- src/cswinrt/code_writers.h | 40 ++-- src/cswinrt/concurrent_containers.h | 116 ++++++++++++ src/cswinrt/cswinrt.vcxproj | 14 +- src/cswinrt/main.cpp | 31 ++-- src/cswinrt/mingw-support/xmllite.def | 8 + src/cswinrt/mingw-support/xmllite_i386.def | 8 + src/cswinrt/pch.h | 2 +- src/cswinrt/prebuild/CMakeLists.txt | 22 +++ src/cswinrt/prebuild/main.cpp | 206 +++++++++++++++++++++ src/cswinrt/prebuild/pch.cpp | 1 + src/cswinrt/prebuild/pch.h | 3 + src/cswinrt/prebuild/prebuild.vcxproj | 79 ++++++++ src/cswinrt/text_writer.h | 10 + src/cswinrt/version.rc | Bin 1558 -> 0 bytes 21 files changed, 805 insertions(+), 250 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 cross-mingw-toolchain.cmake delete mode 100644 src/Strings.props create mode 100644 src/cswinrt/CMakeLists.txt create mode 100644 src/cswinrt/concurrent_containers.h create mode 100644 src/cswinrt/mingw-support/xmllite.def create mode 100644 src/cswinrt/mingw-support/xmllite_i386.def create mode 100644 src/cswinrt/prebuild/CMakeLists.txt create mode 100644 src/cswinrt/prebuild/main.cpp create mode 100644 src/cswinrt/prebuild/pch.cpp create mode 100644 src/cswinrt/prebuild/pch.h create mode 100644 src/cswinrt/prebuild/prebuild.vcxproj delete mode 100644 src/cswinrt/version.rc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..a0f0d6b26 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build-linux-cross-cswinrt: + name: 'cross: Cross-build from Linux' + strategy: + matrix: + arch: [i686, x86_64] + runs-on: ubuntu-22.04 + env: + CMAKE_COLOR_DIAGNOSTICS: 1 + CLICOLOR_FORCE: 1 + steps: + - uses: actions/checkout@v6 + + - name: Install cross compiler + run: | + arch=${{ matrix.arch }} + sudo apt-get install g++-mingw-w64-${arch/_/-} + sudo update-alternatives --set "${{ matrix.arch }}-w64-mingw32-gcc" "/usr/bin/${{ matrix.arch }}-w64-mingw32-gcc-posix" + sudo update-alternatives --set "${{ matrix.arch }}-w64-mingw32-g++" "/usr/bin/${{ matrix.arch }}-w64-mingw32-g++-posix" + + - name: Cross-build cswinrt + run: | + cmake -S src/cswinrt/ -B build/cross/ --toolchain cross-mingw-toolchain.cmake \ + -DCMAKE_SYSTEM_PROCESSOR=${{ matrix.arch }} \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_CXX_FLAGS="-static" \ + -DCMAKE_INSTALL_PREFIX=$PWD/install/ + cmake --build build/cross/ --target install -j2 + + - name: Upload cswinrt.exe + uses: actions/upload-artifact@v6 + with: + name: cross-build-${{ matrix.arch }}-bin + path: install/bin/cswinrt.exe diff --git a/cross-mingw-toolchain.cmake b/cross-mingw-toolchain.cmake new file mode 100644 index 000000000..44c6f4f66 --- /dev/null +++ b/cross-mingw-toolchain.cmake @@ -0,0 +1,41 @@ +# This is a cmake-toolchain(5) file that can be used to cross-build +# cswinrt.exe from Linux or other operating systems using a mingw-w64 cross +# toolchain. This should work with both GCC-based and llvm-mingw toolchains. +# +# Example usage with toolchain installed system-wide: +# +# $ cmake -S src/cswinrt/ -B build/cross_x64/ \ +# --toolchain cross-mingw-toolchain.cmake \ +# -DCMAKE_BUILD_TYPE=RelWithDebInfo \ +# -DCMAKE_CXX_FLAGS="-static" \ +# -DCMAKE_INSTALL_PREFIX=$PWD/install/ +# +# Example usage with external toolchain: +# +# $ cmake -S src/cswinrt/ -B build/cross_x64/ \ +# --toolchain cross-mingw-toolchain.cmake \ +# -DMINGW_BIN_PATH=/opt/llvm-mingw/bin \ +# -DCMAKE_BUILD_TYPE=RelWithDebInfo \ +# -DCMAKE_INSTALL_PREFIX=$PWD/install/ + +set(CMAKE_SYSTEM_NAME Windows) +if(NOT DEFINED CMAKE_SYSTEM_PROCESSOR) + set(CMAKE_SYSTEM_PROCESSOR x86_64) +endif() + +set(TOOLCHAIN_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32) + +if(DEFINED MINGW_BIN_PATH) + set(TOOLCHAIN_PREFIX "${MINGW_BIN_PATH}/${TOOLCHAIN_PREFIX}") +endif() + +set(CMAKE_C_COMPILER "${TOOLCHAIN_PREFIX}-gcc") +set(CMAKE_CXX_COMPILER "${TOOLCHAIN_PREFIX}-g++") +set(CMAKE_RC_COMPILER "${TOOLCHAIN_PREFIX}-windres") + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +unset(TOOLCHAIN_PREFIX) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 68e75c941..3b48b39f5 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -124,7 +124,6 @@ - diff --git a/src/Strings.props b/src/Strings.props deleted file mode 100644 index 58b6aadc3..000000000 --- a/src/Strings.props +++ /dev/null @@ -1,204 +0,0 @@ - - - - - UpdateStringSources;UpdateStringsHeader;$(PrepareForBuildDependsOn) - - - AddStringSources;$(ComputeCompileInputsTargets) - - - - - - - - - - - - GetContentLines(string file) - { - yield return "// This file was generated by the build."; - yield return "#include \"pch.h\""; - yield return "namespace " + Namespace + "::strings {"; - yield return "extern char const " + global + "[] = R\"xyz("; - int count = 0; - foreach(string line in File.ReadLines(file)) - { - count += line.Length + 2; - if(count > 16000) - { - yield return ")xyz\" R\"xyz(" + line; - count = line.Length; - } - else - { - yield return line; - } - } - yield return ")xyz\";"; - yield return "}"; - } - } -]]> - - - - - - - -
- - - - - - (); - lines.Add("// " + Namespace + " static string declarations. This file was generated by the build."); - lines.Add("#pragma once"); - lines.Add("namespace " + Namespace + "::strings"); - lines.Add("{"); - foreach(var index in Input.GroupBy(item => item.GetMetadata("Index"))) - { - foreach(ITaskItem item in index) - { - var content = item.GetMetadata("Content"); - var ns = item.GetMetadata("Namespace"); - var file = item.GetMetadata("File"); - var global = file.Replace('.', '_'); - if (!string.IsNullOrEmpty(ns)) - { - global = ns.Replace('.', '_') + "_" + global; - } - var fileSize = new FileInfo(content).Length; - lines.Add(" extern char const " + global + "[" + fileSize + "];"); - } - lines.Add(" static const struct"); - lines.Add(" {"); - lines.Add(" char const* name;"); - lines.Add(" char const* value;"); - lines.Add(" } " + (index.Key == "" ? "base" : index.Key.Trim('\\')) +"[] = {"); - var delimeter = ""; - foreach(ITaskItem item in index) - { - var ns = item.GetMetadata("Namespace"); - var file = item.GetMetadata("File"); - var key = ns; - if (string.IsNullOrEmpty(key)) - { - key = file; - } - var global = file.Replace('.', '_'); - if (!string.IsNullOrEmpty(ns)) - { - global = ns.Replace('.', '_') + "_" + global; - } - lines.Add(" " + delimeter + "{\"" + key + "\", " + global + "}"); - delimeter = ", "; - } - lines.Add(" };"); - } - lines.Add("}"); - if (File.Exists(Header)) - { - var existing = File.ReadAllLines(Header); - if(existing.SequenceEqual(lines)) - { - Log.LogMessage(MessageImportance.Low, "{0} already up-to-date", Header); - return true; - } - } - Log.LogMessage(MessageImportance.High, "Writing {0}", Header); - Directory.CreateDirectory(new FileInfo(Header).DirectoryName); - File.WriteAllLines(Header, lines); -]]> - - - - - - - - - %(Identity) - $([System.String]::new('$(IntDir)strings\%(RecursiveDir)%(Filename).cpp')) - $([System.String]::new('%(Filename)')) - - - - - - - %(RecursiveDir) - $([System.IO.Path]::GetDirectoryName($([System.IO.Path]::GetDirectoryName($([System.String]::new('%(RecursiveDir)')))))) - $([System.IO.Path]::GetFileName($([System.IO.Path]::GetDirectoryName($([System.String]::new('%(RecursiveDir)')))))) - - - - - - - - - - - - - - - - - - - - @(StaticStringDirs,'|') - - - - - - - %(AdditionalIncludeDirectories);$(IntDir) - - - - - diff --git a/src/cswinrt.sln b/src/cswinrt.sln index c9313f30c..c5a23aded 100644 --- a/src/cswinrt.sln +++ b/src/cswinrt.sln @@ -13,7 +13,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "Tests\UnitTest\ {AE3B0611-2FBB-42AB-A245-B4E79868A5F9} = {AE3B0611-2FBB-42AB-A245-B4E79868A5F9} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "prebuild", "cswinrt\prebuild\prebuild.vcxproj", "{9F6A959A-9F34-43BD-99E7-DF816A5D6904}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cswinrt", "cswinrt\cswinrt.vcxproj", "{6ACFD2B2-E8AA-4CD4-AAD8-213CE8BB2637}" + ProjectSection(ProjectDependencies) = postProject + {9F6A959A-9F34-43BD-99E7-DF816A5D6904} = {9F6A959A-9F34-43BD-99E7-DF816A5D6904} + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Files", "Files", "{96495AB4-2D86-47D1-A174-27D0B436DC98}" ProjectSection(SolutionItems) = preProject @@ -25,8 +30,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Files", "Files", "{96495AB4 get_testwinrt.cmd = get_testwinrt.cmd cswinrt\PreviousPlatforms.linq = cswinrt\PreviousPlatforms.linq README.md = README.md - Strings.props = Strings.props - EndProjectSection +EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestComponent", "TestWinRT\TestComponent\TestComponent.vcxproj", "{2954F343-85A7-46F5-A3F3-F106FDD13900}" ProjectSection(ProjectDependencies) = postProject @@ -217,6 +221,20 @@ Global {9A9F52CA-F624-43A4-B5EF-C50861F584C2}.Release|x64.Build.0 = Release|x64 {9A9F52CA-F624-43A4-B5EF-C50861F584C2}.Release|x86.ActiveCfg = Release|x86 {9A9F52CA-F624-43A4-B5EF-C50861F584C2}.Release|x86.Build.0 = Release|x86 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Debug|ARM.ActiveCfg = Debug|Win32 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Debug|ARM64.ActiveCfg = Debug|Win32 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Debug|ARM64.Build.0 = Debug|Win32 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Debug|x64.ActiveCfg = Debug|x64 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Debug|x64.Build.0 = Debug|x64 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Debug|x86.ActiveCfg = Debug|Win32 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Debug|x86.Build.0 = Debug|Win32 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Release|ARM.ActiveCfg = Release|Win32 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Release|ARM64.ActiveCfg = Release|Win32 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Release|ARM64.Build.0 = Release|Win32 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Release|x64.ActiveCfg = Release|x64 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Release|x64.Build.0 = Release|x64 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Release|x86.ActiveCfg = Release|Win32 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904}.Release|x86.Build.0 = Release|Win32 {6ACFD2B2-E8AA-4CD4-AAD8-213CE8BB2637}.Debug|ARM.ActiveCfg = Debug|Win32 {6ACFD2B2-E8AA-4CD4-AAD8-213CE8BB2637}.Debug|ARM64.ActiveCfg = Debug|Win32 {6ACFD2B2-E8AA-4CD4-AAD8-213CE8BB2637}.Debug|ARM64.Build.0 = Debug|Win32 diff --git a/src/cswinrt/CMakeLists.txt b/src/cswinrt/CMakeLists.txt new file mode 100644 index 000000000..a805ac70c --- /dev/null +++ b/src/cswinrt/CMakeLists.txt @@ -0,0 +1,176 @@ +# CMake build for cswinrt.exe (the C#/WinRT code generator). +# +# This supports building natively on Windows with MinGW, or cross-building +# from Linux for Windows using a mingw-w64 toolchain: +# +# cmake -S . -B build/ --toolchain ../../cross-mingw-toolchain.cmake \ +# -DCMAKE_BUILD_TYPE=RelWithDebInfo \ +# -DCMAKE_INSTALL_PREFIX=$PWD/install/ +# cmake --build build/ --target install -j$(nproc) +# +# The existing cswinrt.vcxproj / MSBuild flow is unaffected by this file. + +cmake_minimum_required(VERSION 3.16) + +project(cswinrt LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +set(CSWINRT_BUILD_VERSION "0.0.0-private.0" CACHE STRING "The version string used for cswinrt.") +message(STATUS "Using version string: ${CSWINRT_BUILD_VERSION}") + +if(WIN32) + add_compile_definitions(_WIN32_WINNT=0x0602) +endif() + + +# === prebuild: Generator tool for strings.h, strings.cpp, version.rc === + +if(CMAKE_CROSSCOMPILING) + include(ExternalProject) + ExternalProject_Add(cswinrt-prebuild + SOURCE_DIR "${PROJECT_SOURCE_DIR}/prebuild" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX= + "-DCSWINRT_BUILD_VERSION=${CSWINRT_BUILD_VERSION}" + ) + ExternalProject_Get_Property(cswinrt-prebuild INSTALL_DIR) + set(PREBUILD_TOOL "${INSTALL_DIR}/bin/cswinrt-prebuild") + unset(INSTALL_DIR) +else() + add_subdirectory(prebuild) + set(PREBUILD_TOOL cswinrt-prebuild) +endif() + + +# === Step to create autogenerated files === + +file(GLOB_RECURSE PREBUILD_STRING_FILES + LIST_DIRECTORIES false + CONFIGURE_DEPENDS + strings/*.cs +) +add_custom_command( + OUTPUT + ${PROJECT_BINARY_DIR}/strings.h + ${PROJECT_BINARY_DIR}/strings.cpp + ${PROJECT_BINARY_DIR}/version.rc + COMMAND "${PREBUILD_TOOL}" ARGS "${PROJECT_SOURCE_DIR}/strings" "${PROJECT_BINARY_DIR}" + DEPENDS + cswinrt-prebuild + ${PREBUILD_STRING_FILES} + VERBATIM +) + + +# === cswinrt executable === + +set(CSWINRT_SRCS + main.cpp + "${PROJECT_BINARY_DIR}/strings.cpp" +) + +set(CSWINRT_HEADERS + pch.h + cmd_reader.h + code_writers.h + concurrent_containers.h + helpers.h + settings.h + task_group.h + text_writer.h + type_writers.h +) + +if(WIN32) + set(CSWINRT_RESOURCES + "${PROJECT_BINARY_DIR}/version.rc" + ) +endif() + +add_executable(cswinrt ${CSWINRT_SRCS} ${CSWINRT_RESOURCES} ${CSWINRT_HEADERS}) +target_compile_definitions(cswinrt PRIVATE VERSION_STRING="${CSWINRT_BUILD_VERSION}" VERSION_NUMBER="0.0.0.0") +target_include_directories(cswinrt PRIVATE ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR}) + +if(WIN32) + target_link_libraries(cswinrt shlwapi advapi32) +endif() + +install(TARGETS cswinrt) + + +# === winmd: External header-only library for reading winmd files === + +set(EXTERNAL_WINMD_INCLUDE_DIR "" CACHE PATH "Path to the include dir of the winmd library headers. Leave empty to download via ExternalProject.") + +if(EXTERNAL_WINMD_INCLUDE_DIR STREQUAL "") + message(STATUS "The winmd library will be downloaded using ExternalProject.") + include(ExternalProject) + ExternalProject_Add(winmd + GIT_REPOSITORY https://github.com/microsoft/winmd.git + GIT_TAG 0f1eae3bfa63fa2ba3c2912cbfe72a01db94cc5a + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + UPDATE_COMMAND "" + ) + add_dependencies(cswinrt winmd) + ExternalProject_Get_Property(winmd SOURCE_DIR) + set(winmd_INCLUDE_DIR "${SOURCE_DIR}/src") +else() + message(STATUS "Using winmd library headers at ${EXTERNAL_WINMD_INCLUDE_DIR}") + set(winmd_INCLUDE_DIR "${EXTERNAL_WINMD_INCLUDE_DIR}") +endif() +target_include_directories(cswinrt PRIVATE "${winmd_INCLUDE_DIR}") + + +# === xmllite import lib for MinGW === + +if(WIN32 AND MINGW) + function(TestLinkXmlLite OUTPUT_VARNAME) + include(CheckCXXSourceCompiles) + set(CMAKE_REQUIRED_LIBRARIES xmllite) + check_cxx_source_compiles(" +#include +int main() { + CreateXmlReader(__uuidof(IXmlReader), nullptr, nullptr); +} + " ${OUTPUT_VARNAME}) + endfunction() + + function(TestIsI386 OUTPUT_VARNAME) + include(CheckCXXSourceCompiles) + check_cxx_source_compiles(" +#if !defined(__i386__) && !defined(_M_IX86) +# error Not i386 +#endif +int main() {} + " ${OUTPUT_VARNAME}) + endfunction() + + set(XMLLITE_LIBRARY xmllite) + TestLinkXmlLite(HAS_LIBXMLLITE) + if(NOT HAS_LIBXMLLITE) + TestIsI386(TARGET_IS_I386) + if(TARGET_IS_I386) + set(XMLLITE_DEF_FILE xmllite_i386) + else() + set(XMLLITE_DEF_FILE xmllite) + endif() + include(CMakeFindBinUtils) + add_custom_command( + OUTPUT + "${PROJECT_BINARY_DIR}/libxmllite.a" + COMMAND "${CMAKE_DLLTOOL}" -k -d "${PROJECT_SOURCE_DIR}/mingw-support/${XMLLITE_DEF_FILE}.def" -l "${PROJECT_BINARY_DIR}/libxmllite.a" + DEPENDS "${PROJECT_SOURCE_DIR}/mingw-support/${XMLLITE_DEF_FILE}.def" + VERBATIM + ) + add_custom_target(gen-libxmllite + DEPENDS "${PROJECT_BINARY_DIR}/libxmllite.a" + ) + set(XMLLITE_LIBRARY "${PROJECT_BINARY_DIR}/libxmllite.a") + add_dependencies(cswinrt gen-libxmllite) + endif() + target_link_libraries(cswinrt "${XMLLITE_LIBRARY}") +endif() diff --git a/src/cswinrt/cmd_reader.h b/src/cswinrt/cmd_reader.h index edc406c9a..dd8b5b309 100644 --- a/src/cswinrt/cmd_reader.h +++ b/src/cswinrt/cmd_reader.h @@ -12,12 +12,16 @@ #include #include #include -#include + +#if defined(_WIN32) || defined(_WIN64) +#include #include -#include +#include +#endif namespace cswinrt { +#if defined(_WIN32) || defined(_WIN64) struct registry_key { HKEY handle{}; @@ -279,6 +283,7 @@ namespace cswinrt return result; } +#endif /* defined(_WIN32) || defined(_WIN64) */ [[noreturn]] inline void throw_invalid(std::string const& message) { @@ -296,7 +301,7 @@ namespace cswinrt { static constexpr uint32_t no_min = 0; static constexpr uint32_t no_max = std::numeric_limits::max(); - + std::string_view name; uint32_t min{ no_min }; uint32_t max{ no_max }; @@ -444,6 +449,7 @@ namespace cswinrt } if (path == "local") { +#if defined(_WIN32) || defined(_WIN64) std::array local{}; #ifdef _WIN64 ExpandEnvironmentStringsA("%windir%\\System32\\WinMetadata", local.data(), static_cast(local.size())); @@ -451,6 +457,9 @@ namespace cswinrt ExpandEnvironmentStringsA("%windir%\\SysNative\\WinMetadata", local.data(), static_cast(local.size())); #endif add_directory(local.data()); +#else /* defined(_WIN32) || defined(_WIN64) */ + throw_invalid("Spec '", path, "' not supported outside of Windows"); +#endif /* defined(_WIN32) || defined(_WIN64) */ continue; } @@ -458,7 +467,11 @@ namespace cswinrt if (path == "sdk" || path == "sdk+") { +#if defined(_WIN32) || defined(_WIN64) sdk_version = get_sdk_version(); +#else /* defined(_WIN32) || defined(_WIN64) */ + throw_invalid("Spec '", path, "' not supported outside of Windows"); +#endif /* defined(_WIN32) || defined(_WIN64) */ } else { @@ -473,6 +486,7 @@ namespace cswinrt if (!sdk_version.empty()) { +#if defined(_WIN32) || defined(_WIN64) auto sdk_path = get_sdk_path(); auto xml_path = sdk_path; xml_path /= L"Platforms\\UAP"; @@ -493,6 +507,9 @@ namespace cswinrt add_files_from_xml(files, sdk_version, xml_path, sdk_path); } +#else /* defined(_WIN32) || defined(_WIN64) */ + throw_invalid("Spec '", path, "' not supported outside of Windows"); +#endif /* defined(_WIN32) || defined(_WIN64) */ continue; } @@ -534,7 +551,11 @@ namespace cswinrt template void extract_option(std::string_view arg, O const& options, L& last) { - if (arg[0] == '-' || arg[0] == '/') + if (arg[0] == '-' +#if defined(_WIN32) || defined(_WIN64) + || arg[0] == '/' +#endif + ) { arg.remove_prefix(1); last = find(options, arg); diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index 9971ae62c..8ae631482 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -5,8 +5,8 @@ #include #include #include -#include -#include +#include +#include "concurrent_containers.h" #define INSPECTABLE_METHOD_COUNT 6 @@ -36,7 +36,7 @@ namespace cswinrt {"string", "String"}, }; - static concurrency::concurrent_unordered_set generic_type_instances; + static cswinrt::concurrent_unordered_set generic_type_instances; generic_type_instance ConvertGenericTypeInstanceToConcreteType(writer& w, const generic_type_instance& generic_instance); auto to_csharp_type(fundamental_type type) @@ -6296,7 +6296,7 @@ return (eventSource.Subscribe, eventSource.Unsubscribe); auto get_managed_marshalers(writer& w, method_signature const& signature, bool /*is_generic*/, bool is_generic_instantiation_class) { std::vector marshalers; - concurrency::concurrent_unordered_set generic_instantiations; + std::unordered_set generic_instantiations; std::function set_marshaler = [&](writer& w, type_semantics const& semantics, managed_marshaler& m) @@ -10363,7 +10363,7 @@ bind(invokeMethodSig)); }); } - void write_temp_class_event_source_subclass(writer& w, TypeDef const& classType, concurrency::concurrent_unordered_map& typeNameToDefinitionMap) + void write_temp_class_event_source_subclass(writer& w, TypeDef const& classType, cswinrt::concurrent_unordered_map& typeNameToDefinitionMap) { for (auto&& ii : classType.InterfaceImpl()) { @@ -10374,24 +10374,24 @@ bind(invokeMethodSig)); auto&& eventTypeSemantics = get_type_semantics(eventObj.EventType()); auto&& eventTypeCode = w.write_temp("%", bind(eventTypeSemantics, typedef_name_type::Projected, false)); auto&& eventClass = w.write_temp("%", bind(eventTypeSemantics)); - typeNameToDefinitionMap[eventTypeCode] = eventClass; + typeNameToDefinitionMap.insert_or_assign(eventTypeCode, eventClass); } }); } } - void write_temp_interface_event_source_subclass(writer& w, TypeDef const& interfaceType, concurrency::concurrent_unordered_map& typeNameToDefinitionMap) + void write_temp_interface_event_source_subclass(writer& w, TypeDef const& interfaceType, cswinrt::concurrent_unordered_map& typeNameToDefinitionMap) { for (auto&& eventObj : interfaceType.EventList()) { auto&& eventTypeSemantics = get_type_semantics(eventObj.EventType()); auto&& eventTypeCode = w.write_temp("%", bind(eventTypeSemantics, typedef_name_type::Projected, false)); auto&& eventClass = w.write_temp("%", bind(eventTypeSemantics)); - typeNameToDefinitionMap[eventTypeCode] = eventClass; + typeNameToDefinitionMap.insert_or_assign(eventTypeCode, eventClass); } } - void add_base_type_entry(TypeDef const& classType, concurrency::concurrent_unordered_map& typeNameToBaseTypeMap) + void add_base_type_entry(TypeDef const& classType, cswinrt::concurrent_unordered_map& typeNameToBaseTypeMap) { writer w(""); auto base_type = get_type_semantics(classType.Extends()); @@ -10401,11 +10401,11 @@ bind(invokeMethodSig)); int numChars = (int)strlen("global::"); auto&& typeName = w.write_temp("%", bind(classType, typedef_name_type::Projected, true)).substr(numChars); auto&& baseTypeName = w.write_temp("%", bind(base_type, typedef_name_type::Projected, true)).substr(numChars); - typeNameToBaseTypeMap[typeName] = baseTypeName; + typeNameToBaseTypeMap.insert_or_assign(typeName, baseTypeName); } } - void add_metadata_type_entry(TypeDef const& type, concurrency::concurrent_unordered_map& authoredTypeNameToMetadataTypeNameMap) + void add_metadata_type_entry(TypeDef const& type, cswinrt::concurrent_unordered_map& authoredTypeNameToMetadataTypeNameMap) { if (settings.component) { @@ -10418,7 +10418,7 @@ bind(invokeMethodSig)); writer w(""); auto&& typeName = w.write_temp("%", bind(type, typedef_name_type::Projected, true)); auto&& metadataTypeName = w.write_temp("%", bind(type, typedef_name_type::CCW, true)); - authoredTypeNameToMetadataTypeNameMap[typeName] = metadataTypeName; + authoredTypeNameToMetadataTypeNameMap.insert_or_assign(typeName, metadataTypeName); } } @@ -10453,7 +10453,7 @@ bind(invokeMethodSig)); }); } - void add_abi_delegates_for_type(std::string_view typeNamespace, std::string_view typeName, std::vector generics, concurrency::concurrent_unordered_set& abiDelegateEntries) + void add_abi_delegates_for_type(std::string_view typeNamespace, std::string_view typeName, std::vector generics, cswinrt::concurrent_unordered_set& abiDelegateEntries) { writer w; if (typeNamespace == "Windows.Foundation" || typeNamespace == "Windows.Foundation.Collections") @@ -10751,7 +10751,7 @@ bind(invokeMethodSig)); } } - void add_if_generic_type_reference(cswinrt::type_semantics const& typeSemantics, bool isArray, concurrency::concurrent_unordered_set& abiDelegateEntries) + void add_if_generic_type_reference(cswinrt::type_semantics const& typeSemantics, bool isArray, cswinrt::concurrent_unordered_set& abiDelegateEntries) { call(typeSemantics, [&](generic_type_instance const& type) @@ -10800,7 +10800,7 @@ bind(invokeMethodSig)); ); } - void add_generic_type_references_in_interface_type(TypeDef const& interfaceType, concurrency::concurrent_unordered_set& abiDelegateEntries) + void add_generic_type_references_in_interface_type(TypeDef const& interfaceType, cswinrt::concurrent_unordered_set& abiDelegateEntries) { for (auto&& method : interfaceType.MethodList()) { @@ -10835,7 +10835,7 @@ bind(invokeMethodSig)); } } - void add_generic_type_references_in_type(TypeDef const& type, concurrency::concurrent_unordered_set& abiDelegateEntries) + void add_generic_type_references_in_type(TypeDef const& type, cswinrt::concurrent_unordered_set& abiDelegateEntries) { switch (get_category(type)) { @@ -11050,20 +11050,18 @@ var ThisPtr = _obj.ThisPtr; // Go through all the generic types and write instantiation classes for them. // While writing them, their implementations might also reference generic types // which may also need instantiation classes if one hasn't been generated already. - concurrency::concurrent_unordered_set written_generic_type_instances; + std::unordered_set written_generic_type_instances; bool types_written = true; while (generic_type_instances.size() != 0 && types_written) { types_written = false; - concurrency::concurrent_unordered_set current_generic_type_instances = generic_type_instances; - generic_type_instances = concurrency::concurrent_unordered_set(); + auto current_generic_type_instances = generic_type_instances.consume(); for (auto& instance : current_generic_type_instances) { - if (written_generic_type_instances.find(instance) != written_generic_type_instances.end()) + if (!written_generic_type_instances.insert(instance).second) { continue; } - written_generic_type_instances.insert(instance); types_written = true; std::vector rcwFunctions, vtableFunctions; diff --git a/src/cswinrt/concurrent_containers.h b/src/cswinrt/concurrent_containers.h new file mode 100644 index 000000000..1fdb8b3d6 --- /dev/null +++ b/src/cswinrt/concurrent_containers.h @@ -0,0 +1,116 @@ +#pragma once + +// Concurrent containers for fork-join parallel patterns. +// +// During the parallel phase, multiple threads insert data concurrently. +// The API deliberately never returns iterators, references, or pointers +// to internal data — these would be invalidated by rehash on the next +// concurrent insert. +// +// After the fork-join barrier, the owner calls consume() +// which atomically moves the internal data into a plain std container, +// transferring sole ownership to the caller. The caller can then iterate, +// read, etc. on the returned container with no thread-safety concerns. +// +// This pattern makes iterator-invalidation bugs structurally impossible: +// - Concurrent phase: no iterators/references exposed. +// - Sequential phase: plain std container, no concurrent modification. + +#include +#include +#include + +namespace cswinrt +{ + // ----------------------------------------------------------------------- + // concurrent_unordered_map + // + // Concurrent: insert_or_assign, empty, size + // Phase-transition: consume (moves data out, resets container) + // ----------------------------------------------------------------------- + template, typename E = std::equal_to> + class concurrent_unordered_map + { + public: + concurrent_unordered_map() = default; + + void insert_or_assign(K const& key, V const& value) + { + std::lock_guard lock(m_mutex); + m_data.insert_or_assign(key, value); + } + + bool empty() const + { + std::lock_guard lock(m_mutex); + return m_data.empty(); + } + + size_t size() const + { + std::lock_guard lock(m_mutex); + return m_data.size(); + } + + // Atomically move all data out and reset. Returns a plain + // std::unordered_map that the caller owns exclusively. + std::unordered_map consume() + { + std::lock_guard lock(m_mutex); + return std::move(m_data); + } + + private: + std::unordered_map m_data; + mutable std::mutex m_mutex; + }; + + // ----------------------------------------------------------------------- + // concurrent_unordered_set + // + // Concurrent: insert, empty, size + // Phase-transition: consume (moves data out, resets container) + // ----------------------------------------------------------------------- + template, typename E = std::equal_to> + class concurrent_unordered_set + { + public: + concurrent_unordered_set() = default; + + void insert(T const& value) + { + std::lock_guard lock(m_mutex); + m_data.insert(value); + } + + void insert(T&& value) + { + std::lock_guard lock(m_mutex); + m_data.insert(std::move(value)); + } + + bool empty() const + { + std::lock_guard lock(m_mutex); + return m_data.empty(); + } + + size_t size() const + { + std::lock_guard lock(m_mutex); + return m_data.size(); + } + + // Atomically move all data out and reset. Returns a plain + // std::unordered_set that the caller owns exclusively. + std::unordered_set consume() + { + std::lock_guard lock(m_mutex); + return std::move(m_data); + } + + private: + std::unordered_set m_data; + mutable std::mutex m_mutex; + }; +} diff --git a/src/cswinrt/cswinrt.vcxproj b/src/cswinrt/cswinrt.vcxproj index e297886a6..954143767 100644 --- a/src/cswinrt/cswinrt.vcxproj +++ b/src/cswinrt/cswinrt.vcxproj @@ -1,4 +1,4 @@ - + @@ -48,11 +48,16 @@ true + %(AdditionalIncludeDirectories);$(OutDir) + + $(BuildOutDir)prebuild\bin\prebuild.exe strings $(OutDir) + + @@ -61,13 +66,16 @@ + + NotUsing + Create - + @@ -137,4 +145,4 @@ - \ No newline at end of file + diff --git a/src/cswinrt/main.cpp b/src/cswinrt/main.cpp index 0a2241eb1..9bcd97b1e 100644 --- a/src/cswinrt/main.cpp +++ b/src/cswinrt/main.cpp @@ -4,13 +4,12 @@ #include "helpers.h" #include "type_writers.h" #include "code_writers.h" -#include -#include +#include "concurrent_containers.h" namespace cswinrt { using namespace std::literals; - using namespace std::experimental::filesystem; + using namespace std::filesystem; using namespace winmd::reader; inline auto get_start_time() @@ -195,8 +194,8 @@ Where is one or more of: task_group group; - concurrency::concurrent_unordered_map typeNameToEventDefinitionMap, typeNameToBaseTypeMap, authoredTypeNameToMetadataTypeNameMap; - concurrency::concurrent_unordered_set abiDelegateEntries; + cswinrt::concurrent_unordered_map typeNameToEventDefinitionMap, typeNameToBaseTypeMap, authoredTypeNameToMetadataTypeNameMap; + cswinrt::concurrent_unordered_set abiDelegateEntries; bool projectionFileWritten = false; for (auto&& ns_members : c.namespaces()) { @@ -376,17 +375,19 @@ Where is one or more of: group.get(); + auto eventDefinitions = typeNameToEventDefinitionMap.consume(); writer eventHelperWriter("WinRT"); write_file_header(eventHelperWriter); eventHelperWriter.write("using System;\nnamespace WinRT\n{\n%\n}", bind([&](writer& w) { - for (auto&& [key, value] : typeNameToEventDefinitionMap) + for (auto&& [key, value] : eventDefinitions) { w.write("%", value); } })); eventHelperWriter.flush_to_file(settings.output_folder / "WinRTEventHelpers.cs"); - if (!typeNameToBaseTypeMap.empty()) + auto baseTypes = typeNameToBaseTypeMap.consume(); + if (!baseTypes.empty()) { writer baseTypeWriter("WinRT"); write_file_header(baseTypeWriter); @@ -406,9 +407,9 @@ ComWrappersSupport.RegisterProjectionTypeBaseTypeMapping(TypeNameToBaseTypeNameM } } })", -typeNameToBaseTypeMap.size(), +baseTypes.size(), bind([&](writer& w) { - for (auto&& [key, value] : typeNameToBaseTypeMap) + for (auto&& [key, value] : baseTypes) { w.write(R"(["%"] = "%",)", key, value); w.write("\n"); @@ -417,7 +418,8 @@ bind([&](writer& w) { baseTypeWriter.flush_to_file(settings.output_folder / "WinRTBaseTypeMappingHelper.cs"); } - if (!abiDelegateEntries.empty() && settings.netstandard_compat) + auto abiDelegates = abiDelegateEntries.consume(); + if (!abiDelegates.empty() && settings.netstandard_compat) { writer baseTypeWriter("WinRT"); write_file_header(baseTypeWriter); @@ -439,7 +441,7 @@ internal static void InitializeAbiDelegates() } })", bind([&](writer& w) { - for (auto&& entry : abiDelegateEntries) + for (auto&& entry : abiDelegates) { w.write("Projections.RegisterAbiDelegate(%, typeof(%));\n", entry.abi_delegate_types, entry.abi_delegate_name); } @@ -450,7 +452,7 @@ internal static void InitializeAbiDelegates() } }), bind([&](writer& w) { - for (auto&& entry : abiDelegateEntries) + for (auto&& entry : abiDelegates) { w.write("%\n", entry.abi_delegate_declaration); } @@ -480,7 +482,8 @@ namespace WinRT.GenericTypeInstantiations genericTypeInstantiationWriter.flush_to_file(settings.output_folder / "WinRTGenericTypeInstantiations.cs"); } - if (!authoredTypeNameToMetadataTypeNameMap.empty() && settings.component) + auto authoredTypes = authoredTypeNameToMetadataTypeNameMap.consume(); + if (!authoredTypes.empty() && settings.component) { writer metadataMappingTypeWriter("WinRT"); write_file_header(metadataMappingTypeWriter); @@ -509,7 +512,7 @@ ComWrappersSupport.RegisterAuthoringMetadataTypeLookup(new Func(GetM } })", bind([&](writer& w) { - for (auto&& [key, value] : authoredTypeNameToMetadataTypeNameMap) + for (auto&& [key, value] : authoredTypes) { w.write(R"(Type _ when type == typeof(%) => typeof(%),)", key, value); w.write("\n"); diff --git a/src/cswinrt/mingw-support/xmllite.def b/src/cswinrt/mingw-support/xmllite.def new file mode 100644 index 000000000..c96291d16 --- /dev/null +++ b/src/cswinrt/mingw-support/xmllite.def @@ -0,0 +1,8 @@ +LIBRARY "XmlLite.dll" +EXPORTS +CreateXmlReader +CreateXmlReaderInputWithEncodingCodePage +CreateXmlReaderInputWithEncodingName +CreateXmlWriter +CreateXmlWriterOutputWithEncodingCodePage +CreateXmlWriterOutputWithEncodingName diff --git a/src/cswinrt/mingw-support/xmllite_i386.def b/src/cswinrt/mingw-support/xmllite_i386.def new file mode 100644 index 000000000..56b63b713 --- /dev/null +++ b/src/cswinrt/mingw-support/xmllite_i386.def @@ -0,0 +1,8 @@ +LIBRARY "XmlLite.dll" +EXPORTS +CreateXmlReader@12 +CreateXmlReaderInputWithEncodingCodePage@20 +CreateXmlReaderInputWithEncodingName@20 +CreateXmlWriter@12 +CreateXmlWriterOutputWithEncodingCodePage@12 +CreateXmlWriterOutputWithEncodingName@12 diff --git a/src/cswinrt/pch.h b/src/cswinrt/pch.h index da7380d53..bac56b647 100644 --- a/src/cswinrt/pch.h +++ b/src/cswinrt/pch.h @@ -1,7 +1,7 @@ #pragma once #include "cmd_reader.h" -#include "winmd_reader.h" +#include #include "task_group.h" #include "text_writer.h" diff --git a/src/cswinrt/prebuild/CMakeLists.txt b/src/cswinrt/prebuild/CMakeLists.txt new file mode 100644 index 000000000..8a451fd29 --- /dev/null +++ b/src/cswinrt/prebuild/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.16) + +project(cswinrt-prebuild LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +if(NOT DEFINED CSWINRT_BUILD_VERSION) + set(CSWINRT_BUILD_VERSION "0.0.0-private.0") +endif() + +set(PREBUILD_SRCS + main.cpp + pch.h +) +add_executable(cswinrt-prebuild ${PREBUILD_SRCS}) +target_compile_definitions(cswinrt-prebuild PRIVATE CSWINRT_VERSION_STRING="${CSWINRT_BUILD_VERSION}") +target_include_directories(cswinrt-prebuild PRIVATE ../ ${CMAKE_CURRENT_SOURCE_DIR}/../) + +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + install(TARGETS cswinrt-prebuild) +endif() diff --git a/src/cswinrt/prebuild/main.cpp b/src/cswinrt/prebuild/main.cpp new file mode 100644 index 000000000..c1036718a --- /dev/null +++ b/src/cswinrt/prebuild/main.cpp @@ -0,0 +1,206 @@ +#include "pch.h" + +struct writer : cswinrt::writer_base +{ + +}; + +int main(int const argc, char** argv) +{ + if (argc < 3) + { + puts("Usage: prebuild.exe input output"); + return 0; + } + + auto const strings_dir = std::filesystem::path(argv[1]); + auto const additions_dir = strings_dir / "additions"; + + writer strings_h; + writer strings_cpp; + + strings_h.write(R"(// cswinrt static string declarations. This file was generated by the build. +#pragma once +namespace cswinrt::strings +{ +)"); + + strings_cpp.write(R"(#include "strings.h" +namespace cswinrt::strings { +)"); + + struct entry + { + std::string key; + std::string global; + }; + + std::vector base_entries; + std::vector addition_entries; + + auto write_string = [&](std::filesystem::path const& path, std::string const& global) + { + auto view = cswinrt::file_to_string(path.string()); + + strings_h.write(R"( extern char const %[]; +)", + global); + + strings_cpp.write(R"(extern char const %[] = R"xyz()xyz" +)", + global); + + std::string_view remainder = view; + + while (!remainder.empty()) + { + auto const size = std::min(size_t{ 16'000 }, remainder.size()); + auto const chunk = remainder.substr(0, size); + + strings_cpp.write(R"(R"xyz(%)xyz" +)", + chunk); + + remainder = remainder.substr(size); + } + + strings_cpp.write(R"(R"xyz( +)xyz"; +)"); + }; + + for (auto&& file : std::filesystem::directory_iterator(strings_dir)) + { + if (!std::filesystem::is_regular_file(file)) + { + continue; + } + + auto path = file.path(); + auto name = path.filename(); + name.replace_extension(); + + auto global = name.string(); + for (auto& c : global) { if (c == '.') c = '_'; } + + write_string(path, global); + base_entries.push_back({ name.string(), global }); + } + + strings_h.write(R"( static const struct + { + char const* name; + char const* value; + } base[] = { +)"); + { + std::string delimeter; + for (auto&& e : base_entries) + { + strings_h.write(" %{\"%\", %}\n", delimeter, e.key, e.global); + delimeter = ", "; + } + } + strings_h.write(" };\n"); + + if (std::filesystem::exists(additions_dir)) + { + for (auto&& ns_dir : std::filesystem::directory_iterator(additions_dir)) + { + if (!std::filesystem::is_directory(ns_dir)) + { + continue; + } + + auto ns_name = ns_dir.path().filename().string(); + + for (auto&& file : std::filesystem::directory_iterator(ns_dir.path())) + { + if (!std::filesystem::is_regular_file(file)) + { + continue; + } + + auto path = file.path(); + auto name = path.filename(); + name.replace_extension(); + + auto ns_part = ns_name; + for (auto& c : ns_part) { if (c == '.') c = '_'; } + auto file_part = name.string(); + for (auto& c : file_part) { if (c == '.') c = '_'; } + auto global = ns_part + "_" + file_part; + + write_string(path, global); + addition_entries.push_back({ ns_name, global }); + } + } + } + + strings_h.write(R"( static const struct + { + char const* name; + char const* value; + } additions[] = { +)"); + { + std::string delimeter; + for (auto&& e : addition_entries) + { + strings_h.write(" %{\"%\", %}\n", delimeter, e.key, e.global); + delimeter = ", "; + } + } + strings_h.write(" };\n"); + + strings_h.write("}\n"); + + strings_cpp.write(R"( +} +)"); + + writer version_rc; + + version_rc.write(R"( +#include "winres.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,0,0,0 + PRODUCTVERSION 0,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x0L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Microsoft Corporation" + VALUE "FileDescription", "C#/WinRT" + VALUE "FileVersion", "0.0.0.0" + VALUE "LegalCopyright", "Microsoft Corporation. All rights reserved." + VALUE "OriginalFilename", "cswinrt.exe" + VALUE "ProductName", "C#/WinRT" + VALUE "ProductVersion", "%" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END +)", + CSWINRT_VERSION_STRING); + + std::filesystem::create_directories(argv[2]); + auto const output = std::filesystem::canonical(argv[2]); + strings_h.flush_to_file(output / "strings.h"); + strings_cpp.flush_to_file(output / "strings.cpp"); + version_rc.flush_to_file(output / "version.rc"); +} diff --git a/src/cswinrt/prebuild/pch.cpp b/src/cswinrt/prebuild/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/src/cswinrt/prebuild/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/cswinrt/prebuild/pch.h b/src/cswinrt/prebuild/pch.h new file mode 100644 index 000000000..18f486be8 --- /dev/null +++ b/src/cswinrt/prebuild/pch.h @@ -0,0 +1,3 @@ +#pragma once + +#include "text_writer.h" diff --git a/src/cswinrt/prebuild/prebuild.vcxproj b/src/cswinrt/prebuild/prebuild.vcxproj new file mode 100644 index 000000000..8177f707f --- /dev/null +++ b/src/cswinrt/prebuild/prebuild.vcxproj @@ -0,0 +1,79 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {9F6A959A-9F34-43BD-99E7-DF816A5D6904} + cswinrt + prebuild + + + + Application + Unicode + + + + + + + + + ..\;%(AdditionalIncludeDirectories) + stdcpp17 + true + Level4 + Use + pch.h + CSWINRT_VERSION_STRING="$(VersionString)";%(PreprocessorDefinitions) + + + Console + + + + + Disabled + MultiThreadedDebug + + + + + MaxSpeed + true + true + MultiThreaded + + + true + true + + + + + + Create + + + + + + + diff --git a/src/cswinrt/text_writer.h b/src/cswinrt/text_writer.h index 56b7ed5f0..13a7252db 100644 --- a/src/cswinrt/text_writer.h +++ b/src/cswinrt/text_writer.h @@ -6,6 +6,7 @@ #include #include #include +#include namespace cswinrt { @@ -127,7 +128,16 @@ namespace cswinrt void write_printf(char const* format, Args const&... args) { char buffer[256]; +#if defined(_WIN32) || defined(_WIN64) size_t const size = sprintf_s(buffer, format, args...); +#else + size_t size = snprintf(buffer, sizeof(buffer), format, args...); + if (size > sizeof(buffer) - 1) + { + fprintf(stderr, "\n*** WARNING: writer_base::write_printf -- buffer too small\n"); + size = sizeof(buffer) - 1; + } +#endif write(std::string_view{ buffer, size }); } diff --git a/src/cswinrt/version.rc b/src/cswinrt/version.rc deleted file mode 100644 index 53e96f23f063d90531d82fb05fe6e5ac5ab39584..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1558 zcmcJPNpBiK5QX2FUxDx?mLdldUwj0!2n7cOm?$TN*@A>;tN~~F^Rd6`c6(-E6xo)n zmU^bUs@|)$AO4+aqeydYb+3h%DyyR3?k?f9)0uv9Q({dt(v-cWA%9)<^^J3vz0H65 zKoagtNZM%iu*=&XT1<5b&K0W;zS#|6>Z_@1B;!|G2i7*+b#=h>$X`LO>W{u?g`_0Q zz{8E)c z>j$}LJ!yHgR64@f2KhPGX<*ZF6*4}*8esRyr)8l6^InOo_L?efh-Ha>gom-KdTMoy zl*+P*=6zKcE{Yp=>z$w(zD0foS@=$I(kGy49U#`1Y5Mn{irl|ItaW@O(}j zF_nn%eb22~Bv{x!<7UHOROn4HW5`C+O@X0rKud9qIrN6_nGu{rvNxa>emcqpvHzcK z0$V~9!CPa=PtBC*ujypV;qb-y15jgn@18vEkMeiP41CgeSR*jUugFp!_r1LDk(D)7 z+`6jX(E(PCH80ctxd*J?c}ZCxi1w5WzPvxq(5}O?<(qQs+{s=;tIjX>1@B@}XSc|0 tGgFnrbAI|J#FV^w?(!2d2Fa#lhws^bw+^z`&QGPCYWmDgwZO~Z?IS>a(9{3` From 24e9024015aa2b1875e08fd55c1f6290c5b16248 Mon Sep 17 00:00:00 2001 From: Eugene Talagrand Date: Mon, 16 Feb 2026 01:32:20 -0800 Subject: [PATCH 2/2] IIDOptimizer & CsWinMD build on Linux --- .github/workflows/ci.yml | 55 ++++++++++++++++++++++++++++++++ src/Authoring/cswinmd/Program.cs | 13 ++++++-- src/Directory.Build.targets | 2 +- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0f0d6b26..667eb9793 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,3 +40,58 @@ jobs: with: name: cross-build-${{ matrix.arch }}-bin path: install/bin/cswinrt.exe + + build-linux-native-cswinrt: + name: 'native: Build cswinrt for Linux' + runs-on: ubuntu-22.04 + env: + CMAKE_COLOR_DIAGNOSTICS: 1 + CLICOLOR_FORCE: 1 + steps: + - uses: actions/checkout@v6 + + - name: Build cswinrt (native Linux) + run: | + cmake -S src/cswinrt/ -B build/linux_native/ \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo + cmake --build build/linux_native/ -j2 + + - name: Smoke test + run: | + file build/linux_native/cswinrt + build/linux_native/cswinrt 2>&1 | head -3 || true + + - name: Upload cswinrt + uses: actions/upload-artifact@v6 + with: + name: native-linux-x64-bin + path: build/linux_native/cswinrt + + build-linux-dotnet-tools: + name: 'dotnet: Build IIDOptimizer & CsWinMD on Linux' + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v6 + + - name: Setup .NET 8 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Build IIDOptimizer + run: | + dotnet build src/Perf/IIDOptimizer/IIDOptimizer.csproj \ + -p:IsDotnetBuild=true -c Release + + - name: Smoke test IIDOptimizer + run: dotnet src/Perf/IIDOptimizer/bin/Release/net8.0/IIDOptimizer.dll 2>&1 || true + + - name: Build CsWinMD + run: | + dotnet build src/Authoring/cswinmd/CsWinMD.csproj \ + -p:IsDotnetBuild=true \ + -p:SolutionDir=${{ github.workspace }}/src/ \ + -c Release + + - name: Smoke test CsWinMD + run: dotnet src/Authoring/cswinmd/bin/Release/net8.0/CsWinMD.dll 2>&1 || true diff --git a/src/Authoring/cswinmd/Program.cs b/src/Authoring/cswinmd/Program.cs index 7d11033e8..33ec1204e 100644 --- a/src/Authoring/cswinmd/Program.cs +++ b/src/Authoring/cswinmd/Program.cs @@ -44,6 +44,7 @@ public static void Main(string[] args) var i = new List(); string? o = null; string? sdkVersion = null; + string? windowsWinmd = null; bool? verbose = false; bool? nologo = false; string? a = null; @@ -70,6 +71,11 @@ public static void Main(string[] args) idx++; sdkVersion = args[idx]; } + else if (args[idx] == "-windowsWinmd") + { + idx++; + windowsWinmd = args[idx]; + } else if (args[idx] == "-verbose") { verbose = true; @@ -80,7 +86,7 @@ public static void Main(string[] args) } } - DoMain(i.ToArray(), o!, a!, sdkVersion, verbose, nologo); + DoMain(i.ToArray(), o!, a!, sdkVersion, windowsWinmd, verbose, nologo); } /// @@ -90,10 +96,11 @@ public static void Main(string[] args) /// Output directory /// Assembly name /// Optional sdk version + /// Direct path to Windows.winmd (bypasses registry lookup, required on non-Windows) /// Verbose logging /// Don't print logo /// Uses System.CommandLine.Dragonfruit - public static void DoMain(string[] i, string o, string a, string? sdkVersion, bool? verbose, bool? nologo) + public static void DoMain(string[] i, string o, string a, string? sdkVersion, string? windowsWinmd, bool? verbose, bool? nologo) { if (!nologo.HasValue || !nologo.Value) { @@ -125,7 +132,7 @@ public static void DoMain(string[] i, string o, string a, string? sdkVersion, bo var assemblyName = a; - var windows_winmd = GetWindowsWinMdPath(sdkVersion); + var windows_winmd = !string.IsNullOrEmpty(windowsWinmd) ? windowsWinmd : GetWindowsWinMdPath(sdkVersion); var compilation = CSharpCompilation.Create( assemblyName: assemblyName, syntaxTrees: new[] { CSharpSyntaxTree.ParseText(inputText, new CSharpParseOptions(LanguageVersion.Preview), inputFile) }, diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index e43169312..2e2f9ceaf 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -41,7 +41,7 @@