# 编译与安装

[TOC]

## 概述

GmSSL当前版本采用CMake构建系统。由于CMake是一个跨平台的编译、安装工具，因此GmSSL可以在大多数主流操作系统上编译、安装和运行。GmSSL项目官方测试了Windows (包括Visual Studio和Cygwin)、Linux、macOS、Android和iOS这几个主流操作系统上的编译，并通过GitHub的CI工作流对提交的最新代码进行自动化的编译测试。

和其他基于CMake的开源项目类似，GmSSL的构建过程主要包含配置、编译、测试、安装这几个步骤。默认配置会生成动态库；如果需要静态库，可以在配置阶段设置`-DBUILD_SHARED_LIBS=OFF`。

### Linux/macOS源码安装

在Linux或macOS环境下，下载并解压GmSSL源代码后，进入源码目录，执行如下命令：

```bash
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build .
ctest --output-on-failure
sudo cmake --install .
```

就可以完成配置、编译、测试和安装。

在执行`cmake --build .`编译成功后，在`build/bin`目录下会生成项目的可执行文件和库文件。对于密码工具来说，在安装使用之前通过`ctest --output-on-failure`进行测试是重要的一步，如果测试失败，那么不应该使用这个软件。在发生某个测试错误后，可以执行`build/bin`下的具体某个测试命令行，如`sm4test`，这样可以看到具体的错误打印信息。

执行`sudo cmake --install .`，安装完成后，可以命令行中调用`gmssl`命令行工具。在Linux和macOS环境下，头文件通常被安装在`/usr/local/include/gmssl`目录下，库文件被安装在`/usr/local/lib`目录下。Linux默认动态库名称为`libgmssl.so`，macOS默认动态库名称为`libgmssl.dylib`。

### macOS二进制包的Gatekeeper提示

GmSSL发布的macOS二进制包当前未进行Apple Developer ID签名和notarization。从浏览器下载并解压后，macOS可能会为文件添加`com.apple.quarantine`属性，运行`gmssl`时提示Apple无法验证该程序是否不含恶意软件。

如果确认二进制包来自GmSSL GitHub Release页面，可以在解压后的目录上执行：

```bash
xattr -dr com.apple.quarantine GmSSL-3.2.0-macOS-arm64
```

Intel x86_64版本对应执行：

```bash
xattr -dr com.apple.quarantine GmSSL-3.2.0-macOS-x86_64
```

然后再运行：

```bash
./GmSSL-3.2.0-macOS-arm64/bin/gmssl version
```

或者将对应`bin`目录加入`PATH`。这个处理方式只移除下载隔离标记，不修改`gmssl`二进制内容。

在Linux上安装动态库时，安装程序不会自动修改`/etc/ld.so.conf.d`或执行`ldconfig`。如果安装后系统找不到`libgmssl.so`，可以临时设置动态库搜索路径：

```bash
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
```

也可以配置系统级动态库搜索路径：

```bash
echo /usr/local/lib | sudo tee /etc/ld.so.conf.d/gmssl.conf
sudo ldconfig
```

如果不希望安装到系统目录，可以指定安装前缀：

```bash
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/local/gmssl
cmake --build .
ctest --output-on-failure
cmake --install .
```

### Windows源码安装

在Windows上建议使用Visual Studio 2022或更新版本。在"Developer Command Prompt for VS 2022"中执行：

```bat
mkdir build
cd build
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
nmake
ctest --output-on-failure
nmake install
```

默认安装目录通常为`C:\Program Files\GmSSL`，命令行工具、头文件和库文件分别安装到`bin`、`include`和`lib`目录。普通用户执行安装可能会因为权限不足失败，需要以管理员身份打开开发者命令提示符，或者通过`-DCMAKE_INSTALL_PREFIX=C:\path\to\gmssl`指定用户可写的目录。

## 项目源代码

GmSSL项目的源代码在GitHub中发布和维护。

项目在GitHub的主页为：https://github.com/guanzhi/GmSSL

源代码包含主分支的最新代码和定期发布的Release版本，建议优先采用主分支最新版。

### 通过CI判断当前代码状态

有时候最新提交的代码可能存在编译错误，通常这些错误会在1-2天内被新的提交修复。如果当前最新代码还没有修复，那么可以通过GitHub的CI状态来选择没有错误的代码。

通过GitHub的CI工作流状态可以判断某次提交是否存在编译错误，目前GmSSL项目中配置了如下编译环境：

* CMake ubuntu-latest
* CMake windows-latest
* CMake macos-latest
* CMake-Android
* CMake-iOS
* CMake-OpenHarmony
* MSVC CMake

通过查看这些CI的状态，可以判断当前代码是否可以在对应操作系统上成功编译。如果当前最新代码无法在某个平台上编译，那么可以选择之前某个通过测试的Commit版本。

## 配置编译选项

在执行`cmake`阶段可以对项目的默认编译配置进行修改，修改是通过设置CMake变量来完成的，可以查看项目源代码中的`CMakeLists.txt`中所有的`option`指令来查看可选的配置。例如：

```cmake
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
```

表明项目默认生成动态库。

### 设置生成动态库或静态库

GmSSL的CMake默认生成动态库，可以通过设定CMake变量`BUILD_SHARED_LIBS`为`ON`或者`OFF`来指定生成动态库或静态库。

```
cmake .. -DBUILD_SHARED_LIBS=ON
cmake .. -DBUILD_SHARED_LIBS=OFF
```

### 设置优化的密码算法实现

GmSSL包含了针对特定硬件和处理指令集的密码算法优化实现，如针对Intel AVX2、AES-NI、ARMv8/AArch64和OpenCL等平台的优化。这些优化实现在匹配的处理器上的实现速度或安全性会大大超过默认的C语言实现。

在配置阶段可以显式地指定采用优化实现，可选的CMake配置变量包括：

* `ENABLE_SM2_ARM64` SM2算法的ARMv8汇编实现。
* `ENABLE_SM2_AMD64` SM2算法的x86_64汇编实现。
* `ENABLE_SM3_ARM64` SM3算法的ARM NEON实现。
* `ENABLE_SM3_SSE` SM3算法的SSE实现。
* `ENABLE_SM4_ARM64` SM4算法的AArch64汇编实现。
* `ENABLE_SM4_CE` SM4算法的ARMv8 Cryptography Extensions实现。
* `ENABLE_SM4_AVX2` SM4算法的AVX2并行实现。
* `ENABLE_SM4_AESNI` SM4算法的AES-NI实现。
* `ENABLE_SM4_CTR_AESNI_AVX` SM4 CTR模式的AES-NI + AVX实现。
* `ENABLE_GMUL_ARM64` GF(2^128)乘法的AArch64实现。
* `ENABLE_INTEL_RDRAND` 基于Intel RDRAND指令的硬件随机数生成器。
* `ENABLE_INTEL_RDSEED` 基于Intel RDSEED指令的硬件随机数生成器。
* `ENABLE_SM4_CL` SM4算法的OpenCL实现。

### 裁剪可选算法和模块

GmSSL的部分算法、协议和硬件接口可以在配置阶段关闭，以减少二进制体积或满足特定部署要求。例如：

```bash
cmake .. -DENABLE_SHA1=OFF -DENABLE_AES=OFF -DENABLE_SKF=OFF -DENABLE_SDF=OFF
cmake --build .
```

可选开关以`CMakeLists.txt`中的`option(...)`定义为准。关闭底层算法可能会影响依赖它的协议、工具和测试用例。

## 在Visual Studio环境中编译

CMake支持通过指定不同的构建系统生成器（Generator），生成不同类型的Makefile。在Windows和Visual Studio环境下，CMake即可以生成常规的Visual Studio解决方案(.sln)文件，在Visual Studio图形界面中完成编译，也可以生成类似于Linux环境下的Makefile文件，在命令行环境下完成编译和测试。

### 生成Makefile编译

在安装完Visual Studio之后，在启动菜单栏中会出现Visual Studio菜单目录，其中包含x64 Native Tools Command Prompt for VS 2022等多个终端命令行环境菜单项。

```bash
C:\Program Files\Microsoft Visual Studio\2022\Community>cd /path/to/gmssl
mkdir build
cd build
cmake .. -G "NMake Makefiles"
nmake
ctest --output-on-failure
```

在编译完成后直接执行安装会报权限错误，这是因为安装过程需要向系统目录中写入文件，而当前打开命令行环境的用户不具备该权限。可以通过右键选择“更多-以管理员身份运行”打开x64 Native Tools Command Prompt for VS 2022终端，执行

```
nmake install
```

那么`gmssl`命令行程序、头文件和库文件分别被写入`C:/Program Files/GmSSL/bin`、`C:/Program Files/GmSSL/include`、`C:/Program Files/GmSSL/lib`这几个系统目录中。为了能够直接在命令行环境任意目录下执行`gmssl`命令行程序，需要将其安装目录加入到系统路径中，可以执行：

```bash
set path=%path%;C:\Program Files\GmSSL\bin
```

设置完毕后可以在命令行中执行`path`，查看新的路径是否已经成功加入。

### 在Visual Studio图形界面中编译

在安装完Visual Studio之后，在启动菜单栏中会出现Visual Studio菜单目录，其中包含x64 Native Tools Command Prompt for VS 2022等多个终端命令行环境菜单项。

```bash
C:\Program Files\Microsoft Visual Studio\2022\Community>cd /path/to/gmssl
mkdir build
cd build
cmake ..
```

完成后可以看到CMake在`build`目录下生成了一个`GmSSL.sln`文件和大量的`.vcxproj`文件。

点击`GmSSL.sln`就打开Visual Studio，点击Visual Studio工具栏上的"本地Windows调试器"按钮，可以启动编译。

在Visual Studio界面中可以选择Debug、Release、MinSizeRel等不同配置。

### 在Visual Studio中运行测试

在解决方案资源管理器中找到`RUN_TESTS`项目，右键菜单选择"调试-启动新实例"，即可运行测试，并且在”输出“窗口中看到测试结果。测试完成后会出现RUN_TESTS拒绝访问的对话框。

### 选择生成32位或64位程序

通过在Visual Studio不同的命令行环境中编译GmSSL，可以生成32位的X86或者64位的X86_64程序，在x64 Native Tools Command Prompt for VS 2022命令行环境下，生成的是64位的程序，在x86 Native Tools Command Prompt for VS 2022命令行环境下，生成的是32位的程序。

可以通过Windows操作系统内置的资源管理器来检查编译生成的可执行程序是32位还是64位，在资源管理器的CPU页面中，通过“选择列”增加“平台”列，这样就可以显示每个进程的是32位或64位。可以运行`gmssl tlcp_client`或者在某个测试文件中增加循环时间来保持命令行运行一段时间。

## 在Cygwin环境中编译

Cygwin是Windows上的Linux模拟运行环境。Cygwin提供了Linux Shell和大量Linux命令行工具，也提供了应用程序开发必须的编译工具、头文件和库文件。面向Linux开发的应用通常依赖`unistd.h`、`sys/socket.h`等头文件及函数，但是Visual Studio的C库并没有提供这些POSIX函数实现，因此这些Linux应用没有办法直接在Windows环境下编译。Cygwin通过封装Windows操作系统原生功能，提供了一个POSIX接口层，以及封装这些功能的动态库(`cygwin1.dll`)，并且提供了GCC、CMake等完整的Linux编译工具链，这意味着标准Linux环境下的头文件都存在，并且代码中依赖GCC编译器的特殊语法都可以被编译器识别（Visual Studio的`cl`编译器不能完整支持C99语法），因此标准的Linux应用都可以通过Cygwin移植到Windows环境，编译为Windows本地应用。Cygwin提供的Linux Shell环境意味着Shell脚本也是可以使用的。

在Cygwin环境下编译生成的可执行程序是原生的Windows程序，和Visual Studio编译的程序的主要区别在于，Cygwin下编译的程序都必须依赖`cygwin1.dll`这个动态库，因为应用所有的POSIX函数调用都需要通过这个动态库翻译为Windows本地的系统调用（如WinSock2），因此发布Cygwin的程序不太方便，必须要包含一个较大的`cygwin1.dll`库文件。另外如果应用涉及大量的系统调用，那么通过Cygwin中间层会引入一定的开销，理论上会比Visual Studio编译的应用效率略低。

总的来说，如果你想在Windows环境下快速尝试一下GmSSL的命令行功能，并且可能需要利用Linux Shell环境下的一些常用工具做实验和测试，或者不太熟悉Visual Studio开发环境，那么采用Cygwin环境是一个非常方便的选择。

### 准备Cygwin环境

Cygwin的安装、配置都是通过一个单一的`setup-x86_64.exe`应用程序完成的。在Cygwin的官网 https://www.cygwin.com/ 可以下载这个应用程序。

注意，在首次安装的时候可能没有选择所有需要的程序，再次运行`setup-x86_64.exe`程序可以对环境进行配置和更新。有些工具，例如CMake，官方提供了独立的Windows安装包，在Cygwin环境下没有必要独立安装这些工具，也不建议安装，所有依赖的Linux工具都应该通过Cygwin环境来配置管理。

在安装、配置完成之后，可以通过运行`Cygwin64 Terminal`应用，打开一个命令行环境。

### 在Cygwin环境中编译GmSSL

Cygwin环境相对标准的Linux环境有一些细微的差别。首先，在Cygwin命令行环境中，文件系统是一个类似Linux文件系统结构的独立目录，如果源代码已经下载到Windows操作系统中（比如，下载到用户的Download目录），那么需要首先将源代码拷贝到Cygwin文件系统的用户目录中（例如当前用户默认目录`~`）。在Cygwin文件系统中，Windows文件系统被映射到`/cygdrive`目录中，Windows当前用户Guan Zhi的下载目录中的`GmSSL-master.zip`文件就被映射到`/cygdrive/c/Users/Guan Zhi/Downloads/GmSSL-master.zip`中。

```bash
cp "/cygdrive/c/Users/Guan Zhi/Downloads/GmSSL-master.zip" ~/
```

然后可以按照Linux环境下相似的过程编译、安装

```bash
unzip GmSSL-master.zip
cd GmSSL-master
mkdir build
cd build
cmake ..
cmake --build .
ctest --output-on-failure
cmake --install .
```

注意，由于在Cygwin环境中用户本身具有系统权限，因此在执行`cmake --install .`时不需要`sudo`。

在安装完成之后，可以在Cygwin的命令行环境下执行`gmssl`命令行，或者运行源代码`demo`目录下的演示脚本。

注意，将`gmssl`等可执行程序直接从Cygwin目录拷贝到Windows文件系统下，在执行时会提示找不到`cygwin1.dll`的错误，运行或者发布可执行程序时，应处理好对这个动态库的依赖问题。

### 存在的问题

Cygwin的动态库名称比较特殊，是以`cyg`开头的。使用Cygwin生成的可执行程序和动态库通常依赖`cygwin1.dll`，发布给非Cygwin环境使用时需要额外处理这个运行时依赖。

## 面向iOS/iPhoneOS的交叉编译

下载 https://github.com/leetal/ios-cmake ，将`ios.toolchain.cmake`文件复制到`build`目录。

```bash
mkdir build; cd build
cmake .. -G Xcode -DCMAKE_TOOLCHAIN_FILE=../ios.toolchain.cmake -DPLATFORM=OS64
cmake --build . --config Release
```

如果出现“error: Signing for "gmssl" requires a development team.”错误，可以用Xcode打开工程文件，在Signing配置中设置Development Team。

## 面向Android的交叉编译

下载Android NDK，执行

```bash
mkdir build; cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake  -DANDROID_ABI=arm64-v8a  -DANDROID_PLATFORM=android-23
cmake --build .
```

## 安装包构建

依赖cmake工具包中的cpack工具，生成可发布的安装包。

生成的安装包在`build`目录下。

### 构建DEB安装包

```
mkdir build; cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build .
cpack -G DEB
```

### 构建RPM安装包

```
mkdir build; cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build .
cpack -G RPM
```

### 构建`.sh`安装脚本

```
mkdir build; cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build .
cpack -G STGZ
```

## 生成二进制包

为了保证兼容性，发布的二进制包不包含针对特定指令集的优化代码，并且不启用编译器的`-O3`优化。

在正式发布之前，需要在测试平台上编译、测试、安装。验证`gmssl`命令行可以正确使用，验证`sm3_demo.c`可以正确和`-lgmssl`编译，并且可以正确输出哈希值。

完成编译和测试后，在源码目录下执行如下操作。二进制包需要同时提供动态库和静态库，因此使用两个构建目录：先安装动态库，再安装静态库到同一个打包目录。第二次安装会覆盖`bin/gmssl`，使发布包中的命令行工具链接静态`libgmssl`，同时`lib`目录中保留动态库和静态库。

```bash
#!/bin/bash -x
VERSION=3.2.0
OS=macos
ARCH=arm64

PREFIX="$PWD/gmssl-$VERSION"
rm -rf "$PREFIX" build-shared build-static

cmake -S . -B build-shared -DCMAKE_BUILD_TYPE=Release \
	-DBUILD_SHARED_LIBS=ON \
	-DCMAKE_INSTALL_PREFIX="$PREFIX"
cmake --build build-shared
ctest --test-dir build-shared --output-on-failure
cmake --install build-shared

cmake -S . -B build-static -DCMAKE_BUILD_TYPE=Release \
	-DBUILD_SHARED_LIBS=OFF \
	-DCMAKE_INSTALL_PREFIX="$PREFIX"
cmake --build build-static
ctest --test-dir build-static --output-on-failure
cmake --install build-static

tar czvf gmssl-$VERSION-$OS-$ARCH.tar.gz gmssl-$VERSION
```

发布包目录结构如下：

```bash
gmssl-$VERSION/
  bin/
    gmssl
  include/
    gmssl/
  lib/
    libgmssl.a
    libgmssl.so      # Linux动态库
    libgmssl.dylib   # macOS动态库
```

其中静态库和动态库的具体文件名会随操作系统变化。例如Linux通常生成`libgmssl.so`及其版本符号链接，macOS通常生成`libgmssl.dylib`及其版本符号链接，Windows通常生成`.dll`和导入库。打包前需要在目标平台上确认`bin/gmssl version`可以运行，并确认一个外部C程序可以通过`-I<package>/include`和`-L<package>/lib -lgmssl`完成编译和链接。
