Merge "Refactor mixed builds to only take one pass" am: 489128b8ef
Original change: https://android-review.googlesource.com/c/platform/build/soong/+/2094705 Change-Id: I635d17c1de4c6e0d0ae3e093b01f6445fe214c1d Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
committed by
Automerger Merge Worker
commit
935261d614
@@ -115,6 +115,27 @@ type Bazelable interface {
|
|||||||
SetBaseModuleType(baseModuleType string)
|
SetBaseModuleType(baseModuleType string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MixedBuildBuildable is an interface that module types should implement in order
|
||||||
|
// to be "handled by Bazel" in a mixed build.
|
||||||
|
type MixedBuildBuildable interface {
|
||||||
|
// IsMixedBuildSupported returns true if and only if this module should be
|
||||||
|
// "handled by Bazel" in a mixed build.
|
||||||
|
// This "escape hatch" allows modules with corner-case scenarios to opt out
|
||||||
|
// of being built with Bazel.
|
||||||
|
IsMixedBuildSupported(ctx BaseModuleContext) bool
|
||||||
|
|
||||||
|
// QueueBazelCall invokes request-queueing functions on the BazelContext
|
||||||
|
// so that these requests are handled when Bazel's cquery is invoked.
|
||||||
|
QueueBazelCall(ctx BaseModuleContext)
|
||||||
|
|
||||||
|
// ProcessBazelQueryResponse uses Bazel information (obtained from the BazelContext)
|
||||||
|
// to set module fields and providers to propagate this module's metadata upstream.
|
||||||
|
// This effectively "bridges the gap" between Bazel and Soong in a mixed build.
|
||||||
|
// Soong modules depending on this module should be oblivious to the fact that
|
||||||
|
// this module was handled by Bazel.
|
||||||
|
ProcessBazelQueryResponse(ctx ModuleContext)
|
||||||
|
}
|
||||||
|
|
||||||
// BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
|
// BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
|
||||||
type BazelModule interface {
|
type BazelModule interface {
|
||||||
Module
|
Module
|
||||||
@@ -342,7 +363,7 @@ func shouldKeepExistingBuildFileForDir(allowlist bp2BuildConversionAllowlist, di
|
|||||||
// converted or handcrafted Bazel target. As a side effect, calling this
|
// converted or handcrafted Bazel target. As a side effect, calling this
|
||||||
// method will also log whether this module is mixed build enabled for
|
// method will also log whether this module is mixed build enabled for
|
||||||
// metrics reporting.
|
// metrics reporting.
|
||||||
func MixedBuildsEnabled(ctx ModuleContext) bool {
|
func MixedBuildsEnabled(ctx BaseModuleContext) bool {
|
||||||
mixedBuildEnabled := mixedBuildPossible(ctx)
|
mixedBuildEnabled := mixedBuildPossible(ctx)
|
||||||
ctx.Config().LogMixedBuild(ctx, mixedBuildEnabled)
|
ctx.Config().LogMixedBuild(ctx, mixedBuildEnabled)
|
||||||
return mixedBuildEnabled
|
return mixedBuildEnabled
|
||||||
@@ -350,7 +371,7 @@ func MixedBuildsEnabled(ctx ModuleContext) bool {
|
|||||||
|
|
||||||
// mixedBuildPossible returns true if a module is ready to be replaced by a
|
// mixedBuildPossible returns true if a module is ready to be replaced by a
|
||||||
// converted or handcrafted Bazel target.
|
// converted or handcrafted Bazel target.
|
||||||
func mixedBuildPossible(ctx ModuleContext) bool {
|
func mixedBuildPossible(ctx BaseModuleContext) bool {
|
||||||
if ctx.Os() == Windows {
|
if ctx.Os() == Windows {
|
||||||
// Windows toolchains are not currently supported.
|
// Windows toolchains are not currently supported.
|
||||||
return false
|
return false
|
||||||
|
@@ -33,6 +33,26 @@ import (
|
|||||||
"android/soong/bazel"
|
"android/soong/bazel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterMixedBuildsMutator(InitRegistrationContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterMixedBuildsMutator(ctx RegistrationContext) {
|
||||||
|
ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
|
||||||
|
ctx.BottomUp("mixed_builds_prep", mixedBuildsPrepareMutator).Parallel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func mixedBuildsPrepareMutator(ctx BottomUpMutatorContext) {
|
||||||
|
if m := ctx.Module(); m.Enabled() {
|
||||||
|
if mixedBuildMod, ok := m.(MixedBuildBuildable); ok {
|
||||||
|
if mixedBuildMod.IsMixedBuildSupported(ctx) && MixedBuildsEnabled(ctx) {
|
||||||
|
mixedBuildMod.QueueBazelCall(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type cqueryRequest interface {
|
type cqueryRequest interface {
|
||||||
// Name returns a string name for this request type. Such request type names must be unique,
|
// Name returns a string name for this request type. Such request type names must be unique,
|
||||||
// and must only consist of alphanumeric characters.
|
// and must only consist of alphanumeric characters.
|
||||||
@@ -62,33 +82,32 @@ type cqueryKey struct {
|
|||||||
configKey configKey
|
configKey configKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// bazelHandler is the interface for a helper object related to deferring to Bazel for
|
// BazelContext is a context object useful for interacting with Bazel during
|
||||||
// processing a module (during Bazel mixed builds). Individual module types should define
|
// the course of a build. Use of Bazel to evaluate part of the build graph
|
||||||
// their own bazel handler if they support deferring to Bazel.
|
// is referred to as a "mixed build". (Some modules are managed by Soong,
|
||||||
type BazelHandler interface {
|
// some are managed by Bazel). To facilitate interop between these build
|
||||||
// Issue query to Bazel to retrieve information about Bazel's view of the current module.
|
// subgraphs, Soong may make requests to Bazel and evaluate their responses
|
||||||
// If Bazel returns this information, set module properties on the current module to reflect
|
// so that Soong modules may accurately depend on Bazel targets.
|
||||||
// the returned information.
|
|
||||||
// Returns true if information was available from Bazel, false if bazel invocation still needs to occur.
|
|
||||||
GenerateBazelBuildActions(ctx ModuleContext, label string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type BazelContext interface {
|
type BazelContext interface {
|
||||||
// The methods below involve queuing cquery requests to be later invoked
|
// Add a cquery request to the bazel request queue. All queued requests
|
||||||
// by bazel. If any of these methods return (_, false), then the request
|
// will be sent to Bazel on a subsequent invocation of InvokeBazel.
|
||||||
// has been queued to be run later.
|
QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey)
|
||||||
|
|
||||||
|
// ** Cquery Results Retrieval Functions
|
||||||
|
// The below functions pertain to retrieving cquery results from a prior
|
||||||
|
// InvokeBazel function call and parsing the results.
|
||||||
|
|
||||||
// Returns result files built by building the given bazel target label.
|
// Returns result files built by building the given bazel target label.
|
||||||
GetOutputFiles(label string, cfgKey configKey) ([]string, bool)
|
GetOutputFiles(label string, cfgKey configKey) ([]string, error)
|
||||||
|
|
||||||
// TODO(cparsons): Other cquery-related methods should be added here.
|
|
||||||
// Returns the results of GetOutputFiles and GetCcObjectFiles in a single query (in that order).
|
// Returns the results of GetOutputFiles and GetCcObjectFiles in a single query (in that order).
|
||||||
GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error)
|
GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error)
|
||||||
|
|
||||||
// Returns the executable binary resultant from building together the python sources
|
// Returns the executable binary resultant from building together the python sources
|
||||||
GetPythonBinary(label string, cfgKey configKey) (string, bool)
|
// TODO(b/232976601): Remove.
|
||||||
|
GetPythonBinary(label string, cfgKey configKey) (string, error)
|
||||||
|
|
||||||
// ** End cquery methods
|
// ** end Cquery Results Retrieval Functions
|
||||||
|
|
||||||
// Issues commands to Bazel to receive results for all cquery requests
|
// Issues commands to Bazel to receive results for all cquery requests
|
||||||
// queued in the BazelContext.
|
// queued in the BazelContext.
|
||||||
@@ -153,19 +172,23 @@ type MockBazelContext struct {
|
|||||||
LabelToPythonBinary map[string]string
|
LabelToPythonBinary map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m MockBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
|
func (m MockBazelContext) QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey) {
|
||||||
result, ok := m.LabelToOutputFiles[label]
|
panic("unimplemented")
|
||||||
return result, ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m MockBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
|
func (m MockBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, error) {
|
||||||
result, ok := m.LabelToCcInfo[label]
|
result, _ := m.LabelToOutputFiles[label]
|
||||||
return result, ok, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m MockBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
|
func (m MockBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error) {
|
||||||
result, ok := m.LabelToPythonBinary[label]
|
result, _ := m.LabelToCcInfo[label]
|
||||||
return result, ok
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MockBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, error) {
|
||||||
|
result, _ := m.LabelToPythonBinary[label]
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m MockBazelContext) InvokeBazel() error {
|
func (m MockBazelContext) InvokeBazel() error {
|
||||||
@@ -188,46 +211,53 @@ func (m MockBazelContext) AqueryDepsets() []bazel.AqueryDepset {
|
|||||||
|
|
||||||
var _ BazelContext = MockBazelContext{}
|
var _ BazelContext = MockBazelContext{}
|
||||||
|
|
||||||
func (bazelCtx *bazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
|
func (bazelCtx *bazelContext) QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey) {
|
||||||
rawString, ok := bazelCtx.cquery(label, cquery.GetOutputFiles, cfgKey)
|
key := cqueryKey{label, requestType, cfgKey}
|
||||||
var ret []string
|
bazelCtx.requestMutex.Lock()
|
||||||
if ok {
|
defer bazelCtx.requestMutex.Unlock()
|
||||||
|
bazelCtx.requests[key] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bazelCtx *bazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, error) {
|
||||||
|
key := cqueryKey{label, cquery.GetOutputFiles, cfgKey}
|
||||||
|
if rawString, ok := bazelCtx.results[key]; ok {
|
||||||
bazelOutput := strings.TrimSpace(rawString)
|
bazelOutput := strings.TrimSpace(rawString)
|
||||||
ret = cquery.GetOutputFiles.ParseResult(bazelOutput)
|
return cquery.GetOutputFiles.ParseResult(bazelOutput), nil
|
||||||
}
|
}
|
||||||
return ret, ok
|
return nil, fmt.Errorf("no bazel response found for %v", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bazelCtx *bazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
|
func (bazelCtx *bazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error) {
|
||||||
result, ok := bazelCtx.cquery(label, cquery.GetCcInfo, cfgKey)
|
key := cqueryKey{label, cquery.GetCcInfo, cfgKey}
|
||||||
if !ok {
|
if rawString, ok := bazelCtx.results[key]; ok {
|
||||||
return cquery.CcInfo{}, ok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
bazelOutput := strings.TrimSpace(result)
|
|
||||||
ret, err := cquery.GetCcInfo.ParseResult(bazelOutput)
|
|
||||||
return ret, ok, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bazelCtx *bazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
|
|
||||||
rawString, ok := bazelCtx.cquery(label, cquery.GetPythonBinary, cfgKey)
|
|
||||||
var ret string
|
|
||||||
if ok {
|
|
||||||
bazelOutput := strings.TrimSpace(rawString)
|
bazelOutput := strings.TrimSpace(rawString)
|
||||||
ret = cquery.GetPythonBinary.ParseResult(bazelOutput)
|
return cquery.GetCcInfo.ParseResult(bazelOutput)
|
||||||
}
|
}
|
||||||
return ret, ok
|
return cquery.CcInfo{}, fmt.Errorf("no bazel response found for %v", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n noopBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
|
func (bazelCtx *bazelContext) GetPythonBinary(label string, cfgKey configKey) (string, error) {
|
||||||
|
key := cqueryKey{label, cquery.GetPythonBinary, cfgKey}
|
||||||
|
if rawString, ok := bazelCtx.results[key]; ok {
|
||||||
|
bazelOutput := strings.TrimSpace(rawString)
|
||||||
|
return cquery.GetPythonBinary.ParseResult(bazelOutput), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no bazel response found for %v", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n noopBazelContext) QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey) {
|
||||||
panic("unimplemented")
|
panic("unimplemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n noopBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
|
func (n noopBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, error) {
|
||||||
panic("unimplemented")
|
panic("unimplemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n noopBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
|
func (n noopBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n noopBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, error) {
|
||||||
panic("unimplemented")
|
panic("unimplemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,24 +344,6 @@ func (context *bazelContext) BazelEnabled() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a cquery request to the Bazel request queue, to be later invoked, or
|
|
||||||
// returns the result of the given request if the request was already made.
|
|
||||||
// If the given request was already made (and the results are available), then
|
|
||||||
// returns (result, true). If the request is queued but no results are available,
|
|
||||||
// then returns ("", false).
|
|
||||||
func (context *bazelContext) cquery(label string, requestType cqueryRequest,
|
|
||||||
cfgKey configKey) (string, bool) {
|
|
||||||
key := cqueryKey{label, requestType, cfgKey}
|
|
||||||
if result, ok := context.results[key]; ok {
|
|
||||||
return result, true
|
|
||||||
} else {
|
|
||||||
context.requestMutex.Lock()
|
|
||||||
defer context.requestMutex.Unlock()
|
|
||||||
context.requests[key] = true
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pwdPrefix() string {
|
func pwdPrefix() string {
|
||||||
// Darwin doesn't have /proc
|
// Darwin doesn't have /proc
|
||||||
if runtime.GOOS != "darwin" {
|
if runtime.GOOS != "darwin" {
|
||||||
@@ -916,7 +928,7 @@ func getConfigString(key cqueryKey) string {
|
|||||||
return arch + "|" + os
|
return arch + "|" + os
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfigKey(ctx ModuleContext) configKey {
|
func GetConfigKey(ctx BaseModuleContext) configKey {
|
||||||
return configKey{
|
return configKey{
|
||||||
// use string because Arch is not a valid key in go
|
// use string because Arch is not a valid key in go
|
||||||
arch: ctx.Arch().String(),
|
arch: ctx.Arch().String(),
|
||||||
|
@@ -5,6 +5,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"android/soong/bazel/cquery"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRequestResultsAfterInvokeBazel(t *testing.T) {
|
func TestRequestResultsAfterInvokeBazel(t *testing.T) {
|
||||||
@@ -13,17 +15,14 @@ func TestRequestResultsAfterInvokeBazel(t *testing.T) {
|
|||||||
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
|
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
|
||||||
bazelCommand{command: "cquery", expression: "deps(@soong_injection//mixed_builds:buildroot, 2)"}: `//foo:bar|arm64_armv8-a|android>>out/foo/bar.txt`,
|
bazelCommand{command: "cquery", expression: "deps(@soong_injection//mixed_builds:buildroot, 2)"}: `//foo:bar|arm64_armv8-a|android>>out/foo/bar.txt`,
|
||||||
})
|
})
|
||||||
g, ok := bazelContext.GetOutputFiles(label, cfg)
|
bazelContext.QueueBazelRequest(label, cquery.GetOutputFiles, cfg)
|
||||||
if ok {
|
|
||||||
t.Errorf("Did not expect cquery results prior to running InvokeBazel(), but got %s", g)
|
|
||||||
}
|
|
||||||
err := bazelContext.InvokeBazel()
|
err := bazelContext.InvokeBazel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
|
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
|
||||||
}
|
}
|
||||||
g, ok = bazelContext.GetOutputFiles(label, cfg)
|
g, err := bazelContext.GetOutputFiles(label, cfg)
|
||||||
if !ok {
|
if err != nil {
|
||||||
t.Errorf("Expected cquery results after running InvokeBazel(), but got none")
|
t.Errorf("Expected cquery results after running InvokeBazel(), but got err %v", err)
|
||||||
} else if w := []string{"out/foo/bar.txt"}; !reflect.DeepEqual(w, g) {
|
} else if w := []string{"out/foo/bar.txt"}; !reflect.DeepEqual(w, g) {
|
||||||
t.Errorf("Expected output %s, got %s", w, g)
|
t.Errorf("Expected output %s, got %s", w, g)
|
||||||
}
|
}
|
||||||
|
@@ -2047,7 +2047,7 @@ func (c *config) UseHostMusl() bool {
|
|||||||
return Bool(c.productVariables.HostMusl)
|
return Bool(c.productVariables.HostMusl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *config) LogMixedBuild(ctx ModuleContext, useBazel bool) {
|
func (c *config) LogMixedBuild(ctx BaseModuleContext, useBazel bool) {
|
||||||
moduleName := ctx.Module().Name()
|
moduleName := ctx.Module().Name()
|
||||||
c.mixedBuildsLock.Lock()
|
c.mixedBuildsLock.Lock()
|
||||||
defer c.mixedBuildsLock.Unlock()
|
defer c.mixedBuildsLock.Unlock()
|
||||||
|
@@ -18,6 +18,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"android/soong/bazel"
|
"android/soong/bazel"
|
||||||
|
"android/soong/bazel/cquery"
|
||||||
|
|
||||||
"github.com/google/blueprint"
|
"github.com/google/blueprint"
|
||||||
)
|
)
|
||||||
@@ -101,6 +102,7 @@ type fileGroup struct {
|
|||||||
srcs Paths
|
srcs Paths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ MixedBuildBuildable = (*fileGroup)(nil)
|
||||||
var _ SourceFileProducer = (*fileGroup)(nil)
|
var _ SourceFileProducer = (*fileGroup)(nil)
|
||||||
|
|
||||||
// filegroup contains a list of files that are referenced by other modules
|
// filegroup contains a list of files that are referenced by other modules
|
||||||
@@ -114,42 +116,11 @@ func FileGroupFactory() Module {
|
|||||||
return module
|
return module
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fg *fileGroup) maybeGenerateBazelBuildActions(ctx ModuleContext) {
|
|
||||||
if !MixedBuildsEnabled(ctx) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
archVariant := ctx.Arch().String()
|
|
||||||
osVariant := ctx.Os()
|
|
||||||
if len(fg.Srcs()) == 1 && fg.Srcs()[0].Base() == fg.Name() {
|
|
||||||
// This will be a regular file target, not filegroup, in Bazel.
|
|
||||||
// See FilegroupBp2Build for more information.
|
|
||||||
archVariant = Common.String()
|
|
||||||
osVariant = CommonOS
|
|
||||||
}
|
|
||||||
|
|
||||||
bazelCtx := ctx.Config().BazelContext
|
|
||||||
filePaths, ok := bazelCtx.GetOutputFiles(fg.GetBazelLabel(ctx, fg), configKey{archVariant, osVariant})
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bazelOuts := make(Paths, 0, len(filePaths))
|
|
||||||
for _, p := range filePaths {
|
|
||||||
src := PathForBazelOut(ctx, p)
|
|
||||||
bazelOuts = append(bazelOuts, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
fg.srcs = bazelOuts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fg *fileGroup) GenerateAndroidBuildActions(ctx ModuleContext) {
|
func (fg *fileGroup) GenerateAndroidBuildActions(ctx ModuleContext) {
|
||||||
fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs)
|
fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs)
|
||||||
if fg.properties.Path != nil {
|
if fg.properties.Path != nil {
|
||||||
fg.srcs = PathsWithModuleSrcSubDir(ctx, fg.srcs, String(fg.properties.Path))
|
fg.srcs = PathsWithModuleSrcSubDir(ctx, fg.srcs, String(fg.properties.Path))
|
||||||
}
|
}
|
||||||
|
|
||||||
fg.maybeGenerateBazelBuildActions(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fg *fileGroup) Srcs() Paths {
|
func (fg *fileGroup) Srcs() Paths {
|
||||||
@@ -161,3 +132,38 @@ func (fg *fileGroup) MakeVars(ctx MakeVarsModuleContext) {
|
|||||||
ctx.StrictRaw(makeVar, strings.Join(fg.srcs.Strings(), " "))
|
ctx.StrictRaw(makeVar, strings.Join(fg.srcs.Strings(), " "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fg *fileGroup) QueueBazelCall(ctx BaseModuleContext) {
|
||||||
|
bazelCtx := ctx.Config().BazelContext
|
||||||
|
|
||||||
|
bazelCtx.QueueBazelRequest(
|
||||||
|
fg.GetBazelLabel(ctx, fg),
|
||||||
|
cquery.GetOutputFiles,
|
||||||
|
configKey{Common.String(), CommonOS})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fg *fileGroup) IsMixedBuildSupported(ctx BaseModuleContext) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fg *fileGroup) ProcessBazelQueryResponse(ctx ModuleContext) {
|
||||||
|
fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs)
|
||||||
|
if fg.properties.Path != nil {
|
||||||
|
fg.srcs = PathsWithModuleSrcSubDir(ctx, fg.srcs, String(fg.properties.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
bazelCtx := ctx.Config().BazelContext
|
||||||
|
filePaths, err := bazelCtx.GetOutputFiles(fg.GetBazelLabel(ctx, fg), configKey{Common.String(), CommonOS})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ModuleErrorf(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bazelOuts := make(Paths, 0, len(filePaths))
|
||||||
|
for _, p := range filePaths {
|
||||||
|
src := PathForBazelOut(ctx, p)
|
||||||
|
bazelOuts = append(bazelOuts, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
fg.srcs = bazelOuts
|
||||||
|
}
|
||||||
|
@@ -2275,7 +2275,11 @@ func (m *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mixedBuildMod, handled := m.isHandledByBazel(ctx); handled {
|
||||||
|
mixedBuildMod.ProcessBazelQueryResponse(ctx)
|
||||||
|
} else {
|
||||||
m.module.GenerateAndroidBuildActions(ctx)
|
m.module.GenerateAndroidBuildActions(ctx)
|
||||||
|
}
|
||||||
if ctx.Failed() {
|
if ctx.Failed() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2331,6 +2335,18 @@ func (m *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext)
|
|||||||
m.variables = ctx.variables
|
m.variables = ctx.variables
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *ModuleBase) isHandledByBazel(ctx ModuleContext) (MixedBuildBuildable, bool) {
|
||||||
|
if !ctx.Config().BazelContext.BazelEnabled() {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if mixedBuildMod, ok := m.module.(MixedBuildBuildable); ok {
|
||||||
|
if mixedBuildMod.IsMixedBuildSupported(ctx) && MixedBuildsEnabled(ctx) {
|
||||||
|
return mixedBuildMod, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
// Check the supplied dist structure to make sure that it is valid.
|
// Check the supplied dist structure to make sure that it is valid.
|
||||||
//
|
//
|
||||||
// property - the base property, e.g. dist or dists[1], which is combined with the
|
// property - the base property, e.g. dist or dists[1], which is combined with the
|
||||||
|
22
cc/binary.go
22
cc/binary.go
@@ -17,6 +17,7 @@ package cc
|
|||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"android/soong/bazel/cquery"
|
||||||
"github.com/google/blueprint"
|
"github.com/google/blueprint"
|
||||||
"github.com/google/blueprint/proptools"
|
"github.com/google/blueprint/proptools"
|
||||||
|
|
||||||
@@ -562,26 +563,33 @@ func (binary *binaryDecorator) verifyHostBionicLinker(ctx ModuleContext, in, lin
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ccBinaryBazelHandler struct {
|
type ccBinaryBazelHandler struct {
|
||||||
android.BazelHandler
|
BazelHandler
|
||||||
|
|
||||||
module *Module
|
module *Module
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *ccBinaryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
|
func (handler *ccBinaryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
|
||||||
bazelCtx := ctx.Config().BazelContext
|
bazelCtx := ctx.Config().BazelContext
|
||||||
filePaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
|
bazelCtx.QueueBazelRequest(label, cquery.GetOutputFiles, android.GetConfigKey(ctx))
|
||||||
if ok {
|
}
|
||||||
|
|
||||||
|
func (handler *ccBinaryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
|
||||||
|
bazelCtx := ctx.Config().BazelContext
|
||||||
|
filePaths, err := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
|
||||||
|
if err != nil {
|
||||||
|
ctx.ModuleErrorf(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if len(filePaths) != 1 {
|
if len(filePaths) != 1 {
|
||||||
ctx.ModuleErrorf("expected exactly one output file for '%s', but got %s", label, filePaths)
|
ctx.ModuleErrorf("expected exactly one output file for '%s', but got %s", label, filePaths)
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
outputFilePath := android.PathForBazelOut(ctx, filePaths[0])
|
outputFilePath := android.PathForBazelOut(ctx, filePaths[0])
|
||||||
handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
|
handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
|
||||||
// TODO(b/220164721): We need to decide if we should return the stripped as the unstripped.
|
// TODO(b/220164721): We need to decide if we should return the stripped as the unstripped.
|
||||||
handler.module.linker.(*binaryDecorator).unstrippedOutputFile = outputFilePath
|
handler.module.linker.(*binaryDecorator).unstrippedOutputFile = outputFilePath
|
||||||
}
|
}
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func binaryBp2build(ctx android.TopDownMutatorContext, m *Module, typ string) {
|
func binaryBp2build(ctx android.TopDownMutatorContext, m *Module, typ string) {
|
||||||
baseAttrs := bp2BuildParseBaseProps(ctx, m)
|
baseAttrs := bp2BuildParseBaseProps(ctx, m)
|
||||||
|
70
cc/cc.go
70
cc/cc.go
@@ -772,6 +772,19 @@ func IsTestPerSrcDepTag(depTag blueprint.DependencyTag) bool {
|
|||||||
return ok && ccDepTag == testPerSrcDepTag
|
return ok && ccDepTag == testPerSrcDepTag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bazelHandler is the interface for a helper object related to deferring to Bazel for
|
||||||
|
// processing a cc module (during Bazel mixed builds). Individual module types should define
|
||||||
|
// their own bazel handler if they support being handled by Bazel.
|
||||||
|
type BazelHandler interface {
|
||||||
|
// QueueBazelCall invokes request-queueing functions on the BazelContext
|
||||||
|
//so that these requests are handled when Bazel's cquery is invoked.
|
||||||
|
QueueBazelCall(ctx android.BaseModuleContext, label string)
|
||||||
|
|
||||||
|
// ProcessBazelQueryResponse uses information retrieved from Bazel to set properties
|
||||||
|
// on the current module with given label.
|
||||||
|
ProcessBazelQueryResponse(ctx android.ModuleContext, label string)
|
||||||
|
}
|
||||||
|
|
||||||
// Module contains the properties and members used by all C/C++ module types, and implements
|
// Module contains the properties and members used by all C/C++ module types, and implements
|
||||||
// the blueprint.Module interface. It delegates to compiler, linker, and installer interfaces
|
// the blueprint.Module interface. It delegates to compiler, linker, and installer interfaces
|
||||||
// to construct the output file. Behavior can be customized with a Customizer, or "decorator",
|
// to construct the output file. Behavior can be customized with a Customizer, or "decorator",
|
||||||
@@ -811,7 +824,7 @@ type Module struct {
|
|||||||
compiler compiler
|
compiler compiler
|
||||||
linker linker
|
linker linker
|
||||||
installer installer
|
installer installer
|
||||||
bazelHandler android.BazelHandler
|
bazelHandler BazelHandler
|
||||||
|
|
||||||
features []feature
|
features []feature
|
||||||
stl *stl
|
stl *stl
|
||||||
@@ -1773,31 +1786,51 @@ func GetSubnameProperty(actx android.ModuleContext, c LinkableInterface) string
|
|||||||
return subName
|
return subName
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if Bazel was successfully used for the analysis of this module.
|
var _ android.MixedBuildBuildable = (*Module)(nil)
|
||||||
func (c *Module) maybeGenerateBazelActions(actx android.ModuleContext) bool {
|
|
||||||
var bazelModuleLabel string
|
func (c *Module) getBazelModuleLabel(ctx android.BaseModuleContext) string {
|
||||||
if c.typ() == fullLibrary && c.static() {
|
if c.typ() == fullLibrary && c.static() {
|
||||||
// cc_library is a special case in bp2build; two targets are generated -- one for each
|
// cc_library is a special case in bp2build; two targets are generated -- one for each
|
||||||
// of the shared and static variants. The shared variant keeps the module name, but the
|
// of the shared and static variants. The shared variant keeps the module name, but the
|
||||||
// static variant uses a different suffixed name.
|
// static variant uses a different suffixed name.
|
||||||
bazelModuleLabel = bazelLabelForStaticModule(actx, c)
|
return bazelLabelForStaticModule(ctx, c)
|
||||||
} else {
|
}
|
||||||
bazelModuleLabel = c.GetBazelLabel(actx, c)
|
return c.GetBazelLabel(ctx, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
bazelActionsUsed := false
|
func (c *Module) QueueBazelCall(ctx android.BaseModuleContext) {
|
||||||
// Mixed builds mode is disabled for modules outside of device OS.
|
c.bazelHandler.QueueBazelCall(ctx, c.getBazelModuleLabel(ctx))
|
||||||
// TODO(b/200841190): Support non-device OS in mixed builds.
|
|
||||||
if android.MixedBuildsEnabled(actx) && c.bazelHandler != nil {
|
|
||||||
bazelActionsUsed = c.bazelHandler.GenerateBazelBuildActions(actx, bazelModuleLabel)
|
|
||||||
}
|
}
|
||||||
return bazelActionsUsed
|
|
||||||
|
func (c *Module) IsMixedBuildSupported(ctx android.BaseModuleContext) bool {
|
||||||
|
return c.bazelHandler != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Module) ProcessBazelQueryResponse(ctx android.ModuleContext) {
|
||||||
|
bazelModuleLabel := c.getBazelModuleLabel(ctx)
|
||||||
|
|
||||||
|
c.bazelHandler.ProcessBazelQueryResponse(ctx, bazelModuleLabel)
|
||||||
|
|
||||||
|
c.Properties.SubName = GetSubnameProperty(ctx, c)
|
||||||
|
apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
|
||||||
|
if !apexInfo.IsForPlatform() {
|
||||||
|
c.hideApexVariantFromMake = true
|
||||||
|
}
|
||||||
|
|
||||||
|
c.makeLinkType = GetMakeLinkType(ctx, c)
|
||||||
|
|
||||||
|
mctx := &moduleContext{
|
||||||
|
ModuleContext: ctx,
|
||||||
|
moduleContextImpl: moduleContextImpl{
|
||||||
|
mod: c,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mctx.ctx = mctx
|
||||||
|
|
||||||
|
c.maybeInstall(mctx, apexInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
|
func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
|
||||||
// TODO(cparsons): Any logic in this method occurring prior to querying Bazel should be
|
|
||||||
// requested from Bazel instead.
|
|
||||||
|
|
||||||
// Handle the case of a test module split by `test_per_src` mutator.
|
// Handle the case of a test module split by `test_per_src` mutator.
|
||||||
//
|
//
|
||||||
// The `test_per_src` mutator adds an extra variation named "", depending on all the other
|
// The `test_per_src` mutator adds an extra variation named "", depending on all the other
|
||||||
@@ -1824,11 +1857,6 @@ func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
|
|||||||
}
|
}
|
||||||
ctx.ctx = ctx
|
ctx.ctx = ctx
|
||||||
|
|
||||||
if c.maybeGenerateBazelActions(actx) {
|
|
||||||
c.maybeInstall(ctx, apexInfo)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
deps := c.depsToPaths(ctx)
|
deps := c.depsToPaths(ctx)
|
||||||
if ctx.Failed() {
|
if ctx.Failed() {
|
||||||
return
|
return
|
||||||
|
@@ -642,18 +642,18 @@ type libraryDecorator struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ccLibraryBazelHandler struct {
|
type ccLibraryBazelHandler struct {
|
||||||
android.BazelHandler
|
BazelHandler
|
||||||
|
|
||||||
module *Module
|
module *Module
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateStaticBazelBuildActions constructs the StaticLibraryInfo Soong
|
// generateStaticBazelBuildActions constructs the StaticLibraryInfo Soong
|
||||||
// provider from a Bazel shared library's CcInfo provider.
|
// provider from a Bazel shared library's CcInfo provider.
|
||||||
func (handler *ccLibraryBazelHandler) generateStaticBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) bool {
|
func (handler *ccLibraryBazelHandler) generateStaticBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) {
|
||||||
rootStaticArchives := ccInfo.RootStaticArchives
|
rootStaticArchives := ccInfo.RootStaticArchives
|
||||||
if len(rootStaticArchives) != 1 {
|
if len(rootStaticArchives) != 1 {
|
||||||
ctx.ModuleErrorf("expected exactly one root archive file for '%s', but got %s", label, rootStaticArchives)
|
ctx.ModuleErrorf("expected exactly one root archive file for '%s', but got %s", label, rootStaticArchives)
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
outputFilePath := android.PathForBazelOut(ctx, rootStaticArchives[0])
|
outputFilePath := android.PathForBazelOut(ctx, rootStaticArchives[0])
|
||||||
handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
|
handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
|
||||||
@@ -679,17 +679,17 @@ func (handler *ccLibraryBazelHandler) generateStaticBazelBuildActions(ctx androi
|
|||||||
Build(),
|
Build(),
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateSharedBazelBuildActions constructs the SharedLibraryInfo Soong
|
// generateSharedBazelBuildActions constructs the SharedLibraryInfo Soong
|
||||||
// provider from a Bazel shared library's CcInfo provider.
|
// provider from a Bazel shared library's CcInfo provider.
|
||||||
func (handler *ccLibraryBazelHandler) generateSharedBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) bool {
|
func (handler *ccLibraryBazelHandler) generateSharedBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) {
|
||||||
rootDynamicLibraries := ccInfo.RootDynamicLibraries
|
rootDynamicLibraries := ccInfo.RootDynamicLibraries
|
||||||
|
|
||||||
if len(rootDynamicLibraries) != 1 {
|
if len(rootDynamicLibraries) != 1 {
|
||||||
ctx.ModuleErrorf("expected exactly one root dynamic library file for '%s', but got %s", label, rootDynamicLibraries)
|
ctx.ModuleErrorf("expected exactly one root dynamic library file for '%s', but got %s", label, rootDynamicLibraries)
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
outputFilePath := android.PathForBazelOut(ctx, rootDynamicLibraries[0])
|
outputFilePath := android.PathForBazelOut(ctx, rootDynamicLibraries[0])
|
||||||
handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
|
handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
|
||||||
@@ -709,30 +709,27 @@ func (handler *ccLibraryBazelHandler) generateSharedBazelBuildActions(ctx androi
|
|||||||
// TODO(b/190524881): Include transitive static libraries in this provider to support
|
// TODO(b/190524881): Include transitive static libraries in this provider to support
|
||||||
// static libraries with deps. The provider key for this is TransitiveStaticLibrariesForOrdering.
|
// static libraries with deps. The provider key for this is TransitiveStaticLibrariesForOrdering.
|
||||||
})
|
})
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *ccLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
|
func (handler *ccLibraryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
|
||||||
bazelCtx := ctx.Config().BazelContext
|
bazelCtx := ctx.Config().BazelContext
|
||||||
ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
|
bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *ccLibraryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
|
||||||
|
bazelCtx := ctx.Config().BazelContext
|
||||||
|
ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
|
ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
|
||||||
return false
|
return
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if handler.module.static() {
|
if handler.module.static() {
|
||||||
if ok := handler.generateStaticBazelBuildActions(ctx, label, ccInfo); !ok {
|
handler.generateStaticBazelBuildActions(ctx, label, ccInfo)
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else if handler.module.Shared() {
|
} else if handler.module.Shared() {
|
||||||
if ok := handler.generateSharedBazelBuildActions(ctx, label, ccInfo); !ok {
|
handler.generateSharedBazelBuildActions(ctx, label, ccInfo)
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return false
|
ctx.ModuleErrorf("Unhandled bazel case for %s (neither shared nor static!)", ctx.ModuleName())
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.module.linker.(*libraryDecorator).setFlagExporterInfoFromCcInfo(ctx, ccInfo)
|
handler.module.linker.(*libraryDecorator).setFlagExporterInfoFromCcInfo(ctx, ccInfo)
|
||||||
@@ -746,7 +743,6 @@ func (handler *ccLibraryBazelHandler) GenerateBazelBuildActions(ctx android.Modu
|
|||||||
// implementation.
|
// implementation.
|
||||||
i.(*libraryDecorator).collectedSnapshotHeaders = android.Paths{}
|
i.(*libraryDecorator).collectedSnapshotHeaders = android.Paths{}
|
||||||
}
|
}
|
||||||
return ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (library *libraryDecorator) setFlagExporterInfoFromCcInfo(ctx android.ModuleContext, ccInfo cquery.CcInfo) {
|
func (library *libraryDecorator) setFlagExporterInfoFromCcInfo(ctx android.ModuleContext, ccInfo cquery.CcInfo) {
|
||||||
|
@@ -17,6 +17,7 @@ package cc
|
|||||||
import (
|
import (
|
||||||
"android/soong/android"
|
"android/soong/android"
|
||||||
"android/soong/bazel"
|
"android/soong/bazel"
|
||||||
|
"android/soong/bazel/cquery"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -47,28 +48,30 @@ func RegisterLibraryHeadersBuildComponents(ctx android.RegistrationContext) {
|
|||||||
ctx.RegisterModuleType("cc_prebuilt_library_headers", prebuiltLibraryHeaderFactory)
|
ctx.RegisterModuleType("cc_prebuilt_library_headers", prebuiltLibraryHeaderFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
type libraryHeaderBazelHander struct {
|
type libraryHeaderBazelHandler struct {
|
||||||
android.BazelHandler
|
BazelHandler
|
||||||
|
|
||||||
module *Module
|
module *Module
|
||||||
library *libraryDecorator
|
library *libraryDecorator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *libraryHeaderBazelHander) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
|
func (handler *libraryHeaderBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
|
||||||
bazelCtx := ctx.Config().BazelContext
|
bazelCtx := ctx.Config().BazelContext
|
||||||
ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
|
bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
|
||||||
if err != nil {
|
|
||||||
ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
if !ok {
|
|
||||||
return false
|
func (h *libraryHeaderBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
|
||||||
|
bazelCtx := ctx.Config().BazelContext
|
||||||
|
ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
|
||||||
|
if err != nil {
|
||||||
|
ctx.ModuleErrorf(err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
outputPaths := ccInfo.OutputFiles
|
outputPaths := ccInfo.OutputFiles
|
||||||
if len(outputPaths) != 1 {
|
if len(outputPaths) != 1 {
|
||||||
ctx.ModuleErrorf("expected exactly one output file for %q, but got %q", label, outputPaths)
|
ctx.ModuleErrorf("expected exactly one output file for %q, but got %q", label, outputPaths)
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
outputPath := android.PathForBazelOut(ctx, outputPaths[0])
|
outputPath := android.PathForBazelOut(ctx, outputPaths[0])
|
||||||
@@ -83,8 +86,6 @@ func (h *libraryHeaderBazelHander) GenerateBazelBuildActions(ctx android.ModuleC
|
|||||||
// validation will fail. For now, set this to an empty list.
|
// validation will fail. For now, set this to an empty list.
|
||||||
// TODO(cparsons): More closely mirror the collectHeadersForSnapshot implementation.
|
// TODO(cparsons): More closely mirror the collectHeadersForSnapshot implementation.
|
||||||
h.library.collectedSnapshotHeaders = android.Paths{}
|
h.library.collectedSnapshotHeaders = android.Paths{}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cc_library_headers contains a set of c/c++ headers which are imported by
|
// cc_library_headers contains a set of c/c++ headers which are imported by
|
||||||
@@ -96,7 +97,7 @@ func LibraryHeaderFactory() android.Module {
|
|||||||
library.HeaderOnly()
|
library.HeaderOnly()
|
||||||
module.sdkMemberTypes = []android.SdkMemberType{headersLibrarySdkMemberType}
|
module.sdkMemberTypes = []android.SdkMemberType{headersLibrarySdkMemberType}
|
||||||
module.bazelable = true
|
module.bazelable = true
|
||||||
module.bazelHandler = &libraryHeaderBazelHander{module: module, library: library}
|
module.bazelHandler = &libraryHeaderBazelHandler{module: module, library: library}
|
||||||
return module.Init()
|
return module.Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
cc/object.go
22
cc/object.go
@@ -19,6 +19,7 @@ import (
|
|||||||
|
|
||||||
"android/soong/android"
|
"android/soong/android"
|
||||||
"android/soong/bazel"
|
"android/soong/bazel"
|
||||||
|
"android/soong/bazel/cquery"
|
||||||
)
|
)
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -46,24 +47,31 @@ type objectLinker struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type objectBazelHandler struct {
|
type objectBazelHandler struct {
|
||||||
android.BazelHandler
|
BazelHandler
|
||||||
|
|
||||||
module *Module
|
module *Module
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *objectBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
|
func (handler *objectBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
|
||||||
bazelCtx := ctx.Config().BazelContext
|
bazelCtx := ctx.Config().BazelContext
|
||||||
objPaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
|
bazelCtx.QueueBazelRequest(label, cquery.GetOutputFiles, android.GetConfigKey(ctx))
|
||||||
if ok {
|
}
|
||||||
|
|
||||||
|
func (handler *objectBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
|
||||||
|
bazelCtx := ctx.Config().BazelContext
|
||||||
|
objPaths, err := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
|
||||||
|
if err != nil {
|
||||||
|
ctx.ModuleErrorf(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if len(objPaths) != 1 {
|
if len(objPaths) != 1 {
|
||||||
ctx.ModuleErrorf("expected exactly one object file for '%s', but got %s", label, objPaths)
|
ctx.ModuleErrorf("expected exactly one object file for '%s', but got %s", label, objPaths)
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
|
handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
|
||||||
}
|
}
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
type ObjectLinkerProperties struct {
|
type ObjectLinkerProperties struct {
|
||||||
// list of static library modules that should only provide headers for this module.
|
// list of static library modules that should only provide headers for this module.
|
||||||
|
@@ -20,6 +20,7 @@ import (
|
|||||||
|
|
||||||
"android/soong/android"
|
"android/soong/android"
|
||||||
"android/soong/bazel"
|
"android/soong/bazel"
|
||||||
|
"android/soong/bazel/cquery"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -406,25 +407,28 @@ type prebuiltObjectLinker struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type prebuiltStaticLibraryBazelHandler struct {
|
type prebuiltStaticLibraryBazelHandler struct {
|
||||||
android.BazelHandler
|
BazelHandler
|
||||||
|
|
||||||
module *Module
|
module *Module
|
||||||
library *libraryDecorator
|
library *libraryDecorator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *prebuiltStaticLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
|
func (h *prebuiltStaticLibraryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
|
||||||
bazelCtx := ctx.Config().BazelContext
|
bazelCtx := ctx.Config().BazelContext
|
||||||
ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
|
bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
|
||||||
if err != nil {
|
|
||||||
ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
|
|
||||||
}
|
}
|
||||||
if !ok {
|
|
||||||
return false
|
func (h *prebuiltStaticLibraryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
|
||||||
|
bazelCtx := ctx.Config().BazelContext
|
||||||
|
ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
|
||||||
|
if err != nil {
|
||||||
|
ctx.ModuleErrorf(err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
staticLibs := ccInfo.CcStaticLibraryFiles
|
staticLibs := ccInfo.CcStaticLibraryFiles
|
||||||
if len(staticLibs) > 1 {
|
if len(staticLibs) > 1 {
|
||||||
ctx.ModuleErrorf("expected 1 static library from bazel target %q, got %s", label, staticLibs)
|
ctx.ModuleErrorf("expected 1 static library from bazel target %q, got %s", label, staticLibs)
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(b/184543518): cc_prebuilt_library_static may have properties for re-exporting flags
|
// TODO(b/184543518): cc_prebuilt_library_static may have properties for re-exporting flags
|
||||||
@@ -439,7 +443,7 @@ func (h *prebuiltStaticLibraryBazelHandler) GenerateBazelBuildActions(ctx androi
|
|||||||
|
|
||||||
if len(staticLibs) == 0 {
|
if len(staticLibs) == 0 {
|
||||||
h.module.outputFile = android.OptionalPath{}
|
h.module.outputFile = android.OptionalPath{}
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out := android.PathForBazelOut(ctx, staticLibs[0])
|
out := android.PathForBazelOut(ctx, staticLibs[0])
|
||||||
@@ -451,30 +455,31 @@ func (h *prebuiltStaticLibraryBazelHandler) GenerateBazelBuildActions(ctx androi
|
|||||||
|
|
||||||
TransitiveStaticLibrariesForOrdering: depSet,
|
TransitiveStaticLibrariesForOrdering: depSet,
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type prebuiltSharedLibraryBazelHandler struct {
|
type prebuiltSharedLibraryBazelHandler struct {
|
||||||
android.BazelHandler
|
BazelHandler
|
||||||
|
|
||||||
module *Module
|
module *Module
|
||||||
library *libraryDecorator
|
library *libraryDecorator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *prebuiltSharedLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
|
func (h *prebuiltSharedLibraryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
|
||||||
bazelCtx := ctx.Config().BazelContext
|
bazelCtx := ctx.Config().BazelContext
|
||||||
ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
|
bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
|
||||||
if err != nil {
|
|
||||||
ctx.ModuleErrorf("Error getting Bazel CcInfo for %s: %s", label, err)
|
|
||||||
}
|
}
|
||||||
if !ok {
|
|
||||||
return false
|
func (h *prebuiltSharedLibraryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
|
||||||
|
bazelCtx := ctx.Config().BazelContext
|
||||||
|
ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
|
||||||
|
if err != nil {
|
||||||
|
ctx.ModuleErrorf(err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
sharedLibs := ccInfo.CcSharedLibraryFiles
|
sharedLibs := ccInfo.CcSharedLibraryFiles
|
||||||
if len(sharedLibs) != 1 {
|
if len(sharedLibs) != 1 {
|
||||||
ctx.ModuleErrorf("expected 1 shared library from bazel target %s, got %q", label, sharedLibs)
|
ctx.ModuleErrorf("expected 1 shared library from bazel target %s, got %q", label, sharedLibs)
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(b/184543518): cc_prebuilt_library_shared may have properties for re-exporting flags
|
// TODO(b/184543518): cc_prebuilt_library_shared may have properties for re-exporting flags
|
||||||
@@ -489,7 +494,7 @@ func (h *prebuiltSharedLibraryBazelHandler) GenerateBazelBuildActions(ctx androi
|
|||||||
|
|
||||||
if len(sharedLibs) == 0 {
|
if len(sharedLibs) == 0 {
|
||||||
h.module.outputFile = android.OptionalPath{}
|
h.module.outputFile = android.OptionalPath{}
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out := android.PathForBazelOut(ctx, sharedLibs[0])
|
out := android.PathForBazelOut(ctx, sharedLibs[0])
|
||||||
@@ -514,8 +519,6 @@ func (h *prebuiltSharedLibraryBazelHandler) GenerateBazelBuildActions(ctx androi
|
|||||||
|
|
||||||
h.library.setFlagExporterInfoFromCcInfo(ctx, ccInfo)
|
h.library.setFlagExporterInfoFromCcInfo(ctx, ccInfo)
|
||||||
h.module.maybeUnhideFromMake()
|
h.module.maybeUnhideFromMake()
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *prebuiltObjectLinker) prebuilt() *android.Prebuilt {
|
func (p *prebuiltObjectLinker) prebuilt() *android.Prebuilt {
|
||||||
|
@@ -129,44 +129,26 @@ func newConfig(availableEnv map[string]string) android.Config {
|
|||||||
return configuration
|
return configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bazel-enabled mode. Soong runs in two passes.
|
// Bazel-enabled mode. Attaches a mutator to queue Bazel requests, adds a
|
||||||
// First pass: Analyze the build tree, but only store all bazel commands
|
// BeforePrepareBuildActionsHook to invoke Bazel, and then uses Bazel metadata
|
||||||
// needed to correctly evaluate the tree in the second pass.
|
// for modules that should be handled by Bazel.
|
||||||
// TODO(cparsons): Don't output any ninja file, as the second pass will overwrite
|
func runMixedModeBuild(configuration android.Config, ctx *android.Context, extraNinjaDeps []string) {
|
||||||
// the incorrect results from the first pass, and file I/O is expensive.
|
ctx.EventHandler.Begin("mixed_build")
|
||||||
func runMixedModeBuild(configuration android.Config, firstCtx *android.Context, extraNinjaDeps []string) {
|
defer ctx.EventHandler.End("mixed_build")
|
||||||
firstCtx.EventHandler.Begin("mixed_build")
|
|
||||||
defer firstCtx.EventHandler.End("mixed_build")
|
|
||||||
|
|
||||||
firstCtx.EventHandler.Begin("prepare")
|
bazelHook := func() error {
|
||||||
bootstrap.RunBlueprint(cmdlineArgs, bootstrap.StopBeforeWriteNinja, firstCtx.Context, configuration)
|
ctx.EventHandler.Begin("bazel")
|
||||||
firstCtx.EventHandler.End("prepare")
|
defer ctx.EventHandler.End("bazel")
|
||||||
|
return configuration.BazelContext.InvokeBazel()
|
||||||
firstCtx.EventHandler.Begin("bazel")
|
|
||||||
// Invoke bazel commands and save results for second pass.
|
|
||||||
if err := configuration.BazelContext.InvokeBazel(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
// Second pass: Full analysis, using the bazel command results. Output ninja file.
|
ctx.SetBeforePrepareBuildActionsHook(bazelHook)
|
||||||
secondConfig, err := android.ConfigForAdditionalRun(configuration)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
firstCtx.EventHandler.End("bazel")
|
|
||||||
|
|
||||||
secondCtx := newContext(secondConfig)
|
ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs, bootstrap.DoEverything, ctx.Context, configuration)
|
||||||
secondCtx.EventHandler = firstCtx.EventHandler
|
|
||||||
secondCtx.EventHandler.Begin("analyze")
|
|
||||||
ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs, bootstrap.DoEverything, secondCtx.Context, secondConfig)
|
|
||||||
ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
|
|
||||||
secondCtx.EventHandler.End("analyze")
|
|
||||||
|
|
||||||
globListFiles := writeBuildGlobsNinjaFile(secondCtx, configuration.SoongOutDir(), configuration)
|
globListFiles := writeBuildGlobsNinjaFile(ctx, configuration.SoongOutDir(), configuration)
|
||||||
ninjaDeps = append(ninjaDeps, globListFiles...)
|
ninjaDeps = append(ninjaDeps, globListFiles...)
|
||||||
|
|
||||||
writeDepFile(cmdlineArgs.OutFile, *secondCtx.EventHandler, ninjaDeps)
|
writeDepFile(cmdlineArgs.OutFile, *ctx.EventHandler, ninjaDeps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the code-generation phase to convert BazelTargetModules to BUILD files.
|
// Run the code-generation phase to convert BazelTargetModules to BUILD files.
|
||||||
|
@@ -25,6 +25,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"android/soong/bazel/cquery"
|
||||||
"github.com/google/blueprint"
|
"github.com/google/blueprint"
|
||||||
"github.com/google/blueprint/bootstrap"
|
"github.com/google/blueprint/bootstrap"
|
||||||
"github.com/google/blueprint/proptools"
|
"github.com/google/blueprint/proptools"
|
||||||
@@ -189,6 +190,8 @@ type Module struct {
|
|||||||
modulePaths []string
|
modulePaths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ android.MixedBuildBuildable = (*Module)(nil)
|
||||||
|
|
||||||
type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
|
type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
|
||||||
|
|
||||||
type generateTask struct {
|
type generateTask struct {
|
||||||
@@ -249,27 +252,36 @@ func toolDepsMutator(ctx android.BottomUpMutatorContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if information was available from Bazel, false if bazel invocation still needs to occur.
|
func (g *Module) ProcessBazelQueryResponse(ctx android.ModuleContext) {
|
||||||
func (c *Module) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
|
g.generateCommonBuildActions(ctx)
|
||||||
|
|
||||||
|
label := g.GetBazelLabel(ctx, g)
|
||||||
bazelCtx := ctx.Config().BazelContext
|
bazelCtx := ctx.Config().BazelContext
|
||||||
filePaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
|
filePaths, err := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
|
||||||
if ok {
|
if err != nil {
|
||||||
|
ctx.ModuleErrorf(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var bazelOutputFiles android.Paths
|
var bazelOutputFiles android.Paths
|
||||||
exportIncludeDirs := map[string]bool{}
|
exportIncludeDirs := map[string]bool{}
|
||||||
for _, bazelOutputFile := range filePaths {
|
for _, bazelOutputFile := range filePaths {
|
||||||
bazelOutputFiles = append(bazelOutputFiles, android.PathForBazelOut(ctx, bazelOutputFile))
|
bazelOutputFiles = append(bazelOutputFiles, android.PathForBazelOut(ctx, bazelOutputFile))
|
||||||
exportIncludeDirs[filepath.Dir(bazelOutputFile)] = true
|
exportIncludeDirs[filepath.Dir(bazelOutputFile)] = true
|
||||||
}
|
}
|
||||||
c.outputFiles = bazelOutputFiles
|
g.outputFiles = bazelOutputFiles
|
||||||
c.outputDeps = bazelOutputFiles
|
g.outputDeps = bazelOutputFiles
|
||||||
for includePath, _ := range exportIncludeDirs {
|
for includePath, _ := range exportIncludeDirs {
|
||||||
c.exportedIncludeDirs = append(c.exportedIncludeDirs, android.PathForBazelOut(ctx, includePath))
|
g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForBazelOut(ctx, includePath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
|
// generateCommonBuildActions contains build action generation logic
|
||||||
|
// common to both the mixed build case and the legacy case of genrule processing.
|
||||||
|
// To fully support genrule in mixed builds, the contents of this function should
|
||||||
|
// approach zero; there should be no genrule action registration done directly
|
||||||
|
// by Soong logic in the mixed-build case.
|
||||||
|
func (g *Module) generateCommonBuildActions(ctx android.ModuleContext) {
|
||||||
g.subName = ctx.ModuleSubDir()
|
g.subName = ctx.ModuleSubDir()
|
||||||
|
|
||||||
// Collect the module directory for IDE info in java/jdeps.go.
|
// Collect the module directory for IDE info in java/jdeps.go.
|
||||||
@@ -575,13 +587,11 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
g.outputFiles = outputFiles.Paths()
|
g.outputFiles = outputFiles.Paths()
|
||||||
|
|
||||||
bazelModuleLabel := g.GetBazelLabel(ctx, g)
|
|
||||||
bazelActionsUsed := false
|
|
||||||
if android.MixedBuildsEnabled(ctx) {
|
|
||||||
bazelActionsUsed = g.GenerateBazelBuildActions(ctx, bazelModuleLabel)
|
|
||||||
}
|
}
|
||||||
if !bazelActionsUsed {
|
|
||||||
|
func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
|
||||||
|
g.generateCommonBuildActions(ctx)
|
||||||
|
|
||||||
// For <= 6 outputs, just embed those directly in the users. Right now, that covers >90% of
|
// For <= 6 outputs, just embed those directly in the users. Right now, that covers >90% of
|
||||||
// the genrules on AOSP. That will make things simpler to look at the graph in the common
|
// the genrules on AOSP. That will make things simpler to look at the graph in the common
|
||||||
// case. For larger sets of outputs, inject a phony target in between to limit ninja file
|
// case. For larger sets of outputs, inject a phony target in between to limit ninja file
|
||||||
@@ -598,6 +608,14 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
|
|||||||
g.outputDeps = android.Paths{phonyFile}
|
g.outputDeps = android.Paths{phonyFile}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Module) QueueBazelCall(ctx android.BaseModuleContext) {
|
||||||
|
bazelCtx := ctx.Config().BazelContext
|
||||||
|
bazelCtx.QueueBazelRequest(g.GetBazelLabel(ctx, g), cquery.GetOutputFiles, android.GetConfigKey(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Module) IsMixedBuildSupported(ctx android.BaseModuleContext) bool {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect information for opening IDE project files in java/jdeps.go.
|
// Collect information for opening IDE project files in java/jdeps.go.
|
||||||
|
Reference in New Issue
Block a user