CMake 命令筆記

CMake 全稱“cross platform make”,是開源、跨平臺的自動化構建系統。CMake 由 Kitware 開發與維護,來自使用者的貢獻使得 CMake 快速成長。

CMake 并不直接建構出最終的軟件,而是依照平臺、編譯器產生標準的建構檔(如 Unix Makefile 或 Visual Studio 的 projects/workspaces),然后再依一般的生成方式使用。和標準的 GNU 開發工具相比,CMake 的角色比 Make 更高階,比較接近 Autotools,而且支持多種不同的平臺與編譯器。

雖然跨平臺是 CMake 的重要特色,但由于 CMake 的簡單與彈性,在單一平臺上使用也很便利。

前言

  • 每一個需要進行 CMake 操作的目錄下面,都必須存在文件 CMakeLists.txt
  • CMake 命令不區分大小寫。習慣上,CMake 命令全小寫,預定義變量全大寫。
  • 變量使用 ${} 方式取值,但是在 if 控制語句中是直接使用變量名。
  • command(parameter1 parameter2 …),參數使用括號括起,參數之間使用空格或分號分開。

常用預定義變量

CMake 的預定義變量

  • PROJECT_SOURCE_DIR:工程根目錄;
  • PROJECT_BINARY_DIR:運行 CMake 命令的目錄。建議定義為 ${PROJECT_SOURCE_DIR}/build 下;
  • CMAKE_INCLUDE_PATH:環境變量,非 CMake 變量;
  • CMAKE_LIBRARY_PATH:環境變量;
  • CMAKE_CURRENT_SOURCE_DIR:當前處理的 CMakeLists.txt 文件所在路徑;
  • CMAKE_CURRENT_BINARY_DIR:target 編譯目錄;
    • 使用 add_subdirectory 命令可以更改該變量的值;
    • set(EXECUTABLE_OUTPUT_PATH <dir>) 命令不會對該變量有影響,但改變了最終目標文件的存儲路徑。
  • CMAKE_CURRENT_LIST_FILE:輸出調用該變量的 CMakeLists.txt 的完整路徑;
  • CMAKE_CURRENT_LIST_LINE:輸出該變量所在的行;
  • CMAKE_MODULE_PATH:定義自己的 CMake 模塊所在路徑;
  • EXECUTABLE_OUTPUT_PATH:重新定義目標二進制可執行文件的存放位置;
  • LIBRARY_OUTPUT_PATH:重新定義目標鏈接庫文件的存放位置;
  • PROJECT_NAME:返回由 project 命令定義的項目名稱;
  • CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS:用來控制 if…else… 語句的書寫方式。

系統信息預定義變量

  • CMAKE_MAJOR_VERSION:CMake 主版本號,如 3.12.0 中的 3;
  • CMAKE_MINOR_VERSION:CMake 次版本號,如 3.12.0 中的 12;
  • CMAKE_PATCH_VERSION:CMake 補丁等級,如 3.12.0 中的 0;
  • CMAKE_SYSTEM:系統名稱,例如 Windows-10.0.17134;
  • CMAKE_SYSTEM_NAME:不包含版本號的系統名,如 Windows;
  • CMAKE_SYSTEM_VERSION:系統版本號,如 10.0.17134;
  • CMAKE_SYSTEM_PROCESSOR:處理器架構,如 AMD64;
  • UNIX:在所有的類 UNIX 平臺為 true,包括 macOS 和 Cygwin;
  • WIN32:在所有的 Win32 平臺為 true,包括 Cygwin。

開關選項

  • BUILD_SHARED_LIBS:控制默認的庫編譯方式;
    • 如果未進行設置,使用 add_library 時又沒有指定庫類型,默認編譯生成的庫都是靜態庫。
  • CMAKE_C_FLAGS:設置 C 編譯選項;
  • CMAKE_CXX_FLAGS:設置 C++ 編譯選項。

常用命令

cmake_minimum_required

該語句一般放置在 CMakeLists.txt 的開頭,用于說明 CMake 最低版本要求。

cmake_minimum_required(VERSION 3.5)

上述示例指 CMake 的版本號最低為 3.5。

project

project(<PROJECT-NAME>)
  • <PROJECT-NAME> 指工程名稱。

該命令一般緊跟 cmake_minimum_required 命令之后,定義了工程的名稱。但項目最終編譯生成的可執行文件并不一定是這個項目名稱,而是由另一條命令確定的,后面會介紹。

執行了該命令之后,將會自動創建兩個變量:

  • <PROJECT-NAME>_BINARY_DIR:二進制文件保存路徑;
  • <PROJECT-NAME>_SOURCE_DIR:源代碼路徑。
project(Lab)

執行了上一條指令,即定義了一個項目名稱 Lab,相應的會生成兩個變量:Lab_BINARY_DIRLab_SOURCE_DIR

CMake 中預定義了兩個變量:PROJECT_BINARY_DIRPROJECT_SOURCE_DIR

在這個例子中:

  • PROJECT_BINARY_DIR 等價于 Lab_BINARY_DIR
  • PROJECT_SOURCE_DIR 等價于 Lab_SOURCE_DIR

建議直接使用 PROJECT_BINARY_DIRPROJECT_SOURCE_DIR,這樣即使項目名稱發生了變化,也不會影響 CMakeLists.txt 文件。

關于 PROJECT_BINARY_DIRPROJECT_SOURCE_DIR 這兩個變量是否相同的問題,涉及到編譯方法是內部編譯還是外部編譯。如果是內部編譯,則這兩個變量相同;如果是外部編譯,則兩個變量不同。此處對內部編譯與外部編譯做出介紹:

外部構建與內部構建

假設此時已經完成了 CMakeLists.txt 的編寫,在 CMakeLists.txt 所在目錄下,有兩種執行 CMake 的方法:

cmake .\\
make

以及

mkdir build
cd .\\build
cmake ..\\
make

第一種方法是內部構建,第二種方法是外部構建。上述兩種方法中,最大不同在于 CMake 與 Make 的工作路徑不同。

內部構建方法中,CMake 生成的中間文件和可執行文件都會存放在項目目錄中;外部構建方法中,中間文件與可執行文件都存放在 build 目錄中。

建議使用外部構建方法。優點顯而易見:最大限度地保持了代碼目錄的整潔,生成、編譯與安裝是不同于項目目錄的其他目錄中,在外部構建方法下,PROJECT_SOURCE_DIR 指向目錄與內部構建相同,為 CMakeLists.txt 所在根目錄;而 PROJECT_BINARY_DIR 不同,它指向 CMakeLists.txt 所在根目錄下的 build 目錄。

set

set(<variable> <value>… CACHE <type> <docstring> [FORCE])

示例:

set(CMAKE_INSTALL_PREFIX C:\\Program Files\\${PROJECT_NAME})

該示例顯式地將 CMAKE_INSTALL_PREFIX 的值定義為 C:\\Program Files\\${PROJECT_NAME}。如此,在外部構建情況下執行 make install 命令時,Make 會將生成的可執行文件拷貝到 C:\\Program Files\\${PROJECT_NAME}\\bin 目錄下。

當然,可執行文件的安裝路徑 CMAKE_INSTALL_PREFIX 也可以在執行 cmake 命令的時候指定,cmake 參數如下:

cmake -D CMAKE_INSTALL_PREFIX="C:\\Program Files\\…"

如果 cmake 參數和 CMakeLists.txt 文件中都不指定該值的話,則該值為默認值(Windows 下為 C:\\Program Files\\${PROJECT_NAME},UNIX 下為 /usr/local)。

add_subdirectory

add_subdirectory(source_dir [binary_dir]
                 [EXCLUDE_FROM_ALL])
  • source_dir:源文件路徑;
  • [binary_dir]:中間二進制與目標二進制文件存放路徑;
  • [EXECLUDE_FROM_ALL]:將這個目錄從編譯過程中排除。

這個命令用于向當前工程添加存放源文件的子目錄。

include_directories

include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 …])
  • [AFTER|BEFORE]:追加標志,指定控制追加或預先添加;
  • [SYSTEM]:指定該目錄為系統包含目錄;
  • dir1, …, dir n:添加的一系列頭文件搜索路徑。

向工程添加多個特定的頭文件搜索路徑,路徑之間用空格分隔。類似于 GCC 中的編譯參數 -l,即指定編譯過程中編譯器搜索頭文件的路徑。當項目需要的頭文件不在系統默認的搜索路徑時,則指定該路徑。

add_executable

add_executable(<name> [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               [source1] [source2 …])
  • name:可執行文件名;
  • source1, …, source n:生成該可執行文件的源文件。

該命令給出源文件,并指出需要編譯出的可執行文件名。

示例 1:

add_executable(hello main.cpp)

該示例中,利用源文件 main.cpp,編譯出名為 hello 的可執行文件。

示例 2:

set(SRC_LIST main.cc
             rpc/CRNode.cpp
             rpc/Schd_types.cpp
             task/TaskExecutor.cpp
             task/TaskMoniter.cpp
             util/Const.cpp
             util/Globals.cc
   )

add_executable(CRNode ${SRC_LIST})

該示例中,定義了該工程會生成一個名為 CRNode 的可執行文件,所依賴的源文件是變量 SRC_LIST 定義的源文件列表。

如果前面 project() 命令中定義的項目名稱也是 CRNode,沒有什么問題,兩者之間沒有任何關系。

add_library

add_library(<name> [STATIC|SHARED|MODULE]
            [EXCLUDE_FROM_ALL]
            [source1] [source2 …])
  • name:庫文件名稱;
  • [STATIC|SHARED|MODULE]:生成的庫的文件類型(靜態庫/共享庫);
  • [EXCLUDE_FROM_ALL]:表示該庫不會被默認構建;
  • source1, …, sourceN:生成庫所依賴的源文件。

示例:

add_library(hello SHARED ${LIB_hello_SRC})

link_directories

link_directories(directory1 directory2 …)

該命令用于添加外部庫的搜索路徑。

target_link_libraries

target_link_libraries(<target> … <item>…)
  • target:目標文件;
  • item:鏈接外部庫文件。

指定鏈接目標文件時需要鏈接的外部庫,效果類似于 GCC 編譯參數 -L,解決外部庫依賴的問題。

message

message([<mode>] "message to display" …)
  • mode:確定消息的類型:

    模式 描述
    (none) 重要信息
    STATUS 附帶信息
    WARNING CMake 警告,繼續處理
    AUTHOR_WARNING CMake 警告(dev),繼續處理
    SEND_ERROR CMake 錯誤,繼續處理,但跳過生成過程
    FATAL_ERROR CMake 錯誤,停止處理和生成過程
    DEPRECATION 如果分別啟用 CMAKE_ERROR_DEPRECATEDCMAKE_WARN_DEPRECATED,則棄用 CMake 錯誤或警告,否則無消息
  • "message to display":需要顯示的文字消息。

set_target_properties

set_target_properties(target1 target2 …
                      PROPERTIES prop1 value1
                      prop2 value2 …)

設置目標的某些屬性,改變它們構建的方式。

該指令為一個目標設置屬性,語法是列出所有用戶想要變更的文件,然后提供想要設置的值。用戶可以使用任何想用的屬性與對應的值,并在隨后的代碼中調用 GET_TARGET_PROPERTY 命令取出屬性的值。

影響目標輸出文件的屬性PROPERTIES詳述如下:

PREFIX、SUFFIX

  • PREFIX 覆蓋了默認的目標名前綴(如 lib);
  • SUFFIX 覆蓋了默認的目標名后綴(如 .so)。

IMPORT_PREFIX、IMPORT_SUFFIX

PREFIXSUFFIX 是等價的屬性,但針對的是 DLL 導入庫(即共享庫目標)。

OUTPUT_NAME

構建目標時,OUTPUT_NAME 用來設置目標的真實名稱。

LINK_FLAGS

為一個目標的鏈接階段添加額外標志。

LINK_FLAGS_<CONFIG> 將為配置 <CONFIG> 添加鏈接標志,如 DebugReleaseRelWithDebInfoMinSizeRel

COMPILE_FLAGS

設置附加的編譯器標志,在構建目標內的源文件時用到。

LINKER_LANGUAGE

改變鏈接可執行文件或共享庫的工具。默認值是設置與庫中文件相匹配的語言。

CXX 與 C 是該屬性的公共值。

VERSION、SOVERSION

VERSION 指定構建的版本號,SOVERSION 指定構建的 API 版本號。

構建或安裝時,如果平臺支持符號鏈接,且鏈接器支持 so 名稱,那么將會創建恰當的符號鏈接。

如果只指定兩者中的一個,缺失的另一個假定為具有相同版本號。

示例 1:

set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")

示例 2:

set_target_properties(hello PROPERTEIES VERSION 1.2 SOVERSION 1)

該命令用于控制版本,VERSION 指代動態庫版本,SOVERSION 指代 API 版本。

aux_source_directory

查找某個路徑下的所有源文件,并將源文件列表存儲到一個變量中。

aux_source_directory(<dir> <variable>)

示例:

aux_source_directory(. SRC_LIST)

該指令將當前目錄下的文件列表全部存入變量 SRC_LIST 中。

install

install 命令可以按照對象的不同分為多種類型:目標文件、非目標文件、目錄。

目標文件

install(TARGETS targets… [EXPORT <export-name>]
        [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
          PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
         [DESTINATION <dir>]
         [PERMISSIONS permissions…]
         [CONFIGURATIONS [Debug|Release|…]]
         [COMPONENT <component>]
         [NAMELINK_COMPONENT <component>]
         [OPTIONAL] [EXCLUDE_FROM_ALL]
         [NAMELINK_ONLY|NAMELINK_SKIP]
        ] […]
        [INCLUDES DESTINATION [<dir> …]]
        )
  • TARGETS targetstargets 即通過 add_executableadd_library 定義的目標文件,可能是可執行二進制文件、動態庫、靜態庫;
  • DESTINATION <dir>dir 即定義的安裝路徑。安裝路徑可以是絕對/相對路徑。在絕對路徑的情況下,CMAKE_INSTALL_PREFIX 就無效了。
    • 如果希望使用 CMAKE_INSTALL_PREFIX 定義安裝路徑,就需要使用相對路徑,這時候安裝后的路徑就是 ${CMAKE_INSTALL_PREFIX}\\<dir>

非目標文件

.sh 腳本文件,即為典型的非目標文件的可執行程序。

install(<FILES|PROGRAMS> files… DESTINATION <dir>
        [PERMISSIONS permissions…]
        [CONFIGURATIONS [Debug|Release|…]]
        [COMPONENT <component>]
        [RENAME <name>] [OPTIONAL] [EXCLUDE_FROM_ALL])

使用方法和上述目標文件指令的 install 基本相同。唯一的區別是,安裝非目標文件之后的權限還包括 OWNER_EXECUTEGOUP_EXECUTEWORLD_EXECUTE,即 755 權限目錄的安裝。

目錄

install(DIRECTORY dirs… DESTINATION <dir>
        [FILE_PERMISSIONS permissions…]
        [DIRECTORY_PERMISSIONS permissions…]
        [USE_SOURCE_PERMISSIONS] [OPTIONAL] [MESSAGE_NEVER]
        [CONFIGURATIONS [Debug|Release|…]]
        [COMPONENT <component>] [EXCLUDE_FROM_ALL]
        [FILES_MATCHING]
        [[PATTERN <pattern> | REGEX <regex>]
         [EXCLUDE] [PERMISSIONS permissions…]] […])
  • DIRECTORY dirsdirs 是所在源文件目錄的相對路徑。但必須注意,abcabc/ 有很大區別:
    • abc:該目錄將被安裝為目標路徑的 abc
    • abc/:將該目錄內容安裝到目標路徑,但不包括該目錄本身。

示例:

install(DIRECTORY icons scripts/ DESTINATION share/myproj
        PATTERN "CVS" EXCLUDE
        PATTERN "scripts/*" PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ)

該指令的執行結果是:

  • 將 icons 目錄安裝到 <prefix>/share/myproj
  • 將 scripts/ 中的內容安裝到 <prefix>/share/myproj
  • 不包含目錄名為 CVS 的目錄;
  • 對于 scripts/* 文件指定權限為 OWNER_EXECUTEOWNER_WRITEOWNER_READGROUP_EXECUTGROUP_READ

基本控制語法

if

if…else… 語法格式有些類似于 Visual Basic .NET:

if(<expression>)
  # then section.
  COMMAND1(<ARGS> …)
  COMMAND2(<ARGS> …)
  #…
elseif(<expression2>)
  # elseif section.
  COMMAND1(<ARGS> …)
  COMMAND2(<ARGS> …)
  #…
else(<expression>)
  # else section.
  COMMAND1(<ARGS> …)
  COMMAND2(<ARGS> …)
  #…
endif(<expression>)

其中,一定要有 endif()if() 對應。

if 基本用法

  • if(<expression>)expression 不為 0OFFNOFALSENIGNORENOTFOUND、空字符串,或者不含后綴 -NOTFOUND 時,為真;

  • if(NOT <expression>:與上一條相反;

  • if(<expr1> AND <expr2>)

  • if(<expr1> OR <expr2>

  • if(COMMAND command-name):如果 command-name 是命令、宏或函數并可調用,為真;

  • if(EXISTS path-to-file-or-directory):如果給定路徑的文件或目錄存在,為真;

  • if(file1 IS_NEWER_THAN file2):當 file1 比 file2 新,或 file1/file2 中有一個不存在時為真,文件名需使用全路徑;

  • if(IS_DIRECTORY path-to-directory):當給定路徑是目錄時,為真。注意使用全路徑;

  • if(DEFINED <variable>):如果變量已被定義,為真;

  • if(<variable|string> MATCHES regex):當給定變量或字符串能匹配正則表達式 regex 時,為真。此處的 variable 直接使用變量名,而非 ${variable}

    • 示例:

      if("hello" MATCHES "ell") 
      message("true") 
      endif("hello" MATCHES "ell")
      

數字比較表達式

  • if(<var> LESS <number>)
  • if(<var> GREATER <number>)
  • if(<var> EQUAL <number>)

字母表順序比較

  • if(<var1> STRLESS <var2>)
  • if(<var1> STRGREATER <var2>)
  • if(<var1> STREQUAL <var2>)

示例 1:

判斷平臺差異。

if(WIN32)
    message(STATUS "This is windows.")
else(WIN32)
    message(STATUS "This is not windows.")
endif(WIN32)

上述代碼可以控制不同平臺進行不同控制。

也許 else(WIN32) 之類的語句閱讀起來很不舒服,這時候可以加上語句:

set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)

這時候上述結構就可以寫成:

if(WIN32)
    message(STATUS "This is windows.")
else()
    message(STATUS "This is not windows.")
endif()

示例 2:

if(WIN32)
    #do something related to WIN32
elseif(UNIX)
    #do something related to UNIX
elseif(APPLE)
    #do something related to APPLE
endif(WIN32)

while

while(<condition>)
  COMMAND1(<ARGS> …)
  COMMAND2(<ARGS> …)
  …
endwhile(<condition>)

類似于 endifwhile() 也需要 endwhile() 匹配。

真假判斷條件可以參考 if 指令。

foreach

foreach 有多種使用形式的語法,且每個 foreach() 都需要一個 endforeach() 與之匹配。

列表語法

foreach(<loop_var> <arg1> <arg2> …)
  COMMAND1(<ARGS> …)
  COMMAND2(<ARGS> …)
  …
endforeach(<loop_var>)

示例:

aux_source_directory(. SRC_LIST)
foreach(F ${SRC_LIST})
     message(${F})
endforeach(F)

該示例中,先將當前路徑下的所有源文件列表賦值給變量 SRC_LIST,然后遍歷 SRC_LIST 中的文件,并持續輸出信息,信息內容是當前路徑下所有源文件的名稱。

范圍語法

foreach(<loop_var> RANGE <total>)
  COMMAND1(<ARGS> …)
  COMMAND2(<ARGS> …)
  …
endforeach(<loop_var>)

示例:

foreach(v RANGE 10)
    message(${v})
endforeach(v)

該示例從 0 到 total(此處為 10),以 1 為步進。此處輸出為:012345678910

范圍步進語法

foreach(<loop_var> RANGE <start> <stop> [<step>])
  COMMAND1(<ARGS> …)
  COMMAND2(<ARGS> …)
  …
endforeach(<loop_var>)

start 開始,到 stop 結束,以 step 為步進。

示例:

foreach(a RANGE 5 15 3)
    message(${a})
endforeach(a)

此處輸出為 581114

迭代語法

foreach 還可以循環訪問生成的數字范圍。這種迭代有三種類型:

  • 指定單個數字時,范圍將包含元素 [0, …, total](包括 total);
  • 指定兩個數字時,范圍將包含從第一個數字到第二個數字(包括)的元素;
  • 第三個可選數字是用于從第一個數字迭代到第二個數字(包括)的增量。
foreach(<loop_var> IN [LISTS [<list1> …]]
                      [ITEMS [<item1> …]])
  COMMAND1(<ARGS> …)
  COMMAND2(<ARGS> …)
  …
endforeach(<loop_var>)

foreach 循環訪問一個精確的項列表:

  • LISTS:給要遍歷的列表值變量命名,包括空元素(空字符串是零長度列表)。注意宏參數不是變量。
  • ITEMS:結束參數解析并包含迭代中它后面的所有參數。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評論 6 545
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,795評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,943評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,057評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,773評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,106評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,282評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,793評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,507評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,741評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,929評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,325評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,661評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,482評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,702評論 2 380

推薦閱讀更多精彩內容