CMake构建静态库与动态库以及使用

news2024/11/15 10:48:56

CMake构建静态库与动态库

  • 一、任务
  • 二、准备工作
  • 三、编译共享库
  • 四、ADD_LIBRARY指令
  • 五、编译静态库
    • 5.1、SET_TARGET_PROPERTIES指令
    • 5.2、GET_TARGET_PROPERTY指令
  • 六、动态库版本号
  • 七、安装共享库和头文件
  • 八、使用外部共享库和头文件
    • 8.1、准备工作
    • 8.2、引入头文件搜索路径
    • 8.3、为 target 添加共享库
  • 九、使用外部静态库和头文件
  • 十、特殊的环境变量
  • 总结

一、任务

用例子的方式通俗易懂地解释CMake构建静态库与动态库的过程。

任务:

  1. 建立一个静态库和动态库,提供HelloFunc 函数供其他程序编程使用,HelloFunc向终端输出Hello World 字符串。
  2. 安装头文件与共享库。
  3. 编写一个程序去使用构建的共享库。

二、准备工作

(1)建立 t3 目录,用于存放本节涉及到的工程。

mkdir t3

(2)建立共享库。

cd t3
mkdir lib

(3)在 t3 目录下建立CMakeLists.txt,内容如下:

PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)

(4)在 lib 目录下建立两个源文件hello.c与hello.h。

hello.c 内容如下:

#include "hello.h"
void hello_func(void) 
{
	printf("Hello World!\n");
	return;
}

hello.h 内容如下:

#ifndef HELLO_H_
#define HELLO_H_ (1)
#include <stdio.h>
void hello_func(void);
#endif

(5)在 lib 目录下建立CMakeLists.txt,内容如下:

SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

三、编译共享库

采用out-of-source 编译的方式,建立一个build 目录,在build目录中执行:

mkdir build
cd build
cmake ..
make

这时,就可以在lib 目录得到一个libhello.so,这就是共享库。

如果要指定libhello.so 生成的位置,可以通过在主工程文件CMakeLists.txt 中修改 ADD_SUBDIRECTORY(lib)指令来指定一个编译输出位置或者在lib/CMakeLists.txt 中添加 SET(LIBRARY_OUTPUT_PATH <路径>)来指定一个新的位置。

四、ADD_LIBRARY指令

语法:

ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)

不需要写全libhello.so,只需要填写hello 即可,cmake 系统会自动生成 libhello.X

类型有三种:

  1. SHARED,动态库。
  2. STATIC,静态库。
  3. MODULE,在使用 dyld 的系统有效,如果不支持dyld,则被当作SHARED 对待。

EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。

五、编译静态库

静态库名字跟动态库名字应该是一致的,只不过后缀是.a 罢了。下面用这个指令再来添加静态库:

 ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})

注意,如果是在上面的编译动态库的基础上添加的上述内容,会发现静态库根本没有被构建,仍然只生成了一个动态库。因为hello 作为一个target 是不能重名的,所以,静态库构建指令无效。如果把上面的hello 修改为hello_static:

ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

就可以构建一个libhello_static.a 的静态库了。

但是这种结果显示不是想要的,我们需要的是名字相同的静态库和动态库,因为 target 名称是唯一的,所以,肯定不能通过 ADD_LIBRARY 指令来实现了。这时候我们需要用到另外一个指令SET_TARGET_PROPERTIES。

5.1、SET_TARGET_PROPERTIES指令

SET_TARGET_PROPERTIES基本语法:

SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)

这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本。

在本例中,需要作的是向lib/CMakeLists.txt 中再添加一条:

SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello") 

这样,我们就可以同时得到libhello.so和libhello.a 两个库了。

lib/CMakeLists.txt最终的的内容:

SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")

5.2、GET_TARGET_PROPERTY指令

与SET_TARGET_PROPERTY对应的指令是GET_TARGET_PROPERTY基本语法为:

GET_TARGET_PROPERTY(VAR target property)

具体用法如下例,向lib/CMakeListst.txt 中添加:

GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
 
MESSAGE(STATUS "This is the hello_static OUTPUT_NAME:"${OUTPUT_VALUE})

如果没有这个属性定义,则返回NOTFOUND。

构建结果会发现,libhello.a 已经构建完成,位于 build/lib 目录中,但是libhello.so 却消失了。这个问题的原因是:cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,因为,在构建libhello.a 时,就会清理掉libhello.so.

为了回避这个问题,比如再次使用SET_TARGET_PROPERTIES 定义 CLEAN_DIRECT_OUTPUT 属性。

向 lib/CMakeLists.txt 中添加:

SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

这时候,我们再次进行构建,会发现build/lib 目录中同时生成了libhello.so 和 libhello.a。

六、动态库版本号

按照规则,动态库是应该包含一个版本号的,我们可以看一下系统的动态库,一般情况是:

libhello.so.1.0
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.0

为了实现动态库版本号,需要使用 SET_TARGET_PROPERTIES 指令。

具体使用方法如下:

SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1) 

VERSION 指代动态库版本,SOVERSION 指代 API 版本。

将上述指令加入lib/CMakeLists.txt 中,重新构建,在 build/lib 目录会生成:

libhello.so.1.2 libhello.so.1->libhello.so.1.2 libhello.so ->libhello.so.1

七、安装共享库和头文件

以上面的例子,需要将libhello.a, libhello.so.x 以及 hello.h 安装到系统目录,才能真正让其他人开发使用,在本例中我们将 hello 的共享库安装到< prefix >/lib目录,将hello.h 安装到< prefix >/include/hello 目录。

利用上一篇了解到的INSTALL 指令,向lib/CMakeLists.txt 中添加如下指令:

INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)

注意,静态库要使用ARCHIVE 关键字

通过如下命令安装:

cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
sudo make install

这样就可以将头文件和共享库安装到系统目录/usr/lib 和/usr/include/hello 中了。

[ 50%] Built target hello
[100%] Built target hello_static
Install the project...
-- Install configuration: ""
-- Installing: /usr/lib/libhello.so.1.2
-- Installing: /usr/lib/libhello.so.1
-- Installing: /usr/lib/libhello.so
-- Installing: /usr/lib/libhello.a
-- Installing: /usr/include/hello/hello.h

如果报错:

CMake Error: cmake_symlink_library: System Error: Operation not supported
CMake Error: cmake_symlink_library: System Error: Operation not supported
make[2]: *** [lib/CMakeFiles/hello_dynamic.dir/build.make:85: lib/libhello.so.1.2] Error 1
make[2]: *** Deleting file 'lib/libhello.so.1.2'
make[1]: *** [CMakeFiles/Makefile2:130: lib/CMakeFiles/hello_dynamic.dir/all] Error 2
make: *** [Makefile:130: all] Error 2

则说明可能是通过hgfs共享的Windows目录下进行make,生成so时需要在纯linux环境运行,比如把t3拷贝到~/t3, 并且先把对应的build目录里面的内容清空,重新执行:

cmake .. 
make

八、使用外部共享库和头文件

8.1、准备工作

(1)建立 t4 目录,本节所有资源将存储在t4 目录。

mkdir t4

(2)在t4目录下建立src 目录。

cd t4
mkdir src

(3)src目录下编写源文件main.c,内容如下:

#include "hello.h"
int main(void) 
{
	hello_func();
	return 0;
}

(4)t4目录下编写工程主文件CMakeLists.txt。

PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)

(5)t4/src目录下编写src/CMakeLists.txt。

ADD_EXECUTABLE(main main.c)

8.2、引入头文件搜索路径

准备工作完成后,直接构建是失败的,因为找不到头文件"hello.h"。错误输出为是:

cmake/t4/src/main.c:1:19: error: hello.h: 没有那个文件或目录

为了让我们的工程能够找到hello.h 头文件,需要引入一个新的指令INCLUDE_DIRECTORIES,其完整语法为:

INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)

这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,可以通过两种方式来进行控制搜索路径添加的方式:

CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过SET 这个cmake 变量为on,可以将添加的头文件搜索路径放在已有路径的前面。
通过AFTER 或者BEFORE 参数,也可以控制是追加还是置前。

现在我们在src/CMakeLists.txt 中添加一个头文件搜索路径,方式很简单,加入:

INCLUDE_DIRECTORIES(/usr/include/hello)

进入build 目录,重新进行构建,这时找不到 hello.h 的错误已经消失,但是出现了一个新的错误:

CMakeFiles/main.dir/main.o:在函数‘main’中:
main.c:(.text+0x5):对‘hello_func’未定义的引用
collect2: error: ld returned 1 exit status
src/CMakeFiles/main.dir/build.make:96: recipe for target 'src/main' failed
make[2]: *** [src/main] Error 1
CMakeFiles/Makefile2:97: recipe for target 'src/CMakeFiles/main.dir/all' failed
make[1]: *** [src/CMakeFiles/main.dir/all] Error 2
Makefile:90: recipe for target 'all' failed
make: *** [all] Error 2

因为我们并没有link 到共享库libhello 上。

8.3、为 target 添加共享库

现在需要完成的任务是将目标文件链接到 libhello,这里我们需要引入两个新的指令:LINK_DIRECTORIES 和TARGET_LINK_LIBRARIES。

LINK_DIRECTORIES 的全部语法是:

LINK_DIRECTORIES(directory1 directory2 ...)

这个指令非常简单,添加非标准的共享库搜索路径,比如,在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径。这个例子中我们没有用到这个指令。

TARGET_LINK_LIBRARIES 的全部语法是:

TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 ...)

这个指令可以用来为target 添加需要链接的共享库,本例中是一个可执行文件,但是同样可以用于为自己编写的共享库添加共享库链接。

为了解决我们前面遇到的HelloFunc 未定义错误,需要做的是向 src/CMakeLists.txt 中添加如下指令:

TARGET_LINK_LIBRARIES(main hello)

也可以写成:

TARGET_LINK_LIBRARIES(main libhello.so)

这里的hello指的是前面构建的共享库 libhello.XX。

进入build 目录重新进行构建。

cmake ..
make

这时就得到了一个连接到libhello 的可执行程序main,位于build/src目录,运行 main 的结果是输出:

$ ./main
Hello World!

来看一下main 的链接情况:

$ ldd src/main 
        linux-vdso.so.1 (0x00007ffd531fd000)
        libhello.so.1 => /usr/lib/libhello.so.1 (0x00007ff212afe000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff21270d000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff212d00000)

可以清楚的看到main 确实链接了共享库libhello,而且链接的是动态库 libhello.so.1。

九、使用外部静态库和头文件

那如何链接到静态库呢?方法很简单:

将 TARGET_LINK_LIBRRARIES 指令修改为:

TARGET_LINK_LIBRARIES(main libhello.a)

重新构建后再来看一下main 的链接情况:

$ ldd src/main 
        linux-vdso.so.1 (0x00007ffc7a161000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff1da80c000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff1dabfd000)

说明,main 确实链接到了静态库libhello.a。

十、特殊的环境变量

CMAKE_INCLUDE_PATH 和CMAKE_LIBRARY_PATH。 务必注意,这两个是环境变量而不是cmake 变量。

使用方法是要在bash 中用export 或者在csh 中使用set 命令设置或者 CMAKE_INCLUDE_PATH=/home/include cmake …等方式。

这两个变量主要是用来解决以前autotools 工程中 --extra-include-dir 等参数的支持的。也就是,如果头文件没有存放在常规路径(/usr/include, /usr/local/include 等),则可以通过这些变量就行弥补。

以本例中的hello.h 为例,它存放在/usr/include/hello 目录,所以直接查找肯定是找不到的。

前面我们直接使用了绝对路径INCLUDE_DIRECTORIES(/usr/include/hello)告诉工程这个头文件目录。

为了将程序更智能一点,我们可以使用 CMAKE_INCLUDE_PATH 来进行,使用bash 的方法如下:

export CMAKE_INCLUDE_PATH=/usr/include/hello
然后在头文件中将INCLUDE_DIRECTORIES(/usr/include/hello)替换为:

FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)

FIND_PATH 用来在指定路径中搜索文件名,比如:

FIND_PATH(myHeader NAMES hello.h PATHS /usr/include /usr/include/hello)

这里我们没有指定路径,但是,cmake 仍然可以帮我们找到hello.h 存放的路径,就是因为我们设置了环境变量CMAKE_INCLUDE_PATH。

如果不使用FIND_PATH,CMAKE_INCLUDE_PATH 变量的设置是没有作用的,不能指望它会直接为编译器命令添加参数-I < CMAKE_INCLUDE_PATH >。

以此为例,CMAKE_LIBRARY_PATH 可以用在 FIND_LIBRARY 中。

同样,因为这些变量直接为FIND_指令所使用,所以所有使用FIND_指令的cmake 模块都会受益。

总结

  1. 通过ADD_LIBRARY 指令构建动态库和静态库。
  2. 通过SET_TARGET_PROPERTIES 同时构建同名的动态库和静态库。
  3. 通过SET_TARGET_PROPERTIES 控制动态库版本。
  4. 使用INSTALL 指令来安装头文件和动态、静态库。
  5. 通过INCLUDE_DIRECTORIES 指令加入非标准的头文件搜索路径。
  6. 通过LINK_DIRECTORIES 指令加入非标准的库文件搜索路径。
  7. 通过TARGET_LINK_LIBRARIES 为库或可执行二进制加入库链接。
  8. CMake链接到静态库的方式。
  9. CMake还有很多高级的话题没有探讨,比如编译条件检查、编译器定义、平台判断、如何跟 pkgconfig 配合使用等等。
  10. cmake 的使用过程其实就是学习cmake 语言并编写 cmake 程序的过程,既然是“cmake 语言”,自然涉及到变量、语法等。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/360515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

leetcode 11~20 学习经历

LeetCode 习题 11 - 2011. 盛最多水的容器12. 整数转罗马数字13. 罗马数字转整数14. 最长公共前缀15. 三数之和16. 最接近的三数之和17. 电话号码的字母组合18. 四数之和19. 删除链表的倒数第 N 个结点20. 有效的括号小结11. 盛最多水的容器 给定一个长度为 n 的整数数组 heigh…

【Servlet篇】Request请求转发详细解读

文章目录1. 前言2. 实战案例3. 特点1. 前言 请求转发是一种在服务器内部的资源跳转方式&#xff0c;如图&#xff1a; 上图的大致过程为&#xff0c;浏览器发送请求给服务器&#xff0c;服务器中 a 资源接收到请求&#xff0c;资源 a 处理完请求后将请求发送给资源 b&#xff…

cdr最新2023版本发布更新及CorelDraw功能介绍

CDR作为一款专业的平面设计软件&#xff0c;拥有着庞大的用户群体&#xff0c;而每年春天CorelDRAW新版本的发布也牵动着每一位小伙伴的心。CorelDraw2023近期刚刚发布本人就开始试用&#xff0c;感觉非常良好&#xff0c;特别给大家提出升级的N个理由!CorelDRAW2023最新版内置…

【基础算法】差分的应用(一维差分和二维差分)

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

FPGA 20个例程篇:20.USB2.0/RS232/LAN控制并行DAC输出任意频率正弦波、梯形波、三角波、方波(一)

在最后一个例程中笔者精挑细选了一个较为综合性的项目实战&#xff0c;其中覆盖了很多知识点&#xff0c;也是从一个转产产品中所提炼出来的&#xff0c;所以非常贴近实战项目。 整个工程实现了用户通过对上位机PC端人机界面的操作&#xff0c;即可达到控制豌豆开发并行DAC输出…

Java---高级流

目录 一、转换流 &#xff08;1&#xff09;指定的字符集读写数据 二、序列化流和反序列化流 三、解压缩流和压缩流 &#xff08;1&#xff09;解压缩流 &#xff08;2&#xff09;压缩流 一&#xff1a;压缩文件 二&#xff1a;压缩文件夹 注&#xff1a;本文并未介绍J…

CVE-2023-24055 KeePass信息明文传输漏洞复现

前言 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 如果文章中的漏洞出现敏感内容产生了部分影响&#xff0c;请及时联系作者&#xff0c;望谅解。 一、漏洞描述 漏洞简述 Kee…

STM32 SystemInit()函数学习总结

拿到程序后如何看系统时钟&#xff1f;User文件夹——system_stm32f4xx程序&#xff0c;先找systemcoreclock(系统时钟&#xff09;但是这里这么多个系统时钟应该如何选择?点击魔法棒&#xff0c;然后点击C/C可以看到define的是F40_41XXX.USE这一款 &#xff0c;对应着就找出了…

R语言、MaxEnt模型融合技术的物种分布模拟、参数优化方法、结果分析制图与论文写作

基于R语言、MaxEnt模型融合技术的物种分布模拟、参数优化方法、结果分析制图与论文写作技术应用第一章、理论篇以问题导入的方式&#xff0c;深入掌握原理基础什么是MaxEnt模型&#xff1f;MaxEnt模型的原理是什么&#xff1f;有哪些用途&#xff1f;MaxEnt运行需要哪些输入文件…

对云原生集群网络流量可观测性的一点思考

问题背景 在云原生技术的广泛普及和实施过程中&#xff0c;笔者接触到的很多用户需求里都涉及到对云原生集群的可观测性要求。 实现集群的可观测性&#xff0c;是进行集群安全防护的前提条件 。而在可观测性的需求中&#xff0c;集群中容器和容器之间网络流量的可观测性需求是…

别错过!4C首发直播,上届全国总冠军带你入门赛题

和志同道合的伙伴并肩作战&#xff0c;用指尖敲出奇思妙想&#xff0c;飞桨黑客马拉松PaddlePadddle Hackathon第四期全新升级&#xff0c;开放报名啦&#xff01; 玩技术&#xff0c;秀操作&#xff01;这是一场高手云集的开发者盛会。四大赛道&#xff1a;核心框架开源贡献&…

Python每日一练(20230221)

目录 1. 不同路径 II 2. 字符串转换整数 (atoi) 3. 字符串相乘 1. 不同路径 II 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为“Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中…

一文讲明Hystrix熔断器

前言 解决问题: 主要防止服务器集群发生雪崩, 起到对服务器的保护作用 GitHub地址&#xff1a;https://github.com/Netflix/Hystrix/wiki 1 Hystrix是什么 1.1 分布式系统面临的问题 复杂分布式体系结构中的应用程序有数十个依赖关系&#xff0c;每个依赖关系在某些时候将不…

Milvus 群星闪耀时|又一个小目标达成 :社区正式突破 15,000 星!

如果把 Milvus 看作开源世界中的一束微光&#xff0c;那用户便是无垠宇宙中点点闪烁的星光。用户每一次点亮 star 之时&#xff0c;Milvus 就会迸发出更加耀眼的光芒。不知不觉&#xff0c;已有数以万计的 star 为 Milvus 而亮。2022 年 4 月&#xff0c;Milvus 在 GitHub 的 …

【C++】-- C++11基础常用知识点(下)

上篇&#xff1a; 【C】-- C11基础常用知识点&#xff08;上&#xff09;_川入的博客-CSDN博客 目录 新的类功能 默认成员函数 可变参数模板 可变参数 可变参数模板 empalce lambda表达式 C98中的一个例子 lambda表达式 lambda表达式语法 捕获列表 lambda表达底层 …

【Pytorch项目实战】基于PaddlenHub的口罩检测与语音提示

文章目录一、项目思路二、环境配置1.1、PaddlenHub模块&#xff08;飞桨预训练模型应用工具&#xff09;&#xff08;1&#xff09;预训练模型&#xff1a;pyramidbox_lite_mobile_mask&#xff08;2&#xff09;face_detection人脸检测模型&#xff08;默认为 pyramidbox_lite…

TCP的三次握手、四次挥手

文章目录前言一、一些重要字段的含义二、TCP总括图三、三次握手详细过程1.第一次握手2.第二次握手3.第三次握手三次握手小结4.为什么必须要进行三次握手&#xff0c;两次或四次就不行四、四次挥手1.第一次挥手2.第二次挥手3.第三次挥手4.第四次挥手四次挥手简述前言 一个TCP的…

MySQL中自带的数据库表相关介绍

mysql的自带数据库表主要有以下几个&#xff1a; &#xff08;1&#xff09;information_schema &#xff08;2&#xff09;performance_schema &#xff08;3&#xff09;mysql &#xff08;4&#xff09;sys &#xff08;5&#xff09;可能存在空数据库test 一、informa…

预告|2月25日 第四届OpenI/O 启智开发者大会昇腾人工智能应用专场邀您共启数字未来!

如今&#xff0c;人工智能早已脱离科幻小说中的虚构想象&#xff0c;成为可触及的现实&#xff0c;并渗透到我们的生活。随着人工智能的发展&#xff0c;我们正在迎来一个全新的时代——数智化时代。数据、信息和知识是这个时代的核心资源&#xff0c;而人工智能则是这些资源的…

【TensorFlow 】查看Tensorflow和python对应版本、将现有的TensorFlow更新到指定的版本

1、查看Tensorflow和python对应版本 1.1这里我是在TensorFlow官方网址产看的 1、打开官方网址 https://pypi.org/project/tensorflow/1.1.0rc2/#files但是这个网址好像打不开&#xff0c;点击会出现这样 问题不大 输入Tensorflow然后点击搜索&#xff0c;就会跳转到https://p…