溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么實現(xiàn)Go Module依賴關(guān)系的可視化

發(fā)布時間:2021-06-24 11:17:09 來源:億速云 閱讀:225 作者:chen 欄目:大數(shù)據(jù)

這篇文章主要介紹“怎么實現(xiàn)Go Module依賴關(guān)系的可視化”,在日常操作中,相信很多人在怎么實現(xiàn)Go Module依賴關(guān)系的可視化問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么實現(xiàn)Go Module依賴關(guān)系的可視化”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!


最近,我開發(fā)了一個非常簡單的小工具,總的代碼量 200 行不到。今天,簡單介紹下它。這是個什么工具呢?它是一個用于可視化展示 Go Module 依賴關(guān)系的工具。

為何開發(fā)

為什么會想到開發(fā)這個工具?主要有兩點原因:

一是最近經(jīng)??吹酱蠹以谏鐓^(qū)討論 Go Module。于是,我也花了一些時間研究了下。期間,遇到了一個需求,如何清晰地識別模塊中依賴項之間的關(guān)系。一番了解后,發(fā)現(xiàn)了 go mod graph。

效果如下:

$ go mod graph
github.com/poloxue/testmod golang.org/x/text@v0.3.2
github.com/poloxue/testmod rsc.io/quote/v3@v3.1.0
github.com/poloxue/testmod rsc.io/sampler@v1.3.1
golang.org/x/text@v0.3.2 golang.org/x/tools@v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote/v3@v3.1.0 rsc.io/sampler@v1.3.0
rsc.io/sampler@v1.3.1 golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c
rsc.io/sampler@v1.3.0 golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c

每一行的格式是 模塊 依賴模塊,基本能滿足要求,但總覺得還是不那么直觀。

二是我之前手里有一個項目,包管理一直用的是 dep。于是,我也了解了下它,把官方文檔仔細讀了一遍。其中的某個章節(jié)介紹了依賴項可視化展示的方法。

文檔中給出的包關(guān)系圖:

<center> <img src="https://blogimg.poloxue.com/0014-go-mod-graph-visible-02.png"> </center>

看到這張圖的時候,眼睛瞬間就亮了,圖形化就是優(yōu)秀,不同依賴之間的關(guān)系一目了然。這不就是我想要的效果嗎?666,點個贊。

但 ...,隨之而來的問題是,go mod 沒這個能力啊。怎么辦?

如何實現(xiàn)

先看看是不是已經(jīng)有人做了這件事了。網(wǎng)上搜了下,沒找到。那是不是能自己實現(xiàn)?應(yīng)該可以借鑒下 dep 的思路吧?

如下是 dep 依賴實現(xiàn)可視化的方式:

# linux
$ sudo apt-get install graphviz
$ dep status -dot | dot -T png | display

# macOS
$ brew install graphviz
$ dep status -dot | dot -T png | open -f -a /Applications/Preview.app

# Windows
> choco install graphviz.portable
> dep status -dot | dot -T png -o status.png; start status.png

這里展示了三大系統(tǒng)下的使用方式,它們都安裝了一個軟件包,graphviz。從名字上看,這應(yīng)該是一個用來實現(xiàn)可視化的軟件,即用來畫圖的。事實也是這樣,可以看看它的官網(wǎng)。

再看下它的使用,發(fā)現(xiàn)都是通過管道命令組合的方式,而且前面的部分基本相同,都是 dep status -dot | dot -T png。后面的部分在不同的系統(tǒng)就不同了,Linux 是 display,MacOS 是 open -f -a /Applications/Preview.app,Window 是 start status.png。

稍微分析下就會明白,前面是生成圖片,后面是顯示圖片。因為不同系統(tǒng)的圖片展示命令不同,所以后面的部分也就不同了。

現(xiàn)在關(guān)心的重點在前面,即 dep status -dot | dot -T png 干了啥,它究竟是如何實現(xiàn)繪圖的?大致猜測,dot -T png 是由 dep status -dot 提供的數(shù)據(jù)生成圖片。繼續(xù)看下 dep status -dot 的執(zhí)行效果吧。

$ dep status -dot
digraph {
	node [shape=box];
	2609291568 [label="github.com/poloxue/hellodep"];
	953278068 [label="rsc.io/quote\nv3.1.0"];
	3852693168 [label="rsc.io/sampler\nv1.0.0"];
	2609291568 -> 953278068;
	953278068 -> 3852693168;
}

咋一看,輸出的是一段看起來不知道是啥的代碼,這應(yīng)該是 graphviz 用于繪制圖表的語言。那是不是還有學(xué)習(xí)下?當(dāng)然不用啊,這里用的很簡單,直接套用就行了。

試著分析一下吧,前面兩行可以不用關(guān)心,這應(yīng)該是 graphviz 特定的寫法,表示要畫的是什么圖。我們主要關(guān)心如何將數(shù)據(jù)以正確形式提供出來。

2609291568 [label="github.com/poloxue/hellodep"];
953278068 [label="rsc.io/quote\nv3.1.0"];
3852693168 [label="rsc.io/sampler\nv1.0.0"];
2609291568 -> 953278068;
953278068 -> 3852693168;

一看就知道,這里有兩種結(jié)構(gòu),分別是為依賴項關(guān)聯(lián) ID ,和通過 ID 和 -> 表示依賴間的關(guān)系。

按上面的猜想,我們可以試著畫出一個簡單的圖, 用于表示 a 模塊依賴 b 模塊。執(zhí)行命令如下,將繪圖代碼通過 each 管道的方式發(fā)送給 dot 命令。

$ echo 'digraph {
node [shape=box];
1 [label="a"];
2 [label="b"];
1 -> 2;
}' | dot -T png | open -f -a /Applications/Preview.app

效果如下:

<center> <img src="https://blogimg.poloxue.com/0014-go-mod-graph-visible-03.png"/> </center>

繪制一個依賴關(guān)系圖竟然這么簡單。

看到這里,是不是發(fā)現(xiàn)問題已經(jīng)變得非常簡單了。我們只要將 go mod graph 的輸出轉(zhuǎn)化為類似的結(jié)構(gòu)就能實現(xiàn)可視化了。

開發(fā)流程介紹

接下來,開發(fā)這個小程序吧,我將這個小程序命名為 modv,即 module visible 的意思。項目源碼位于 poloxue/modv。

接收管道的輸入

先要檢查數(shù)據(jù)輸入管道是否正常。

我們的目標(biāo)是使用類似 dep 中作圖的方式,go mod graph 通過管道將數(shù)據(jù)傳遞給 modv。因此,要先檢查 os.Stdin,即檢查標(biāo)準(zhǔn)輸入狀態(tài)是否正常, 以及是否是管道傳輸。

下面是 main 函數(shù)的代碼,位于 main.go 中。

func main() {
	info, err := os.Stdin.Stat()
	if err != nil {
		fmt.Println("os.Stdin.Stat:", err)
		PrintUsage()
		os.Exit(1)
	}

	// 是否是管道傳輸
	if info.Mode()&os.ModeNamedPipe == 0 {
		fmt.Println("command err: command is intended to work with pipes.")
		PrintUsage()
		os.Exit(1)
	}

一旦確認輸入設(shè)備一切正常,我們就可以進入到數(shù)據(jù)讀取、解析與渲染的流程了。

	mg := NewModuleGraph(os.Stdin)
	mg.Parse()
	mg.Render(os.Stdout)
}

接下來,開始具體看看如何實現(xiàn)數(shù)據(jù)的處理流程。

抽象實現(xiàn)結(jié)構(gòu)

先定義一個結(jié)構(gòu)體,并大致定義整個流程。

type ModGraph struct {
	Reader io.Reader  // 讀取數(shù)據(jù)流
}

func NewModGraph(r io.Reader) *ModGraph {
    return &ModGraph{Reader: r}
}

// 執(zhí)行數(shù)據(jù)的處理轉(zhuǎn)化
func (m *ModGraph) Parse() error {}

// 結(jié)果渲染與輸出
func (m *ModGraph) Render(w io.Writer) error {}

再看下 go mod graph 的輸出吧,如下:

github.com/poloxue/testmod golang.org/x/text@v0.3.2
github.com/poloxue/testmod rsc.io/quote/v3@v3.1.0
...

每一行的結(jié)構(gòu)是 模塊 依賴項?,F(xiàn)在的目標(biāo)是要它解析成下面這樣的結(jié)構(gòu):

digraph {
    node [shape=box];
    1 github.com/poloxue/testmod;
    2 golang.org/x/text@v0.3.2;
    3 rsc.io/quote/v3@v3.1.0;
    1 -> 2;
    1 -> 3;
}

前面說過,這里包含了兩種不同的結(jié)構(gòu),分別是模塊與 ID 關(guān)聯(lián)關(guān)系,以及模塊 ID 表示模塊間的依賴關(guān)聯(lián)。為 ModGraph 結(jié)構(gòu)體增加兩個成員表示它們。

type ModGraph struct {
	r io.Reader  // 數(shù)據(jù)流讀取實例,這里即 os.Stdin
 
	// 每一項名稱與 ID 的映射
	Mods         map[string]int
	// ID 和依賴 ID 關(guān)系映射,一個 ID 可能依賴多個項
	Dependencies map[int][]int
}

要注意的是,增加了兩個 map 成員后,記住要在 NewModGraph 中初始化下它們。

mod graph 輸出解析

如何進行解析?

介紹到這里,目標(biāo)已經(jīng)很明白了。就是要將輸入數(shù)據(jù)解析到 ModsDependencies 兩個成員中,實現(xiàn)代碼都在 Parse 方法中。

為了方便進行數(shù)據(jù)讀取,首先,我們利用 bufio 基于 reader 創(chuàng)建一個新的 bufReader,

func (m *ModGraph) Parse() error {
	bufReader := bufio.NewReader(m.Reader)
	...

為便于按行解析數(shù)據(jù),我們通過 bufReader 的 ReadBytes() 方法循環(huán)一行一行地讀取 os.Stdin 中的數(shù)據(jù)。然后,對每一行數(shù)據(jù)按空格切分,獲取到依賴關(guān)系的兩項。代碼如下:

for {
	relationBytes, err := bufReader.ReadBytes('\n')
	if err != nil {
		if err == io.EOF {
			return nil
		}
		return err
	}

    relation := bytes.Split(relationBytes, []byte(" "))
    // module and dependency
    mod, depMod := strings.TrimSpace(string(relation[0])), strings.TrimSpace(string(relation[1]))

    ...
}

接下來,就是將解析出來的依賴關(guān)系組織到 ModsDependencies 兩個成員中。模塊 ID 是生成規(guī)則采用的是最簡單的實現(xiàn)方式,從 1 自增。實現(xiàn)代碼如下:

modId, ok := m.Mods[mod]
if !ok {
	modId = serialID
	m.Mods[mod] = modId
	serialID += 1
}

depModId, ok := m.Mods[depMod]
if !ok {
	depModId = serialID
	m.Mods[depMod] = depModId
	serialID += 1
}

if _, ok := m.Dependencies[modId]; ok {
	m.Dependencies[modId] = append(m.Dependencies[modId], depModId)
} else {
	m.Dependencies[modId] = []int{depModId}
}

解析的工作到這里就結(jié)束了。

渲染解析的結(jié)果

這個小工具還剩下最后一步工作要做,即將解析出來的數(shù)據(jù)渲染出來,以滿足 graphviz 工具的作圖要求。實現(xiàn)代碼是 Render部分:

首先,定義一個模板,以生成滿足要求的輸出格式。

var graphTemplate = `digraph {
node [shape=box];
{{ range $mod, $modId := .mods -}}
{{ $modId }} [label="{{ $mod }}"];
{{ end -}}
{{- range $modId, $depModIds := .dependencies -}}
{{- range $_, $depModId := $depModIds -}}
{{ $modId }} -> {{ $depModId }};
{{  end -}}
{{- end -}}
}
`

這一塊沒啥好介紹的,主要是要熟悉 Go 中的 text/template 模板的語法規(guī)范。為了展示友好,這里通過 - 實現(xiàn)換行的去除,整體而言不影響閱讀。

接下來,看 Render 方法的實現(xiàn),把前面解析出來的 ModsDependencies 放入模板進行渲染。

func (m *ModuleGraph) Render(w io.Writer) error {
	templ, err := template.New("graph").Parse(graphTemplate)
	if err != nil {
		return fmt.Errorf("templ.Parse: %v", err)
	}

	if err := templ.Execute(w, map[string]interface{}{
		"mods":         m.Mods,
		"dependencies": m.Dependencies,
	}); err != nil {
		return fmt.Errorf("templ.Execute: %v", err)
	}

	return nil
}

現(xiàn)在,全部工作都完成了。最后,將這個流程整合到 main 函數(shù)。接下來就是使用了。

使用體驗

開始體驗下吧。補充一句,這個工具,我現(xiàn)在只測試了 Mac 下的使用,如有問題,歡迎提出來。

首先,要先安裝一下 graphviz,安裝的方式在本文開頭已經(jīng)介紹了,選擇你的系統(tǒng)安裝方式。

接著是安裝 modv,命令如下:

$ go get github.com/poloxue/modv

安裝完成!簡單測試下它的使用。

以 MacOS 為例。先下載測試庫,github.com/poloxue/testmod。 進入 testmod 目錄執(zhí)行命令:

$ go mod graph | modv | dot -T png | open -f -a /Applications/Preview.app

如果執(zhí)行成功,將看到如下的效果:

怎么實現(xiàn)Go Module依賴關(guān)系的可視化

完美地展示了各個模塊之間的依賴關(guān)系。

一些思考

本文是篇實踐性的文章,從一個簡單想法到成功呈現(xiàn)出一個可以使用的工具。雖然,開發(fā)起來并不難,從開發(fā)到完成,僅僅花了一兩個小時。但我的感覺,這確實是個有實際價值的工具。

還有一些想法沒有實現(xiàn)和驗證,比如一旦項目較大,是否可以方便的展示某個指定節(jié)點的依賴樹,而非整個項目。還有,在其他項目向 Go Module 遷移的時候,這個小工具是否能產(chǎn)生一些價值。

到此,關(guān)于“怎么實現(xiàn)Go Module依賴關(guān)系的可視化”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI