curl android 编译,跨平台:libcurl+openssl编译(Mac、Android、Windows、MD/MT模式、XP)...

2023-11-12 01:59

本文主要是介绍curl android 编译,跨平台:libcurl+openssl编译(Mac、Android、Windows、MD/MT模式、XP)...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前提概要

众所周知,http/https是当下开发应用程序时,网路部分不可或缺的部分,我们可以基于socket自己来实现,因为http/https本身是基于TCP实现的应用层协议(位于网络模型的第7层)。但随着行业的发展,https加密、业内非标准http协议的推广(CDN非标准协议)等这些部分,都需要耗费大量的开发成本,基于socket自己实现http/https的方案,成本上已经难以接受,选择开源的成熟方案是当下业内的共识。而curl是http/https最成熟的开源方案,其兼顾稳定性和易用性、跨平台性,是作为底层库的首选。当然其他一体化底层解决方案也是不错的选择,例如Mars(微信开源框架)、Qt等,这里我们仅在单一http/https方案这一选择中来做探讨。

curl虽然易于使用,但在各平台编译上,有不少晦涩难懂的地方,也是它对于使用者来说的障碍,这篇文章旨在消除这些障碍,拨开云雾,一站式解决各平台编译问题,从而将大家宝贵的精力从中抽出,用在更有价值的事情上。

参考资料

基本脉络这里是我个人理解下来,需要大家提前搞懂的几个点,整理出脉络图,以便理解。和代码阅读类似,我们先观其行,然后再达其意,有利于各个击破,如果能接触到一两个有意思的技术历史,那也不失为过程中的风景,本篇文章也会按照各平台来逐一介绍和阐述。

503833b384d8

整体工程

请务必下载下来如下链接中整体curl编译工程,然后再针对性阅读后续介绍

针对整体工程,我们分如下几部分做介绍:1.mac/ios编译

2.windows编译

3.android编译

4.openssl多线程安全

工程目录结构如下:

503833b384d8

一、Mac编译

主要参考curl官方文档:iOS的编译,这里我稍作说明,由于个人精力的缘故,没有完整实践过,之前看官方文档的时候,大致看到方法应该是类似的,由于移动平台CPU多种多样,这里是否有编译上的差异,我还未做考证。总之,再麻烦总不会麻烦过Android(文章后半段大家会感觉到这一点),请大家阅读好官方文档,我们要做的大多数情况下只是保持好正确的坐姿,设置好编译参数,然后正确调用编译命令。

注意事项

需要更新到xcode9.4.1以上版本,curl-7.63.0版本在xcode9.2.1版本编译会报如下错误:Undefined symbols for architecture x86_64:

“_SSLCopyALPNProtocols”, referenced from:

_darwinssl_connect_step2 in libcurl.a(libcurl_la-darwinssl.o)

“_SSLSetALPNProtocols”, referenced from:

_darwinssl_connect_common in libcurl.a(libcurl_la-darwinssl.o)

ld: symbol(s) not found for architecture x86_64

clang: error: linker command failed with exit code 1 (use -v to see invocation)

make[2]:[curl] Error 1

make[1]:[install-recursive] Error 1

make: [install-recursive] Error 1

编译脚本build/libcurl/build_for_mac.sh执行的步骤:1.解压源码:curl-7.63.0.tar.gz

2.编译libcurl

关键代码(限于篇幅只贴出部分脚本):其中current_path是当前脚本执行路径,是编译输出路径,也可以配置为自定义的输出路径export MACOSX_DEPLOYMENT_TARGET="10.6"

# buid configure

./buildconf

./configure --prefix=$current_path/out \

--disable-shared \

--enable-static \

--with-darwinssl \

--enable-threaded-resolver \

--disable-ldap \

--disable-ldaps

# workaround still works though: make CFLAGS=-Wno-error for buid bug before v7.55.1

# the build error is:connectx' is only available on macOS 10.11 or newer

#make CFLAGS=-Wno-error

make

# install

make install

在win/linux/android下使用openssl,在mac/ios下用apple体系下ssl实现(Apple's SSL/TLS implementation)通过指定编译参数来指明:--with-darwinssl, remark: it is not necessary to use the option --without-ssl

二、Windows编译

windows上libcurl的编译,可以参考libcurl源码下winbuild/BUILD.WINDOWS.txt

准备环境:2.安装nasm:官网下载http://www.nasm.us,附件中(build/build_depend_tools)也有安装包,并在系统环境变量中添加nasm安装路径(也可以使用附件中包含的批处理文件添加

3.安装python:确认系统环境变量中是否已自动添加python路径,若没有手动添加

1.编译入口

编译脚本build/build_for_win.bat执行步骤:1.设置7zip环境变量,用于解压源码(整体工程压缩包中,带有7zip,curl/7-Zip路径下)

2.编译openssl

3.编译libcurl

4.删除openssl临时生成文件

5.删除libcurl临时文件

关键代码(限于篇幅只贴出部分脚本):@echo off

@set workdir=%cd%

@set sevenzip=%workdir%\7-Zip\7z.exe

:: build openssl

@cd %workdir%\openssl

@call build_for_win.bat %sevenzip%

@cd %workdir%

:: build libcurl

@cd %workdir%\libcurl

@call build_for_win.bat %sevenzip%

@cd %workdir%

:: delete openssl temp files

@if exist %workdir%\openssl\openssl-1.0.2l (rd /s /q "%workdir%\openssl\openssl-1.0.2l") else echo can not find openssl-1.0.2l dir

@if exist %workdir%\openssl\output_lib (rd /s /q "%workdir%\openssl\output_lib") else echo can not find output_lib dir

:: delete libcurl temp files

@if exist %workdir%\libcurl\curl-7.63.0 (rd /s /q "%workdir%\libcurl\curl-7.63.0") else echo can not find curl-7.63 dir

@echo on

2.首先编译openssl

编译脚本build/openssl/build_for_win.bat执行步骤:1.设置VC环境变量(这里使用的是VS2015,可以根据自身需要自定义修改)

2.解压源码:openssl-1.0.2l.tar.gz

3.设置输出目录

4.代码工程相关设置

5.将/MD设置为/MT模式

这一步根据自己工程的需要来做,如果应用程序其他模块都是/MD模式,则不需要执行这一步

另外,由于openssl中没有提供脚本选项来自动生成/MT工程,所以只能替换生成的.mak中对应选项

6.编译

7.同步生成文件到目标路径,脚本中是对应工程kernel的输出路径,这里可以根据自身工程需要修改为自定义路径

8.同步生成的.pdb文件目标路径,脚本中是对应工程kernel的输出路径,这里可以根据自身工程需要修改为自定义路径

由于openssl安装脚本中没有提供pdb文件安装选项,所以这里需要额外从openssl临时生成路径下拷贝出来

关键代码(限于篇幅只贴出部分脚本)::: 3)generate VC project config

@perl configure VC-WIN32 --prefix=%outputlib%

@call ms\do_ms.bat

@call ms\do_nasm.bat

:: 4)replace "/MD" to "/MT" in ms/ntdll.mak

@setlocal enabledelayedexpansion

@set ntdll_mak_file=%currentPath%\openssl-1.0.2l\ms\ntdll.mak

@set ntdll_mak_file_temp=%currentPath%\openssl-1.0.2l\ms\ntdll_temp.mak if exist %ntdll_mak_file_temp% (del %ntdll_mak_file_temp%) else echo create temp file ntdll_temp.mak"

for /f "delims=" %%i in (%ntdll_mak_file%) do (

set str=%%i

set str=!str:/MD=/MT!

echo !str!>>%ntdll_mak_file_temp% )

@move /y "%ntdll_mak_file_temp%" "%ntdll_mak_file%"

@endlocal enabledelayedexpansion

:: 5)build

@nmake -f ms\ntdll.mak

@nmake -f ms\ntdll.mak install @cd %currentPath%

3.然后设置openssl依赖,编译curl

脚本build/libcurl/build_for_win.bat执行步骤:1.设置VC环境变量(这里使用的是VS2015,可以根据自身需要自定义修改)

2.解压源码:curl-7.63.0.tar.gz

3.支持Windows XP版本

VS2010以后,XP系统需要单独设置才能支持,若不需要,可以在curl/build/build_for_win.bat中去掉“@call build_for_win.bat %sevenzip% enable_xp”中enable_xp参数即可

VS2015,推荐使用,兼容性好,工程中curl/build/build_for_win.bat编译脚本中默认开启了XP支持,这种情况下编译脚本自动化不会出错

VS2013,不推荐,curl源码中对自动化编译支持有兼容性问题,打开了XP支持参数后,VS2013需要手动编译才能编译通过,VS2013工程在curl/build/libcurl/curl-7.63.0/project/windows/VC12路径下

4.编译这里使用的是/MT模式,如果需要使用/MD模式,择修改“RTLIBCFG=static” 为 “RTLIBCFG=dll”  RTLIBCFG=static,表示libcurl是/MT  RTLIBCFG=dll,表示libcurl是/MD

5.同步生成文件到目标路径下,脚本中是对应工程kernel的输出路径,这里可以根据自身工程需要修改为自定义路径

关键代码(限于篇幅只贴出部分脚本)::: 2) support Windows XP (add build command into “winBuild/MakefileBuild.vc”)

if "%supportXP%"=="enable_xp”(

echo modify "winbuild/MakefileBuild.vc" to support windows xp

python %currentPath%\build_for_win_support_xp.py %currentPath%\curl-7.63.0\winbuild\MakefileBuild.vc

)

:: 3)build

@cd %currentPath%\curl-7.63.0\winbuild

@nmake /f Makefile.vc WITH_DEVEL=../../../openssl/output_lib mode=dll VC=14 RTLIBCFG=static WITH_SSL=dll GEN_PDB=yes DEBUG=no MACHINE=x86

@cd %currentPath%

:: 4)sync build result to kernel

@set output=%currentPath%\curl-7.63.0\builds\libcurl-vc14-x86-release-dll-ssl-dll-ipv6-sspi

@copy /y "%output%\bin\libcurl.dll" "%currentPath%\..\..\..\..\output\bin"

@copy /y "%output%\lib\libcurl.lib" "%currentPath%\..\..\..\..\output\lib"

@copy /y "%output%\lib\libcurl.pdb" "%currentPath%\..\..\..\..\output\bin"

三、Android编译

1.编译入口

脚本build/build_for_android.sh执行步骤:1.声明环境变量:NDK、NDK根目录、NDK版本、CPU指令架构(arm/x86/…)

2.抽取NDK对应的CPU指令架构工具集(编译时需要用到)

3.编译openssl

4.编译libcurl

5.删除抽取出来的NDK指令架构工具集

6.同步生成文件到目标路径下,脚本中是对应工程kernel的输出路径,这里可以根据自身工程需要修改为自定义路径

NDK工具集抽取:这里首先需要有一些技术上的概念理解,才能做到真正的掌握,技术上无惑于心太重要,毕竟我个人的目的也不仅仅是让大家去使用我提供的编译脚本,否则我没有必要赘述这篇文章了

主机编译:一般来说,大多数可执行程序的编译都是主机编译,例如,windows上VS编译windows程序,mac上xcode编译mac程序,都是主机编译。

交叉编译:与主机编译相对应,在其他系统上编译出目标系统上的可执行程序,例如,目前在Android机器上没有完备的编译开发环境,从而导致只能在其他系统上编译Android应用,这种就是典型的交叉编译。

在NDK之中,根据Android系统的CPU指令架构的不同,包含了能够实现Android应用程序交叉编译的各种工具集,以16b版本的NDK为例,其工具集目录是这样:

android-ndk-r16b

| - toolchains

| - arm-linux-androideabi-4.9

| - aarch64-linux-android-4.9

| - mipsel-linux-android-4.9

| - mips64el-linux-android-4.9

| - renderscript

| - x86-4.9

| - x86_64-4.9

| - llvm

一般来说,跨平台项目的编译,编译脚本中兼容性做得好的话,使用者是不需要关心当前应该使用哪一种交叉编译工具集的,但很可惜,openssl这里需要我们关心这一点,这也是当时我在处理这块时,一开始遇到障碍与困惑的地方,也花了不少时间,直到搞清楚了这些基本技术概念的来龙去脉后,才找到正确的方法。NDK这块,有太多的东西,这里我们需要做到怎样的程度呢,对于这种类似的问题,我个人一般秉持一个原则:“代码的编写,通透到底为好,而工具的使用,则是技术盲区的知识补充到刚好够用即可”,毕竟人的精力有限,兴趣之上就看个人了。最后,言归正传,NDK本身提供了比较完善的工具集抽取命令,我们这里的编译脚本中也是简单调用,而后抽取出来的工具集路径在openssl编译的脚本中正确设置给环境变量即可。

关键代码(限于篇幅只贴出部分脚本):echo 抽取NDK指令集目录

ndk_toolchain_dir="$work_dir/ndk_toolchain"

rm -rf $ndk_toolchain_dir

$ndk_dir/build/tools/make_standalone_toolchain.py --arch $target_cpu --api $ndk_ver --stl gnustl --install-dir=$ndk_toolchain_dir --force

echo 编译openssl

bash $work_dir/openssl/build_for_android.sh $ndk_root $ndk_toolchain_dir $target_cpu

echo 编译libcurl

bash $work_dir/libcurl/build_for_android.sh $ndk_toolchain_dir $work_dir/openssl $target_cpu

echo 删除NDK临时目录$ndk_toolchain_dir

rm -rf $ndk_toolchain_dir

echo 同步libcurl和openssl头文件

cp $work_dir/libcurl/out/$target_cpu/include/curl/*.h $work_dir/../

cp $work_dir/openssl/out/$target_cpu/include/openssl/*.h $work_dir/../openssl

2.首先编译openssl

脚本build/openssl/build_for_android.sh执行步骤:1.设置NDK相关环境变量,内部编译时会用到,

2.编译

关键代码(限于篇幅只贴出部分脚本):arch_target=arch-x86

if [ $target_cpu == "x86" ]; then

arch_target=android-x86

fi

if [ $target_cpu == "arm64" ]; then

arch_target=android-armv7

fi

if [ $target_cpu == "arm" ]; then

arch_target=android-armv7

fi

./Configure $arch_target no-shared no-comp no-hw no-engine --prefix=$ssl_path --openssldir=$ssl_path --sysroot=$CROSS_SYSROOT -D__ANDROID_API__=18 -isystem$ANDROID_SYSTEM

if [ $? != 0 ]; then

exit 1

fi

make depend

if [ $? != 0 ]; then

exit 1

fi

make all

if [ $? != 0 ]; then

exit 1

fi

make install_sw

if [ $? != 0 ]; then

exit 1

fi

3.然后设置openssl依赖后,编译curl

脚本build/libcurl/build_for_android.sh执行步骤:1.解压源码:curl-7.63.0.tar.gz

1.设置NDK工具集目录(入口build/build_for_android.sh编译脚本中抽取的NDK对应的CPU指令架构工具集)

2.设置openssl输出目录(依赖openssl)

3.设置目标机器指令集

4.编译

关键代码(限于篇幅只贴出部分脚本):# 自己的android-toolchain(NDK针对特定配置抽取出来的独立目录)

export ANDROID_HOME=$ndk_toolchain_dir

# openssl的输出目录

export CFLAGS="-isystem$openssl_dir/out/$target_cpu/include"

export LDFLAGS="-L$openssl_dir/out/$target_cpu/lib"

export TOOLCHAIN=$ANDROID_HOME/bin

# 设置目标机器指令集

arch_flags="-march=i686 -msse3 -mstackrealign -mfpmath=sse"

arch=arch-x86

tool_target=i686-linux-android

host_os=i686-linux-android

if [ $target_cpu == "x86" ]; then

arch_flags="-march=i686 -msse3 -mstackrealign -mfpmath=sse"

arch=arch-x86

tool_target=i686-linux-android

host_os=i686-linux-android

fi

if [ $target_cpu == "arm" ]; then

arch_flags="-march=armv7-a -msse3 -mstackrealign -mfpmath=sse"

arch=arch-arm

tool_target=arm-linux-androideabi

host_os=arm-androideabi-linux

fi

if [ $target_cpu == "arm64" ]; then

arch_flags="-march=armv8 -msse3 -mstackrealign -mfpmath=sse"

arch=arch-arm

tool_target=arm-linux-androideabi

host_os=arm-androideabi-linux

fi

echo 当前CPU指令集匹配arch为"$arch",arch_flags为$arch_flags

export TOOL=$tool_target

export ARCH_FLAGS=$arch_flags

export ARCH=$arch

export CC=$TOOLCHAIN/$TOOL-gcc

export CXX=$TOOLCHAIN/${TOOL}-g++

export LINK=${CXX}

export LD=$TOOLCHAIN/${TOOL}-ld

export AR=$TOOLCHAIN/${TOOL}-ar

export RANLIB=$TOOLCHAIN/${TOOL}-ranlib

export STRIP=$TOOLCHAIN/${TOOL}-strip

export CPPFLAGS="-DANDROID -D__ANDROID_API__=18"

export LIBS="-lssl -lcrypto"

export CROSS_SYSROOT=$TOOLCHAIN/sysroot

cd $source_dir

./configure --prefix=$current_path/out/$target_cpu \

--exec-prefix=$current_path/out/$target_cpu \

--bindir=$TOOLCHAIN \

--sbindir=$TOOLCHAIN \

--libexecdir=$TOOLCHAIN \

--with-sysroot=$CROSS_SYSROOT \

--host=$host_os \

--enable-ipv6 \

--enable-threaded-resolver \

--disable-dict \

--disable-gopher \

--disable-ldap \

--disable-ldaps \

--disable-manual \

--disable-pop3 \

--disable-smtp \

--disable-imap \

--disable-rtsp \

--disable-smb \

--disable-telnet \

--disable-verbose

make install

四、openssl多线程安全openssl在多线程这块,有些历史因素,导致不同版本应用层需要做的事情不一样:

1.v1.0.2和之前的版本,多线程安全需要应用层自己实现,在openssl/crypto.h中有预留实现接口位置

文档中示例程序th-lock.c有做说明

2.v1.1.0版本之后,多线程安全加锁的实现,从运行时转到了编译期间

编译时启用多线程安全参数,则openssl会将各平台加锁实现打包进来

我们这里使用的版本是v1.0.2l版本,所以多线程安全部分需要应用层自己实现,虽然如此,openssl其实已经做了很多,我们只需要实现特定的几个位置的代码即可,具体代码如下:

加锁实现部分:#include "openssl/crypto.h"

#include "openssl/err.h"

#if defined(WIN32)

#define MUTEX_TYPE HANDLE

#define MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL)

#define MUTEX_CLEANUP(x) CloseHandle(x)

#define MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE)

#define MUTEX_UNLOCK(x) ReleaseMutex(x)

#define THREAD_ID GetCurrentThreadId()

#else

#include

#define MUTEX_TYPE pthread_mutex_t

#define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL)

#define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))

#define MUTEX_LOCK(x) pthread_mutex_lock(&(x))

#define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x))

#define THREAD_ID pthread_self()

#endif

static MUTEX_TYPE *mutexArray = NULL;

static int32_t nNumLocks = 0;

static void locking_function(int mode, int n, const char * file, int line)

{

if (n >= nNumLocks) {

return;

}

if (mode & CRYPTO_LOCK) {

MUTEX_LOCK(mutexArray[n]);

} else {

MUTEX_UNLOCK(mutexArray[n]);

}

}

static unsigned long threadId_function(void)

{

return ((unsigned long)THREAD_ID);

}

namespace OpenSSLThreadLock

{

void OpenSSLLock_Setup(void)

{

nNumLocks = CRYPTO_num_locks();

#ifdef _MSC_VER

mutexArray = (MUTEX_TYPE*)OPENSSL_malloc(nNumLocks * sizeof(MUTEX_TYPE));

#else

mutexArray = (MUTEX_TYPE*)malloc(nNumLocks * sizeof(MUTEX_TYPE));

#endif

if (!mutexArray) {

return;

}

for (int32_t  i = 0;  i 

MUTEX_SETUP(mutexArray[i]);

}

CRYPTO_set_id_callback(threadId_function);

CRYPTO_set_locking_callback(locking_function);

}

void OpenSSLLock_Cleanup(void)

{

if (!mutexArray) {

return;

}

CRYPTO_set_id_callback(NULL);

CRYPTO_set_locking_callback(NULL);

for (int32_t i = 0; i < CRYPTO_num_locks(); ++i) {

MUTEX_CLEANUP(mutexArray[i]);

}

#ifdef _MSC_VER

OPENSSL_free(mutexArray);

#else

free(mutexArray);

#endif

mutexArray = NULL;

}

}

调用部分则比较简单,只需要在libcurl模块整体初始化和退出调用对应接口即可1.http模块初始化时

OpenSSLThreadLock::OpenSSLLock_Setup();

2.http模块退出时

// curl_global_cleanup();之前调用

OpenSSLThreadLock::OpenSSLLock_Cleanup();

这篇关于curl android 编译,跨平台:libcurl+openssl编译(Mac、Android、Windows、MD/MT模式、XP)...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/394332

相关文章

ESP32 esp-idf esp-adf环境安装及.a库创建与编译

简介 ESP32 功能丰富的 Wi-Fi & 蓝牙 MCU, 适用于多样的物联网应用。使用freertos操作系统。 ESP-IDF 官方物联网开发框架。 ESP-ADF 官方音频开发框架。 文档参照 https://espressif-docs.readthedocs-hosted.com/projects/esp-adf/zh-cn/latest/get-started/index

C++工程编译链接错误汇总VisualStudio

目录 一些小的知识点 make工具 可以使用windows下的事件查看器崩溃的地方 dumpbin工具查看dll是32位还是64位的 _MSC_VER .cc 和.cpp 【VC++目录中的包含目录】 vs 【C/C++常规中的附加包含目录】——头文件所在目录如何怎么添加,添加了以后搜索头文件就会到这些个路径下搜索了 include<> 和 include"" WinMain 和

如何开启和关闭3GB模式

https://jingyan.baidu.com/article/4d58d5414dfc2f9dd4e9c082.html

C/C++的编译和链接过程

目录 从源文件生成可执行文件(书中第2章) 1.Preprocessing预处理——预处理器cpp 2.Compilation编译——编译器cll ps:vs中优化选项设置 3.Assembly汇编——汇编器as ps:vs中汇编输出文件设置 4.Linking链接——链接器ld 符号 模块,库 链接过程——链接器 链接过程 1.简单链接的例子 2.链接过程 3.地址和

Mac excel 同时冻结首行和首列

1. 选择B2窗格 2. 选择视图 3. 选择冻结窗格 最后首行和首列的分割线加粗了就表示成功了

十四、观察者模式与访问者模式详解

21.观察者模式 21.1.课程目标 1、 掌握观察者模式和访问者模式的应用场景。 2、 掌握观察者模式在具体业务场景中的应用。 3、 了解访问者模式的双分派。 4、 观察者模式和访问者模式的优、缺点。 21.2.内容定位 1、 有 Swing开发经验的人群更容易理解观察者模式。 2、 访问者模式被称为最复杂的设计模式。 21.3.观察者模式 观 察 者 模 式 ( Obser

Windwos +vs 2022 编译openssl 1.0.2 库

一 前言 先说 结论,编译64位报错,查了一圈没找到解决方案,最后换了32位的。 使用qt访问web接口,因为是https,没有openssl库会报错 QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());if (reply){if (reply->error() == QNetworkReply::NoError

问题-windows-VPN不正确关闭导致网页打不开

为什么会发生这类事情呢? 主要原因是关机之前vpn没有关掉导致的。 至于为什么没关掉vpn会导致网页打不开,我猜测是因为vpn建立的链接没被更改。 正确关掉vpn的时候,会把ip链接断掉,如果你不正确关掉,ip链接没有断掉,此时你vpn又是没启动的,没有域名解析,所以就打不开网站。 你可以在打不开网页的时候,把vpn打开,你会发现网络又可以登录了。 方法一 注意:方法一虽然方便,但是可能会有

Windows/macOS/Linux 安装 Redis 和 Redis Desktop Manager 可视化工具

本文所有安装都在macOS High Sierra 10.13.4进行,Windows安装相对容易些,Linux安装与macOS类似,文中会做区分讲解 1. Redis安装 1.下载Redis https://redis.io/download 把下载的源码更名为redis-4.0.9-source,我喜欢跟maven、Tomcat放在一起,就放到/Users/zhan/Documents