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

能够负责任的说,这篇文档是windows10安装pygraphviz中,在中文技术网站中最新的文档,没有之一。是本身彻底结合各类问题,包括调试等,总结出来的。html

问题来源:主要是可视化RvNN网络的树结构。python

pygraphviz安装时,我参考了博文http://www.myexception.cn/perl-python/2046792.html。可是,文章的解决方案已经失效。git

已有博文存在的问题:windows下pygraphviz‑1.3.1‑cp34‑none‑win_amd64.whl文件没法适用于python3.6版本

站在巨人的肩膀上。github

前述做者在上述博文连接中阐述,windows10下安装pygraphviz要去http://www.lfd.uci.edu/~gohlke/pythonlibs/的地址下载python packages在windows平台上的安装包。windows

可是,如今这个资源已经404.网络

其提供的过程就是:先安装graphviz,而后使用以下命令安装pygraphviz函数

pip install pygraphviz‑1.3.1‑cp27‑none‑win_amd64.whl

很遗憾,虽然网上没有这些资源了,我在csdn上仍是找到了以下版本:工具

惋惜,python是3.6,使用这个,依旧安装失败。visual-studio

 

由于只在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.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.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.38\bin下的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-master\GraphViz_x64-master\graphviz-2.38_x64彻底复制到C:\Program Files (x86)下,而且更名为Graphviz2.38,同时将lib目录下的全部文件复制到lib\release\lib目录中。
  6. 此时,再python setup.py install安装会提示:LNK2001: unresolved external symbol PyIOBase_Type。按照前述指导,将附件中我按照前述网址指导的patch修改后的graphviz_wrap.c和graphviz.i文件替换到H:\pygraphviz-master\pygraphviz中去。
  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_msi\bin写入系统的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\release\lib目录下,你编译,连接,都不会有问题。可是,你再运行测试程序的时候,直接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版本。

相关文章
相关标签/搜索