Build APM บน Windows ตอนที่ 3: build Apache httpd 2.4.69 ด้วย CMake
Apache httpd บน Windows ไม่มี installer ทางการให้ ต้องคอมไพล์เองหรือพึ่งของที่คนอื่นแพ็กให้ ตอนนี้เราคอมไพล์เองทั้งชุดใน 4 นาที แล้วให้สคริปต์ตรวจเองว่า DLL ที่ต้องใช้ครบ และไม่มีตัวไหนเผลอลิงก์ไปหา DLL รุ่น debug
พอดีตอนผมเขียนสคริปต์ 20-apache.ps1 ครั้งแรก มันสร้าง httpd.exe ออกมาได้ตั้งแต่รอบแรก แต่พอไล่ดูจริงกลับพบว่า mod_md.so ไปลิงก์หา libcurl-d.dll ซึ่งเป็น DLL รุ่น debug ที่ไม่มีทางอยู่บนเครื่องของใครนอกจากเครื่อง build และถ้าไม่ได้ไล่ดู ผมจะรู้ก็ต่อเมื่อเอาไป start บนเครื่องอื่นแล้วโมดูลโหลดไม่ขึ้น เรื่องนี้เลยเป็นเหตุผลที่สคริปต์ตัวสุดท้ายมีส่วนไล่ตรวจ DLL ด้วย dumpbin และล้มทันทีถ้าเจอของ debug ตอนที่ 3 นี้จะพาไล่ทั้งสคริปต์ ทุก option ของ CMake และวิธีตรวจผลทีละคำสั่ง
ก่อนอ่านต่อ ตอนที่ 2 ต้องเสร็จแล้ว คือมี source อยู่ใน C:\apm-build\src และมี lib ที่ vcpkg build ไว้ใน C:\apm-build\build\vcpkg_installed\x64-windows ถ้ายังไม่มี ให้กลับไปทำตอนที่ 2 ก่อน เพราะสคริปต์ตัวนี้ไม่ได้ดาวน์โหลดอะไรเลย
1. เตรียมตัว: เช็กของจากตอนที่ 2
ทุกขั้นรันใน pwsh (PowerShell 7) สิทธิ์ user ปกติ ไม่ต้องใช้ Administrator และไม่ต้องเปิด Developer Prompt เพราะสคริปต์โหลด environment ของ MSVC ให้เอง
ขั้นที่ 1.1 เปิด pwsh แล้วเข้าโฟลเดอร์ของ kit
Set-Location C:\apm-build
ขั้นที่ 1.2 เช็กว่า source ของ Apache อยู่ครบ
Test-Path C:\apm-build\src\httpd-2.4.69\CMakeLists.txt
Test-Path C:\apm-build\src\apr-1.7.6\CMakeLists.txt
Test-Path C:\apm-build\src\apr-util-1.6.5\CMakeLists.txt
Test-Path C:\apm-build\src\mod_fcgid-2.3.9\modules\fcgid\CMakeLists.txt
ต้องได้ True สี่บรรทัด ถ้าได้ False ข้อไหน แปลว่า 00-fetch.ps1 ยังไม่เสร็จหรือแตกไฟล์ผิดชั้น กลับไปตอนที่ 2 ขั้น 4.5
ขั้นที่ 1.3 เช็กว่า lib ของ vcpkg อยู่ครบ
Test-Path C:\apm-build\build\vcpkg_installed\x64-windows\lib\libssl.lib
Test-Path C:\apm-build\build\vcpkg_installed\x64-windows\lib\pcre2-8.lib
Test-Path C:\apm-build\build\vcpkg_installed\x64-windows\bin\libcrypto-3-x64.dll
ต้องได้ True สามบรรทัด
ขั้นที่ 1.4 เช็กว่า CMake ใหม่พอ
สคริปต์ใช้ cmake --fresh ซึ่งมีตั้งแต่ CMake 3.24
cmake --version | Select-Object -First 1
ninja --version
cmake version 4.1.2
1.13.1
เลขรุ่นของคุณอาจใหม่กว่านี้ ขอแค่ CMake ไม่ต่ำกว่า 3.24
ขั้นที่ 1.5 เช็กว่าสคริปต์หา Visual Studio เจอ
สคริปต์ lib.ps1 ไม่ฝัง path ของ vcvars64.bat แล้ว มันถาม vswhere.exe (ตัวที่มากับ Visual Studio Installer) ว่า Visual Studio 2022 ที่มี C++ x64 tools ลงอยู่ที่ไหน เลยใช้ได้กับทุก edition (Build Tools, Community, Professional, Enterprise) และทุก path ที่ติดตั้ง ลองคำสั่งเดียวกับที่สคริปต์ใช้
$vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -version '[17.0,18.0)' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
Test-Path "$vs\VC\Auxiliary\Build\vcvars64.bat"
ต้องได้ True ถ้าไม่ได้ True หรือ vswhere ไม่พิมพ์อะไรเลย แปลว่ายังไม่มี Visual Studio 2022 ที่ติด component "MSVC v143 - VS 2022 C++ x64/x86 build tools" ให้กลับไปตอนที่ 1 แล้วดูหัวข้อ "ถ้าพังตรงนี้"
2. ภาพรวม: build 4 ชิ้นต่อกัน เรียงตามที่ใครพึ่งใคร
Apache httpd ไม่ได้เป็นก้อนเดียว มันพึ่งพากันเป็นชั้น และต้อง build ตามลำดับนี้เท่านั้น เพราะชิ้นหลังใช้ header กับ .lib ของชิ้นก่อนที่เพิ่งติดตั้งลง stage\apache
- APR 1.7.6 (Apache Portable Runtime) ชั้นล่างสุด เป็นตัวห่อหุ้มฟังก์ชันของระบบปฏิบัติการ ไฟล์ ซ็อกเก็ต เธรด หน่วยความจำ ให้ Apache เขียนโค้ดเดียวรันได้ทุกระบบ
- APR-util 1.6.5 ชั้นบนของ APR เพิ่มของที่ใช้บ่อย เช่น อ่าน XML, เข้ารหัส (ตัวนี้ใช้ OpenSSL จาก vcpkg), ฐานข้อมูลแบบ dbm
- httpd 2.4.69 ตัว web server กับ module ที่ติดมากับ source (ssl, http2, rewrite, proxy ฯลฯ)
- mod_fcgid 2.3.9 module ที่ Apache ใช้คุยกับ
php-cgi.exeเป็นโปรเจกต์แยก ไม่ได้อยู่ใน source ของ httpd (ตอนที่ 6 จะเห็นว่าใช้ยังไง)
ทั้งสี่ชิ้นรันด้วยท่าเดียวกัน คือ cmake ตั้งค่า (configure) แล้ว cmake --build คอมไพล์ แล้ว cmake --install คัดลอกผลลัพธ์ไปไว้ที่ C:\apm-build\stage\apache ใช้ Ninja เป็นตัวรันคอมไพล์จริง เพราะเร็วกว่าและใช้ซีพียูทุกคอร์โดยไม่ต้องตั้งอะไร
ของสำเร็จรูปบน Windows ตามรุ่นของ upstream ช้ากว่าที่ผมรับได้เมื่อมีแพตช์ความปลอดภัย การ build เองทำให้เปลี่ยนเลขเวอร์ชันใน versions.json แล้วได้ binary ที่ผ่านการตรวจ hash ของ source ภายในวันเดียว ราคาที่จ่ายคือต้องรู้ว่า option ทุกตัวในตอนนี้ทำอะไร
3. สคริปต์ 20-apache.ps1 ทั้งฉบับ
อ่านทั้งไฟล์ก่อนหนึ่งรอบ ยังไม่ต้องเข้าใจทุกบรรทัด ข้างล่างจะแตกอธิบายตามลำดับที่มันรัน
# 20-apache.ps1 - build APR, APR-util, httpd and mod_fcgid with CMake + Ninja (MSVC x64)
# Needs: 00-fetch.ps1 (sources) + 10-deps-vcpkg.ps1 (third-party libs)
$ErrorActionPreference = 'Stop'
$Root = Split-Path $PSScriptRoot -Parent
. "$PSScriptRoot\lib.ps1"
$V = (Get-Content "$Root\versions.json" -Raw | ConvertFrom-Json).sources
$Dep = "$Root\build\vcpkg_installed\x64-windows" -replace '\\','/'
$Pfx = "$Root\stage\apache" -replace '\\','/'
$B = "$Root\build"
$Log = "$B\logs\apache.log"
New-Item -ItemType Directory -Force "$B\logs" | Out-Null
Remove-Item $Log -ErrorAction SilentlyContinue
Enter-VsEnv
# CMAKE_POLICY_VERSION_MINIMUM: APR/httpd/fcgid declare cmake_minimum_required < 3.5, which CMake 4.x rejects
# CMAKE_MAP_IMPORTED_CONFIG_*: vcpkg config packages (CURL::libcurl) have no RelWithDebInfo -> would fall back to the debug DLL
$common = @('-G','Ninja','-DCMAKE_BUILD_TYPE=RelWithDebInfo','-DCMAKE_POLICY_VERSION_MINIMUM=3.5',
'-DCMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO=Release;RelWithDebInfo;',"-DCMAKE_INSTALL_PREFIX=$Pfx","-DCMAKE_PREFIX_PATH=$Dep")
function Build-Cmake($name, $src, [string[]]$opts) {
$bdir = "$B\$name"
Invoke-Step "$name configure" { cmake --fresh -S $src -B $bdir @common @opts } $Log
Invoke-Step "$name build" { cmake --build $bdir } $Log
Invoke-Step "$name install" { cmake --install $bdir } $Log
}
Build-Cmake 'apr' "$Root\src\apr-$($V.apr.version)" @('-DAPR_INSTALL_PRIVATE_H=ON','-DAPR_BUILD_TESTAPR=OFF')
Build-Cmake 'apr-util' "$Root\src\apr-util-$($V.'apr-util'.version)" @(
'-DAPU_HAVE_CRYPTO=ON','-DAPU_HAVE_ODBC=OFF','-DAPR_HAS_LDAP=OFF','-DAPR_BUILD_TESTAPR=OFF')
Build-Cmake 'httpd' "$Root\src\httpd-$($V.httpd.version)" @(
'-DENABLE_MODULES=i','-DINSTALL_MANUAL=OFF',
"-DPCRE_INCLUDE_DIR=$Dep/include","-DPCRE_LIBRARIES=$Dep/lib/pcre2-8.lib",'-DPCRE_CFLAGS=-DHAVE_PCRE2',
"-DNGHTTP2_INCLUDE_DIR=$Dep/include","-DNGHTTP2_LIBRARIES=$Dep/lib/nghttp2.lib",
"-DBROTLI_INCLUDE_DIR=$Dep/include","-DBROTLI_LIBRARIES=$Dep/lib/brotlienc.lib;$Dep/lib/brotlicommon.lib",
"-DCURL_INCLUDE_DIR=$Dep/include","-DCURL_LIBRARIES=$Dep/lib/libcurl.lib",
"-DJANSSON_INCLUDE_DIR=$Dep/include","-DJANSSON_LIBRARIES=$Dep/lib/jansson.lib")
# mod_lua is skipped automatically: httpd only looks for Lua 5.1 (not needed by the target production setup)
# cmake --install of httpd does not lay down conf/error/icons -> copy the runtime data files from the source tree
$hs = "$Root\src\httpd-$($V.httpd.version)\docs"
New-Item -ItemType Directory -Force "$Pfx\conf\extra", "$Pfx\conf\vhosts" | Out-Null
Copy-Item "$hs\conf\mime.types", "$hs\conf\magic", "$hs\conf\charset.conv" "$Pfx\conf" -Force
Copy-Item "$hs\error", "$hs\icons" $Pfx -Recurse -Force
Build-Cmake 'mod_fcgid' "$Root\src\mod_fcgid-$($V.mod_fcgid.version)\modules\fcgid" @()
# runtime DLLs of the third-party libs next to httpd.exe - only those actually imported (transitively)
$avail = @{}; Get-ChildItem "$Dep\bin\*.dll" | ForEach-Object { $avail[$_.Name.ToLower()] = $_.FullName }
$debugOnly = @{}; Get-ChildItem "$Dep\debug\bin\*.dll" -ErrorAction SilentlyContinue |
Where-Object { -not $avail.ContainsKey($_.Name.ToLower()) } | ForEach-Object { $debugOnly[$_.Name.ToLower()] = 1 }
$queue = [System.Collections.Generic.Queue[string]]::new()
Get-ChildItem "$Pfx\bin\*.exe", "$Pfx\bin\*.dll", "$Pfx\modules\*.so" | ForEach-Object { $queue.Enqueue($_.FullName) }
$need = @{}
while ($queue.Count) {
dumpbin /nologo /dependents $queue.Dequeue() | ForEach-Object {
$n = $_.Trim().ToLower()
if ($debugOnly.ContainsKey($n)) { throw "[ERR] release binary imports a debug DLL: $n" }
if ($avail.ContainsKey($n) -and -not $need.ContainsKey($n)) { $need[$n] = $avail[$n]; $queue.Enqueue($avail[$n]) }
}
}
$need.Values | Copy-Item -Destination "$Pfx\bin" -Force
# OpenSSL loads its providers at runtime (not an import) - keep the legacy provider for old PKCS#12 files
if (Test-Path "$Dep\bin\legacy.dll") { Copy-Item "$Dep\bin\legacy.dll" "$Pfx\bin" -Force }
Write-Host "[OK] runtime DLLs: $(($need.Keys | Sort-Object) -join ' ')"
& "$Pfx\bin\httpd.exe" -V *>> $Log
Write-Host "[OK] apache -> $Pfx"
& "$Pfx\bin\httpd.exe" -v
ส่วนหัว: ตัวแปรและ environment ของ MSVC
$Rootคือโฟลเดอร์ของ kit (พ่อของโฟลเดอร์scripts) เลยรันสคริปต์จากที่ไหนก็ได้$Depกับ$Pfxแปลง\เป็น/เพราะ CMake ตีความ backslash เป็นตัวหลบ (escape) ถ้าส่ง path แบบ Windows ตรง ๆ จะเพี้ยนRemove-Item $Logลบ log เก่าทิ้ง เพื่อให้apache.logมีแต่ของรอบนี้Enter-VsEnv(อยู่ในlib.ps1) โหลด environment ของ MSVC เข้า pwsh ที่กำลังรัน อธิบายด้านล่าง
ฟังก์ชันจาก lib.ps1 ที่สคริปต์เรียก
# Any VS 2022 edition with the C++ x64 tools (Build Tools here, Enterprise on GitHub runners)
function Get-VsPath {
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$p = & $vswhere -latest -products * -version '[17.0,18.0)' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
if (-not $p) { throw '[ERR] Visual Studio 2022 with C++ x64 tools not found (see https://linuxthai.org/article/apm-build-windows-1-prepare-tools)' }
return $p
}
$Script:VcVars = "$(Get-VsPath)\VC\Auxiliary\Build\vcvars64.bat"
function Enter-VsEnv {
if ($env:VSCMD_ARG_TGT_ARCH -eq 'x64') { return }
$vswhereDir = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer"
cmd /c "set `"PATH=$vswhereDir;%PATH%`" && `"$Script:VcVars`" >nul && set" | ForEach-Object {
if ($_ -match '^([^=]+)=(.*)$') { [Environment]::SetEnvironmentVariable($Matches[1], $Matches[2]) }
}
# never let XAMPP binaries leak into a build
$env:Path = ($env:Path -split ';' | Where-Object { $_ -and $_ -notmatch '(?i)xampp' }) -join ';'
if (-not (Get-Command cl.exe -ErrorAction SilentlyContinue)) { throw '[ERR] MSVC environment not loaded' }
}
function Invoke-Step([string]$Name, [scriptblock]$Block, [string]$Log) {
Write-Host "[..] $Name"
& $Block *>> $Log
if ($LASTEXITCODE) { Get-Content $Log -Tail 30; throw "[ERR] $Name failed rc=$LASTEXITCODE (log: $Log)" }
Write-Host "[OK] $Name"
}
Get-VsPath ถาม vswhere หา Visual Studio 2022 ตัวล่าสุดที่มี C++ x64 tools แล้วคืนโฟลเดอร์ติดตั้งมาให้ $Script:VcVars ต่อ path ไปที่ vcvars64.bat ถ้าหาไม่เจอสคริปต์หยุดด้วย [ERR] Visual Studio 2022 with C++ x64 tools not found พร้อมลิงก์กลับไปตอนที่ 1 (kit รุ่นก่อนบ่าย 22 ต.ค. ฝัง path ของ BuildTools ไว้ตัวเดียว ใครลง edition อื่นหรือลงคนละ path สคริปต์พังตั้งแต่ต้น)
Enter-VsEnv ทำเหมือนที่ Developer Prompt ทำ คือรัน vcvars64.bat ใน cmd (มันตั้ง PATH, INCLUDE, LIB ให้เจอ cl.exe และ dumpbin.exe) แล้วพิมพ์ตัวแปรทั้งหมดออกมาด้วย set จากนั้นเอาแต่ละบรรทัดมาตั้งค่าใน pwsh ที่รันอยู่ ถ้า cl.exe ยังไม่เจอ สคริปต์หยุดด้วย [ERR] MSVC environment not loaded
Invoke-Step ห่อทุกคำสั่งที่ยาวและเสียงดัง มันพิมพ์ [..] ชื่อขั้น บนจอ ส่ง output ทั้งหมดของคำสั่ง (ทุก stream) ต่อท้ายไฟล์ log แล้วเช็กรหัสออก ถ้าไม่ใช่ 0 จะพิมพ์ 30 บรรทัดท้ายของ log ออกจอแล้วหยุดด้วย [ERR] ชื่อขั้น failed rc=... จอเลยสะอาด มีแค่บรรทัด [..] กับ [OK] ส่วน output จริงของ CMake กับคอมไพเลอร์ไปอยู่ใน C:\apm-build\build\logs\apache.log
ลำดับที่สคริปต์รันจริง
- ตั้ง
$common(option ที่ใส่ทุกชิ้น) แล้วนิยามฟังก์ชันBuild-Cmakeที่รัน 3 ขั้น configure, build, install - build APR ติดตั้งลง
stage\apache - build APR-util (ใช้ APR ที่เพิ่งติดตั้ง)
- build httpd (ใช้ทั้ง APR กับ APR-util และ lib ของ vcpkg)
- คัดลอกไฟล์ที่
cmake --installของ httpd ไม่ติดตั้งให้:mime.types,magic,charset.convไปไว้ในconf\และโฟลเดอร์error\,icons\จากdocs\ใน source ตามที่คอมเมนต์บอก - build mod_fcgid จากโฟลเดอร์ย่อย
modules\fcgidของ source เขา - ไล่หา DLL ที่ต้องใช้แล้วคัดลอกไปไว้ข้าง
httpd.exe(หัวข้อ 6) - พิมพ์
httpd.exe -Vลง log และhttpd.exe -vลงจอ
4. ทุก option ของ CMake และเหตุผลที่ใส่
option ที่ใส่ให้ทุกชิ้น ($common)
| option | ทำอะไร | ถ้าไม่ใส่ |
|---|---|---|
-G Ninja |
ใช้ Ninja เป็นตัว build | ได้ตัว build ช้ากว่า (ปกติ CMake บน Windows จะเลือก Visual Studio generator) |
-DCMAKE_BUILD_TYPE=RelWithDebInfo |
optimize เต็มที่ และสร้างไฟล์ .pdb ไว้ด้วย |
ถ้าเป็น Release ไม่มี .pdb ไว้ debug เมื่อ Apache crash ถ้าเป็น Debug ช้าและต้องใช้ DLL รุ่น debug |
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 |
ปิดข้อห้ามของ CMake 4.x | APR, httpd, fcgid ประกาศ cmake_minimum_required ต่ำกว่า 3.5 ไว้ CMake 4.x เลยหยุดด้วย Compatibility with CMake < 3.5 has been removed |
-DCMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO=Release;RelWithDebInfo; |
บอกว่าถ้า package ของ vcpkg ไม่มี config ชื่อ RelWithDebInfo ให้ใช้ของ Release | mod_md จะไปลิงก์ libcurl-d.dll (ตัว debug) เพราะ target CURL::libcurl ของ vcpkg ไม่มี config RelWithDebInfo |
-DCMAKE_INSTALL_PREFIX=C:/apm-build/stage/apache |
ปลายทางของ cmake --install |
ไปลงที่ C:\Program Files |
-DCMAKE_PREFIX_PATH=C:/apm-build/build/vcpkg_installed/x64-windows |
ให้ CMake ไปค้นหา lib ที่นี่ก่อน | หา OpenSSL, zlib, libxml2 ไม่เจอ หรือไปเจอตัวอื่นในเครื่อง |
นอกจาก option พวกนี้ สคริปต์เรียก cmake ด้วย --fresh ทุกครั้ง ซึ่งลบ CMakeCache.txt เก่าทิ้งก่อน configure ใหม่ ที่ต้องทำเพราะค่าเก่าค้างใน cache แล้วทำให้ผลเพี้ยน (เจอจริงตอนทดลองใส่ค่า N ให้ module แล้วลบทิ้งทีหลัง)
APR 1.7.6
| option | ทำอะไร |
|---|---|
-DAPR_INSTALL_PRIVATE_H=ON |
ติดตั้ง header ภายใน (private) ของ APR ที่ httpd ต้องใช้ตอน build ใน log ขึ้นว่า Install private .h for httpd .... : ON |
-DAPR_BUILD_TESTAPR=OFF |
ไม่ build ชุดทดสอบ ประหยัดเวลา ใน log ขึ้นว่า Build test suite ... : OFF |
APR-util 1.6.5
| option | ทำอะไร |
|---|---|
-DAPU_HAVE_CRYPTO=ON |
เปิดโมดูลเข้ารหัสที่ใช้ OpenSSL ได้ไฟล์ apr_crypto_openssl-1.dll |
-DAPU_HAVE_ODBC=OFF |
ไม่เอา driver ODBC |
-DAPR_HAS_LDAP=OFF |
ไม่เอา LDAP ตรงกับเครื่อง production ที่ไม่ใช้ mod_ldap |
-DAPR_BUILD_TESTAPR=OFF |
ไม่ build ชุดทดสอบ |
ตอน configure ตัวนี้ จะเห็นใน log ว่า CMake หา OpenSSL กับ expat เจอใน vcpkg ซึ่งเป็นหลักฐานว่า CMAKE_PREFIX_PATH ทำงาน
-- Found OpenSSL: C:/apm-build/build/vcpkg_installed/x64-windows/lib/libcrypto.lib (found version "3.5.9")
-- Found EXPAT: C:/apm-build/build/vcpkg_installed/x64-windows/lib/libexpat.lib (found version "2.9.0")
httpd 2.4.69
| option | ทำอะไร |
|---|---|
-DENABLE_MODULES=i |
ขอ build ทุก module เท่าที่ prerequisite ครบ ตัวไหนขาดของ CMake ข้ามให้เองพร้อมบอกเหตุผลใน log (ดูรายการด้านล่าง) ค่าที่ httpd ยอมรับมีแค่ O, i, I, a, A ถ้าใส่ N จะ error |
-DINSTALL_MANUAL=OFF |
ไม่ติดตั้งคู่มือ HTML ของ Apache (ใหญ่และไม่จำเป็น) |
-DPCRE_INCLUDE_DIR, -DPCRE_LIBRARIES |
ชี้ไป pcre2-8.lib ใน vcpkg ใช้กับ regex |
-DPCRE_CFLAGS=-DHAVE_PCRE2 |
บอกโค้ดว่า lib ที่ให้เป็น PCRE2 (รุ่นใหม่) ไม่ใช่ PCRE1 |
-DNGHTTP2_INCLUDE_DIR, -DNGHTTP2_LIBRARIES |
ชี้ไป nghttp2.lib ใช้กับ mod_http2 |
-DBROTLI_INCLUDE_DIR, -DBROTLI_LIBRARIES |
ชี้ไป brotlienc.lib กับ brotlicommon.lib (สองไฟล์ คั่นด้วย ; ใน option เดียว) ใช้กับ mod_brotli |
-DCURL_INCLUDE_DIR, -DCURL_LIBRARIES |
ชี้ไป libcurl.lib ใช้กับ mod_md |
-DJANSSON_INCLUDE_DIR, -DJANSSON_LIBRARIES |
ชี้ไป jansson.lib ใช้กับ mod_md |
lib ที่ไม่ต้องบอก path เอง (OpenSSL, zlib, libxml2) CMake ค้นเจอเองผ่าน CMAKE_PREFIX_PATH ใน log ของ httpd ขึ้นว่า
-- Found LibXml2: C:/apm-build/build/vcpkg_installed/x64-windows/lib/libxml2.lib (found version "2.15.4")
-- Found OpenSSL: C:/apm-build/build/vcpkg_installed/x64-windows/lib/libcrypto.lib (found version "3.5.9")
-- Found ZLIB: C:/apm-build/build/vcpkg_installed/x64-windows/lib/z.lib (found version "1.3.2")
-- Found CURL: C:/apm-build/build/vcpkg_installed/x64-windows/share/curl/CURLConfig.cmake (found version "8.23.0")
ส่วนสรุปที่ CMake พิมพ์ท้าย configure ของ httpd บอกว่าตรวจเจออะไร และ module ไหนถูกข้าม
-- Summary of feature detection:
--
-- LIBXML2_FOUND ............ : TRUE
-- LUA51_FOUND .............. : FALSE
-- NGHTTP2_FOUND ............ : TRUE
-- OPENSSL_FOUND ............ : TRUE
-- ZLIB_FOUND ............... : TRUE
-- BROTLI_FOUND ............. : TRUE
-- CURL_FOUND ............... : TRUE
-- JANSSON_FOUND ............ : TRUE
-- APR_HAS_LDAP ............. : FALSE
-- APR_HAS_XLATE ............ : FALSE
-- APU_HAVE_CRYPTO .......... : TRUE
...
-- Modules not built:
-- mod_authnz_ldap
-- mod_socache_dc
-- mod_charset_lite
-- mod_ldap
-- mod_lua
ทั้ง 5 module ที่ไม่ถูก build ไม่ใช่ปัญหา เพราะเครื่อง production ไม่ได้ใช้เลย mod_ldap กับ mod_authnz_ldap ขาด LDAP, mod_charset_lite ขาด XLATE ของ APR, mod_socache_dc ต้องใช้ lib ที่ไม่มี, และ mod_lua โมดูลนี้ httpd ค้นหาแค่ Lua 5.1 ซึ่งไม่มีในชุดของเรา ตรงตามที่ log บอกว่า mod_lua was requested but couldn't be built due to a missing prerequisite (LUA51_FOUND)
mod_fcgid 2.3.9
ไม่มี option เพิ่มเลย (@()) ใช้เฉพาะ $common มันเป็นโปรเจกต์ CMake เล็ก ๆ 13 ขั้น ได้ไฟล์เดียวคือ mod_fcgid.so ผมจัดให้ build หลัง httpd เสมอ เพราะต้องใช้ header กับ .lib ของ APR และ httpd ที่เพิ่งติดตั้งลง stage\apache
5. รัน build ทีละขั้น
ขั้นที่ 5.1 สั่งรัน
ที่ไหน: pwsh (user ปกติ) โฟลเดอร์ C:\apm-build
pwsh -NoProfile -ExecutionPolicy Bypass -File scripts\20-apache.ps1
ขั้นที่ 5.2 ดูจอระหว่างรอ
รอบแรกใช้เวลาประมาณ 4 นาทีบนเครื่องผม (i5-12500H) จอจะเดินเป็นคู่ [..] แล้ว [OK] ทีละขั้น ทั้งหมด 12 คู่ (4 ชิ้น คูณ configure, build, install) ถ้าเห็นหยุดนิ่งที่ [..] httpd build นานเป็นนาทีถือว่าปกติ เพราะนี่คือขั้นที่ใหญ่ที่สุด (523 ขั้นของ Ninja)
[..] apr configure
[OK] apr configure
[..] apr build
[OK] apr build
[..] apr install
[OK] apr install
[..] apr-util configure
[OK] apr-util configure
[..] apr-util build
[OK] apr-util build
[..] apr-util install
[OK] apr-util install
[..] httpd configure
[OK] httpd configure
[..] httpd build
[OK] httpd build
...
ใช้เวลาแต่ละขั้นต่างกันมาก เช่น configure ของ APR ใช้ 1.6 วินาที ของ httpd ใช้ 5.1 วินาที (ตัวเลขจาก log) ส่วนขั้น build คือที่กินเวลาเกือบทั้งหมด จำนวนขั้นที่ Ninja ทำ: APR 164, APR-util 94, httpd 523, mod_fcgid 13
ขั้นที่ 5.3 ดูเลขความคืบหน้าสดจาก log (ไม่บังคับ)
ถ้าอยากรู้ว่าตอนนี้ถึงไหน เปิด pwsh หน้าต่างที่สองแล้วดูท้าย log
Get-Content C:\apm-build\build\logs\apache.log -Tail 3 -Wait
จะเห็นบรรทัดแบบ [124/523] Linking C shared library libhttpd.dll เลขหน้าคือขั้นที่ทำแล้ว เลขหลังคือทั้งหมด กด Ctrl+C เพื่อเลิกดู (ไม่กระทบ build)
ขั้นที่ 5.4 ดูบรรทัดสุดท้ายเมื่อเสร็จ
ท้ายสคริปต์ควรเห็นสามบรรทัดนี้ รายการ DLL จะถูกเรียงตามตัวอักษร (ผมตัดบรรทัดที่ตามมาจาก httpd.exe -v ออกไปครึ่งหนึ่ง ดูของเต็มในขั้น 7.1)
[OK] runtime DLLs: brotlicommon.dll brotlienc.dll iconv-2.dll jansson.dll libcrypto-3-x64.dll libcurl.dll libexpat.dll libssl-3-x64.dll libxml2.dll nghttp2.dll pcre2-8.dll z.dll
[OK] apache -> C:/apm-build/stage/apache
Server version: Apache/2.4.69 (Win64)
Server built: Oct 22 2026 12:19:57
DLL ที่ถูกคัดลอกมี 12 ตัวตามนี้ (บรรทัด runtime DLLs เรียงจากโค้ดของสคริปต์ ผมตรวจชื่อกับไฟล์ที่อยู่ใน stage\apache\bin ของเครื่องผมแล้วครบทั้ง 12) บรรทัด Server built จะเป็นเวลา build ของคุณเอง
ขั้นที่ 5.5 รันซ้ำได้ไหม
ได้ สั่งซ้ำได้เลย ตัว Ninja จะ build เฉพาะที่เปลี่ยน และ --fresh แค่ configure ใหม่ ผมวัดรอบที่ cache พร้อมแล้วได้ 1.6 นาที (ส่วนใหญ่หมดไปกับ configure ใหม่ทั้งสี่ชิ้นกับไล่ DLL)
6. ไล่ DLL ที่ต้องวางข้าง httpd.exe ด้วย dumpbin
ปัญหาของ Windows คือเมื่อ httpd.exe โหลด mod_ssl.so มันต้องการ libssl-3-x64.dll ซึ่ง Windows ค้นหาตามลำดับโดยมีโฟลเดอร์ของตัว exe เป็นที่แรก ถ้าไม่เจอ module ที่ต้องใช้ไม่โหลดและ Apache ไม่ start เราเลยต้องวาง DLL ของ vcpkg ไว้ใน stage\apache\bin ข้าง httpd.exe คำถามคือต้องวางตัวไหนบ้าง
วิธีที่ง่ายสุดคือคัดลอก DLL ทั้งโฟลเดอร์ bin ของ vcpkg ซึ่งมี 18 ตัวไปไว้เลย แต่ผมไม่ทำ เพราะจะมีของที่ไม่มีใครเรียกติดไปด้วย และบอกไม่ได้ว่าตัวไหนจำเป็น สคริปต์เลยใช้ dumpbin /dependents ซึ่งเป็นเครื่องมือของ MSVC ที่พิมพ์รายชื่อ DLL ที่ไฟล์หนึ่ง import มา
ลองรัน dumpbin ด้วยมือก่อน
ที่ไหน: pwsh ธรรมดา ไม่ใช่ Developer Prompt dumpbin.exe ไม่อยู่ใน PATH ของ pwsh ธรรมดา ต้องหา path เต็มก่อน (ขั้น 6.1) ซึ่งสคริปต์ไม่ต้องทำเพราะ Enter-VsEnv จัดการให้แล้ว
ขั้นที่ 6.1 หา dumpbin.exe
$vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -version '[17.0,18.0)' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
$dumpbin = (Get-ChildItem "$vs\VC\Tools\MSVC\*\bin\Hostx64\x64\dumpbin.exe" | Select-Object -Last 1).FullName
$dumpbin
C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x64\dumpbin.exe
เลขรุ่น 14.44.35207 ของคุณอาจต่างได้
ขั้นที่ 6.2 ดู import ของ httpd.exe
& $dumpbin /nologo /dependents C:\apm-build\stage\apache\bin\httpd.exe
Dump of file C:\apm-build\stage\apache\bin\httpd.exe
File Type: EXECUTABLE IMAGE
Image has the following dependencies:
libhttpd.dll
libapr-1.dll
libaprutil-1.dll
VCRUNTIME140.dll
api-ms-win-crt-runtime-l1-1-0.dll
api-ms-win-crt-stdio-l1-1-0.dll
...
KERNEL32.dll
ผมตัดบรรทัด api-ms-win-crt-* และตารางสรุปท้ายออก httpd.exe เองเรียกแค่สามตัวแรก (ที่ build เอง) ส่วนที่เหลือเป็น DLL ของ Windows กับ runtime ของ MSVC ซึ่งมีอยู่ในเครื่องอยู่แล้ว
ขั้นที่ 6.3 ดู import ของ module ที่ซับซ้อนที่สุด
& $dumpbin /nologo /dependents C:\apm-build\stage\apache\modules\mod_md.so
Image has the following dependencies:
libcrypto-3-x64.dll
libcurl.dll
jansson.dll
libhttpd.dll
libapr-1.dll
libaprutil-1.dll
VCRUNTIME140.dll
api-ms-win-crt-string-l1-1-0.dll
...
KERNEL32.dll
mod_md.so ต้องการ libcrypto-3-x64.dll, libcurl.dll, jansson.dll จาก vcpkg ส่วน libcurl.dll เองก็ import ต่ออีก (ขั้นตอนถัดไป) นี่คือเหตุผลที่สคริปต์ต้องไล่ต่อเนื่องเป็นทอด ๆ
ขั้นที่ 6.4 ดูว่า libcurl.dll ลากอะไรต่อ
& $dumpbin /nologo /dependents C:\apm-build\stage\apache\bin\libcurl.dll | Select-String '\.dll'
ถ้ามี z.dll ในรายการ แปลว่า libcurl.dll พึ่ง zlib ที่ vcpkg build ไว้ ผมเทียบกับชื่อใน vcpkg_installed\x64-windows\bin แล้วพบว่า libcurl ลาก z.dll ต่อ ส่วน libxml2 ลาก z.dll กับ iconv-2.dll brotlienc ลาก brotlicommon libssl-3-x64 ลาก libcrypto-3-x64
สคริปต์ทำสิ่งเดียวกันเป็นอัตโนมัติ
ส่วนท้ายของสคริปต์ทำตามนี้ อ่านตามลำดับ
$availคือทะเบียนชื่อ DLL (ตัวพิมพ์เล็ก) ทั้งหมดที่อยู่ในvcpkg_installed\x64-windows\binพร้อม path เต็ม ใช้ตอบคำถามว่า "ชื่อ DLL ที่เห็นนี้ เป็นของ vcpkg ไหม" DLL ของ Windows อย่างKERNEL32.dllไม่อยู่ในทะเบียน เลยถูกข้ามไปเอง$debugOnlyคือชื่อ DLL ที่มีเฉพาะในโฟลเดอร์debug\binของ vcpkg แต่ไม่มีในbinในเครื่องผมได้แก่jansson_d.dll,libcurl-d.dll,libexpatd.dll,pcre2-8d.dll,zd.dllและอีกไม่กี่ตัว นี่คือรายชื่อต้องห้าม$queueคือคิวของไฟล์ที่ต้องสแกน เริ่มด้วยทุก.exeกับ.dllในstage\apache\binและทุก.soในstage\apache\modules- วนจนคิวว่าง ดึงไฟล์หนึ่งออกมา เรียก
dumpbin /dependentsแล้วดูทีละบรรทัดผลลัพธ์ (ตัดช่องว่าง แปลงเป็นตัวพิมพ์เล็ก)- ถ้าชื่ออยู่ใน
$debugOnlyหยุดสคริปต์ทันที ด้วย[ERR] release binary imports a debug DLL: <ชื่อ> - ถ้าชื่ออยู่ใน
$availและยังไม่เคยจด ให้จดลง$needและใส่ DLL ตัวนั้นเข้าคิวด้วย เพื่อให้มันถูกสแกนต่อว่าเรียกอะไรอีก (นี่คือจุดที่ทำให้ได้z.dllจากlibcurl.dll) - ชื่ออื่น (บรรทัดหัว ตารางสรุป DLL ของ Windows) ไม่เข้าเงื่อนไขใดเลย ถูกข้ามไป
- ถ้าชื่ออยู่ใน
- จบลูป คัดลอกทุกตัวใน
$needไปไว้ในstage\apache\bin - คัดลอก
legacy.dllเพิ่มอีกตัวด้วยมือ เพราะ OpenSSL 3 โหลด provider ตอนรันด้วยชื่อ ไม่ได้ import ตรง ๆ dumpbin เลยมองไม่เห็น ตัวนี้ไว้อ่านไฟล์ PKCS#12 รุ่นเก่า
ผลสุดท้ายคือ 12 ตัวที่ถูก import จริง บวก legacy.dll อีก 1 ตัว จาก 18 ตัวใน vcpkg ที่เหลืออีก 5 ตัวไม่มีใครเรียก เลยไม่ถูกคัดลอก (brotlidec, charset-1, pcre2-16, pcre2-32, pcre2-posix) ตรวจจำนวนได้เลย
(Get-ChildItem C:\apm-build\stage\apache\bin\*.dll).Count
17
17 ตัวคือ DLL ที่ build เอง 4 ตัว (libapr-1, libaprutil-1, apr_crypto_openssl-1, libhttpd) บวก 12 ตัวที่ไล่เจอ บวก legacy.dll
ตัวกันไม่ให้ลิงก์ DLL ตัว debug
เรื่องที่เล่าตอนต้น เกิดขึ้นเพราะ vcpkg สร้างไว้สองชุด คือ bin\ (release) กับ debug\bin\ (debug) ชื่อไฟล์ของชุด debug ต่างออกไป เช่น libcurl-d.dll และ zd.dll ถ้า CMake เลือก config ผิด .so ที่เราสร้างด้วย RelWithDebInfo จะอ้างชื่อพวกนี้ และ Windows หาไม่เจอบนเครื่องอื่น แต่ บนเครื่อง build ที่ PATH ของคุณบังเอิญชี้เข้า vcpkg อยู่ มันอาจหาเจอจนไม่รู้ตัว ผมถึงไม่ไว้ใจการทดสอบด้วยการรันบนเครื่องเดียวกัน และให้สคริปต์เช็กจากชื่อใน import โดยตรง
option -DCMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO=Release;RelWithDebInfo; ในหัวข้อ 4 คือวิธีแก้ที่ต้นเหตุ และตัวกันนี้คือด่านตรวจว่าวิธีแก้ยังได้ผลอยู่ ถ้าวันหน้า vcpkg อัปเดตแล้วทำให้มันกลับมาพัง คุณจะเห็นสคริปต์หยุดพร้อมชื่อ DLL ตัวที่ผิดทันที
7. ตรวจผลทีละอย่าง
ขั้นที่ 7.1 ตรวจเวอร์ชันของ httpd
ที่ไหน: pwsh ธรรมดา โฟลเดอร์ใดก็ได้ ใส่ 2>&1 เพราะ httpd เขียนข้อความเวอร์ชันออกทาง error stream
C:\apm-build\stage\apache\bin\httpd.exe -V 2>&1 | Select-String "Server (version|loaded|MPM)"
Server version: Apache/2.4.69 (Win64)
Server loaded: APR 1.7.6, APR-UTIL 1.6.5, PCRE 10.49 2026-09-28
Server MPM: WinNT
สามบรรทัดนี้บอกครบ: httpd 2.4.69 แบบ 64 บิต, APR/APR-util รุ่นที่เรา build เอง, PCRE รุ่น 10.49 (คือ PCRE2 จาก vcpkg) และ MPM แบบ WinNT ซึ่งเป็นแบบเดียวที่มีบน Windows
ขั้นที่ 7.2 นับ module
(Get-ChildItem C:\apm-build\stage\apache\modules\*.so).Count
119
ผมนับได้ 119 ในเครื่องที่วัดล่าสุด (บันทึกเก่าของผมจดไว้ 120) ถ้าคุณได้ 119 หรือ 120 ก็ปกติ ที่สำคัญกว่าเลขคือ module ที่ config ของเราเรียกใช้ครบทุกตัว ขั้นถัดไปเช็กตรงนั้น
ขั้นที่ 7.3 เช็กว่า module ที่ template เรียกมีไฟล์ครบ
ไฟล์ templates\httpd.conf มีบรรทัด LoadModule ทั้งหมด 41 บรรทัด คำสั่งนี้อ่านทุกบรรทัดแล้วรายงานเฉพาะไฟล์ .so ที่ ไม่มี ใน stage
Select-String -Path C:\apm-build\templates\httpd.conf -Pattern '^LoadModule \w+ (modules/\S+)' |
ForEach-Object { $_.Matches[0].Groups[1].Value } |
Where-Object { -not (Test-Path "C:\apm-build\stage\apache\$_") }
ถ้าทุกอย่างครบ จะไม่มีอะไรพิมพ์ออกมาเลย ถ้ามีชื่อไฟล์โผล่ แปลว่า module นั้นไม่ถูก build ให้เปิดรายการ "Modules not built" ใน apache.log
ขั้นที่ 7.4 สร้างโฟลเดอร์ที่ config ทดสอบต้องใช้
httpd -t ตรวจว่าโฟลเดอร์ที่ config อ้างถึงมีอยู่จริง และ stage ยังไม่มีโฟลเดอร์พวกนั้น (ตัว deploy.cmd ในตอนที่ 7 เป็นคนสร้างให้บน C:\APM) เราเลยสร้างชั่วคราวใน stage
$S = 'C:\apm-build\stage'
New-Item -ItemType Directory -Force "$S\www\default","$S\logs\apache","$S\tmp" | Out-Null
ขั้นที่ 7.5 วางไฟล์ php-cgi.exe ปลอมชั่วคราว
config มีบรรทัดนี้ (ตัดมาจาก templates\httpd.conf)
# inner \" quotes: mod_fcgid splits the wrapper command line on spaces (apr_tokenize_to_argv), so a path
# like "D:/APM test/php/php-cgi.exe" must be quoted inside the directive argument as well
FcgidWrapper "\"${APM}/php/php-cgi.exe\"" .php
สังเกตว่ามีเครื่องหมายคำพูดสองชั้น ชั้นนอกเป็นของ Apache ที่ครอบ argument ส่วนชั้นในที่ escape ด้วย \" ถูกส่งต่อให้ mod_fcgid เหตุผลคือ mod_fcgid เอาค่านี้ไปเป็นคำสั่งรัน แล้วแยกคำด้วยช่องว่าง (ฟังก์ชัน apr_tokenize_to_argv) ถ้า path ติดตั้งมีช่องว่าง เช่น D:/APM test/php/php-cgi.exe มันจะอ่านแค่ D:/APM เป็นชื่อโปรแกรม แล้ว Apache ฟ้อง Wrapper D:/APM cannot be accessed (path ในข้อความนี้เป็นตัวอย่างสมมติ) template รุ่นก่อนบ่าย 22 ต.ค. เขียนไม่มีเครื่องหมายคำพูดชั้นใน ใช้ได้เฉพาะ path ที่ไม่มีช่องว่าง ตอนนี้แก้แล้ว
mod_fcgid ตรวจตอนอ่าน config ว่าไฟล์นั้นมีจริง แต่ PHP ยังไม่ได้ build (ตอนที่ 6) เราเลยวางไฟล์เปล่าชื่อเดียวกันไว้ให้ผ่านการตรวจ
New-Item -ItemType Directory -Force "$S\php" | Out-Null
New-Item -ItemType File "$S\php\php-cgi.exe" | Out-Null
ผมลองเอาออกแล้วรัน -t ได้ error จริง (ดูตาราง "ถ้าพังตรงนี้") ถ้าคุณ build PHP เสร็จแล้ว stage\php\php-cgi.exe ตัวจริงมีอยู่ ข้ามขั้นนี้ได้และห้ามเขียนทับ
ขั้นที่ 7.6 สร้างไฟล์ config ทดสอบจาก template
template มีช่องว่างให้เติม {{APM_ROOT}}, {{HTTP_PORT}} และ {{BIND}} (ที่อยู่ที่ Apache ฟัง บรรทัดจริงคือ Listen {{BIND}}:{{HTTP_PORT}} ค่าปกติเป็น 127.0.0.1 คือรับเฉพาะเครื่องนี้ ตอนที่ 7 มีสวิตช์ -Lan เปลี่ยนเป็น 0.0.0.0) ซึ่งตอน deploy จริงสคริปต์ 90-deploy.ps1 เป็นคนเติม ที่นี่เติมเองด้วย -replace (ใช้พอร์ต 8099 สมมติ เพราะ -t ไม่เปิดพอร์ตจริง และ BIND เป็น 127.0.0.1) และตัดบรรทัด Include conf/extra/apm-ssl.conf ทิ้ง เพราะไฟล์ TLS กับใบรับรองยังไม่มีจนกว่าจะ deploy
$conf = (Get-Content C:\apm-build\templates\httpd.conf -Raw) `
-replace '\{\{APM_ROOT\}\}', ($S -replace '\\','/') `
-replace '\{\{HTTP_PORT\}\}', '8099' `
-replace '\{\{BIND\}\}', '127.0.0.1' `
-replace 'Include conf/extra/apm-ssl.conf', ''
Set-Content "$S\apache\conf\apm-conftest.conf" $conf -Encoding ascii
ขั้นที่ 7.7 ตรวจ config
C:\apm-build\stage\apache\bin\httpd.exe -t -f C:\apm-build\stage\apache\conf\apm-conftest.conf
Syntax OK
-t อ่าน config ทั้งหมด และโหลดทุก module ใน LoadModule จริง แต่ไม่เปิดพอร์ตและไม่ start server ดังนั้น Syntax OK ยืนยันได้สองเรื่องพร้อมกัน คือ config ถูกไวยากรณ์ และ .so ทุกตัวกับ DLL ที่มันพึ่งโหลดขึ้นได้ ซึ่งเป็นการพิสูจน์ที่ดีกว่าการดูรายชื่อ DLL ด้วยตา
ขั้นที่ 7.8 ดูรายชื่อ module ที่โหลดจริง (ไม่บังคับ)
C:\apm-build\stage\apache\bin\httpd.exe -M -f C:\apm-build\stage\apache\conf\apm-conftest.conf 2>&1 | Select-Object -First 8
Loaded Modules:
core_module (static)
win32_module (static)
mpm_winnt_module (static)
http_module (static)
so_module (static)
access_compat_module (shared)
actions_module (shared)
ในเครื่องผมได้ 46 module ทั้งหมด คือ 5 ตัวที่ฝังใน httpd.exe (static) บวก 41 ตัวจาก LoadModule ซึ่งตรงกับตัวเลข 41 ที่นับในขั้น 7.3
ขั้นที่ 7.9 เก็บกวาดไฟล์ทดสอบ
ลบไฟล์ config ทดสอบกับไฟล์ปลอม เพื่อไม่ให้ไปปนกับของจริงตอน deploy ส่วน www, logs, tmp ใน stage ปล่อยไว้ได้ เพราะ deploy.cmd ก๊อปปี้เฉพาะ stage\apache, stage\php, stage\mariadb
Remove-Item C:\apm-build\stage\apache\conf\apm-conftest.conf
Remove-Item C:\apm-build\stage\php\php-cgi.exe
บรรทัดที่สองข้ามถ้าคุณมี PHP ตัวจริงอยู่แล้ว (ขั้น 7.5) ส่วนโฟลเดอร์ stage\php ที่ว่างอยู่ ตอนที่ 6 จะลบทิ้งเองก่อน build
โครงของ stage\apache ที่ได้
8. อ่าน apache.log ให้เป็น ก่อนที่จะต้องใช้
log ไฟล์เดียวนี้ยาวมาก (ของผม apache.log มี 1,687 บรรทัด ใหญ่ประมาณ 130 KB) ถ้าเปิดอ่านตั้งแต่บรรทัดแรกจะหลงทาง ผมใช้สามคำสั่งนี้ตามลำดับเวลามีอะไรผิดปกติ ทุกคำสั่งรันใน pwsh ธรรมดา
ขั้นที่ 8.1 หา error ตัวแรก
error ตัวแรกคือต้นเหตุ ตัวหลัง ๆ มักเป็นผลต่อเนื่อง คอมไพเลอร์ของ MSVC ขึ้นด้วยรูปแบบ error C ตามด้วยเลขรหัส ส่วน Ninja ขึ้น FAILED: นำหน้าคำสั่งที่ล้ม
Select-String -Path C:\apm-build\build\logs\apache.log -Pattern 'error C\d+|FAILED:|CMake Error' | Select-Object -First 5
ถ้า build ผ่านทั้งหมด คำสั่งนี้ไม่พิมพ์อะไรเลย
ขั้นที่ 8.2 ดูรายการที่ CMake บอกว่าข้าม
บรรทัดที่ขึ้นว่า was requested but couldn't be built คือ module ที่ถูกข้ามเพราะขาดของ ตรวจว่ามีแค่ 5 ตัวที่เรารู้จัก
Select-String -Path C:\apm-build\build\logs\apache.log -Pattern "couldn't be built" | ForEach-Object Line
-- mod_authnz_ldap was requested but couldn't be built due to a missing prerequisite (APR_HAS_LDAP)
-- mod_socache_dc was requested but couldn't be built due to a missing prerequisite (AN_UNIMPLEMENTED_SUPPORT_LIBRARY_REQUIREMENT)
-- mod_charset_lite was requested but couldn't be built due to a missing prerequisite (APR_HAS_XLATE)
-- mod_ldap was requested but couldn't be built due to a missing prerequisite (APR_HAS_LDAP)
-- mod_lua was requested but couldn't be built due to a missing prerequisite (LUA51_FOUND)
ถ้าคุณเห็น module อื่นโผล่ในรายการนี้ เช่น mod_ssl หรือ mod_http2 แปลว่า lib ของ vcpkg ตัวนั้นหายไป ให้กลับไปตอนที่ 2
ขั้นที่ 8.3 ดูว่าขั้นไหนกินเวลา
เลขหน้า [n/total] ของ Ninja บอกว่าแต่ละชิ้นมีกี่ขั้น ถ้าอยากเห็นตัวเลขของตัวเอง ดูบรรทัดสุดท้ายที่ขึ้นเลข total ของแต่ละชิ้น
Select-String -Path C:\apm-build\build\logs\apache.log -Pattern '^\[1/\d+\]' | ForEach-Object { ($_.Line -split ' ')[0] }
ผลของผมเป็น [1/164] สำหรับ APR, [1/94] สำหรับ APR-util, [1/523] สำหรับ httpd และ [1/13] สำหรับ mod_fcgid ซึ่งเป็นที่มาของตัวเลขใน statgrid ต้นบทความ
ทำไมต้องมีไฟล์ .pdb
เราเลือก RelWithDebInfo ตั้งแต่ต้นเพื่อให้มีไฟล์ .pdb ข้างทุก .exe .dll .so ไฟล์นี้เก็บชื่อฟังก์ชันและเลขบรรทัดของโค้ด ไม่ได้ทำให้โปรแกรมช้าลง และไม่ถูกโหลดตอนรัน แต่ถ้าวันหนึ่ง httpd.exe crash คุณเปิด crash dump ด้วย WinDbg หรือ Visual Studio แล้วเห็นชื่อฟังก์ชันจริงแทนที่จะเป็นตัวเลขที่อยู่ ตอน deploy (ตอนที่ 7) สคริปต์ไม่ก๊อป .pdb ไปไว้ใน C:\APM เพื่อลดขนาด ส่วนที่ stage\ ยังเก็บไว้ครบ ถ้าจะ debug ให้ใช้ชุดที่อยู่ที่นี่
ทำไมเลือก mod_fcgid ไม่ใช่ตัวอื่น
บน Linux เครื่อง production รัน PHP ผ่าน php-fpm ซึ่งไม่มีบน Windows ตัวที่ใกล้เคียงที่สุดและเสถียรบน Windows คือ mod_fcgid ที่สั่ง php-cgi.exe ให้ทำงานเป็นกลุ่ม process ผมเลยเลือกตัวนี้ รายละเอียดของการตั้งค่าอยู่ใน templates\httpd.conf (บรรทัด FcgidWrapper, FcgidMaxProcesses 32 และพวกเดียวกัน) ซึ่งตอนที่ 6 กับตอนที่ 7 จะกลับมาพูดถึงตอนทดสอบกับ PHP จริง
9. ถ้าพังตรงนี้
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
[ERR] Visual Studio 2022 with C++ x64 tools not found (see https://linuxthai.org/article/apm-build-windows-1-prepare-tools) |
vswhere ไม่เจอ Visual Studio 2022 ที่มี component C++ x64 (ยังไม่ได้ลง หรือลงแต่ไม่ได้ติ๊ก "MSVC v143 - VS 2022 C++ x64/x86 build tools" หรือเป็น Visual Studio 2019 หรือ 2026 ซึ่งสคริปต์กรองออก) |
รันคำสั่งในขั้น 1.5 ดู ถ้าว่างให้เปิด Visual Studio Installer แล้วติ๊ก workload C++ ตามตอนที่ 1 ไม่ต้องแก้ไฟล์สคริปต์ และติดตั้งไว้ path ไหนก็ได้ |
[ERR] MSVC environment not loaded |
เจอ Visual Studio แล้ว แต่รัน vcvars64.bat แล้ว cl.exe ยังไม่โผล่ (ติดตั้งค้างหรือ component C++ เสียหาย) |
ใน Visual Studio Installer กด Repair แล้วรันสคริปต์ใหม่ |
cmake หรือ ninja is not recognized |
PATH ยังไม่อัปเดตหลังติดตั้ง | ปิด pwsh แล้วเปิดใหม่ หรือกลับไปตอนที่ 1 |
CMake ไม่รู้จัก option --fresh |
CMake เก่ากว่า 3.24 | อัปเดต CMake (winget upgrade Kitware.CMake) |
Compatibility with CMake < 3.5 has been removed |
รัน cmake ด้วยมือโดยไม่ใส่ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 |
ใช้สคริปต์แทน หรือใส่ option นั้น (CMake 4.x ปฏิเสธไฟล์ CMake เก่าของ APR/httpd/fcgid) |
Unexpected enablement value "N" for mod_lua |
ใส่ -DENABLE_LUA=N (httpd รับแค่ O, i, I, a, A) หรือค่าเก่าค้างใน cache |
เอา option นั้นออก แล้วรันสคริปต์ใหม่ --fresh จะล้าง cache ให้ |
[ERR] release binary imports a debug DLL: libcurl-d.dll (หรือ zd.dll ฯลฯ) |
.so ลิงก์ไปที่ lib ชุด debug ของ vcpkg |
ตรวจว่า option CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO ยังอยู่ใน $common ของสคริปต์ ถ้าเพิ่งแก้ไฟล์ ให้ลบโฟลเดอร์ build\httpd แล้วรันใหม่ |
mod_lua ถูก build ด้วย Lua 5.5 |
ใส่ lua ใน deps\apache\vcpkg.json แล้ว FindLua51 ไปจับ Lua รุ่นใหม่ที่เข้ากันไม่ได้ |
เอา lua ออกจาก manifest แล้วรัน 10-deps-vcpkg.ps1 ใหม่ (vcpkg ถอดให้เอง) |
[ERR] httpd build failed rc=1 (log: ...) พร้อม 30 บรรทัดท้าย |
คอมไพล์ error ที่ใดที่หนึ่ง | เปิด apache.log หาคำว่า error C หรือ FAILED: ตัวแรก (ไม่ใช่ warning) บรรทัดนั้นบอกไฟล์และบรรทัดที่พัง |
| หา OpenSSL / zlib / libxml2 ไม่เจอตอน configure | lib ของ vcpkg ไม่ครบ หรือ CMAKE_PREFIX_PATH ชี้ผิดที่ |
เช็กขั้น 1.3 และรัน 10-deps-vcpkg.ps1 ซ้ำให้จบ |
httpd -t ขึ้น Wrapper ...php-cgi.exe cannot be accessed: (720003)The system cannot find the path specified. |
ยังไม่มี stage\php\php-cgi.exe |
ทำขั้น 7.5 (ผมได้ข้อความนี้จริงตอนไม่วางไฟล์ปลอม) |
Apache ขึ้น Wrapper D:/APM cannot be accessed (path ในข้อความอาจต่างจากนี้) |
path ติดตั้งมีช่องว่าง และ FcgidWrapper ไม่มีเครื่องหมายคำพูดชั้นใน (mod_fcgid แยกคำสั่งด้วยช่องว่าง) |
ใช้ templates\httpd.conf รุ่นใหม่ที่เขียน FcgidWrapper "\"${APM}/php/php-cgi.exe\"" .php ถ้า deploy ไปแล้วให้แก้บรรทัดเดียวกันใน httpd.conf ของ C:\APM หรือโหลด kit ใหม่แล้ว deploy.cmd -ResetConfig |
httpd -t ขึ้น DocumentRoot '.../stage/www/default' is not a directory, or is not readable |
ยังไม่ได้สร้างโฟลเดอร์ | ทำขั้น 7.4 |
httpd -t ขึ้น AH02291: Cannot access directory '.../stage/logs/apache/' for main error log |
ไม่มีโฟลเดอร์ logs\apache |
ทำขั้น 7.4 |
httpd -t ขึ้นว่าหา mime.types ไม่เจอ |
ขั้นคัดลอกไฟล์จาก docs\conf ไม่ได้ทำ (สคริปต์พังก่อนถึง) |
รัน 20-apache.ps1 ให้จบทั้งฉบับ ไม่ใช่รันครึ่งเดียว |
Windows แจ้ง "ไม่พบ <ชื่อ>.dll" ตอนรัน httpd.exe |
DLL ไม่อยู่ข้าง httpd.exe |
ดูว่าสคริปต์จบด้วย [OK] runtime DLLs: ... และมีไฟล์ในขั้น 6 ครบ |
ใน apache.log มีข้อความถาม specify a file name or directory name on the target (F = file, D = directory)? |
คำสั่งคัดลอกของ cmake --install ของ httpd ถามระหว่างทาง (ไม่มีใครตอบ) |
ไม่ต้องทำอะไร ในการ build ของผม log มีข้อความนี้สองครั้งและทุกขั้นยังจบด้วย [OK] |
ใน log มี warning C4996 (stricmp, getenv) และ warning C4244 |
คำเตือนจากโค้ดของ Apache และ mod_fcgid เอง | ไม่ใช่ปัญหา build จบปกติ |
| ช้าผิดปกติ (หลายสิบนาที) | Defender สแกนไฟล์ .obj ที่เกิดใหม่ |
ตั้ง exclusion ตามตอนที่ 1 |
ข้อสุดท้ายที่ควรรู้ ถ้าสคริปต์ล้มกลางทาง แก้เหตุแล้วรันซ้ำจากต้นได้เลยโดยไม่ต้องลบอะไร เพราะ --fresh ล้าง cache ของ CMake ให้ และ Ninja ข้ามไฟล์ที่คอมไพล์เสร็จแล้ว
10. เช็กลิสต์ก่อนไปตอนที่ 4
- สคริปต์
20-apache.ps1จบด้วยบรรทัด[OK] apache -> C:/apm-build/stage/apacheและServer version: Apache/2.4.69 (Win64) httpd.exe -VรายงานAPR 1.7.6, APR-UTIL 1.6.5, PCRE 10.49และServer MPM: WinNT(ขั้น 7.1)- มีไฟล์
mod_fcgid.soและmod_ssl.soในstage\apache\modulesและนับไฟล์.soได้ราว 119-120 ไฟล์ (ขั้น 7.2) - คำสั่งตรวจ
LoadModuleในขั้น 7.3 ไม่พิมพ์อะไรออกมาเลย httpd.exe -tกับ config ทดสอบได้Syntax OK(ขั้น 7.7)- มี DLL 17 ตัวใน
stage\apache\binและไม่มีไฟล์ชื่อลงท้าย-d.dllหรือd.dllของ vcpkg ปนอยู่ - ลบไฟล์ทดสอบ
apm-conftest.confแล้ว (ขั้น 7.9)
ใครอยากได้ APM ไปใช้เลยโดยไม่ต้องคอมไพล์ ผมปล่อยตัวที่ build เสร็จแล้วไว้ที่ github.com/ChonLaWan/APM-Server โหลด APM-1.0.0-win64.zip (ราว 140 MB) จากหน้า Releases เทียบ sha256 กับไฟล์ .sha256 ที่แนบไว้ แตกไว้ที่ไหนก็ได้ แล้วเปิด manager\APMManager.exe ตัว Manager จะเลือกพอร์ต เขียน config ออก cert และสร้างฐานข้อมูลให้เอง มีปุ่ม Check for updates ที่โหลดรุ่นใหม่จาก Releases มาตรวจ sha256 แล้วแทน binary ให้ โดยไม่แตะ config ฐานข้อมูล และเว็บของเรา
สารบัญ · ตอนก่อนหน้า: ตอนที่ 2 ดึง source และ lib ด้วย vcpkg · ตอนถัดไป: ตอนที่ 4 build MariaDB
ถึงตรงนี้คุณมี Apache ที่คอมไพล์เองครบชุดแล้ว ยังไม่ได้ start และยังไม่มี httpd.conf จริง ซึ่ง deploy.cmd ในตอนที่ 7 จะสร้างให้พร้อมใบรับรองสำหรับ HTTPS ตอนที่ 4 ไป build ฐานข้อมูล MariaDB ซึ่งเป็นขั้นที่นานที่สุดรองจาก vcpkg เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ
อยากให้เราทำให้ไหม?
ให้เราช่วยติดตั้ง ตั้งค่า ย้ายระบบ หรือแก้ปัญหาเซิร์ฟเวอร์ เพื่อให้คุณมีเวลาไปทำงานที่สำคัญกว่า
เคยเจองานแบบนี้ไหม? ติดตรงไหน?
มาแลกวิธีกันต่อในบอร์ด LinuxThai



ความคิดเห็น 0
ยังไม่มีความคิดเห็น — เป็นคนแรกเลย