使用Scons软件构建工具

http://cplus.abang.com/od/cprogram/a/sconsintroduct1.htm



在unix/linux环境下用c/c++语言开发大中型项目时,你是否为写复杂的Makefile来编译、打包你的unix/linux c/c++程序,还需要对上万行、几十万代码进行开发和维护感到苦恼不已。”工欲善其事,必先利其器”,这里我将向大家介绍一种开源的软件构件利器——SCons  SCons是下一代的软件构件工具,它跨平台,功能类似autoconf/automake, 它能帮助你更方便,更可靠,更快速的构建软件,下面将重点来介绍一下SCons,及如何SCons。  SCons通过决定哪个具体的模块必须被rebuild,并执行相关的命令来进行rebuild。默认情况下,SCons将在当前目录以以下次序(SConstruct,Sconstruct,sconstruct)来搜索配置文件,从读取的第一个文件中读取相关配置。我们可以通过-f选项来指定替代的配置文件,具体方法如下:  scons –f configfilename  在配置文件需要指定build哪些目标文件,也可以指定build这些目标的规则,对于大多数项目,我们仅仅需要指定目标和输入文件。   在配置文件SConstruct中可以使用函数SConscript()函数来定附属的配置文件。按惯例,这些附属配置文件被命名为”SConscript”,当然也可以使用任意其它名字。  Scons中的配置文件SConscript由python脚本编写,所以我们能使用python脚本的灵活性来处理复杂的build。Scons在读取和执行所有SConscript文件之后,才对目标进行构建,具体流程,我们可以看一下示例: 
 
 
$ scons foo.out scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... cp foo.in foo.out scons: done building targets.




本章重点介绍Scons中的一个重要概念——Enviroment(环境):Scons需要在一个特定的Enviroment(环境)中来构建软件,环境中定义了一些构建软件过程中需要的变量和变量的值。但scons不会自动复制构建目标文件的外部环境,以保证无论何时调用scons,构建的结果不会出现差异。无论何时创建enviroment,你可以通过以下步骤从外部环境中复制环境变量PATH的值到创建的enviroment  
 
 
import os env = Environment(ENV = {’PATH’ : os.environ[’PATH’]})
相似的,可以复制诸如$PATH,$HOME,$JAVA_HOME,$LANG,$SHELL,$TERM等环境变量的值到创建的enviroment
import os env = Environment(ENV = {’PATH’ : os.environ[’PATH’], ’HOME’:os.environ[’HOME’]})
或者你可以复制完整的一个外部enviroment:
import os env = Environment(ENV = os.environ)
如果如上完全拷贝外部环境,我们必须保证外部环境中的环境变量被正确的设置。




Scons默认从包含Sconstruct文件的根目录开始执行,也可以在命令行参数中指定需要build的目标文件  使用命令”scons”将会build当前目录下的所有的目标文件。如果在命令行中没有指定需要build的目标,我们需要使用函数Default()来显示的指定默认目标。  即使在SConscript file中使用Default指定build目标,我们也可以在命令行中显示的指定当前目录(.),来build所有当前目录下的所有目标,具体命令格式如下 
 
 
scons .
如果想build当前目录以外的任何文件,可以使用绝对路径来指定build目录,具体格式如下
scons /dir/
在windows下也可以使用盘符,具体如下:
scons c:\ d:\
为了build特定的目标,我们可以用指定目标为命令行参数,具体如下:
scons foo bar
如果想清除build过程中产生的中间文件和目标文件,可以使用以下命令进行清除
scons –c .



如果想build项目代码树的一个子集,一个目录,只需要指定项目根目录和需要build的子目录,就能对项目的一个子目录进行build,具体如下: 
 
 
scons src/subdir
如果想在项目的子目录进行构建该目录,调用scons时-u选项,scons将会沿着目录层次关系向上遍历,直到找到SConstruct文件,然后build与当前子目录相关的目标:
cd src/subdir scons –u .
scons支持并行build多个目标通过使用-j选项:
scons –j 4
如上例,同时build4个目标。 scons可以通过以下方式来传递变量及其值:
scons debug = 1
在SConscript文件中可以通过ARGUMENTS字典来读取和修改:
if ARGUMENTS.get(’debug’, 0): env = Environment(CCFLAGS = ’-g’) else: env = Environment()


附:

(1)       scons主页:http://www.scons.org/

(2)       scons文档:http://www.scons.org/documentation.php




转载二:

使用 SCons 轻松建造程序


http://www.ibm.com/developerworks/cn/linux/l-cn-scons/index.html


简介: 在软件项目开发过程中,make 工具通常被用来建造程序。make 工具通过一个被称为 Makefile 的配置文件可以自动的检测文件之间的依赖关系,这对于建造复杂的项目非常有帮助,然而,编写 Makefile 本身却不是一件容易的事情。SCons 是一个用 Python 语言编写的类似于 make 工具的程序。与 make 工具相比较,SCons 的配置文件更加简单清晰明了,除此之外,它还有许多的优点。本文将简单介绍如何在软件开发项目中使用 SCons,通过本文,读者可以学习到如何使用 SCons 来建造自己的程序项目。

发布日期: 2011 年 4 月 22 日 
级别: 中级 
访问情况 : 14632 次浏览 
评论: 2 (查看 | 添加评论 - 登录)

平均分 5 星 共 25 个评分 平均分 (25个评分)
为本文评分

前言

make 这个工具自上个世纪 70 年代 Stuart Feldman 在贝尔实验室开发出以来,就一直是类 UNIX 程序员的最爱之一。通过检查文件的修改时间,make 工具可以知道编译目标文件所要依赖的其他文件。在复杂的项目中,如果只有少数几个文件修改过,make 工具知道仅仅需要对哪些文件重新编译就可以确保目标程序被正确的编译链接。这样做的好处就是在编译中,不仅可以节省大量的重复输入,还可以确保程序可以被正确的链接,缩短编译的时间。虽然如此,但是为 make 工具编写建造规则却不是一件容易的事。它复杂的配置规则,即使是有经验的开发者也望而生畏。make 工具的许多替代品便因此而诞生,SCons 就是是其中之一。SCons 是一个用 Python 语言编写的类似于 make 工具的程序。与 make 工具相比较,SCons 的配置文件更加简单清晰明了,除此之外,它还有许多的优点。

SCons 简介

SCons 是一个开放源代码、以 Python 语言编写的下一代的程序建造工具。它最初的名字是 ScCons, 基于由 perl 语言编写的 Cons 软件开发而成,它在 2000 年 8 月获得了由 Software Carpentry 举办的 SC 建造比赛的大奖。现在 ScCons 已经被改名为 SCons,目的是为了表示不再与 Software Carpentry 有联系,当然,还有一个目的,就是为了更方便的输入。

作为下一代的软件建造工具,SCons 的设计目标就是让开发人员更容易、更可靠和更快速的建造软件。与传统的 make 工具比较,SCons 具有以下优点:

  • 使用 Python 脚本做为配置文件
  • 对于 C,C++ 和 Fortran, 内建支持可靠自动依赖分析 . 不用像 make 工具那样需要 执行"make depends"和"make clean"就可以获得所有的依赖关系。
  • 内建支持 C, C++, D, Java, Fortran, Yacc, Lex, Qt,SWIG 以及 Tex/Latex。 用户还可以根据自己的需要进行扩展以获得对需要编程语言的支持。
  • 支持 make -j 风格的并行建造。相比 make -j, SCons 可以同时运行 N 个工作,而 不用担心代码的层次结构。
  • 使用 Autoconf 风格查找头文件,函数库,函数和类型定义。
  • 良好的夸平台性。SCons 可以运行在 Linux, AIX, BSD, HP/UX, IRIX, Solaris, Windows, Mac OS X 和 OS/2 上。

安装 SCons

SCons 支持多种操作系统平台,并为各个系统制作了易于安装的文件,因此在各个系统平台上的安装方法不尽相同,在 SCons 的官方网站上可以查每个平台的具体安装方法。 如果 SCons 没有为你的系统制作相应的安装包,你也可以下载 SCons 的源代码,直接进行安装。 首先,从 SCons 的网站上下载最新的 SCons 源代码包(目前 SCons 的最新版本是 2.0.1)。 其次,解压下载的源代码。视下载的源代码包的格式不同而有不同的方法,在 Windows 平台上,可是使用 winzip 或者其他类似的工具解压。在 Linux 平台上,对于 tar 包,使用 tar 命令进行解压,如:

 $ tar -zxf scons-2.0.1.tar.gz 

然后切换进入解压后的目录进行安装,如

 $ cd scons-2.0.1 
 $ sudo python setup.py install 

命令执行如果没有错误,那么 scons 就被安装到系统上了。对于 Linux 来说,scons 会默认安装到 /usr/loca/bin 目录下,而在 Windows 平台上,则会被安装到 C:\Python25\Scripts 下。

使用 SCons

在 SCons 安装完成后,我们就可以使用 SCons 来建造我们的程序或者项目了。像很多编程书籍那样,在这里我们也通过一个简单的 helloscons 例子来说明如何使用 SCons。例子 helloscons 包含两个文件 :

 $ ls helloscons 
 helloscons.c  SConstruct 

其中 helloscons.c 是程序的源文件,SConstruct 是 scons 的配置文件,类似使用 make 工具时的 Makefile 文件,因此,为了编译你的项目,需要手工创建一个 SConstruct 文件(注意:文件名是大小写敏感的)。不过,在编译的时候不需要指定它。 要编译这个例子,切换到 helloscons 的目录下,运行 scons 命令,如下:

 $ cd helloscons/ 
 $ scons 
 scons: Reading SConscript files ... 
 scons: done reading SConscript files. 
 scons: Building targets ... 
 gcc -o helloscons.o -c helloscons.c 
 gcc -o helloscons helloscons.o 
 scons: done building targets. 

来查看一下运行 scons 命令后得到的结果 :

 $ ls 
 helloscons  helloscons.c  helloscons.o  SConstruct 

建造结束后,得到了二进制文件 helloscons 以及编译的过程中产生的一些以 .o 结尾的目标文件。试运行 helloscons 一下 , 会得到 :

 $ ./helloscons 
 Hello, SCons! 

现在让我们回过头来解析一下 helloscons 这个例子 . helloscons.c 是这个例子里的唯一一个源代码文件,它所做的事就是在控制台上输出一行简单的"Hello,SCons", 它的源代码如下:


清单 1. helloscons.c
				
 #include <stdio.h> 
 #include <stdlib.h> 

 int main(int argc, char* argv[]) 
 { 
        printf("Hello, SCons!\n"); 
        return 0; 
 } 

作为项目建造规则的配置文件 SConstruct 的内容如下 :


清单 2. SConstruct 文件
 Program('helloscons.c') 

你可能很惊讶 SConstruct 的内容只有一行,然而事实确实如此,它比传统的 Makefile 简单很多。SConstruct 以 Python 脚本的语法编写,你可以像编写 Python 脚本一样来编写它。其中的 Program 是编译的类型,说明你准备想要建造一个可执行的二进制程序,它由 helloscons.c 文件来生成。在这里,没有指定生成的可执行程序的名字。不过不用担心,SCons 会把源代码文件名字的后缀去掉,用来作为可执行文件的名字。在这里,我们甚至不需要像 Makefile 那样指定清理的动作,就可以执行清理任务。在 SCons 中,执行清理任务由参数 -c 指定,如下 :

 $ scons -c 
 scons: Reading SConscript files ... 
 scons: done reading SConscript files. 
 scons: Cleaning targets ... 
 Removed helloscons.o 
 Removed helloscons 
 scons: done cleaning targets. 

 $ ls 
 helloscons.c  SConstruct 

如果你不想直接编译可执行的二进制文件,那也没有关系。SCons 支持多种编译类型,你可以根据自己的需要,任意选用其中的一种。SCons 支持的编译类型有:

  • Program: 编译成可执行程序(在 Windows 平台上即是 exe 文件),这是最常用的一种编译类型。
  • Object: 只编译成目标文件。使用这种类型,编译结束后,只会产生目标文件。在 POSIX 系统中,目标文件以 .o 结尾,在 Windows 平台上以 .OBJ 结尾。
  • Library: 编译成库文件。SCons 默认编译的库是指静态链接库。
  • StaticLibrary: 显示的编译成静态链接库,与上面的 Library 效果一样。
  • SharedLibrary: 在 POSIX 系统上编译动态链接库,在 Windows 平台上编译 DLL。

这个简单的 SConstruct 的配置文件从一个侧面说明了使用 SCons 来建造程序是多么的简单。 在实际的项目开发中,程序的建造规则远比 helloscons 这个例子复杂。不过,这些都不是问题,你可以像扩展你自己的 Python 脚本文件那样去扩展 SConstruct. 如果你不想使用 SConstruct 为你设置的默认可执行文件的名字,而是选择你自己喜欢的名字,如 myscons,可以把 SConstruct 的内容修改为 :

 Program('myscons, 'helloscons.c') 

其中 myscons 就是你想要的可执行文件的名字,你可以把它换成任意你喜欢的名字, 不过有点注意的是,这个名字必须放在第一位。 然后在 helloscons 目录下运行 scons 命令,就会得到 myscons 这个可执行文件,如 下:

 $ scons -Q 
 gcc -o helloscons.o -c helloscons.c 
 gcc -o myscons helloscons.o 

其中的 -Q 参数是减少编译时的由 scons 产生的冗余信息。 如果你的项目由多个源文件组成,而且你想指定一些编译的宏定义,以及显式的指定使用某些库,这些对于 SCons 来说,都是非常简单的事情。我们的另外一个例子 helloscons2 很好的说明这种情况。 helloscons2 由 3 个源文件组成 , 它们是 helloscon2.c, file1.c, file2.c,另外指定了编译的选项,同时还指定了使用哪些具体的库。让我们来看一下 helloscons2 的 SConstruct 文件 :

 Program('helloscons2', ['helloscons2.c', 'file1.c', 'file2.c'], 
        LIBS = 'm', 
        LIBPATH = ['/usr/lib', '/usr/local/lib'], 
        CCFLAGS = '-DHELLOSCONS') 

正如你想像的那样,这样一个配置文件并不复杂 . 该 SConstruct 文件指出,它将生成一个名叫 helloscons2 的可执行程序,该可执行程序由 helloscons2.c, file1.c 和 file2.c 组成。注意,多个源文件需要放在一个 Python 列表中。如果你的源程序代码文件很多,有十几个甚至上百个,那不要一个个的将他们都列出来,你可以使用 glob('*.c') 来代替源代码列表。如下 :

 Program('helloscons2', Glob('*.c') 

配置文件中 LIBS,LIBAPTH 和 CCFLAGS 是 SCons 内置的关键字,它们的作用如下:

  • LIBS: 显示的指明要在链接过程中使用的库,如果有多个库,应该把它们放在一个列表里面。这个例子里,我们使用一个称为 m 的库。
  • LIBPATH: 链接库的搜索路径,多个搜索路径放在一个列表中。这个例子里,库的搜索路径是 /usr/lib 和 /usr/local/lib。
  • CCFLAGS: 编译选项,可以指定需要的任意编译选项,如果有多个选项,应该放在一个列表中。这个例子里,编译选项是通过 -D 这个 gcc 的选项定义了一个宏 HELLOSCONS。

运行 scons 命令的时候,可以看到这些变量如何被使用的,让我们执行一下 scons 命令 :

 $ scons -Q 
 gcc -o file1.o -c -DHELLOSCONS file1.c 
 gcc -o file2.o -c -DHELLOSCONS file2.c 
 gcc -o helloscons2.o -c -DHELLOSCONS helloscons2.c 
 gcc -o helloscons2 helloscons2.o file1.o file2.o -L/usr/lib -L/usr/local/lib -lm 

scons 命令的输出显示了可执行程序 helloscons2 如何由多个源文件而生成,以及在 SConstruct 中定义的 LIBS,LIBPATH 和 CCFLAGS 如何被使用。 可见,即使对于复杂的项目,SCons 的编译配置文件也很简单。除此之外,SCons 也提供了很多功能以适应不同的需要,如果读者想更深入的了解如何使用 SCons,可以参考 SCons 的帮助手册。

总结

本文简单介绍了 SCons 的特点,如何安装 SCons,以及通过例子来说明如何在项目中使用 SCons。 作为下一代的软件建造工具,SCons 使用 Python 语言作为配置文件,不但功能强大,而且简单易用,对于跨平台的项目,非常适合。 如果你厌烦了 make 工具的那种复杂的编写规则,尝试一下新鲜的 SCons 吧。






最近用scons的收获


http://dipplum.com/2009/11/13/scons-tips/


背景

Windows平台上的编译工程工具很多,Visual C++ 6的dsp文件,2003以后用的vcproj,以及最近2010支持的用msbuild编译的vcxproj,加上古老的nmake,cygwin、msys移植的make,等等很多。

make的问题是扩展性比较差,尤其是在Windows平台上,nmake的功能更弱,导致写一个工程文件很费劲,管理多个工程有大量的重复工作要做。make最大的问题是不能automake那种简洁的工程写法(automake同样存在扩展问题,m4语言不懂).

最新的msbuild 4.0试用了一下,感觉不是很好,很费劲。因为最近迷恋上了命令行,一直不愿意安装完整的Visual Studio 2010 Beta 2,直接把2010的命令行拷贝出来用cl.exe之类的。想只装.Net Framework 4.0就试试msbuild,发现不行,把2010里的msbuild相关资源拷贝出来也不行。而且用XML手写工程文件很麻烦。

猛然想起之前听说过,鼎鼎大名,但之前简单了解后放弃了的scons。再次使用之后,才发现scons的妙处。


简介Scons

Scons首页赫然是Eric Raymond的推荐,很是唬人的样子。Scons是python写的,这不重要。Scons脚本用语言是python,这是Scons最吸引人的地方。用python写工程文件无疑对我这种熟悉python的人有很大的吸引力。

实际上,Scons最大的优势,就是描述语言本身沿用python。这种设计极大的提升的Scons的扩展能力。Scons本身功能是否强大不重要,对于熟悉python的人来说,扩展Scons达到他的要求,变得比以前简单的多。下面是我用Scons写的一个zlib的工程文件,其中WinLib和WinDLL是我自己扩展的在Windows平台上编译静态库和动态库的方法(cl的宏定义和link的参数有所不同)。

target = 'libz'
defs = 'ASMV ASMINF'
incs = '.'
 
Import('env')
lib_src = Split('contrib/masmx86/inffas32.asm contrib/masmx86/gvmat32.asm adler32.c compress.c crc32.c deflate.c gzio.c infback.c inffast.c inflate.c inftrees.c trees.c uncompr.c zutil.c contrib/masmx86/gvmat32c.c')
lib_src += env.RES('win32/zlib1.rc')
dll_src = lib_src + Split('win32/zlib.def')
 
Import('WinLib WinDLL')
lib = WinLib(target, lib_src, CPPDEFINES=defs, CPPPATH=incs)
dll = WinDLL(target, dll_src, CPPDEFINES=defs, CPPPATH=incs)
 
Alias(target, [lib, dll])

实际上Scons本身的功能比较有限,为了写成这个简洁的模式,还费了不少劲。

Scons的特点

简单列举下最近有体会的Scons的特点,权当备忘录了。

1. Variables的设计很好。

通过Variables可以扩展scons支持的命令行参数,限制参数种类(Bool,枚举,多选),可以把上次的参数保存到文件,下次编译不需要命令行输入过多的参数。下面摘下我的Variable定义:

vars = Variables('options.ini')
vars.AddVariables(
    BoolVariable('verbose', 'Show verbose messages', 'no'),
    BoolVariable('unicode', 'Use unicode API', 'yes'),
    BoolVariable('debug', 'Generate debug code', 'no'),
    EnumVariable('warn', 'Show warning level', 'no', ['no', 'low', 'all']),
    EnumVariable('optimize', 'Set optimization mode', 'normal', ['normal', 'max']),
    PathVariable('repos_root', 'Path to root of source repository', '..'),
    PathVariable('build_root', 'Path to root of build directory', 'build',
        PathVariable.PathIsDirCreate),
    PathVariable('output_root', 'Path to output directory', '.',
        PathVariable.PathIsDirCreate),
)
env = Environment(ENV = os.environ, variables = vars)

2. Environment的设计也很好。

Environment可以保存全套的编译命令,参数配置,用Clone操作可以从一个基本编译环境作出很多变种,支持不同项目类型,不同配置的编译。Clone实在是太方便了。下面是前面提到的Windows平台下编译DLL和静态库、命令行程序的不同参数配置,用Clone操作可以在基本编译环境上,生成多个用于编译静态库、DLL等不同的编译环境,使用不同的参数编译。

libEnv = env.Clone()
libEnv.Append(CPPDEFINES = Split('_WINDOWS _WINDLL'), LINKFLAGS = ['/SUBSYSTEM:WINDOWS'])
dllEnv = env.Clone()
dllEnv.Append(CPPDEFINES = ['_WINDOWS'], LINKFLAGS = ['/SUBSYSTEM:WINDOWS'])
conEnv = env.Clone()
conEnv.Append(CPPDEFINES = ['_CONSOLE'], LINKFLAGS = ['/SUBSYSTEM:CONSOLE'])

题外话,关于Windows下编译不同工程的参数,这篇文章总结的很全。

3. Builder的设计问题。

Scons可以用env.Library(‘liba’, ‘a.c’)的方法用环境的定义编译静态库,或者用Program定义可执行程序。并且支持输入和环境定义不同的参数,如

env.Library(‘liba’, ‘a.c’, CPPDEFINES = ['NEWMACRO'], CCFLAGS=['/Ox'])。

不过,很可惜,Builder默认是覆盖环境中现有参数,而不是追加。所以,要用多个编译参数稍有不同的程序和库编译只能用Clone后Append。

tempEnv = libEnv.Clone()
tempEnv.Append(CPPDEFINES = ['NEWMACRO'], CCFLAGS=['/Ox'])
tempEnv.Library('liba', 'a.c')

或许我要求太高,不过这样写看起来实在太傻了。实际使用中,不同的程序用的编译参数就没有完全一样的,尤其是Include目录、宏定义、库路径、库这四个参数肯定有或多或少的不一样。不能追加的话,就只能Clone出很多个Env,甚至是一个Program或Library一个Env,这就失去了Env封装编译环境的通用意义了。

4. Sconscript与源码的位置问题

Scons设计时更多是支持将Sconscript放在源码同一目录下,甚至每个源码目录一个Soncscript。如果不习惯或者不能用这种方式,比如为别的项目写工程文件,遇到一些困扰。

Scons好在有类似make的VPATH功能,叫Repository,用着还行。

5. 源码和编译目录分开的问题

这个问题和上面类似,但更严重。Scons支持用VariantDir设定与源码目录不同的编译目录,但必须所有的文件都用编译目录的路径,如下

VariantDir('build_dir', 'src_dir')
Program('build_dir/prog', 'build_src/main.c')

这就很傻了,明明源码在src_dir目录,一定要写成build_src。不这么写也行,那就写两个Sconcript文件,一个引用另外一个

#SConscriptA
Program('prog', 'src/main.c')
 
#SConscriptB
SConscript('SconscriptA', variant_dir='build')

要用好VariantDir,就必须写多个脚本。尤其是要支持编译同一程序的多个版本,还只能用两个脚本。SconscriptB可以按如下方式写,就能编译两个版本的prog,分别在debug和release目录下。用一个Sconscript完成同样功能实在是很费劲的。

SConscript('SconscriptA', variant_dir='debug')
SConscript('SconscriptA', variant_dir='release')

6. 同一个源码编译多份的问题

这个问题和上面类似,但不同。例如编译静态库和动态库都用一批源码,但编译参数不一样,Scons就会报错。但这个问题用variant_dir解决不方便。如下例:

Program('hello1', 'hello.c', CCFLAGS=['-Od'])
Program('hello2', 'hello.c', CCFLAGS=['-Ox'])

Scons报错如下:

scons: *** Two environments with different actions were specified for the same target:

经过我多方论证,发现这个问题目前看来最佳、最简单的解决方法是给OBJ文件设置前缀。

Program('hello1', 'hello.c', OBJPREFIX='dbg-', CCFLAGS=['-Od'])
Program('hello2', 'hello.c', OBJPREFIX='opt-', CCFLAGS=['-Ox'])

7. 简化编译输出的问题

Scons支持自定义的编译输出。但有些命令不能替换,例如msvc工具集中的链接过程的输出。

暂时这么多吧…想到再补充。








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值