欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Go?依賴管理?go?mod?tidy

 更新時(shí)間:2022年10月31日 17:26:28   作者:午夜游民  
這篇文章主要為大家介紹了詳解Go?依賴管理?go?mod?tidy,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

go mod tidy的作用是把項(xiàng)目所需要的依賴添加到go.mod,并刪除go.mod中,沒有被項(xiàng)目使用的依賴。

Tidy makes sure go.mod matches the source code in the module.
It adds any missing modules necessary to build the current module's
packages and dependencies, and it removes unused modules that
don't provide any relevant packages. It also adds any missing entries
to go.sum and removes any unnecessary ones.

接下來我們將深入源碼研究go mod tidy的執(zhí)行過程

  • 版本 go 1.18
  • 編輯器 vscode

Debug準(zhǔn)備

源碼的位置

輸入命令行go env,找到GOROOT這一項(xiàng)(go的安裝路徑)

路徑${GOROOT}/src/cmd/go/internal/modcmd就是go mod命令相關(guān)的源碼了。其程序入口${GOROOT/src/cmd/go/main.go}進(jìn)入該目錄(其實(shí)也可以不進(jìn),但是待會看源碼時(shí)還是得進(jìn)去)執(zhí)行以下命令go build -o ./godebug.exe -gcflags all="-N -l" -mod=mod .得到以下程序。

注:可以直接調(diào)試main.go這個(gè)文件,但是 go mod tidy這個(gè)命令是根據(jù)執(zhí)行命令時(shí)的工作路徑查找go.mod文件,這無形指定了工作路徑為:${GOROOT}/src/cmd

debug 配置文件

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "GO debug",
            "type": "go",
            "request": "attach",
            "mode": "remote",
            "host": "127.0.0.1",
            "port": 2345,
        }
    ]
}

dlv啟動

隨便進(jìn)一個(gè)go項(xiàng)目中,執(zhí)行命令dlv exec --headless --listen :2345 --api-version=2 D:/go/src/cmd/go/godebug.exe -- mod tidy,這里的程序是我們上面剛剛編譯出來的,路徑自己CV就行。

現(xiàn)在dlv服務(wù)端已經(jīng)ok了,現(xiàn)在回到源碼那邊,啟動客戶端就行了。

如果走的exec,好像是沒法vscode的restart按鈕貌似不起作用,每次都需要重復(fù)執(zhí)行以上兩個(gè)動作

執(zhí)行過程

入口${GOOROOT}/src/cmd/go/internal/modcmd/tidy.go,該方法只是做了一些參數(shù)配置,主要邏輯在modload.LoadPackages

func runTidy(ctx context.Context, cmd *base.Command, args []string) {
        ...
	modload.LoadPackages(ctx, modload.PackageOpts{
		GoVersion:                tidyGo.String(),
		Tags:                     imports.AnyTags(),
		Tidy:                     true,
		TidyCompatibleVersion:    tidyCompat.String(),
		VendorModulesInGOROOTSrc: true,
		ResolveMissingImports:    true,
		LoadTests:                true,
		AllowErrors:              tidyE,
		SilenceMissingStdImports: true,
	}, "all")
}
  • 加載項(xiàng)目go.mod的文件內(nèi)容
  • 構(gòu)建整個(gè)項(xiàng)目的依賴關(guān)系
  • 更新go.mod文件
// {GOROOT}/src/cmd/go/internal/modload/load.go
func LoadPackages(參數(shù)省略)(參數(shù)省略) {
	...
        //  加載項(xiàng)目go.mod的文件內(nèi)容
	initialRS := LoadModFile(ctx)
	...
}

加載go.mod文件

1.根據(jù)執(zhí)行go mod tidy時(shí)所在的工作路徑,向上查找最先找到的go.mod文件,讀取并解析該文件內(nèi)容。

// ${GOROOT}/src/cmd/go/internal/modload/init.go
func LoadModFile(ctx context.Context) *Requirements {
	...
	// 做一些初始化的設(shè)置,獲取當(dāng)前項(xiàng)目的go.mod路徑
	// 執(zhí)行g(shù)o mod tidy 是的工作路徑往上一層層尋找,找到的第一個(gè)路徑即為目標(biāo)路徑
        // 查找路徑的調(diào)用棧`Init() => findModuleRoot(base.Cwd())`
	Init()
	...
	// 讀取go.mod文件并解析該文件內(nèi)容;modRoots的長度為1,大于1的情況我沒有遇到過
	for _, modroot := range modRoots {
		gomod := modFilePath(modroot)
		data, f, err := ReadModFile(gomod, fixVersion(ctx, &fixed))
	}
	...
	// 只獲取go.mod文件中的require列表,并記錄每個(gè)依賴的最高版本號
	rs := requirementsFromModFiles(ctx, modFiles)
	...
	// 如果發(fā)現(xiàn)當(dāng)前的go.mod文件有重復(fù)的依賴路徑
        // 這里會先對當(dāng)前項(xiàng)目的go.mod文件進(jìn)行一次依賴項(xiàng)的計(jì)算
	if rs.hasRedundantRoot() {
        // If any module path appears more than once in the roots, we know that the
        // go.mod file needs to be updated even though we have not yet loaded any
        // transitive dependencies.
		...
	}
	...
}

加載依賴

// {GOROOT}/src/cmd/go/internal/modload/load.go
func LoadPackages(...) (...) {
    // 找出項(xiàng)目的所有依賴,有個(gè)全局變量負(fù)責(zé)最后的存儲的
    ld := loadFromRoots(ctx, loaderParams{
        PackageOpts:  opts,
        requirements: initialRS,
        allPatternIsRoot: allPatternIsRoot,
        listRoots: func(rs *Requirements) (roots []string) {
            // 實(shí)際上調(diào)用的是 matchPackages() 方法
            updateMatches(rs, nil)
            // 這里的matches長度也是1個(gè)
            for _, m := range matches {
                roots = append(roots, m.Pkgs...)
            }
            return roots
        },
    })
}
  • 獲取遍歷樹的根節(jié)點(diǎn)(當(dāng)前項(xiàng)目的所有滿足條件的文件夾路徑)loadFromRoots()=> listRoots() => matchPackages()
// ${GOROOT}/src/cmd/go/internal/modload/search.go
func matchPackages(...) {
    // 遍歷項(xiàng)目根路徑
    walkPkgs := func(root, importPathRoot string, prune pruning) {
        ...
        // 這里的root為go.mod所在的目錄,importPathRoot為go.mod定義的module
        err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error {
            // 一大堆判斷過濾
            ...
            // 包名 = moduleName + 相對路徑
            name := importPathRoot + filepath.ToSlash(path[len(root):])
            if _, _, err := scanDir(path, tags); err != imports.ErrNoGo {
                m.Pkgs = append(m.Pkgs, name)
            }
            return nil
        })
    }
    // 同樣的這里modules也只有1個(gè),多個(gè)的沒遇到過
    for _, mod := range modules {
        walkPkgs(root, modPrefix, prune)
    }
    return
}
  • 從項(xiàng)目跟路徑出發(fā)構(gòu)建依賴關(guān)系
// {GOROOT}/src/cmd/go/internal/modload/load.go
func loadFromRoots(ctx context.Context, params loaderParams) *loader {
    ...
    // 注這里是多次循環(huán)的過程
    // a=>b,只有當(dāng)b加載后才能知道是否有b=>c,b=>d。
    // 所以這里會不斷的重復(fù)這個(gè)過程,直至所有的依賴關(guān)系構(gòu)建完畢
    for {
        ld.reset()
        ...
        // 找出項(xiàng)目下的文件夾路徑,這里的rootPkgs每次循環(huán)都是一樣的
        rootPkgs := ld.listRoots(ld.requirements)
        ...
        // 從根路徑出發(fā),遍歷全部的文件,獲取依賴關(guān)系
        // 在加載依賴A的同時(shí),會根據(jù)依賴A里面的go.mod繼續(xù)去找依賴B
        // 如果發(fā)現(xiàn)項(xiàng)目中有直接引用依賴A,但是當(dāng)前項(xiàng)目的go.mod沒有(前面加載過,存放在ld.requirements),
        // 則會給該pkg一個(gè)err(這里不是module,是module里面的某個(gè)包,例如 A/xxxx,A/yyy),
        // 這里會交由ld.resolveMissingImports去處理
        for _, path := range rootPkgs {
            // 這里是并發(fā)加載,速度還是比較快的
            // 主要的邏輯在在ld.load方法上
            root := ld.pkg(ctx, path, pkgIsRoot)
            if !inRoots[root] {
                ld.roots = append(ld.roots, root)
                inRoots[root] = true
            }
        }
        // 這個(gè)只是將依賴樹給平鋪了存放在 ld.pkgs
        ld.buildStacks()
        ...
        // 某種程度上, 可以認(rèn)為這里下載的是缺失的直接依賴,即go.mod里面沒聲明,但是項(xiàng)目卻使用到了的
        // 如果發(fā)現(xiàn)沒有缺失的直接依賴了,即可認(rèn)為依賴關(guān)系已經(jīng)構(gòu)建完畢。
        // 因?yàn)樯鲜鲞^程會自動構(gòu)建依賴關(guān)系,這里只是添加缺失的直接依賴,然后由上面的循環(huán)來構(gòu)建依賴關(guān)系
        modAddedBy := ld.resolveMissingImports(ctx)
        if len(modAddedBy) == 0 {
            break
        }
    }
    ...
}
// 因?yàn)闃?gòu)建的是整個(gè)依賴關(guān)系,所以上述過程完成后,項(xiàng)目中不需要的依賴也已經(jīng)自動剔除了
  • 加載一個(gè)單獨(dú)的pkg
// {GOROOT}/src/cmd/go/internal/modload/load.go
func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
	... 
	// 找出pkg的module及其所在目錄
	pkg.mod, pkg.dir, pkg.altMods, pkg.err = importFromModules(ctx, pkg.path, ld.requirements, mg)
	if pkg.dir == "" {
		return
	}
	...
	// 掃描文件獲取所有的import
	// 這里是一個(gè)pkg的所有import
	/* 
	例如: 
		A/B/xxx.go 
			import "11111"
		A/B/yyy.go
			import "22222"
	則 pkg
		import "1111"
		import "2222"
	*/ 
	imports, testImports, err = scanDir(pkg.dir, ld.Tags)
	...
	// 遞歸執(zhí)行 ld.pkg 組裝下數(shù)據(jù)結(jié)構(gòu),又回來繼續(xù)調(diào)用 ld.load
	for _, path := range imports {
		pkg.imports = append(pkg.imports, ld.pkg(ctx, path, importFlags))
	}
}

更新go.mod文件

這里就比較簡單了,就是單純的寫文件而已。在第二過程的時(shí)候已經(jīng)將依賴關(guān)系構(gòu)建完成了,其結(jié)果存放在一個(gè)全局變量里面MainModules,這里就是單純校驗(yàn)寫文件了

func LoadPackages(...) (...) {
	...
	if err := commitRequirements(ctx); err != nil {
		base.Fatalf("go: %v", err)
	}
	...
}

以上就是詳解Go 依賴管理 go mod tidy的詳細(xì)內(nèi)容,更多關(guān)于Go 依賴管理 go mod tidy的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 手把手帶你走進(jìn)Go語言之運(yùn)算符解析

    手把手帶你走進(jìn)Go語言之運(yùn)算符解析

    這篇文章主要介紹了手Go語言之運(yùn)算符解析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • Go語言字符串及strings和strconv包使用實(shí)例

    Go語言字符串及strings和strconv包使用實(shí)例

    字符串是工作中最常用的,值得我們專門的練習(xí)一下,下面這篇文章主要給大家介紹了關(guān)于Go語言字符串及strings和strconv包使用的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-06-06
  • Go語言nil標(biāo)識符(空值/零值)

    Go語言nil標(biāo)識符(空值/零值)

    本文主要介紹了Go語言nil標(biāo)識符(空值/零值),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Go?Fiber快速搭建一個(gè)HTTP服務(wù)器

    Go?Fiber快速搭建一個(gè)HTTP服務(wù)器

    Fiber?是一個(gè)?Express?啟發(fā)?web?框架基于?fasthttp?,最快?Go?的?http?引擎,這篇文章主要介紹了Go?Fiber快速搭建一個(gè)HTTP服務(wù)器,需要的朋友可以參考下
    2023-06-06
  • Go語言中的Array、Slice、Map和Set使用詳解

    Go語言中的Array、Slice、Map和Set使用詳解

    這篇文章主要介紹了Go語言中的Array、Slice、Map和Set使用詳解,本文給出了它們的創(chuàng)建、使用、多維等代碼實(shí)例,需要的朋友可以參考下
    2014-10-10
  • golang 的string與[]byte轉(zhuǎn)換方式

    golang 的string與[]byte轉(zhuǎn)換方式

    這篇文章主要介紹了golang 的string與[]byte轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Golang?sync.Map底層實(shí)現(xiàn)場景示例詳解

    Golang?sync.Map底層實(shí)現(xiàn)場景示例詳解

    這篇文章主要為大家介紹了Golang?sync.Map底層實(shí)現(xiàn)及使用場景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Golang實(shí)現(xiàn)自己的Redis(有序集合跳表)實(shí)例探究

    Golang實(shí)現(xiàn)自己的Redis(有序集合跳表)實(shí)例探究

    這篇文章主要為大家介紹了Golang實(shí)現(xiàn)自己的Redis(有序集合跳表)實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • 淺談go語言中別名類型的使用

    淺談go語言中別名類型的使用

    類型別名是 Go 1.9 版本添加的新功能,主要用于解決代碼升級、遷移中存在的類型兼容性問題,本文主要介紹了go語言中別名類型的使用,感興趣的可以了解一下
    2024-01-01
  • go語言的panic和recover函數(shù)用法實(shí)例

    go語言的panic和recover函數(shù)用法實(shí)例

    今天小編就為大家分享一篇關(guān)于go語言的panic和recover函數(shù)用法實(shí)例,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-04-04

最新評論