ผลต่างระหว่างรุ่นของ "CMake/config.h"
Cardcaptor (คุย | มีส่วนร่วม) |
|||
(ไม่แสดง 1 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน) | |||
แถว 139: | แถว 139: | ||
ADD_SUBDIRECTORY(print_cwd) | ADD_SUBDIRECTORY(print_cwd) | ||
− | CONFIGURE_FILE( config.h.in config.h ) | + | CONFIGURE_FILE( config.h.in ${CMAKE_SOURCE_DIR}/config.h ) |
สังเกตว่าเนื้อหามีส่วนเพิ่มเติมเข้ามาสองส่วนจากเนื้อหาไฟล์ CMakeListst.txt ที่เราพบในบทความก่อนหน้านี้ ได้แก่ | สังเกตว่าเนื้อหามีส่วนเพิ่มเติมเข้ามาสองส่วนจากเนื้อหาไฟล์ CMakeListst.txt ที่เราพบในบทความก่อนหน้านี้ ได้แก่ | ||
แถว 226: | แถว 226: | ||
เพื่อให้แน่ใจได้ว่าตัวแปรทุกตัวถูกกำหนดไว้เรียบร้อยแล้ว | เพื่อให้แน่ใจได้ว่าตัวแปรทุกตัวถูกกำหนดไว้เรียบร้อยแล้ว | ||
− | + | แต่สังเกตว่าเราไม่ได้ใส่ชื่อ config.h ไว้เฉยๆ แต่เราเติม ${CMAKE_SOURCE_DIR}/ ไปข้างหน้ามัน | |
− | config.h | ||
ที่เป็นเช่นนี้ก็เพราะว่า'''ไฟล์ที่ CMake สร้างจะอยู่ใน binary directory เสมอ เว้นแต่จะกำหนดเป็นอย่างอื่น''' | ที่เป็นเช่นนี้ก็เพราะว่า'''ไฟล์ที่ CMake สร้างจะอยู่ใน binary directory เสมอ เว้นแต่จะกำหนดเป็นอย่างอื่น''' | ||
− | + | แต่เราต้องการให้ config.h อยู่ในไดเรคทอรี src ดังนั้นเราจึงใช้ตัวแปร CMAKE_SOURCE_DIR | |
− | + | ซึ่งเป็นไดเรคทอรีที่มีไฟล์ CMakeLists.txt ของโปรเจคอยู่ (ในที่นี้แค่ src) | |
− | + | สังเกตอีกอย่างว่าเวลาเราใช้ตัวแปรของ CMake | |
+ | เราจะล้อมชื่อตัวแปรด้วย ${ และ } เหมือนกับการใช้ตัวแปรใน unix shell ต่างๆ | ||
− | หากเราอยู่ในระบบปฏิบัติการ Mac OS เราจะได้ | + | หากเราอยู่ในระบบปฏิบัติการ Mac OS เราจะได้ src/config.h ที่มีเนื้อหาดังต่อไปนี้ |
<geshi lang="c"> | <geshi lang="c"> | ||
แถว 246: | แถว 246: | ||
</geshi> | </geshi> | ||
− | ในทางกลับกันหากเราอยู่ใน Windows แล้ว | + | ในทางกลับกันหากเราอยู่ใน Windows แล้ว src/config.h จะมีเนื้อหาดังต่อไปนี้ |
<geshi lang="c"> | <geshi lang="c"> |
รุ่นแก้ไขปัจจุบันเมื่อ 09:55, 12 พฤษภาคม 2553
เราเคยกล่าวไปแล้วในบทความการใช้ CMake เบื้องต้น ว่าปัญหาหนึ่งที่โปรแกรมเมอร์ภาษา C++ ที่ต้องการเขียนโปรแกรมให้รันได้ในหลายแพลตฟอร์มเจอคือการที่แพลตฟอร์มต่างๆ มีไลบรารีและฟังก์ชันให้เรียกใช้ไม่เหมือนกัน
ตัวอย่างหนึ่งคือฟังก์ชันสำหรับคืนชื่อไดเรคทอรีที่โปรแกรมทำงานอยู่ปัจจุบัน (current working directory) ซึ่งในระบบปฏิบัติการที่สืบเชื้อสายมาจาก Unix จะมีชื่อว่า getcwd และต้อง include ไฟล์ unistd.h เพื่อใช้งาน แต่ใน Windows ฟังก์ชันนี้กลับมีชื่อว่า _getcwd และต้อง include ไฟล์ direct.h แทน
ดังนั้น หากเราจะเขียนโปรแกรม print_cwd ที่พิมพ์ current working directory ออกทางหน้าจอ เราจะต้องรู้ว่าโปรแกรมของถูกคอมไพล์อยู่ในแพลตฟอร์มใด ซึ่งในบทความ CMake เบื้องต้น ผู้เขียนได้เสนอให้ใช้มาโคร _WIN32 เป็นตัวเช็คว่าโปรแกรมถูกคอมไพล์ใน Windows หรือไม่ อย่างไรก็ดีผู้เขียนคิดว่าการใช้ _WIN32 ไม่น่าจะเป็นความคิดที่ดี เนื่องจากเราไม่ทราบว่าคอมไพเลอร์ตัวอื่นนอกจาก cl ของไมโครซอฟต์จะนิยาม _WIN32 เอาไว้หรือไม่
สิ่งที่เราต้องการคือมาโครซึ่งจะถูกนิยามตามแพลตฟอร์มที่โปรแกรมถูกคอมไพล์ เนื่องจากผู้เขียนเขียนโปรแกรมอยู่ในสามแพลตฟอร์มใหญ่ๆ คือ Windows, Mac, และ Linux (ซึ่งเป็น Unix แบบหนึ่ง)ผู้เขียนจึงอยากให้มีมาโครต่อไปนี้
- __WIN_PLATFORM__ ถูกนิยามก็ต่อเมื่อโปรแกรมถูกคอมไพล์ใน Windows
- __MAC_PLATFORM__ ถูกนิยามก็ต่อเมื่อโปรแกรมถูกคอมไพล์ใน Mac OS
- __UNIX_PLATFORM__ ถูกนิยามก็ต่อเมื่อโปรแกรมถูกคอมไพล์ด้วยระบบปฏิบัติการที่สืบเชื้อสายมาจาก Unix อื่นๆ เช่น Linux แต่ไม่ใช่ Mac OS
ตามมาตรฐานการเขียนโค้ดของ GNU แล้ว มาโครที่ใช้สำหรับบอกแพลตฟอร์มเหล่านี้จะถูกบรรจุอยู่ในไฟล์ชื่อ config.h ซึ่งอยู่ในชั้นบนสุดของ source directory ของโปรเจค ไฟล์ config.h นี้จะถูกสร้างขึ้นโดยอัตโนมัติด้วยเครื่องมือชื่อ autoconf ในบทความนี้เราจะเลียนแบบมาตรฐานของ GNU แต่เราจะใช้ CMake สร้าง config.h แทน
หากมี config.h แล้วซอร์สโค้ดของโปรแกรม print_cwd อาจมีเนื้อหาดังต่อไปนี้
<geshi lang="c"> /* print_cwd.cpp */
- include "../config.h"
- ifdef __WIN_PLATFORM__
- include <direct.h>
- else
- include <unistd.h>
- endif
- include <stdio.h>
- include <stdlib.h>
int main() {
char *buffer; #ifdef __WIN_PLATFORM__ buffer = _getcwd(NULL, 0); #else buffer = getcwd(NULL, 0); #endif printf("%s\n", buffer); free(buffer); return 0;
} </geshi>
หมายเหตุ: ที่เรา include ../config.h แทนที่จะ include config.h เฉยๆ เป็นเพราะว่าจากบทความที่แล้ว เราได้เรียนรู้ว่าควรจัดให้ print_cwd.cpp อยู่ในไดเรคทอรี src/print_cwd เพื่อไม่ให้ไปปนกับไฟล์ของ target อื่นๆ แต่ config.h ควรอยู่ในไดเรคทอรี src เนื่องจากเป็นไฟล์ที่ทุกๆ target จะต้องใช้ร่วมกัน
การสร้างไฟล์ config.h โดยใช้ CMake มีขั้นตอนอยู่สามขั้นตอน
1. เขียนไฟล์ config.h.in เพื่อใช้เป็นต้นแบบของ config.h
2. เขียน CMakeLists.txt เพื่อกำหนดตัวตัวแปรต่างๆ ในการนำไปสร้าง config.h
3. เรียกคำสั่ง CONFIGURE_FILE ใน CMakeLists.txt เพื่อสร้าง config.h
เนื้อหา
config.h.in
config.h.in ควรอยู่ในไดเรคทอรี src เหมือนกับ config.h ดังนั้นโปรเจคของเราจึงมีโครงสร้างไดเรคทอรีดังต่อไปนี้
sample/ build/ src/ CMakeLists.txt config.h.in print_cwd/ CMakeLists.txt print_cwd.cpp
ที่ไม่มี config.h เนื่องจากมันยังไม่ถูกสร้างขึ้นจริง
เนื้อหาของไฟล์ config.h.in นั้นเหมือนกับเนื้อหาของ header file ของภาษา C/C++ ธรรมดา แต่มาโครใดที่เราต้องกให้ CMake นิยามตามเงื่อนไขต่างๆ ของแพลตฟอร์ม เราจะใช้คำสั่ง #cmakedefine แทน #define ในกรณีของการนิยามมาโครบอกแพลตฟอร์มสามมาโครข้างต้น เราสามารถเขียน config.h.in ได้ดังต่อไปนี้
<geshi lang="c"> /* config.h.in */
- ifndef __CONFIG_H__
- define __CONFIG_H__
- cmakedefine __WIN_PLATFORM__
- cmakedefine __MAC_PLATFORM__
- cmakedefine __UNIX_PLATFORM__
- endif
</geshi>
เวลา CMake สร้างไฟล์ config.h มันจะเอา config.h.in เป็นต้นฉบับและเปลี่ยนแปลงเฉพาะบรรทัดที่มี #cmakedefine เท่านั้น บรรทัดอื่นๆ จะมีเนื้อหาเหมือนเดิม
CMakeLists.txt
ในที่นี้เราจะสนใจเฉพาะไฟล์ CMakeLists.txt ที่อยู่ในไดเรคทอรี src เท่านั้น ไฟล์ CMakeLists.txt ที่อยู่ใน src/print_cwd นั้นมีหน้าที่สร้าง executable ชื่อ print_cwd ซึ่งเนื้อหาของมันจะคล้ายกับไฟล์ CMakeLists.txt ทำนองเดียวกันซึ่งปรากฏอยู่ในบทความที่แล้ว
ไฟล์ CMakeLists.txt ที่เราสนใจอาจมีเนื้อหาดังต่อไปนี้
PROJECT(sample) CMAKE_MINIMUM_REQUIRED(VERSION 2.6) IF(WIN32) SET(__WIN_PLATFORM__ ON) ELSE(WIN32) SET(__WIN_PLATFORM__ OFF) ENDIF(WIN32) IF(UNIX) IF(APPLE) SET(__MAC_PLATFORM__ ON) SET(__UNIX_PLATFORM__ OFF) ELSE(APPLE) SET(__MAC_PLATFORM__ OFF) SET(__UNIX_PLATFORM__ ON) ENDIF(APPLE) ELSE(UNIX) SET(__MAC_PLATFORM__ OFF) SET(__UNIX_PLATFORM__ OFF) ENDIF(UNIX) ADD_SUBDIRECTORY(print_cwd) CONFIGURE_FILE( config.h.in ${CMAKE_SOURCE_DIR}/config.h )
สังเกตว่าเนื้อหามีส่วนเพิ่มเติมเข้ามาสองส่วนจากเนื้อหาไฟล์ CMakeListst.txt ที่เราพบในบทความก่อนหน้านี้ ได้แก่
- ส่วนที่เริ่มจาก IF(WIN32) จงถึง ENDIF(UNIX)
- คำสั่ง CONFIGURE_FILE ในบรรทัดสุดท้าย
กำหนดค่าให้ตัวแปร
ส่วนซึ่งเริ่มตั้งแต่ IF(WIN32) ถึง ENDIF(UNIX) ทำหน้าที่นิยามตัวแปร __MAC_PLATFORM__, __WIN_PLATFORM__, และ __UNIX_PLATFORM__ ให้มีค่าเป็น "จริง" หรือ "เท็จ" ที่ถูกต้องตรงตามแพลตฟอร์มที่โปรแกรมถูกคอมไพล์ ส่วนนี้มีหลักษณะคล้ายๆ กับโปรแกรมคอมพิวเตอร์เล็กๆ โปรแกรมหนึ่ง
CMake มีภาษาสคริปต์ที่ทรงพลังพอสมควร ภาษาของ CMake อนุญาตให้เราและกำหนดค่าให้ตัวแปร นอกจานี้ยังมีคำสั่ง if และลูป while ให้ใช้ คุณสามารถดูรายละเอียดของโครงสร้างของภาษาเหล่านี้จาก documentation ของ CMake หรือจากวิกิของ Kernigh
คำสั่ง SET
คำสั่งที่เราใช้มีอยู่สองคำสั่งได้แก่คำสั่ง SET ซึ่งใช้กำหนดค่าให้ตัวแปร ซึ่งคำสั่งนี้มีรูปแบบ
SET(<ชื่อตัวแปร> <ค่าของตัวแปร>)
ตัวแปรใน CMake มีค่าเป็นสตริง (string) เท่านั้น ดังนั้นค่า ON และ OFF ที่เรากำหนดให้จริงแล้วจึงเป็นค่าสายอักษร "ON" และ "OFF"
ค่าความจริง
CMake ถือว่าสตริง "ON", "YES", และ "TRUE" มีค่าเป็น "จริง" และถือว่าสตริง "OFF", "NO", และ "FALSE" มีค่าเป็นเท็จ ดังั้นเราอาจจะเปลี่ยน ON เป็น TRUE หรือ OFF เป็น NO ก็ได้
(จริงๆ แล้วสตริงที่ไม่ใช่สตริงข้างบนที่อาจมีค่าเป็นจริงหรือเท็จได้อีก กรุณาอ่านวิกิของ Kernigh หากต้องการหาข้อมูลเพิ่มเติม)
คำสั่ง IF
คำสั่ง IF ใน CMAKE มีรูปแบบดังต่อไปนี้
IF(<เงื่อนไข>) <กลุ่มคำสั่งที่จะทำเมื่อเงื่อนไขเป็นจริง> ENDIF(<เงื่อนไข>)
หรือ
IF(<เงื่อนไข>) <กลุ่มคำสั่งที่จะทำเมื่อเงื่อนไขเป็นจริง> ELSE(<เงื่อนไข>) <กลุ่มคำสั่งที่จะทำเมื่อเงื่อนไขเป็นเท็จ> ENDIF(<เงื่อนไข>)
ซึ่งคำสั่งทั้งสองรูปแบบมีความหมายเหมือนกับคำสั่ง if ในภาษาโปรแกรมอื่นๆ
สังเกตว่าเวลาเขียนคำสั่งเหล่านี้ เงื่อนไขที่อยู่ในวงเล็บหลัง IF, ELSE, และ ENDIF จะต้องเป็นเงื่อนไขเดียวกันเสมอ ไม่งั้น CMake จะจับคู่ IF กับ ELSE หรือ ENDIF ไม่ถูก พึงระวังว่า CMake ไม่มีคำสั่ง ELSEIF ให้ใช้ภาษาโปรแกรมทั่วๆ ไป
WIN32, APPLE, และ UNIX
WIN32, APLLE, และ UNIX เป็นตัวแปรสามตัวที่ CMake กำหนดค่าให้อัตโนมัติก่อนการประมวลผล CMakeLists.txt โดยที่
- WIN32 จะมีค่าเป็นจริงก็ต่อเมื่อระบบปฏิบัติการที่ใช้อยู่ในตระกูล Windows
- APPLE จะมีค่าเป็นจริงก็ต่อเมื่อระบบปฏิบัติการที่ใช้อยู่ในตระกูล Mac OS
- UNIX จะมีค่าเป็นจริงก็ต่อเมื่อระบบปฏิบัติการที่ใช้สืบเชื้อสายมาจาก Unix
หมายเหตุ: ผู้เขียนสร้างตัวแปรใหม่สามตัวแทนที่จะใช้ WIN32, APPLE, และ UNIX ไปเลยเนื่องจาก (1) ความหมายของ __UNIX_PLATFORM__ ไม่ตรงกับความหมายของ UNIX (UNIX จะเป็นจริงในระบบปฏิบัติการตระกูล MAC OS ด้วย) และ (2) ผู้เขียนเห็นว่าชื่อมาโครที่ไม่มี underscore นำหน้าควรจะถูกเปิดไว้ให้ผู้เขียนโปรแกรมนำไปใช้
คำสั่ง CONFIGURE_FILE
คำสั่ง CONFIGURE_FILE มีรูปแบบดังต่อไปนี้
CONFIGURE_FILE(<ชื่อไฟล์ต้นฉบับ> <ชื่อไฟล์ที่ต้องการสร้าง>)
สังเกตว่าเราเขียนคำสั่ง CONFIG_FILE ไว้เป็นคำสั่งสุดท้ายหลังจากการตั้งแต่ตัวแปรและการกำหนด target ต่างๆ (เรากำหนด target ผ่านการสั่ง ADD_DIRECTORY) เนื่องจากคำสั่ง CONFIGURE_FILE จะนำค่าของตัวแปรต่างๆ ณ ตำแหน่งที่ CONFIGURE_FILE ถูกเรียกมาใช้ในการสร้าง config.h ดังนั้นเราจึงเอาคำสั่งนี้ไว้เป็นคำสั่งสุดท้าย เพื่อให้แน่ใจได้ว่าตัวแปรทุกตัวถูกกำหนดไว้เรียบร้อยแล้ว
แต่สังเกตว่าเราไม่ได้ใส่ชื่อ config.h ไว้เฉยๆ แต่เราเติม ${CMAKE_SOURCE_DIR}/ ไปข้างหน้ามัน ที่เป็นเช่นนี้ก็เพราะว่าไฟล์ที่ CMake สร้างจะอยู่ใน binary directory เสมอ เว้นแต่จะกำหนดเป็นอย่างอื่น แต่เราต้องการให้ config.h อยู่ในไดเรคทอรี src ดังนั้นเราจึงใช้ตัวแปร CMAKE_SOURCE_DIR ซึ่งเป็นไดเรคทอรีที่มีไฟล์ CMakeLists.txt ของโปรเจคอยู่ (ในที่นี้แค่ src) สังเกตอีกอย่างว่าเวลาเราใช้ตัวแปรของ CMake เราจะล้อมชื่อตัวแปรด้วย ${ และ } เหมือนกับการใช้ตัวแปรใน unix shell ต่างๆ
หากเราอยู่ในระบบปฏิบัติการ Mac OS เราจะได้ src/config.h ที่มีเนื้อหาดังต่อไปนี้
<geshi lang="c">
- ifndef __CONFIG_H__
- define __CONFIG_H__
/* #undef __WIN_PLATFORM__ */
- define __MAC_PLATFORM__
/* #undef __UNIX_PLATFORM__ */
- endif
</geshi>
ในทางกลับกันหากเราอยู่ใน Windows แล้ว src/config.h จะมีเนื้อหาดังต่อไปนี้
<geshi lang="c">
- ifndef __CONFIG_H__
- define __CONFIG_H__
- define __WIN_PLATFORM__
/* #undef __MAC_PLATFORM__ */ /* #undef __UNIX_PLATFORM__ */
- endif
</geshi>
ซึ่งนิยามมาโครตรงตามความต้องการของเราเป๊ะ