可以负责任的说,这篇文档是windows10安装pygraphviz中,在中文技术网站中最新的文档,没有之一。是自己完全结合各种问题,包括调试等,总结出来的。
问题来源:主要是可视化RvNN网络的树结构。
pygraphviz安装时,我参考了博文http://www.myexception.cn/perl-python/2046792.html。但是,文章的解决方案已经失效。
已有博文存在的问题:windows下pygraphviz‑1.3.1‑cp34‑none‑win_amd64.whl文件无法适用于python3.6版本
站在巨人的肩膀上。
前述作者在上述博文链接中阐述,windows10下安装pygraphviz要去http://www.lfd.uci.edu/~gohlke/pythonlibs/的地址下载python packages在windows平台上的安装包。
但是,现在这个资源已经404.
其提供的过程就是:先安装graphviz,然后使用如下命令安装pygraphviz
pip install pygraphviz‑1.3.1‑cp27‑none‑win_amd64.whl
很遗憾,虽然网上没有这些资源了,我在csdn上还是找到了如下版本:
- pygraphviz‑1.3.1‑cp34‑none‑win_amd64.whl
可惜,python是3.6,使用这个,依旧安装失败。
因为只在python3.4版本上才能使用。
基于pygraphviz源码包的方式安装
先安装graphviz-2.38.msi文件。是官网上的。
第一个问题:缺失vc14
去官网下载代码。
https://github.com/pygraphviz/
直接python setup.py install,会报出找不到vc14版本的相关错误。
但是信息不够详细。于是,spyder带参数install调试setup.py程序,看究竟是哪一步出了问题。
在spyder console键入:
然后一直跟踪到出错的位置:
调试pygraphviz的setup.py 并且带参数”install” 调试,以后发现,其出错函数在这里:
执行以后会提示:
那么对于这个函数,里面有这样一段说明:
Microsoft Visual C++ 14.0:
Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
也就是说:安装对应的即可。
找到了相应的Microsoft Visual C++ 14.0 builder的生成器。但是由于我的电脑安装了vs2015,因此产生冲突。所以,我升级visual studio为2017版本。下载界面在:
def msvc14_get_vc_env(plat_spec):
"""
Patched "distutils._msvccompiler._get_vc_env" for support extra
compilers.
Set environment without use of "vcvarsall.bat".
Known supported compilers
-------------------------
Microsoft Visual C++ 14.0:
Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
Parameters
----------
plat_spec: str
Target architecture.
Return
------
environment: dict
"""
# Try to get environment from vcvarsall.bat (Classical way)
try:
return get_unpatched(msvc14_get_vc_env)(plat_spec)
except distutils.errors.DistutilsPlatformError:
# Pass error Vcvarsall.bat is missing
pass
# If error, try to set environment directly
try:
return EnvironmentInfo(plat_spec, vc_min_ver=14.0).return_env()
except distutils.errors.DistutilsPlatformError as exc:
_augment_exception(exc, 14.0)
raise
因此,下载Microsoft Visual C++ 14.0并且安装。但是提示Visual studio2015版本和它不兼容。于是,又升级2015版本到2017版本,然后再执行这个程序。成功。这个程序我是从csdn上下载的,后续会放到本文末尾的附件链接当中。
第二个问题:缺失头文件
pygraphviz/graphviz_wrap.c(2987): fatal error C1083: Cannot open include file: 'graphviz/cgraph.h': No such file or directory
error: command 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\BIN\\x86_amd64\\cl.exe' failed with exit status 2
此时,修改setup.py文件,添加如下代码。这是因为pygraphviz要对一个graphviz_wrap.c文件进行编译,因此就要设置头文件和库文件的包含路径。
注意,实际lib是在release下。
第三个问题:cannot open input file 'cdt.lib'
虽然添加了头文件路径和库文件路径,但是会提示无法打开lib文件。
我们发现,错误是在执行running build_ext时,注意,ext就是extension的意思,也就是在python程序中调用编译器编译C语言的相关文件。
可以看到:C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe对文件进行编译的时候,里面已经添加了"-IC:\Program Files (x86)\Graphviz2.38\include"的头文件路径,解决了之前头文件缺失的问题。
但是,在执行后续C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\link.exe
即链接所有库,生成可执行程序的时候,却提示无法打开cdt.lib文件,我们看到这里面的寻址路径中并没有之前修改setup.py文件中添加的那句话:
library_dirs=['C:\Program Files (x86)\Graphviz2.38\lib\release\lib']
但是,修改setup.py文件时添加的include_dirs=['C:\Program Files (x86)\Graphviz2.38\include']
确实生效了。
为什么在setup.py中添加library_dirs之后,再调用VC的链接程序,并没有向指定库文件路径下寻找cdt.lib呢?
这是问题的根本所在。启动调试进程,调试setup.py文件。在spyder的console端键入如下:
debugfile('H:/pygraphviz-master-wrong2/setup.py', args='install', wdir='H:/pygraphviz-master-wrong2')
跟踪上图中setup函数中的执行,尤其是对ext_modules的数据处理。跟踪这个数据处理,就能找到哪里引用了include_dirs,哪里引用了library_dirs。
数据就是通道
我们需要找到输出异常的位置,离它越近越好,就像逼近真相。
查看出错时的输出信息,有如下最关键的地方:
- running build_ext
- building 'pygraphviz._graphviz' extension
然后跟踪程序执行,会在python的系统文件dist.py中有run_commands的函数
这里面就有running %s的输出。那么我相信关键进程就在cmd_obj.run()中。正是这个执行过程,里面出错。
所以,log.info处下断点,当输出running buid_ext之后,进入run的函数内部:run内部又会跟进到了build_ext.py文件的核心函数run中:
里面会对compiler编译器进行设置,一直到执行self.build_extensions函数。
跟入该函数,
然后,在console端调试:
这里面的extension的命名“pygraphviz._graphviz”和setup.py文件中指定的命名是一致的。
也就是说,到目前为止:
已经找到了对setup.py文件中extension进行处理的核心代码,继续跟踪就会知道library_dirs为什么会失效。
跟踪进入cython_sources函数,就会看到:sources在第一个for循环中正是extension除去名称之后的第一行。
我们在cython_sources中下断点,直到sources是library-dirs的那一行。
可惜,直接一次循环就报出了本文所出现的link.exe链接的错误。
于是,直接跟入build_extension函数,就第一次循环就跟进去:如下,
我们输出可以看到:
我们跟进compile函数,确实没有给library_dirs进行赋值的选项。
继续跟,跟完compile以后,发现compile只是进行了编译操作。
会输出:
ipdb> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD "-IC:\Program Files (x86)\Graphviz2.38\include" -IC:\ProgramData\Anaconda3\include -IC:\ProgramData\Anaconda3\include "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" /Tcpygraphviz/graphviz_wrap.c /Fobuild\temp.win-amd64-3.6\Release\pygraphviz/graphviz_wrap.obj
现在,继续在build_extension中跟入:
这个就是链接过程。也就是在python程序中调用c编译器编译c目标程序时,会执行的函数。
我们跟进去。
ipdb> print (ext.library_dirs)
['C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib']
这也说明,确确实实是传入进去了。我们进入这个函数:
一直跟到里面的一个link函数时,我们发现有问题!
也就是,传入的library_dirs已经变成了
C:\Program Files (x86)\Graphviz2.38\lib
elease\lib
这是什么鬼!!!
里面有一个_fix_lib_args的操作,但是可以看到:
lib路径已经错了。本来应该是lib\\release\lib的,却变成了libelease\lib了。
然后一直进入到:
self.spawn([self.linker] + ld_args)
我们可以输出如下:
结果这里面输出的ld_args竟然是:
C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib'
千万不要以为是正确的地址。正确的是C:\\Program Files (x86)\\Graphviz2.38\\lib\\release\\lib。
我们来观察一下:
ld_args = (ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename])
然后ld_args是:
猛地一看,还是错误的。为什么在console端用print(ld_args )输出的是:
C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib'
这是因为,你没有双击开。你双击开看到的是:
看到了吗?这是windows下的换行符号\r。在控制台输出的时候变成了\r。
在spyder中查看的时候,是换行。
而实际上:
目前为止,真正传入的就是这么个玩意:
/LIBPATH:C:\Program Files (x86)\Graphviz2.38\lib
elease\lib
也就是说,是换行符号\r。
我们双击点开libopts也是一样的。libopts是构成ld_args的重要组成。所以,后面的就不用跟了。
link程序必然出错。因为找不到cdt.lib文件。所以,link.exe程序必然失败,报出1181错误。
现在已经知道怎么改了,就是把setup.py中路径的反斜杠,全部改为斜杠。但是我不能容忍不知道为什么传递的时候出错。
为什么在某个环节C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib
变成了C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib
我们会发现是在调用link_shared_object的时候出的错误:
此时调试输出的是:
当跟入函数以后:
所以,错误就是ext.library_dirs生成的过程出错。
那么就要追踪ext.library_dirs是如何依据setup.py文件中的extension项目生成的。
所以,关键是找到调用build_extension函数的地方,并且看是谁把值传入了ext。
现在开始倒推:
def build_extensions(self):
# First, sanity-check the 'extensions' list
self.check_extensions_list(self.extensions)
for ext in self.extensions:
ext.sources = self.cython_sources(ext.sources, ext)
self.build_extension(ext)
在进入这个函数的时候,输出仍然是:
ipdb> print (ext.library_dirs)
['C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib']
所以,错误就在于生成self.extensions的过程中就已经注定了。
那么什么时候生成self.extensions的呢?
我们进入check_extensions_list函数,仍旧是输出的是错误的地址。
所以,错误的形成不是在build_extensions中。
而是在调用build_extensions之前,然后生成了self.extensions,里面包含了'扭曲"的地址。
那么就是build_ext类的问题了,因为它就是那个self。
我们通过观察build_ext的类结构,和快速的扫描代码,找到了。
它的finalize_options函数中对self.extensions进行了设置。
这个时候,刚刚执行完下面代码:
self.extensions = self.distribution.ext_modules
我们就在console端键入:
可以看出,已经错了。所以,错误的形成不是在self.extensions的生成中。
而是在self.distribution.ext_modules的生成中。于是,进一步倒推。
在build_ext中,并不能找到self.distribution的相关代码。我们发现,build_ext继承的是Command类。Command类中有self.distribution。
所以,我们就要关注于build_ext这个类对象的生成。
这个时候,就要重头开始调试,看哪个地方生成了build_ext这个类的对象引用。
- 调试到run_command函数内部,下断点,当执行完log.info("running %s", command)以后输出的是running build_ext,我们继续下一步
log.info("running %s", command) cmd_obj = self.get_command_obj(command) cmd_obj.ensure_finalized() cmd_obj.run()
肯定是在上面的三行代码中,完成了对build_ext类的生成,在这里完成了对self.distribution的操作。
首先跟入get_command_obj函数,感觉这个是最有可能对Command子类build_ext类对象的生成过程。因为从名字来看就是get_command_obj,并且注释是:
"""Return the command object for 'command'. Normally this object
is cached on a previous call to 'get_command_obj()'; if no command
object for 'command' is in the cache, then we either create and
return it (if 'create' is true) or return None.
"""
执行完第一行程序:
cmd_obj = self.command_obj.get(command)
由于之前是有:
self.extensions = self.distribution.ext_modules
所以我们直接在调试console输入:
结果发现:名称已经出错了!!!
那么问题肯定是在self.command_obj.get中了!!!它在生成build_ext类对象的时候,初始化父类Command的distribution成员时,就已经把地址”扭曲“了!!!
最遗憾的是!!!这个self.command_obj.get压根跟入不进去。很有可能代码不是开源的,只是作为功能提供在链接库当中。所以,这是一个bug。
什么bug???
呕血调试发现python的bug
就是在setup.py文件中的extension中写入library_dirs的时候,如果传入如下的地址:
library_dirs=['C:\Program Files (x86)\Graphviz2.38\lib\release\lib'],
那么出于某种原因,python系统,会将\r看作换行符。因此,在转变的过程中:
本来是要将\处理成\\的,但是这个\r被遗漏了。于是就产生了如下“扭曲”的地址
C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib
最后,python中编译(setup.py文件中extension指定)的c程序,就会出现找不到库的问题。
于是乎,报告这个bug的同时,建议全部用斜杠/。
提交了bug,和处理结果(被老外狠狠的鄙视了一把,他不认为是bug)
回复:
msg3530 (view) Author: berker.peksag Date: 2018-08-20.16:26:32
remove
> In windows, we always give a path using '\' and python 3 can correctly dispose
> it just as we using '/' in Linux. But if you offer
> a path in windows with a '\' followed as 'r'. Everyting will goes wrong.
You need to use raw strings to avoid this. Replace
"C:\Program Files (x86)\Graphviz2.38\lib\release\lib"
with
r"C:\Program Files (x86)\Graphviz2.38\lib\release\lib"
See https://blog.lerner.co.il/avoiding-windows-backslash-problems-with-pythons-raw-strings/ for more details about raw strings.
This tracker is for issues with bugs.python.org. Please use Stack Overflow or python-list to ask usage questions.
老外不承认是bug。说,windows反斜杠的处理,要加r。嗯嗯。就这样吧。
第四个问题:unresolved external symbol agwrite
解决方案见网址:
https://github.com/pygraphviz/pygraphviz/issues/58
这里面说用64位lib下的库文件覆盖到目录lib下即可。也就是说这些unresolved是缺失了一些库。可以看到是生成_graphviz.cp36_win_amd64.lib的时候出错。这是因为安装的graphviz是32位的模块,缺失了很多库文件。
这个库,会放在本文附件里。
接着问题就能排除。注意是将我附件中的\GraphViz_x64-master\graphviz-2.38_x64\lib放置到:
graphviz的msi安装程序之后的release的lib下面,而不是直接的lib下面C:\Program Files (x86)\Graphviz2.38\lib\release\lib。
但是,仅仅覆盖lib文件时不够的。否则在后续的测试程序中,import pygraphviz的时候回报错提示,dll win32位的有问题。
所以,除了lib路径需要特殊处理以外,需要把附件中GraphViz_x64-master\graphviz-2.38_x64中的
所有的内容全部覆盖到C:\Program Files (x86)\Graphviz2.38中去。(所以,你直接换个名字吧。具体见文末的总结)
倒数第二个问题:unresolved external symbol PyIOBase_Type
仍然是下面的这个网址:
https://github.com/pygraphviz/pygraphviz/issues/58
里面提到:(在网络海量信息中去伪存真)
最后在如下这个网址:
https://github.com/pygraphviz/pygraphviz/issues/74#issuecomment-238323405
中找到:
点击进去(https://github.com/Kagami/pygraphviz/commit/fe442dc16accb629c3feaf157af75f67ccabbd6e)
就是一个补丁文件:
按照补丁文件对graphviz.i和pygraphviz/graphviz_wrap.c进行修改(我一行一行对着补丁改的。。。应该有依据补丁的自动化修改工具)。修改后的文件见附件。
最后一个问题,测试程序时输出ValueError: Program neato not found in path。
import pygraphviz as pgv
A=pgv.AGraph()
A.add_edge(1,2)
A.add_edge(2,3)
A.add_edge(1,3)
print(A.string()) # print to screen
print("Wrote simple.dot")
A.write('simple.dot') # write to simple.dot
B=pgv.AGraph('simple.dot') # create a new graph from file
B.layout() # layout with default (neato)
B.draw('simple.png') # draw png
print("Wrote simple.png")
上面是测试程序。通过搜集资料可知,是因为neato的问题。双击C:\Program Files (x86)\Graphviz2.38\bin下的neato,会报出异常。
如何解决,见文末总结。
附件
链接:https://pan.baidu.com/s/18VKkVj\_CupmvFwihdHwH8Q 密码:aqr7
总结,全部安装过程
- 安装graphviz-2.38.msi,这是官网提供的graphviz文件,安装位置是:C:\Program Files (x86)\Graphviz2.38\
- 安装vc14编译器,在visualcppbuildtools_full中,前提是安装了visual studio2017版本。
- pygraphviz-master的setup.py文件中按照前述指导,添加头文件和库文件目录路径。或者直接替换为我附件中提供的setup.py文件
- 如果此时直接python setup.py install,会提示很多的 error LNK2001: unresolved external symbol错误。这个时候,是因为安装的graphviz2.38不是64位版本。这个时候,是因为缺少64位graphviz的相关lib文件。(而且截止到目前为止,我都没有在graphviz中找到相关的明确指明为64位的安装包。我提供的64位的目录文件是从github上一个网页提供的资源上下载的)这个时候,我们将C:\Program Files (x86)\Graphviz2.38中的Graphviz2.38直接改为Graphviz2.38_msi,表示这个目录下是原始官网graphviz-2.38.msi安装后的文件目录。
- 然后将,附件中的H:\安装包和补丁文件\GraphViz_x64-master\GraphViz_x64-master\graphviz-2.38_x64完全复制到C:\Program Files (x86)下,并且改名为Graphviz2.38,同时将lib目录下的所有文件复制到lib\release\lib目录中。
- 此时,再python setup.py install安装会提示:LNK2001: unresolved external symbol PyIOBase_Type。按照前述指导,将附件中我按照前述网址指导的patch修改后的graphviz_wrap.c和graphviz.i文件替换到H:\pygraphviz-master\pygraphviz中去。
- 再次运行python setup.py install文件。成功。
- 运行前述测试程序。会提示:ValueError: Program neato not found in path。因为我提供的64位的graphviz中没有这个neato文件。事实上,我查了一下,gvplugin_neato_layout.dll文件是有的。唯独缺失neato.exe文件。我直接怀疑是https://github.com/mahkoCosmo/GraphViz\_x64/的作者有意而为之。这种事情以前读研的时候遇到,BAP(一个二进制分析平台)的源码,我曾经一直在研究。后来全部源码被下架。实话讲,国内很多项目都是参照国外的开源代码。如果国外搞技术封锁,呵呵。。。。。。
- 如何解决?直接将C:\Program Files (x86)\Graphviz2.38_msi\bin写入系统的path变量中。然后重启spyder和Anaconda,即可。
- 得到最后的结果。结束。
- 有人会问,两个版本的graphviz(即一个国外提供的免安装版本,还一个是官网的版本)并存,会有影响吗?我的解释是,那就看pygraphviz是怎么写的了。但是到目前为止,没有遇到错误。编译和链接pygraphviz中的graphviz_wrap.c文件的时候,需要国外某作者提供的免安装版本的lib文件,官网的有缺失(或者说官网的64位版本能够支持pygraphviz需求的,已经找不到资源)。所以就必须要用国外提供的免安装版本。但是,国外免安装版本中的neato文件又被蓄意删去。这个时候,实际程序运行的过程中,pygraphviz会调用neato文件。因此,我们就把官网graphviz安装包安装后的路径写入到系统Path变量中。这样就能调用其neato程序。
- 如果有人还不明白的话,我这样解释吧。假如你只是将GraphViz_x64-master中的lib文件修改到C:\Program Files (x86)\Graphviz2.38中的lib\release\lib目录下,你编译,链接,都不会有问题。但是,你再运行测试程序的时候,直接import pygraphviz都会出错,会提示“DLL 不是有效的win32程序”(注意,win32不是说就是32位。64位也说是win32。)。为什么?这是因为,官网提供的graphviz,安装在64位系统以后,也是32位的模块。而目前压根找不到64位的模块。于是,只好用国外某作者可能蓄意删除neato程序以后的graphviz_x64文件的全部内容。然后再将pygraphviz运行时可能缺失的neato文件用原始msi安装后的neato文件进行补充。
- 所以,就是两版本兼容。
- 总的来说,pygraphviz很强势。但是为什么做的,连win10 64位下的安装支持的都不够好。原因未知。当哪天突然有人不想开源了,想做成商业化了,我估计这种情况会更多。正如本文最初提到的,连whl文件的地址都失效了。这不是技术封锁,是什么。
- 最后,我的附件中提供pygraphviz-1.3.1-cp34-none-win_amd64.whl文件。仅限于win 64位系统,以及python 3.4上使用。我没有实验过。但是python 3.6版本是肯定不行。我相信大部分人不会只为了装一个pygraphviz,而把python降低为3.4版本。