万字总结简化跨平台编译利器CMake,从入门到项目实战演练!(上)

简介: 万字总结简化跨平台编译利器CMake,从入门到项目实战演练!

一、什么是 CMake

你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。


CMake就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等 [1]。


CMake是我非常喜欢且一直使用的工具。它不但能帮助我跨平台、跨编译器,而且最酷的是,它帮我节约了太多的存储空间。特别是与水银结合起来使用,其友好的体验,足以给我们这些苦逼码农一丝慰藉。


精选文章推荐阅读:


[1]牛客网论坛最具争议的Linux内核成神笔记,GitHub已下载量已过百万


[2]牛客网论坛考研计算机组成原理笔记,GitHub已下载量已过百万


[3]探索网络通信核心技术,手写TCP/IP用户态协议栈,让性能飙升起来!


[4]非985、211,C/C++校招学到啥程度才能进鹅厂、阿里、百度等大厂


以下内容翻译自官网教程CMake(https://cmake.org/)


1.1CMake Tutorial


A Basic Starting Point (Step 1)


最基本的就是将一个源代码文件编译成一个exe可执行程序。对于一个简单的工程来说,两行的CMakeLists.txt文件就足够了。这将是我们教程的开始。CMakeLists.txt文件看起来会像这样:


cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

注意,在这个例子中,CMakeLists.txt都是使用的小写字母。事实上,CMake命令是大小写不敏感的,你可以用大写,也可以用小写,也可以混写。tutorial.cxx源码会计算出一个数的平方根。它的第一个版本看起来非常简单,如下:

// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main (int argc, char *argv[])
{
 if (argc < 2)
   {
   fprintf(stdout,"Usage: %s number\n",argv[0]);
   return 1;
   }
 double inputValue = atof(argv[1]);
 double outputValue = sqrt(inputValue);
 fprintf(stdout,"The square root of %g is %g\n",
         inputValue, outputValue);
 return 0;
}

Adding a Version Number and Configured Header File


我们第一个要加入的特性是,在工程和可执行程序上加一个版本号。虽然你可以直接在源代码里面这么做,然而如果用CMakeLists文件来做的话会提供更多的灵活性。为了增加版本号,我们可以如此更改CMakeLists文件:


cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)

# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
 "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
 "${PROJECT_BINARY_DIR}/TutorialConfig.h"
 )

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

# add the executable
add_executable(Tutorial tutorial.cxx)

由于配置文件必须写到binary tree中,因此我们必须将这个目录添加到头文件搜索目录中。我们接下来在源码目录中创建了TutorialConfig.h.in文件,其内容如下:

// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

当CMake配置了这个头文件, @Tutorial_VERSION_MAJOR@ 和 @Tutorial_VERSION_MINOR@ 的值将会被改变。接下来,我们修改了tutorial.cxx来包含配置的头文件并且使用版本号。最终的源代码如下所示:

// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"

int main (int argc, char *argv[])
{
 if (argc < 2)
   {
   fprintf(stdout,"%s Version %d.%d\n",
           argv[0],
           Tutorial_VERSION_MAJOR,
           Tutorial_VERSION_MINOR);
   fprintf(stdout,"Usage: %s number\n",argv[0]);
   return 1;
   }
 double inputValue = atof(argv[1]);
 double outputValue = sqrt(inputValue);
 fprintf(stdout,"The square root of %g is %g\n",
         inputValue, outputValue);
 return 0;
}

最主要的变更是包含了TutorialConfig.h头文件,并输出了版本号。

【文章福利】小编推荐自己的Linux C++技术交流群:【1106675687】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送大厂面试题。

640.png

编辑


添加图片注释,不超过 140 字(可选)


资料领取直通车:https://docs.qq.com/doc/DTlhVekRrZUdDUEpy

Linux服务器学习网站:https://ke.qq.com/course/417774?flowToken=1028592

Adding a Library (Step 2)

现在我们给工程添加一个库。这个库会包含我们自己的平方根实现。如此,应用程序就可以使用这个库而非编译器提供的库了。在这个教程中,我们将库放入一个叫MathFunctions的子文件夹中。

它会使用如下的一行CMakeLists文件:

add_library(MathFunctions mysqrt.cxx)

原文件mysqrt.cxx有一个叫做mysqrt的函数可以提供与编译器的sqrt相似的功能。为了使用新的库,我们需要在顶层的CMakeLists 文件中添加add_subdirectory的调用。我们也要添加一个另外的头文件搜索目录,使得MathFunctions/mysqrt.h可以被搜索到。最后的改变就是将新的库加到可执行程序中。顶层的CMakeLists 文件现在看起来是这样:

include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)

# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial MathFunctions)

现在我们来考虑如何使得MathFunctions库成为可选的。虽然在这个教程当中没有什么理由这么做,然而如果使用更大的库或者当依赖于第三方的库时,你或许希望这么做。第一步是要在顶层的CMakeLists文件中加上一个选择项。

# should we use our own math functions?
option(USE_MYMATH
       
"Use tutorial provided math implementation" ON)

这个选项会显示在CMake的GUI,并且其默认值为ON。当用户选择了之后,这个值会被保存在CACHE中,这样就不需要每次CMAKE都进行更改了。下面一步条件构建和链接MathFunctions库。为了达到这个目的,我们可以改变顶层的CMakeLists文件,使得其看起来像这样:

# add the MathFunctions library?
#
if (USE_MYMATH)
 include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
 add_subdirectory (MathFunctions)
 set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)

# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial  ${EXTRA_LIBS})

这里使用了USE_MYMATH来决定MathFunctions是否会被编译和使用。注意这里变量EXTRA_LIBS的使用方法。这是保持一个大的项目看起来比较简洁的一个方法。源代码中相应的变化就比较简单了:


// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif

int main (int argc, char *argv[])
{
 if (argc < 2)
   {
   fprintf(stdout,"%s Version %d.%d\n", argv[0],
           Tutorial_VERSION_MAJOR,
           Tutorial_VERSION_MINOR);
   fprintf(stdout,"Usage: %s number\n",argv[0]);
   return 1;
   }

 double inputValue = atof(argv[1]);

#ifdef USE_MYMATH
 double outputValue = mysqrt(inputValue);
#else
 double outputValue = sqrt(inputValue);
#endif

 fprintf(stdout,"The square root of %g is %g\n",
         inputValue, outputValue);
 return 0;
}

在源代码中我们同样使用了USE_MYMATH这个宏。它由CMAKE通过配置文件TutorialConfig.h.in来提供给源代码。

#cmakedefine USE_MYMATH

Installing and Testing (Step 3)

接下来我们会为我们的工程增加安装规则和测试支持。安装规则是非常非常简单的。对于MathFunctions库我们安装库和头文件只需要添加如下的语句:

install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)

对于应用程序,我们只需要在顶层CMakeLists 文件中如此配置即可以安装可执行程序和配置了的头文件:

# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"        
        DESTINATION include)

这就是所有需要做的。现在你就可以编译这个教程了,然后输入make install(或者编译IDE中的INSTALL目标),则头文件、库和可执行程序等就会被正确地安装。CMake变量CMAKE_INSTALL_PREFIX被用来决定那些文件会被安装在哪个根目录下。添加测试也是一个相当简单的过程。在最顶层的CMakeLists文件的最后我们可以添加一系列的基础测试来确认这个程序是否在正确工作。

# does the application run
add_test (TutorialRuns Tutorial 25)

# does it sqrt of 25
add_test (TutorialComp25 Tutorial 25)

set_tests_properties (TutorialComp25
 PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")

# does it handle negative numbers
add_test (TutorialNegative Tutorial -25)
set_tests_properties (TutorialNegative
 PROPERTIES PASS_REGULAR_EXPRESSION "-25 is 0")

# does it handle small numbers
add_test (TutorialSmall Tutorial 0.0001)
set_tests_properties (TutorialSmall
 PROPERTIES PASS_REGULAR_EXPRESSION "0.0001 is 0.01")

# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage
 PROPERTIES
 PASS_REGULAR_EXPRESSION "Usage:.*number")

第一个测试简单地确认应用是否运行,没有段错误或者其它的崩溃问题,并且返回0。这是CTest的最基本的形式。下面的测试都使用了PASS_REGULAR_EXPRESSION测试属性来确认输出的结果中是否含有某个字符串。如果你需要添加大量的测试来判断不同的输入值,则你需要考虑创建一个类似于下面的宏:

#define a macro to simplify adding tests, then use it
macro (do_test arg result)
 add_test (TutorialComp${arg} Tutorial ${arg})
 set_tests_properties (TutorialComp${arg}
   PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)

# do a bunch of result based tests
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")

对do_test的任意一次调用,就有另一个测试被添加到工程中。

Adding System Introspection (Step 4)

接下来,我们来考虑添加一些有些目标平台可能不支持的代码。在这个样例中,我们将根据目标平台是否有log和exp函数来添加我们的代码。当然大多数平台都是有这些函数的,只是本教程假设这两个函数没有被那么普遍地支持。如果平台有log,那么在mysqrt中,就用它来计算平方根。我们首先使用CheckFunctionExists.cmake来测试这些函数的是否存在,在顶层的CMakeLists文件中:

# does this system provide the log and exp functions?
include (CheckFunctionExists.cmake)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)

接下来我们修改TutorialConfig.h.in来定义CMake是否找到这些函数的宏

// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP

重要的一点是,对tests和log的测试必须要在配置文件命令前完成。配置文件命令会使用CMake中的配置立马配置文件。最后在mysqrt函数中我们提供了两种实现方式:

// if we have both log and exp then use them
#if defined (HAVE_LOG) && defined (HAVE_EXP)
 result = exp(log(x)*0.5);
#else // otherwise use an iterative approach
 . . .

Adding a Generated File and Generator (Step 5)

在这一节当中,我们会告诉你如何将一个生成的源文件加入到应用程序的构建过程中。在此例中,我们会创建一个预先计算好的平方根的表,并将这个表编译到应用程序中去。为了达到这个目的,我们首先需要一个程序来生成这样的表。在MathFunctions这个子目录下一个新的叫做MakeTable.cxx的源文件就是用来干这个的。


// A simple program that builds a sqrt table
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main (int argc, char *argv[])
{
 int i;
 double result;

 // make sure we have enough arguments
 if (argc < 2)
   {
   return 1;
   }
 
 // open the output file
 FILE *fout = fopen(argv[1],"w");
 if (!fout)
   {
   return 1;
   }
 
 // create a source file with a table of square roots
 fprintf(fout,"double sqrtTable[] = {\n");
 for (i = 0; i < 10; ++i)
   {
   result = sqrt(static_cast<double>(i));
   fprintf(fout,"%g,\n",result);
   }

 // close the table with a zero
 fprintf(fout,"0};\n");
 fclose(fout);
 return 0;
}

注意到这张表是由一个有效的C++代码产生的,并且输出文件的名字是由参数代入的。下一步就是添加合适的命令到MathFunctions的CMakeLists文件中来构建MakeTable这个可执行程序,并且作为构建过程中的一部分。完成它需要一些命令,如下:

# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)

# add the command to generate the source code
add_custom_command (
 OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
 COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
 DEPENDS MakeTable
 )

# add the binary tree directory to the search path for
# include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )

# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h  )

首先,就像其它可执行程序一样,MakeTable被添加为可执行程序。然后我们添加了一个自定义命令来详细描述如何通过运行MakeTable来产生Table.h。接下来,我们需要让CMake知道mysqrt.cxx依赖于生成的文件Table.h。这是通过往MathFunctions这个库里面添加生成的Table.h来实现的。我们也需要添加当前的生成目录到搜索路径中,从而Table.h可以被mysqrt.cxx找到。

当这个工程被构建时,它首先会构建MakeTable这个可执行程序。然后运行MakeTable从而生成Table.h。最后,它会编译mysqrt.cxx来生成MathFunctions library。

在这一刻,我们添加了所有的特征到最顶层的CMakeLists文件,它现在看起来是这样的:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)

# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)

# does this system provide the log and exp functions?
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)

check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)

# should we use our own math functions
option(USE_MYMATH
 "Use tutorial provided math implementation" ON)

# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
 "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
 "${PROJECT_BINARY_DIR}/TutorialConfig.h"
 )

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories ("${PROJECT_BINARY_DIR}")

# add the MathFunctions library?
if (USE_MYMATH)
 include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
 add_subdirectory (MathFunctions)
 set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)

# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial  ${EXTRA_LIBS})

# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"        
        DESTINATION include)

# does the application run
add_test (TutorialRuns Tutorial 25)

# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage
 PROPERTIES
 PASS_REGULAR_EXPRESSION "Usage:.*number"
 )


#define a macro to simplify adding tests
macro (do_test arg result)
 add_test (TutorialComp${arg} Tutorial ${arg})
 set_tests_properties (TutorialComp${arg}
   PROPERTIES PASS_REGULAR_EXPRESSION ${result}
   )
endmacro (do_test)

# do a bunch of result based tests
do_test (4 "4 is 2")
do_test (9 "9 is 3")
do_test (5 "5 is 2.236")
do_test (7 "7 is 2.645")
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")
do_test (0.0001 "0.0001 is 0.01")

TutorialConfig.h是这样的:

// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
#cmakedefine USE_MYMATH

// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP

最后MathFunctions的CMakeLists文件看起来是这样的:

# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
# add the command to generate the source code
add_custom_command (
 OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
 DEPENDS MakeTable
 COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
 )
# add the binary tree directory to the search path
# for include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )

# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h)

install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)

Building an Installer (Step 6)

最后假设我们想要把我们的工程发布给别人从而让他们去使用。我们想要同时给他们不同平台的二进制文件和源代码。这与步骤3中的install略有不同,install是安装我们从源代码中构建的二进制文件。而在此例中,我们将要构建安装包来支持二进制安装以及cygwin,debian,RPMs等的包管理特性。为了达到这个目的,我们会使用CPack来创建平台相关的安装包。具体地说,我们需要在顶层CMakeLists.txt文件中的底部添加数行。

# build a CPack driven installer package
include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE  
    "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
set (CPACK_PACKAGE_VERSION_PATCH "${Tutorial_VERSION_PATCH}")
include (CPack)

这就是所有要做的。我们首先包含了InstallRequiredSystemLibraries。这个模块将会包含当前平台所需要的所有运行时库。接下来,我们设置了一些CPack的变量来保存license以及工程的版本信息。版本信息利用了我们在早前的教程中使用到的变量。最后我们包含了CPack这个模块来使用这些变量和你所使用的系统的其它特性来设置安装包。

接下来一步是用通常的方式构建工程,然后在CPack上运行它。如果要构建一个二进制包你需要运行:

cpack --config CPackConfig.cmake

如果要创建一个关代码包你需要输入

cpack --config CPackSourceConfig.cmake

Adding Support for a Dashboard (Step 7)

将测试结果上传到dashboard上是非常简单的。我们在早前的步骤中已经定义了一些测试。我们仅需要运行这些例程然后提交到dashboard上。为了包含对dashboards的支持,我们需要在顶层的CMakeLists文件中包含CTest模块。

# enable dashboard scripting
include (CTest)

我们也创建了一个CTestConfig.cmake文件来指定这个工程在dashobard上的的名字。

set (CTEST_PROJECT_NAME "Tutorial")

CTest会读这个文件并且运行它。如果要创建一个简单的dashboard,你可以在你的工程上运行CMake,改变生成路径的目录,然后运行ctest -D Experimental。你的dashboard的结果会被上传到Kitware的公共dashboard中。

在Linux平台下使用 CMake生成Makefile 并编译的流程如下:

  1. 编写 CMake 配置文件 CMakeLists.txt 。
  2. 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile 1 1ccmakecmake 的区别在于前者提供了一个交互式的界面。。其中, PATH 是 CMakeLists.txt 所在的目录。
  3. 使用 make 命令进行编译。

本文将从实例入手,一步步讲解 CMake 的常见用法,文中所有的实例代码可以在这里找到。如果你读完仍觉得意犹未尽,可以继续学习我在文章末尾提供的其他资源。

【文章福利】小编推荐自己的Linux内核技术交流群:【865977150】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!

640.png

资料领取直通车:https://docs.qq.com/doc/DTlhVekRrZUdDUEpy

Linux服务器学习网站:https://ke.qq.com/course/417774?flowToken=1028592

相关文章
|
开发框架 移动开发 小程序
强烈推荐:绝对是好用的小程序开源框架
强烈推荐:绝对是好用的小程序开源框架
190 0
|
7月前
|
存储 Rust 测试技术
【一起学Rust · 项目实战】命令行IO项目minigrep——测试驱动开发完善功能
【一起学Rust · 项目实战】命令行IO项目minigrep——测试驱动开发完善功能
136 0
|
2月前
|
Rust 安全 Linux
Rust-01 Hello Rust 10分钟上手编写第一个Rust程序 背景介绍 发展历史 环境配置 升级打怪的必经之路
Rust-01 Hello Rust 10分钟上手编写第一个Rust程序 背景介绍 发展历史 环境配置 升级打怪的必经之路
53 0
Rust-01 Hello Rust 10分钟上手编写第一个Rust程序 背景介绍 发展历史 环境配置 升级打怪的必经之路
|
6月前
|
数据管理 测试技术 持续交付
自动化测试的进阶之路:从脚本到框架
【6月更文挑战第28天】在软件开发的生命周期中,自动化测试是确保产品质量和提升开发效率的关键步骤。本文深入探讨了自动化测试的演变历程,从简单的脚本编写到构建复杂的测试框架,揭示了如何通过持续集成和持续部署(CI/CD)实现自动化测试的高效执行。文章不仅介绍了自动化测试的基本概念和工具,还提供了实用的策略和技巧,帮助读者理解如何在现代软件工程实践中有效地应用自动化测试,以及如何克服常见的挑战。
|
NoSQL 测试技术 Shell
万字总结简化跨平台编译利器CMake,从入门到项目实战演练!(下)
万字总结简化跨平台编译利器CMake,从入门到项目实战演练!(下)
|
7月前
|
Rust 前端开发 JavaScript
Rust在前端与全栈开发中的实践探索
随着Rust语言的日渐成熟,其应用场景已经从后端扩展到前端和全栈开发领域。本文将深入探讨Rust语言在前端与全栈开发中的实际应用案例,分析Rust语言在这些领域的优势和面临的挑战,并展望Rust未来的发展趋势。
|
7月前
|
Go 开发者
Go语言插件开发:Pingo库实践
Go语言插件开发:Pingo库实践
107 0
|
存储 IDE 编译器
万字总结简化跨平台编译利器CMake,从入门到项目实战演练!(中)
万字总结简化跨平台编译利器CMake,从入门到项目实战演练!(中)
|
机器学习/深度学习 安全 测试技术
Go语言进阶-工程进阶
Go语言进阶-工程进阶
Go语言进阶-工程进阶
|
小程序 JavaScript API
01-小程序:开发入门篇
01-小程序:开发入门篇
418 0
01-小程序:开发入门篇