2 pygraphviz在windows10 64位下的安装问题(反斜杠的血案)

Posted xiaojieshisilang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2 pygraphviz在windows10 64位下的安装问题(反斜杠的血案)相关的知识,希望对你有一定的参考价值。

可以负责任的说,这篇文档是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上还是找到了如下版本:

可惜,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版本。下载界面在:

https://visualstudio.microsoft.com/zh-hans/thank-you-downloading-visual-studio/?sku=Professional&rel=15&rr=https%3A%2F%2Fdocs.microsoft.com%2Fzh-cn%2Fvisualstudio%2Freleasenotes%2Fvs2017-relnotes#

 

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.0VCBINx86_amd64cl.exe对文件进行编译的时候,里面已经添加了"-IC:Program Files (x86)Graphviz2.38include"的头文件路径,解决了之前头文件缺失的问题。

但是,在执行后续C:Program Files (x86)Microsoft Visual Studio 14.0VCBINx86_amd64link.exe
即链接所有库,生成可执行程序的时候,却提示无法打开cdt.lib文件,我们看到这里面的寻址路径中并没有之前修改setup.py文件中添加的那句话:

library_dirs=[C:Program Files (x86)Graphviz2.38lib
eleaselib]

但是,修改setup.py文件时添加的include_dirs=[‘C:Program Files (x86)Graphviz2.38include‘]

确实生效了。

为什么在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.0VCBINx86_amd64cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD "-IC:Program Files (x86)Graphviz2.38include" -IC:ProgramDataAnaconda3include -IC:ProgramDataAnaconda3include "-IC:Program Files (x86)Microsoft Visual Studio 14.0VCINCLUDE" "-IC:Program Files (x86)Windows Kits10include10.0.10240.0ucrt" "-IC:Program Files (x86)Windows Kits8.1includeshared" "-IC:Program Files (x86)Windows Kits8.1includeum" "-IC:Program Files (x86)Windows Kits8.1includewinrt" /Tcpygraphviz/graphviz_wrap.c /Fobuild emp.win-amd64-3.6Releasepygraphviz/graphviz_wrap.obj

现在,继续在build_extension中跟入:

技术分享图片

这个就是链接过程。也就是在python程序中调用c编译器编译c目标程序时,会执行的函数。

我们跟进去。

ipdb> print (ext.library_dirs)
[‘C:\\Program Files (x86)\\Graphviz2.38\\lib elease\\lib‘]

这也说明,确确实实是传入进去了。我们进入这个函数:

一直跟到里面的一个link函数时,我们发现有问题!

技术分享图片

 也就是,传入的library_dirs已经变成了

C:Program Files (x86)Graphviz2.38lib

eleaselib

这是什么鬼!!!

技术分享图片

里面有一个_fix_lib_args的操作,但是可以看到:

lib路径已经错了。本来应该是lib\\releaselib的,却变成了libeleaselib了。

 然后一直进入到:

 self.spawn([self.linker] + ld_args)

我们可以输出如下:

技术分享图片

结果这里面输出的ld_args竟然是:

C:\\Program Files (x86)\\Graphviz2.38\\lib elease\\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 elease\\lib‘

这是因为,你没有双击开。你双击开看到的是:

技术分享图片

看到了吗?这是windows下的换行符号 。在控制台输出的时候变成了 。

在spyder中查看的时候,是换行。

而实际上:

目前为止,真正传入的就是这么个玩意:

/LIBPATH:C:Program Files (x86)Graphviz2.38lib

eleaselib

也就是说,是换行符号 。

我们双击点开libopts也是一样的。libopts是构成ld_args的重要组成。所以,后面的就不用跟了。

link程序必然出错。因为找不到cdt.lib文件。所以,link.exe程序必然失败,报出1181错误。

现在已经知道怎么改了,就是把setup.py中路径的反斜杠,全部改为斜杠。但是我不能容忍不知道为什么传递的时候出错。

为什么在某个环节C:\\Program Files (x86)\\Graphviz2.38\\lib elease\\lib

变成了C:\\Program Files (x86)\\Graphviz2.38\\lib elease\\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
elease\\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.38lib
eleaselib],

那么出于某种原因,python系统,会将 看作换行符。因此,在转变的过程中:

本来是要将处理成\\的,但是这个 被遗漏了。于是就产生了如下“扭曲”的地址

C:\\Program Files (x86)\\Graphviz2.38\\lib
elease\\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.38lib eleaselib"

with

r"C:Program Files (x86)Graphviz2.38lib eleaselib"

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
-mastergraphviz-2.38_x64lib放置到: graphviz的msi安装程序之后的release的lib下面,而不是直接的lib下面C:Program Files (x86)Graphviz2.38lib eleaselib。 但是,仅仅覆盖lib文件时不够的。否则在后续的测试程序中,import pygraphviz的时候回报错提示,dll win32位的有问题。 所以,除了lib路径需要特殊处理以外,需要把附件中GraphViz_x64-mastergraphviz-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.ipygraphviz/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.38in下的neato,会报出异常。

如何解决,见文末总结。

附件

 链接:https://pan.baidu.com/s/18VKkVj_CupmvFwihdHwH8Q 密码:aqr7

总结,全部安装过程

  1. 安装graphviz-2.38.msi,这是官网提供的graphviz文件,安装位置是:C:Program Files (x86)Graphviz2.38
  2. 安装vc14编译器,在visualcppbuildtools_full中,前提是安装了visual studio2017版本。
  3. pygraphviz-master的setup.py文件中按照前述指导,添加头文件和库文件目录路径。或者直接替换为我附件中提供的setup.py文件
  4. 如果此时直接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安装后的文件目录。
  5. 然后将,附件中的H:安装包和补丁文件GraphViz_x64-masterGraphViz_x64-mastergraphviz-2.38_x64完全复制到C:Program Files (x86)下,并且改名为Graphviz2.38,同时将lib目录下的所有文件复制到lib eleaselib目录中。
  6. 此时,再python setup.py install安装会提示:LNK2001: unresolved external symbol PyIOBase_Type。按照前述指导,将附件中我按照前述网址指导的patch修改后的graphviz_wrap.c和graphviz.i文件替换到H:pygraphviz-masterpygraphviz中去。
  7. 再次运行python setup.py install文件。成功。
  8. 运行前述测试程序。会提示:ValueError: Program neato not found in path。因为我提供的64位的graphviz中没有这个neato文件。事实上,我查了一下,gvplugin_neato_layout.dll文件是有的。唯独缺失neato.exe文件。我直接怀疑是https://github.com/mahkoCosmo/GraphViz_x64/的作者有意而为之。这种事情以前读研的时候遇到,BAP(一个二进制分析平台)的源码,我曾经一直在研究。后来全部源码被下架。实话讲,国内很多项目都是参照国外的开源代码。如果国外搞技术封锁,呵呵。。。。。。
  9. 如何解决?直接将C:Program Files (x86)Graphviz2.38_msiin写入系统的path变量中。然后重启spyder和Anaconda,即可。
  10. 得到最后的结果。结束。
  11. 有人会问,两个版本的graphviz(即一个国外提供的免安装版本,还一个是官网的版本)并存,会有影响吗?我的解释是,那就看pygraphviz是怎么写的了。但是到目前为止,没有遇到错误。编译和链接pygraphviz中的graphviz_wrap.c文件的时候,需要国外某作者提供的免安装版本的lib文件,官网的有缺失(或者说官网的64位版本能够支持pygraphviz需求的,已经找不到资源)。所以就必须要用国外提供的免安装版本。但是,国外免安装版本中的neato文件又被蓄意删去。这个时候,实际程序运行的过程中,pygraphviz会调用neato文件。因此,我们就把官网graphviz安装包安装后的路径写入到系统Path变量中。这样就能调用其neato程序。
  12. 如果有人还不明白的话,我这样解释吧。假如你只是将GraphViz_x64-master中的lib文件修改到C:Program Files (x86)Graphviz2.38中的lib eleaselib目录下,你编译,链接,都不会有问题。但是,你再运行测试程序的时候,直接import pygraphviz都会出错,会提示“DLL 不是有效的win32程序”(注意,win32不是说就是32位。64位也说是win32。)。为什么?这是因为,官网提供的graphviz,安装在64位系统以后,也是32位的模块。而目前压根找不到64位的模块。于是,只好用国外某作者可能蓄意删除neato程序以后的graphviz_x64文件的全部内容。然后再将pygraphviz运行时可能缺失的neato文件用原始msi安装后的neato文件进行补充。
  13. 所以,就是两版本兼容。
  14. 总的来说,pygraphviz很强势。但是为什么做的,连win10 64位下的安装支持的都不够好。原因未知。当哪天突然有人不想开源了,想做成商业化了,我估计这种情况会更多。正如本文最初提到的,连whl文件的地址都失效了。这不是技术封锁,是什么。
  15. 最后,我的附件中提供pygraphviz-1.3.1-cp34-none-win_amd64.whl文件。仅限于win 64位系统,以及python 3.4上使用。我没有实验过。但是python 3.6版本是肯定不行。我相信大部分人不会只为了装一个pygraphviz,而把python降低为3.4版本。

技术分享图片

 




以上是关于2 pygraphviz在windows10 64位下的安装问题(反斜杠的血案)的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Windows 10 64 位上安装 pygraphviz

在 Windows 10 64 位、Python 3.6 上安装 pygraphviz

在 Windows 上安装 pygraphviz

Pygraphviz 在绘制 170 个图后崩溃

如何在 Windows 上从 pygraphviz 运行neato

windows下怎么安装pygraphviz?请各位帮帮忙