Commit 12b05bf8 authored by Michael Hudson-Doyle's avatar Michael Hudson-Doyle Committed by Ian Lance Taylor

cmd/go: support -buildmode=shared with gccgo

Change-Id: Id93b8ab42fa311ce32209734ec9a0813f8736e25
Reviewed-on: https://go-review.googlesource.com/9914Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
parent f74ea6cd
......@@ -20,6 +20,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
......@@ -274,13 +275,17 @@ func dynStrings(path string, flag elf.DynTag) []string {
return dynstrings
}
func AssertIsLinkedTo(t *testing.T, path, lib string) {
func AssertIsLinkedToRegexp(t *testing.T, path string, re *regexp.Regexp) {
for _, dynstring := range dynStrings(path, elf.DT_NEEDED) {
if dynstring == lib {
if re.MatchString(dynstring) {
return
}
}
t.Errorf("%s is not linked to %s", path, lib)
t.Errorf("%s is not linked to anything matching %v", path, re)
}
func AssertIsLinkedTo(t *testing.T, path, lib string) {
AssertIsLinkedToRegexp(t, path, regexp.MustCompile(regexp.QuoteMeta(lib)))
}
func AssertHasRPath(t *testing.T, path, dir string) {
......@@ -306,7 +311,7 @@ func TestTrivialExecutable(t *testing.T) {
// Build a GOPATH package into a shared library that links against the goroot runtime
// and an executable that links against both.
func TestGOPathShlib(t *testing.T) {
func TestGopathShlib(t *testing.T) {
goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
AssertIsLinkedTo(t, filepath.Join(gopathInstallDir, "libdep.so"), soname)
goCmd(t, "install", "-linkshared", "exe")
......@@ -437,13 +442,85 @@ func TestNotes(t *testing.T) {
// Build a GOPATH package (dep) into a shared library that links against the goroot
// runtime, another package (dep2) that links against the first, and and an
// executable that links against dep2.
func TestTwoGOPathShlibs(t *testing.T) {
func TestTwoGopathShlibs(t *testing.T) {
goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep2")
goCmd(t, "install", "-linkshared", "exe2")
run(t, "executable linked to GOPATH library", "./bin/exe2")
}
// Build a GOPATH package into a shared library with gccgo and an executable that
// links against it.
func TestGoPathShlibGccgo(t *testing.T) {
gccgoName := os.Getenv("GCCGO")
if gccgoName == "" {
gccgoName = "gccgo"
}
_, err := exec.LookPath(gccgoName)
if err != nil {
t.Skip("gccgo not found")
}
libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
gccgoContext := build.Default
gccgoContext.InstallSuffix = suffix + "_fPIC"
gccgoContext.Compiler = "gccgo"
gccgoContext.GOPATH = os.Getenv("GOPATH")
depP, err := gccgoContext.Import("dep", ".", build.ImportComment)
if err != nil {
t.Fatalf("import failed: %v", err)
}
gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs")
goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep")
AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep.so"), libgoRE)
goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe")
AssertIsLinkedToRegexp(t, "./bin/exe", libgoRE)
AssertIsLinkedTo(t, "./bin/exe", "libdep.so")
AssertHasRPath(t, "./bin/exe", gccgoInstallDir)
// And check it runs.
run(t, "gccgo-built", "./bin/exe")
}
// The gccgo version of TestTwoGopathShlibs: build a GOPATH package into a shared
// library with gccgo, another GOPATH package that depends on the first and an
// executable that links the second library.
func TestTwoGopathShlibsGccgo(t *testing.T) {
gccgoName := os.Getenv("GCCGO")
if gccgoName == "" {
gccgoName = "gccgo"
}
_, err := exec.LookPath(gccgoName)
if err != nil {
t.Skip("gccgo not found")
}
libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
gccgoContext := build.Default
gccgoContext.InstallSuffix = suffix + "_fPIC"
gccgoContext.Compiler = "gccgo"
gccgoContext.GOPATH = os.Getenv("GOPATH")
depP, err := gccgoContext.Import("dep", ".", build.ImportComment)
if err != nil {
t.Fatalf("import failed: %v", err)
}
gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs")
goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep")
goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep2")
goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe2")
AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep.so"), libgoRE)
AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep2.so"), libgoRE)
AssertIsLinkedTo(t, filepath.Join(gccgoInstallDir, "libdep2.so"), "libdep.so")
AssertIsLinkedToRegexp(t, "./bin/exe2", libgoRE)
AssertIsLinkedTo(t, "./bin/exe2", "libdep2")
AssertIsLinkedTo(t, "./bin/exe2", "libdep.so")
// And check it runs.
run(t, "gccgo-built", "./bin/exe2")
}
// Testing rebuilding of shared libraries when they are stale is a bit more
// complicated that it seems like it should be. First, we make everything "old": but
// only a few seconds old, or it might be older than 6g (or the runtime source) and
......
......@@ -8,6 +8,7 @@ import (
"bufio"
"bytes"
"container/heap"
"debug/elf"
"errors"
"flag"
"fmt"
......@@ -752,19 +753,37 @@ func goFilesPackage(gofiles []string) *Package {
return pkg
}
func readpkglist(shlibpath string) []*Package {
pkglistbytes, err := readELFNote(shlibpath, "Go\x00\x00", 1)
if err != nil {
fatalf("readELFNote failed: %v", err)
}
scanner := bufio.NewScanner(bytes.NewBuffer(pkglistbytes))
var pkgs []*Package
// readpkglist returns the list of packages that were built into the shared library
// at shlibpath. For the native toolchain this list is stored, newline separated, in
// an ELF note with name "Go\x00\x00" and type 1. For GCCGO it is extracted from the
// .go_export section.
func readpkglist(shlibpath string) (pkgs []*Package) {
var stk importStack
for scanner.Scan() {
t := scanner.Text()
pkgs = append(pkgs, loadPackage(t, &stk))
if _, gccgo := buildToolchain.(gccgoToolchain); gccgo {
f, _ := elf.Open(shlibpath)
sect := f.Section(".go_export")
data, _ := sect.Data()
scanner := bufio.NewScanner(bytes.NewBuffer(data))
for scanner.Scan() {
t := scanner.Text()
if strings.HasPrefix(t, "pkgpath ") {
t = strings.TrimPrefix(t, "pkgpath ")
t = strings.TrimSuffix(t, ";")
pkgs = append(pkgs, loadPackage(t, &stk))
}
}
} else {
pkglistbytes, err := readELFNote(shlibpath, "Go\x00\x00", 1)
if err != nil {
fatalf("readELFNote failed: %v", err)
}
scanner := bufio.NewScanner(bytes.NewBuffer(pkglistbytes))
for scanner.Scan() {
t := scanner.Text()
pkgs = append(pkgs, loadPackage(t, &stk))
}
}
return pkgs
return
}
// action returns the action for applying the given operation (mode) to the package.
......@@ -914,23 +933,26 @@ func (b *builder) libaction(libname string, pkgs []*Package, mode, depMode build
// was not passed on the command line and it is not present in
// another shared library, add it here.
seencgo := false
for _, p := range pkgs {
seencgo = seencgo || (p.Standard && p.ImportPath == "runtime/cgo")
}
if !seencgo {
var stk importStack
p := loadPackage("runtime/cgo", &stk)
if p.Error != nil {
fatalf("load runtime/cgo: %v", p.Error)
_, gccgo := buildToolchain.(gccgoToolchain)
if !gccgo {
for _, p := range pkgs {
seencgo = seencgo || (p.Standard && p.ImportPath == "runtime/cgo")
}
computeStale(p)
// If runtime/cgo is in another shared library, then that's
// also the shared library that contains runtime, so
// something will depend on it and so runtime/cgo's staleness
// will be checked when processing that library.
if p.Shlib == "" || p.Shlib == libname {
pkgs = append([]*Package{}, pkgs...)
pkgs = append(pkgs, p)
if !seencgo {
var stk importStack
p := loadPackage("runtime/cgo", &stk)
if p.Error != nil {
fatalf("load runtime/cgo: %v", p.Error)
}
computeStale(p)
// If runtime/cgo is in another shared library, then that's
// also the shared library that contains runtime, so
// something will depend on it and so runtime/cgo's staleness
// will be checked when processing that library.
if p.Shlib == "" || p.Shlib == libname {
pkgs = append([]*Package{}, pkgs...)
pkgs = append(pkgs, p)
}
}
}
......@@ -938,6 +960,9 @@ func (b *builder) libaction(libname string, pkgs []*Package, mode, depMode build
var libdir string
for _, p := range pkgs {
plibdir := p.build.PkgTargetRoot
if gccgo {
plibdir = filepath.Join(plibdir, "shlibs")
}
if libdir == "" {
libdir = plibdir
} else if libdir != plibdir {
......@@ -1429,7 +1454,7 @@ func (b *builder) build(a *action) (err error) {
// linker needs the whole dependency tree.
all := actionList(a)
all = all[:len(all)-1] // drop a
if err := buildToolchain.ld(b, a.p, a.target, all, a.objpkg, objects); err != nil {
if err := buildToolchain.ld(b, a, a.target, all, a.objpkg, objects); err != nil {
return err
}
}
......@@ -1477,68 +1502,10 @@ func (b *builder) installShlibname(a *action) error {
return nil
}
// setextld sets the appropriate linker flags for the specified compiler.
func setextld(ldflags []string, compiler []string) []string {
for _, f := range ldflags {
if f == "-extld" || strings.HasPrefix(f, "-extld=") {
// don't override -extld if supplied
return ldflags
}
}
ldflags = append(ldflags, "-extld="+compiler[0])
if len(compiler) > 1 {
extldflags := false
add := strings.Join(compiler[1:], " ")
for i, f := range ldflags {
if f == "-extldflags" && i+1 < len(ldflags) {
ldflags[i+1] = add + " " + ldflags[i+1]
extldflags = true
break
} else if strings.HasPrefix(f, "-extldflags=") {
ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):]
extldflags = true
break
}
}
if !extldflags {
ldflags = append(ldflags, "-extldflags="+add)
}
}
return ldflags
}
func (b *builder) linkShared(a *action) (err error) {
// TODO(mwhudson): obvious copy pasting from gcToolchain.ld, should make a few
// changes to that function and then call it. And support gccgo.
allactions := actionList(a)
importArgs := b.includeArgs("-L", allactions[:len(allactions)-1])
ldflags := []string{"-installsuffix", buildContext.InstallSuffix}
ldflags = append(ldflags, "-buildmode=shared")
ldflags = append(ldflags, buildLdflags...)
cxx := a.p != nil && (len(a.p.CXXFiles) > 0 || len(a.p.SwigCXXFiles) > 0)
for _, a := range allactions {
if a.p != nil && (len(a.p.CXXFiles) > 0 || len(a.p.SwigCXXFiles) > 0) {
cxx = true
}
}
// If the user has not specified the -extld option, then specify the
// appropriate linker. In case of C++ code, use the compiler named
// by the CXX environment variable or defaultCXX if CXX is not set.
// Else, use the CC environment variable and defaultCC as fallback.
var compiler []string
if cxx {
compiler = envList("CXX", defaultCXX)
} else {
compiler = envList("CC", defaultCC)
}
ldflags = setextld(ldflags, compiler)
for _, d := range a.deps {
if !strings.HasSuffix(d.target, ".a") { // omit unsafe etc and actions for other shared libraries
continue
}
ldflags = append(ldflags, d.p.ImportPath+"="+d.target)
}
return b.run(".", a.target, nil, buildToolExec, tool("link"), "-o", a.target, importArgs, ldflags)
allactions = allactions[:len(allactions)-1]
return buildToolchain.ldShared(b, a.deps, a.target, allactions)
}
// install is the action for installing a single package or executable.
......@@ -2062,8 +2029,10 @@ type toolchain interface {
// an archive from a set of object files.
// typically it is run in the object directory.
pack(b *builder, p *Package, objDir, afile string, ofiles []string) error
// ld runs the linker to create a package starting at mainpkg.
ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error
// ld runs the linker to create an executable starting at mainpkg.
ld(b *builder, root *action, out string, allactions []*action, mainpkg string, ofiles []string) error
// ldShared runs the linker to create a shared library containing the pkgs built by toplevelactions
ldShared(b *builder, toplevelactions []*action, out string, allactions []*action) error
compiler() string
linker() string
......@@ -2103,7 +2072,11 @@ func (noToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []s
return noCompiler()
}
func (noToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
func (noToolchain) ld(b *builder, root *action, out string, allactions []*action, mainpkg string, ofiles []string) error {
return noCompiler()
}
func (noToolchain) ldShared(b *builder, toplevelactions []*action, out string, allactions []*action) error {
return noCompiler()
}
......@@ -2326,9 +2299,39 @@ func packInternal(b *builder, afile string, ofiles []string) error {
return dst.Close()
}
func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
// setextld sets the appropriate linker flags for the specified compiler.
func setextld(ldflags []string, compiler []string) []string {
for _, f := range ldflags {
if f == "-extld" || strings.HasPrefix(f, "-extld=") {
// don't override -extld if supplied
return ldflags
}
}
ldflags = append(ldflags, "-extld="+compiler[0])
if len(compiler) > 1 {
extldflags := false
add := strings.Join(compiler[1:], " ")
for i, f := range ldflags {
if f == "-extldflags" && i+1 < len(ldflags) {
ldflags[i+1] = add + " " + ldflags[i+1]
extldflags = true
break
} else if strings.HasPrefix(f, "-extldflags=") {
ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):]
extldflags = true
break
}
}
if !extldflags {
ldflags = append(ldflags, "-extldflags="+add)
}
}
return ldflags
}
func (gcToolchain) ld(b *builder, root *action, out string, allactions []*action, mainpkg string, ofiles []string) error {
importArgs := b.includeArgs("-L", allactions)
cxx := len(p.CXXFiles) > 0 || len(p.SwigCXXFiles) > 0
cxx := len(root.p.CXXFiles) > 0 || len(root.p.SwigCXXFiles) > 0
for _, a := range allactions {
if a.p != nil && (len(a.p.CXXFiles) > 0 || len(a.p.SwigCXXFiles) > 0) {
cxx = true
......@@ -2338,7 +2341,7 @@ func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action,
if buildContext.InstallSuffix != "" {
ldflags = append(ldflags, "-installsuffix", buildContext.InstallSuffix)
}
if p.omitDWARF {
if root.p.omitDWARF {
ldflags = append(ldflags, "-w")
}
......@@ -2354,11 +2357,42 @@ func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action,
}
ldflags = setextld(ldflags, compiler)
ldflags = append(ldflags, "-buildmode="+ldBuildmode)
if p.buildID != "" {
ldflags = append(ldflags, "-buildid="+p.buildID)
if root.p.buildID != "" {
ldflags = append(ldflags, "-buildid="+root.p.buildID)
}
ldflags = append(ldflags, buildLdflags...)
return b.run(".", p.ImportPath, nil, buildToolExec, tool("link"), "-o", out, importArgs, ldflags, mainpkg)
return b.run(".", root.p.ImportPath, nil, buildToolExec, tool("link"), "-o", out, importArgs, ldflags, mainpkg)
}
func (gcToolchain) ldShared(b *builder, toplevelactions []*action, out string, allactions []*action) error {
importArgs := b.includeArgs("-L", allactions)
ldflags := []string{"-installsuffix", buildContext.InstallSuffix}
ldflags = append(ldflags, "-buildmode=shared")
ldflags = append(ldflags, buildLdflags...)
cxx := false
for _, a := range allactions {
if a.p != nil && (len(a.p.CXXFiles) > 0 || len(a.p.SwigCXXFiles) > 0) {
cxx = true
}
}
// If the user has not specified the -extld option, then specify the
// appropriate linker. In case of C++ code, use the compiler named
// by the CXX environment variable or defaultCXX if CXX is not set.
// Else, use the CC environment variable and defaultCC as fallback.
var compiler []string
if cxx {
compiler = envList("CXX", defaultCXX)
} else {
compiler = envList("CC", defaultCC)
}
ldflags = setextld(ldflags, compiler)
for _, d := range toplevelactions {
if !strings.HasSuffix(d.target, ".a") { // omit unsafe etc and actions for other shared libraries
continue
}
ldflags = append(ldflags, d.p.ImportPath+"="+d.target)
}
return b.run(".", out, nil, buildToolExec, tool("link"), "-o", out, importArgs, ldflags)
}
func (gcToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
......@@ -2432,26 +2466,42 @@ func (gccgoToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles
return b.run(p.Dir, p.ImportPath, nil, "ar", "cru", mkAbs(objDir, afile), absOfiles)
}
func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
func (tools gccgoToolchain) ld(b *builder, root *action, out string, allactions []*action, mainpkg string, ofiles []string) error {
// gccgo needs explicit linking with all package dependencies,
// and all LDFLAGS from cgo dependencies.
apackagesSeen := make(map[*Package]bool)
afiles := []string{}
shlibs := []string{}
xfiles := []string{}
ldflags := b.gccArchArgs()
cgoldflags := []string{}
usesCgo := false
cxx := len(p.CXXFiles) > 0 || len(p.SwigCXXFiles) > 0
objc := len(p.MFiles) > 0
// Prefer the output of an install action to the output of a build action,
// because the install action will delete the output of the build action.
// Iterate over the list backward (reverse dependency order) so that we
// always see the install before the build.
for i := len(allactions) - 1; i >= 0; i-- {
a := allactions[i]
if !a.p.Standard {
if a.p != nil && !apackagesSeen[a.p] {
cxx := len(root.p.CXXFiles) > 0 || len(root.p.SwigCXXFiles) > 0
objc := len(root.p.MFiles) > 0
actionsSeen := make(map[*action]bool)
// Make a pre-order depth-first traversal of the action graph, taking note of
// whether a shared library action has been seen on the way to an action (the
// construction of the graph means that if any path to a node passes through
// a shared library action, they all do).
var walk func(a *action, seenShlib bool)
walk = func(a *action, seenShlib bool) {
if actionsSeen[a] {
return
}
actionsSeen[a] = true
if a.p != nil && !seenShlib {
if a.p.Standard {
return
}
// We record the target of the first time we see a .a file
// for a package to make sure that we prefer the 'install'
// rather than the 'build' location (which may not exist any
// more). We still need to traverse the dependencies of the
// build action though so saying
// if apackagesSeen[a.p] { return }
// doesn't work.
if !apackagesSeen[a.p] {
apackagesSeen[a.p] = true
if a.p.fake && a.p.external {
// external _tests, if present must come before
......@@ -2466,6 +2516,16 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []
}
}
}
if strings.HasSuffix(a.target, ".so") {
shlibs = append(shlibs, a.target)
seenShlib = true
}
for _, a1 := range a.deps {
walk(a1, seenShlib)
}
}
for _, a1 := range root.deps {
walk(a1, false)
}
afiles = append(xfiles, afiles...)
......@@ -2474,6 +2534,9 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []
// The go tool can dig up runtime/cgo from GOROOT and
// think that it should use its CgoLDFLAGS, but gccgo
// doesn't use runtime/cgo.
if a.p == nil {
continue
}
if !a.p.Standard {
cgoldflags = append(cgoldflags, a.p.CgoLDFLAGS...)
}
......@@ -2505,10 +2568,20 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []
ldflags = append(ldflags, cgoldflags...)
ldflags = append(ldflags, envList("CGO_LDFLAGS", "")...)
ldflags = append(ldflags, p.CgoLDFLAGS...)
ldflags = append(ldflags, root.p.CgoLDFLAGS...)
ldflags = stringList("-Wl,-(", ldflags, "-Wl,-)")
for _, shlib := range shlibs {
ldflags = append(
ldflags,
"-L"+filepath.Dir(shlib),
"-Wl,-rpath="+filepath.Dir(shlib),
"-l"+strings.TrimSuffix(
strings.TrimPrefix(filepath.Base(shlib), "lib"),
".so"))
}
var realOut string
switch ldBuildmode {
case "exe":
......@@ -2556,20 +2629,43 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []
}
}
if err := b.run(".", p.ImportPath, nil, tools.linker(), "-o", out, ofiles, ldflags, buildGccgoflags); err != nil {
if err := b.run(".", root.p.ImportPath, nil, tools.linker(), "-o", out, ofiles, ldflags, buildGccgoflags); err != nil {
return err
}
switch ldBuildmode {
case "c-archive":
if err := b.run(".", p.ImportPath, nil, "ar", "rc", realOut, out); err != nil {
if err := b.run(".", root.p.ImportPath, nil, "ar", "rc", realOut, out); err != nil {
return err
}
}
return nil
}
func (tools gccgoToolchain) ldShared(b *builder, toplevelactions []*action, out string, allactions []*action) error {
args := []string{"-o", out, "-shared", "-nostdlib", "-zdefs", "-Wl,--whole-archive"}
for _, a := range toplevelactions {
args = append(args, a.target)
}
args = append(args, "-Wl,--no-whole-archive", "-shared", "-nostdlib", "-lgo", "-lgcc_s", "-lgcc", "-lc")
shlibs := []string{}
for _, a := range allactions {
if strings.HasSuffix(a.target, ".so") {
shlibs = append(shlibs, a.target)
}
}
for _, shlib := range shlibs {
args = append(
args,
"-L"+filepath.Dir(shlib),
"-Wl,-rpath="+filepath.Dir(shlib),
"-l"+strings.TrimSuffix(
strings.TrimPrefix(filepath.Base(shlib), "lib"),
".so"))
}
return b.run(".", out, nil, tools.linker(), args, buildGccgoflags)
}
func (tools gccgoToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
inc := filepath.Join(goroot, "pkg", "include")
cfile = mkAbs(p.Dir, cfile)
......
......@@ -530,7 +530,12 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
shlib, err := ioutil.ReadFile(shlibnamefile)
if err == nil {
libname := strings.TrimSpace(string(shlib))
p.Shlib = filepath.Join(p.build.PkgTargetRoot, libname)
if buildContext.Compiler == "gccgo" {
p.Shlib = filepath.Join(p.build.PkgTargetRoot, "shlibs", libname)
} else {
p.Shlib = filepath.Join(p.build.PkgTargetRoot, libname)
}
} else if !os.IsNotExist(err) {
fatalf("unexpected error reading %s: %v", shlibnamefile, err)
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment