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版本。

相關文章
相關標籤/搜索