Skip to content

Commit 660e0e1

Browse files
authored
Merge pull request #6231 from hjmjohnson/windows-utf8-active-codepage
ENH: Embed UTF-8 active-code-page manifest in Windows test executables (supersedes #4390)
2 parents a4fed47 + 4eca583 commit 660e0e1

10 files changed

Lines changed: 118 additions & 0 deletions

CMake/ITKModuleTest.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ EM_ASM(
9090
PRIVATE
9191
"$<$<AND:$<C_COMPILER_ID:AppleClang>,$<VERSION_GREATER_EQUAL:$<C_COMPILER_VERSION>,15.0>>:LINKER:-no_warn_duplicate_libraries>"
9292
)
93+
itk_target_attach_windows_utf8_manifest(${KIT}TestDriver)
9394
itk_module_target_label(${KIT}TestDriver)
9495
endfunction()
9596

@@ -286,6 +287,7 @@ function(CreateGoogleTestDriver KIT KIT_LIBS KitTests)
286287
PRIVATE
287288
"$<$<AND:$<C_COMPILER_ID:AppleClang>,$<VERSION_GREATER_EQUAL:$<C_COMPILER_VERSION>,15.0>>:LINKER:-no_warn_duplicate_libraries>"
288289
)
290+
itk_target_attach_windows_utf8_manifest(${exe})
289291
itk_module_target_label(${exe})
290292

291293
include(GoogleTest)

CMake/ITKWindowsUtf8.cmake

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Attach the UTF-8 active-code-page manifest to a Windows executable so its
2+
# narrow-char Win32 APIs (CreateFileA, fopen, std::ifstream(const char*), ...)
3+
# treat byte strings as UTF-8 on Windows 10 1903 and later. No-op on
4+
# non-MSVC toolchains and on platforms where the manifest is irrelevant.
5+
#
6+
# Usage: itk_target_attach_windows_utf8_manifest(<target>)
7+
set(
8+
_itk_windows_utf8_manifest
9+
"${CMAKE_CURRENT_LIST_DIR}/Windows-utf8-codepage.manifest"
10+
)
11+
12+
function(itk_target_attach_windows_utf8_manifest _target)
13+
if(MSVC)
14+
target_sources(${_target} PRIVATE "${_itk_windows_utf8_manifest}")
15+
endif()
16+
endfunction()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
3+
<application>
4+
<windowsSettings>
5+
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
6+
</windowsSettings>
7+
</application>
8+
</assembly>

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ include(PreventInSourceBuilds)
180180
include(PreventInBuildInstalls)
181181
include(ITKModuleMacros)
182182
include(ITKExternalData)
183+
include(ITKWindowsUtf8)
183184
include(ITKModuleTest)
184185
include(itkCheckSourceTree)
185186

Modules/Core/TestKernel/include/itkTestDriverBeforeTest.inc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
// CMake command create_test_sourcelist. This is included directly before
2020
// the call to the test main function.
2121

22+
#ifdef _WIN32
23+
// Render UTF-8 console output correctly. The active code page itself
24+
// is set to UTF-8 by the Windows-utf8-codepage.manifest attached at
25+
// link time; this call only affects what stdout / stderr render.
26+
SetConsoleOutputCP(CP_UTF8);
27+
#endif
2228
vnl_sample_reseed(8775070);
2329
try
2430
{

Modules/Core/TestKernel/include/itkTestDriverInclude.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252
#include "itkIntTypes.h"
5353
#include "itkWin32Header.h"
5454

55+
#ifdef _WIN32
56+
# include <windows.h>
57+
#endif
58+
5559
constexpr int ITK_TEST_DIMENSION_MAX{ 6 };
5660

5761
extern int

Modules/Core/TestKernel/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ if(NOT DO_NOT_BUILD_ITK_TEST_DRIVER)
1212
add_executable(itkTestDriver itkTestDriver.cxx)
1313
target_link_libraries(itkTestDriver PRIVATE ${ITKTestKernel_LIBRARIES})
1414

15+
itk_target_attach_windows_utf8_manifest(itkTestDriver)
1516
itk_module_target_label(itkTestDriver)
1617
itk_module_target_export(itkTestDriver)
1718
target_link_options(

Modules/Core/TestKernel/src/itkTestDriver.cxx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ TestDriverInvokeProcess(const ArgumentsList & args)
263263
int
264264
main(int argc, char * argv[])
265265
{
266+
#ifdef _WIN32
267+
SetConsoleOutputCP(CP_UTF8);
268+
#endif
266269
try
267270
{
268271
RegisterRequiredFactories();

Modules/Core/TestKernel/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ set(
4949
itkGoogleTest.cxx
5050
itkGoogleTestFixture.cxx
5151
itkTestingComparisonImageFilterGTest.cxx
52+
itkUtf8FilenameGTest.cxx
5253
)
5354

5455
creategoogletestdriver(ITKTestKernel "${ITKTestKernel-Test_LIBRARIES}" "${ITKTestKernelGTests}")
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
19+
// Round-trip a small file at a path that contains non-ASCII (UTF-8)
20+
// characters. On Windows, the Windows-utf8-codepage.manifest attached
21+
// to every ITK test driver makes the narrow Win32 *A APIs treat byte
22+
// strings as UTF-8 (Windows 10 1903 and later), so std::ofstream and
23+
// std::ifstream constructed from a UTF-8 const char * "just work". On
24+
// POSIX platforms the encoding is byte-transparent.
25+
26+
#include <cstdio>
27+
#include <fstream>
28+
#include <string>
29+
30+
#include "gtest/gtest.h"
31+
32+
namespace
33+
{
34+
35+
// "speciäl-fil👍.txt" encoded as UTF-8 bytes.
36+
const char *
37+
SpecialBasename()
38+
{
39+
return "speci\xC3\xA4l-fil\xF0\x9F\x91\x8D.txt";
40+
}
41+
42+
std::string
43+
TempDirFromEnv()
44+
{
45+
for (const char * var : { "ITK_TEST_OUTPUT_DIR", "TEMP", "TMP", "TMPDIR" })
46+
{
47+
if (const char * envDir = std::getenv(var))
48+
{
49+
return envDir;
50+
}
51+
}
52+
return ".";
53+
}
54+
55+
} // namespace
56+
57+
TEST(WindowsUtf8Codepage, RoundTripFileAtNonAsciiPath)
58+
{
59+
const std::string path = TempDirFromEnv() + "/" + SpecialBasename();
60+
const std::string payload = "round-trip payload\n";
61+
62+
{
63+
std::ofstream out(path.c_str());
64+
ASSERT_TRUE(out.is_open()) << "failed to open " << path << " for writing";
65+
out << payload;
66+
}
67+
68+
std::ifstream in(path.c_str());
69+
const bool readOpened = in.is_open();
70+
const std::string actual =
71+
readOpened ? std::string((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()) : std::string{};
72+
std::remove(path.c_str());
73+
74+
ASSERT_TRUE(readOpened) << "failed to open " << path << " for reading";
75+
EXPECT_EQ(actual, payload);
76+
}

0 commit comments

Comments
 (0)