xmake 测试

makefile 不会写,cmake 虽然常用,但文档太烂了,真不好用。前一段有同事说 xmake 不错,试试


安装

官方网站有脚本可以直接下载安装,这里是 github 下载的安装包

./xmake-v2.8.1.gz.run

测试

不会写makefile?没关系,直接在源码目录运行以下命令即可直接编译:

xmake
xmake -P .

xmake f -m release
xmake f -p cross --cross=arm-linux-gnueabihf- -m release

xmake build -j4
xmake install -o install/armhf
xmake uninstall
xmake clean

xmake会自动扫描在当前目录下的源码结构,生成一个xmake.lua工程描述文件,然后尝试直接编译。

想要直接运行编译后的可执行程序,简单,直接敲:

xmake run

创建项目

xmake create -l c++ -t qt.quickapp test
xmake create -l c -t test
xmake project -k vs2017 -m "debug,release"
xmake project -k makefile

静态库

xmake create -l c -t static test

动态库

xmake create -l c -t shared test

手工填写 xmake.lua

target("test")
    set_kind("binary") 
    add_files("src/*.c")
    
target("test") 
    set_kind("static") 
    add_files("src/*.c")
    add_files("lib/libxxx.a", "obj/bbb.o")
    
target("test")
    set_kind("binary") 
    add_files("src/*.c") 
    if is_mode("debug") then 
        add_cxflags("-DDEBUG") 
    end
    
target("test") 
    set_kind("binary") 
    add_files("src/*.c") 
    after_build(function (target) 
        os.exec("file %s", target:targetfile()) 
    end)

添加依赖

target("test") 
    set_kind("static") 
    add_files("src/test/*.c") 
target("hello") 
    add_deps("test") --添加依赖 
    set_kind("binary") 
    add_files("src/hello/*.c")

直接运行

xmake run

如果有多个target目标,你可以指定需要运行的target名,例如:

xmake run test

想要快速调试程序?加上-d参数即可

xmake run -d test

界面

xmake f --menu

可以显示指定 target 配置信息,可以看到各种配置来源于哪个配置文件和具体的行数。

xmake show -t <target>

检查工程配置和代码。

xmake check

xrepo项目

xmake create -l c++ test

xmake.lua 文件

add_rules("mode.debug", "mode.release")
add_requires("cereal")
target("test")
    set_kind("binary")
    add_files("src/*.cpp")
    add_packages("cereal")

github 无法下载,手工下载

mkdir lib && cd lib
wget -O cereal-1.3.2.tar.gz https://ghproxy.com/https://github.com/USCiLab/cereal/archive/refs/tags/v1.3.2.tar.gz

设置搜索库绝对路径

xmake --pkg_searchdirs="/mnt/d/Download/test/lib/"

编译

xmake

看起来可以,不过本地没有头文件,看起来也不太舒服

语义版本设置

Xmake 的依赖包管理是完全支持语义版本选择的,例如:”~1.6.1”,对于语义版本的具体描述见:https://semver.org/

比如下面一些语义版本写法:

add_requires("tbox 1.6.*", "pcre 1.3.x", "libpng ^1.18") 
add_requires("libpng ~1.16", "zlib 1.1.2 || >=1.2.11 <1.3.0")

当然,如果我们对当前的依赖包的版本没有特殊要求,那么可以直接这么写:

add_requires("tbox", "libpng", "zlib")

这会使用已知的最新版本包,或者是master分支的源码编译的包,如果当前包有git repo地址,我们也能指定特定分支版本:

add_requires("tbox master") 
add_requires("tbox dev")

Xmake 的语义版本支持,在几年前就已经很好的支持,而 vcpkg 也仅仅在最近一年才通过清单模式勉强支持它。

即使现在,vcpkg 对版本语义的支持也很受限,只能支持 >=1.0, 1.0 等几种版本模式,想要选择任意版本的包,比如 >=1.0 <1.5 等复杂版本条件的包,vcpkg 还是无法支持。

可选包设置

如果指定的依赖包当前平台不支持,或者编译安装失败了,那么 Xmake 会编译报错,这对于有些必须要依赖某些包才能工作的项目,这是合理的。 但是如果有些包是可选的依赖,即使没有也可以正常编译使用的话,可以设置为可选包:

add_requires("tbox", {optional = true})
使用系统库

默认的设置,Xmake 会去优先检测系统库是否存在(如果没设置版本要求),如果用户完全不想使用系统库以及第三方包管理提供的库,那么可以设置:

add_requires("tbox", {system = false})

而如果配置成:

add_requires("tbox", {system = true})

就是仅仅查找使用系统库,不会去远程下载安装它,这类似于 CMake 的 find_package,但是集成方式更加简单一致。

使用调试版本的包

如果我们想同时源码调试依赖包,那么可以设置为使用debug版本的包(当然前提是这个包支持debug编译):

add_requires("tbox", {debug = true})
启用包的可选特性

我们也可以安装带有指定特性的包,比如安装开启了 zlib 和 libx265 的 ffmpeg 包。

add_requires("ffmpeg", {configs = {zlib = true, libx265 = true}})
传递额外的编译选项

我们也可以传递额外的编译选项给包:

add_requires("spdlog", {configs = {cxflags = "-Dxxx"}})

独立的包管理命令 Xrepo

Xrepo 是一个基于 Xmake 的跨平台 C/C++ 包管理器。

它是一个独立于 Xmake 的命令程序,用于辅助用户去管理依赖包,类似 vcpkg/conan,但相比它们,有额外多了一些实用的特性,我们会简单介绍一些。

多仓库管理

除了可以直接从官方仓库:xmake-repo 检索安装包之外, 我们还可以添加任意多个自建的仓库,甚至可以完全隔离外网,仅仅在公司内部网络维护私有包的安装集成。

只需要通过下面的命令,添加上自己的仓库地址:

xrepo add-repo myrepo https://github.com/mygroup/myrepo

基本使用

xrepo install zlib tbox

安装指定版本包

完整支持 Semantic Versioning (语义版本)。

xrepo install "zlib 1.2.x" 
xrepo install "zlib >=1.2.0"

安装指定平台包

xrepo install -p iphoneos -a arm64 zlib 
xrepo install -p android [--ndk=/xxx] zlib 
xrepo install -p mingw [--mingw=/xxx] zlib 
xrepo install -p cross --sdk=/xxx/arm-linux-musleabi-cross zlib

安装调试版本包

xrepo install -m debug zlib

安装动态库版本包

xrepo install -k shared zlib

安装指定配置包

xrepo install -f "vs_runtime=MD" zlib 
xrepo install -f "regex=true,thread=true" boost

安装第三方包管理器的包

xrepo install brew::zlib 
xrepo install vcpkg::zlib 
xrepo install conan::zlib/1.2.11

查看包的库使用信息

xrepo fetch pcre2 { { linkdirs = { "/usr/local/Cellar/pcre2/10.33/lib" }, links = { "pcre2-8" }, defines = { "PCRE2_CODE_UNIT_WIDTH=8" }, includedirs = "/usr/local/Cellar/pcre2/10.33/include" } }

导入导出安装后的包

xrepo 可以快速导出已经安装后的包,包括对应的库文件,头文件等等。

xrepo export -o /tmp/output zlib

也可以在其他机器上导入之前导出的安装包,实现包的迁移。

xrepo import -i /xxx/packagedir zlib

搜索支持的包

xrepo search zlib "pcr*" 
    zlib: 
      -> zlib: A Massively Spiffy Yet Delicately Unobtrusive Compression Library (in xmake-repo) 
    pcr*:
      -> pcre2: A Perl Compatible Regular Expressions Library (in xmake-repo) 
      -> pcre: A Perl Compatible Regular Expressions Library (in xmake-repo)

另外,现在还可以从 vcpkg, conan, conda 以及 apt 等第三方包管理器中搜索它们的包,只需要加上对应的包命名空间就行,例如:

xrepo search vcpkg::pcre 
The package names: 
    vcpkg::pcre: 
      -> vcpkg::pcre-8.44
      #8: Perl Compatible Regular Expressions -> vcpkg::pcre2-10.35
      #2: PCRE2 is a re-working of the original Perl Compatible Regular Expressions library

包虚拟环境管理

我们可以通过在当前目录下,添加 xmake.lua 文件,定制化一些包配置,然后进入特定的包 shell 环境。

add_requires("zlib 1.2.11") 
add_requires("python 3.x", "luajit")

进行虚拟环境:

xrepo env shell 
python --version 
luajit --version

在 Xmake 中集成第三方构建系统

在 Xmake 中集成 Cmake 项目

Xmake 并不打算分裂 C/C++ 生态,它能很好和兼容复用现有 cmake/autoconf/meson 维护的项目,比如可以将一些其他使用 CMake 维护的代码库,直接本地集成进来,参与混合编译。

也就是说,Xmake 不会强制用户将所有的项目重新 port 到 xmake.lua,现有的 CMake 项目,一样可以快速集成到 Xmake 项目中去。

例如,我们有如下项目结构:

.
├── foo 
│ ├── CMakeLists.txt 
│ └── src 
│ ├── foo.c 
│ └── foo.h 
├── src 
│ └── main.c 
├── test.lua 
└── xmake.lua

foo 目录下是一个使用 CMake 维护的静态库,而根目录下使用了 Xmake 来维护,我们可以在 xmake.lua 中通过定义 package("foo") 包来描述如何构建 foo 代码库。

add_rules("mode.debug", "mode.release") 

package("foo") 
add_deps("cmake") 
set_sourcedir(path.join(os.scriptdir(), "foo")) 
on_install(function (package) 
local configs = {} 
table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:debug() and "Debug" or "Release")) 
table.insert(configs, "-DBUILD_SHARED_LIBS=" .. (package:config("shared") and "ON" or "OFF")) 
import("package.tools.cmake").install(package, configs) 
end) 

on_test(function (package) 
assert(package:has_cfuncs("add", {includes = "foo.h"})) 
end) 
package_end() 

add_requires("foo") 
target("demo") 
set_kind("binary") 
add_files("src/main.c") 
add_packages("foo")

其中,我们通过 set_sourcedir() 来设置 foo 包的代码目录位置,然后通过 import 导入 package.tools.cmake 辅助模块来调用 cmake 构建代码,xmake 会自动获取生成的 libfoo.a 和对应的头文件。

如果仅仅本地源码集成,我们不需要额外设置 add_urls 和 add_versions。

定义完包后,我们就可以通过 add_requires("foo") 和 add_packages("foo") 来集成使用它了,就跟集成远程包一样的使用方式。

另外,on_test 是可选的,如果想要严格检测包的编译安装是否成功,可以在里面做一些测试。

在 Xmake 中集成 Autoconf 项目

我们也可以使用 package.tools.autoconf 来本地集成带有 autoconf 维护的第三方代码库。

package("libev") 
set_sourcedir(path.join(os.scriptdir(), "3rd/libev")) 

on_install(function (package) 
import("package.tools.autoconf").install(package) 
end)

package.tools.autoconf 和 package.tools.cmake 模块都是可以支持 mingw/cross/iphoneos/android 等交叉编译平台和工具链的,xmake 会自动传递对应的工具链进去,用户不需要做任何其他事情。

在 Xmake 中集成 Gn 项目

我们也可以使用 package.tools.gn 来本地集成带有 GN 维护的第三方代码库。

package("skia") 
set_sourcedir(path.join(os.scriptdir(), "3rd/skia")) 
add_deps("gn", "ninja") 

on_install(function (package) 
import("package.tools.gn").install(package) 
end)

在 Xmake 中查找使用 CMake/C++ 包

现在 CMake 已经是事实上的标准,所以 CMake 提供的 find_package 已经可以查找大量的系统库和模块,我们也可以完全复用 CMake 的这部分生态来扩充 xmake 对包的集成。

只需要像集成 vcpkg/conan 包那样,将包命名空间改成 cmake:: 就可以了。

add_requires("cmake::ZLIB", {alias = "zlib", system = true}) 

target("test") 
set_kind("binary") 
add_files("src/*.c") 
add_packages("zlib")

我们指定 system = true 告诉 xmake 强制从系统中调用 cmake 查找包,如果找不到,不再走安装逻辑,因为 cmake 没有提供类似 vcpkg/conan 等包管理器的安装功能,只提供了包查找特性。

指定版本

add_requires("cmake::OpenCV 4.1.1", {system = true})

指定组件

add_requires("cmake::Boost", {system = true, configs = {components = {"regex", "system"}})}

预设开关

add_requires("cmake::Boost", {system = true, configs = {components = {"regex", "system"}, presets = {Boost_USE_STATIC_LIB = true}}})

相当于内部调用 find_package 查找包之前,在 CMakeLists.txt 中预定义一些配置,控制 find_package 的查找策略和状态。

set(Boost_USE_STATIC_LIB ON) 
-- will be used in FindBoost.cmake find_package(Boost REQUIRED COMPONENTS regex system)

设置环境变量

add_requires("cmake::OpenCV", {system = true, configs = {envs = {CMAKE_PREFIX_PATH = "xxx"}}})

指定自定义 FindFoo.cmake 模块脚本目录
mydir/cmake_modules/FindFoo.cmake

add_requires("cmake::Foo", {system = true, configs = {moduledirs = "mydir/cmake_modules"}})

在 Cmake 中集成 Xrepo 依赖包

除了可以在 Xmake 中集成 CMake 项目,我们也可以在 CMake 中直接集成 Xmake/Xrepo 提供的包,只需要使用 xrepo-cmake 提供的 CMake Wrapper。

例如:

cmake_minimum_required(VERSION 3.13.0) 
project(foo)

# Download xrepo.cmake if not exists in build directory. 
if(NOT EXISTS "${CMAKE_BINARY_DIR}/xrepo.cmake") 
    message(STATUS "Downloading xrepo.cmake from https://github.com/xmake-io/xrepo-cmake/") 

    # mirror https://cdn.jsdelivr.net/gh/xmake-io/xrepo-cmake@main/xrepo.cmake 
    file(DOWNLOAD "https://raw.githubusercontent.com/xmake-io/xrepo-cmake/main/xrepo.cmake" "${CMAKE_BINARY_DIR}/xrepo.cmake" TLS_VERIFY ON) 
endif() 

# Include xrepo.cmake so we can use xrepo_package function. 
include(${CMAKE_BINARY_DIR}/xrepo.cmake) 
xrepo_package("zlib") 
add_executable(example-bin "") 
target_sources(example-bin PRIVATE src/main.cpp ) 
xrepo_target_packages(example-bin zlib)
添加带有配置的包

我们,也可以跟在 Xmake 中一样,定制包的可选特性。

xrepo_package("gflags 2.2.2" CONFIGS "shared=true,mt=true") 
add_executable(example-bin "") 
target_sources(example-bin PRIVATE src/main.cpp ) 
xrepo_target_packages(example-bin gflags)
使用来自第三个存储库的包

除了从 Xmake 官方维护的存储库安装软件包之外,我们也可以直接在 CMake 中使用它来安装来自第三方仓库的包,只需将仓库名称添加为命名空间即可。

例如:vcpkg::zlib, conan::pcre2。

xrepo_package("conan::gflags/2.2.2") 
xrepo_package("conda::gflags 2.2.2") 
xrepo_package("vcpkg::gflags")
xrepo_package("brew::gflags")

通过这种方式,我们将在 CMake 中集成使用 vcpkg/conan 包的方式进行了统一,并且额外提供了自动包安装特性,以及对 homebrew/conda 等其他包仓库的支持。


xmake-vscode 插件

我们之前的所有实验,都是使用 xmake 的命令行程序在终端下操作完成的,这对于一些初学者来说还是有不少门槛的,并且操作起来也不能够像其它 IDE 等带有可视化界面的开发环境那样顺手,尤其是代码的编辑、编译和断点调试都需要不停的切换各种终端、编辑器环境才能完成。

为了方便我们的日常开发,xmake 官方提供了可以快速无缝集成到 Visual Studio Code 编辑器的 xmake-vscode 插件,使用这个插件我们可以通过 vscode 编辑器环境来一站式进行 C/C++ 程序开发,内置 xmake 编译、断点调试、编译错误分析定位、编译配置的快速切换等各种实用功能。

而 Visual Studio Code 编辑器是微软推出的一款轻量级跨平台的编辑器,具有非常好的跨平台性、可扩展性,我们在实验环境的桌面上,就能找到带有 Visual Studio Code 字样的图标,双击运行就可以打开它。

安装 xmake-vscode 插件

首先在环境中安装 xmake,执行如下命令:

bash <(curl -kfsSL https://labfile.oss.aliyuncs.com/courses/2764/shget.text) v2.3.7
source ~/.xmake/profile

检查版本,验证安装成功

xmake --version

在使用 xmake-vscode 插件之前,我们需要安装它,安装过程很简单,只需要切换到 vscode 的插件扩展 tab 页,然后输入 xmake 搜索相关插件。

如果看到下图所示内容,说明我们已经成功找到了 xmake-vscode 插件,然后点击旁边的 Install 字样按钮就可以完成安装了。

创建和打开 C/C++ 工程

插件安装完成后,我们可以先来创建一个空的 C 工程,之前介绍过可以使用 xmake create 命令来创建工程。但是这里我们打算直接使用 xmake-vscode 插件提供的工程创建功能来完成 C/C++ 工程的创建。

在创建之前,需要先准备一个空目录用来存放工程文件,例如将工程放置在 ~/Code/vscode_test 目录下,如果还不存在此目录,可以先手动创建下。

然后,我们直接在 vscode 里面打开这个空目录,只需要通过点击下图红框位置的按钮。

点击后,我们进入刚刚创建的 vscode_test 目录,选中并打开它。

打开目录后,我们就可以开始创建工程文件了,继续进入菜单的 view 子菜单打开 vscode 里面的命令面板,如下图。

命令面板打开后,我们输入 xmake 字符串,就会看到一系列跟 xmake 插件相关的命令,然后从其中找到带 Create Project 字样的命令。

这个时候,由于当前还没有 xmake.lua 文件,会在底下弹出一个提示框,继续点击里面的蓝色按钮。
接着就会弹出编程语言的选择列表,这里选择第二项,也就是 C++ 语言。

选择完语言后,还会提示选择工程类型,这里选择 console 终端程序类型。

选择完成后,整个项目就创建好了,下图左边就是新创建的工程文件,根目录下有 xmake.lua,而图最底下的工具栏就是 xmake 插件的操作面板了,我们的大部分操作都可以通过这个面板快速完成,包括:编译、运行、调试以及配置切换等等。

工具栏面板介绍

创建完成工程后,我们会看到 vscode 底部工具链出现了一排跟 xmake 相关的操作面板,xmake 的大部分操作都可以通过这个面板上的工具按钮来快速完成。

另外,不仅仅是创建工程,如果安装 xmake-vscode 插件后,使用 vscode 打开一个带有 xmake.lua 文件的 C/C++ 工程根目录,那么 vscode 底部的 xmake 工具栏面板也会被自动激活。

通过上图,我们大概能知道每个按钮的具体功能,具体更进一步的使用方式,我们会接下来会挨个讲解。

编译 C/C++ 程序

刚刚我们通过 vscode 创建的一个 C++ 项目工程,其内部就是执行了 xmake 的 xmake create vscode_test 命令来完成的,整个工程文件结构如下。

.
├── src
│   └── main.cpp
└── xmake.lua

在编译之前,先调整下生成的 xmake.lua 文件,修改成如下配置。

add_rules("mode.debug", "mode.release")

target("test")
set_kind("binary")
add_files("src/*.cpp")

其实,也就是将程序目标名改成 test,然后点击 vscode 底下的 xmake 工具栏里面的 Build 按钮,来编译我们新创建的 C++ 工程。

运行程序

编译完成后就可以尝试运行程序了,还是使用底下的工具栏按钮,点击运行图标。
如果运行成功,就会看到实际的运行输出信息:Hello world!。

调试程序

接下来,我们重点讲解如何通过 vscode 配合 xmake-vscode 插件来实现断点调试 C/C++ 程序。

不过在调试前还需要做一些准备工作,由于默认 xmake 采用 release 模式编译的目标程序,是不带调试符号信息的,因此我们需要先将编译模式切换到 debug 调式编译模式去重新编译它,使其带上调试符号信息。

具体如何切换到 debug 模式,可以参考下图的操作,点击底下 release 文本所在的按钮,然后在上面列出的列表中,选择 debug 项即可。

完成切换后可以看到底下的 release 按钮已经变成了 debug 字样,然后点击 build 按钮,就可以重新编译带有调试符号的目标程序了。

调试版本程序编译完成后,还需要额外做一件事,那就是安装 vscode 的 C/C++ 插件,因为 xmake-vscode 插件的调试功能是基于这个插件的,我们仅仅只需要首次使用时安装它即可。

重新切到插件市场页面,搜索 C/C++,显示出来的第一项就是,点击 Install 安装即可。

安装好 C/C++ 插件,就可以开始调试操作了。首先点击 main.cpp 打开源文件,然后在需要下断点的代码行左侧位置点击下断点,如果出现小红点,说明已经成功下好了调试断点。

断点下好后,就可以点击底下的调试按钮,开启断点调试了,如果一切顺利,程序运行起来后就会命中刚刚设置的断点。

目标程序切换

接下来再来详细讲解编译,之前我们已经使用过 build 按钮来编译工程,但如果一个项目中存在多个目标程序又不想全部编译,我们就需要指定编译哪个目标程序。

在命令行中,我们可以通过 xmake build test 命令来显示的指定编译哪个目标,而在 vscode 中,xmake 插件也提供了很方便的目标切换操作,来快速切换编译。

首先修改 xmake.lua 文件,新增一个名为 test2 的目标程序,用于之后的目标切换测试,例如。

add_rules("mode.debug", "mode.release")

target("test")
set_kind("binary")
add_files("src/*.cpp")

target("test2")
set_kind("binary")
add_files("src/*.cpp")

然后点击底下 default 字样的按钮,之后会在顶部显示整个项目所有目标程序名的列表,我们点击其中的 test2 目标程序,就可以完成切换。

如果切换成功,底下的 default 文本就会变成 test2,由于默认编译 xmake 会自动编译所有目标程序,所以最初显示的是 default 文本。

这个时候,我们再执行编译,就能看到实际仅仅只编译了我们指定的 test2 目标程序。

编译错误信息

如果我们的工程代码没写对导致编译出错,可以直接从编译输出中看到错误信息。

而 xmake-vscode 插件还会自动解析编译错误输出信息,分类每个编译错误,并可通过双击指定错误,跳转定位到指定的错误代码位置,也就是下图所示位置。

查看编译详细信息

在命令行中,我们可以通过 xmake -v 来查看编译过程中的详细命令参数信息,而在 vscode 中同样可以通过配置开启详细输出。

首先打开菜单,点击 File -> Preferences -> Settings 子菜单。

然后在打开的 Setting 配置页面,输入 xmake 找到所有跟 xmake 插件相关的配置项,其中有一项是 BuildLevel,它就是用于设置编译过程中的输出信息级别。

默认是 warnings 级别,仅仅输出编译警告信息以及正常信息,我们把它改为 verbose 级别,就可以输出完整的编译命令行参数了。

至于 debug 级别对应的就是 xmake -vD 的诊断信息,还会进一步打印出错的栈信息。

我们这里将配置切换成 verbose 级别后,再重新构建下程序,看看实际的输出是怎样的。不过由于底部只提供了 build 按钮,没有 rebuild 按钮,为了执行重新编译,我们需要从菜单里面的命令面板中,找到 xmake 的 Rebuild 命令,点击执行才行。

从下图的红色箭头位置找到对应的命令面板。

然后再打开的命令面板中输入 xmake 过滤出所有跟 xmake 相关的命令列表,找到 Rebuild 命令后点击编译即可,如下图。

开启 verbose 级别后,我们就能看到编译输出中完整的命令参数了。

xmake.lua 编辑和自动补全

xmake-vscode 插件还内置了对 xmake.lua 文件编写时的自动提示和补全支持,我们只需要输入 add_、set_ 等字样的文本,就会自动列举出与其相关的所有 api 供我们使用,来方便快速配置 xmake.lua。

我们可以通过编辑 xmake.lua 文件对里面 test2 目标程序添加一个 add_defines("TEST2") 来体验下自动补全功能。

最终的完整配置内容如下。

add_rules("mode.debug", "mode.release")

target("test")
set_kind("binary")
add_files("src/*.cpp")

target("test2")
set_kind("binary")
add_files("src/*.cpp")
add_defines("TEST2")

而我们输入时的补全特性可以通过下面的图片体会到。

完成配置后,我们再来点击底下的 Build 按钮执行编译,看下详细命令输出,应该能够正常看到新加上的 -DTEST2 宏定义了。


简单使用还挺不错,以后尽量不用 cmake 了。
后面那些都是来自官网,我也没看完,备用吧。


Share Tweet Send
0 Comments
Loading...
You've successfully subscribed to 隔窗听雨
Great! Next, complete checkout for full access to 隔窗听雨
Welcome back! You've successfully signed in
Success! Your account is fully activated, you now have access to all content.
豫ICP备19045256号-1