CMake/Multiple Targets

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา

ในโปรเจคซอฟต์แวร์หนึ่งๆ อาจมีการสร้างโปรแกรมที่สามารถนำไปรันได้โดยตรง (executable) หรือไลบรารี (library) มากกว่าหนึ่งอันขึ้นไป (เพื่อให้ง่ายต่อการเขียน ผู้เขียนจะเรียกไฟล์ที่คอมไพเลอร์ภาษา C++ สร้างว่า target ตามที่ Xcode เรียก) ซึ่งอาจจะเป็นเพราะ target เหล่านี้เป็นโปรแกรมซึ่งทำงานสนับสนุนซึ่งกันและกัน หรือ target บางตัวเป็นไลบรารีซึ่ง target ตัวอื่นเรียกใช้ ในโพสต์นี้เราจะมาพูดถึงการเขียน CMakeLists.txt เพื่อสร้าง target หลายๆ target ในการ build ครั้งเดียว

วิธีการเขียน CMakeLists.txt ให้บรรลุวัตถุประสงค์ข้างบนมีหลายวิธี เราจะนำเสนอวิธีการต่างๆ จากง่ายไปยาก โดยวิธีการที่ง่ายๆ นี้หมายความถึงวิธีการที่เขียนไฟล์ง่ายๆ สั้นๆ ไม่ต้องเสียแรงมาก แต่ไม่เหมาะสมกับโปรเจคที่มีขนาดไหน ส่วนวิธีการที่ยากนั้นเขียนไฟล์ยาวกว่าและอาจจะต้องมีการสร้างไฟล์ CMakeLists.txt เพิ่มเติม เพื่อให้จัดการโปรเจคที่ซับซ้อนได้ดียิ่งขึ้น

Hello World ปะทะ Good Morning

สมมติว่าเราจะสร้างโปรเจค sample1 ซึ่งมี target สอง target ดังต่อไปนี้

  • helloworld เป็นโปรแกรมพิมพ์ข้อความ "hello, world" ออกทางหน้าจอ
  • goodmorning เป็นโปรแกรมพิมพ์ข้อความ "good morning" ออกทางหน้าจอ

ซึ่งมีซอร์สโค้ดเป็นดังต่อไปนี้

<geshi lang="c"> /* helloworld.cpp */

  1. include <stdio.h>

int main() {

   printf("hello, world\n");
   return 0;

} </geshi>

<geshi lang="c"> /* goodmorning.cpp */

  1. include <stdio.h>

int main() {

   printf("good morning\n");
   return 0;

} </geshi>

เนื่องจากไฟล์มีอยู่แค่สองไฟล์ เราอาจจะให้ไฟล์ทั้งสองอยู่ในไดเรคทอรีเดียวกันไปเลยดังโครงสร้างไดเรคทอรีข้างล่างนี้

   sample1/
       src/
           CMakeLists.txt
           helloworld.cpp
           goodmorning.cpp

และในไฟล์ CMakeLists.txt เราสามารถสั่งให้ CMake สร้าง executable สอง executable ได้ด้วยการสั่ง ADD_EXECUTABLE สองครั้ง

   PROJECT(sample2)
   CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
   
   ADD_EXECUTABLE(helloworld helloworld.cpp)
   ADD_EXECUTABLE(goodmorning goodmorning.cpp)

หนึ่ง target หนึ่งไดเรคทอรี

ในการเขียนซอฟต์แวร์ขนาดใหย่ เราอาจพบว่า target สอง target อาจมีการใช้ไฟล์ชื่อเดียวกัน ฉะนั้นเราจึงไม่สามารถใส่ไฟล์ของทุก target ไว้ในไดเรคทอรีเดียวกันได้ ฉะนั้นเพื่อให้ไฟล์จัดการง่าย เราควรจะแยกไฟล์ของ target แต่ละ target ไว้ในไดเรคทอรีของมันเอง

เราอาจจะจำลองสถานการณ์นั้นโดยแยก helloworld.cpp และ goodmorning.cpp เป็นสองส่วน คือ text.h และ main.cpp ดังนี้ (ขออภัยที่การจำลองสถานการ์นี้อาจจะไม่เหมือนจริงไปสักหน่อย)

<geshi lang="c"> /* text.h ของ helloworld */

  1. include <stdio.h>

const char *helloworld_text = "hello, world\n"; </geshi>

<geshi lang="c"> /* main.cpp ของ helloworld */

  1. include "text.h"

int main() {

   printf("%s", helloworld_text);
   return 0;

} </geshi>

<geshi lang="c"> /* text.h ของ goodmorning */

  1. include <stdio.h>

const char *goodmorning_text = "good morning\n"; </geshi>

<geshi lang="c"> /* main.cpp ของ goodmorning */

  1. include "text.h"

int main() {

   printf("%s", goodmorning_text);
   return 0;

} </geshi>

เราสามารถจัดไฟล์ตามโครงสร้างไดเรคทอรีต่อไปนี้ได้

   sample2/
       src/
           CMakeLists.txt
           helloworld/
               text.h
               main.cpp
           goodmorning/
               text.h
               main.cpp

และเขียนไฟล์ CMakeLists.txt ใหม่ว่า

   PROJECT(sample2)
   CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
   
   ADD_EXECUTABLE(helloworld helloworld/text.h helloworld/main.cpp)
   ADD_EXECUTABLE(goodmorning goodmorning/text.h goodmorning.cpp)

หนึ่งไดเรคทอรี หนึ่ง CMakeLists.txt

สังเกตว่าในตัวอย่างที่แล้ว เวลาเราจะใส่ไฟล์เข้าใน executable หนึ่งๆ เราจะต้องใส่ไดเรคทอรีของไฟล์นั้นเข้าไปด้วย เนื่องจากชื่อไฟล์ที่กำหนดใน CMakeLists.txt ใดๆ จะเป็นชื่อไฟล์สัมพัทธ์กับไดเรคทอรีที่ CMakeLists.txt อยู่เสมอ ซึ่งทำให้เกิดความไม่สะดวกอย่างมาก

นอกจากนี้ หาก target ของเราเป็น target ที่มีไฟล์อยู่หลายไฟล์ CMakeLists.txt จะต้องมีชื่อไฟล์เหล่านี้ทั้งหมด ทำให้ CMakeLists.txt มีขนาดใหญ่มาก ทำให้แก้ไขและจัดการยาก

CMake อนุญาตให้เราเขียน CMakeLists.txt ได้หลายๆ ไฟล์​(แต่ในไดเรคทอรีหนึ่งๆ จะมี CMakeLists.txt ได้เพียงไฟล์เดียวเท่านั้น เนื่องจากชื่อไฟล์ซ้ำกันไม่ได้) แล้วให้ไฟล์ CMakeLists.txt ของไดเรคทอรีแม่ไปเอาเนื้อหาของ CMakeLists.txt ในไดเรคทอรีลูกมาประมวลผลได้ ความสามารถนี้ช่วยให้เราสามารถเขียน CMakeLists.txt ของแต่ละ target แยกกันได้หากเราแยกไฟล์ของ target แต่ละตัวอยู่ในไดเรคทอรีของมันแล้ว

เราจะนำตัวอย่างที่แล้วมาดัดแปลงให้ใช้ความสามารถใหม่นี้ อันดับแรกเราจะสร้างไฟล์ CMakeLists.txt ไว้ในไดเรคทอรี helloworld และ goodmorning อย่างละไฟล์ ตามโครงสร้างไดเรคทอรีข้างล่าง

   sample3/
       src/
           CMakeLists.txt
           helloworld/
               CMakeLists.txt
               text.h
               main.cpp
           goodmorning/
               CMakeLists.txt
               text.h
               main.cpp

ในไฟล์ CMakeLists.txt ของไดเรคทอรี src เราเปลี่ยนคำสั่ง ADD_EXECUTABLE เป็นคำสั่ง ADD_SUBDIRECTORY เพื่อบอกให้ CMake ไปอ่านและประมวลไฟล์ CMakeLists.txt ในไดเรคทอรีย่อยที่กำหนดให้

### sample3/src/CMakeLists.txt
     
PROJECT(sample3)
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)

ADD_SUBDIRECTORY(helloworld)
ADD_SUBDIRECTORY(goodmorning)

ส่วนในไฟล์ CMakeLists.txt ของ helloworld และ goodmorning เราก็ใช้คำสั่ง ADD_EXECUTABLE เหมือนเดิม แต่เราไม่ต้องใส่ชื่อไดเรคทอรีนำหน้าไฟล์ต่างๆ แล้ว เนื่องจาก CMakeLists.txt อยู่ในไดเรคทอรีเดียวกับไฟล์พวกนั้นแล้ว

### sample3/src/helloworld/CMakeLists.txt

ADD_EXECUTABLE(helloworld text.h main.cpp)
### sample3/src/goodmorning/CMakeLists.txt

ADD_EXECUTABLE(goodmorning text.h main.cpp)