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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM