Qt 6.6.1 MSVC 2022로 빌드

MSVC 2022로 Qt 6.6.1버전 빌드 시 발생하는 오류 리스트

  1. 소스코드 파일 인코딩 오류 (비 유니코드 언어가 영어 (미국)이 아닌경우)
  2. qtwebengine/src/3rdparty/gn/src/gn/variables.cc 텍스트 오류 (1번 항목과 동일한 환경, 577번 라인의 0xA0(NBSP) 캐릭터에서 에러 발생하며 0x20(SPACE)으로 변경하면 해결)
  3. qtwebengine/src/3rdparty/chromium/third_party/webrtc/rtc_base/checks.h LogStreamer의 연산자 오류
  4. gperf 버전이 3.1이상일 경우 오류 발생 ((standard input):1915: warning: junk after %% is ignored 메시지 발생하며 파일이 정상처리 안됨)

크게 네가지 오류가 나타난다. 이 중 1, 2번은 이 글에서 “지역설정” 항목을 참조하여 변경하면 해결 된다. 3번 항목은 vcpkg 프로젝트에서 qtwebengine 패키지의 패치(clang-cl.patch, msvc-template.patch)를 그대로 적용하면 해결된다. 4번은 Qt 5.15.x 버전을 다운받은 후 gnuwin32\bin 경로를 PATH의 맨 앞으로 추가하여 clean & build 하면 해결된다.

만약 시스템 로케일 변경이 귀찮거나 재부팅을 하기에 곤란하다면 아래 소스코드를 빌드 후 실행하여 UTF-8 BOM을 추가하고 환경변수에 “PYTHONUTF8=1″을 추가(프롬프트에서 SET PYTHONUTF8=1 실행)하여 파이선의 파일 인코딩을 UTF-8로 인식하도록 설정한다.

아래 코드는 NBSP문자를 SPACE로 변환 및 확장 아스키 영역의 문자가 있을 경우 UTF-8 BOM을 추가한다.

/*
 * 파일명: CSourceSetUtf8Bom.cpp
 * 빌드명령: cl CSourceSetUtf8Bom.cpp /O2 /std:c++20 /EHsc
 * 실행: CSourceSetUtf8Bom <qt source path>
 * 실행 예: CSourceSetUtf8Bom D:\OpenSource\Qt6.6.1.src
 */

#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>

static void fetch(const std::filesystem::path& source_path, std::size_t& count)
{
    std::cout << count << "\r";
    std::filesystem::directory_iterator it(source_path);

    for (; it != std::filesystem::end(it); it++)
    {
        if (std::filesystem::is_directory(*it))
        {
            fetch(*it, count);
        }
        else
        {
            std::string path = it->path().string();

            if (!path.ends_with(".c") && !path.ends_with(".cc") && !path.ends_with(".cpp") && !path.ends_with(".h"))
            //if (!path.ends_with(".py"))
            {
                continue;
            }

            count++;

            std::basic_ifstream<unsigned char> istream(path);
            std::basic_string<unsigned char> content((std::istreambuf_iterator<unsigned char>(istream)), std::istreambuf_iterator<unsigned char>());

            istream.close();

            if (content.size() >= 3 && content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF)
            {
                continue;
            }

            bool extended = std::find_if(content.begin(), content.end(), [](unsigned char c) { return c > 127; }) != content.end();
            if (!extended)
            {
                continue;
            }

            std::cout << count << ", " << it->path().string() << ", " << content.size() << " bytes\n";

            // replace NBSP -> SPACE
            std::replace(content.begin(), content.end(), 0xA0, 0x20);
            std::basic_ofstream<unsigned char> ofstream(path);

            ofstream << static_cast<unsigned char>(0xEF);
            ofstream << static_cast<unsigned char>(0xBB);
            ofstream << static_cast<unsigned char>(0xBF);
            ofstream << content;

            ofstream.close();
        }
    }
}

int main(int argc, char* argv[])
{
    if (argc < 2)
    {
        std::cout << argv[0] << " <FOLDER>\n";

        return EXIT_FAILURE;
    }

    std::size_t count = 0;
    std::filesystem::path source_root(argv[1]);

    if (!std::filesystem::is_directory(source_root))
    {
        return EXIT_FAILURE;
    }
    fetch(source_root, count);

    return EXIT_SUCCESS;
}

SQLite3 MSVC로 빌드시 주의사항

소스파일의 기준은 https://www.sqlite.org/download.html 이곳에서 sqlite-autoconf-VERSION.tar.gz 기준. (현재는 3380200)

소스파일 압축본 내 README.txt 파일과 https://www.sqlite.org/howtocompile.html 이곳을 참조하여 nmake "OPTS=-DSQLITE_ENABLE_STAT4=1 -DSQLITE_OMIT_JSON=1" /f Makefile.msc 이렇게 빌드하였다.

이후 sqlite3.lib 파일로 링크하면 링크 오류가 발생하고 lib 파일을 열어다보니 export된 함수가 없다는것을 알 수 있었다. make 중 dumpbin /all sqlite3.lo 명령으로 sqlite3.def 파일을 생성하는 부분이 눈에 보이는데 단순히 버그인지 아니면 MSVC의 버전이 올라가면서 컴파일러 전처리기에 차이가 발생하는건지 sqlite3.def 파일에 export될 함수들이 없어 발생하는 문제인거같다.

stack overflow의 글(https://stackoverflow.com/a/71106434)을 참조하면 식별자 SQLITE_API로 dllexport를 지정하면 된다고 되어있으며 Makefile.msc 파일을 확인해본 결과 DYNAMIC_SHELL=1을 nmake 실행 시 지정하면 되는것으로 파악되었다.

결론: nmake "OPTS=-DSQLITE_ENABLE_STAT4=1 -DSQLITE_OMIT_JSON=1" DYNAMIC_SHELL=1 /f Makefile.msc 이렇게 빌드하면 된다. sqlite3.h, sqlite3.lib, sqlite3.dll 파일들을 적당히 복사하여 사용하면 끗.

Boost 1.78.0 + Visual Studio 환경에서 빌드하기

이미 관련된 글을 남긴적이 있지만 조금 더 알게된것이 있고 장황하지 않고 짧게 기록하기위해 남기는 글. 콘솔 등 자잘한 내용은 기존의 글을 참조할 수 있으니 넘어가고 필요한 최소한의 내용만을 남긴다. (이전 글에서 문자열 ‘--‘은 ‘–‘로 치환되어 표시되고있으니 주의필요. 언젠가 수정할…지도?)

환경 및 빌드 대상
– Visual Studio Community 2019
– Boost 1.78.0
– 소스위치: D:\OpenSource\boost_1_78_0
– 설치위치: D:\OpenSource\boost

Boost Bootstrap
1. D:\OpenSource\boost_1_78_0\tools\build 폴더로 이동
2. bootstrap.bat 실행
3. b2 install --prefix=D:\OpenSource\boost 실행
4. D:\OpenSource\boost_1_78_0 폴더로 이동

환경변수 변경
set PATH=%PATH%;D:\OpenSource\boost 명령을 실행하여 설치된 b2의 바이너리 경로를 PATH에 추가

Boost 빌드
b2 --build-type=complete toolset=msvc stage

Boost 설치
b2 --build-type=complete --prefix=D:\OpenSource\boost toolset=msvc install

Boost 사용
헤더와 라이브러리는 아래와같은 경로를 지정하여 사용하면 된다. 헤더는 버전별 폴더로 구분되어 있고 라이브러리나 dll은 파일명에 버전 넘버가 붙어있으니 다른 버전의 boost를 동일한 경로로 설치해도 헤더 경로만 사용하고 싶은 경로를 지정하여 사용하면된다.
헤더 경로: D:\OpenSource\boost\include\boost-1_78
라이브러리 경로: D:\OpenSource\lib

기타
1. 빌드와 설치를 개별 명령으로 실행하면 시간적으로 손해를 본다. 그냥 바로 Boost 설치 명령을 실행해도 된다.
2. --build-dir 옵션을 넣으면 stage 파일의 위치를 변경할 수 있다. 대신 Boost 빌드와 Boost 설치 실행 시 아래와같이 --build-dir 값을 동일하게 입력해야한다.
b2 --build-dir=D:\OpenSource\boost_build\1_78_0 --build-type=complete toolset=msvc stage
b2 --build-dir=D:\OpenSource\boost_build\1_78_0 --build-type=complete --prefix=D:\OpenSource\boost toolset=msvc install
3. --prefix 없이 설치하면 기본위치는 C:\Boost 이다.
4. Boost 소스폴더에서 b2 --help 를 실행하면 --layout 옵션을 볼 수 있다. 별별짓 다 해봤지만 윈도 환경에서는 기본값인 versioned로 빌드하여 설치하는게 가장 정신건강에 이롭다. 다른 값으로 빌드하면 헤더나 라이브러리 링크에서 정신건강에 해로울것이다.
5. 언제부턴가 32비트와 64비트 라이브러리가 기본적으로 함께 빌드되도록 변경된거같다. 특정 플랫폼만 대상으로 빌드한다면 address-model=32 또는 address-model=64 옵션을 함께 사용하면 된다. (toolset과 마찬가지로 -- 없이 address-model이다.)
6. 더 자세히 알고싶으면 이곳 Boost의 문서를 참조.
7. (추가) boost 빌드 시 시스템에 깔린 최신 버전의 MSVC를 사용하여 빌드되는것으로 보인다. 특정 버전의 MSVC를 대상으로 빌드하려면 MSVC 2019 기준 toolset=msvc-14.2와 같이 지정하면 되며 boost 1.81 기준 버전 넘버는 이곳 Boost Build 문서를 참조.

ICU 라이브러리 MSVC로 빌드

매우 오랜만에 ICU를 빌드하려하다보니 Windows SDK Version이 변경되지않는 이상한 문제가 발생하였다. 이거로 두시간동안 구글링도해보고 솔루션, 프로젝트, 프로퍼티 파일을 열어보아도 답이 안나와서 결국 MSYS2에서 빌드하였다(…).

빌드방법은 간단. VC 콘솔에서 MSYS2가 설치된 폴더로 이동 후 msys2_shell.cmd -use-full-path 명령으로 실행하면 프롬포트의 환경번수가 유지되어 bash 쉘이 뜨게된다. 이제 압축을 푼 icu 폴더의 source 폴더로 이동하여 ./runConfigureICU MSYS/MSVC --prefix=/d/OpenSource/icu-67.1.x64 --enable-tests=no --enable-samples=no 이런식으로 Makefile을 생성. 그리고 원인은 모르지만 config 파일을 정확하게 지정하지 못하는데… 이건 source 폴더의 하위 폴더인 config에서 cp mh-msys-msvc mh-unknown와같이 복사 후 make하면 끝. (nmake가 아닌 make 명령. 만약 D 옵션을 알 수 없다는 오류가 뜬다면 /usr/bin/link.exe 파일의 이름을 잠깐 다른 이름으로 바꾸면 오류를 회피할 수 있다. tests를 비활성화하면 링크에서 오류가 난다.)

이것으로 삽질 끝.

만약 위와같이 rc 명령에서 오류가 난다면 data 폴더에 out폴더 생성, 그 안애 tmp 폴더를 생성하면 된다. 추가로 extra의 scptrun에서 링크오류가 발생하면 해당 폴더의 makefile을 열어서 LINK.cc를 검색하여 -o $@ 부분을 -out:$@로 변경하면 된다.

추가)
icu 70.1버전에서 data폴더에서 dirs.timestamp관련 오류가 발생한다. 패치는 이곳에서 확인가능하며 아래가 패치파일 내용.

diff --urN a/source/data/Makefile.in b/source/data/Makefile.in
--- a/source/data/Makefile.in
+++ b/source/data/Makefile.in
@@ -236,11 +236,12 @@
 ## Include the main build rules for data files
 include $(top_builddir)/$(subdir)/rules.mk
 
+PKGDATA_LIST = $(TMP_DIR)/icudata.lst
 
 ifeq ($(ENABLE_SO_VERSION_DATA),1)
 ifeq ($(PKGDATA_MODE),dll)
 SO_VERSION_DATA = $(OUTTMPDIR)/icudata.res
-$(SO_VERSION_DATA) : $(MISCSRCDIR)/icudata.rc | $(TMP_DIR)/dirs.timestamp
+$(SO_VERSION_DATA) : $(MISCSRCDIR)/icudata.rc $(PKGDATA_LIST)
 ifeq ($(MSYS_RC_MODE),1)
 	rc.exe -i$(srcdir)/../common -i$(top_builddir)/common -fo$@ $(CPPFLAGS) $<
 else
@@ -249,7 +250,6 @@
 endif
 endif
 
-PKGDATA_LIST = $(TMP_DIR)/icudata.lst
 
 
 #####################################################

CMake-gui를 Qt5환경으로 빌드하기

빌드하게 매우 귀찮아 CMake를 MSI 패키지로 설치를 하다가 매우 오랜만에 직접 빌드해보았다. (대신 MSYS2를 깔아야한다.) 전에는 Qt4를 사용했기때문에 쉽게 빌드된거같은데 Qt5를 사용한 GUI 환경을 빌드하려하니 구글링도 안되어 꽤나 삽질했다. CMakeLists.txt를 열어보면 Qt5를 지원하는 모양이였지만 find_package(Qt5Widgets QUIET)구문이 어떻게 동작하는지 알 수 없어 리눅스 환경에서 bootstrap을 실행하여 생성된 CMakeCache.txt 파일을 직접 분석하여 찾아본 결과 -DBUILD_QtDialog=ON -DQT_QMAKE_EXECUTABLE=D:\OpenSource\Qt5.15.0.x64\bin\qmake.exe -DQt5Widgets_DIR=D:\OpenSource\Qt5.15.0.x64\lib\cmake\Qt5Widgets 이렇게 변수를 설정하면 된다는것을 알게되었다.

자세한 내용은 CMake 소스코드에서 README.rst 파일을 열어보면 되고 이미 빌드된 CMake가 없는 상태에서 간략한 빌드과정은

MSYS2를 설치한다. 설치완료 후 MSYS2를 실행하고 pacman -S --needed git base-devel mingw-w64-x86_64-gcc 명령으로 필요한 패키지를 설치한다. (git는 필요없어보이지만 README.rst에서 설치하라고하니..) 그 후 exit 명령으로 쉘을 종료하고 시작메뉴에서 MSYS2 MinGW 64bit를 실행한다. 이렇게 기본 빌드환경은 끝.

3.18.1버전의 cmake를 압축 풀었다면 폴더 이름이 cmake-3.18.1일것이다. 압축 푼 경로가 D:\OpenSource\cmake-3.18.1 이라면 cd /d/OpenSource 명령으로 폴더 이동 후 임의 폴더 생성한다. (cmake-build1로 만들었다.) cmake-build1 폴더에서 ../cmake-3.18.1/configure –prefix=/d/OpenSource/cmake-bootstrap 명령을 실행 후 make && make install 명령을 실행하여 빌드 및 바이너리를 설치한다. 이렇게 bootstrap 환경은 완료.

Visual Studio의 64비트 툴 콘솔을 실행하여 set PATH=%PATH%;C:\msys64\mingw64\bin 명령을 수행 후(msys2를 기본 경로에 설치했을경우) D:\OpenSource\cmake-build2 폴더를 생성 & 이동하여 ../cmake-bootstrap/bin/cmake ..\cmake-3.18.1 -DCMAKE_INSTALL_PREFIX=D:\OpenSource\cmake -DBUILD_QtDialog=ON -DQT_QMAKE_EXECUTABLE=D:\OpenSource\Qt5.15.0.x64\bin\qmake.exe -DQt5Widgets_DIR=D:\OpenSource\Qt5.15.0.x64\lib\cmake\Qt5Widgets 이렇게 실행하면 Visual Studio Solution 파일(CMake.sln)이 생성된다. (nmake 타겟으로 생성가능하지만 VS 솔루션 파일에서 빌드하면 병렬빌드가 되기에 그냥 기본 타겟으로 생성하였다.) 이제 솔루션 파일을 열고 빌드 형식을 Release / x64로 설정하여 CMakePredefinedTargets/ALL_BUILD 프로젝트를 우클릭하여 빌드 메뉴를 선택한다. 모두 빌드되면 CMakePredefinedTargets/INSTALL 프로젝트를 우클릭하여 빌드 메뉴를 선택하면 CMAKE_INSTALL_PREFIX로 설정된 D:\OpenSource\cmake에 CMake가 설치된다.

D:\OpenSource\cmake\bin\cmake-gui.exe 파일을 실행하면 DLL 종속성 오류가 뜰것이다. 명령 프롬포트에서 D:\OpenSource\cmake\bin 폴더로 이동 후 D:\OpenSource\Qt5.15.0.x64\bin\windeployqt.exe cmake-gui.exe 명령을 실행하면 종속된 파일이 전부 복사되고 이제 cmake-gui.exe를 실행하면 아래와같이 GUI 환경의 cmake 프로그램이 실행된다.

이제 필요한것들을 빌드해야겠다. 실행도 깔끔하게 잘 되니 이것으로 끝!