來源:互聯(lián)網(wǎng) 閱讀:-
Go編程語言的軟件包管理和部署的完整概述
如果您熟悉Java或NodeJS之類的語言,那么您可能非常熟悉軟件包。 包不過是帶有一些代碼文件的目錄,該目錄從單個引用點公開了不同的變量(功能)。 讓我解釋一下,這意味著什么。
想象一下,在任何項目中進(jìn)行工作時,您都需要不斷地使用上千種功能。 其中一些功能具有共同的行為。 例如,toUpperCase和toLowerCase函數(shù)可轉(zhuǎn)換字符串的大小寫,因此您可以將它們寫入單個文件(可能為case.go)。 還有其他函數(shù)對字符串?dāng)?shù)據(jù)類型執(zhí)行其他一些操作,因此您也將它們寫在單獨的文件中。
由于您有許多文件可以處理字符串?dāng)?shù)據(jù)類型,因此您創(chuàng)建了一個名為string的目錄,并將所有與字符串相關(guān)的文件放入其中。 最后,將所有這些目錄放在一個父目錄中,該目錄將成為您的軟件包。 整個程序包結(jié)構(gòu)如下所示。
package-name├── string| ├── case.go| ├── trim.go| └── misc.go└── number ├── arithmetics.go └── primes.go
我將詳細(xì)解釋如何從包中導(dǎo)入函數(shù)和變量,以及如何將所有內(nèi)容融合在一起形成一個包,但是現(xiàn)在,將您的包想象成一個包含.go文件的目錄。
每個Go程序都必須是某些程序包的一部分。 如Go入門課程中所述,獨立的可執(zhí)行Go程序必須具有包main聲明。 如果程序是主軟件包的一部分,則go install將創(chuàng)建一個二進(jìn)制文件; 在執(zhí)行時調(diào)用程序的主要功能。 如果程序不是main包的一部分,則使用go install命令創(chuàng)建包庫文件。 不用擔(dān)心,我將在以后的主題中解釋所有這些內(nèi)容。
讓我們創(chuàng)建一個可執(zhí)行程序包。 眾所周知,要創(chuàng)建一個二進(jìn)制可執(zhí)行文件,我們需要程序成為主程序包的一部分,并且它必須具有main函數(shù),該函數(shù)是執(zhí)行的入口點。
軟件包名稱是src目錄中包含的目錄的名稱。 在上述情況下,因為app是src目錄的子目錄,所以app是軟件包。 因此,執(zhí)行g(shù)o install app命令在GOPATH的src目錄中查找app子目錄。 然后,它編譯了程序包,并在bin目錄中創(chuàng)建了應(yīng)用二進(jìn)制可執(zhí)行文件,該文件應(yīng)可從終端執(zhí)行,因為PATH中的bin目錄。
像上面示例中的package main應(yīng)該是代碼的第一行的包聲明,它可以與包名稱不同。 因此,您可能會發(fā)現(xiàn)某些軟件包,其中軟件包名稱(目錄名稱)與軟件包聲明不同。 導(dǎo)入程序包時,程序包聲明用于創(chuàng)建程序包引用變量,如本文稍后所述。
go install <package> 命令在給定的軟件包目錄中查找具有主軟件包聲明的任何文件。 如果找到文件,則Go知道這是一個可執(zhí)行程序,因此需要創(chuàng)建一個二進(jìn)制文件。 一個包可以有許多文件,但只有一個具有主要功能的文件,因為該文件將是執(zhí)行的入口。
如果軟件包不包含帶有main軟件包聲明的文件,則Go會在pkg目錄中創(chuàng)建一個軟件包庫0(.a)文件。
由于app不是可執(zhí)行程序包,因此它在pkg目錄中創(chuàng)建了app.a文件。 該文件不是二進(jìn)制文件,因此無法執(zhí)行。
包命名約定
Go社區(qū)建議對軟件包使用簡單的名稱。 例如,用于字符串實用程序功能的strutils,或用于HTTP請求相關(guān)功能的http。 應(yīng)該避免使用下劃線,連字符或大小寫混合的軟件包名稱。
正如我們所討論的,有兩種類型的軟件包。 可執(zhí)行程序包和實用程序包。 可執(zhí)行包是您的主要應(yīng)用程序,因為您將在運行它。 實用程序包不是自可執(zhí)行的,相反,它通過提供實用程序功能和其他重要資產(chǎn)來增強可執(zhí)行程序包的功能。
眾所周知,一個包只不過是一個目錄,讓我們在src中創(chuàng)建greet目錄,并在其中創(chuàng)建幾個文件。 這次,我們將在每個文件的頂部編寫一個包聲明 package greet,以聲明這是一個實用程序包。
導(dǎo)出成員
實用程序包應(yīng)該為導(dǎo)入它的包提供一些變量。 與JavaScript中的 export 語法一樣,如果變量名稱以大寫字母開頭,則Go會導(dǎo)出變量。 所有其他不以大寫字母開頭的變量對程序包都是私有的。
從現(xiàn)在開始,我將使用 variable 來描述導(dǎo)出成員,但是導(dǎo)出成員可以是常量,映射,函數(shù),結(jié)構(gòu),數(shù)組,切片等任何類型。
讓我們從day.go文件中導(dǎo)出一個問候變量。
在上面的程序中,Morning變量將從包中導(dǎo)出,但是morning變量不會,它以小寫字母開頭,因此不會。
導(dǎo)入包
現(xiàn)在,我們需要一個可執(zhí)行包,它將使用我們的greet包。 讓我們在src內(nèi)創(chuàng)建一個app目錄,并創(chuàng)建帶有main包聲明和main函數(shù)的entry.go文件。 請注意,Go包沒有像在Node中index.js這樣的入口文件命名系統(tǒng)。 對于可執(zhí)行程序包,具有main功能的文件是要執(zhí)行的入口文件。
要導(dǎo)入軟件包,我們使用import語法,后跟軟件包名稱。
與其他編程語言不同,包名稱也可以是諸如some-dir / greet的子路徑,而Go會自動為我們解析到greet包的路徑,如前面的嵌套包主題所示。
首先轉(zhuǎn)到GOROOT / src目錄中的包目錄,如果找不到包,則查找GOPATH / src。 由于fmt軟件包是Go的標(biāo)準(zhǔn)庫(位于GOROOT / src中)的一部分,因此從那里導(dǎo)入。 由于Go無法在GOROOT中找到greet包,因此它將在GOPATH / src中進(jìn)行查找,并且我們已經(jīng)在其中了。
上面的程序拋出編譯錯誤,因為從包中看不到morning變量。 如您所見,我們使用. (點)表示法,用于從包中訪問導(dǎo)出的成員。 導(dǎo)入程序包時,Go使用程序包的程序包聲明創(chuàng)建一個全局變量。 在上述情況下,greet是Go創(chuàng)建的全局變量,因為我們在greet軟件包中包含的程序中使用了package greet聲明。
我們可以使用分組語法(括號)將fmt和greet包一起導(dǎo)入。 這次,我們的程序?qū)⒕幾g得很好,因為可以從包外部獲得Morning變量。
嵌套包
我們可以將包嵌套在包中。 由于對于Go,包只是目錄,就像在現(xiàn)有包中創(chuàng)建子目錄一樣。 我們要做的就是提供嵌套包的相對路徑。
包編譯
如上一課所述,go run命令編譯并執(zhí)行程序。 我們知道,go install命令可以編譯軟件包并創(chuàng)建二進(jìn)制可執(zhí)行文件或軟件包庫文件。 這是為了避免每次編譯(導(dǎo)入這些程序包的程序)每次都編譯程序包。 go install會預(yù)編譯一個軟件包,而Go則引用.a文件。
通常,當(dāng)您安裝第三方軟件包時,Go會編譯該軟件包并創(chuàng)建軟件包存檔文件。 如果您在本地編寫軟件包,則IDE可能會在將文件保存在軟件包中或在軟件包被修改后立即創(chuàng)建軟件包庫文件。 如果已安裝Go插件,則VSCode在保存時會編譯該軟件包。
包初始化
當(dāng)我們運行Go程序時,Go編譯器會按照一定的執(zhí)行順序執(zhí)行程序包,程序包中的文件以及程序包中的變量聲明。
包范圍
范圍是代碼塊中可訪問定義變量的區(qū)域。 包作用域是包中的一個區(qū)域,在該區(qū)域中可從包內(nèi)(包中的所有文件)訪問聲明的變量。 此區(qū)域是軟件包中任何文件的最上面的塊。
看一下go run命令。 這次,我們不再使用一個文件,而是使用一種全局模式將所有文件都包含在應(yīng)用程序包中以便執(zhí)行。 Go具有足夠的智能,可以找出應(yīng)用程序的0=6圖片;v'entry.go入口點,因為它具有main函數(shù)。 我們還可以使用如下所示的命令(文件名順序無關(guān)緊要)。
go run src/app/version.go src/app/entry.go
go install或go build命令需要一個軟件包名稱,其中包括軟件包中的所有文件,因此我們不必像上面那樣指定它們。
回到我們的主要問題,我們可以在包的任何地方使用在version.go文件中聲明的變量version,即使它沒有導(dǎo)出(如Version),因為它是在包范圍內(nèi)聲明的。 如果在函數(shù)中聲明了version變量,則說明該version 變量不在包范圍內(nèi),并且上述程序無法編譯。
不允許在同一包中重新聲明具有相同名稱的全局變量。 因此,一旦聲明了版本變量,就不能在程序包范圍內(nèi)對其進(jìn)行重新聲明。 但是您可以隨意在其他地方重新聲明。
變量初始化
當(dāng)變量a依賴于另一個變量b時,應(yīng)事先定義b,否則程序?qū)o法編譯。 Go在函數(shù)內(nèi)部遵循此規(guī)則。
但是,當(dāng)這些變量在包范圍內(nèi)定義時,它們將在初始化周期中聲明。 讓我們看看下面的簡單示例。
在上面的示例中,第一個c被聲明,因為它的值已經(jīng)被聲明。 在以后的初始化周期中,聲明了b,因為它取決于c并且已經(jīng)聲明了c的值。 在最后的初始化周期中,聲明a并將其分配給b的值。 Go可以處理如下所示的復(fù)雜初始化周期。
在上面的示例中,將首先聲明c,然后聲明b,因為其值取決于c,最后是a,因為其值取決于b。 您應(yīng)該避免像下面這樣的初始化循環(huán)。
包范圍的另一個示例是,在單獨文件中具有函數(shù)f,該函數(shù)從主文件引用變量c。
初始化函數(shù)
像main函數(shù)一樣,初始化包時,Go會調(diào)用init函數(shù)。 它不帶任何參數(shù),也不返回任何值。 Go隱式聲明了init函數(shù),因此您無法從任何地方引用它(或像init()一樣調(diào)用它)。 您可以在文件或包中聲明多個init函數(shù)。 文件中init函數(shù)的執(zhí)行順序?qū)⒏鶕?jù)它們出現(xiàn)的順序。
您可以在包中的任何位置聲明init函數(shù)。 這些初始化函數(shù)以文件名字母順序調(diào)用。
畢竟,要執(zhí)行init函數(shù),然后才能調(diào)用main函數(shù)。 因此,init函數(shù)的主要工作是初始化無法在全局上下文中初始化的全局變量。 例如,數(shù)組的初始化。
由于for語法在包范圍內(nèi)無效,因此我們可以在init函數(shù)內(nèi)部使用for循環(huán)來初始化大小為10的數(shù)組整數(shù)。
包別名
導(dǎo)入包時,請使用包的包聲明創(chuàng)建一個變量。 如果要導(dǎo)入多個具有相同名稱的軟件包,則將導(dǎo)致沖突。
// parent.gopackage greetvar Message = "Hey there. I am parent."// child.gopackage greetvar Message = "Hey there. I am child."
因此,我們使用包別名。 我們在import關(guān)鍵字和程序包名稱之間聲明一個變量名,該變量名成為引用該程序包的新變量。
在上面的示例中,greet / greet包現(xiàn)在由子變量引用。 如果您注意到的話,我們在greet包中使用下劃線作為別名。 下劃線是Go中的一個特殊字符,用作空容器。 由于我們導(dǎo)入的是greet包,但未使用它,因此Go編譯器會抱怨它。 為了避免這種情況,我們將對該包的引用存儲到_中,而Go編譯器只會忽略它。
有時,如果要初始化一個包但不使用它,則用下劃線來給一個包起別名似乎什么也沒做是很有用的。
// parent.gopackage greetimport "fmt"var Message = "Hey there. I am parent."func init() { fmt.Println("greet/parent.go ==> init()")}// child.gopackage greetimport "fmt"var Message = "Hey there. I am child."func init() { fmt.Println("greet/greet/child.go ==> init()")}
要記住的主要事情是,每個包只對導(dǎo)入的包初始化一次。 因此,如果包中有任何import語句,則導(dǎo)入的包將在main包執(zhí)行的生命周期中僅初始化一次。
如果我們使用.(點)作為類似import的別名。 " greet / greet",那么greet包的所有導(dǎo)出成員將在本地文件塊作用域中可用,我們可以在不使用限定符child的情況下引用Message。 因此,fmt.Println(Message)可以正常工作。 這種類型的導(dǎo)入被稱為"點導(dǎo)入" Go社區(qū)不是很喜歡它,因為它可能會引起一些問題。
到目前為止,我們已經(jīng)了解了有關(guān)軟件包的所有內(nèi)容。 現(xiàn)在,讓我們結(jié)合對程序如何在Go中初始化的理解。
go run *.go├── Main package is executed├── All imported packages are initialized| ├── All imported packages are initialized (recursive definition)| ├── All global variables are initialized | └── init functions are called in lexical file name order└── Main package is initialized ├── All global variables are initialized └── init functions are called in lexical file name order
這是一個證明這一點的小例子。
// version/get-version.gopackage versionimport "fmt"func init() { fmt.Println("version/get-version.go ==> init()")}func getVersion() string { fmt.Println("version/get-version.go ==> getVersion()") return "1.0.0"}/***************************/// version/entry.gopackage versionimport "fmt"func init() { fmt.Println("version/entry.go ==> init()")}var Version = getLocalVersion()func getLocalVersion() string { fmt.Println("version/entry.go ==> getLocalVersion()") return getVersion()}/***************************/// app/fetch-version.gopackage mainimport ( "fmt" "version")func init() { fmt.Println("app/fetch-version.go ==> init()")}func fetchVersion() string { fmt.Println("app/fetch-version.go ==> fetchVersion()") return version.Version}/***************************/// app/entry.gopackage mainimport "fmt"func init() { fmt.Println("app/entry.go ==> init()")}var myVersion = fetchVersion()func main() { fmt.Println("app/fetch-version.go ==> fetchVersion()") fmt.Println("version ===> ", myVersion)}
安裝第三方軟件包只不過是將遠(yuǎn)程代碼克隆到本地src / 目錄中。 不幸的是,Go不支持程序包版本或不提供程序包管理器,但這正在等待提案。
(Go 1.3已經(jīng)正式支持Go Modules 譯者注)
由于Go沒有統(tǒng)一的正式軟件包注冊中心,因此它會要求您提供軟件包的主機名和路徑。
$ go get -u github.com/jinzhu/gorm
上面的命令從http://github.com/jinzhu/gorm URL導(dǎo)入文件,并將其保存在src / github.com / jinzhu / gorm目錄中。 如嵌套軟件包中所述,您可以像下面那樣導(dǎo)入gorm軟件包。
package mainimport "github.com/jinzhu/gorm"// use ==> gorm.SomeExportedMember
因此,如果您制作了一個程序包并希望人們使用它,只需將其發(fā)布在GitHub上就可以了。 如果您的程序包是可執(zhí)行的,則人們可以將其用作命令行工具,也可以將其導(dǎo)入程序中并將其用作實用程序模塊。 他們唯一需要做的就是使用以下命令。
$ go get github.com/your-username/repo-name
Go提供了一個稱為Go Modules模塊的新功能,該功能提供了輕松管理項目和依賴項的靈活性。 如果您正在使用Go軟件包,則應(yīng)考慮將項目重新定位到Go Modules。
(本文翻譯自Uday Hiwarale的文章《Everything you need to know about Packages in Go》,參考:https://medium.com/rungo/everything-you-need-to-know-about-packages-in-go-b8bac62b74cc)
推薦閱讀:華為手機怎么選