2009-09-17 37 views
33

Tôi muốn chuyển một số tùy chọn cho trình biên dịch. Tùy chọn sẽ phải được tính toán tại thời gian biên dịch - mỗi khi 'make' được gọi, không phải khi 'cmake', do đó lệnh execute_process không cắt nó. (Hiện nó?)Làm thế nào để chạy lệnh tại thời gian biên dịch trong Makefile được tạo bởi CMake?

Ví dụ đi qua một ngày đến một g ++ biên dịch như thế:

g++ prog.cpp -o prog -DDATETIME="17:09:2009,14:25" 

Nhưng với DATETIME tính theo thời gian biên dịch.

Bất kỳ ý tưởng nào về cách thực hiện trong CMake?

Bounty chỉnh sửa:

dung dịch A hackish nhất sẽ được chấp nhận.

Xin lưu ý rằng tôi muốn có thể thực hiện lệnh tùy ý tại thời gian biên dịch, không chỉ 'ngày'.

Chỉnh sửa 2:

Nó phải làm việc trên Linux, Windows (VS), MinGW, Cygwin và OS X. Bạn không thể giả định Ruby, Perl hay Python vì họ là phi tiêu chuẩn trên Windows . Bạn có thể giả sử BOOST, nhưng tôi đoán nó không sử dụng.

Mục tiêu là buộc cmake tạo Makefile (trong trường hợp Linux) khi thực hiện xong, sẽ thực hiện công việc.

Tạo tệp * .h tùy chỉnh là ok, nhưng nó phải được khởi tạo bởi một Makefile (hoặc tương đương trên hệ điều hành khác) bằng cách thực hiện. Việc tạo ra * .h không phải (và không cần phải) sử dụng cmake.

Trả lời

45

Bạn đang rời ra một số thông tin, chẳng hạn như các nền tảng bạn cần để chạy trên và nếu có bất kỳ công cụ bổ sung mà bạn có thể sử dụng. Nếu bạn có thể sử dụng Ruby, Perl, của Python, mọi thứ trở nên đơn giản hơn nhiều. Tôi sẽ giả sử rằng bạn muốn chạy trên cả UNIX và Windows pqlatform và rằng không có sẵn các công cụ bổ sung.

Nếu bạn muốn đầu ra từ lệnh trong một biểu tượng tiền xử lý, cách đơn giản nhất là tạo một tệp tiêu đề thay vì không quan trọng xung quanh với các tham số dòng lệnh. Hãy nhớ rằng CMake có một kịch bản-mode (-P), nơi nó chỉ xử lý các lệnh script trong file, vì vậy bạn có thể làm điều gì đó như thế này:

CMakeLists.txt:

project(foo) 
cmake_minimum_required(VERSION 2.6) 
add_executable(foo main.c custom.h) 
include_directories(${CMAKE_CURRENT_BINARY_DIR}) 
add_custom_command(OUTPUT custom.h 
    COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/custom.cmake) 

Các tập tin " custom.h "được tạo tại thời gian biên dịch bằng lệnh" cmake -P custom.cmake ". custom.cmake trông như thế này:

execute_process(COMMAND uname -a 
    OUTPUT_VARIABLE _output OUTPUT_STRIP_TRAILING_WHITESPACE) 
file(WRITE custom.h "#define COMPILE_TIME_VALUE \"${_output}\"") 

Nó thực thi lệnh (trong trường hợp này "uname -a", bạn sẽ thay thế nó với bất cứ lệnh bạn muốn), và đặt ra trong biến _output , sau đó viết cho custom.h.Lưu ý rằng điều này sẽ chỉ hoạt động nếu lệnh xuất ra một dòng. (Nếu bạn cần Multiline đầu ra, bạn sẽ phải viết một custom.cmake phức tạp hơn, tùy thuộc vào cách bạn muốn dữ liệu multiline vào chương trình của bạn.)

Các chương trình chính như sau:

#include <stdio.h> 
#include "custom.h" 
int main() 
{ 
    printf("COMPILE_TIME_VALUE: %s\n", COMPILE_TIME_VALUE); 
    return 0; 
} 

Nếu bạn thực sự muốn tính toán các tùy chọn trình biên dịch tại thời gian biên dịch, mọi thứ trở nên phức tạp hơn nhiều. Đối với các trình tạo Bourne-shell, bạn chỉ có thể chèn lệnh bên trong các dấu gạch chéo ngược. Nếu bạn nhận được điên khi tìm ra trích dẫn, di chuyển tất cả các logic của lệnh của bạn bên trong một vỏ kịch bản để bạn chỉ cần đặt mycommand.sh trong add_definitions của bạn():

if(UNIX) 
    add_definitions(`${CMAKE_CURRENT_SOURCE_DIR}/custom-options.sh`) 
endif() 

Đối với máy phát điện của Windows tập tin thực thi công việc cơ bản có nhiều mẹo nhỏ hơn, và tôi không có giải pháp tốt. Vấn đề là các lệnh PRE_BUILD không được thực thi như là một phần của cùng một tệp batch như trình biên dịch thực tế gọi (BuildLog.htm để biết chi tiết), vì vậy ý ​​tưởng ban đầu của tôi không hoạt động (tạo custom.bat trong một PRE_BUILD và sau đó thực hiện "gọi custom.bat" trên đó để nhận tập hợp biến có thể sau này được tham chiếu trong dòng lệnh trình biên dịch). Nếu có tương đương với backticks trong các tệp hàng loạt, điều đó sẽ giải quyết được sự cố.

Hy vọng điều này sẽ đưa ra một số ý tưởng và điểm xuất phát.

(Bây giờ đến vấn ngược lại không thể tránh khỏi: là những gì bạn thực sự cố gắng để làm gì?)

EDIT: Tôi không chắc chắn lý do tại sao bạn không muốn để cho CMake được sử dụng để tạo ra các tập tin tiêu đề. Sử dụng $ {CMAKE_COMMAND} sẽ mở rộng đến CMake được sử dụng để tạo các tệp Makefiles/.vcproj và vì CMake không thực sự hỗ trợ các tệp Makefiles/.vcproj di động, bạn sẽ cần chạy lại CMake trên các máy mục tiêu.

CMake cũng có một loạt các lệnh tiện ích (Chạy "cmake -E" cho danh sách) vì lý do rõ ràng này. Ví dụ: bạn có thể làm

add_custom_command(OUTPUT custom.h COMMAND ${CMAKE_COMMAND} -E copy file1.h file2.h) 

để sao chép tệp1.h thành file2.h.

Dù sao, nếu bạn không muốn tạo header-files sử dụng CMake, bạn sẽ có cần phải gọi .bat/script sh riêng biệt để tạo ra các tập tin tiêu đề, hoặc làm điều đó bằng tiếng vang:

add_custom_command(OUTPUT custom.h COMMAND echo #define SOMETHING 1 > custom.h) 

Điều chỉnh trích dẫn nếu cần.

+0

Tôi đã làm rõ câu hỏi của mình. Bạn có thể làm rõ bạn trả lời trong ánh sáng này? –

-1

Tính năng này có hoạt động không?

d=`perl -e"print qq(Whatever calculated at runtime);"`; g++ prog.cpp -o prog -DDATETIME=$$d 
+0

Câu hỏi đặt ra là về CMake, không phải về perl/bash –

+0

@ Łukasz Lew: Không phải là có thể phát hiện dòng này bên trong CMake? – Curd

+2

Có, nhưng nó sẽ được thực hiện tại thời gian cmake không tại thời gian. –

2

Tôi sẽ sử dụng các phương pháp sau đây:

  1. Tạo một file thực thi được in ngày hiện tại để stdout (CMake thiếu chức năng này)
  2. Thêm một mục tiêu đó là luôn luôn coi hết hạn
  3. Hãy mục tiêu gọi một tập lệnh CMake khác
  4. Để tập lệnh CMake được gọi tạo ra tệp tiêu đề

Ví dụ mã cho việc này:

--- CMakeLists.txt ---

PROJECT(Foo) 
ADD_EXECUTABLE(RetreiveDateTime ${CMAKE_CURRENT_SOURCE_DIR}/datetime.cpp) 
ADD_CUSTOM_TARGET(GenerateFooHeader 
        COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/Generate.cmake 
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 
        DEPENDS RetreiveDateTime) 
ADD_EXECUTABLE(Foo "test.cpp" "${CMAKE_CURRENT_BINARY_DIR}/generated.h") 
ADD_DEPENDENCIES(Foo GenerateFooHeader) 

--- Generate.cmake ---

EXECUTE_PROCESS(COMMAND ${CMAKE_BINARY_DIR}/RetreiveDateTime OUTPUT_VARIABLE DATETIMESTRING) 
MESSAGE(STATUS "DATETIME=\"${DATETIMESTRING}\"") 
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in ${CMAKE_CURRENT_BINARY_DIR}/generated.h @ONLY) 

--- generate.h.in ---

#pragma once 

#define DATETIMESTRING "@[email protected]" 

--- datetime.cpp ---

#include <iostream> 
#include <ctime> 
#include <cstring> 

int main(int, char*[]) 
{ 
time_t now; 
time(&now); 
tm * timeinfo = localtime(&now); 

char * asstring = asctime(timeinfo); 
asstring[strlen(asstring) - 1] = '\0'; // Remove trailing \n 
std::cout << asstring; 
return 0; 
} 

--- test.cpp ---

#include "generated.h" 

#include <iostream> 

int main(int, char*[]) 
{ 
std::cout << DATETIMESTRING << std::endl; 
return 0; 
} 

Điều này dẫn đến một tiêu đề "generated.h" được tái sinh trên mỗi xây dựng. Nếu bạn không cần DATETIME, ví dụ này có thể được đơn giản hóa đáng kể vì CMake thiếu tính năng này và một chương trình phải được xây dựng để mô phỏng chức năng.

Tuy nhiên, tôi sẽ nghĩ nhiều hơn hai lần trước khi thực hiện việc này. Hãy nhớ rằng tệp tiêu đề sẽ được tạo lại mỗi lần thực hiện chạy, làm cho mục tiêu của bạn không hợp lệ tại tất cả lần. Bạn sẽ không bao giờ có nhị phân được coi là cập nhật.

4

Giải pháp ở trên (sử dụng tệp tập lệnh CMake riêng để tạo tệp tiêu đề) có vẻ rất linh hoạt nhưng hơi phức tạp đối với những gì đang được thực hiện trong ví dụ.

Cách khác là đặt thuộc tính COMPILE_DEFINITIONS trên một tệp nguồn và đích hoặc trong trường hợp đó các biến tiền xử lý được xác định trước sẽ chỉ được đặt cho tệp nguồn hoặc tệp trong đích được biên dịch.

Thuộc tính COMPILE_DEFINITIONS có định dạng khác với định dạng được sử dụng trong lệnh add_definitions và có lợi thế là bạn không cần lo lắng về cú pháp "-D" hoặc "\ D" và chúng hoạt động trên nhiều nền tảng.

Ví dụ mã

- CMakeLists.txt -

execute_process(COMMAND svnversion 
    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 
    OUTPUT_VARIABLE SVN_REV) 
string(STRIP ${SVN_REV} SVN_REV) 

execute_process(COMMAND date "+%Y-%m-%d-%H:%M" 
    OUTPUT_VARIABLE BUILD_TIME) 
string(STRIP ${BUILD_TIME} BUILD_TIME) 

set_source_files_properties(./VersionInfo.cpp 
    PROPERTIES COMPILE_DEFINITIONS SVN_REV=\"${SVN_REV}\";BUILD_TIME=\"${BUILD_TIME}\"") 

Dòng đầu tiên chạy một lệnh shell svnversion và đặt kết quả trong biến SVN_REV. Cần có lệnh string(STRIP ...) để xóa các ký tự dòng mới ở đầu ra.

Lưu ý điều này giả định rằng lệnh đang chạy là đa nền tảng. Nếu không, bạn có thể cần phải có lựa chọn thay thế cho các nền tảng khác nhau.Ví dụ tôi sử dụng thực hiện Cygwin của Unix date lệnh, và có:

if(WIN32) 
execute_process(COMMAND cmd /C win_date.bat 
    OUTPUT_VARIABLE BUILD_TIME) 
else(WIN32) 
    execute_process(COMMAND date "+%Y-%m-%d-%H:%M" 
    OUTPUT_VARIABLE BUILD_TIME) 
endif(WIN32) 
string(STRIP ${BUILD_TIME} BUILD_TIME) 

cho các lệnh ngày, nơi win_date.bat là một file bat mà kết quả đầu ra ngày theo định dạng mong muốn.

Hai biến tiền xử lý không có sẵn trong tệp ./VersionInfo.cpp nhưng không được đặt trong bất kỳ tệp nào khác. Sau đó bạn có thể có

- VersionInfo.cpp -

std::string version_build_time=BUILD_TIME; 
std::string version_svn_rev=SVN_REV; 

Điều này dường như làm việc độc đáo trên nền tảng và giảm thiểu số lượng mã nền tảng cụ thể.

+2

Một vấn đề tôi thấy w/đây là tập tin VersionInfo.cpp sẽ không thay đổi và do đó không được biên dịch lại. Làm thế nào bạn sẽ giải quyết điều này? Đơn giản chỉ cần kích hoạt một biên dịch thủ công sẽ làm cho toàn bộ phép thuật cmake ít hữu ích hơn vì nó sẽ không làm nhiều hơn việc sử dụng trực tiếp các macro '__DATE__' và' __TIME__'. –

Các vấn đề liên quan