理解Golang包导⼊,import、包名、⽬录名的关系import后⾯的是⽬录
包名和⽬录名没有关系,但是包名最好等于⽬录名
同⼀个⽬录下只能有⼀种包名。
转⾃:tonybai/2015/03/09/understanding-import-packages/
使⽤包(package)这种语法元素来组织源码,所有语法可见性均定义在package这个级别,与Java 、python等语⾔相⽐,这算不上什么创新,但与C传统的include相⽐,则是显得“先进”了许多。
中包的定义和使⽤看起来⼗分简单:
通过package关键字定义包:
package xxx
使⽤import关键字,导⼊要使⽤的标准库包或第三⽅依赖包。
import "a/b/c"
import "fmt"
c.Func1()
fmt.Println("Hello, World")
很多Golang初学者看到上⾯代码,都会想当然的将import后⾯的"c"、"fmt"当成包名,将其与c.Func1()和 fmt.Println()中的c和fmt认作为同⼀个语法元素:包名。但在深⼊Golang后,很多⼈便会发现事实上并⾮如此。⽐如在使⽤实时分布式消息平台nsq提供的go client api时:
我们导⼊的路径如下:
import “github/bitly/go-nsq”
但在使⽤其提供的export functions时,却⽤做前缀包名:
q, _ := nsq.NewConsumer("write_test", "ch", config)
⼈们不禁要问:import后⾯路径中的最后⼀个元素到底代表的是啥? 是包名还是仅仅是⼀个路径?我们⼀起通过试验来理解⼀下。实验环境:darwin_amd64 , go 1.4。
初始试验环境⽬录结果如下:
GOPATH = /Users/tony/Test/Go/pkgtest/
pkgtest/
pkg/
src/
libproj1/
foo/
<
app1/
<
⼀、编译时使⽤的是包源码还是.a
我们知道⼀个⾮main包在编译后会⽣成⼀个.a⽂件(在临时⽬录下⽣成,除⾮使⽤go install安装到$GOROOT或$GOPATH下,否则你看不到.a),⽤于后续可执⾏程序链接使⽤。
⽐如Go标准库中的包对应的源码部分路径在:$GOROOT/src,⽽标准库中包编译后的.a⽂件路径在$GOROOT/pkg/darwin_amd64下。⼀个奇怪的问题在我脑袋中升腾起来,编译时,编译器到底⽤的是.a还是源码?
我们先以⽤户⾃定义的package为例做个⼩实验。
$GOPATH/src/
libproj1/foo/
–
app1
–
//
package foo
import "fmt"
func Foo1() {
fmt.Println("Foo1")
}
//
package main
import (
"libproj1/foo"
)
func main() {
foo.Foo1()
}
执⾏go install libproj1/foo,Go编译器编译foo包,并将foo.a安装到$GOPATH/pkg/darwin_amd64/libproj1下。
编译app1:go build app1,在app1⽬录下⽣成app1*可执⾏⽂件,执⾏app1,我们得到⼀个初始预期结果:
$./app1
Foo1
现在我们⽆法看出使⽤的到底是foo的源码还是foo.a,因为⽬前它们的输出都是⼀致的。我们修改⼀下的代码:
//
package foo
import "fmt"
func Foo1() {
fmt.Println("Foo1 – modified")
}
重新编译执⾏app1,我们得到结果如下:
$./app1
Foo1 – modified
实际测试结果告诉我们:(1)在使⽤第三⽅包的时候,当源码和.a均已安装的情况下,编译器链接的是源码。
那么是否可以只链接.a,不⽤第三⽅包源码呢?我们临时删除掉libproj1⽬录,但保留之前install的libproj1/foo.a⽂件。
我们再次尝试编译app1,得到如下错误:
$go build app1
<:5:2: cannot find package "libproj1/foo" in any of:
/Users/tony/.Bin/go14/src/libproj1/foo (from $GOROOT)
/Users/tony/Test/Go/pkgtest/src/libproj1/foo (from $GOPATH)
编译器还是去源码,⽽不是.a,因此我们要依赖第三⽅包,就必须搞到第三⽅包的源码,这也是Golang包管理的⼀个特点。
其实通过编译器的详细输出我们也可得出上⾯结论。我们在编译app1时给编译器传⼊-x -v选项:
$go build -x -v app1
WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-build797811168
libproj1/foo
mkdir -p $WORK/libproj1/foo/_obj/
mkdir -p $WORK/libproj1/
cd /Users/tony/Test/Go/pkgtest/src/libproj1/foo
/
Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6g -o $WORK/libproj1/foo.a -trimpath $WORK -p libproj1/foo -complete -D
_/Users/tony/Test/Go/pkgtest/src/libproj1/foo -I $WORK -pack ./ ./
app1
mkdir -p $WORK/app1/_obj/
mkdir -p $WORK/app1/_obj/exe/
cd /Users/tony/Test/Go/pkgtest/src/app1
/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6g -o $WORK/app1.a -trimpath $WORK -p app1 -complete -D
_/Users/tony/Test/Go/pkgtest/src/app1 -I $WORK -I /Users/tony/Test/Go/pkgtest/pkg/darwin_amd64 -pack ./
cd .
/
Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6l -o $WORK/app1/_obj/exe/a.out -L $WORK -L
/Users/tony/Test/Go/pkgtest/pkg/darwin_amd64 -extld=clang $WORK/app1.a
mv $WORK/app1/_obj/exe/a.out app1
可以看到编译器6g⾸先在临时路径下编译出依赖包foo.a,放在$WORK/libproj1下。但我们在最后6l链接器的执⾏语句中并未显式看到app1链接的是$WORK/libproj1下的foo.a。但是从6l链接器的-L参数来看:-L $WORK -L /Users/tony/Test/Go/pkgtest/pkg/darwin_amd64,我们发
现$WORK⽬录放在了前⾯,我们猜测6l⾸先搜索到的时$WORK下⾯的libproj1/foo.a。
为了验证我们的推论,我们按照编译器输出,按顺序⼿动执⾏了⼀遍如上命令,但在最后执⾏6l命令时,去掉了-L $WORK:
/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6l -o $WORK/app1/_obj/exe/a.out -L /Users/tony/Test/Go/pkgtest/pkg/darwin_amd64 -extld=clang $WORK/app1.a
这样做的结果是:
$./app1
Foo1
编译器链接了$GOPATH/pkg下的foo.a。(2)到这⾥我们明⽩了所谓的使⽤第三⽅包源码,实际上是链接了以该最新源码编译的临时⽬录下的.a⽂件⽽已。
Go标准库中的包也是这样么?对于标准库,⽐如fmt⽽⾔,编译时,到底使⽤的时$GOROOT/src下源码还是$GOROOT/pkg下已经编译好的.a呢?
我们不妨也来试试,⼀个最简单的hello world例⼦:
//
import "fmt"
func main() {
fmt.Println("Hello, World")
}
我们先将$GOROOT/src/fmt⽬录rename 为fmtbak,看看go compiler有何反应?
go build -x -v ./
$go build -x -v ./
WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-build957202426
<:4:8: cannot find package "fmt" in any of:
/Users/tony/.Bin/go14/src/fmt (from $GOROOT)
/Users/tony/Test/Go/pkgtest/src/fmt (from $GOPATH)
不到fmt包了。显然标准库在编译时也是必须要源码的。不过与⾃定义包不同的是,即便你修改了fmt包的源码(未重新编译GO安装包),⽤户源码编译时,也不会尝试重新编译fmt包的,依旧只是在链接时链接已经编译好的fmt.a。通过下⾯的gc输出可以验证这点:
$go build -x -v ./
WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-build773440756
app1
mkdir -p $WORK/app1/_obj/
mkdir -p $WORK/app1/_obj/exe/
cd /Users/tony/Test/Go/pkgtest/src/app1
/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6g -o $WORK/app1.a -trimpath $WORK -p app1 -complete -D
_/Users/tony/Test/Go/pkgtest/src/app1 -I $WORK -pack ./
cd .
/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6l -o $WORK/app1/_obj/exe/a.out -L $WORK -extld=clang $WORK/app1.a
mv $WORK/app1/_obj/exe/a.out app1
可以看出,编译器的确并未尝试编译标准库中的fmt源码。
⼆、⽬录名还是包名?
从第⼀节的实验中,我们得知了编译器在编译过程中依赖的是包源码的路径,这为后续的实验打下了基础。下⾯我们再来看看,Go语⾔中import后⾯路径中最后的⼀个元素到底是包名还是路径名?
本次实验⽬录结构:
$GOPATH
src/
libproj2/
foo/
<
app2/
<
按照Golang语⾔习惯,⼀个go package的所有源⽂件放在同⼀个⽬录下,且该⽬录名与该包名相同,⽐如libproj1/foo⽬录下的package为foo,、 …共同组成foo package的源⽂件。但⽬录
名与包名也可以不同,我们就来试试不同的。
我们建⽴libproj2/foo⽬录,其中的代码如下:
//
package bar
import "fmt"
func Bar1() {
fmt.Println("Bar1")
}
注意:这⾥package名为bar,与⽬录名foo完全不同。
接下来就给app2带来了难题:该如何import bar包呢?
我们假设import路径中的最后⼀个元素是包名,⽽⾮路径名。
/
/
package main
import (
"libproj2/bar"
)
func main() {
bar.Bar1()
}
编译app2:
$go build -x -v app2
WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-build736904327
<:5:2: cannot find package "libproj2/bar" in any of:
/Users/tony/.Bin/go14/src/libproj2/bar (from $GOROOT)
/Users/tony/Test/Go/pkgtest/src/libproj2/bar (from $GOPATH)
编译失败,在两个路径下⽆法到对应libproj2/bar包。
我们的假设错了,我们把它改为路径:
//
package main
import (
import语句"libproj2/foo"
)
func main() {
bar.Bar1()
}
再编译执⾏:
$go build app2
$app2
Bar1
这回编译顺利通过,执⾏结果也是OK的。这样我们得到了结论:(3)import后⾯的最后⼀个元素应该是路径,就是⽬录,并⾮包名。
go编译器在这些路径(libproj2/foo)下bar包。这样看来,go语⾔的惯例只是⼀个特例,即恰好⽬录名与包名⼀致罢了。也就是说下⾯例⼦中的两个foo含义不同:
import "libproj1/foo"
func main() {
foo.Foo()
}
import中的foo只是⼀个⽂件系统的路径罢了。⽽下⾯foo.Foo()中的foo则是包名。⽽这个包是在libproj1/foo⽬录下的源码中到的。
再类⽐⼀下标准库包fmt。
import "fmt"
fmt.Println("xxx")
这⾥上下两⾏中虽然都是“fmt",但同样含义不同,⼀个是路径,对于标准库来说,是$GOROOT/src/fmt这个路径。⽽第⼆⾏中的fmt则是包名。gc会在$GOROOT/src/fmt路径下到fmt包的源⽂件。
三、import m "lib/math"
Go language specification中关于import package时列举的⼀个例⼦如下:
Import declaration Local name of Sin
import "lib/math" math.Sin
import m "lib/math" m.Sin
import . "lib/math" Sin
我们看到import m "lib/math" m.Sin⼀⾏。我们说过lib/math是路径,import语句⽤m替代lib/math,并在代码中通过m访问math包中的导出函数Sin。
那m到底是包名还是路径呢?既然能通过m访问Sin,那m肯定是包名了,Right!那import m "lib/math"该如何理解呢?
根据上⾯⼀、⼆两节中得出的结论,我们尝试理解⼀下m:(4)m指代的是lib/math路径下唯⼀的那个包。
⼀个⽬录下是否可以存在两个包呢?我们来试试。
我们在libproj1/foo下新增⼀个go源⽂件,:
package bar
import "fmt"
func Bar1() {
fmt.Println("Bar1")
}
我们重新构建⼀下这个⽬录下的包:
$go build libproj1/foo
can't load package: package libproj1/foo: found (bar) (foo) in /Users/tony/Test/Go/pkgtest/src/libproj1/foo
我们收到了错误提⽰,编译器在这个路径下发现了两个包,这是不允许的。
我们再作个实验,来验证我们对m含义的解释。
我们建⽴app3⽬录,其的源码如下:
//
package main
import m "libproj2/foo"
func main() {
m.Bar1()
}
libproj2/foo路径下的包的包名为bar,按照我们的推论,m指代的就是bar这个包,通过m我们可以访问bar的Bar1导出函数。
编译并执⾏上⾯:
$go build app3
$app3
Bar1
执⾏结果与我们推论完全⼀致。
附录:6g, 6l⽂档位置:
6g – $GOROOT/src/cmd/
6l – $GOROOT/src/cmd/
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论