Sep 23

汇编语言是一种最贴近机器硬件、运行效率很高的低级编译型语言。由于它与硬件紧密相关,因此它的开发完全受平台限制,所开发的程序代码基本上不具有移植性。当然,只要硬件(主要是中央处理器)相近,那就有可能在不同平台上采用同一套汇编语言编程工具进行程序开发。MSYS2 是 GCC 工具链在 MS Windows 平台上极为成功的移植!利用 MSYS2 提供的编程工具,就可以通过 C/C++/Python 等编程语言在 MS Windows 平台上进行原生程序开发。这既可以借鉴 GNU Linux 平台上优秀开源软件的成功开发经验,还可以将这些优秀的软件移植到 MS Windows 平台。不过,这里要介绍的是如何利用 MSYS2 中基于 MinGW x64 工具链提供的 GNU as 与 ld 工具进行 64 位 GNU 汇编(也即采用 AT&T 语法)程序的开发。

先来看一个示例,它主要通过指令 call 调用了标准 C 函数(比如 printf、exit 等):

    /* test.s */
    .section .rodata
msg: .asciz "Hello, MSVC Library!"
    .section .text
    .global _start
_start:
    # align RSP
    push %rbp
    mov %rsp, %rbp
    sub $0x20, %rsp

    lea msg(%rip), %rcx
    call printf

    xor %eax, %eax
    call exit

上述汇编源代码在 MSYS2 环境下的编译以及运行过程是这样的:

$ as -o test.o test.s
$ ld -o test.exe test.o -lmsvcrt
$ ./test.exe

为了正确编写 GNU 汇编源代码,除了熟悉 AT&T 汇编语法与 Amd64 指令集之外,特别需要了解 MS Windows x64 的调用协定(FASTCALL 的一种变体)、MS Windows C/C++ 运行库,请参看 https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170、https://learn.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features?view=msvc-170。有一点要特别强调一下:微软的 C 运行时库中还存在着一些以单下划线开头并跟有标准 C 函数名的接口(比如 _printf、_exit 等),这些接口是微软实现的但并不是标准 C 函数,不同版本的 MS Windows 中的 C 运行时库(例如 msvcrt 等)可能提供了标准 C 接口,也可能提供了微软的扩展接口,或者两者兼有,或者只有其中之一。

再来看如何调用 MS Windows 的应用程序编程接口?

    /* test.s */
    .section .rodata
msg:
    .asciz "Hello, Win32 ASM World"
    len = .-msg
    .section .text
    .global _start
_start:
    # align RSP
    push %rbp
    mov %rsp, %rbp
    sub $0x20, %rsp

    # https://learn.microsoft.com/en-us/windows/console/getstdhandle
    # get handle for standard I/O
    # GetStdHandle(STD_OUTPUT_HANDLE)
    mov $-11, %rcx
    call GetStdHandle

    # https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile
    # WriteFile(hstdOut, msg, len, &bytes, 0)
    mov %rax, %rcx
    lea msg(%rip), %rdx
    mov $len, %r8
    lea -0x20(%rbp), %r9
    push $0
    call WriteFile

    # https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-exitprocess
    # exit with zero
    # ExitProcess(0)
    xor %ecx, %ecx
    call ExitProcess

上述源代码在 MSYS2 环境下的编译以及运行过程如下:

$ as -o test.o test.s
$ ld test.o -lkernel32 -o test.exe
$ ./test.exe

上面的汇编代码中用到了 Windows 应用程序编程接口中最重要的动态链接库:Kernel32.dll (包含那些用于管理内存、进程和线程的函数,例如 GetStdHandle、WriteFile、ExitProcess 等函数)。另一个值得一提的是:User32.dll(包含那些用于执行用户界面任务的函数,例如 MessageBoxA 等 函数)。如果想生成对话框程序,可试试下述汇编代码

    /* test.s */
    .section .rodata
mba_msg:
    .asciz "Hello ASM world!"
mba_title:
    .asciz "Simple Message Box"
    .section .text
    .globl _start
_start:
    # align RSP
    push %rbp
    mov %rsp, %rbp
    sub $0x20, %rsp 

    # https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxa
    # display a message box
    # MessageBoxA(NULL, mba_msg, mba_title, MB_ICONINFORMATION)
    xor %ecx, %ecx
    lea mba_msg(%rip), %rdx
    lea mba_title(%rip), %r8
    mov $0x40, %r9d
    call MessageBoxA
 
    # ExitProcess(0)
    xor %ecx, %ecx
    call ExitProcess

它的编译与执行过程如下:

$ as -o test.o test.s
$ ld test.o -lkernel32 -luser32 -o test.exe
$ ./test.exe

MS Windows 的应用程序编程接口被分为两种:一种是遵循 Win32 时代的应用程序编程接口,这部分被称为本地应用程序编程(Native API);另一种则是以 .NET Framework 为基础开发的,称为 Managed API。本地应用程序编程接口(https://learn.microsoft.com/en-us/windows/win32/api/) 以二进制方式发布、供 C/C++ 程序直接调用。这些本地应用程序开发接口的名称大多以 Nt/Zw 开头,而所有系统调用都以字母 Nt 开头。换而言之,MS Windows 的系统调用是本地应用程序编程(Native API)的子集。来看一下 Linux 与 MS Windows 的应用程序编程接口对照表(https://www.cnblogs.com/UnGeek/p/2981439.html):

如果 GNU 汇编代码中调用了 Nt/Zw 开头的 MS Windows 应用程序编程接口,那么只需在使用 GNU 链接器时增加 -lntdll 选项。

实际上,GCC 工具链为 GNU 汇编语言提供了第三代系统调用指令 syscall,若想通过它调用 MS Windows 的系统调用,那就需要提前知道相应的系统调用号。然而 MS Windows 并没有提供任何相关细节,好在已有开发者对动态库 NTDLL.dll 进行了逆向工程,得到了有关每个 Windows 内核的部分系统调用号的一些非官方信息:

  • https://github.com/ikermit/11Syscalls
  • https://j00ru.vexillium.org/syscalls/nt/64/
  • http://j00ru.vexillium.org/syscalls/win32k/64/
  • https://github.com/j00ru/windows-syscalls

请注意,即使是在 MS Windows 10 的不同版本之间,MS Windows 的同一个系统调用的调用号也会发生变化。

/* NtCreateThreadEx on Windows 10 (1511) */
mov %rcx, %r10
mov $0xB4, %rax
syscall

有关 Win32 汇编的基础知识,可参考 https://www.cnblogs.com/LyShark/p/11136319.html。还有一点值得提到的是,通过 MinGW64 制作 MS Windows 上动态库(DLL)的方法,它与 Linux 稍有不同,请参考 https://nullprogram.com/blog/2021/05/31/。

Jan 20

1、安装街机模拟器
心血来潮,来分享一下Linux中安装街机模拟器玩拳皇的经历。首先,Linux中已经有多款街机模拟器可供选择,此地以比较著名的MAME为例。在Red Hat系列的发行版中,安装它的方法如下

$ sudo dnf upgrade
$ sudo dnf install mame

在Debian系列的发行版中,它的安装方法类似:

$ sudo apt-get update
$ sudo apt-get install mame 

安装后可以输入指令

$ dpkg -s mame

查看MAME的版本信息。MAME的系统配置文件是/etc/mame/mame.ini,它用户配置文件是 $HOME/.mame/mame.ini,里面含有比较重要的配置信息,例如下面这两行

language                "Chinese (Simplified)"
rompath                 $HOME/.local/share/games/mame/roms;/usr/local/share/games/mame/roms;/usr/share/games/mame/roms

中,language用来设定MAME的界面语言,rompath指向了下载的ROM文件该放在哪里。对于普通用户来说,当然是放在$HOME/mame/roms目录了。一般来说,这个目录需要用户自己创建,可执行

$ mkdir -p $HOME/mame/roms

创建它。为了确认MAME安装已经成功,先去MAME官网https://www.mamedev.org 下个坦克游戏(https://www.mamedev.org/roms/supertnk/)来玩玩吧:

$ cd $HOME/mame/roms
$ wget https://www.mamedev.org/roms/supertnk/supertnk.zip

可以在程序菜单中启动MAME模拟器选择supertnk开始玩吧。当然,对于命令行达人,则可以在终端输入

$ mame supertnk

一切正常,便可以放松玩游戏了。顺便提一下,模拟器里按键设置的提示是TAB,如果找不到控制按钮了,别忘了试试它。

2、一点小历史
以前的游戏机用的都是卡带,里面是一块或几块集成电路芯片,游戏程序由生产厂家一次性写入这几块芯片里,以后用户玩游戏的时候只能读取里面的游戏,而不能写。所以这样的芯片叫ROM(Read Only Memory,只读存贮器)。后来有好事者将卡带里面的游戏用特殊的设备读出来,以文件的形式写在电脑的硬盘上,这些文件就叫ROM文件。然后,又有好事者编写一种程序来模拟原来的游戏机,通过读取这些ROM文件来代替插游戏卡。这样的程序便是街机模拟器。通过街机模拟器与ROM文件,人们就能在电脑上玩到以前只能在游戏机上玩的游戏了。

3、拳皇的ROM文件
为了在模拟器中玩拳皇97,可从网上下载拳皇97的ROM文件,一般它含有两个核心文件kof97.zip和neogeo.zip。注意,neogeo.zip并不是拳皇的ROM文件,但凡是由SNK(前身NEO-GEO)公司开发的街机游戏,都需要这个公共文件,必须在rompath目录放置该文件,拳皇的ROM文件当然也不能例外。ROM文件准备停当之后,在启动游戏之前,可以输入

$ mame -verifyroms kof97

查看拳皇97的ROM包状态。没有问题的话,在终端输入

$ mame kof97

开始战斗吧。

4、常见问题
网上流传的街机游戏ROM文件合集下载包(有4G之多),其中的neogeo.zip在MS Windows使用下没问题,但在Linux下则会报缺少sm1.sm1等文件的错误。可以在搜索引擎里以neogeo.zip、linux为关键词在网上搜索并下载一个新的neogeo.zip便可解决问题。这里给大家提供一个可行的下载地址https://www.mediafire.com/file/4bgudedonwi/neogeo.zip/file

附注

MAME官网也提供了MS Windows平台上的安装包,但这个安装包只是个命令行工具。好在有很多人为它开发了图形前端,例如MAMEUI(http://www.mameui.info/)、MAMEPLUS(http://sourceforge.net/projects/mameplus)等。

Oct 7

MSYS2 简介

简单的说,MSYS2 是 MSYS 的一个升级版, 提供了 GCC 工具链。与 MSYS 最大的区别是移植了 Arch Linux 的软件包管理系统 Pacman。有了 Pacman 升级系统,安装新软件包,还有解决软件包间的依赖问题就变得简单多了。从 http://msys2.github.io/ 下载安装包并安装,安装包的文件名通常为 msys2-<架构>-<日期>.exe ,根据 CPU 架构选择最新的安装包。

MSYS2 设计目标是在 MS Windows 上运行 MinGW 工具链的,它有两套 MinGW 工具链,一个是 mingw32,一个是 mingw64。它们的功能是一样的,都是用于编译 MS Windows 上的代码(能调用 MS Windows 应用程序接口,不能调用 Unix/Linux 系统调用)的,可以生成“纯净”的 win32 和 win64 可执行文件。这些可执行文件运行时只依赖 MS Windows 系统的动态库或静态库。另外,MSYS2 自身还带了一个原生 GCC 工具链,它可用于编译 UNIX/Linux 代码,但支持的函数仅限于 POSIX 部分,其他系统调用都不支持,如 Linux 的 epoll、eventfd 等等,所以功能有限。原生 GCC 工具链可编译生成 MS Windows 平台上的可执行文件,但它们的运行必须依赖 msys2xxx.dll。开发专业应用软件,不要使用 MSYS2 的原生 GCC 工具链,它只是为了编译 MinGW 工具链而存在的,是制造工具的工具。

可以看到 MSYS2 有三个执行脚本,分别是 msys2_shell.bat、mingw32_shell.bat 和 mingw64_shell.bat,查看相应的文件内容可以看到其中的区别。实际上,三个 .bat 的区别就是对环境变量 PATH 的不同设置,mingw32_shell.bat 优先使用 msys64/mingw32 下的工具,mingw64_shell.bat 优先使用 msys64/mingw64 下的工具,而 msys2_shell.bat 两个都不使用,只用自身 msys 的工具(依赖 msys2xxx.dll)。这么做的好处是当需要编译 32 位的项目时使用 mingw32_shell.bat,64 位的项目使用 mingw64_shell.bat,各套工具互不干扰。

配置软件源

需要修改安装目录下,/etc/pacman.d 文件夹中的 mirrorlist.msys、mirrorlist.mingw64、mirrorlist.mingw32 三个文件,分别在文件开始处添加

> notepad /etc/pacman.d/mirrorlist.msys:
##中国科学技术大学开源软件镜像
Server = http://mirrors.ustc.edu.cn/msys2/MSYS2/$arch
##北京理工大学镜像
Server = http://mirror.bit.edu.cn/msys2/REPOS/MSYS2/$arch
##清华大学开源镜像
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/msys/$arch

> notepad /etc/pacman.d/mirrorlist.mingw64:
##中国科学技术大学开源软件镜像
Server = http://mirrors.ustc.edu.cn/msys2/MINGW/x86_64
##北京理工大学镜像
Server = http://mirror.bit.edu.cn/msys2/REPOS/MINGW/x86_64
##清华大学开源镜像
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/x86_64

> notepad /etc/pacman.d/mirrorlist.mingw32:
##中国科学技术大学开源软件镜像
Server = http://mirrors.ustc.edu.cn/msys2/MINGW/i686
##北京理工大学镜像
Server = http://mirror.bit.edu.cn/msys2/REPOS/MINGW/i686
##清华大学开源镜像
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/i686

提高数据库访问速度

Pacman 将所有软件包的信息放在一一对应的许多小文件中。通过改善数据库访问速度,可以减少花在数据库相关任务上的时间,比如:寻找软件包、检索软件包依赖性。最安全最简单的方法是以root身份运行

$ pacman-optimize

上述命令试图将所有小文件放在磁盘上同一个物理区域,以减少磁头移动。这种方法很安全,但不一定有效。其效果取决于你的文件系统、磁盘使用率、和磁盘碎片程度。另一种更激进的方式是在优化数据库之前首先删除 cache 中所有未安装以及不使用的仓库中的包:

$ pacman -Sc && pacman-optimize

加快下载速度

如果你的包下载速度变得极慢,首先确定你用的是那些(镜像)网站而不是 MSYS2 的官方链接。其次,可以通过各种下载工具而不是 Pacman 内置的下载方式,来改善 Pacman 的下载速度。不论怎样,在做任何修改前,你必须确定拥有了最新版的Pacman:

$ pacman -Syu

对于需要更强大代理支持的用户来说,用 wget 比用 Pacman 自己的下载方式更加方便。要使用 wget,首先使用

$ pacman -S wget

安装它,然后修改 /etc/pacman.conf 并在其中的 [options] 区段将下面内容去掉注释:

$ nano -w /etc/pacman.conf
XferCommand = /usr/bin/wget -c --passive-ftp -c %u

除了将 wget 参数放在 /etc/pacman.conf 里,你也可以直接修改 wget 配置文件(全局文件是/etc/wgetrc,各个用户的文件是 $HOME/.wgetrc)。 如果对于支持断点续传的下载工具还不满足需求,那么可以使用支持多线程的断点下载工具。例如 aria2 是一个具有断点续传和分块下载功能的轻量级下载软件,支持 HTTP/HTTPS/FTP 协议。aria2 可以以多线程的方式通过 HTTP/HTTPS和FTP 协议连接镜像服务器,显著提高下载速度。先使用下述方式

$ pacman -S aria2

安装 aria2。接着修改 /etc/pacman.conf,在 [option] 段添加下列一行(如果已存在则修改之):

$ notepad /etc/pacman.conf
XferCommand = /usr/bin/aria2c --allow-overwrite=true -c --file-allocation=none --log-level=error -m2 --max-connection-per-server=2 --max-file-not-found=5 --min-split-size=5M --no-conf --remote-time=true --summary-interval=60 -t5 -d / -o %o %u

开启 pacman 的多线程下载之旅吧!

安装必要的工具链和必要的开发库

下面假设使用的是 64 位系统。如果使用的是 32 位系统,仅需后面出现的 x86_64 改为 i686。在 MS Windows 开始菜单中运行 mingw64_shell.bat 以打开命令行窗口。更新本地软件包数据库也即与软件仓库同步:

$ pacman -S --refresh

$ pacman -Sy

然后升级软件包也即更新系统:

$ pacman -S --sysupgrade

$ pacman -Su

通常,将上述命令合二为一

$ pacman -Syu

现在开始安装工具链

$ pacman -S mingw-w64-x86_64-toolchain

如果提示冲突,则使用

$ pacman -S --force mingw-w64-x86_64-toolchain

开发的各种辅助工具

$ pacman -S base-devel

安装 GTK+-3 的库

$ pacman -S --force mingw-w64-x86_64-gtk3

安装 GTK+ 快速构建工具 Glade 包

$ pacman -S --force mingw-w64-x86_64-glade3

安装 wxWidgets 的库

$ pacman -S --force mingw-w64-x86_64-wxWidgets

安装 OpenGL 库(虽然微软操作系统自带了 OpenGL 库,为了与 MSYS2 相容,选用 GLEW、FreeGLUT 库)

$ pacman -S --force mingw-w64-x86_64-glew mingw-w64-x86_64-freeglut

pacman 的常用用法

查看 pacman 工具帮助

$ pacman -h
$ pacman -S -h

列出本地 pacman 软件包数据库中的软件包清单

$ Pacman -Sl

列出所有已安装的软件包

$ pacman -Q --explicit

或者

$ pacman -Q -e

或直接

$ pacman -Q

安装新的软件包或软件组

$ pacman -S 

比如我要安装 gcc,那么执行:

$ pacman -S gcc

很多时候,我们不知道要安装软件的准确名称,这时就要先查询软件包的名称。

$ pacman -Ss 

比如我想安装 gcc 相关的软件,那么可以这样搜索

$ pacman -Ss gcc

可以列出所有的软件组

$ pacman -Q --groups

或者

$ pacman -Q -g

查看软件组

$ pacman -S -g

查看软件组包含的软件

$ pacman -Q -g 

例如

$ pacman -Q -g base-devel

查询软件包的内容

$ pacman -Q -l 

例如查询软件包 gtk+ 的内容

$ pacman -Q -l gtk+

查询软件所在的软件包或软件组

$ pacman -Q -s 

例如

$ pacman -Q -s nettle

强制安装某个包(不管是否已安装)

$ pacman -Sf nettle

更新系统但不升级某个包

$ pacman -Su --ignore mysql

删除一个软件包

$ pacman -R 

例如删掉某个包但忽略无用依赖的删除

$ pacman -R glade3

删除指定软件包,及其所有没有被其他已安装软件包使用的依赖关系:

$ pacman -Rs mysql

清理当前未被安装软件包的缓存(/var/cache/pacman/pkg):

$ pacman -Sc

清理所有下载的包与数据库

$ pacman -Scc

下载包而不安装它

$ pacman -Sw mysql
Feb 19

GTK+ 是一套跨平台的图形用户界面(Graphical User Interface)开发工具包,按 LGPL 许可协议发布的。此地介绍一下 GTK+ 开发工具包在 MS Windows 上的安装配置。

〇、预备工作

由于 GTK+ 是由 C 语言开发,因此要开发 GTK+ 应用程序,首先需要配置好 C 编译环境。在 MS Windows 平台,C 编译器的选择很多,主流的有 MSVC、GCC 的移植版本 MinGW 与 Cygwin。由于 GTK+ 是 GNU 项目,因此,这里推荐使用 GNU 的编译器,当然并不是说不能使用其他 C 编译器,这完全取决于个人喜好。MinGW 的下载与安装参考其官方网站 http://www.mingw.org 的说明。由于 MinGW 最初是 32 位,近年来,TDM-GCC 推出了 MinGW 的 64 位版本,详见其官方主页 http://tdm-gcc.tdragon.net/。至于 Cygwin,请到它的官方网站 http://www.cygwin.com/ 了解详情。在 Cygwin 与 MinGW 两者之间,一般推荐 MinGW 作为 C/C++ 编译工具链,除非需要在 MS Windows 平台上开发 Unix/Linux 应用程序。当然,在 MS Windows 平台上开发大型应用程序,少不得使用集成开发环境。集成开发环境也有很多选择,例如 MSVS、Code::Blocks、QtCreator 等。下面的介绍中使用的 Code::Blocks,请到其官网 http://codeblocks.org 下载最新版本(留意 Code::Blocks 的安装包是否已带有 MinGW)。

一、下载与配置 GTK+ 开发包

在 MS Windows 中安装 GTK+ 有多种选择,可利用 Cygwin 或 MinGW 工具自行由源码编译 GTK+ 工具包,不过要解决 GTK+ 依赖,工程相对浩大。目前,GTK+ 官方推荐利用 MSYS2(官网地址为 http://msys2.github.io)提供的包管理器 pacman 来自动完成 GTK+ 的编译与安装。这些 GTK+ 安装方法的共同特点是,安装过程都会顺带安装预备工作中提到的编译器。为了与预备工作中相衔接,这里直接利用预编译的 GTK+ 集成开发包来完成它们安装。

虽然预编译的集成开发包有些陈旧,集成开发包有些陈旧,但好处也是明显地,它包含了所有开发与运行 GTK+ 应用程序的头文件与库文件。需要 GTK+ 2 的用户,可到 http://ftp.gnome.org/pub/GNOME/binaries/ 下载 32 位的集成开发包 gtk+-bundle_2.24.10-20120208_win32.zip 或 64 位的集成开发包 gtk+-bundle_2.22.1-20101229_win64.zip;需要 GTK+ 3 的用户,可到 http://win32builder.gnome.org/ 下载 32 位的集成开发包 gtk+-bundle_3.10.4-20131202_win32.zip 或 64 位的集成开发包 gtk+-bundle_3.10.4-20131202_win64.zip。它们的安装非常简单,只需要将这些压缩包解压缩即可,一般建议将它们安装磁盘的根目录(如 C:\GTK+-2.12C:\GTK+-3.10),但不推荐安装到 MS Windows 的应用程序文件夹。为了能够使用 GTK+ 提供的 pkg-config 来配置 GCC 的编译、链接选项,还需配置环境变量 PATH。只需在 MS Windows 的文件管理器中鼠标右击 "My Computer" -> "Properties" -> "Advanced system settings" -> "Environment variables",接着双击 "System variables" 页面中的 PATH 行,将 GTK+ 目录中的 bin 路径(如 ;C:\GTK+-2.12\bin;C:\GTK+-3.10\bin)添加到原 PATH 变量的末尾。命令行操作也可完成 PATH 的设置,也即打开命令行窗口(CMD.exe),然后执行

> setx PATH "%PATH%;C:\GTK+-2.12\bin"

或者

> setx PATH "%PATH%;C:\GTK+-3.10\bin"

设置好环境变量 PATH 之后,可选择性的做下述设置

> pango-querymodules > C:\GTK+-2.12\etc\pango\pango.modules
> gdk-pixbuf-query-loaders > C:\GTK+-2.12\lib\gdk-pixbuf-2.0\2.10.0\loaders.cache
> gtk-query-immodules-2.0 > C:\GTK+-2.12\lib\gtk-2.0\2.12.11\immodules.cache

二、开发环境的测试与 Code::Blocks 的整合

在正式开始 GTK+ 开发工作之前,要确保 GTK+ 环境配置的正确性,为此还需做一些简单的测试。前述 GTK+ 集成开发包带有 GTK+ 的开发演示文档,可到相应目录查找。此地,以命令行方式演示一个由 GTK+ 2 开发简单应用的例子:

> notepad.exe hellogtk.c

#include <gtk/gtk.h>

int main(int argc, char *argv[])
{
    GtkWidget *window;
    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    g_signal_connect(G_OBJECT(window), "destroy",
    G_CALLBACK(gtk_main_quit), NULL);

    gtk_widget_show(window);
    gtk_main();
    return 0;
}

现在在 MinGW Shell 中执行

> gcc hellogtk.c -o hellogtk.exe `pkg-config --cflags --libs gtk+-2.0`

一切正常的话,会在 hellogtk.c 所在目录生成一个可执行文件 hellogtk.exe,鼠标双击运行它。

要开发一个大型的 GTK+ 程序,除了使用 Makefile 管理源代码文件之外,使用集成开发环境是方便的。下面以 Code::Blocks 作为集成开发环境演示如何进行 GTK+ 的开发工作,而这仅需搞清楚如何在 Code::Blocks 中配置 GTK+ 的库文件与头文件路径,当然有很多方法达到这个目的,这里只给出针对 GTK+ 工程相关的配置方法。为此,先按下述操作新建一个 GTK+ 工程:启动 Code::Blocks,点击 "File" -> "New" -> "GTK+ Project" 创建一个 GTK+ 2 项目。下面点击该工程对应的菜单 "Project" -> "build options" 子标签。下面从简到烦介绍三种在 GTK+ 工程属性中配置 GTK+ 开发环境的方法:

1、利用 pkg-config 直接设置编译选项与链接选项

由于 pkg-config 已在 PATH 中,故而 GTK+ 的编译选项与链接选项的设置非常简捷,只需在 "Project build options "-> "Compiler settings" -> "Other Compiler options" 中增加编译选项设置 pkg-config --cflags gtk+-2.0;再在 "Project build options" -> "Linker settings" -> "Other linker options" 增加链接选项为 pkg-config --libs gtk+-2.0

2、利用 pkg-config 的输出设置编译选项与链接选项

2.1、设置编译选项。先在命令行窗口(CMD.exe) 中执行

> pkg-config --cflags gtk+-2.0
-mms-bitfields -IC:/GTK+-2.12/include/gtk-2.0 -IC:/GTK+-2.12/lib/gtk-2.0/include -IC:/GTK+-2.12/include/atk-1.0 -IC:/GTK+-2.12/include/cairo -IC:/GTK+-2.12/include/pango-1.0 -IC:/GTK+-2.12/include/glib-2.0 -IC:/GTK+-2.12/lib/glib-2.0/include -IC:/GTK+-2.12/include/libpng12

然后开启 Code::Blocks,将 -mms-bitfields -IC:/GTK+-2.12/include/gtk-2.0 -IC:/GTK+-2.12/lib/gtk-2.0/include -IC:/GTK+-2.12/include/atk-1.0 -IC:/GTK+-2.12/include/cairo -IC:/GTK+-2.12/include/pango-1.0 -IC:/GTK+-2.12/include/glib-2.0 -IC:/GTK+-2.12/lib/glib-2.0/include -IC:/GTK+-2.12/include/libpng12 拷贝到 "Project build options" -> "Compiler settings" -> "Other Compiler options";

2.2、设置链接选项。在命令行窗口(CMD.exe) 中执行

> pkg-config --libs gtk+-2.0
-LC:/GTK+-2.12/lib -lgtk-win32-2.0 -lgdk-win32-2.0 -latk-1.0 -lgio-2.0 -lgdk_pixbuf-2.0 -lpangowin32-1.0 -lgdi32 -lpangocairo-1.0 -lpango-1.0 -lcairo -lgobject-2.0 -lgmodule-2.0 -lglib-2.0 -lintl

然后将 -LC:/GTK+-2.12/lib -lgtk-win32-2.0 -lgdk-win32-2.0 -latk-1.0 -lgio-2.0 -lgdk_pixbuf-2.0 -lpangowin32-1.0 -lgdi32 -lpangocairo-1.0 -lpango-1.0 -lcairo -lgobject-2.0 -lgmodule-2.0 -lglib-2.0 -lintl 拷贝到 "Project build options" -> "Linker settings" -> "Other linker options"。

3、将搜索路径、编译选项、链接选项分开设置

3.1、设置编译选项。在 "Project build options" -> "Compiler settings" -> "Other Compiler options" 加入编译选项 -mms-bitfields

3.2、设置链接选项与库文件。在 "Project build options" -> "Linker settings" -> "Link libraries" 中将 GTK+ 2 库的所有库文件(动态库或者静态库)加入,也即点击 ADD 按钮后弹出的文件选择对话框中将 atk-1.0cairogdi32gdk-win32-2.0gdk_pixbuf-2.0gio-2.0glib-2.0gmodule-2.0gobject-2.0gtk-win32-2.0intlpango-1.0pangowin32-1.0pangocairo-1.0 逐一加入;在 "Project build options" -> "Linker settings" -> "Other linker options" 中加入链接选项 -mwindows

3.3、设置搜索目录。在 "Project build options" -> "Search directories" -> "Compiler" 子标签中加入 GTK+ 2 的头文件路径:C:\GTK+-2.12\include\atk-1.0C:\GTK+-2.12\include\cairoC:\GTK+-2.12\include\glib-2.0C:\GTK+-2.12\include\gtk-2.0C:\GTK+-2.12\include\libpng12C:\GTK+-2.12\include\pango-1.0C:\GTK+-2.12\lib\glib-2.0\includeC:\GTK+-2.12\lib\gtk-2.0\include。一般来说就这些,如果安装了新的库再加。若上一步的 "Link libraries" 已带有绝对路径,就不必设库文件的搜索路径了;如若不然,还需在 "Project build options" -> "Search directories" -> "Linker" 子标签中加入库文件路径,也即 C:\GTK+-2.12\lib

介绍第三种比较麻烦的配置方法的目的是让那些使用 MSVC 作为编译工具的用户看清楚如何配置 GTK+ 开发环境。

三、GTK+ 程序界面的快速构建工具

Glade 用来为 GTK+ 和 GNOME 程序快速地设计图形用户界面,并自动生成图形界面的 C 源代码。历史上,Glade 有三个版本,不过目前主要的是 Glade2 与 Glade3,推荐使用 Glade3。Glade2 和 Glade3 的主要区别是: Glade2 会自动生成 makefile 等文件,而 Glade3 只是用来生成界面,然后采用 Libglade 和 GtkBuilder 格式调用 .glade 文件,这样的好处是代码和界面完全分开,避免代码的改变又需要重新编译。与 GTK+ 类似,在 MS Windows 中安装 Glade 的方法很多种,如前,此地直接利用预编译的 Glade 工具包。安装方法很简单,到 http://ftp.gnome.org/pub/GNOME/binaries/ 下载安装包 glade3_3.8.1-1_win32.zip 即可,由于 Glade 是来生成布局文件的,因此无须在意是否是 32 位还是 64 位。当然,为了避免在运行时出现依赖错误,也可安装带有完整 GTK+ 库的版本,例如 glade3-3.6.7-with-GTK+.exe。若使用 Libglade 格式的布局文件,还需调用 libglade 库,为此安装 libglade 库,也即到 http://ftp.gnome.org/pub/GNOME/binaries/下载 32 位安装包 libglade-dev_2.6.4-1_win32.zip 或 64 位安装包 libglade-dev_2.6.3-1_win64.zip 即可;并假设 Glade 的安装目录均在 C:\Glade,据此设置环境变量 PKG_CONFIG_PATH,也即在命令行窗口(CMD.exe) 中执行

> setx  PKG_CONFIG_PATH "%PKG_CONFIG_PATH%;C:\Glade\lib\pkgconfig"

接着试试下述命令行

> pkg-config --modversion libglade-2.0

看是否有版本信息输出?若找不到该命令,就需要重启电脑;若有,说明系统变量设置正确。

四、GTK+ 的 C++ 开发包的安装与配置

GTKmm 便是 GTK+ 的 C++ 开发包。与 GTK+ 类似,在 MS Windows 中安装 GTKmm 有多种选择,如前,这里直接利用预编译的 GTKmm 开发包包来完成安装,不过需要注意,它是以 GTK+ 包的安装为前提,要预先配置好 GTK+ 才行。从 http://ftp.gnome.org/pub/GNOME/binaries/ 下载 32 位的 GTKmm 开发包 gtkmm-win32-devel-2.22.0-1.exe 或者 64 位的 GTKmm 开发包 gtkmm-win64-devel-2.22.0-1.exe,然后点击它们安装即可,假设安装目录是 C:\GTKmm-2.22。在使用 GTKmm 之前,还需设置系统变量,打开命令行窗口(CMD.exe) ,执行:

> setx PKG_CONFIG_PATH "%PKG_CONFIG_PATH%;C:\GTKmm-2.22\lib\pkgconfig"

在打开命令行窗口(CMD.exe)中再试试

> pkg-config --modversion gtkmm-2.4

若一切正常,可以简单测试一下 GTKmm 是否正常工作?在 MinGW Shell 中,执行下述命令行

> notepad.exe hellogtkmm.cpp

/*Gtkmm程序的基本头文件,下面用到的两个类就由它定义*/
#include <gtkmm.h>

int main (int argc, char *argv[])
{
   /* 
   每一个Gtkmm应用程序都必须有这样一个类的实例,并且必需是第一个生成的Gtk对象。
   它对环境进行必要的初始化,需要用 argc 和 argv 两个参数对它进行实例化。
   */
   Gtk::Main app(argc, argv);
   
   /* 定义一个GTK的窗口类,默认地它以可执行程序名作为窗口标题。*/
   Gtk::Window main_win;
   
   /* 显示窗口,并进入监听事件的循环状态,当窗口关闭时返回。*/
   app.run(main_win);
   
   return 0;
}

> g++ hellogtkmm.cpp -o hellogtkmm `pkg-config --cflags --libs gtkmm-2.4`
> hellogtkmm.exe

附录

MS Visual C/C++(MSVC) 是由 Mirosoft 公司在 MS Windows 操作系统开发的 C/C++ 编译器,目前对应的集成开发环境是 MS Visual Studio(MSVS)。因此,开发 GTK+,使用 MSVS 也是一个不错的选择,这里介绍一下如何利用 MSVS(或 MSVC)进行开发工作。

先来说说在命令行环境中测试 GTK+ 开发环境的方法。通常,安装完 MSVS 之后,并不会将 MSVC 的命令行路径加入到系统环境变量 PATH 之中,但 MSVS 带有配置好环境变量 PATH 的 MSVS 命令行提示窗口。因此,要使用 MSVC 命令行,可在启动 MSVS 之后调用 MSVS 命令行提示窗口,也即启动 MSVS -> "Open the Visual Studio Command Prompt",在打开的命令行窗口中试试 pkg-config 的输出 :

> pkg-config --cflags gtk+-2.0

若一切正常的话,将这些选项保存下来,并删去其中 -mms-bitfields,并在末尾增加 -Dinline= /link /libpath:C:\GTK+-2.12\lib gtk-win32-3.0.lib gobject-2.0.lib。例如编译前述 hellogtk.c,在 MSVS 命令行提示窗口中执行

> cl hellogtk.c -IC:/GTK+-2.12/include/gtk-2.0 -IC:/GTK+-2.12/lib/gtk-2.0/include -IC:/GTK+-2.12/include/atk-1.0 -IC:/GTK+-2.12/include/cairo -IC:/GTK+-2.12/include/pango-1.0 -IC:/GTK+-2.12/include/glib-2.0 -IC:/GTK+-2.12/lib/glib-2.0/include -IC:/GTK+-2.12/include/libpng12 -Dinline= /link /libpath:C:/GTK+-2.12/lib gtk-win32-3.0.lib gobject-2.0.lib

其中 "gtk-win32-3.0.lib gobject-2.0.lib" 是编译 GTK+ 应用程序最少的链接选项要求;更多的链接选项请执行

> pkg-config --libs gtk+2.0

之后,自行添加需要的 .lib 项。

跟以往一样,开发大型程序,还是使用集成开发环境比较好。不过 MSVS 并没有提供 GTK+ 工程向导,因此只能通过在 MS Visual Studio 中新建一个空的 C++ 项目,然后在该项目的工程属性中配置 GTK+ 所需要的编译、链接参数。具体来说,

1、配置头文件搜索目录:鼠标右键新建的 C++ 工程属性->配置属性->C++目录->包含目录,把下述目录 C:\GTK+-2.12\include\atk-1.0C:\GTK+-2.12\include\cairoC:\GTK+-2.12\include\glib-2.0C:\GTK+-2.12\include\gtk-2.0C:\GTK+-2.12\include\libpng12C:\GTK+-2.12\include\pango-1.0C:\GTK+-2.12\lib\glib-2.0\includeC:\GTK+-2.12\lib\gtk-2.0\include 添加进去。

2、配置库文件搜索目录,鼠标右键新建的 C++ 工程属性->配置属性->C++目录->库目录,把 C:\GTK+-2.12\lib 目录添加进去;

3、配置附加依赖项, C++ 工程属性->配置属性->链接器->输入->附加依赖项,把 atk-1.0.libcairo.libgdi32.libgdk-win32-2.0.libgdk_pixbuf-2.0.libgio-2.0.libglib-2.0.libgmodule-2.0.libgobject-2.0.libgtk-win32-2.0.libintl.libpango-1.0.libpangowin32-1.0.libpangocairo-1.0.lib 等项添加进去,注意每项要换行。

要注意,不同的 GTK+ 版本,每个链接项的版本可能不同,请根据 pkg-config 的输出自行调整。

Feb 19

一、简介

GTK+ 是一套跨平台的图形用户界面(Graphical User Interface)开发工具包,按 LGPL 许可协议发布的。虽然最初是为 GIMP(GNU Image Manipulation Program) 写的,但早已发展为一个功能强大、设计灵活的通用图形库。特别是在 GTK+ 被 Linux 桌面项目 GNOME 选中使得它广为流传,成为 Linux 下开发图形用户界面应用程序的主流开发工具之一。当然 GTK+ 应用程序开发与运行并不要求必须在 Linux 上,事实上,GTK+ 早已经被成功地移植到了 Mac OS X 以及 MS Windows 上。在开发早期的 GIMP 版本时,Peter Mattis 和 Spencer Kimball 创建了 GTK(GIMP Toolkit) 工具包,作为 Motif 工具包的替代,后者在那个时候不是免费的。当 GTK 开发工具包获得了面向对象特性和可扩展性之后,才在名称后面追加上了符号 "+",也即 GTK+。

二、GCC 工具链

GCC(GNU Compiler Collections) 是 Linux 系统中默认的开发工具、Autotools 是 Linux 中开发大型项目管理工具。Red Hat 系中这些开发工具的安装可执行

$ sudo dnf install "Development Tools"

Debian 系可执行

$ sudo apt-get install build-essential

Gentoo 由于本身的理念就是编译所有的包,因此系统已经自带了 GCC 工具链,无须额外安装其他 GCC 开发工具。

三、C/C++ 的手册页

在编程的过程中有时会记不得某个函数的用法,此时查找手册页是比较快的。为了能够用命令行 man 查看 C/C++ 的函数,需要安装相关的手册页及其工具。在 Red Hat 系中执行

$ sudo dnf install man-pages libstdc++-docs

Debian 系可执行

$ sudo apt-get install manpages-dev manpages-posix manpages-posix-dev glibc-doc libstdc++6-4.3-doc

Gentoo 中安装稍微麻烦一点,可执行

$ sudo sh -c 'echo "sys-devel/gcc doc cxx " >> /etc/portage/package.use'
$ sudo emerge -av1 gcc
$ sudo emerge -av man-pages man-pages-posix

手册页的索引由 mandb 命令管理,有时在安装了新的手册页后,可能需要更新一下索引才能用 man kman f 查询到函数,也即

$ mandb c

然后就可以查看这些文档了。比如,查看 fopen 的手册页:

$ man fopen

四、GTK+ 库的概述

GTK+ 是基于以下库开发的:

  • glib:GTK+ 与 GNOME 的底层核心库,主要提供了 C 数据结构、可移植封装、运行时功能接口,譬如事件循环、多线程、动态装载和对象机制等。换而言之,Glib 是 GTK+ 能够“面向对象”的基础。GTK+ 中与界面无关的底层部分基本都被并入 glib。GLib 库还进一步分离成 GIO、GObject 等库。GIO 专门处理输入输出流。而 GObject 则维护着 GTK+ 所使用的一套对象系统。正是 GObject 提供的面向对象的机制,使得 GTK+ 可绑定很多种开发语言,例如 C++、Python、Perl、Java、C#、PHP 等其他高级语言。
  • pango:国际化文本陈列及渲染库,它是 GTK+ 的文本与字体处理核心;
  • atk:可访问接口库,它可以让 GTK+ 程序很方便地使用屏幕阅读器、放大镜以及一些输入设备等;
  • cairo:过去 cario 被称为 Xr 或 Xr/Xc,它是一个跨平台的开放源代码的矢量图形函数库,可以提供高质量的显示和打印输出。通过 Glitz 函数库, Cairo 能使用 OpenGL 或 X Render 扩展的硬件加速功能来绘制图像,这基于 Cairo 的应用能在现代化的 3D 显示硬件上获得益处。
  • gdk-pixbuf:GDK(GDK 是 GTK+ 从窗口系统细节中提取出来的一组接口,可以直接访问窗口细节。实际上,GDK 是 GTK+ 用户界面图形库提供的一些底层“图形实现”和“窗口实现”的方法,在 Linux 中,GDK 是对 Xlib 库提供接口的封装。)的一个部分,提供了一组位图函数,包括位图变换、位图文件读写等等,用于加载图像和维护图像“缓存”的(pixel buffer)。

要装 GTK+,先安装这写库,当然它们又依赖其它一些库,在 Linux 系统中可完全交由 Linux 的包管理器处理。还有一些库是 GTK+ 运行依赖的,下面是几个重要的:

  • gettext:它是国际化库,主要用于制作多语言程序。运行时 gettext 自动识别操作系统语言,然后从已有语言包中选择一个最合适用户的语言。当操作系统上没有 gettext() 函数的时候需要安装它。
  • libiconv:它是字符集转换库,通常操作系统上没有提供 iconv() 函数的时候才会安装它。GTK+ 内部使用 UTF-8 字符集,有时需要字符集转换。
  • freetype:它是一个操作字体的函数库,不但可以处理点阵字体,也可以处理多种矢量字体,包括 truetype 字体,为上层应用程序提供了一个统一的调用接口。
  • fontconfig:它提供系统范围内字体设置、定制和允许应用程序访问的函数库。实际上,GTK+ 的字体绘制是通过 pango、freetype 与 fontconfig 三者协作来完成的。其中,fontconfig 负责字体的管理和配置,freetype 负责单个字符的绘制,pango 则完成对文字的排版布局。
  • libjpeg:它提供了 JPEG 算法压缩文件图形文件供 GTK+ 程序读写 JPEG 格式的图形文件;
  • libtiff:它是用操作 TIFF 格式(标记图象文件格式)的图形文件并为 GTK+ 程序读写 TIFF 格式的图形文件的库;
  • libpng:它是用来创建与操作 PNG 格式的图形文件并为 GTK+ 程序提供读写 PNG 文件的库。PNG 格式的图形文件是被设计来替代 GIF 格式的,它对于更小范围的 TIFF 格式是来说,有了很多的进步和拓展并且减少了关于专利权的麻烦。

五、GTK+ 的 C 开发包

目前,GTK+ 开发包有三个版本:GTK+ 1、GTK+ 2 与 GTK+ 3,在 Linux 系统上是可以共存的,但由它们开发的程序需要对应的 GTK+ 开发包才能正常编译与运行。现在 GTK+ 2 是主流,但都在向 GTK+ 3 过度。根据自己的需要选择 GTK+ 版本,这里就以 GTK+ 2 为例。安装 GTK+ 开发环境,在 Red Hat 系中执行

$ sudo dnf install gtk2-devel gtk2-devel-docs

Debian 系可执行

$ sudo apt-get install libgtk2.0-dev libgtk2.0-doc

Gentoo 中执行

$ sudo sh -c 'echo "x11-libs/gdk-pixbuf doc \
                    dev-libs/glib doc \
                    x11-libs/pango doc \
                    x11-libs/cairo doc \
                    dev-libs/atk doc \
                    x11-libs/gtk+ doc" >> /etc/portage/package.use'
$ sudo emerge -av gtk+

若在开发过程中需整合 GNOME 环境,在 Red Hat 系中执行

$ sudo dnf install libgnome-devel gnome-devel-docs

Debian 系可执行

$ sudo apt-get install gnome-devel gnome-devel-docs

Gentoo 中执行

$ sudo emerge -av gnome-devel-docs

六、GTK+ 应用程序接口文档查看器

在 GNOME 桌面中,查看 GTK+ 应用程序接口文档的查看器是 DevHelp。安装它的方法很简单,在 Red Hat 系中执行

$ sudo dnf install devhelp

Debian 系可执行

$ sudo apt-get install devhelp

Gentoo 中执行

$ sudo emerge -av devhelp

通常,这些 API 文档会随着开发工具包的安装会被放入 /usr/share/gtk-doc/ 目录下。有了 DevHelp,查看它们非常容易,只需要在应用程序列表中点击 Devhelp 图标,或者在终端模拟器中执行

$ devhelp

八、pkg-config 的安装与使用

pkg-config 是编译器的辅助工具,可以帮助 GCC 找到所需要的头文件与库文件路径。它的安装非常简单。在 Red Hat 系中执行

$ sudo dnf install pkg-config

Debian 系可执行

$ sudo apt-get install pkg-config

Gentoo 中执行

$ sudo emerge -av dev-util/pkgconfig

现在试试 pkg-config,先查看 Linux 系统中是否安装 glib 库:

$ pkg-config --list-all | grep -i glib

若已安装,例如 glib-2.0,接着查看一下所安装的 glib 的具体版本:

$ pkg-config --modversion glib-2.0

再来列出 GCC 所需要的 glib-2.0 头文件所在目录

$ pkg-config --cflags glib-2.0

接着列出 GCC 所需要的 glib-2.0 库文件所在目录

$ pkg-config --libs glib-2.0

当然 pkg-config 无法智能到无所不知的地步,它是通过存放在标准目录 /usr/lib/pkgconfig 下的 .pc 文件来查找 GCC 所需头文件与库文件路径的。当然,也可以通过设置环境变量 PKG_CONFIG_PATH 让 pkg-config 找到非标准目录下的 .pc 文件,在这些文件里同样记录了 GCC 所需要的头文件和库文件所在的路径。例如

$ export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib

另外,值得提到一下 GTK+ 1 自带的工具 gtk-config,它与 pkg-config 的运作机制相像,但显然已被 pkg-config 取代。

九、GTK+ 的基本概念与约定

首先来解释 GTK+ 中几个基本的概念,以方便将来的分析。

  • 物件(GtkWidget):GTK+ 中每一个窗口里的组成要素都被视为一个物件,如按钮、文本等等,窗口本身也是一个物件。总之 GTK+ 的界面就是由物件构成的。注意,物件都使用指针来管理,物件外在表现就是一个特定类型的指针。
  • 容器(GtkContainer):物件里的一大类,容器的特点是其内部能够容纳其他物件。容器最基本的功能之一是将各种物件良好地组织起来。 GTK+ 的容器能在大小改变时自动调整内含物件的大小,这使得 GTK+ 能够很智能地相应窗口或其他物件的大小改变。这为我们提供了很大的方便,往往我们不需要指定某个物件的大小,只需说明他所在的容器位置, GTK+ 会把物件的实际位置和大小自动计算出来。
  • 继承、组合:虽然是 C 语言写的,但 GTK+ 灵活地运用了面向对象思想。 GTK+ 的物件体系中就有继承、组合这样的关系,如窗口(GtkWindow)是由容器(GtkContainer)派生出来的。
  • 类型转换宏:C 语言本身没有“继承”这个概念,那么,如果把派生的物件直接当做基物件使用,会出现一个编译警告,即“隐式指针类型转换”,但不会出错。为了消除这个警告,需要做指针类型转换。一般情况下类型转换使用类型转换宏。类型转换宏内部会检查物件的继承关系,确定能否进行转换,然后再做显式类型转换。
  • 事件(event):用户的操作,比如按下某个按钮或快捷键,被视为一个事件。
  • 信号(signal):GTK+ 是基于信号回调(signal-slot)机制的。信号捆绑了一个事件和一个函数,在用户触发这个事件时,这个函数会被调用一次。从这个角度来说, GTK+ 是基于物件的,即程序围绕物件属性、事件、方法进行。
  • 主循环(main loop):GTK+ 程序在一个主循环中运行。当一个事件被触发时,它将被插入队列中;在主循环中被触发的事件会被逐个处理(和这个事件绑定的函数被逐个调用);没有事件被触发时,程序就处于等待状态,等待下一个事件被用户触发。直到退出主循环的函数被调用,GTK+ 程序才结束。

GTK+ 拥有开源软件的很多特点,比如结构高度严谨、可读性甚好。现在介绍一下 GTK+ 的关键字命名方式也即 GTK+ 命名规范,以便阅读一段 GTK+ 程序。

  • 普通变量类型名:全小写写法。其中以 "g" 开头属于 GLib 库,如 "gint";
  • 物件类型名:驼峰写法(首字母大写),以 "Gtk" 开头,形如 "GtkWindow"。在 GTK+ 内部,类型是像下面这样定义的(以 GtkWindow 为例)。
    typedef _GtkWindow GtkWindow
  • 函数名:小写夹下划线写法,以 "gtk_"、"g_" 为前缀,形如 "gtk_main()"、"g_print()"。如果是针对某类物件的函数,则前缀中还有物件类型名,形如 "gtk_window_new()"。
  • 常量名:大写夹下划线写法,以 "GTK_" 为前缀,形如 "GTK_WINDOW_TOPLEVEL"。
  • 类型转换宏:大写夹下划线写法,以 "GTK_" 为前缀。一般来说,宏名字和类型名相仿,比如要把 GtkWindow* 类型的物件转换为 GtkContainer* 类型,就使用宏 "GTK_CONTAINER()"。

GTK+ 本身只负责界面组织,它提供的函数大致可分为三类,物件(Widget)、对象(Object)和其它工具函数。物件就不多说了,GTK+ 中的对象是一些功能更加复杂的不可见元素,它们和界面息息相关,比如 GtkBuilder。工具函数提供一些与界面关系密切的实用功能,比如剪贴板读写。

十、GTK+ 开发环境的简单测试

GTK+ 安装完成后,做个测试程序,代码如下

$ cat hellogtk.c

#include <gtk/gtk.h>

void hello(GtkWidget *widget,gpointer data)
{
    g_print("Hello GTK+!\n");
}

gint delete_event(GtkWidget *widget,GdkEvent *event,gpointer data)
{
    g_print ("delete event occurred\n");
    return(TRUE);
}

void destroy(GtkWidget *widget,gpointer data)
{
    gtk_main_quit();
}

int main( int argc, char *argv[] )
{
    GtkWidget *window;
    GtkWidget *button;
    gtk_init (&argc, &argv);
    window=gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_signal_connect (GTK_OBJECT(window),"delete_event",GTK_SIGNAL_FUNC(delete_event),NULL);
    gtk_signal_connect (GTK_OBJECT (window), "destroy",GTK_SIGNAL_FUNC (destroy), NULL);
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    button = gtk_button_new_with_label ("Hello Ubuntu!");
    gtk_signal_connect (GTK_OBJECT (button), "clicked",GTK_SIGNAL_FUNC (hello), NULL);
    gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
                                                 GTK_SIGNAL_FUNC(gtk_widget_destroy),GTK_OBJECT (window));
    gtk_container_add (GTK_CONTAINER (window), button);
    gtk_widget_show (button);
    gtk_widget_show (window);   /*显示一个窗口*/
    gtk_main();   /*进入主循环*/
    return(0);
}

用下面命令编译运行

$ gcc hellogtk.c -o hellogtk `pkgconfig --cflags --libs gtk+2.0`
$ ./hellogtk

会显示带有一个按钮的窗口,点击按钮以后窗口关闭,命令行显示 Hello GTK+!

十一、安装 GTK+ 界面快速设计工具

Glade 是 GTK+ 的界面辅助设计工具,可以通过拖放控件的方式快速设计出用户界面,这样的优势在于在设计的同时能直观地看到界面上的控件,并且可以随时调整界面上的设计。用 Glade 设计的图形用户界面是以 XML 格式的文件保存,它描述了控件的结构、每个控件的属性。用户可以动态加载这个界面文件。而且,界面和程序逻辑是完全分离,用户修改了界面,也不需要重新编译程序。Glade 有三个版本:基于 GTK+ 1 版本的 Glade、基于 GTK+ 2 及之后版本的 Glade2、Glade3。一般推荐使用 Glade3 设计 GTK+ 程序界面。

Glade 的布局文件有 2 种格式: Libglade、GtkBuilder。由于布局文件格式的不一样,最终使用的库函数不一样。选择 GtkBuilder 直接生成 XML 格式文件,但是后缀名仍是 .glade。如果选择 Libglade,可通过下述命令行

$ gtk-builder-convert

将它装换为 XML 格式文件。总的来说,推荐使用 GtkBuilder 格式的布局文件。

Glade 的安装很容易,不要要记住,若要使用 Libglade 格式的布局文件,需要安装 libglade 库。在 Red Hat 系中执行

$ sudo dnf install glade3 libglade2-devel

Debian 系可执行

$ sudo apt-get install glade libglade2-dev

Gentoo 中执行

$ sudo sh -c 'echo "dev-util/glade doc" >> /etc/portage/package.use'
$ sudo emerge -av dev-util/glade libglade

安装完成后,在应用程序列表中点击 Devhelp 图标,或者在终端模拟器中执行

$ glade

即可启动 Glade 工具。

1)Glade 画 UI,注意保存为 Libglade 格式,然后在下述 C 代码中使用该布局文件

$ cat libgladedemo.c

#include <glade/glade.h>
#include <gtk/gtk.h>

/**
 * 假设布局文件定义了一个名为 button1 的按钮,并且其 clicked 信号处理函数如下
 * 若是在 MS Windows 中,回调函数要加上修饰词 G_MODULE_EXPORT,也即
 */
/* G_MODULE_EXPORT */
void on_button1_clicked(GtkWidget* widget,gpointer data)
{
    g_print("hello, world!\n\r");
}

int main(int argc,char **argv)
{
    /* Libglade 类型,用于布局 */
    GladeXML *gxml;
    GtkWidget *window;
    gtk_init(&argc,&argv);

    /* 下面开始通过文件获取布局信息了 */
    gxml=glade_xml_new("glade.glade",NULL,NULL);
    /* 信号连接 */
    glade_xml_signal_autoconnect(gxml);

    /* 获取构件 */
    window=glade_xml_get_widget(gxml,"window1");  /* window1 是 glade3 中窗口的名字*/
    button = glade_xml_get_widget(gxml,"button1");  /* button1 是 glade3 中按钮的名字*/

    gtk_widget_show(window);
    gtk_main();
    return 0;
}

在编译代码时,加上 libglade-2.0,如:

$ gcc libgladedemo.c -o libgladedemo `pkg-config --cflags --libs gtk+-2.0 libglade-2.0`
$ ./libgladedemo

2)Glade 画 UI,注意保存为 GtkBuilder 格式,同样在 C 代码中使用该布局文件

$ cat gtkbuilderdemo.c

#include <gtk/gtk.h>

/**
 * 假设布局文件定义了一个名为 button1 的按钮,并且其 clicked 信号处理函数如下
 * 若是在 MS Windows 中,回调函数要加上修饰词 G_MODULE_EXPORT,也即
 */
/* G_MODULE_EXPORT */
void on_button1_clicked(GtkWidget* widget,gpointer data)
{
    g_print("Hello World !\r\n");
}

int main (int argc, char **argv)
{
    /* GtkBuilder 类型,用于布局 */
    GtkBuilder *gtkBuilder;
    GtkWidget *mainwin;
    /* Initialize the widget set */
    gtk_init (&argc, &argv);
    /* Create the main window */
    /* 通过 main.glade 建立布局 */
    gtkBuilder= gtk_builder_new();
    gtk_builder_add_from_file(gtkBuilder,"main.glade",NULL);

    /* 连接信号,信号名在布局文件中定义 */
    gtk_builder_connect_signals (gtkBuilder, NULL);

    /* 通过布局文件获得构件,此处为一个对话框型的窗体 */
    mainwin= GTK_WIDGET(gtk_builder_get_object(gtkBuilder,"dialog1"));
    button = GTK_WIDGET(gtk_builder_get_object (builder, "button1"));
    g_object_unref ( G_OBJECT(gtkBuilder) );
    /* Show the application window */
    gtk_widget_show_all ( mainwin );
    /* Enter the main event loop, and wait for user interaction */
    gtk_main ();
    /* The user lost interest */

    return 0;
}

在编译代码时,无须增加额外库,也即:

$ gcc gtkbuilderdemo.c -o gtkbuilderdemo `pkg-config --cflags --libs gtk+-2.0`
$ ./gtkbuilderdemo

十二、GTK+ 的其他语言开发包

将 GTK+ 与 C 语言的近亲 C++ 绑定,便是 GTKmm。Glade 还是 GTKmm 的界面快速构建工具,推荐使用 Glade3。若使用 libglade 格式的界面布局文件,需额外安装相应的库。在 Red Hat 系中执行

$ sudo dnf install gtkmm24-devel gtkmm24-docs libglademm24-devel

Debian 系可执行

$ sudo apt-get install libgtkmm-2.4-dev libgtkmm-2.4-doc libglademm-2.4-dev libglademm-2.4-doc

Gentoo 中执行

$ sudo sh -c 'echo "dev-cpp/libsigc++ \
                    dev-cpp/glibmm doc \
                    dev-cpp/pangomm doc \
                    dev-cpp/cairomm doc \
                    dev-cpp/atkmm doc \
                    dev-cpp/gtkmm doc \
                    dev-cpp/libglademm doc" >> /etc/portage/package.use'
$ sudo emerge -av gtkmm libglademm

测试一下 GTKmm 开发环境,下面是一个简单 Hello GTKmm 的例子

$ cat hellogtkmm.cpp

#include <gtkmm.h>
#include <libglademm/xml.h>         /* 访问Glade文件所需的头文件 */
#include <iostream>                  /* 输出错误信息到控制台 */
#include <assert.h>                  /* assert()断言 */
 
using namespace Gnome::Glade;
 
class HelloApp: public Gtk::Main {    /* 继承“Gtk::Main”类 */
public:
    HelloApp(int argc, char *argv[]):
        Gtk::Main(argc,argv),             /* 必须在初始化列表中调用父类的构造函数 */
        main_window(0)                     /* 为主窗口的指针赋初值 */
    {
        try
        {
            /* 从同目录下的GLADE文件创建“Gnome::Glade::Xml”对象 */
            ref_xml = Xml::create("helloapp.glade");
        }
        catch(const XmlError& ex)
        {
            /*   出错时错误信息输出到控制台,并返回 */
            std::cerr << ex.what() << std::endl;
            return;
        }
   
        /*
          取得主窗口的指针,存入main_window变量中,并确保成功。
          main_window为0值表示失败,多是因为指定的控件名称不正确或不存在。
          第一个参数就是Glade文件中定义的控件名称,是字符串型。
        */
        ref_xml->get_widget("main_window", main_window);
        assert(main_window);
    }
 
    ~HelloApp()
    {
        /* 由于采用了智能指针,我们不需要管理资源的释放 */
    }
 
    show_window()
    {
        /*
          如果取得主窗口的指针成功,就调用父类的run()函数显示它,
          并进入监听事件的循环状态,当主窗口关闭时返回。
        */
        if (main_window) {
            run( *main_window );
        }
    }
   
protected:
    /* 通过它访问Glade文件的内容,是一种智能指针,能自动释放占用的资源 */
    Glib::RefPtr<Gnome::Glade::Xml> ref_xml;
   
    Gtk::Window* main_window;         /* 存储主窗口的指针 */
};
 
int main (int argc, char *argv[])
{
    /*
      可以看到,这跟“最简单的Gtkmm程序”非常相似。HelloApp继承自
      “Gtk::Main”类,也需要通过 argc 和 argv 两个参数进行实例化。
    */
    HelloApp app(argc, argv);
   
    /*
      在这个函数中调用Gtk::Main::run函数来实现与上面相同的功能。   
      这里不需要窗口对象作参数,因为它已封装在HelloApp类中了。
    */
    app.show_window();
   
    return 0;
}

$ g++ hellogtkmm.cpp -o hellogtkmm `pkg-config --cxxflags --libs gtkmm-2.0`

$ ./hellogtkmm

将 GTK+ 与 Python 语言绑定,便是 PyGTK。Glade 仍是 PyGTK 的界面快速构建工具,若使用 libglade 格式的界面布局文件,需额外安装相应的库。在 Red Hat 系中执行

$ sudo dnf install pygtk2-devel pygtk2-docs pygtk2-libglade

Debian 系可执行

$ sudo apt-get install python-gtk2-dev python-gtk2-doc python-glade2

Gentoo 中执行

$ sudo sh -c 'echo "dev-python/pygtk doc \
                    dev-python/pygobject doc \
                    dev-python/pycairo doc" >> /etc/portage/package.use'
$ sudo emerge -av pygtk pygtk2-libglade

还需测试一下 PyGTK 开发环境,下面是一个简单 Hello PyGTK 的例子

$ cat hellopygtk.py

from gtk import * 
 
window = GtkWindow(WINDOW_TOPLEVEL) # 创建一个顶层窗口 
window.set_title("Hello, world!") 
window.connect("destroy", mainquit) # 将注销事件与mainquit处理连接 
 
window.show() # 显示主窗口 
mainloop() # 进入事件循环 

$ python hellopygtk.py

附录

作为跨平台的图形用户界面开发工具,GTK+ 程序在非 Unix/Linux 平台上并非原生样式。若有这种需要,wxWidgets 便是一个更好的选择,它是由 C++ 开发的跨平台的图形用户界面开发包。另外,基于 wxWidgets 工具包,还开发了 C/C++ 集成开发环境 Code::Blocks,它是跨平台的集成开发环境。该集成开发环境还为 wxWidgets 图形界面开发提供了快速开发插件 wxSmith。在 Linux 平台上,为了让图形用户界面显示 GTK+ 样式,wxWidgets 封装了 GTK+ 包,因此 wxWidgets 在 Linux 平台上也被命名为 wxGTK。好了,要在 Linux 平台上开发 wxWidgets 程序,安装所有的工具包了。在 Red Hat 系中执行

$ sudo dnf install wxGTK-devel wxGTK-docs codeblocks codeblocks-contrib

Debian 系可执行

$ sudo apt-get install libwxgtk3.0-dev wx3.0-doc wx3.0-headers wx3.0-i18n wx3.0-examples codeblocks codeblocks-contrib

Gentoo 中执行

$ sudo sh -c 'echo "dev-util/codeblocks doc contrib " >> /etc/portage/package.use'
$ sudo emerge -av wxGTK codeblocks

在 Gentoo 上使用 wxWidgets,还需要用 eselect 的选择合适的版本

$ sudo eselect list wxWidgets
$ sudo eselect wxWidgets set 1

在 Linux 使用 wxWidgets 的方法非常简单,它提供了 wx-config 工具帮助 GCC 查找头文件与库文件。例如

$ wx-config --cppflags
$ wx-config --libs

再来看一个编译 wxWidgets 程序的实例

$ g++ hellowx.cpp -o hellowx `wx-config --cppflags --libs`
$ ./hellowx

与 GTK+ 类似,wxWidgets 也有其他语言的绑定,这里介绍一下 wxWidgets 与 Python 的绑定,也即 wxPython 开发包,对应的界面快速设计工具是 wxGlade。它们的安装非常简单,Red Hat 系中执行

$ sudo dnf install wxpython wxpython-devel wxpython-docs wxGlade

Debian 系可执行

$ sudo apt-get install python-wxgtk3.0 python-wxtools python-wxversion python-wxglade

Gentoo 中执行

$ sudo emerge -av wxPython wxglade

测试一下 wxPython 开发环境,下面是一个简单 Hello wxPython 的例子

$ cat hellowxpy.py

import sys, os
from wxPython.wx import *
 
class main_window(wxFrame):
      def __init__(self, parent, id, title):
      	  wxFrame.__init__(self, parent, -1, title, size = (200, 100),style=wxDEFAULT_FRAME_STYLE|wxNO_FULL_REPAINT_ON_RESIZE)
      	  self.control = wxTextCtrl(self, -1, style=wxTE_MULTILINE)
      	  self.Show(true)
 
class App(wxApp):
      def OnInit(self):
      	  frame = main_window(None, -1, "wxPython: (A Demonstration)")
      	  self.SetTopWindow(frame)
      	  return true
 
app = App()
app.MainLoop()

$ python hellowxpy.py
Nov 9

在开源领域 Linux 风声水起,缘于一众富有想象力的开源工具。在 Linux 服务器的自动化管理方面,也有众多选择,例如 Puppet、Chef、SaltStack、Ansible。前三个管理工具采用的是服务端/客户端模式,换而言之,既要配置客户端又要配置服务端才能让它们工作。Ansible 与它们不同,它充分利用 Linux 服务器的现有设施。使用 Ansible 无需安装服务端和客户端,只要配置好 OpenSSH 服务即可。Ansible 的配置文件采用的是易读格式,例如,Ansible 的主机文件使用 INI 格式,支持分组,能够指定模式;而 Playbook 则是 YAML 格式。这比 Puppet 等工具使用独有配置文件格式要容易读写。当前使用 Ansible 较为知名的用户包括 Fedora、Rackspace、Evernote 等等。

1、安装 Ansible

Ansible 能够安装到 Linux、BSD、Mac OS X 等平台,Python 版本最低要求为 2.6。大多数 Linux 发行版可以通过包管理器安装 Ansible。例如,对于 Red Hat 系来说,需要配置 EPEL 源,然后执行

$ sudo yum install ansible

Debian 系只需

$ sudo apt-get install ansible

Gentoo 可执行

$ sudo emerge -avt ansible

如果所用系统的软件包仓库中找不到 Ansible 或者系统本身没有软件仓库,那么也可以通过 pip 来安装 Ansible

$ pip install --user ansible

上述命令行执行同时也会安装 paramiko、PyYAML、jinja2 等 Python 依赖库。 现在来看看 Ansible 是否能正常工作:

$ ansible 127.0.0.1 -m ping -u james -k

请注意此地连接的是本地主机,为保证 ansible 正常工作,需在本地配置 OpenSSH 服务并以允许密码认证方式登录,同时还需创建 james 账户。

2、Ansible 的主机文件

Ansible 的主机文件用来定义你要管理的主机,系统级主机文件的默认位置在 /etc/ansible/hosts。当然也可以设置 Ansible 的用户级主机文件,它可覆盖系统级主机文件,具体来说就是在用户家目录创建 Ansible 配置文件并在其中指定用户级主机文件的路径,例如

$ nano -w ~/.ansible.cfg
[defaults]
# some basic default values...
# ansible 1.8 or ago
#hostfile     = $HOME/.ansible/hosts
# ansible 1.9 or later
inventory      = $HOME/.ansible/hosts
$ mkdir -p ~/.ansible
$ touch ~/.ansible/hosts

最后,还可通过 ansible 命令行选项 -i 指定主机文件的路径,它能覆盖前面两者的配置。主机文件是一些被管理的机器的 IP 或域名组成的。这些主机可以分组存放,组名可以使用 [] 指定,未分组的机器需保留在主机文件的顶部,主机文件中可以指定远程主机的 SSH 端口。例如:

202.121.111.8
[vps]
202.121.111.10
[web]
202.121.111.18
172.16.10.124:5555
[db]
202.121.111.19

同时,分组也能嵌套:

[vps:child]
web
db

此外,也可以通过数字和字母模式来指定一系列连续主机,如:

202.121.111.[1:3] # 相当于 202.121.111.1、202.121.111.2、202.121.111.3
[a:c].exmaple.org # 相当于 a.example.org b.example.org、c.example.org

3、Ansible 的 Ad-Hoc 模式

Ansible 的强大很大程度上体现在它 Playbook 上,后者基本上就是一些写好的 "Ansible 脚本"。不过在正式介绍它之前,先从简单的 ansible 命令行开始,这就是 ansible 的 Ad-Hoc 模式。Ad-Hoc 模式与在 Bash Shell 中执行单行命令差不多,用来快速完成简单任务。先来看看 ansible 的命令行选项:

  • -i,--inventory-file=:指定主机文件,不指定的话默认依顺序使用用户级、系统级的主机文件
  • all:针对主机文件中定义的所有主机执行,也可以指定组名或模式
  • -m,--module-name=:指定所用的模块,不指定的话默认使用 command 模块
  • -a:指定所用模块的参数,不指定的话所用模块不使用参数
  • -u,--user=:指定远端机器的用户,若不指定,默认是执行 ansible 的本地用户账户名
  • -c,--connection=:指定远端主机用户登录时的认证模块,通常有 smart、paramiko、sshpass,默认是 smart
  • -f,--forks=:参数让 ansible 在主机上并行运行指令
  • -k,--ask-pass:询问 SSH 密码;若使用 SSH 密匙,请不要使用 -k

通常,执行 Ansible 需要 SSH 密码认证,提示 SSH 密码:

$ ansible all -m ping -u root --ask-pass

在一些机器上使用 --ask-pass 会需要指定连接方式,例如:

$ ansible all -m ping -u root --ask-pass -c paramiko

当然也可以使用模块 sshpass,然而 sshpass 并不总是在标准的仓库中提供,因此 paramiko 可能更为简单。需要注意,操作很多主机时需逐个输入账户名与密码,这些都是相当麻烦的。为简单起见,可做下述配置

$ sudo nano -w /etc/ansible/hosts
#定义被控主机
[webservers]
172.16.10.123 ansible_ssh_user=root ansible_ssh_pass=centos
172.16.10.125 ansible_ssh_port=5555 ansible_ssh_user=root ansible_ssh_pass=centos
web1 ansible_ssh_host=172.16.10.126 ansible_ssh_port=5555 ansible_ssh_user=root ansible_ssh_pass=centos
[dbservers]
172.16.10.125 ansible_ssh_user=root ansible_ssh_pass=centos

下面再看看远端主机的 uptime:

$ ansible webservers -a 'uptime'

此处省略了 -i,ansible 使用默认的主机文件;webservers 代表默认主机文件中的某个主机组;也省略了 -m,ansible 默认使用 command 模块;-a 指定模块的参数,即执行 uptime 命令。如果被管理端主机的 Python 为 2.4,那么需要 python-simplejson 包,可以通过以下命令在所有 CentOS 主机上安装它:

$ ansible webservers -m raw -a 'yum -y install python-simplejson'

此处使用 Ansible 的 raw 模块,它的作用相当于直接在 OpenSSH 中执行 -a 后面的命令。再如利用 Ansible 可执行系统更新

$ ansible webservers -m raw -a 'yum update -y'

或者安装某些服务

$ ansible webservers -m raw -a 'yum -y install httpd'
$ ansible webservers -m raw -a "service httpd start"
$ ansible webservers -m raw -a "chkconfig httpd on"

实际上,将 OpenSSH 密码放入主机文件是非常不安全的,强烈推荐为 Ansible 设置 SSH 公钥认证。除了密码安全问题之外,对于非超级用户登录远程主机并执行超级用户命令,需要开启 su 或 sudo 权限。例如,vps 组中的账户 james 配置了 sudo 权限,可用该账户升级系统,例如

$ ansible vps -m raw -a "yum upgrade -y" -u james -k -b --become-user=root --become-method=sudo

或者

$ ansible vps -m raw -a "sudo yum upgrade -y" -u james -k

4、Ansible 的帮助文档

Ansible 的强大功能均是由它众多模块提供的,诸如为 Linux 创建用户及组、安装软件包、分发配置文件、管理服务等等。要想了解 Ansible 模块的全部功能,必要的文档是不可少的。在命令行下,可通过 ansible-doc 查询模块文档,如:

$ ansible-doc raw

列出模块 raw 中相关指令

$ ansible-doc -s raw

要查看所有安装模块

$ ansible-doc -l

若 Ansible 提供了模块功能,尽量使用模块,例如 Red Hat 系有 yum 模块,因此前述 CentOS 上的操作可替换为

$ ansible webservers -m yum -a 'name=* state=latest'
$ ansible webservers -m yum -a 'name=httpd state=latest'
$ ansible webservers -m service -a 'name=httpd state=started'
$ ansible webservers -m service -a 'name=httpd enabled=yes'

5、设置 SSH 公钥认证

现在让我们创建和配置 SSH 公钥认证,以便省去 -c 和 --ask-pass 选项。首先,生成密匙对:

$ ssh-keygen -t rsa

上述命令会在当前用户的 ~/.ssh 中生成密匙对,假设当前用户为 james。显然有很多种方式把 SSH 公匙放到远程主机上,既然要介绍 ansible 的用法,就用它来完成这个操作吧:

$ ansible all -m copy -a "src=/home/james/.ssh/id_rsa.pub dest=/tmp/id_rsa.pub" --ask-pass -c paramiko

下一步,把公钥文件添加到远程服务器里。输入:

$ ansible all -m shell -a "cat /tmp/id_rsa.pub >> /root/.ssh/authorized_keys" --ask-pass -c paramiko

由于把密匙放入超级用户目录,但又没有指定 -u 选项,因此默认登录远程主机的账户是执行 ansible 的本地账户,通常会因权限不够执行会失败。不要急,需要用 root 来执行这个命令,加上 -u 参数即可:

$ ansible all -m shell -a "cat /tmp/id_rsa.pub >> /root/.ssh/authorized_keys" --ask-pass -c paramiko -u root

刚才演示了通过 ansible 来传输文件的操作。事实上 ansible 有一个更加方便的内置 SSH 密钥管理支持:

$ ansible all -m authorized_key -a "user=james key='{{ lookup('file', '/home/james/.ssh/id_rsa.pub') }}' path=/home/james/.ssh/authorized_keys manage_dir=no" --ask-pass -c paramiko

至此,公钥已经设置好了。试着随便跑一个命令,比如 hostname,希望不会被提示要输入密码

$ ansible all -m shell -a "hostname" -u root

现在可以用 root 来执行命令,并且不会被输入密码的提示干扰了。最后,把 /tmp 中的公钥文件删除:

$ ansible all -m file -a "dest=/tmp/id_rsa.pub state=absent" -u root

下面来做一些更复杂的事情,而且无须输入密码。例如:

$ ansible all -m yum -a "name=httpd state=latest" -u root

利用 Ansible 的 yum 模块安装好了最新版的 Apache 服务器。实际上,上一条命令可以用下述 Playbook 替代:

---
- name: test.yaml                  # Playbook 的名称
  hosts: webservers                # Playbook 所作用的主机列表
  remote_user: root                # 远程主机的用户名
  tasks:                           # 任务列表
  - yum: name=httpd state=latest   # 使用 yum 模块执行任务一

现在有一个简单的 Playbook 了,可以这样运行它:

$ ansible-playbook test.yaml -f 10

既然 Playbook 的操作如此简单,不妨把导入 SSH 公钥的操作从 ansible 命令行转移到 Playbook 中,这将在设置新主机的时候提供很大的方便,甚至让新主机直接可以运行一个 Playbook。为了演示,把之前的公钥例子放进一个 Playbook 里:

---
- hosts: webservers               # Playbook 所作用的主机列表
  remote_user: james              # 执行任务的远程主机用户名
  sudo: yes                       # 启用 sudo 权限执行任务
  tasks:
  - authorized_key:
        user=root
        key="{{ lookup('file', '/home/james/.ssh/id_rsa.pub') }}"
        path=/root/.ssh/authorized_keys
        manage_dir=no

在准备开始写更多更复杂的 Playbook 之前,另一个值得考虑的事情是,引入版本管理器可以有效节省时间。虽然需要管理的 Linux 的服务器需求会不断变化,然而并不需要在每次机器发生变化时都重新写一个 Playbook,只需要更新相关的部分并提交这些修改。这里强烈推荐版本管理器 Git 来管理各类 Playbook 脚本。

6、使用 Playbook 管理复杂任务

对于需反复执行的、较为复杂的任务,我们可以通过定义 Playbook 来搞定。Playbook 是 Ansible 真正强大的地方,它允许使用变量、条件、迭代以及模板,也能通过角色及包含指令来重用既有内容。我们来看一个简单的例子,该例子在远端机器上创建一个新的用户:

$ nano -w user.yaml
---
- name: create user                    # Playbook 的名称
  hosts: vps                           # 执行任务的主机表
  user: root                           # 执行任务的远程主机用户
  gather_facts: false                  # 不获取远程主机的信息

  vars:                                # 自定义变量列表
  - user1: "toy"                       # 第一个自定义变量

  tasks:                               # 任务列表
  - name: create {{ user1 }} on vps    # 第一个任务的名称
    user: name="{{ user1 }}"           # 任务格式:"action: module options" 或 "module: options"
  - name: template configuration file  # 第二个任务的名称
    template: src=template.j2 dest=/etc/foo.conf
    notify:                            # 可用于在每个 play 的最后被触发,避免多次有改变发生时每次都执行指定的操作,取而代之,仅在所有的变化发生完成后一次性地执行指定操作。
    - restart memcached                # 调用 handler 中定义的操作名
    - restart apache

  handlers:                            # 用于当关注的资源发生变化时采取一定的操作。handler 是 task 列表,这些 task 与前述的 task 并没有本质上的不同。
  - name: restart memcached            # 第一个 handler 名
    service:  name=memcached state=restarted
  - name: restart apache               # 第二个 handler 名
    service: name=httpd state=restarted

首先,我们给 Playbook 指定了一个名称;接着,通过 hosts 让该 Playbook 仅作用于 vps 组;user 指定以 root 帐号执行,Ansible 也支持普通用户以 sudo 方式执行任务;gather_facts 的作用是搜集远端机器的相关信息,稍后可通过变量形式在 Playbook 中使用;vars 用于定义变量,也可单独放在文件中;tasks 指定要执行的任务,其中 notify 用来监视资源变化,据此调用相关的 handlers;handlers 类似于 tasks,但它仅根据 notify 信号来决定是否执行相应的 handler。要执行 Playbook,可以敲入:

$ ansible-playbook user.yaml

6.1、Ansible 中的变量

正如前述例子中所看到的,Ansible 的 Playbook 中是可以定义变量的。Ansible 的变量名仅能由字母、数字和下划线组成,且只能以字母开头。Ansible 本身也保存了一些变量,它们称之为 facts。facts 是由正在通信的远程目标主机发回的信息,这些信息被保存在 Ansible 变量中。要获取指定的远程主机所支持的所有 facts,可使用如下命令进行:

$ ansible vps -m setup

通过上述命令,Ansible 会存储很多远程主机相关的变量供 Playbook 使用,例如 ansible_os_family 等。 Ansible 可以通过角色传递变量。当给一个远程主机应用角色的时候可以传递变量,然后在角色内使用这些变量。例如:

---
- hosts: webservers
  roles:     # 角色列表
  - common   # 第一个角色
  - { role: wordpress, dir: '/var/www/htdocs/blog',  port: 8080 } # 第二个角色

Ansible 通过 register 把任务的输出定义为变量,然后用于其他任务,示例如下:

---
- tasks:
  - shell: /usr/bin/foo
    register: foo_result  # 将该任务的输出作为 foo_result 变量,
    ignore_errors: True

除此之外,Ansible 可以通过命令行传递变量。在运行 Playbook 的时候,通过命令行可以传递一些变量供 Playbook 使用。例如:

$ ansible-playbook test.yml --extra-vars "hosts=www user=mageedu"

上述命令行传入了 hosts 与 user 变量可覆盖 Playbook 中的相关定义。

6.2、Ansible 中的条件

如果需要根据变量、facts 或此前任务的执行结果来作为某 task 执行与否的前提时要用到条件测试。只需在 tasks 后添加 when 子句即可使用条件测试;when 语句支持 Jinja2 表达式语法。when 语句中还可以使用 Jinja2 的大多 filter,例如要忽略此前某语句的错误并基于其结果(failed 或者 success)运行后面指定的语句,可使用类似如下形式:

---
- tasks:
  - command: /bin/false
    register: result     # 注册器
    ignore_errors: True  # 忽略错误信息
  - command: /bin/something
    when: result|failed  # 第一条命令失败时(result 为 failed 时),才执行第二条命令
  - command: /bin/something_else
    when: result|success #(result 为 success 时),才执行
  - command: /bin/still/something_else
    when: result|skipped # skipped:已经执行过跳过执行

此外,when 语句中还可以使用 facts 或 Playbook 中定义的变量。例如:

---
- tasks:
  - name: "shutdown Red Hat flavored systems"
    command: /sbin/shutdown -h now
    when: ansible_os_family == "RedHat"  #条件测试,ansible_os_family 来自 facts

6.3、Ansible 中的迭代

在迭代中只能使用 item 变量,变量引用为{{ }}两个大括号,变量两边有空格。当有需要重复性执行的任务时,可以使用迭代机制。其使用格式为将需要迭代的内容定义为 item 变量引用,并通过 with_items 语句来指明迭代的元素列表即可。例如:

---
- name: add several users
  user: name={{ item }} state=present groups=wheel stae=present  # 用户得存在且加入 wheel 组中
  with_items:
  - testuser1  # 分别使用 testuser1 替换 name={{item}} 中的 item 项
  - testuser2

上面语句的功能等同于下面的语句:

---
- name: add user testuser1
  user: name=testuser1 state=present groups=wheel
- name: add user testuser2
  user: name=testuser2 state=present groups=wheel

事实上,with_items 中可以使用元素还可为 hashes,例如:

---
- name: add several users
  user: name={{ item.name }} state=present groups={{ item.groups }}
  with_items:
  - { name: 'testuser1', groups: 'wheel' }
  - { name: 'testuser2', groups: 'root' }

迭代还支持列表,使用 with_flattened 语句。例如

---
- vars:                                
  - packages_LNMP:   # 定义列表
    - [ 'nginx', 'mysql-server', 'php-fpm' ]

- tasks:
  - name: Install LNMP
    yum: name={{ item }} state=present
    with_flattened:
    - packages_LNMP  # 再在迭代中引用它

6.4、Ansible 中的角色

Playbook 还可以构造非常重要的文档目录组织结构,也即被称为角色,一个带有角色的 Playbook 的结构如下

$ tree /root/lamp
/root/lamp/
    ├── hosts
    ├── group_vars
    │      └── all
    ├── roles
    │      ├── Apache
    │      │      ├── defaults
    │      │      │      └── main.yml
    │      │      ├── files
    │      │      │      └── httpd.conf
    │      │      ├── handlers
    │      │      │      └── main.yml
    │      │      ├── meta
    │      │      │      └── main.yml
    │      │      ├── tasks
    │      │      │      ├── delete_httpd.yml
    │      │      │      └── main.yml
    │      │      ├── templates
    │      │      │      └── file.j2
    │      │      └── vars
    │      │             └── main.yml
    │      ├── MariaDB
    │      │      ├── tasks
    │      │      │      └── main.yml
    │      │      ... 
    │      └── PHP
    │             ├── tasks
    │             │      └── main.yml
    │             ... 
    └── site.yml

以上显示了名为 lamp 的 Playbook 的目录结构。其中 site.yml 是该 Playbook 的主文件,,它里面的可能写法是

---
- name: install LAMP
  hosts: all
  user: root
  roles:
  - Apache     # 调用 roles/Apache/tasks/main.yml
  - MariaDB    # 调用 roles/MariaDB/tasks/main.yml
  - PHP        # 调用 roles/PHP/tasks/main.yml

hosts 是该 Playbook 的主机文件,它只在该 Playbook 中有效;roles 目录中有三个角色 Apache、MariaDB 与 PHP。下面再来看看角色 Apache 中各个子目录或文件的作用:

  • defaults:当前 role 中的各种变量,主要存放在 defaults/main.yml 中,也可以通过 include 包含更多。它具有最低级别,可被 vars 等处的变量覆盖;
  • files:存放各种文件,Ansible 默认会到这里目录去找文件,对应 task 里面的 copy 或 script 等模块;
  • tasks:tasks:任务列表主要存放在 tasks/main.yml 中,也可以通过 include 包含更多的任务列表。可能的写法如下
    ---
    - name: install httpd
      yum: name=httpd  state=present
    - name: configuration Apache
      copy: src=httpd.conf dest=/etc/httpd/httpd.conf
    - name: configuration iptables
      template: src=file.js2 dest=/etc/sysconfig/iptables
      notify:                     # 会调用 roles/Apache/handlers/main.yml
      - restart httpd           # 对应 name 为 restart httpd 和 restart iptables 的相应命令并执行,
      - restart iptables        # 若之前 Apache 服务已安装,再次执行,不会触发 notify
    #- include: delete_httpd.yml
    
  • handlers:处理列表主要存放在 handlers/main.yml 中,也可以通过 include 包含更多的处理列表。可能的写法如下
    ---
    - name: restart httpd
      service: name=httpd  state=restarted
    - name: restart iptables
      service: name=iptables  state=restarted
  • templates:存放模板,对应 tasks 里面的模块 template,会自动在此目录中寻找 Jinja2 模板文件;
  • vars:定义的变量主要存放在 vars/main.yml 中,也可以通过 include 包含更多。它只对当前 role 有作用;
  • meta:元信息主要存放在 meta/main.yml 中,也可以通过 include 包含更多。它的作用是定义 role 和 role 直接的依赖关系,标准格式如下
    ---
    dependencies:
    - { role: Apache, port: 80 }
    - { role: mariadb, dbname: blarg, other_parameter: 12 }

group_vars 目录中存放供各个角色共同使用的 Ansible 全局变量,一般存放在 group_vars/all 文件中,也可以通过 include 包含更多。

运行带角色的 Playbook 方法与前面类似,只需要调用该 Playbook 的主文件。例如:

$ ansible-playbook site.yml

6.5、Ansible 中的 include

通过一个具体的 Playbook 的例子来说明 include 的用法

$ tree /root/sample
/root/sample/
    ├── hosts
    ├── roles
    │      └── install_client
    │             └── tasks
    │                    ├── db.yml
    │                    ├── app.yml
    │                    └── main.yml
    └── site.yml
$ nano -w ~/sample/hosts
[db]
192.168.24.10
[app]
192.168.24.11
$ nano -w ~/sample/site.yml
---
- hosts: '{{ myhosts }}'
  user: james
  sudo: yes
  sudo_user: root
  roles:
    - install_client
$ nano -w ~/sample/roles/install_client/tasks/main.yml       # 本次测试的关键地方
---
- include: db.yml
  when: "myhosts == 'db'"
- include: app.yml
  when: "myhosts == 'app'"
$ nano -w ~/sample/roles/install_client/tasks/db.yml
---
- name: Touch db file
  shell: touch /tmp/db.txt
$ nano -w ~/sample/roles/install_client/tasks/app.yml
---
- name: Touch app file
  shell: touch /tmp/app.txt

6.6、Ansible 中的标签

当 Playbook 中有很多配置工作时,若中间的某个环节出错了,修改后重新执行的话,会发现有一大堆无关步骤可能隐藏错误。虽然 Ansible 提供了 retry 文件,但它却只是根据主机来判断是否重新执行,仍然不够方便;又或者,中间的某些步骤特别耗时,比如下载一个很大的数据包,每次执行特别浪费时间。注释掉 Playbook 中不需要的部分是绕过它中间环节的办法之一,但不是最最好的办法。实际上,Playbook 中有一个名为 tags 的关键字,它可以有效的解决 Playbook 的调试问题。tags 可以和一个 play(就是很多个 task)或者一个 task 进行捆绑;而 ansible-playbook 提供了 "--skip-tags" 和 "--tags" 来指明是跳过特定的 tags 还是执行特定的 tags。请看例子:

---
- name: test1.yml
  hosts: test-agent
  tasks:
  - command: echo "test1"
    tags:
    - test1
  - command: echo "test2"
    tags:
    - test2
  - command: echo "test3"
    tags:
    - test3

执行

$ ansible-playbook test1.yml --tags="test1,test3"

则只会执行 test1 和 test3 的 echo 命令。执行

$ ansible-playbook test1.yml --skip-tags="test2"

同样只会执行 test1 和 test3 的 echo 命令。

Nov 7

一、Python 的安装

众所周知,目前 Python 有两个分支,也即 Python2 与 Python3。尽管从版本号上看,后者是前者的更新版本,但可惜的是 Python3 不完全兼容 Python2。这就导致了众多使用 Python 的 Linux 系统中需要安装两套 Python 工具。在 MS Windows 中存在同样的问题,不过由于 MS Windows 系统本身不依赖于 Python,因此,安装哪个版本的 Python 取决于使用的 Python 应用程序的依赖关系了。如果作为全新的 Python 开发环境,安装哪个版本的 Python 都无所谓。

安装 Python2 与 Python3 的方法几乎是一样的。例如,要安装 Python2(当前版本是 2.7.10),先到它的官方网站 http://www.python.org 下载安装包

> wget --no-check-certificate https://www.python.org/ftp/python/2.7.10/python-2.7.10.amd64.msi

然后将它安装到 C:\Python27 目录。再如安装 Python3(当前版本是 3.5.0)完全类似,先到它的官方网站 http://www.python.org 下载安装包

> wget --no-check-certificate https://www.python.org/ftp/python/3.5.0/python-3.5.0.amd64.msi

然后将它安装到 C:\Python35 目录。请注意,为了防止 MS Windows 升级过程中影响 Python 的使用,一般不建议将 Python 安装到 MS Windows 系统的应用程序文件目录 %PROGRAMFILES% 中。这就是为什么将 Python 安装到磁盘根目录的原因。

Python 安装完毕之后,需要它们的可执行目录添加到 MS Windows 的 PATH 环境变量中。例如

> setx PATH "%PATH%;C:\Python27;C:\Python27\Scripts"

其中 C:\Python27 里存放的是 Python 解释器;而 C:\Python27\Scripts 则提供了很多工具。现在打开 CMD.EXE 窗口,试试下述命令查看 Python 版本以确认否正常工作

> python.exe -V

一切正常的话,可以进入 Python 解释器了

> python.exe
>>> 

其中 >>> 是 Python 解释器的提示符。里面可以运行 Python 命令,例如想获取 Python 模块 sys 的帮助,可执行

>>> help('sys')

一般而言,Python 内置模块的帮助文档的阅读器默认是 less,因此翻页、退出方法很简单,只需操作键盘上的方向键与 q 即可。退出 Python 解释器的方法是

>>> exit()

实际上,Python 的帮助文档也可直接在命令行或者 Web 界面中调用 pydoc,例如在 CMD.EXE 窗口中执行

> pydoc sys

若不知晓模块名,可按下述方式搜索关键字

> pydoc -k sys

或者启动用小型 Web 服务器,也即在 CMD.EXE 窗口中执行

> pydoc -p 8000

打开网页浏览器,访问 http://localhost:8000 即可查看文档。甚至可到 Python 的官方网页下载帮助文档 http://docs.python.org/download.html。另外,Python 命令也可以写到文件中,这样的文件称为 Python 脚本,一般以 .py 为扩展名。通常,在 Linux 系统中编写 Python 脚本,脚本文件的第一行比较特殊,例如

#!/bin/python

该行被称为 Shebang,它的作用是告诉 Linux 系统执行 Python 脚本所调用的 Python 解释器的版本。不过在 MS Windows 中,Shebang 是没有用处的。例如编写如下的 Python 脚本 test.py:

> notepad test.py
print "Hello, Python World!"

保存后,运行它的方法是

> test.py

会根据环境变量 PATH 调用默认的 Python 解释器。当然也可指定 Python 解释器执行 Python 脚本

> C:\Python35\python.exe test.py

类似,可指定 Python 解释器执行 Python 模块,例如用 Python3 调用模块 pdb 调试 Python 脚本

> C:\Python35\python.exe -m pdb test.py

再如用 Python3 启动小型 Web 服务器

> C:\Python35\python.exe -m SimpleHTTPServer

可在本地网页浏览器中输入 http://127.0.0.1:8000 访问该 Web 服务。

以上介绍的是 Python 官方提供的安装套件。由于 Python 的使用场景众多、应用范围广泛,因此出现了很多非官方的 Python 定制套件。其中比较出名的有 Anaconda(https://www.continuum.io)、Enthought Canopy(https://www.enthought.com) 与 PythonXY(http://sourceforge.net/projects/python-xy),它们在科学计算领域中很有市场。这些 Python 工具的安装方法一如前述官方的 Python 安装方法,当然,这么多 Python 套件也为用户维护、切换它们带来烦恼。鉴于这样的原因,在 Linux 服务器系统或者 Mac OS X 中率先开发了 pyenv 工具来管理这些 Python 套件。作为 MS Windows 用户也不必担心,pywin 提供了类似 pyenv 的管理功能,可惜功能上没那么强大。

二、Python 模块/包管理工具的安装与使用

熟悉开源工具的朋友都知道,很多大型开源开发工具都有相应的开发包集散地与管理工具,例如 TeX 有 tlmgr(http://www.ctan.org,也即 CTAN)、Perl 有 cpan(http://www. cpan.org,也即 CPAN),Ruby 有 gem(http://rubygems.org),PHP 有 pear(http://pear.php.net)。幸运地是,Python 也拥有 pip(http://pypi.python.org,也即 PyPI)。Python 提供了两组工具使用 PyPI,也即 easy_install 与 pip,推荐使用 pip。不过,某些时候,easy_install 还是很有用的,这里也介绍了一下它的用法。另外,最新的 Python 版本一般都自带这两个工具,但是考虑到这两个工具的重要性,还是稍微了解一下这两个工具的安装方法。

1、easy_install 的安装与使用

easy_install 是由 PEAK(Python Enterprise Application Kit) 开发的 setuptools 包里带的一个命令行,所以使用 easy_install 实际上是在调用 setuptools 来完成安装模块的工作。引导 setuptools 的 ez_setup 工具和随之而生的扩展后的 easy_install 与 PyPI(Python Package Index)一起工作来实现相同的功能。它可以很方便的让您自动下载、编译、安装和管理 Python 包。

setuptools 可以在 http://pypi.python.org/pypi/setuptools 页面上找到 MS Windows 的安装方式,现在官网建议使用的是下载 ez_setup.py 来安装。

> wget http://peak.telecommunity.com/dist/ez_setup.py

或者

> wget --no-check-certificate https://bootstrap.pypi.io/ez_setup.py

下载完 ez_setup.py,在 CMD.EXE 窗口中切换到下载目录,使用命令行:

> python.exe ez_setup.py 

该命令行会自动下载最新版本,之后会自动安装 setuptools。setuptools 安装完毕之后,在 C:\Python27\Scripts 下会有一个 easy_install.exe 可执行文件。有 setuptools 之后,剩下的就比较简单了。通常,easy_install 会将第三方 Python 模块安装到 C:\Python27\Lib\site-packages 目录中。

a、通过模块名称来安装。setuptools 会自动搜索 PyPI 以查找最新版本的模块。如果找到的话,它会自动下载、编译和安装。例如

> easy_install SQLObject
> easy_install "python_dateutil==1.5"
> easy_install "python_dateutil>=1.5"

通过 easy_install 安装 pip

> easy_install pip

b、指定查找页面来使用名称和版本信息来安装或升级一个模块:

> easy_install -f http://sqlobject.org/SQLObject
> easy_install -U SQLObject

c、从指定 PyPI下载镜像地址来下载模块源码并在下载成功之后编译安装

> easy_install -i http://e.pypi.python.org/simple pip

d、在 easy_install 编译及安装 Python 包的过程中,需要使用 C/C++ 编译工具。在 MS Windows 操作系统中,同样需要事先安装 Visual Studio 或者 MingGW 等编译软件。PyPI 还提供了已经编译好的二进制文件 egg 给用户直接使用。如果用户直接使用 egg 的话,就无需在操作系统中准备编译器了。egg 是以平台相关的形式发布的,多数面向 MS Windows 用户。在理想情况中,egg 是一个使用 zip 压缩的文件,其中包括了所有需要的包文件。但是在某些情况下,setuptools 会决定(或被开关告知)包不应该是 zip 压缩的。在这些情况下,egg 只是一个简单的未曾压缩的子目录,但是里面的内容是相同的。使用单一的版本可以方便地进行转换,并可以节省一点磁盘空间,但是 egg 目录从功能和组织结构上来说都是相同的。为第三方 Python 模块发布 egg 文件非常简单,只需要在 Python 源码包的 setup.py 所在目录中执行

> python setup.py bdist_egg

在本地已经存在的 egg 文件基础上来安装模块

> easy_install D:\Downloads\OtherPackage-3.2.1-py2.3.egg

e、如果 PyPI 中某个已安装模块有最新版本,可升级到最新版本:

> easy_install --upgrade PyProtocols

f、从源码中安装模块(源码已经下载并解压到当前文件夹下)

> easy_install .

g、手工删除 Python 模块

如果想删除通过 easy_install 安装的软件包,由于 easy_install 本身没有卸载功能,需要手动分步完成。比如说卸载 MySQL-python,首先,可以执行命令:

> easy_install -mxm MySQL-python

此操作会从 C:\Python27\Lib\site-packages\easy-install.pth 文件里把 MySQL-python 的相关信息抹去;接着可以手动删除 MySQL-python 的 egg 文件或 MySQL-python 文件夹;最后,查找 C:\Python27\Scripts\ 目录并删除相关的可执行文件。

一般的 Python 第三方模块或包删除方法就是到 Python 的第三方模块或包的存放位置进行手工删除文件和文件夹,然后删除 C:\Python27\Lib\site-packages\easy-install.pth 文件中的相应的行。卸载 .egg 的方法类似,只需到 C:\Python27\Lib\site-packages 目录删除相应的 egg 文件或目录,接着在从 C:\Python27\Lib\site-packages\easy-install.pth 中删除相应的行。

2、pip 的安装与使用

pip 是 easy_install 的替代,可在 https://pypi.python.org/pypi/pip 找到 pip 的详细信息。pip 的安装需要 setuptools 或者 distribute 模块。distribute 是 setuptools 的替代( setuptools 包后期不再维护了)。若使用的是 Python3,那么只能使用 distribute 来安装了,因为 Python3 不再支持 setuptools 了。

利用 Python 的 setup.py 安装 pip,它默认会使用 setuptools 构建并导入 Python 模块

> wget --no-check-certificate https://pypi.python.org/packages/source/p/pip/pip-7.1.2.tar.gz#md5=3823d2343d9f3aaab21cf9c917710196
> tar -zxf pip-7.1.2.tar.gz
> cd pip-7.1.2
> python setup.py install

利用 distribute_setup 安装 pip

> wget http://python-distribute.org/distribute_setup.py
> wget --no-check-certificate https://bootstrap.pypi.io/get-pip.py
> python distribute_setup.py
> python get-pip.py

安装完成之后,在 C:\Python27\Scripts 下会有一个 pip.exe 可执行文件。有了 pip,Python 模块的安装就非常简单了。通常,pip 也会将第三方 Python 模块安装到 C:\Python27\Lib\site-packages 目录中,除非指定 --user 选项。关于该选项的介绍,请参看 https://docs.python.org/2/install/index.html#inst-alt-install-user。请注意,将 %APPDATA%\Python\Scripts 加入环境变量 PATH,也即

> setx PATH "%PATH%;%APPDATA%\Python\Scripts"

a、在 PyPi 中搜索并安装 Python 模块

> pip search "SomePackage"
> pip install SomePackage
在 pip 编译及安装 Python 包的过程中,同样可能需要使用 C/C++ 编译工具;对于不同版本的包,pip 可以指定包的版本,例如
> pip install SomePackage==version
甚至安装开发版本,例如
> pip install --pre SomePackage

b、pip 可以从指定的文件中读取需要安装的 Python 模块名

> echo Django > requirements.txt
> pip install -r requirements.txt

c、还可为 pip 指定 Python 模块的源码包路径,包括源代码包的本地路径或者网络链接:

> pip install D:\Downloads\SomePackage-1.0.4.tar.gz
> pip install http://my.package.repo/SomePackage-1.0.4.zip
> pip install http://sourceforge.net/projects/pychecker/files/pychecker/0.8.19/pychecker-0.8.19.tar.gz/download 

当然,这些包能够安装的前提是包内都已经写好了 setup.py。

d、可用下述命令查看本地所安装的 Python 模块的文件列表

> pip show --files SomePackage

另外,可通过下述方法列出所有通过 pip 安装的第三方 Python 模块

> pip list

还可以按需求将由 pip 所安装的第三方 Python 模块输出为 pip 命令能辨认的格式供 pip 使用

> pip freeze

e、若 PyPI 中有本地已安装 Python 模块的更新时,可利用下述命令更新它

> pip install --upgrade SomePackage
> pip install --upgrade SomePackage==version
> pip install --upgrade -r requirements.txt

可以利用下述方法列出所有的可更新的 Python 模块

> pip list --outdate

还可升级 pip 自身

> python -m pip install -U pip

f、pip 命令不建议使用 egg 文件。如果你想使用 egg 安装的话,请使用 easy_install 命令。不过 pip 支持 wheel 文件。wheel 本质上是一个 zip 包格式,它使用 .whl 扩展名,用于 python 模块的安装,它的出现是为了替代 Eggs。模块 wheel 还提供了一个 bdist_wheel 作为 setuptools 的扩展命令,它可以用来生成 wheel 包。为第三方 Python 模块发布 whell 文件非常简单,只需要在 Python 源码包的 setup.py 所在目录中执行

> python setup.py bdist_wheel

pip 也提供了一个 wheel 子命令来发布 wheel 包,当然,需要先安装 wheel 模块。同时,pip 可直接安装 wheel 文件,例如

> pip install beautifulsoup4-py2-none-any.whl
实际上,pip 总是优先使用 .whl 文件;若没有 .whl 文件才会从源代码利用 wheel 模块编译;当然,即使有 .whl 文件,也可以让 pip 只从源码编译,例如
> pip install --no-binary :all: beautifulsoup

g、pip 比 easy_install 强大的地方之一是提供了卸载本地安装的第三方 Python 模块,例如

> pip uninstall SomePackage
> pip uninstall -r requirements.txt

pip 还有许多有用的选项,如 --download-cache=DIR 可以指定下载安装文件时缓存至 DIR 路径,下次需要时则直接读取缓存文件;再如 -i 可用来指定 pip 的安装镜像。更多的选项信息可以执行

> pip --help
> pip help install

除了命令行选项之外,还可通过环境变量设置 pip 的行为,例如 PIP_LOG_FILE、PIP_DOWNLOAD_CACHE、PIP_CONFIG_FILE。随后,还可以通过修改 pip 的配置文件来实现上述需求。在 MS Windows 下,pip 配置文件是 %HOMEPATH%\.pip\pip.conf,若在其中增加下述内容

> notepad.exe %HOMEPATH%\.pip\pip.conf
[global]
index-url = http://mirrors.aliyun.com/pypi/simple/

这样在使用 pip 来安装时,会默认使用此源。更多的配置请参考 http://pip.readthedocs.org/en/latest/user_guide.html#configuration。配置的优先级如下:

  • 命令行选项 --host=foo 覆盖 PIP_HOST=foo;
  • 环境变量 PIP_HOST=foo 覆盖 pip 配置文件中的 [global] 小节中的 host = foo;
  • pip 配置文件中的命令 command 小节 [<command>] 中的 host = bar 覆盖配置文件中的 [global] 小节中的相应的配置。

作为使用 pip 的实例,下面来介绍一下 MS Windows 系统中 Python 套件管理工具 pywin 的安装以及使用方法。安装 pywin 之前需要首先确保安装了一套 Python 工具包,它是 pywin 正常工作的保障。pywin 的安装非常简单

> pip install --user pywin

首先初始化 pywin 环境

> pyassoc

搜索所有已安装的 Python 并在 pywin.bat 所在目录中创建相关启动文件

> pywin genlaunchers

若要启动现有 PATH 中的第一个 Python 套件,可执行

> pywin

若想在当前 CMD.EXE 窗口中启用 python 2.7,可执行

> pywin -2.7

若想将 Python 3.3 设置为默认的 Python 套件,可执行

> pywin setdefault 3.3

三、python 虚拟环境的创建与使用

Python 虚拟环境为 Python 模块的开发、测试提供了极大的便利,它可以有效的隔离无关模块。下面来介绍两个建立 Python 虚拟环境的典型方法。

1、利用 virtualenv 模块

安装 virtualenv:

> easy_install virtualenv==1.10.1

或者

> pip install virtualenv

安装完毕之后,同样在 C:\Python27\Scripts 下面可以看到 virtualenv.exe 文件。

安装完毕 virtualenv 之后,就可以创建虚拟环境了。先在 CMD.EXE 窗口中切换到需要创建虚拟环境的目录下,创建的虚拟环境是在该目录下的一个文件夹

> cd D:\some\directory\to\create\VirtualEnvs\
> virtualenv MyEnv

默认情况下,虚拟环境会依赖系统环境中的 site-packages,就是说系统中已经安装好的第三方 Python 包也会安装在虚拟环境 MyEnv 中。如果不想依赖这些 Python 包,那么可以加上参数 --system-site-packages 建立虚拟环境

> virtualenv --system-site-packages MyEnv

如果系统中安装有多个 Python 版本,要想使用指定版本的 Python 来创建虚拟环境,可执行

> virtualenv --python=C:\Python35\python.exe MyEnv

创建之后,可以检查一下 MyEnv 目录中是否有文件生成,同时确认一下 MyEnv\Scripts\ 目录中是否有 activate.bat 和 deactivate.bat 两个文件。至此,虚拟环境 MyEnv 就创建好了。运行虚拟环境只需要在 CMD.EXE 窗口中下执行

> cd D:\some\directory\to\create\VirtualEnvs\
> MyEnv\Scripts\activate.bat

虚拟环境 MyEnv 启动后,会在 CMD.EXE 窗口的提示符前多出一个 (MyEnv),在该 CMD.EXE 窗口中进行的所有操作均在虚拟环境 MyEnv 中,例如下述命令

(MyEnv)> pip install Pelican

会将 Python 模块 Pelican 安装到虚拟环境 MyEnv 所在目录 D:\some\directory\to\create\VirtualEnvs\MyEnv 中,而不是 Python 套件所在目录 C:\Python27 中。退出虚拟环境只需要在该 CMD.EXE 窗口中运行

(MyEnv)> deactivate.bat

简单说明一下与 virtualenv 相关的环境变量 VIRTUALENV_USE_ DISTRIBUTE。VIRTUALENV_USE_DISTRIBUTE 是向 Python 模块 distribute 指明 Python 运行信息的环境变量。如果在开发过程中,希望所开发的 Python 应用程序有“不兼容旧版本的 Python”或者“兼容最新版本 Python”的要求,最好对该环境变量进行设定。为了不在每次登录都输入这样的命令,可在 MS Windows 中设置环境变量,例如

> setx VIRTUALENV_USE_DISTRIBUTE=true

模块 distribute 会根据这个环境变量是否存在,判断虚拟运行环境的配置有效或无效。因此在不使用模块 distribute 的时候,建议取消该环境变量。

2、利用 virtualenvwrapper-win 模块

当然更好的虚拟环境封装是安装下述 Python 模块

> pip install virtualenvwrapper-win

并设置环境变量 WORKON_HOME 为虚拟环境的工作目录

> setx WORKON_HOME "%USERPROFILE%\Virtualenvs"

a、创建虚拟环境,例如下述命令行会创建一个名为 MyEnv 的虚拟环境

> mkvirtualenv MyEnv

使用指定版本的 Python 来创建虚拟环境,例如

> mkvirtualenv --python=C:\Python35\python.exe MyEnv

实际上,virtualenvwrapper 提供了更多的定制变量,例如 VIRTUALENVWRAPPER_PYTHON、VIRTUALENVWRAPPER_VIRTUALENV 以及 VIRTUALENVWRAPPER_VIRTUALENV_ARGS 等。

b、列出所有虚拟环境

> lsvirtualenv

切换到虚拟环境,例如

> workon MyEnv

此时在 CMD.EXE 窗口的提示符前多出一个 (MyEnv)。此时可进入虚拟环境的所在目录,例如

(MyEnv)> cdvirtualenv

根据环境变量 WORKON_HOME 的设定,会将目录切换到 %USERPROFILE%\Virtualenvs\MyEnv。

c、在虚拟环境中的创建工程,例如在 SomeEnv 虚拟环境中创建名为 ProInSomeEnv 的工程

(MyEnv)> mkproject ProInSomeEnv

进入虚拟环境中的工程目录,例如

(MyEnv)> cdproject ProInSomeEnv

退出当前虚拟环境

(MyEnv)> deactivate

d、移除虚拟环境,例如

> rmvirtualenv MyEnv

3、虚拟环境的迁移

当需要将虚拟环境 MyEnv 迁移或复制到另一个虚拟环境(可能不在同一台机器上)YourEnv 时,首先仍然需要在目的机器上安装 pip 和 virtualenv(或者 virtualenvwrapper-win),然后采用以下方法之一安装其他的 Python 模块。

a、直接将 MyEnv 里的文件全部复制到 YourEnv 里,然后修改涉及路径的文件。此种方法可能正常使用,但显然不是好办法。

b、进入原虚拟环境 MyEnv,然后执行

(MyEnv)> pip freeze > requirements.txt

将包依赖信息保存在 requirements.txt 文件中。然后进入目的虚拟环境 YourEnv,执行

(MyEnv)> pip install -r requirements.txt

pip 就会自动从网上下载并安装所有包。

c、pip 默认会从 PyPI 服务器(http://pypi.python.org/simple)下载包的安装文件,如果目的机器无法连外网,则可以采用以下办法:

c1、搭建自己的 PyPI 服务器。专业的,可以使用第三方的软件包来搭建一个完整的 PyPI 镜像服务器。更快速的方法只需要一条命令

> python -m SimpleHTTPServer

即可完成搭建服务器,具体的目录结构可参考原 PyPI 服务器,简而言之,就是把安装文件打包放入目录即可。搭建好服务器之后,在目的虚拟环境中,就可以使用 pip 来安装了,命令如:

(YourEnv)> pip install -i http://127.0.0.1:8000/ -r requirements.txt

c2、若不想搭建 PyPI 服务器,也有办法。首先将所有包的安装文件下载到本地目录中,可以手动下载,也可以使用 pip,例如

(MyEnv)> pip install -d D:\Downloads -r requirements.txt

然后将 D:\Downloads 中缓存的所有 Python 包以及 requirements.txt 文件复制到目标主机中,再在目的虚拟环境中使用 pip 安装,例如所有缓存的 Python 包也被复制到 D:\Downloads 中,则

(YourEnv)> pip install -d D:\Downloads -r requirements.txt

即可。

c3、还有一种途径,就是 pip 提供的 bundle 选项。首先执行

(MyEnv)> pip bundle MyEnv.pybundle -r requirements.txt

将生成一个 MyEnv.pybundle 文件,该文件夹包含所有包的安装文件(注意必须后缀名必须是 .pybundle),默认是重新从 PyPI 服务器下载安装文件的,如果愿意,也可以利用 c1 中的方法,指定本地的 PyPI 服务器。然后在目的虚拟环境中执行

(YourEnv)> pip install MyEnv.pybundle

即可。

四、图形用户界面相关的 Python 工具

GTK+ 是由 C 语言开发的跨平台图形用户界面库,PyGTK 是将 GTK+ 与 Python 语言绑定,它能够用 Python 语言进行图形用户界面的开发,它的项目主页是 http://www.pygtk.org。最新的 PyGTK 版本是 2.24.2,不过目前还不支持 Python3。PyGTK 有两种安装方式:一是安装将 GTK+、Python 等集成在一起的 PyGTK 包,可到 http://ftp.gnome.org/pub/GNOME/binaries/ 下载 32 位的安装包 pygtk-all-in-one-2.24.2.win32-py2.7.msi 或 64 位的安装包 pygtk-all-in-one-2.24.2.win64-py2.7.msi;二是分别单独安装 Python、GTK+ 与 PyGTK 工具包。为了行文统一,此地选择后者作为安装 PyGTK 的方法,Python 已由前述方案安装,请记住所安装的 Python 版本是 Python 2.7;可到 http://ftp.gnome.org/pub/gnome/binaries 下载最新版本 GTK+-2,也即 32 位安装包 gtk+-bundle_2.24.10-20120208_win32.zip 或者 64 位安装包 gtk+-bundle_2.24.10-20120208_win64.zip,若不需要用 C 语言开发 GTK+ 的话,也可只安装 GTK+ 的运行库,到 https://sourceforge.net/projects/gtk-win/files/GTK%2B%20Runtime%20Environment/GTK%2B%202.24 下载 gtk2-runtime-2.24.10-2012-10-10-ash.exe;至于 PyGTK,32 位的包可到 http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/ 下载 pygtk-2.24.0.win32-py2.7.msi,64 位的包可到 http://ftp.gnome.org/pub/GNOME/binaries/win64/pygtk/ 下载 pygtk-2.24.0.win64-py2.7.msi。由于未集成的 PyGTK 工具包还依赖 PyGObject、PyCairo,32 位对应的到 http://ftp.gnome.org/pub/GNOME/binaries/win32/ 下载最新版本 pygobject-2.28.3.win32-py2.7.msi、pycairo-1.8.10.win32-py2.7.msi,64 位对应的到 http://ftp.gnome.org/pub/GNOME/binaries/win64/ 下载最新版本 pygobject-2.28.3.win64-py2.7.msi、pycairo-1.8.10.win64-py2.7.msi。安装过程相对容易,就不多言了。还需测试一下 PyGTK 开发环境,下面是一个简单 Hello PyGTK 的例子
> notepad.exe hellopygtk.py
from gtk import * 

window = GtkWindow(WINDOW_TOPLEVEL) # 创建一个顶层窗口 
window.set_title("Hello, world!") 
window.connect("destroy", mainquit) # 将注销事件与mainquit处理连接 

window.show() # 显示主窗口 
mainloop() # 进入事件循环 
> python hellopygtk.py
 
QT 是由 C++ 语言开发的跨平台图形用户界面库,PyQT 是将 QT 与 Python 语言绑定,它也能够用 Python 语言进行图形用户界面的开发,项目主页在 https://www.riverbankcomputing.com/software/pyqt。PyQT 只提供了一种预编译二进制包安装方式,也即集成除 Python 本身之外所有工具包(如 QT 等)的安装包,最新版本是 PyQT5-5.5.1,可到 http://sourceforge.net/projects/pyqt/files/PyQt5/ 下载 32 位安装包 PyQt5-5.5.1-gpl-Py3.4-Qt5.5.1-x86.exe 或 64 位安装包PyQt5-5.5.1-gpl-Py3.4-Qt5.5.1-x64.exe。将 PyQT下载后安装即可,安装它前需配置好 Python 开发环境,需要注意的是该包需要的 Python 版本是 Python 3.4,若是其他 Python 版本,需自行编译 PyQT 源代码。测试一下 PyQT 开发环境,下面是一个简单 Hello PyQT 的例子
> notepad.exe hellopyqt.py
import sys
from PyQt5 import QtWidgets, QtCore
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QWidget()
widget.resize(360, 360)
widget.setWindowTitle("Hello, PyQt5!")
widget.show()
sys.exit(app.exec_())
> python hellopyqt.py
 
wxWidgets 也是由 C++ 语言开发的跨平台图形用户界面库,wxPython 是将 wxWidgets 与 Python 语言绑定进行图形用户界面的开发,它的项目主页在 http://www.wxpython.org。wxPython 只提供了一种预编译二进制包安装方式,也即集成除 Python 本身之外所有工具包(如 wxWidgets 等)的安装包,最新版本是 wxPython 3.0,可到 http://www.wxpython.org/download.php 下载 32 位安装包 wxPython3.0-win32-3.0.2.0-py27.exe 或 64 位安装包 wxPython3.0-win64-3.0.2.0-py27.exe。安装 wxPython 前需配置好 Python 开发环境,下载的安装包对应的 Python 版本应该是 Python 2.7。测试一下 wxPython 开发环境,下面是一个简单 Hello wxPython 的例子
> notepad.exe hellowxpy.py
import sys, os
from wxPython.wx import *

class main_window(wxFrame):
      def __init__(self, parent, id, title):
         wxFrame.__init__(self, parent, -1, title, size = (200, 100),style=wxDEFAULT_FRAME_STYLE|wxNO_FULL_REPAINT_ON_RESIZE)
         self.control = wxTextCtrl(self, -1, style=wxTE_MULTILINE)
         self.Show(true)

class App(wxApp):
      def OnInit(self):
         frame = main_window(None, -1, "wxPython: (A Demonstration)")
         self.SetTopWindow(frame)
         return true

app = App()
app.MainLoop()
> python hellowxpy.py
 
五、各种科学计算相关的 Python 工具
 
iPython 是增强的交互环境,它支持变量自动补全、自动缩进、支持 Bash Shell 命令,内置了许多很有用的功能和函数。 它的项目主页是 http://ipython.org。由于 iPython 4.0 之后,iPython 将语言无关的部分从 iPython 分离出来成了 jupyter,例如它包含了 iPython.notebook 等。
> pip install ipython jupyter
 
Spyder 是 Python 集成开发环境。由于 spyder 依赖 pyQT,需先安装 pyQT 开发包并配置相关的环境变量才能使用 pip 安装 spyder:
> pip install spyder
如果先解决依赖比较麻烦,也可从它的主页 https://github.com/spyder-ide/spyder/releases 获取预编译二进制包。
 
NumPy 是 Python 的数学计算基础库,它包括了 N 维数组、线性代数计算、傅立叶变换、随机数等。它的项目主页是 http://numpy.org。由于 NumPy 几乎不存在外部依赖,可直接用 pip 安装
> pip install numpy
当然,如果有外部的 LAPACK、BLAS 等 C/C++ 库的话,会对于 NumPy 的安装更有帮助。
 
SciPy 是 Python 的数值计算库,它包括了线性代数、拟合与优化、插值、数值积分、稀疏矩阵、图像处理、统计等。它的项目主页是 http://scipy.org。由于 SciPy 依赖与外部的 LAPACK、BLAS、ATLAS 等 C/C++ 库以及 Numpy 包,若解决了这些依赖问题,可用 pip 安装
> pip install scipy
否则请从它的主页 http://sourceforge.net/projects/scipy/files/scipy/ 获取二进制包。
 
SymPy 是 Python 的符号运算库,它的项目主页是 http://sympy.org。由于 SymPy 不依赖非 Python 库,可直接用 pip 安装
> pip install sympy
 
matplotlib 是 Python 的绘图库,它可用来绘制二维图形和图表,它的项目主页是 http://matplotlib.org。由于 matplotlib 依赖众多外部库,不建议在 MS Windows 上用源码安装,不过好在 PYPI 提供了 wheel 包,可直接安装它
> pip install matplotlib
或从它的主页 http://sourceforge.net/projects/matplotlib/files 获取二进制包。
 
Pandas 是 Python 的数据分析库,它包括了数据导入、整理、处理、分析等功能,它的项目主页是 http://pandas.pydata.org。安装很容易:
> pip install pandas
 
Chaco 是 Python 用来处理交互式图表的,它的项目主页是 http://code.enthought.com/projects/chaco。直接用 pip 安装
> pip install chaco
 
OpenCV 是 Python 的计算机视觉库,它的项目主页是 http://opencv.org。由于依赖比较复杂,建议从项目主页上下载安装最新的预编译二进制包 opencv-3.1.0.exe。
 
Cython 是 Python 代码转 C 代码的转换器,它是编写高效运算扩展库的首选工具。项目主页在 http://www.cython.org。安装非常简单:
> pip install Cython
 
ipdb 是增强的 Python 调试器,也可通过 logging 模块提供的日志功能进行调试。用 pip 安装即可
> pip install ipdb logging
若不想因依赖外部包而不能成功安装上述 Python 包,而又比较简单地获得 MS Windows 上大部分常用的 Python 二进制包,可试试下述地址: http://www.lfd.uci.edu/~gohlke/pythonlibs

若对用 Python 处理计算流体的问题有兴趣,不妨仔细阅读 https://github.com/barbagroup/CFDPython 中的课程。

Aug 23

Unix/GNU Linux 上的 X11 图形程序会从系统的 /etc/profile 以及用户目录的 $HOME/.bashrc、$HOME/.bash_profile、$HOME/.xprofile 等处读取环境变量。例如 GNU Emacs、Code::Blocks、CodeLite、Eclipse 等均可从系统的环境变量中获取需要的环境变量。尽管 Mac OS X 是类 Unix 系统,但毕竟它的图形界面不是由 X11 提供的,因此,它上面的图形程序获取环境变量的方式也是不同的。由于 Mac OS X 中图形程序有多种运行方式,例如从终端调用命令行运行、在 Finder 、Launchpad 以及 Dock 上点击图标运行或者用 SpotLight 调用,这几种运行方式下图形程序的环境变量读取以及生效方式也不尽一致。

首先,谈谈终端环境以及由终端调用的程序(在 Bash Shell 中)如何读取环境变量。根据 /etc/profile 中的设置可以看到,Mac OS X 系统会借助 /usr/libexec/path_helper 程序设置路径变量 PATH。path_helper 会分别读取 /etc/paths、/etc/paths.d/ 与 /etc/manpaths、/etc/manpaths.d/ 中的设置来获取 PATH 与 MANPATH 的环境变量。之后 /etc/profile 还会综合 /etc/bashrc 给出 Shell 中所有系统级环境变量的设置。因此,系统级的环境变量 PATH 的设置可通过修改 /etc/profile、/etc/bashrc 与 /etc/paths.d/ 等相关文件来实现,除此而外的系统级环境变量则可在 /etc/profile、/etc/bashrc 中设置。有一点值得指出,那就是 MANPATH 变量,通常它是不需要设置的,这是因为手册页的搜索除去用 MANPATH 变量配置之外,还可以在 PATH 的相对路径中搜索。至于 Shell 中用户级环境变量的设置,与 Linux 中一样,首先继承来自 /etc/profile 中的系统级环境变量,再根据 Shell 是不是登录 Shell 决定是否读取用户家目录中的 $HOME/.bashrc 或 $HOME/.bash_profile 中的环境变量。一般为了避免配置重复出现,无论登录还是非登录 Shell,均做如下配置

$ cat > ~/.bash_profile << EOF
if [ -f ~/.bashrc ]; then 
    source ~/.bashrc
fi
EOF

相关环境变量的设置都在 $HOME/.bashrc 中进行。这就给出了 Shell 中所有用户级环境变量的配置方法。对于从终端调用的(无论是否是图形界面的)用户级程序而言,环境变量直接继承自 Shell 中所有用户级环境变量。例如从终端打开 Code::Blocks 程序

$ open -a CodeBlocks.app

随后在 Settings -> Compiler...,Global Compiler settings -> Compiler settings -> Other options 中填入 `echo $PATH`,接着随便写一段简单的 C/C++ 程序并点击 Build,在 Code::Blocks 日志框中便会看到环境变量 PATH 的输出。很容易验证 Code::Blocks 确实是从 Shell 中读取了环境变量。先退出 Code::Blocks,记得退出时选择保存设置,因为还要用它进行同样的测试。请记得在测试完了之后,删掉 Code::Blocks 中全局的编译选项设置。为了做一个比较,可以尝试从 Launchpad 中点击 Code::Blocks 图标启动,做同样的尝试,会发现此时 echo $PATH 没有在日志窗口中输出,这表明从 Launchpad 中启动程序不会继承终端程序的环境变量。

那么除了 Terminal.app 之外,对于那些从 Finder、Launchpad 以及 Dock 处启动或者用 SpotLight 调用的程序,是如何读取环境变量的呢?在 Mountain Lion 之前,从那些位置启动的图形界面程序会从 $HOME/.MacOSX/enviroment.plist 文件继承环境变量;但是自 Mountain Lion 之后,~/.MacOSX/enviroment.plist 失效,只有通过 launchtl setenv 命令设置的环境变量才有可能被它们继承,而且设置完必须重启 Finder、Dock 以及 Spotlight 才能生效,具体取决于程序从哪儿被调用?

先来谈谈 ~/.MacOSX/enviroment.plist 的配置,尽管 Mountain Lion 版本已经过时,但难保不会有一些老的机器会停留在那些版本上。通常,~/.MacOSX/environment.plist 文件并不存在,需要先创建再编辑。例如

$ mkdir ~/.MacOSX && touch  ~/.MacOSX/enviroment.plist

根据 ~/.bashrc 中设置的环境变量情况,利用 defaults 命令操作该文件,例如

$ defaults write $HOME/.MacOSX/environment.plist PATH "$PATH”
$ defaults write $HOME/.MacOSX/environment.plist JAVA_HOME "$JAVA_HOME”
$ defaults write $HOME/.MacOSX/environment.plist ANDROID_HOME "$ANDROID_HOME"

注意上述 defaults 命令是将当前终端中的 PATH、JAVA_HOME 与 ANDROID_HOME 写入了 ~/.MacOSX/environment.plist。至于编辑 ~/.MacOSX/environment.plist,最好用 Xcode.app,例如

$ open -a Xcode.app ~/.MacOSX/enviroment.plist

设置调整完成,可以再试试从 Launchpad 中点击 Code::Blocks 图标启动,做前面一样的测试,会发现此时 echo $PATH 在日志窗口中输出正常。

现在来谈谈 Mountain Lion 版本以后,用 launchctl setenv 设置图形界面程序的环境变量的方法。从当前终端运行

$ launchctl setenv PATH “$PATH"

别急着作测试,因为从 Mac OS Yosemite 开始,光这样设置,从 Finder、SpotLight、Launchpad 以及 Dock 点击图标运行的程序还是不会继承这些变量。对于从 Launchpad 或 Dock 中启动的程序,需重启 Dock 才能让设置生效:

$ killall Dock

对于从 Finder 中启动的程序,需重启 Finder 才能让设置生效:

$ killall Finder

对于从 Spotlight 中启动的程序,需重启才能让设置生效:

$ killall Spotlight
$ killall SystemUIServer

好了,有了这些操作之后,再试试从 Launchpad 中点击 Code::Blocks 图标启动,做前面一样的测试,会发现此时 echo $PATH 在日志窗口中输出正常。

但是可以看到,这样的操作无比麻烦。为了省去这些麻烦,可以建立一个用户级的服务来替代这些操作过程。先创建

$ mkdir -p ~/.local/bin
$ cat > ~/.local/bin/environment.sh <<EOF
#!/bin/sh

set -e

syslog -s -l warn "Set environment variables with ~/.local/bin/environment.sh \$(whoami) - start"

launchctl setenv PATH "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/android-sdk-manager/platform-tools:/usr/local/android-sdk-manager/tools:/usr/local/apache-ant/bin:/usr/local/mysql/bin:/usr/local/MacGPG2/bin:/usr/texbin:"
launchctl setenve JAVA_HOME "/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home"
launchctl setenv ANDROID_HOME "/usr/local/android-sdk-manager/"

osascript -e 'tell app "Dock" to quit'
osascript -e 'tell app “Finder" to quit'
osascript -e 'tell app “Spotlight" to quit'
osascript -e 'tell app "SystemUIServer" to quit'

syslog -s -l warn "Set environment variables with ~/.local/bin/environment.sh \$(whoami) - complete"
EOF
$ chmod +x ~/.local/bin/environment.sh

接着再创建服务配置文件

$ nano -w ~/Library/LaunchAgents/org.easior.environment.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>org.easior.environment.plist</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/easior/.local/bin/environment.sh</string>
    </array>
    <key>KeepAlive</key>
    <false/>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

并启动它

$ launchctl load -w ~/Library/LaunchAgents/org.easior.environment.plist

现在来看看以上设置对于环境变量 PATH 带来了怪异现象。首先,第三方终端程序 iTerm.app 作为图形程序,将继承自 launchctl setenv 设定的环境变量 PATH;其次,Mac OS 自带的终端程序 Terminal.app 不会继承来自 launchctl setenv 设置的环境变量,它仍按 Shell 的标准方法设置 PATH 变量;再次,由 XQuartz 提供的 X11.app 中终端程序 xterm 将既会继承由 launchctl setenv 设定环境变量 PATH,还会再次执行登录 Shell,也即会再次继承系统级的 PATH 变量。如此一来,若既在 ~/.bashrc 或 ~/.bash_profile 中设置 PATH,也在 ~/.local/bin/environment.sh 中设置 PATH,且这两个 PATH 设置不一致的话,例如

$ cat >> ~/.bashrc << EOF
~/.local/bin
EOF
$ launchctl setenv PATH "$HOME/bin"
$ killall Dock

那么从 Launchpad 启动的这三个终端的 PATH 变量将会各不相同。为了避免这种情况的发生,建议在 ~/.bashrc 或者 ~/.bash_profile 中设置

if [ ! `launchctl getenv PATH` == "" ]; then
   export PATH=.:`launchctl getenv PATH`
fi

这样一来,每个终端启动的 PATH 均是一致的。还有一点需要指出:launchctl setenv PATH 设置为空与不设置它,这是两码事。例如

$ launchctl getenv PATH ""
$ killall Dock

现在试试从 Launchpad 或 Dock 调用 iTerm.app,会有意想不到的事情发生。

除了 launchctl setenv 的方法之外,修改 Mac OS X 图形程序的 Info.plist 文件中的 LSEnvironment 变量也可以设置需要读取环境变量。例如,在 Finder 的 /Applications 目录鼠标右击 Codeblocks.app 图标 -> Show package contents,接着用 Xcode.app 打开 Contents 目录的 info.plist 文件,在其中增加一个新的 key/dict 对:

<key>LSEnvironment</key>
<dict>
     <key>PATH</key>
<string>/Users/James/.rvm/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:</string>
</dict>

保存完改动并在命令行中执行

$ /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user 
$ killall Finder

再试试从 Launchpad 中点击 Code::Blocks 图标启动,还是做与前面一样的测试,看看 echo $PATH 在日志窗口中输出。为了 Code::Blocks 能正常工作,测试完成后请去掉 Info.list 中的 LSEnvironment 设置。

另外,还有些图形程序,本身提供了设定环境变量的方法。例如对于 GNU Emacs 而言,要继承环境变量可直接在 Emacs 中配置。例如

$ cat >> ~/.emacs <<EOF
(setenv “PATH” (concat (getenv “PATH”) “:” “/opt/local/bin” “:” “/opt/local/sbin”))
EOF

能让 GNU Emacs 的子进程直接读取 PATH 变量。

Aug 23

Homebrew 是一个由 Ruby 语言开发的 Mac OS X 上的第三方包管理工具,它使 Mac OS X 系统更加完整。Homebrew 能够判断 Mac OS X 系统中现有组件的状况,并能够依赖这些已有组件进行安装,而不必重新下载一套重复组件。这一点与 Mac OS X 上其他的第三方包管理工具不同。实际上,Mac OS X 上的第三方包管理工具有很多,例如 MacPortsFinkpkgsrcGentoo Prefix 等等。除 Homebrew 之外,这些第三方包管理工具均会把需要的所有组件全部安装到系统的某个目录(例如 /opt 目录),带来的问题就是 Mac OS X 系统很多已经有的组件都要重新下载安装,这除了带来系统臃肿不够整洁之外,还有可能带来冲突。Homebrew 本身使用 Git 管理,升级非常方便。它的项目主页在 http://brew.sh/

零、准备工作

Homebrew 的工作过程中需要使用编译器、Git 等工具,这些工具均由 Apple 官方的集成开发环境 Xcode.app 工具提供。它的安装方法有两种,一种是进入 Apple 的应用商店下载 Xcode;另一种是去 Apple 开发者主页 https://developer.apple.com/xcode 去下载 Xcode,不过开发者主页下载前一般需要先免费注册一下。下载安装完成后,执行下述命令

$ xcode-select --install

安装 Homebrew 真正需要的命令行工具。实际上,作为安装 Homebrew 的前提,仅需要下载 Xcode.app 的命令行工具,如果注册了 Apple 开发者的话,可直接到 https://developer.apple.com/downloads/ 页面下载它而不必安装完整的 Xcode.app 包。

一、Homebrew 的安装

Homebrew 默认会被安装在 /usr/local/ 目录。但是该目录非普通用户所有,相关操作需要管理员权限,普通用户需要 sudo 权限。若想免去 sudo 的麻烦,可更改目录的属主

$ sudo chown -R `whoami` /usr/local

这对于非服务器版本的 Mac OS X 系统的使用并无大碍。Homebrew 由 Ruby 语言开发,安装当然也只需要用 ruby 命令即可。先确认 Mac 系统上已经默认安装了 ruby:

$ ruby --version

安装 Homebrew 只需要如下的一条命令

$ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

以上脚本执行完成后,Homebrew 会被安装在默认位置,可执行下述命令查看

$ brew --prefix
/usr/local

由于 /usr/local 已在系统 PATH 之中,Homebrew 已无须任何配置。

当然,Homebrew 也可被安装其他指定位置。为了更加清晰地了解安装过程,此地以家目录下的 Developer 目录为例:

$ mkdir ~/Developer/ && cd ~/Developer
$ git clone https://github.com/mxcl/homebrew.git

由于 Homebrew 没有装载默认位置,需要对它稍作配置。将 Homebrew 加入系统环境变量 PATH 中:

$ echo "export PATH=\$PATH:\$HOME/Developer/homebrew/bin" >> ~/.bashrc

环境变量的设置一般需退出并重新打开终端才能生效。为了立即生效,可执行下述命令

$ source ~/.bashrc

确认一下此种方式下面 Homebrew 的安装位置

$ brew --prefix

为了确认 Homebrew 运行时,对系统中各个目录时候有权限等问题,可执行下述命令诊断

$ brew doctor

二、Homebrew 的基本用法

在 Mac OS X 中没有提供想要的软件,可以使用 Homebrew 安装;这里以安装 wget 为例,先在 Homebrew 中查找有没有需要的软件(或者说 formula)

$ brew search wget

如果幸运的话,brew 包含了该软件的 formula,那么可执行

$ brew install wget

所有由 Homebrew 安装的包将被装入 `brew --prefix`/Cellar,然后通过软链接到 `brew --prefix` 目录中。例如

$ cd `brew --prefix`
$ find Cellar -iname 'wget'
Cellar/wget/1.15
Cellar/wget/1.15/bin/wget
Cellar/wget/1.15/share/man/man1/wget.1
$ ls -l bin/wget
bin/wget -> ../Cellar/wget/1.15/bin/wget

若想删掉由 homebrew 安装包,非常简单

$ brew uninstall wget

若不幸没有找到需要的 formula,可到邮件列表 homebrew@librelist.com 中反映,或者自己动手写一个 formula。创建 Homebrew formula 的典型方法如下:先用 Homebrew 下载源码包:

$ brew create http://foo.com/bar-1.0.tgz

该命令会创建原始的 formula,详见 `brew --prefix`/Library/Formula/bar.rb。接着编辑该 formula 让它工作,编辑的样例可在 Homebrew 系统中查找,例如

$ brew edit wget
Homebrew formulae are simple Ruby scripts:
require "formula"

class Wget < Formula
  homepage "http://www.gnu.org/software/wget/"
  url "http://ftp.gnu.org/gnu/wget/wget-1.15.tar.gz"
  sha1 "f3c925f19dfe5ed386daae4f339175c108c50574"

  def install
    system "./configure", "--prefix=#{prefix}"
    system "make", "install"
  end
end

不管怎么样,Homebrew 的开发是在 Ruby 与 git 下进行的,因此它的修改以及与上游更新合并均非常容易。

更新本地已安装的软件包

$ brew update
$ brew upgrade --all

清理残留的旧版本及相关日志

$ brew cleanup

三、Homebrew 的扩展源

当然,若 Homebrew 官方提供的包不能满足需要的话,那么还可以为 Homebrew 安装扩展源,甚至可以安装某个包的多个版本。例如,Mac OS X 已经自带了 PHP,若想安装其他版本的 PHP,可利用 josegonzalez 提供的 PHP 扩展,实际上若网络正常工作的话,下述命令会告知有哪些扩展源提供了 PHP:

$ brew search php

先增加扩展源

$ brew tap josegonzalez/php

现在可以安装需要的包了

$ brew install php55

删掉扩展源提供的安装包还是非常简单:

$ brew uninstall php55

删除 tap 仓库

$ brew untap josegonzalez/php

由于 Mac OS X 中的服务是由 launchctl 管理,对于习惯 Linux 的用户,不一定习惯这种服务管理方式。这就不得不提一下扩展源 gapple/services 提供的 Homebrew-services 了,它提供了类似管理 Linux 服务的命令。先安装扩展源

$ brew tap gapple/services

管理由 Homebrew 安装的服务,例如

$ brew install tomcat
$ brew services start catalina

打开浏览器查看 Tomcat 是否正常运行,注意到 Tomcat 的默认端口是 8080,因此

$ open http://127.0.0.1:8080 &

停止服务的方法也很简单

$ brew services stop catalina

若利用 Homebrew 安装了很多服务,想查看一下,也很简单,例如

$ brew install mysql nginx
$ brew services list

列出已安装的 tap 仓库

$ brew ls-taps

四、更多 Homebrew 指令

显示与 Homebrew 相关的系统信息

$ brew config

显示软件内容信息

$ brew info wxmac

根据列出的信息使用额外的编译选项安装 wxmac 包,例如

$ brew install wxmac --with-stl

对于已安装的包,也可重新选择编译选项安装

$ brew reinstall wxmac --with-static

Homebrew 可一次安装多个包

$ brew install w3m gnutls cscope

显示包依赖

$ brew deps w3m

用浏览器打开 Homebrew 的主页

$ brew home

用浏览器打开 w3m 的 formula 页面

$ brew home w3m

列出已安装的软件

$ brew list

查看已安装的无依赖包

$ brew leaves

查看 Homebrew 帮助

$ brew -h

若需要更多的帮助命令,可执行

$ brew commands

也可以查看 Homebrew 的手册页获得帮助

$ man brew

五、删除 Homebrew

万一用的不爽了,卸载 Homebrew 的方法也很简单

$ cd `brew –prefix`
$ rm -rf Cellar
$ brew prune
$ rm -rf Library .git .gitignore bin/brew *.md share/man/man1/brew.1
$ rm -rf ~/Library/Caches/Homebrew ~/Library/Logs/Homebrew /Library/Caches/Homebrew

此种卸载方式对于系统来说非常干净。

六、附录

Homebrew 也被移植到了 Linux 上,称为 Linuxbrew。虽然各种 Linux 发行都带有自己的包管理工具,诸如 apt-get、yum、pacman、emerge 等等,但对那些工作于服务器版本但没有管理员权限的 Linux 用户来说,Linuxbrew 仍有用武之地,可通过它无须管理员权限安装一些管理员没有提供的或者比系统中更新的包。它的项目主页:http://brew.sh/linuxbrew/

在安装 Linuxbrew 之前,需要先准备好依赖。例如在 Fedora 上,先执行如下命令

$ sudo yum groupinstall 'Development Tools'
$ sudo yum install curl git ruby bzip2-devel curl-devel expat-devel ncurses-devel zlib-devel

接着,将 Linuxbrew 从 GitHub 克隆下来:

$ mkdir ~/Developer/
$ git clone https://github.com/Homebrew/linuxbrew.git ~/Developer/linuxbrew

简单配置

$ echo "export PATH=\$HOME/Developer/linuxbrew/bin:\$PATH
      export C_INCLUDE_PATH=\$HOME/Developer/linuxbrew/include
      export LIBRARY_PATH=\$HOME/Developer/linuxbrew/lib" >> ~/.bashrc

然后执行:

$ source ~/.bashrc

这样子 Linuxbrew 就算装好了。

Aug 21

MinGW、Code::Blocks 和 wxWidgets 是三个著名的开源项目,分别是编译器(GNU Compiler Collections)、集成开发环境(IDE)和图形界面开发库(GUI Library)。由这三个工具搭建起来的全开源 C++ 开发环境,功能不逊色于 VC,由于使用的开源软件,这样的开发环境是免费的,并且是跨平台的。下面说一下在 MS Windows 下的搭建过程。

一、编译器

MinGW 是指只用自由软件来生成纯粹的 Win32 可执行文件的编译环境,它是 Minimalist GNU on Windows 的略称。实际上,MinGW 并不是一个 C/C++ 编译器,而是一套 GNU 工具集合在 MS Windows 系统上的一个移植。MinGW 官方网站为 http://www.mingw.org

1、MinGW 的安装

安装 MinGW 有两种安装方式:自动安装与手动安装。下面分别来介绍它们。

1.1、自动安装。

下载自动安装程序(http://sourceforge.net/projects/mingw/files/Installer/)mingw-get-setup.exe,然后一步步的来就行了。大致如下:设置安装目录,默认安装到 C:\MinGW;选择安装组件,这个根据大家需要选择安装组件( C 编译器、C++ 编译器、Fortran 编译器、ObjC 编译器、Ada 编译器等),一般选择 C/C++ 编译器即可,看各位用途确定,组件列表中还有 MSYS 基本系统等可供选择;等待下载并安装完成。

实际上, MinGW 提供了一个非常方便的包管理工具 mingw-get。在 MinGW 安装完成并适当的配置环境变量之后,可以在命令行窗口(cmd.exe)中或者 MSYS 基本系统中通过 mingw-get 命令来进行包管理。例如在命令行窗口(cmd.exe)中可以使用

> mingw-get --help

查看详细的使用方式,主要命令与各种 Linux 包管理器如 apt-get、dnf 等的操作类似,常用的几个包括

> mingw-get update
> mingw-get install packagename
> mingw-get remove packagename
> mingw-get upgrade packagename
> mingw-get list

1.2、手动安装。

简言之就是本来自动安装的内容要自己下载并且复制到自己设定的目录中。根据 MinGW 官方网站 http://www.mingw.org/wiki/InstallationHOWTOforMinGW 介绍,到 http://sourceforge.net/projects/mingw/files/MinGW/Base/ 下载保证 MinGW 正常工作所需要的下述文件

  • binutils (bin)
  • mingw-runtime (dev and dll)
  • w32api
  • Required runtime libraries for GCC:
    • mpc (dll)
    • mpfr (dll)
    • gmp (dll)
    • pthreads (dev and dll)
    • iconv (dll)
    • zlib
    • gettext
  • gcc-core (bin and dev and dll)

作为 C++ 开发工具,还需再到 http://sourceforge.net/projects/mingw/files/MinGW/Extension/ 下载下述文件

  • gcc-c++ (bin and dev and dll) for c++
  • mingw-gdb and libexpat for debugger
  • mingw32-make for make
  • mingw-utils for MinGW Utilities

然后将这些文件解压到同一个目录下,例如 C:\MinGW。实际上,利用 mingw-get 也可实现半自动化安装,也即将所有包放入它的缓存目录,也即 C:\MinGW\var\cache\mingw-get\packages,然后正常安装 mingw-get-setup.exe 便可利用已下载的包完成安装。半自动安装方法适用于那些暂时或者长时间网络条件比较差的电脑主机。

2、设置 MinGW 的环境变量

MinGW 环境的设置也有好几种方法,但通常的做法是要么类似 Linux 终端起一个 MinGW Shell,要么类似 Visual Studio 用 batch 脚本起一个配置好环境变量的命令行窗口(cmd.exe)。

2.1、若前面安装了 MSYS 基本系统,则在 MS Windows 的开始菜单中会有一个 MinGW Shell 子菜单,也即开始菜单 ->程序 -> MinGW -> MinGW Shell,运行它可在 MS Windows 中打开 MinGW Shell。需要注意,第一次运行 MinGW Shell 时,记得在打开的 MinGW Shell 中执行下述命令

$ /postinstall/pi.sh

它可以将 MinGW 与 MSYS 绑定在一起。以后每次使用 MinGW 时,只需点击该子菜单便可自动在命令行窗口中配置好 MinGW 的环境。如果 MinGW 没有安装在标准位置,那还可适当调整 MinGW Shell 的配置

> notepad.exe C:\MinGW\msys\1.0\etc\fstab
C:\MinGW /mingw

根据此配置,来设定 MinGW Shell 的根目录位置;MinGW Shell 的用户家目录由环境变量 %HOME%、%USERPROFILES% 等决定;还有,MS Windows 中盘符如 C:\、D:\ 等对应的是 /c/、/d/。实际上,安装 MSYS 基本系统的好处也在这里,它将 Linux 系统中那套 Bash Shell 工具搬到了 MS Windows 中来。特别是,在 MinGW shell 中能执行那些只能在 Bash Shell 中执行的 Linux 命令行。若 MinGW 安装过程中没有事先选择安装 MSYS 基本系统的话,可自行通过 C:\MinGW\msys\1.0\msys.bat 脚本创建 MinGW Shell 子菜单;当然,也可以在 MS Windows 桌面或者状态栏创建名为 MinGW Shell 的快捷方式。

2.2、若想全局的设置 MinGW 环境变量,可依次鼠标点击桌面“我的电脑”->选择左侧的“高级系统设置”,选择“高级”->“环境变量”,然后在 PATH 里增加 ;C:\MinGW\bin 声明。也可将 MSYS 的环境变量全局话,也即在 PATH 设置的最后加入 ;C:\MinGW\msys\1.0\bin;C:\MinGW\msys\1.0\local\bin。当然,也可在 MS Windows 的命令行窗口(cmd.exe)中执行

> setx PATH "%PATH%;C:\MinGW\bin;C:\MinGW\msys\1.0\bin;C:\MinGW\msys\1.0\local\bin"

请注意路径 C:\MinGW\msys\1.0\local,它是用户在 MinGW Shell 中安装第三方源码包编译执行 make install 时的安装目录。

2.3、若不想全局启用 MinGW 同时也没有安装 MSYS 基本系统,那么可在命令行窗口(cmd.exe)中临时启用 MinGW,也即

> SET PATH=C:\MinGW\bin;%PATH%

当然,每次都要执行这样的命令还不如建一个环境变量配置脚本来得简单。例如在命令行窗口(cmd.exe)中执行

> notepad.exe mingw32setvar.bat
SET PATH=C:\MinGW\bin;%PATH%

要启用 MinGW 时,可打开命令窗口(cmd.exe)执行

> mingw32setvar.bat

2.4、为了看到前述 MinGW 环境配置是否成功,做一些简单地测试。这里为了简单起见,就以环境变量的配置脚本为例,在命令行窗口(cmd.exe)中执行

> mingw32setvar.bat
> notepad.exe test.cpp
#include <iostream>
using namespace std;

int main(){
    cout << “hello world!”; 
}
> gcc test.cpp
> test.exe

3、为 MSYS 基本系统安装手册页

由于 MSYS 基本系统中没有手册页,一旦遇到不熟悉的 API 就麻烦了,下面介绍一下给 MSYS 基本系统安装手册页的方法。首先打开 MinGW Shell,然后执行

$ mingw-get install msys-groff msys-man

MSYS 基本系统中安装好了手册页工具,试试执行

$ man --help

测试一下。若一切正常,可到 http://www.kernel.org/pub/linux/docs/man-pages/ 下载手册 man-pages-4.02.tar.gz。下载后放到 MS Windows 的用户目录,例如 C:\Users\james,也即 MSYS 基本系统的用户家目录,故直接执行

$ cd ~
$ tar -xzvf man-pages-4.02.tar.gz

进行解压。进入目录手册页源码目录编译

$ cd man-pages-4.02
$ make && make install

大概几分钟后,man 手册就安装成功了。最后,执行

$ man printf

测试一下。

二、Code::Blocks 的安装与配置

1、Code::Blocks 的安装

Code::Blocks 是一个集成开发环境(IDE),本身不含编译,它支持多种编译器,界面近似于 VC。官方网站:http://codeblocks.org。到其官方网站下载最新版本并安装,目前是 codeblocks-13.12-setup.exe,例如安装到 C:\CodeBlocks 目录。

若不想单独配置 MinGW 环境(也即第一步 MinGW 的安装过程),可到 http://sourceforge.net/projects/codeblocks/files/Binaries/13.12/Windows/ 下载安装带有 MinGW 的 Code::Blocks,例如 32 位的 MS Windows 系统选择 codeblocks-13.12mingw-setup.exe、64 位的 MS Windows 系统选择 codeblocks-13.12mingw-setup-TDM-GCC-481.exe。若想在命令行状态下单独使用 MinGW 环境,则须配置系统的环境变量 PATH,例如

> setx PATH "%PATH%;C:\CodeBlocks\MinGW\bin;C:\CodeBlocks\MinGW\libexec\gcc\mingw32\4.7.1"

若第一次运行 Code::Blocks,会要求选 MinGW 为默认编译器。在主菜单 Settings -> Compiler..., Global Compiler settings 子标签页中将 Setected compiler 修改为 GNU GCC Compiler 编译器(就是 MinGW)。在同一个页面中选择 Toolchain executables 选项卡中先修改 Compiler's installation directory 为 MinGW 安装路径,这里就是 C:\MinGW。再在 program files 选项页中选择 C Compiler 为 gcc.exe、C++ Compiler 为 g++.exe、Links for dynamic libs 为 g++.exe、Links for static libs 为 gcc-ar.exe、Debugger 为 gdb.exe;Rescource Compiler 为 windres.exe 以及 Make Program 为 mingw32-make.exe。这样,集成开发环境和编译器就搭建好了。

2、字符编码的设置

自从上帝摧毁了人类建造巴别塔的企图之后,人类的语言就因地域不同而不能直接交流了。计算机中也同样存在着“语言”交流问题,这就是字符编码问题。计算机刚问世的一段时间内,只存储和显示由 8 位字长的二进制数表示的字符,这便是 ASCII 码表示的 ASCII 字符。随着计算机越来越普及,支持多国语言文字提上日程,由各个地区制定的由 ASCII 码衍生出来的表示各地字符的编码统称为 ANSI 编码。例如 GB18030、GB2312、GBK、Big5 等均是中文字符的 ANSI 编码,且前三个是超集关系。ANSI 编码规定:编码位于 0x00——0x7F 之间的字符,依旧是 1 个字节代表 1 个字符。除此而外,不同 ANSI 编码之间不一定兼容。由于这种混乱,Unicode 编码应运而生,它的目标是废除所有的地域性编码方案,重新搞一个包括地球上所有文化所使用的字母和符号的编码。Unicode 编码规定必须用两个字节表示字符(后来还有扩充,可以用以 4 个字节等表示字符),换而言之,所有的字符必须用 16 位字长的二进制数表示。同时,对于原先的 ASCII 字符,Unicode 保持其原编码不变,只是将其字长由原来的 8 位扩展为 16 位,而其他文化、语言的字符则全部重新统一编码。这一点是 ANSI 编码与 Unicode 编码之间的众多区别之一。另外,Unicode 只是规定了字符编码,但没有规定怎么在计算机中存储这些编码。如果直接存储这些编码的话,对于 ASCII 字符来说,第一个字节均是 0,浪费存储资源。这就产生了 Unicode 编码在存储的实现问题。一般,字符编码的存储实现也直接称为编码。UTF-8 编码、UTF-16 编码等都是 Unicode 编码的存储实现。前面的 GB18030 编码、GB2312 编码、GBK 编码、Big5 编码等中文字符的 ANSI 编码既是编码也可以是 ANSI 编码的存储实现。更一般地,ANSI 编码可以与其存储实现一致。在 Linux 世界中,使用 UTF-8 编码是共识。但在 MS Windows 中,字符编码的存储由 Windows 代码页实现。例如,Big5 编码在 MS Windows 中的存储实现是代码页 Windows 950 或者 cp950,GB18030、GB2302、GBK 对应的代码页是 cp54936、cp20936、cp936。另外,微软公司根据中国政府的规定在中国境内销售的中文版 Windows 系统必须设定 GB 编码。这在互联网时代之初带来了困扰中文用户很久的乱码问题,原因在于 GB 编码与 UTF-8 编码除去 ASCII 字符等很少一部分字符重合之外,两者并不完全一致。如此一来,若不对文本做一些额外的处理,跨平台文本交流就会出现乱码。

由于前面的编译器采用的是 MinGW 提供的 GCC,它在编译时默认编译源代码是由 UTF-8 编码编写,编译而成的可执行文本输出时也是 UTF-8 编码。因此,为了让程序正常工作,推荐 Code::Blocks 编辑器中的编码设定为 UTF-8。因此,需要确认一下该编码设置是否正确,也即 Setting -> Editor... -> General Setting -> Other setting 中 Encoding 栏的 Use encoding when opening files 复选框中选择 UTF-8 编码。

如果要用 Code::Blocks 打开久远年代用 GBK 编码写成的工程文件时,特别是打开那些带有久远的库文件的工程时,为了防止乱码,好的选择是再次修改 Code::Blocks 编码设置并更改编译选项设置。例如,老工程是 GBK 编码所写,那么先在 Setting -> Editor... -> General Setting -> Other setting 中 Encoding 栏的 Use encoding when opening files 复选框中选择 GBK 编码,代码页为 Windows 936;以保证 Code::Blocks 编辑器中源代码显示正常。其次,在 Setting -> Compiler... -> Global Compiler Settings -> Compiler Settings -> Other options 框中输入两个选项:-finput-charset=GBK-fexec-charset=GBK。总之,要做到源代码文件的编码和编译时输入、输出编码统一。

3、定制 F1 帮助系统

在主菜单 Settings -> Environment... -> Help files 子标签页中新建一个条目,名为 manual Pages,接着的对话框选 no,然后手动在下面的路径框中填写:man:/usr/share/man;再将使用 F1 快捷键勾上。这样就可以使用 man pages 了。在 Editor 中,将光标停在想要查询的内容上面,按 F1,就会弹出一个 panel,里面就有查询内容的 man 手册,非常的方便。比如 光标位于 printf,按 F1,弹出的 panel 中就显示 printf 的 man 手册。建议安装如下手册页:C API 手册页、posix 函数手册页、C API 手册页 glibc-doc、C++ 标准类库手册页、C++ API 手册页等。

4、变量声明、函数原型等自动提示

每个 Code::Blocks 工程都可在主菜单 Project -> Properties...,Project/Target options -> C/C++ parser options 中添入头文件所在目录,这样信息提示功能才可正常工作,如下是标准库头文件的目录:

  • C:\MinGW\include
  • C:\MinGW\include\c++\4.8.1
  • C:\MinGW\include\c++\4.8.1\mingw32
  • C:\MinGW\include\c++\4.8.1\backward

若项目中还使用了其他库,也可添加更多的头文件路径。例如将后面马上要提到图形界面开发库 wxWidgets 的文件路径添加进去

  • C:\wxWidgets-3.0.1\include
  • C:\wxWidgets-3.0.1\lib\gcc_dll\mswu\

需要注意,添上后不是立即起效,需要过一段时间后才能起作用!

5、自动补全与缩写

首先,打开主菜单 Settings -> Editor... -> Code-completion 标签页,点击 Code-completion 选项卡

  • 将 Keyword sets to additionally include 中1到9都勾上,1 ~ 9 对应的关键字可在 Settings -> Editor... -> Syntax highlighting 的 keywords... 按钮中设置,默认其中 1 是 C++ 关键字,3 是 Doxygen 关键字;
  • 将 Delay for auto-kick-in when typing [.::->] 拉到 200ms,这样快点出来提示;
  • 将 Automatically launch when typed # letters 中的 4 改成 2,这样打两个字母就会有提示了;
  • 将 Case-sensitive match 的勾去掉,它会帮你纠正大小写。

其次,在主菜单 Settings -> Editor... -> Abbreviation 标签页中定义了许多缩写(还可以自定义),只要输入这些缩写,并按 Ctrl+J,就可以自动完成常用的代码框架,并可将光标放在恰当的地方(自定义时用|表达),常用的有:guard、class、switch 等。

6、Code::Blocks 的汉化

Code::Blocks 的本地化(翻译)项目在 https://translations.launchpad.net/codeblocks/+translations 上,有兴趣的用户可以加入。Code::Blocks 的语言包也在该主页上,下载对应的语言包,不过下载前需要注册一个账号。能下载的压缩包有两种:mo 和 po 格式,本地化需要的是 mo 格式文件,下载并打开 mo 压缩包,解压得到里面的文件,找到对应语言的 mo 文件。接着在 Code::Blocks 安装目录下的建立相应语言的文件夹,例如

> mkdir C:\CodeBlocks\share\CodeBlocks\locale\zh_cn

并复制刚才找到的 mo 文件到该位置。启动 Code::Blocks,找到主菜单上的 Settings -> Environment...,Environment -> View 选项卡,在第二行 internationalization 上打钩,在右边选择 chinese(simplified),点 OK。重启 Code::Blocks,界面就汉化了。

安装 Code::Blocks 每日提示双语文件

本文件仅仅用于实现启动 Code::Blocks 时,出现的“今日提示”内容的汉化。请妥当备份本文件,因为在每次进行升级时,该文件都有可能被原英文提示文件覆盖掉。所在以每次升级之后,都需要进行本操作。下载 Code::Blocks 每日提示中英双语文件。请打开 Code::Blocks 安装目录下的 C:\CodeBlocks\share\CodeBlocks 子目录,找到 tips.txt,如有必要,请先备份原文件。然后解压上面下载的文件,覆盖原有文件。

7、为 Code::Blocks 创建新的工程向导文件

Code::Blocks 提供了很多工程向导模板,但是还有很多图形工程没有模板,例如 GTKmm。实际上,在 Code::Blocks 增加工程向导模板是非常容易的,这里以 GTKmm 模板为例。首先,在用户的 Code::Blocks 应用程序数据目录中新建工程向导文件目录 GTKmm,也即 %APPDATA%\codeblocks\share\codeblocks\templates\wizard\gtkmm,通常要在这个目录中创建如下几个文件 wizard.scriptlogo.pngwizard.png 以及模板文件夹 files;这些文件的创建方法可到 Code::Blocks 安装目录中查看相应例子。接着,向 Code::Blocks 注册新建的工程文件,也即编辑 %APPDATA%\codeblocks\share\codeblocks\templates\wizard\config.script,向其中写入如下的行 RegisterWizard(wizProject, _T("gtkmm"), _T("GTKmm project"), _T("GUI"));

8、Code::Blocks 配置文件备份与还原

Code::Blocks 的配置文件位于 %APPDATA%\codeblocks\default.conf 之中,用户可以修改或者保存它。不过 Code::Blocks 不建议直接备份它,而是在 Code::Blocks 的安装目录中提供了一个可执行文件 cb_share_config.exe,它可以用来导出、导入配置:

> C:\CodeBlocks\cb_share_config.exe

9、Code::Blocks 语法高亮配置

Code::Blocks 的知识百科上提供了 16 中语法高亮配置包,请看 http://wiki.codeblocks.org/index.php?title=Syntax_highlighting_custom_colour_themes,也即文件 colour_themes。使用方法非常简单,首先将该文件解压,接着关闭 Code::Blocks,用文本编辑器打开 %APPDATA%\codeblocks\default.conf,将解压文件中介于 <colour_sets></colour_sets> 之间的内容替换成原来内容。

10、Code::Blocks 的外部工具(Tools、Tools+)以及社区插件需要的外部程序

Code::Blocks 有内建插件、社区插件( contrib plugin)。有些插件直接就可以使用,例如代码补全、帮助文件等;有些插件在 MS Windows 平台直接可用,但在其他平台需要配置,例如代码格式化(Astyle)、拼写检查(Spellcheck);还有一些插件是需要安装外部程序才能使用的,例如

再有些插件只有特定平台才能使用,例如 Dev-pack 插件只能在 MS Windows 上使用,而检查内存泄漏的 Valgrind 插件无法在 MS Windows 上使用。

Code::Blocks 的 Tools、Tools+ 具有插件的功用,但那些功能还有没有插件实现。例如,代码的版本库功能对于管理代码非常有效,但目前 Code::Blocks 还没有完全实现这些插件。于是可以利用外部工具 Tools、Tools+ 与版本管理工具 git、svn 来实现一些简单的功能。当然,对这些强大的功能,还是希望有比较完善的插件来实现。

三、wxWidgets 界面库的编译与安装

wxWidgets 是一个由 C++ 编写的用来提供图形界面的开发框架。它最早是由 Julian Smart 于 1992 年开发的,包含了一个可以支持现今几乎所有操作系统(MS Windows、Unix/Linux with X11/GTK+/QT/Motif、MacOS、OS/2)的图形界面库,提供了类似微软基础类库(MFC)的功能。但是,wxWidgets 不仅仅是一个图形界面开发库,同时它也内置了基于 ODBC 的数据库、线程库以及网络通信库等类库。除此而外,wxWidgets 还有很多第三方库的支援,例如 wxSQLite、wxMathPlot、wxHTTPSever 等。特别要说一下,wxWidgets 库的后续版本还提供了对掌上电脑(SYMBIAN、iOS)的支持,还打算在新版本中对 Android 设备提供支持。wxWidgets 宣称使用其库所开发的软件只需要对源代码做少量更改(或者完全不用更改),就能在各种平台上编译并运行。wxWidgets 库使用了大量的宏,使用它开发的代码编译后尽量使用目标操作系统的本地图形界面样式。换而言之,wxWidgets 开发的程序界面,在 MS Windows 中显示 MS Windows 的样式,在 Linux 的 GNOME 桌面(KDE 桌面)显示的 GTK+ 样式(QT 样式),在 Mac OS X 中则显示 Aqua 样式。

虽然 wxWidgets 库本身使用 C++ 语言开发,但也有其它不同编程语言的绑定,例如:Python(wxPython)、Lua(wxlua)、Perl(wxPerl)、Ruby(wxRuby)、Smalltalk(wxSmalltalk)、Java(wx4j)、C#(wx.NET)甚至是 JavaScript(wxjs)等。

wxWidgets 的授权许可证是经过开放源代码促进会认证的,等同于 GNU 宽通用公共许可证(LGPL)。特别地,wxWidgets 授权允许修改者以自己的许可证发布。它的官方网址:http://www.wxwidgets.org/

wxWidgets 的安装方法大概有三种:稳定版源码包编译安装、开发版源码编译安装以及二进制版类库安装,下面开始逐一介绍。

1、到其官方网站下载最新版本的 wxWidgets 源码,目前最新版本为 3.0.1,推荐下载 wxMSW-3.0.1-Setup.exe,默认安装在 C:\wxWidgets-3.0.1 目录下。在 MinGW shell 中依次执行如下命令

$ cd /C/wxWidgets-3.0.1
$ ./configure
$ make
$ make install

请注意 make install 会将 wxWidgets 库文件安装到 MSYS 基本系统的根目录,此地便是 C:\MinGW\msys\1.0\local。这样的好处是,若在 MinGW Shell 中执行

$ wx-config --cxxflags
$ wx-config --libs

便可看到使用 wxWidgets 库文件的编译参数信息。

前述编译方法是 Linux 中源代码编译、安装的标准方法,甚至来编译参数设定也可以用典型的 Linux 方法,也即 wx-config 命令,当然,这一切以安装了 MSYS 基本系统为前提。但在 MS Windows 上,对于 wxWidgets 库文件来说,一般并不推荐安装,直接在源码的编译目录中使用它即可。鉴于这种处理方式,给出 wxWidgets 库文件在 MS Windows 平台的正常编译方法,也即在 MS Windows 的命令行窗口(cmd.exe)中执行:

> cd C:\wxWidgets-3.0.1\build\msw

里面有很多 Makefile,它们用来控制 wxWidgets 库文件的编译过程。实际上,wxWidgets 可被分别编译成 Release/Debug、ANSI/Unicode 与动态/静态版本的库。动态版本库的扩展名是 .dll,静态版本库的扩展名是 .lib 或者 .a;用 d 表示 Debug 版本,没有 d 则表示 Release 版本;u 表示 Unicode 版本,没有 u 表示 ANSI 版本。选择不同的版本库,编译出来的库文件路径及其头文件路径也是不一样的,例如 msw、mswd、mswu、mswud 分别对应采用 ANSI 字符集的 Release 版本、采用 ANSI 字符集的 debug 版本、采用 Unicode 字符集的 Release 版本、采用 Unicode 字符集的 Debug 版本;再如 wxbase30.lib、wxbase30d.lib、wxbase30u.lib、wxbase30ud.lib 分别对应采用 ANSI 字符集的 Release 版本、采用 ANSI 字符集的 Debug 版本、采用 Unicode 字符集的 Release 版本、采用 Unicode 字符集的 Debug 版本,且它们都是静态库。版本库在调试起见,使用 Debug 版;正式发布时,肯定需要 Release 版,这也是 Code::Blocks 源代码中的 cbp 工程文件所需要使用的 wxWidgets 版本;至于程序源代码字符集采用的是 ANSI 还是 Unicode,可在工程“属性”->“常规”->“字符集”里面查看与设置。例如采用 Unicode 字符集的 Release 版本动态库的编译方法

> mingw32-make -f makefile.gcc MONOLITHIC=0 SHARED=1 UNICODE=1 BUILD=debug

采用 ANSI 字符集的 Debug 版本静态库的编译方法

> mingw32-make -f makefile.gcc MONOLITHIC=0 SHARED=0 UNICODE=0 BUILD=debug

无论哪种编译结果,可分别在 C:\wxWidgets-3.0.1\lib\gcc_dll\mswudC:\wxWidgets-3.0.1\lib\gcc_lib\mswd 及其上层目录看到很多 wxWidgets 头文件与库文件。若想把 wxWidgets 库文件变成一个,可启用 MONOLITHIC 参数。另外一个参数 vendor 值得一提,它是指编译或者发布 wxWidgets 库的作者或机构,默认是 custom,而由 Code::Blocks 团队发布的 wxWidgets 库的 vendor 则是 cb。若重新调整编译参数之后再编译,最好先清理先前编译留下的残余。于是可尝试

> mingw32-make -f makefile.gcc MONOLITHIC=1 SHARED=1 UNICODE=1 BUILD=release vendor=juk clean
> mingw32-make -f makefile.gcc MONOLITHIC=1 SHARED=1 UNICODE=1 BUILD=release vendor=juk

编译之后可在 C:\wxWidgets-3.0.1\lib\gcc_dll\mswu 及其上层目录中看到编译好的头文件与库文件。 有关编译过程的详细参数说明请看文件 C:\wxWidgets-3.0.1\build\msw\config.gcc;详细安装说明看文件 C:\wxWidgets-3.0.1\docs\msw\install.txt

2、若想编译最新的 wxWidgets 源码,可从 wxWidgets 项目的源代码仓库中拖源代码。目前,该仓库由版本管理工具 git 管理,故先在 msysgit 提供的 Git Shell 中拖源代码:

$ cd /c/
$ git clone https://github.com/wxWidgets/wxWidgets.git wxWidgets_git

然后可在 MinGW Shell 中执行编译过程

$ cd /c/wxWidgets_git
$ ./autogen.sh
$ mkdir mswbuild && mswbuild
$ ../configure
$ make

当然,这是 Linux 中的开发版源代码的典型处理方法。如果没有安装 MSYS 基本系统,也可采用 MS Windows 平台的典型编译方法,例如在命令行窗口中执行

> cd C:\wxWidgets_git\build\msw
> mingw32-make -f makefile.gcc MONOLITHIC=1 SHARED=0 UNICODE=0 BUILD=release vendor=juk

编译之后可在 C:\wxWidgets_git\lib\gcc_lib\msw 及其上层目录中看到编译好的 Debug 版的头文件与静态库文件。如果有兴趣的话,自行比较一下 MS Windows 平台中不同编译参数下生成的库文件的大小。

3、如果不想自己编译,可以到 http://sourceforge.net/projects/wxpack 下载已经编译好的库(wxPack)的最新版本,目前是 wxPack_v2.8.12.01.exe。一般 wxPack 的版本相比源码编译的版本要老。wxPack 的安装目录可选在 C:\wxWidgets-2.8。值得一提的是,wxPack 包提供了供 VC、GCC 使用的各种版本的库文件,还提供了图形界面快速构建工具 wxformbuilder。wxformbuilder 对于本身没有为 wxWidgets 提供快速构建工具的集成开发环境(例如 Visual C++、Visual Studio、Xcode 等)提供了极大的方便。它的官方主页 http://www.wxformbuilder.org

4、现在,可以利用 wxWidgets 库创建 wxWidgets 项目应用程序。不过,由于库文件的使用是个比较复杂的问题,此地为简单起见,以稳定版源码包编译并安装到 MSYS 基本系统的 wxWidgets 库为例,测试一下前面编译的 wxWidgets 是否能够正常使用。在 MinGW Shell 中执行

$ notepad.exe sam.cpp
#include <wx/wx.h>

class Simple: public wxFrame
{
public:
    Simple(const wxString& title);
};

class MyApp: public wxApp
{
public:
    virtual bool OnInit();
};

Simple::Simple(const wxString& title) :
    wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 150))
{
    Centre();
}

IMPLEMENT_APP (MyApp)

bool MyApp::OnInit()
{
    Simple *simple = new Simple(wxT("Simple"));
    simple->Show(true);
    return true;
}

接着编译并运行它

$ g++ sam.cpp `wx-config --cxxflags --libs`
$ sam.exe

当然,如果没有安装 MSYS 基本系统的话,在 MS Windows 命令行窗口(cmd.exe)执行也可以,只要配置好了环境变量,也即配置好全局 PATH 保证能够找到 wx-config;不过最好还是等到将 wxWidgets 整合到集成开发环境之后再来试比较好。实际上,wxWidgets 自带了 demo 程序用来测试 wxWidgets 库是否正常工作,例如

> cd C:\wxWidgets-3.0.1\demos\bomb\
> mingw32-make -f makefile.gcc

不过由于 demo 程序很久没人维护,必定会报错。不过不用担心,编译报错的原因只是 makefile.gcc 中的 CXXFLAGS 需要增加 -I..\..\lib\gcc_dll\mswu\wx\include\msw-unicode-3.0,可试试下面的命令

> mingw32-make -f makefile.gcc
> bomb.exe

四、整合 MinGW、wxWidgets 与集成开发环境

使用 wxWidgets 开发库是个头疼的问题,首先,系统中可能同时有多个版本的 wxWidgets 开发库;其次,编译出来的开发库可能是静态库,也可能是动态库;再次,在程序编译与运行过程中,动态库的加载有顺序问题。此类问题对于如何在 Code::Blocks 使用 wxWidgets 开发库造成了一些麻烦。不过,稍微留意一下附录中提到的 MS Windows 系统中动态库的加载顺序,就会有很多解决方案。例如将库文件放在程序可执行文件的目录中;或者放在通过环境变量 PATH 指定的某个路径中。为了避免因设置全局 dll 路径而引起 dll 灾难(dll hell),MS Windows 程序发布往往采用前者。

刚刚指出了运行程序时如何解决库文件的搜索问题。但是,由于开发过程不仅牵涉到程序运行,还要保证程序的正常编译,这就要保证不仅库文件要被找到,还有与库文件关联的头文件也要被找到。要解决库及其头文件的放置能够让程序正常编译,比较好的办法是通过 GCC 编译器的编译参数。以下就来谈谈将 MinGW、wxWidgets 与集成开发环境整合成整体。

先来说说全局配置 wxWidgets 库,主要介绍 5 种方法。当然,这些方法之间还能相互共用产生更多的方法,特别是再结合后面要谈到的工程相关(局部)配置方法。作为日常使用 Code::Blocks 来说,只需要掌握工程相关(局部)配置方法就可以了。话说回来,集成开发环境的库配置方法所涉及的原理不仅限于 Code::Blocks,同样适用于众所周知的集成开发环境(例如 Visual C++、Visual Studio、Dev-C++、QT-Creator、CodeLite、Xcode 等)。

1、由于 GCC 编译器可以通过 GCC 编译参数的编译参数找到它们所需要的库文件与头文件,于是通过设置编译选项与链接选项即可达成目的。

1.1、设置编译选项。先在 MinGW Shell 中执行

$ wx-config --cxxflags
-I/usr/local/lib/wx/include/msw-unicode-3.0 -I/usr/local/include/wx-3.0 -D_LARGEFILE_SOURCE=unknown -DWXUSINGDLL -D__WXMSW__ -mthreads

然后开启 Code::Blocks,将 -IC:\MinGW\MSYS\1.0\local\lib\wx\include\msw-unicode-3.0 -IC:\MinGW\MSYS\1.0\local\include\wx-3.0 -D_LARGEFILE_SOURCE=unknown -DWXUSINGDLL -D__WXMSW__ -mthreads 拷贝到主菜单 Settings -> Compiler...,Global Compiler settings -> Compiler settings -> Other options;

1.2、设置链接选项。在 MinGW Shell 中执行

$ wx-config --libs
-L/usr/local/lib -Wl,--subsystem,windows -mwindows -lwx_mswu_xrc-3.0 -lwx_mswu_webview-3.0 -lwx_mswu_html-3.0 -lwx_mswu_qa-3.0 -lwx_mswu_adv-3.0 -lwx_mswu_core-3.0 -lwx_baseu_xml-3.0 -lwx_baseu_net-3.0 -lwx_baseu-3.0

然后将 -LC:\MinGW\MSYS\1.0\local\lib -Wl,--subsystem,windows -mwindows -lwx_mswu_xrc-3.0 -lwx_mswu_webview-3.0 -lwx_mswu_html-3.0 -lwx_mswu_qa-3.0 -lwx_mswu_adv-3.0 -lwx_mswu_core-3.0 -lwx_baseu_xml-3.0 -lwx_baseu_net-3.0 -lwx_baseu-3.0 拷贝到 Settings -> Compiler...,Global Compiler settings -> Linker settings -> Other linker options。

实际上,若在 Linux 上配置 wxWidget 库的话,那么编译选项与链接选项的设置更为简捷,只需主菜单 Settings -> Compiler...,Global Compiler settings -> Compiler settings -> Other options 编译选项设置为 `wx-config --cxxflags`;Settings -> Compiler...,Global Compiler settings -> Linker settings -> Other linker options 链接选项设置为 `wx-config --libs`。这种设置方法在 Linux 中很有优势,不过在 MS Windows 中无法使用。

另外,以上设置方法对于在非工程模式下编译源代码文件非常有效。例如按上法配置完 wxWidgets 库之后,在 Code::Blocks 中新建空文件,也即 File -> New -> Empty File,然后将前面 sam.cpp 的内容复制进来,并保存重命名为 sam.cpp。试试 Build。

2、通过分离 GCC 编译选项中的路径选项也可配置 wxWidgets 库。

2.1、设置编译选项。开启 Code::Blocks,进入主菜单 Settings -> Compiler...,Global Compiler settings -> Compiler settings -> Other Options,将 -D_LARGEFILE_SOURCE=unknown -DWXUSINGDLL -D__WXMSW__ -mthreads 加入。

2.2、设置链接选项与库文件。在 Settings -> Compiler...,Global Compiler settings -> Linker stettings 标签页中的 Other linker options 文本框中加入 -Wl,--subsystem,windows -mwindows;接着在 Global Compiler settings -> Linker stettings 标签页的对话框 Link libraries 下,点击 ADD 按钮,将 C:\wxWidgets-3.0.1\lib\gcc_dll\mswu\ 中所需要的库一一选中加入即可。

2.3、设置搜索目录。Settings -> Compiler...,Global Compiler settings -> Search directories -> Compiler 设置头文件目录为 C:\wxWidgets-3.0.1\includeC:\wxWidgets-3.0.1\lib\gcc_dll\mswu\wx\include\msw-unicode-3.0。注意,这里没有设置库文件的搜索路径,原因在于前一步中装入的库文件名是绝对路径。如果上一步加入的库文件只有文件名没有路径的话,那么这一步还要设置库文件的搜索路径,也即 Settings -> Compiler...,Global Compiler settings -> Search directories -> Linker 设置库文件目录为 C:\wxWidgets-3.0.1\lib\gcc_dll\mswu\

这种设置方法与第一种全局设置方法的差异在于 wx-config 输出中的 -I、-L 以及 -l 参数(也可部分地)替代成了路径与文件名。

3、通过环境变量配置全局设置库文件及其头文件的路径也是一种途径。

全局设置库文件及其头文件的路径是 Linux 的常用方法,例如 Linux 系统常常将库文件放入 /usr/lib 而头文件则放入 /usr/include。此地利用 MSYS 基本系统来做类似的事情,假设已将 wxWidgets 库装入 C:\MinGW\msys\1.0\local

3.1、设置编译选项。开启 Code::Blocks,进入主菜单 Settings -> Compiler...,Global Compiler settings -> Compiler settings -> Other Options,加入 -D_LARGEFILE_SOURCE=unknown -DWXUSINGDLL -D__WXMSW__ -mthreads

3.2、设置链接选项。在 Settings -> Compiler...,Global Compiler settings -> Linker stettings 标签页中的 Other linker options 文本框中加入 -Wl,--subsystem,windows -mwindows;接着在 Global Compiler settings -> Linker stettings 标签页的对话框 Link libraries 下面,点击 ADD 按钮后弹出的文件选择对话框中将 C:\MinGW\msys\1.0\local\lib\ 目录下的文件全选就可以将所有选中的文件一次性加。

3.3、设置全局环境变量来替代搜索目录的设定,例如

> SETX LIBRARY_PATH "%LIBRARY_PATH%;C:\MinGW\lib;C:\MinGW\msys\1.0\local\lib"
> SETX C_INCLUDE_PATH "C:\MinGW\msys\1.0\local\include"
> SETX CPLUS_INCLUDE_PATH "C:\MinGW\msys\1.0\local\include"

需要注意,这种方法毕竟与 MS Windows 习惯不同,故而在 MS Windows 平台很少使用。

4、通过 Code::Blocks 的全局变量也可配置 wxWidgets 库。

Code::Blocks 的全局变量可在工程创建期间使用,也可在各个配置选项卡中使用,下面通过全局变量配置 wxWidgets 库给出引用这些变量的方法。

4.1、设置全局变量。点击主菜单 Setting -> Global Variables...,会弹出一个 Global Variable Editor 的窗口, 在该窗口的 default 变量集下新建立一个 wx 变量,在左下 Build-in fields 的 base 中填入 C:\wxWidgets-3.0.1、include 中填入 C:\wxWidgets-3.0.1\include、lib 中填入 C:\wxWidgets-3.0.1\lib\gcc_dll\mswu、cflags 中填入 -D_LARGEFILE_SOURCE=unknown -DWXUSINGDLL -D__WXMSW__ -mthreads、lflags 中填入 -Wl,--subsystem,windows -mwindows -lwx_mswu_xrc-3.0 -lwx_mswu_webview-3.0 -lwx_mswu_html-3.0 -lwx_mswu_qa-3.0 -lwx_mswu_adv-3.0 -lwx_mswu_core-3.0 -lwx_baseu_xml-3.0 -lwx_baseu_net-3.0 -lwx_baseu-3.0。 除去 Build-in fields 之外,还可以有 user-defined fields(自定义域)。

4.2、使用全局变量。从主菜单 Settings -> Compiler...,Global Compiler settings -> Compiler settings -> Other Options 中填入 $(#wx.clfags);Global Compiler settings -> Linker settings -> Other linker options 中填入 $(#wx.lflags);在 Global Compiler settings -> Search directories -> Compiler 中填入 $(#wx.include)$(#wx.lib)\wx\include\msw-unicode-3.0.1;在 Global Compiler settings -> Search directories -> Linker 中填入 $(#wx.lib)

从配置过程可以看到引用 Code::Blocks 的全局变量 wx 的方法是 $(#wx),引用 Build-in fields(或 user-defined fields)lib 的方法是 $(#wx.lib)。另有一点,带域的全局变量,如 $(#wx.include)、$(#wx.lib) 等,有缺省值。例如不给定 $(#wx.lib) 的值,则默认为 (#wx)\lib。但是,带域的全局变量不能缺省,例如 $(#wx) 本身是不能缺省的,否则会报错。

实际上,也可以在 Code::Blocks 中定义全局定制变量(custom variable),也即打开主菜单 Settings -> Compiler...,Global Compiler settings -> Custom variables,点击 ADD 添加按钮增加变量,供 Global Compiler settings 子选项卡中的各个设定使用。例如设置定制变量(custom variable) VENDOR,引用的方法是 $(VENDOR)。一般而言,定制变量的使用不要太依赖于外部程序。

5、Code::Blocks 的全局变量结合系统环境变量也是一种方法。

由于 Code::Blocks 的全局变量可以引用系统的全局变量,因此可通过系统环境变量配置 wxWidgets 库。例如,在环境变量里添加一个 wxWidgets 根目录环境变量,这里命名为:WXWIN,值为 C:\wxWidgets-3.0.1,例如

> setx WXWIN "C:\wxWidgets-3.0.1"

然后再打开主菜单 Settings -> Global Variables...,在 Global Variables Editor 窗口的 default 变量集下新建立一个 wx 变量,在左下 Build-in fields 的 base 中填入 ${WXWIN},include 中填入 ${WXWIN}\include${WXWIN}\lib\gcc_dll\mswu\wx\include\msw-unicode-3.0.1,lib 中填入 ${WXWIN}\lib\gcc_dll\mswu。由此,可以看到 Code::Blocks 引用系统的环境变量方法类似与 Bash Shell 中的变量引用。

为了与前一种设定方法相比较体现出新意,这里将全局变量用在 wxWidgets 工程向导里。设置完成后,尝试建立 wxWidgets 工程。先通过菜单 File -> New -> Project...,选择最后面的 wxWidgets 工程。点击 Go 进入工程配置向导,首先会出来一个欢迎窗口,在这里直接下一步。在选择已安装的 wxWidgets 版本时请注意,由于安装的 wxWidgets 版本是 3.0.1,所以选择它。接下来的就是 wxWidgets 环境的一些设置了,这里根据刚才设置的 wxWidgets 根目录,直接填入 $(#wx)。剩下几步很容易自行完成。另外,该工程引用的全局变量是来自 default 变量集;如有需要,可在 Setting -> Global Variables... 弹出一个 Global Variable Editor 的窗口定义更多的变量集,同时可在主菜单 Project -> build options,Project build options -> EnvVars options 中选择要使用的变量集。

全局配置 wxWidgets 库的方法谈到这里。前 4 种全局配置方法(第 5 中全局变量配置已针对 Code::Blocks 工程了)对于使用 wxWidgets 库的非工程模式的源代码编译很有帮助。但是可以看到弱点,那就是很多用不到 wxWidgets 库的源代码也在不必要的使用这些编译选项、链接选项,这可能造成错误。因此,有必要说说如何避免全局配置 wxWidgets 库了。首先,对于非工程模式的(特别是单文件)源代码,尽量在命令行环境中而不要在集成开发环境中开发;如果非要在集成开发环境中进行的话,将非工程模式的源代码导入集成开发环境的工程中来。而对于那些由 Code::Blocks 工程管理的使用 wxWidgets 库的源代码,配置使用 wxWidgets 库的方法最好是基于项目的,也即局部配置。此种配置方法只需要使用 Code::Blocks 的工程属性便能实现。

下面仅给出局部配置 wxWidgets 库的一种方法。借鉴前面的全局配置方法,很容易给出更多的局部配置方法。 先启动 Code::Block,打开或者创建一个 wxWidgets 工程,随后点击主菜单 Project -> build options,打开的 Project build options 子标签。

1、设置编译选项。在 Compiler settings -> Other options 子标签页中加入编译选项 -D_LARGEFILE_SOURCE=unknown -DWXUSINGDLL -D__WXMSW__ -mthreads

2、设置链接选项与库文件。在 Linker settings -> Link libraries 中将 wxWidgets 库的所有库文件(动态库或者静态库)加入,也即点击 ADD 按钮后弹出的文件选择对话框中将 C:\wxWidgets-3.0.1\lib\gcc_dll\mswu\ 目录下的文件全选一次性加入;在 Linker settings -> Other Linker options 子标签页中加入链接选项 -Wl,--subsystem,windows -mwindows

3、设置搜索目录。在 Search directories -> Compiler 子标签中加入 wxWidgets 头文件的路径:C:\wxWidgets-3.0.1\includeC:\wxWidgets-3.0.1\lib\gcc_dll\mswu\wx\include\msw-unicode-3.0.1;若上一步的 Link libraries 已带有绝对路径,就不必设库文件的搜索路径了。不然还需在 Search directories -> Linker 子标签中加入库文件路径:C:\wxWidgets-3.0.1\lib\gcc_dll\mswu\。 同样地,也可以在工程属性中定义定制变量,也即 Project -> build options,Project build options -> Custom variables,点击 ADD 添加按钮增加变量,供 Project build options 的子选项卡中的各个设定使用。例如定义工程相关的定制变量 RELEASE,引用的方法是 $(RELEASE)。

五、编译 Code::Blocks 源代码

源码编译 Code::Blocks,也有两种方案:稳定版源码编译以及开发版源码编译。值得提到的是三点:一、不借助 MSYS 直接在 Windows 平台上编译 Code::Blocks 源代码,最好使用 Code::Blocks 二进制文件来完成源代码的编译,这就是所谓的自举;二、用 Code::Blocks 编译 Code::Blocks 源代码,需要使用 Unicode 字符集 Release 版的 wxWidgets 库,除非修改 cbp 工程文件;三、用 Code::Blocks 编译 Code::Blocks 源代码,还需确保 zip.exe 在环境变量 PATH 指定的路径中,最好还能保证 svn.exe 也在其中。

先来说说稳定版源码的编译过程,先到 Code::Blocks 的官网下载最新的稳定版源码,当前是 codeblocks_13.12-1.tar.gz,将它下载后解压到 C:\CBSC。现在用 Code::Blocks 打开 C:\CBSC\src\ 目录中的 CodeBlocks_wx30.cbp(根据 wxWidgets 库的版本以及 CPU 的类型选择),打开过程中会弹出两次 Global Variable Editor 窗口;第一次弹出的 Global Variable Editor 窗口中 Current Variable 项中的内容应该是 wx,在左下 Built-in fields 的 base 中填写 wxWidgets 库所在目录,例如 C:\wxWidgets-3.0.1,然后点 Close 按钮;第二次弹出的 Global Variable Editor 窗口中 Current Variable 项中的内容应该是 cb_release_type,左下 Built-in fields 的 base 中要填写 Code::Blocks 的编译版本类型,若是 Debug 版,请填写 -g,若是发布版本,请填写 -O2,一般选择 Debug 版本,然后点 Close 按钮;随后点主菜单上的 Build 开始编译 Code::Blocks。如果一切正常,会在 C:\CBSC\src\devel30 目录中出现编译好的 codeblocks.exe,试试运行它。如果没有发现任何问题,修改 cb_release_type 的值为 -O2,再次编译,并在命令行窗口中执行

> cd C:\CBSC\src
> update30.bat

这样会在 C:\CBSVN\src\output30 目录中出现正式发布的 codeblocks.exe。为了 Code::Blocks 运行不受动态库版本与路径的干扰,可复制 wxWidgets 库文件至该目录。当然,刚刚编译好的 Code::Blocks 是没有插件的,不过编译插件的步骤大同小异。用 Code::Blocks 打开 C:\CBSC\src 目录下的 ContribPlugins.workspace,根据需要选择要编译的插件项目,过程与 Code::Blocks 编译相仿。

若想编译最新的开发版代码,用 TortoiseSVN 或者在它的可执行文件路径下可执行

> cd C:\
> svn co svn://svn.code.sf.net/p/codeblocks/code/trunk CBSVN

从 SVN 版本库编译可在 MinGW Shell 中执行下述命令

$ cd /c/CBSVN
$ ./bootsrap
$ mkdir mswbuild && cd mswbuid
$ ../configure --with-contrib-plugins=all
$ make

需要注意这是以 Linux 的方式编译 Code::Blocks,一定要保证 wxWidgets 库已被安装到 MSYS 基本系统,且 wx-config 有正常输出。

对于没有 MSYS 基本系统的用户,可在 MS Windows 命令行窗口(cmd.exe)中设置环境变量

> cd C:\CBSVN\src
> set CB_ROOT=C:\CodeBlocks\
> set GCC_ROOT=C:\MinGW\bin\

主要是指定二进制版 Code::Blocks 的安装位置以及 GCC 编译器(这里是指 MingW)的可执行文件目录位置。接着执行

> batch_build_core_30.bat

之后与前面一样会两次弹出 Global Variable Editor 窗口;第一次弹出的 Global Variable Editor 窗口中 Current Variable 项中的内容应该是 wx,在左下 Built-in fields 的 base 中填写 wxWidgets 库所在目录,例如 C:\wxWidgets-3.0.1,然后点 Close 按钮;第二次弹出的 Global Variable Editor 窗口中 Current Variable 项中的内容应该是 cb_release_type,左下 Built-in fields 的 base 中要填写 Code::Blocks 的编译版本类型,填写 -g,然后点 Close 按钮;随后点主菜单上的 Build 开始编译 Code::Blocks。如果一切正常,会在 C:\CBSVN\src\devel30 目录中出现编译好的 codeblocks.exe,试试运行它。如果没有发现任何问题,那么可执行

> update30.bat

这样会在 C:\CBSVN\src\output30 目录中出现正式发布的 codeblocks.exe。当然,编译好的 Code::Blocks 是没有 contrib 插件的,要编译插件编译只需接着执行

> batch_build_all_30.bat
> update30.bat

即可。

若想发布刚刚编译好的 Code::Blocks,可以采用 zip 包的形式。当然,对于普通用户来说还是为它提供一个打包的安装程序比较合适。MS Windows 上收费的打包安装程序有很多,例如 Visual Studio 自带的安装程序、InstallShield 等;也有很多免费的打包安装程序,例如 NSIS 或者 Inno Setup 等。根据自己的需要选用它们,官方推荐使用的是 NSIS 脚本,如想使用可用下述方式检出

> svn co svn://svn.code.sf.net/p/codeblocks/code/setup

自行修改其中的 setup.nsi 脚本。

附录

MS Windows 中动态链接库(dll)的搜索顺序

MS Windows 系统可以包含同一个动态链接库(dll) 的多个版本。应用程序能够通过使用动态链接库重定向或清单文件指定要加载的 DLL 的全路径。下面谈谈具体的动态库搜选顺序,详细内容请参考 http://msdn2.microsoft.com/en-us/library/ms682586.aspx

一、标准的搜索顺序

DLL 的搜索顺序取决于是否安全 DLL 搜索模式是启用或禁用。安全 DLL 搜索模式在默认状态下是启用的。通过创建 HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode 注册表项并将它的值设为0可以关闭这个属性。直到 Windows XP 与 Windows 2000 with SP4,安全 DLL 搜索模式在默认状态下是禁用的。通过创建 HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode 注册表项并将它的值设为1可以启用这个属性。

假如安全 DLL 搜索模式启用,搜索顺序如下:

  • 应用程序所在的路径;
  • Windows SYSTEM 目录。通过调用 GetSystemDirectory 函数可以获取这个目录的路径;
  • 16 位系统的目录。并没有函数可以获取这个目录的路径,但是它会被查找;
  • Windows 目录。通过调用 GetWindowsDirectory 函数可以获取这个目录的路径;
  • 当前目录;
  • PATH 环境变量指定的路径。请注意,这并不包括每个应用程序的应用程序路径注册表项中指定。在应用程序路径注册表项的键值并不作为 DLL 的搜索路径。

假如安全 DLL 搜索模式禁用,搜索顺序如下:

  • 应用程序所在的路径;
  • 当前目录;
  • Windows SYSTEM 目录。通过调用 GetSystemDirectory 函数可以获取这个目录的路径;
  • 16 位系统的目录。并没有函数可以获取这个目录的路径,但是它会被查找;
  • Windows 目录。通过调用 GetWindowsDirectory 函数可以获取这个目录的路径;
  • PATH 环境变量指定的路径。请注意,这并不包括每个应用程序的应用程序路径注册表项中指定。在应用程序路径注册表项的键值并不作为 DLL 的搜索路径。

二、预备的搜索顺序

由系统指定的标准搜索顺序可以通过调用 LoadLibraryEx 函数加上 LOAD_WITH_ALTERED_SEARCH_PATH 参数值得到改变。标准搜索顺序也可以通过调用 SetDllDirectory 函数得到改变。如果您指定一个备用的搜索顺序,程序将按备用的搜索顺序进行搜索,直到所有相关的可执行模块被找到。系统启动后,DLL 初始化例程处理,该系统将恢复为标准的搜索顺序。

LoadLibraryEx 函数通过指定 LOAD_WITH_ALTERED_SEARCH_PATH 属性和 lpFileName 参数指定一个绝对路径支持一个预备的搜索顺序。请注意:标准搜索顺序和通过调用指定 LOAD_WITH_ALTERED_SEARCH_PATH 属性的 LoadLibraryEx 函数来设置的预备搜索顺序只是有一点不同:标准搜索顺序开始于搜索应用程序所在的路径而预备搜索顺序开始于 LoadLibraryEx 函数所要加载的可执行模块的所在目录。

假如安全 DLL 搜索模式启用,搜索顺序如下:

  • lpFileName 参数值所指定的目录;
  • Windows SYSTEM 目录。通过调用 GetSystemDirectory 函数可以获取这个目录的路径;
  • 16 位系统的目录。并没有函数可以获取这个目录的路径,但是它会被查找;
  • Windows 目录。通过调用 GetWindowsDirectory 函数可以获取这个目录的路径;
  • 当前目录;
  • PATH 环境变量指定的路径。请注意,这并不包括每个应用程序的应用程序路径注册表项中指定。在应用程序路径注册表项的键值并不作为DLL的搜索路径。

假如安全 DLL 搜索模式禁用,搜索顺序如下:

  • lpFileName 参数值所指定的目录;
  • 当前目录;
  • Windows SYSTEM 目录。通过调用 GetSystemDirectory 函数可以获取这个目录的路径;
  • 16 位系统的目录。并没有函数可以获取这个目录的路径,但是它会被查找;
  • Windows 目录。通过调用 GetWindowsDirectory 函数可以获取这个目录的路径;
  • PATH 环境变量指定的路径。请注意,这并不包括每个应用程序的应用程序路径注册表项中指定。在应用程序路径注册表项的键值并不作为DLL的搜索路径。

假如 lpPathName 参数指定了一个路径,SetDllDirectory 函数支持一个预备的搜索顺序。这个预备的搜索顺序如下:

  • 应用程序所在的路径;
  • lpFileName 参数值所指定的目录;
  • Windows SYSTEM 目录。通过调用 GetSystemDirectory 函数可以获取这个目录的路径;
  • 16 位系统的目录。并没有函数可以获取这个目录的路径,但是它会被查找;
  • Windows 目录。通过调用 GetWindowsDirectory 函数可以获取这个目录的路径;
  • PATH 环境变量指定的路径。请注意,这并不包括每个应用程序的应用程序路径注册表项中指定。在应用程序路径注册表项的键值并不作为DLL的搜索路径。

如果 lpPathName 参数为一个空字符串,当前目录将会从搜索顺序中删除。

SetDllDirectory 有效地禁用安全 DLL 搜索模式,而在搜索指定的目录路径。要恢复安全 DLL 搜索模式的 SafeDllSearchMode 注册表值的基础和恢复当前目录到搜索顺序,调用 lpPathName 的参数值为 NULL 的 SetDllDirectory 函数。