func()

in libgo/go/cmd/go/internal/work/gccgo.go [261:575]


func (tools gccgoToolchain) link(b *Builder, root *Action, out, importcfg string, allactions []*Action, buildmode, desc string) error {
	// gccgo needs explicit linking with all package dependencies,
	// and all LDFLAGS from cgo dependencies.
	afiles := []string{}
	shlibs := []string{}
	ldflags := b.gccArchArgs()
	cgoldflags := []string{}
	usesCgo := false
	cxx := false
	objc := false
	fortran := false
	if root.Package != nil {
		cxx = len(root.Package.CXXFiles) > 0 || len(root.Package.SwigCXXFiles) > 0
		objc = len(root.Package.MFiles) > 0
		fortran = len(root.Package.FFiles) > 0
	}

	readCgoFlags := func(flagsFile string) error {
		flags, err := os.ReadFile(flagsFile)
		if err != nil {
			return err
		}
		const ldflagsPrefix = "_CGO_LDFLAGS="
		for _, line := range strings.Split(string(flags), "\n") {
			if strings.HasPrefix(line, ldflagsPrefix) {
				line = line[len(ldflagsPrefix):]
				quote := byte(0)
				start := true
				var nl []byte
				for len(line) > 0 {
					b := line[0]
					line = line[1:]
					if quote == 0 && (b == ' ' || b == '\t') {
						if len(nl) > 0 {
							cgoldflags = append(cgoldflags, string(nl))
							nl = nil
						}
						start = true
						continue
					} else if b == '"' || b == '\'' {
						quote = b
					} else if b == quote {
						quote = 0
					} else if quote == 0 && start && b == '-' && strings.HasPrefix(line, "g") {
						line = line[1:]
						continue
					} else if quote == 0 && start && b == '-' && strings.HasPrefix(line, "O") {
						for len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
							line = line[1:]
						}
						continue
					}
					nl = append(nl, b)
					start = false
				}
				if len(nl) > 0 {
					cgoldflags = append(cgoldflags, string(nl))
				}
			}
		}
		return nil
	}

	var arArgs []string
	if cfg.Goos == "aix" && cfg.Goarch == "ppc64" {
		// AIX puts both 32-bit and 64-bit objects in the same archive.
		// Tell the AIX "ar" command to only care about 64-bit objects.
		arArgs = []string{"-X64"}
	}

	newID := 0
	readAndRemoveCgoFlags := func(archive string) (string, error) {
		newID++
		newArchive := root.Objdir + fmt.Sprintf("_pkg%d_.a", newID)
		if err := b.copyFile(newArchive, archive, 0666, false); err != nil {
			return "", err
		}
		if cfg.BuildN {
			// TODO(rsc): We could do better about showing the right _cgo_flags even in -n mode.
			// Either the archive is already built and we can read them out,
			// or we're printing commands to build the archive and can
			// forward the _cgo_flags directly to this step.
			b.Showcmd("", "ar d %s _cgo_flags", newArchive)
			return "", nil
		}
		err := b.run(root, root.Objdir, desc, nil, tools.ar(), arArgs, "x", newArchive, "_cgo_flags")
		if err != nil {
			return "", err
		}
		err = b.run(root, ".", desc, nil, tools.ar(), arArgs, "d", newArchive, "_cgo_flags")
		if err != nil {
			return "", err
		}
		err = readCgoFlags(filepath.Join(root.Objdir, "_cgo_flags"))
		if err != nil {
			return "", err
		}
		return newArchive, nil
	}

	// If using -linkshared, find the shared library deps.
	haveShlib := make(map[string]bool)
	targetBase := filepath.Base(root.Target)
	if cfg.BuildLinkshared {
		for _, a := range root.Deps {
			p := a.Package
			if p == nil || p.Shlib == "" {
				continue
			}

			// The .a we are linking into this .so
			// will have its Shlib set to this .so.
			// Don't start thinking we want to link
			// this .so into itself.
			base := filepath.Base(p.Shlib)
			if base != targetBase {
				haveShlib[base] = true
			}
		}
	}

	// Arrange the deps into afiles and shlibs.
	addedShlib := make(map[string]bool)
	for _, a := range root.Deps {
		p := a.Package
		if p != nil && p.Shlib != "" && haveShlib[filepath.Base(p.Shlib)] {
			// This is a package linked into a shared
			// library that we will put into shlibs.
			continue
		}

		if haveShlib[filepath.Base(a.Target)] {
			// This is a shared library we want to link against.
			if !addedShlib[a.Target] {
				shlibs = append(shlibs, a.Target)
				addedShlib[a.Target] = true
			}
			continue
		}

		if p != nil {
			target := a.built
			if p.UsesCgo() || p.UsesSwig() {
				var err error
				target, err = readAndRemoveCgoFlags(target)
				if err != nil {
					continue
				}
			}

			afiles = append(afiles, target)
		}
	}

	for _, a := range allactions {
		// Gather CgoLDFLAGS, but not from standard packages.
		// 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.Package == nil {
			continue
		}
		if !a.Package.Standard {
			cgoldflags = append(cgoldflags, a.Package.CgoLDFLAGS...)
		}
		if len(a.Package.CgoFiles) > 0 {
			usesCgo = true
		}
		if a.Package.UsesSwig() {
			usesCgo = true
		}
		if len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0 {
			cxx = true
		}
		if len(a.Package.MFiles) > 0 {
			objc = true
		}
		if len(a.Package.FFiles) > 0 {
			fortran = true
		}
	}

	wholeArchive := []string{"-Wl,--whole-archive"}
	noWholeArchive := []string{"-Wl,--no-whole-archive"}
	if cfg.Goos == "aix" {
		wholeArchive = nil
		noWholeArchive = nil
	}
	ldflags = append(ldflags, wholeArchive...)
	ldflags = append(ldflags, afiles...)
	ldflags = append(ldflags, noWholeArchive...)

	ldflags = append(ldflags, cgoldflags...)
	ldflags = append(ldflags, envList("CGO_LDFLAGS", "")...)
	if root.Package != nil {
		ldflags = append(ldflags, root.Package.CgoLDFLAGS...)
	}
	if cfg.Goos != "aix" {
		ldflags = str.StringList("-Wl,-(", ldflags, "-Wl,-)")
	}

	if root.buildID != "" {
		// On systems that normally use gold or the GNU linker,
		// use the --build-id option to write a GNU build ID note.
		switch cfg.Goos {
		case "android", "dragonfly", "linux", "netbsd":
			ldflags = append(ldflags, fmt.Sprintf("-Wl,--build-id=0x%x", root.buildID))
		}
	}

	var rLibPath string
	if cfg.Goos == "aix" {
		rLibPath = "-Wl,-blibpath="
	} else {
		rLibPath = "-Wl,-rpath="
	}
	for _, shlib := range shlibs {
		ldflags = append(
			ldflags,
			"-L"+filepath.Dir(shlib),
			rLibPath+filepath.Dir(shlib),
			"-l"+strings.TrimSuffix(
				strings.TrimPrefix(filepath.Base(shlib), "lib"),
				".so"))
	}

	var realOut string
	goLibBegin := str.StringList(wholeArchive, "-lgolibbegin", noWholeArchive)
	switch buildmode {
	case "exe":
		if usesCgo && cfg.Goos == "linux" {
			ldflags = append(ldflags, "-Wl,-E")
		}

	case "c-archive":
		// Link the Go files into a single .o, and also link
		// in -lgolibbegin.
		//
		// We need to use --whole-archive with -lgolibbegin
		// because it doesn't define any symbols that will
		// cause the contents to be pulled in; it's just
		// initialization code.
		//
		// The user remains responsible for linking against
		// -lgo -lpthread -lm in the final link. We can't use
		// -r to pick them up because we can't combine
		// split-stack and non-split-stack code in a single -r
		// link, and libgo picks up non-split-stack code from
		// libffi.
		ldflags = append(ldflags, "-Wl,-r", "-nostdlib")
		ldflags = append(ldflags, goLibBegin...)

		if nopie := b.gccNoPie([]string{tools.linker()}); nopie != "" {
			ldflags = append(ldflags, nopie)
		}

		// We are creating an object file, so we don't want a build ID.
		if root.buildID == "" {
			ldflags = b.disableBuildID(ldflags)
		}

		realOut = out
		out = out + ".o"

	case "c-shared":
		ldflags = append(ldflags, "-shared", "-nostdlib")
		ldflags = append(ldflags, goLibBegin...)
		ldflags = append(ldflags, "-lgo", "-lgcc_s", "-lgcc", "-lc", "-lgcc")

	case "shared":
		if cfg.Goos != "aix" {
			ldflags = append(ldflags, "-zdefs")
		}
		ldflags = append(ldflags, "-shared", "-nostdlib", "-lgo", "-lgcc_s", "-lgcc", "-lc")

	case "pie":
		ldflags = append(ldflags, "-pie")

	default:
		base.Fatalf("-buildmode=%s not supported for gccgo", buildmode)
	}

	switch buildmode {
	case "exe", "c-shared":
		if cxx {
			ldflags = append(ldflags, "-lstdc++")
		}
		if objc {
			ldflags = append(ldflags, "-lobjc")
		}
		if fortran {
			fc := cfg.Getenv("FC")
			if fc == "" {
				fc = "gfortran"
			}
			// support gfortran out of the box and let others pass the correct link options
			// via CGO_LDFLAGS
			if strings.Contains(fc, "gfortran") {
				ldflags = append(ldflags, "-lgfortran")
			}
		}
	}

	if err := b.run(root, ".", desc, nil, tools.linker(), "-o", out, ldflags, forcedGccgoflags, root.Package.Internal.Gccgoflags); err != nil {
		return err
	}

	switch buildmode {
	case "c-archive":
		if err := b.run(root, ".", desc, nil, tools.ar(), arArgs, "rc", realOut, out); err != nil {
			return err
		}
	}
	return nil
}