C++:如何在CentOS上编译envoy

目录

一、下载envoy和bazel

1.下载envoy-1.15.0

2.下载并安装bazel-3.1.0

二、准备编译环境

1.设置用户可执行sudo操作

2.创建交换分区

3.在Vmware中共享文件夹

三、准备编译软件

1.安装gcc开发工具

2.安装git和配置git

3.安装JDK

4.安装cmake

5.安装python3以上版本

6.安装automake autoconf

7.安装libtool

8.安装ninja

四、编译envoy

1.初始化软件环境

2.初始化系统参数

3.执行编译

 五、报错汇总

1.报错1

2.报错2

3.报错3

4.报错4

5.报错5

6.报错6

7.报错7

8.报错8

9.报错9


在VMWare上安装了CentOS7,并在此系统上编译envoy1.15.0

一、下载envoy和bazel

1.下载envoy-1.15.0

1).下载地址:

https://codeload.github.com/envoyproxy/envoy/zip/v1.15.0

2).解压

ubzip envoy-1.15.0.zip

 3).在解压目录下找到.bazelversion并打开,查看里面的内容:

3.1.0

则说明编译envoy需要用到bazel-3.1.0。

 

2.下载并安装bazel-3.1.0

1).bazel下载地址

https://github.com/bazelbuild/bazel/releases

2).安装

sudo ./bazel-3.1.0-installer-linux-x86_64.sh --prefix=/usr/local/bazel-3.1.0

 3).在vi /etc/profile中添加

export PATH=$PATH:/usr/local/bazel-3.1.0/bin

bazel官网
https://www.bazel.build/
bazel源码:
https://github.com/bazelbuild/bazel
bazel下载
https://github.com/bazelbuild/bazel/releases

 

二、准备编译环境

1.设置用户可执行sudo操作

$ su

在/etc/sudoers中添加

testuser    ALL=(ALL)       ALL

2.创建交换分区

1).create a file which will be used as a swap space. count means 1024M * 1024 = 1G

sudo dd if=/dev/zero of=/swapfile bs=1024 count=4096000

2).Ensure that only the root user can read and write the swap file:

sudo chmod 600 /swapfile

3).set up a Linux swap area on the file

sudo mkswap /swapfile

4).activate the swap

sudo swapon /swapfile

5)."sudo swapon --show" or "sudo free -h" you will see the swap space.

如果要删除交换分区:

swapoff /swapfile
rm -rf /swapfile

3.在Vmware中共享文件夹

在Vmware中, 设置->选项->共享文件夹->添加,

名称:     sharedir
主机路径:E:/CentOS7-share/

使用如下命令查看共享目录是否已经设置成功

$ vmware-hgfsclient

手动挂载

$ vmhgfs-fuse -o nonempty .host:/sharedir /mnt/hgfs

三、准备编译软件

由于envoy1.15.0需要使用到C++14的一些特性,而CentOS7上默认的版本太旧,因此需要安装gcc7以上版本、python3等。

需要安装的主要必备软件如下:

1.安装gcc开发工具

1).安装scl源:

yum install centos-release-scl scl-utils-build

2).列出scl有哪些源可以用

yum list all --enablerepo='centos-sclo-rh' | grep gcc

3).安装 gcc、gcc-c++、gdb

yum -y install devtoolset-8-gcc.x86_64 devtoolset-8-gcc-c++.x86_64 devtoolset-8-gcc-gdb-plugin.x86_64

4).查看从 SCL 中安装的包的列表:

scl --list 或 scl -l

5).切换版本

scl enable devtoolset-8 bash

2.安装git和配置git

安装git
1).安装scl源:

yum install centos-release-scl

2). 查找可以安装的git版本

yum list all --enablerepo='centos-sclo-rh' | grep git

3).安装

yum install rh-git218

4).激活git的打开bash

scl enable rh-git218 bash

5).查看安装的版本

git --version

配置git
1).使用 git init 新建一个.git目录

git init

2).首先

git commit --allow-empty -n -m "Initial commit"

3).然后

git config --global user.email "aaaa@aaa.com"
git config --global user.name "aaaa"

4).最后

git commit --allow-empty -n -m "Initial commit"

3.安装JDK

1).查看可安装JDK版本

yum list all --enablerepo='centos-sclo-rh' | grep jdk

2).安装JDK

yum -y install java-1.8.0-openjdk.x86_64 java-1.8.0-openjdk-devel.x86_64 java-1.8.0-openjdk-src.x86_64 java-1.8.0-openjdk-javadoc.noarch

3).在/etc/profile中添加如下内容

export JAVA_HOME=/usr/java/jdk1.8.0_261-amd64
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib

4).使用添加的内容生效

source /etc/profile

4.安装cmake

1).下载cmake-3.18.4.tar.gz

2).解压到/usr/local

tar zxvf cmake-3.18.4.tar.gz
mv ./cmake-3.18.4  /usr/local/

3).配置PATH

export PATH=/usr/local/cmake-3.18.4-Linux-x86_64/bin:$PATH

4).建立软链接

ln -s /usr/local/cmake-3.18.4-Linux-x86_64/bin/cmake  /usr/local/bin

5.安装python3以上版本

1).查看python3是否已创建

python3 -V

2).安装

sudo yum -y install python36

3).创建软链接

ln -s /usr/bin/python3.6 /usr/bin/python3

6.安装automake autoconf

sudo yum -y install automake autoconf

7.安装libtool

sudo yum -y install libtool

8.安装ninja

1).下载Ninja二进行制文件,并放入/usr/local/bin中

下载地址:

https://github.com/ninja-build/ninja/releases

2).建立软链接

ln -s /usr/local/bin/ninja /usr/bin/ninja-build
ln -s /usr/local/bin/ninja /usr/local/bin/ninja-build

四、编译envoy

1.初始化软件环境

scl enable devtoolset-8 bash
scl enable rh-git218 bash

2.初始化系统参数

export CC=/opt/rh/devtoolset-8/root/usr/bin/gcc
export LD_LIBRARY_PATH=/opt/rh/devtoolset-8/root/usr/bin:/opt/rh/devtoolset-8/root/usr/lib/gcc/x86_64-redhat-linux/8:/opt/rh/devtoolset-8/root/usr/libexec/gcc/x86_64-redhat-linux/8

3.执行编译

最简单的编译命令:

bazel build -c opt //source/exe:envoy-static

1).如果需要在生产环境上编译,可以先把依赖的工程下载到本地,下载方式:

bazel fetch  --repository_cache=/home/testuser/Downloads/repo_cache   //source/exe:envoy-static

然后,编译时指定使用此本地库:

bazel build -c opt //source/exe:envoy-static  --repository_cache=/home/testuser/Downloads/repo_cache

2).如果要指定编译参数,可以使用--cxxopt

bazel build -c opt //source/exe:envoy-static  --cxxopt="-Wno-error=maybe-uninitialized"  --cxxopt="-Wno-error=uninitialized" --cxxopt="-DENVOY_IGNORE_GLIBCXX_USE_CXX11_ABI_ERROR=1"

 3).如果要查看编译报错的详细信息可以使用 --sandbox_debug --verbose_failures

bazel build --sandbox_debug --verbose_failures -c opt //source/exe:envoy-static

最终使用的编译命令:

bazel build --sandbox_debug --verbose_failures -c opt //source/exe:envoy-static  --cxxopt="-Wno-error=maybe-uninitialized"  --cxxopt="-Wno-error=uninitialized" --cxxopt="-DENVOY_IGNORE_GLIBCXX_USE_CXX11_ABI_ERROR=1" --repository_cache=/mnt/hgfs

 五、报错汇总

1.报错1

ERROR: An error occurred during the fetch of repository 'config_validation_pip3':
   pip_import failed:  (src/main/tools/process-wrapper-legacy.cc:58: "execvp(python3, ...)": No such file or directory
)

说明没有安装python3以上版本,则使用如下命令安装

sudo yum -y install python36

查看是否安装python3以上版本

$ which python3
/usr/bin/python3

查看python3是否已创建

$ python3 -V

如果没有创建,则创建软链接

$ ln -s /usr/bin/python3.6 /usr/bin/python3

2.报错2

Error applying patch command ./autogen.sh:
./autogen.sh: line 3: autoreconf: command not found
ERROR: Analysis of target '//source/exe:envoy-static' failed; build aborted: no such package '@com_github_gperftools_gperftools//': Traceback (most recent call last):
    File "/home/xiongli/.cache/bazel/_bazel_xiongli/7854416c8fb516e9cba561fc29849acc/external/bazel_tools/tools/build_defs/repo/http.bzl", line 121
        patch(ctx)
    File "/home/xiongli/.cache/bazel/_bazel_xiongli/7854416c8fb516e9cba561fc29849acc/external/bazel_tools/tools/build_defs/repo/utils.bzl", line 161, in patch
        fail(<1 more arguments>)
Error applying patch command ./autogen.sh:
./autogen.sh: line 3: autoreconf: command not found

缺少库:automake autoconf

$ sudo yum -y install automake autoconf

3.报错3

ERROR: Analysis of target '//source/exe:envoy-static' failed; build aborted: no such package '@com_github_gperftools_gperftools//': Traceback (most recent call last):
    File "/home/xiongli/.cache/bazel/_bazel_xiongli/7854416c8fb516e9cba561fc29849acc/external/bazel_tools/tools/build_defs/repo/http.bzl", line 121
        patch(ctx)
    File "/home/xiongli/.cache/bazel/_bazel_xiongli/7854416c8fb516e9cba561fc29849acc/external/bazel_tools/tools/build_defs/repo/utils.bzl", line 161, in patch
        fail(<1 more arguments>)
Error applying patch command ./autogen.sh:
configure.ac:172: error: possibly undefined macro: AC_PROG_LIBTOOL
      If this token and others are legitimate, please use m4_pattern_allow.
      See the Autoconf documentation.
autoreconf: /usr/bin/autoconf failed with exit status: 1

缺少库:libtool

$ sudo yum -y install libtool

4.报错4

ERROR: Process exited with status 1
fatal: Not a git repository (or any of the parent directories): .git
Target //source/exe:envoy-static failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 0.313s, Critical Path: 0.02s
INFO: 0 processes.
FAILED: Build did NOT complete successfully

使用 git init 新建一个.git目录

$ git init

5.报错5

ERROR: Process exited with status 1
fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
Target //source/exe:envoy-static failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 0.259s, Critical Path: 0.02s
INFO: 0 processes.
FAILED: Build did NOT complete successfully

配置git

git commit --allow-empty -n -m "Initial commit"

然后

git config --global user.email "aaaa@aaa.com"
git config --global user.name "aaaa"

最后再次

git commit --allow-empty -n -m "Initial commit"

6.报错6

CMake Error at CMakeLists.txt:22 (cmake_minimum_required):
  CMake 3.1.2 or higher is required.  You are running version 2.8.12.2

下载并解压cmake-3.18.4.tar.gz

$ tar zxvf cmake-3.18.4.tar.gz

然后

export PATH=/usr/local/cmake-3.18.4-Linux-x86_64/bin:$PATH

如果报与openSSL相关的问题,可以安装openSSL

$ yum install openssl openssl-devel

7.报错7

CMake Error: CMake was unable to find a build program corresponding to "Ninja".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
CMake Error: Error required internal CMake variable not set, cmake may be not be built correctly.
Missing variable is:
CMAKE_C_COMPILER_ENV_VAR
CMake Error: Error required internal CMake variable not set, cmake may be not be built correctly.
Missing variable is:
CMAKE_C_COMPILER
CMake Error: Could not find cmake module file: /tmp/tmp.kpA4SLdmHD/CMakeFiles/2.8.12.2/CMakeCCompiler.cmake
-- Configuring incomplete, errors occurred!

下载Ninja二进行制文件,下载地址:

https://github.com/ninja-build/ninja/releases

放入/usr/local/bin中

ln -s /usr/local/bin/ninja /usr/bin/ninja-build
ln -s /usr/local/bin/ninja /usr/local/bin/ninja-build

参考文档
https://www.cnblogs.com/xlmeng1988/p/cmake_ninja_error.html
https://stackoverflow.com/questions/63970811/cmake-unable-to-find-a-build-program-corresponding-to-ninja

8.报错8

gcc: error trying to exec 'cc1plus': execvp: No such file or directory

这个问题一般是由于没安装gcc-c++,或gcc与gcc-c++版本不一致造成的
安装与gcc版本一致的gcc-c++

$ yum -y install gcc-c++

9.报错9

  (cd /root/.cache/bazel/_bazel_testuser/3ac7a8c4088a54e071a77317875e8a53/sandbox/processwrapper-sandbox/2665/execroot/envoy && \
  exec env - \
    BAZEL_LINKLIBS=-l%:libstdc++.a \
    BAZEL_LINKOPTS=-lm \
    CC=/opt/rh/devtoolset-8/root/usr/bin/gcc \
    PATH=/opt/rh/rh-git218/root/usr/bin:/opt/rh/devtoolset-8/root/usr/bin:/usr/local/cmake-3.18.4-Linux-x86_64/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/testuser/.local/bin:/home/testuser/bin \
    PWD=/proc/self/cwd \
    TMPDIR=/tmp \
  /root/.cache/bazel/_bazel_testuser/install/f439a981a1e06f45be981c123f9858d5/process-wrapper '--timeout=0' '--kill_delay=15' /opt/rh/devtoolset-8/root/usr/bin/gcc -U_FORTIFY_SOURCE -fstack-protector -Wall -Wunused-but-set-parameter -Wno-free-nonheap-object -fno-omit-frame-pointer -g0 -O2 '-D_FORTIFY_SOURCE=1' -DNDEBUG -ffunction-sections -fdata-sections '-std=c++0x' -MD -MF bazel-out/k8-opt/bin/source/common/http/_objs/header_map_lib/header_map_impl.d '-frandom-seed=bazel-out/k8-opt/bin/source/common/http/_objs/header_map_lib/header_map_impl.o' -D__CLANG_SUPPORT_DYN_ANNOTATION__ -DSPDLOG_FMT_EXTERNAL -DFMT_HEADER_ONLY -iquote . -iquote bazel-out/k8-opt/bin -iquote external/com_google_absl -iquote bazel-out/k8-opt/bin/external/com_google_absl -iquote external/com_github_fmtlib_fmt -iquote bazel-out/k8-opt/bin/external/com_github_fmtlib_fmt -iquote external/com_github_gabime_spdlog -iquote bazel-out/k8-opt/bin/external/com_github_gabime_spdlog -iquote external/com_github_cyan4973_xxhash -iquote bazel-out/k8-opt/bin/external/com_github_cyan4973_xxhash -iquote external/envoy_api -iquote bazel-out/k8-opt/bin/external/envoy_api -iquote external/com_github_cncf_udpa -iquote bazel-out/k8-opt/bin/external/com_github_cncf_udpa -iquote external/com_google_googleapis -iquote bazel-out/k8-opt/bin/external/com_google_googleapis -iquote external/com_google_protobuf -iquote bazel-out/k8-opt/bin/external/com_google_protobuf -iquote external/com_envoyproxy_protoc_gen_validate -iquote bazel-out/k8-opt/bin/external/com_envoyproxy_protoc_gen_validate -iquote external/com_googlesource_code_re2 -iquote bazel-out/k8-opt/bin/external/com_googlesource_code_re2 -Ibazel-out/k8-opt/bin/source/common/http/_virtual_includes/header_map_lib -Ibazel-out/k8-opt/bin/source/common/http/_virtual_includes/headers_lib -Ibazel-out/k8-opt/bin/include/envoy/http/_virtual_includes/header_map_interface -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/assert_lib -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/minimal_logger_lib -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/base_logger_lib -Ibazel-out/k8-opt/bin/include/envoy/common/_virtual_includes/base_includes -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/fmt_lib -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/lock_guard_lib -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/thread_annotations -Ibazel-out/k8-opt/bin/include/envoy/thread/_virtual_includes/thread_interface -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/macros -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/non_copyable -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/logger_impl_lib_standard -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/hash_lib -Ibazel-out/k8-opt/bin/source/common/singleton/_virtual_includes/const_singleton -Ibazel-out/k8-opt/bin/source/common/singleton/_virtual_includes/threadsafe_singleton -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/dump_state_utils -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/empty_string -Ibazel-out/k8-opt/bin/source/common/common/_virtual_includes/utility_lib -Ibazel-out/k8-opt/bin/include/envoy/common/_virtual_includes/interval_set_interface -Ibazel-out/k8-opt/bin/include/envoy/common/_virtual_includes/time_interface -Ibazel-out/k8-opt/bin/source/common/runtime/_virtual_includes/runtime_features_lib -Ibazel-out/k8-opt/bin/include/envoy/runtime/_virtual_includes/runtime_interface -Ibazel-out/k8-opt/bin/include/envoy/stats/_virtual_includes/stats_interface -Ibazel-out/k8-opt/bin/include/envoy/stats/_virtual_includes/refcount_ptr_interface -Ibazel-out/k8-opt/bin/include/envoy/stats/_virtual_includes/symbol_table_interface -Ibazel-out/k8-opt/bin/external/com_google_protobuf/_virtual_includes/any_proto -Ibazel-out/k8-opt/bin/external/com_google_protobuf/_virtual_includes/descriptor_proto -Ibazel-out/k8-opt/bin/external/com_google_protobuf/_virtual_includes/duration_proto -Ibazel-out/k8-opt/bin/external/com_google_protobuf/_virtual_includes/timestamp_proto -isystem external/com_github_fmtlib_fmt/include -isystem bazel-out/k8-opt/bin/external/com_github_fmtlib_fmt/include -isystem external/com_github_gabime_spdlog/include -isystem bazel-out/k8-opt/bin/external/com_github_gabime_spdlog/include -isystem external/com_google_protobuf/src -isystem bazel-out/k8-opt/bin/external/com_google_protobuf/src -isystem bazel-out/k8-opt/bin/external/envoy/bazel/foreign_cc/zlib/include -fPIC -Wno-error -Wall -Wextra
-Werror -Wnon-virtual-dtor -Woverloaded-virtual -Wold-style-cast -Wformat -Wformat-security -Wvla '-std=c++14' -ggdb3 -DTCMALLOC -DENVOY_HANDLE_SIGNALS -DENVOY_OBJECT_TRACE_ON_DUMP -DENVOY_HOT_RESTART -DENVOY_GOOGLE_GRPC -fno-canonical-system-headers -Wno-builtin-macro-redefined '-D__DATE__="redacted"' '-D__TIMESTAMP__="redacted"' '-D__TIME__="redacted"' -c source/common/http/header_map_impl.cc -o bazel-out/k8-opt/bin/source/common/http/_objs/header_map_lib/header_map_impl.o)
In file included from external/com_google_absl/absl/container/inlined_vector.h:54,
                 from external/com_google_absl/absl/strings/cord.h:78,
                 from external/com_google_absl/absl/container/internal/hash_function_defaults.h:56,
                 from external/com_google_absl/absl/container/flat_hash_map.h:40,
                 from bazel-out/k8-opt/bin/source/common/common/_virtual_includes/hash_lib/common/common/hash.h:7,
                 from bazel-out/k8-opt/bin/include/envoy/http/_virtual_includes/header_map_interface/envoy/http/header_map.h:15,
                 from bazel-out/k8-opt/bin/source/common/http/_virtual_includes/header_map_lib/common/http/header_map_impl.h:10,
                 from source/common/http/header_map_impl.cc:1:
external/com_google_absl/absl/container/internal/inlined_vector.h: In constructor 'Envoy::Http::HeaderString::HeaderString()':
external/com_google_absl/absl/container/internal/inlined_vector.h:437:5: error: '<anonymous>.absl::inlined_vector_internal::Storage<char, 128, std::allocator<char> >::data_' is used uninitialized in this function [
-Werror=uninitialized]
     data_ = other_storage.data_;
     ^~~~~
external/com_google_absl/absl/container/internal/inlined_vector.h: In member function 'void Envoy::Http::HeaderString::setInteger(uint64_t)':
external/com_google_absl/absl/container/internal/inlined_vector.h:437:5: error: '<anonymous>.absl::inlined_vector_internal::Storage<char, 128, std::allocator<char> >::data_' may be used uninitialized in this function [
-Werror=maybe-uninitialized]
     data_ = other_storage.data_;
     ^~~~~
external/com_google_absl/absl/container/internal/inlined_vector.h:437:5: error: '<anonymous>.absl::inlined_vector_internal::Storage<char, 128, std::allocator<char> >::data_' may be used uninitialized in this function [
-Werror=maybe-uninitialized]
     data_ = other_storage.data_;
     ^~~~~
external/com_google_absl/absl/container/internal/inlined_vector.h:437:5: error: '<anonymous>.absl::inlined_vector_internal::Storage<char, 128, std::allocator<char> >::data_' may be used uninitialized in this function [
-Werror=maybe-uninitialized]
     data_ = other_storage.data_;
     ^~~~~
external/com_google_absl/absl/container/internal/inlined_vector.h: In member function 'void Envoy::Http::HeaderString::setCopy(const char*, uint32_t)':
external/com_google_absl/absl/container/internal/inlined_vector.h:437:5: error: '<anonymous>.absl::inlined_vector_internal::Storage<char, 128, std::allocator<char> >::data_' may be used uninitialized in this function [
-Werror=maybe-uninitialized]
     data_ = other_storage.data_;
     ^~~~~
external/com_google_absl/absl/container/internal/inlined_vector.h:437:5: error: '<anonymous>.absl::inlined_vector_internal::Storage<char, 128, std::allocator<char> >::data_' may be used uninitialized in this function [
-Werror=maybe-uninitialized]
     data_ = other_storage.data_;
     ^~~~~
external/com_google_absl/absl/container/internal/inlined_vector.h: In member function 'void Envoy::Http::HeaderString::append(const char*, uint32_t)':
external/com_google_absl/absl/container/internal/inlined_vector.h:437:5: error: '<anonymous>.absl::inlined_vector_internal::Storage<char, 128, std::allocator<char> >::data_' may be used uninitialized in this function [
-Werror=maybe-uninitialized]
     data_ = other_storage.data_;
     ^~~~~
cc1plus: all warnings being treated as errors

由于编译时使用了参数:-Werror,导致出现-Wuninitialized-Wmaybe-uninitialized这两种警告时会报错,并中止编译。可以添加编译参数,对这2 种情况不再报错:

--cxxopt="-Wno-error=maybe-uninitialized"  --cxxopt="-Wno-error=uninitialized"

参考文档

CentOS 上最佳的第三方仓库
https://linux.cn/article-8509-1.html?utm_source=weibo&utm_medium=weibo

VMware设置centos7共享文件夹
https://blog.csdn.net/nesxiaogu/article/details/85274626

yum更新gcc到版本8
https://blog.csdn.net/wfx15502104112/article/details/96508940?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-2-96508940.nonecase&utm_term=yum%20%E6%8C%87%E5%AE%9Agcc%E7%89%88%E6%9C%AC

centos 下 yum安装python3
https://www.cnblogs.com/iam-ironman/articles/10969663.html

centos安装jdk1.8的三种方法
https://blog.csdn.net/dhr201499/article/details/81626466

 

 

 

 

 

 

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值