Skip to content

Commit 73676e3

Browse files
committed
cmd/go: run cgo and cgo compiles in their own actions
This change splits package builds further into even more actions. Between the cache action (and cover action, if present) and the actual build action we insert three layers of actions: Check Cache Action | | | V | Run Cover Action (if cover requested) | | | V New Layer 1 | Run Cgo Action (if package is built with cgo) | | | | (multiple arrows representing fan-out to | V V V multiple actions) New Layer 2 | gcc Compile Action for each C/C++/S/F/M file (if cgo) | |/ (arrow represeting fan-in to single action) | V New Layer 3 | Cgo collect action (if cgo) | | \ V Build action The first run cgo action takes the input source files and runs cgo on them to produce the cgo-processed go files, which are given to the compiler, and to produce additional C files to compile and headers to use in the following compilations. The action also takes care of running SWIG before running cgo if there are swig files. This will produce additional cgo sources that are inputs to cgo. The run cgo action action fans out to multiple actions to do each of the C/C++/Obj-C/assembly/Fortran compilations for the non-Go files in the package, as well as those produced as outputs by the cgo command invocation. These actions then join into a single noop "collect" action which primarily exists so that we don't pollute the build action's dependencies with a bunch of non-go compile actions. (The build action expects its dependencies to mostly be other build actions). All of this work in the new actions was previously being done in the build action itself. There's still a remnant of the original cgo logic left in the build action to run the cgo command with -dynimport to produce a go file to be built with the rest of the package, and do some checks. Most of this CL consists of moving code around. Just like the previous CL breaking out the coverage logic into a separate action, we don't cache the outputs of the cgo actions, and just treat all the actions used to build a single package as one cacheable unit. This makes things a bit simpler. If we decide in a future CL to cache the outputs separately, we could remove the dependency on the cover action on the check cache action (which in turn depends on all the package's dependencies) and could start non-go compilation pretty much as early as we want in the build. The 'cgoAction' function in action.go takes care of creating the layers of cgo action dependencies, which are inserted as dependencies of the build action. It's mostly straightforward, except for the fact that we need to tell each non-go compile action which non-go file to compile, so we need to compute the names of the generated files. (Alternatively we could give each action a number and have it build the nth file produced by the run cgo action, but that seems even more complicated). The actors produced to run the action logic are pretty light wrappers around the execution logic in exec.go. In the 'build' function in exec.go, most of the new code mainly checks for the information from the cgo actions to use instead of running it, and then passes it to the processCgoOutputs function. The only other weird thing in the build functian is that we we call the logic to compute the nonGoOverlay separately just for the C files that are being built with gccgo. We compute the overlay for the non-go files used in a cgo build in the run cgo action. The 'cgo' function that previously ran the cgo logic for the build has now been split into three: the first half, which runs cgo is now in the runCgo function, the center part, which compiles the non-go files is now partly in creating the invididual non-go compile actions, as well as the cgoCompileActor's Act function. And the final part, which runs cgo -dynimport is now in processCgoOutputs. These parts communicate with each other through the providers that are set on the cgo actions. One further improvement we can make to this change in the future is to compile the dynimport file separately from the build action: its output is only needed by the linker. This would remove any dependencies from dependent packages' build actions on the cgo compile actions, allowing more flexibility for scheduling actions. Fixes golang#9887 Change-Id: Ie3c70bbf985148ba73094cddfc78c39dc6faad6e Reviewed-on: https://go-review.googlesource.com/c/go/+/694475 Reviewed-by: Michael Pratt <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Matloob <[email protected]>
1 parent 0e1b989 commit 73676e3

File tree

2 files changed

+409
-194
lines changed

2 files changed

+409
-194
lines changed

src/cmd/go/internal/work/action.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"internal/platform"
2020
"os"
2121
"path/filepath"
22+
"slices"
2223
"strings"
2324
"sync"
2425
"time"
@@ -578,6 +579,47 @@ func (ca *coverActor) Act(b *Builder, ctx context.Context, a *Action) error {
578579
return nil
579580
}
580581

582+
// runCgoActor implements the Actor interface for running the cgo command for the package.
583+
type runCgoActor struct {
584+
}
585+
586+
func (c runCgoActor) Act(b *Builder, ctx context.Context, a *Action) error {
587+
var cacheProvider *checkCacheProvider
588+
for _, a1 := range a.Deps {
589+
if pr, ok := a1.Provider.(*checkCacheProvider); ok {
590+
cacheProvider = pr
591+
break
592+
}
593+
}
594+
need := cacheProvider.need
595+
need &^= needCovMetaFile // handled by cover action
596+
if need == 0 {
597+
return nil
598+
}
599+
return b.runCgo(ctx, a)
600+
}
601+
602+
type cgoCompileActor struct {
603+
file string
604+
605+
compileFunc func(*Action, string, string, []string, string) error
606+
getFlagsFunc func(*runCgoProvider) []string
607+
608+
flags *[]string
609+
}
610+
611+
func (c cgoCompileActor) Act(b *Builder, ctx context.Context, a *Action) error {
612+
pr, ok := a.Deps[0].Provider.(*runCgoProvider)
613+
if !ok {
614+
return nil // cgo was not needed. do nothing.
615+
}
616+
a.nonGoOverlay = pr.nonGoOverlay
617+
buildAction := a.triggers[0].triggers[0] // cgo compile -> cgo collect -> build
618+
619+
a.actionID = cache.Subkey(buildAction.actionID, "cgo compile "+c.file) // buildAction's action id was computed by the check cache action.
620+
return c.compileFunc(a, a.Objdir, a.Target, c.getFlagsFunc(pr), c.file)
621+
}
622+
581623
// CompileAction returns the action for compiling and possibly installing
582624
// (according to mode) the given package. The resulting action is only
583625
// for building packages (archives), never for linking executables.
@@ -677,6 +719,17 @@ func (b *Builder) CompileAction(mode, depMode BuildMode, p *load.Package) *Actio
677719
a.Deps = append(a.Deps, coverAction)
678720
}
679721

722+
// Create actions to run swig and cgo if needed. These actions always run in the
723+
// same go build invocation as the build action and their actions are not cached
724+
// separately, so they can use the same objdir.
725+
if p.UsesCgo() || p.UsesSwig() {
726+
deps := []*Action{cacheAction}
727+
if coverAction != nil {
728+
deps = append(deps, coverAction)
729+
}
730+
a.Deps = append(a.Deps, b.cgoAction(p, a.Objdir, deps, coverAction != nil))
731+
}
732+
680733
return a
681734
})
682735

@@ -701,6 +754,114 @@ func (b *Builder) CompileAction(mode, depMode BuildMode, p *load.Package) *Actio
701754
return a
702755
}
703756

757+
func (b *Builder) cgoAction(p *load.Package, objdir string, deps []*Action, hasCover bool) *Action {
758+
cgoCollectAction := b.cacheAction("cgo collect", p, func() *Action {
759+
// Run cgo
760+
runCgo := b.cacheAction("cgo run", p, func() *Action {
761+
return &Action{
762+
Package: p,
763+
Mode: "cgo run",
764+
Actor: &runCgoActor{},
765+
Objdir: objdir,
766+
Deps: deps,
767+
}
768+
})
769+
770+
// Determine which files swig will produce in the cgo run action. We'll need to create
771+
// actions to compile the C and C++ files produced by swig, as well as the C file
772+
// produced by cgo processing swig's Go file outputs.
773+
swigGo, swigC, swigCXX := b.swigOutputs(p, objdir)
774+
775+
oseq := 0
776+
nextOfile := func() string {
777+
oseq++
778+
return objdir + fmt.Sprintf("_x%03d.o", oseq)
779+
}
780+
compileAction := func(file string, getFlagsFunc func(*runCgoProvider) []string, compileFunc func(*Action, string, string, []string, string) error) *Action {
781+
mode := "cgo compile " + file
782+
return b.cacheAction(mode, p, func() *Action {
783+
return &Action{
784+
Package: p,
785+
Mode: mode,
786+
Actor: &cgoCompileActor{file: file, getFlagsFunc: getFlagsFunc, compileFunc: compileFunc},
787+
Deps: []*Action{runCgo},
788+
Objdir: objdir,
789+
Target: nextOfile(),
790+
}
791+
})
792+
}
793+
794+
var collectDeps []*Action
795+
796+
// Add compile actions for C files generated by cgo.
797+
cgoFiles := p.CgoFiles
798+
if hasCover {
799+
cgoFiles = slices.Clone(cgoFiles)
800+
for i := range cgoFiles {
801+
cgoFiles[i] = strings.TrimSuffix(cgoFiles[i], ".go") + ".cover.go"
802+
}
803+
}
804+
cfiles := []string{"_cgo_export.c"}
805+
for _, fn := range slices.Concat(cgoFiles, swigGo) {
806+
cfiles = append(cfiles, strings.TrimSuffix(filepath.Base(fn), ".go")+".cgo2.c")
807+
}
808+
for _, f := range cfiles {
809+
collectDeps = append(collectDeps, compileAction(objdir+f, (*runCgoProvider).cflags, b.gcc))
810+
}
811+
812+
// Add compile actions for S files.
813+
var sfiles []string
814+
// In a package using cgo, cgo compiles the C, C++ and assembly files with gcc.
815+
// There is one exception: runtime/cgo's job is to bridge the
816+
// cgo and non-cgo worlds, so it necessarily has files in both.
817+
// In that case gcc only gets the gcc_* files.
818+
if p.Standard && p.ImportPath == "runtime/cgo" {
819+
for _, f := range p.SFiles {
820+
if strings.HasPrefix(f, "gcc_") {
821+
sfiles = append(sfiles, f)
822+
}
823+
}
824+
} else {
825+
sfiles = p.SFiles
826+
}
827+
for _, f := range sfiles {
828+
collectDeps = append(collectDeps, compileAction(f, (*runCgoProvider).cflags, b.gas))
829+
}
830+
831+
// Add compile actions for C files in the package, M files, and those generated by swig.
832+
for _, f := range slices.Concat(p.CFiles, p.MFiles, swigC) {
833+
collectDeps = append(collectDeps, compileAction(f, (*runCgoProvider).cflags, b.gcc))
834+
}
835+
836+
// Add compile actions for C++ files in the package, and those generated by swig.
837+
for _, f := range slices.Concat(p.CXXFiles, swigCXX) {
838+
collectDeps = append(collectDeps, compileAction(f, (*runCgoProvider).cxxflags, b.gxx))
839+
}
840+
841+
// Add compile actions for Fortran files in the package.
842+
for _, f := range p.FFiles {
843+
collectDeps = append(collectDeps, compileAction(f, (*runCgoProvider).fflags, b.gfortran))
844+
}
845+
846+
// Add a single convenience action that does nothing to join the previous action,
847+
// and better separate the cgo action dependencies of the build action from the
848+
// build actions for its package dependencies.
849+
return &Action{
850+
Mode: "collect cgo",
851+
Actor: ActorFunc(func(b *Builder, ctx context.Context, a *Action) error {
852+
// Use the cgo run action's provider as our provider output,
853+
// so it can be easily accessed by the build action.
854+
a.Provider = a.Deps[0].Deps[0].Provider
855+
return nil
856+
}),
857+
Deps: collectDeps,
858+
Objdir: objdir,
859+
}
860+
})
861+
862+
return cgoCollectAction
863+
}
864+
704865
// VetAction returns the action for running go vet on package p.
705866
// It depends on the action for compiling p.
706867
// If the caller may be causing p to be installed, it is up to the caller

0 commit comments

Comments
 (0)