Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 91220d73 authored by Jingwen Chen's avatar Jingwen Chen
Browse files

Add os/target configurable selects for label list attributes.

This CL is pretty large, so I recommend starting with reading the newly
added tests for the expected behavior.

This change works in conjunction with the linked CLs in the Gerrit topic.
Those CLs add support for new platform() definitions for OS targets
specified in Soong's arch.go, which are configurable through
Android.bp's `target {}` property. It works similary to previous CLs
adding support for the `arch {}` property.

These configurable props are keyed by the OS: android, linux_bionic,
windows, and so on. They map to `select` statements in label list
attributes, which this CL enables for cc_library_headers' header_libs
and export_header_lib_headers props.

This enables //bionic/libc:libc_headers to be generated correctly, from:

    cc_library_headers {
        name: "libc_headers",
        target: {
            android: {
                header_libs: ["libc_headers_arch"],
                export_header_lib_headers: ["libc_headers_arch"],
            },
            linux_bionic: {
                header_libs: ["libc_headers_arch"],
                export_header_lib_headers: ["libc_headers_arch"],
            },
        },
        // omitted props
    }

to:

    cc_library_headers(
        name = "libc_headers",
        deps = [] + select({
            "//build/bazel/platforms/os:android": [
                ":libc_headers_arch",
            ],
            "//build/bazel/platforms/os:linux_bionic": [
                ":libc_headers_arch",
            ],
            "//conditions:default": [],
        }),
    )

Test: TH
Test: Verify generated //bionic/libc:libc_headers
Fixes: 183597786

Change-Id: I01016cc2cc9a71449f02300d747f01decebf3f6e
parent 252831b0
Loading
Loading
Loading
Loading
+87 −0
Original line number Diff line number Diff line
@@ -1709,3 +1709,90 @@ func (m *ModuleBase) GetArchProperties(dst interface{}) map[ArchType]interface{}
	}
	return archToProp
}

// GetTargetProperties returns a map of OS target (e.g. android, windows) to the
// values of the properties of the 'dst' struct that are specific to that OS
// target.
//
// For example, passing a struct { Foo bool, Bar string } will return an
// interface{} that can be type asserted back into the same struct, containing
// the os-specific property value specified by the module if defined.
//
// While this looks similar to GetArchProperties, the internal representation of
// the properties have a slightly different layout to warrant a standalone
// lookup function.
func (m *ModuleBase) GetTargetProperties(dst interface{}) map[OsType]interface{} {
	// Return value of the arch types to the prop values for that arch.
	osToProp := map[OsType]interface{}{}

	// Nothing to do for non-OS/arch-specific modules.
	if !m.ArchSpecific() {
		return osToProp
	}

	// archProperties has the type of [][]interface{}. Looks complicated, so
	// let's explain this step by step.
	//
	// Loop over the outer index, which determines the property struct that
	// contains a matching set of properties in dst that we're interested in.
	// For example, BaseCompilerProperties or BaseLinkerProperties.
	for i := range m.archProperties {
		if m.archProperties[i] == nil {
			continue
		}

		// Iterate over the supported OS types
		for _, os := range OsTypeList {
			// e.g android, linux_bionic
			field := os.Field

			// If it's not nil, loop over the inner index, which determines the arch variant
			// of the prop type. In an Android.bp file, this is like looping over:
			//
			// target: { android: { key: value, ... }, linux_bionic: { key: value, ... } }
			for _, archProperties := range m.archProperties[i] {
				archPropValues := reflect.ValueOf(archProperties).Elem()

				// This is the archPropRoot struct. Traverse into the Targetnested struct.
				src := archPropValues.FieldByName("Target").Elem()

				// Step into non-nil pointers to structs in the src value.
				if src.Kind() == reflect.Ptr {
					if src.IsNil() {
						continue
					}
					src = src.Elem()
				}

				// Find the requested field (e.g. android, linux_bionic) in the src struct.
				src = src.FieldByName(field)

				// Validation steps. We want valid non-nil pointers to structs.
				if !src.IsValid() || src.IsNil() {
					continue
				}

				if src.Kind() != reflect.Ptr || src.Elem().Kind() != reflect.Struct {
					continue
				}

				// Clone the destination prop, since we want a unique prop struct per arch.
				dstClone := reflect.New(reflect.ValueOf(dst).Elem().Type()).Interface()

				// Copy the located property struct into the cloned destination property struct.
				err := proptools.ExtendMatchingProperties([]interface{}{dstClone}, src.Interface(), nil, proptools.OrderReplace)
				if err != nil {
					// This is fine, it just means the src struct doesn't match.
					continue
				}

				// Found the prop for the os, you have.
				osToProp[os] = dstClone

				// Go to the next prop.
				break
			}
		}
	}
	return osToProp
}
+10 −0
Original line number Diff line number Diff line
@@ -108,6 +108,11 @@ type Bp2BuildConfig map[string]BazelConversionConfigEntry
type BazelConversionConfigEntry int

const (
	// A sentinel value to be used as a key in Bp2BuildConfig for modules with
	// no package path. This is also the module dir for top level Android.bp
	// modules.
	BP2BUILD_TOPLEVEL = "."

	// iota + 1 ensures that the int value is not 0 when used in the Bp2buildAllowlist map,
	// which can also mean that the key doesn't exist in a lookup.

@@ -224,10 +229,15 @@ func (b *BazelModuleBase) ConvertWithBp2build(ctx BazelConversionPathContext) bo
func bp2buildDefaultTrueRecursively(packagePath string, config Bp2BuildConfig) bool {
	ret := false

	// Return exact matches in the config.
	if config[packagePath] == Bp2BuildDefaultTrueRecursively {
		return true
	}
	if config[packagePath] == Bp2BuildDefaultFalse {
		return false
	}

	// If not, check for the config recursively.
	packagePrefix := ""
	// e.g. for x/y/z, iterate over x, x/y, then x/y/z, taking the final value from the allowlist.
	for _, part := range strings.Split(packagePath, "/") {
+18 −8
Original line number Diff line number Diff line
@@ -270,13 +270,23 @@ func (context *bazelContext) issueBazelCommand(runName bazel.RunName, command st
	cmdFlags = append(cmdFlags, labels...)
	cmdFlags = append(cmdFlags, "--package_path=%workspace%/"+context.intermediatesDir())
	cmdFlags = append(cmdFlags, "--profile="+shared.BazelMetricsFilename(context, runName))
	// Set default platforms to canonicalized values for mixed builds requests. If these are set
	// in the bazelrc, they will have values that are non-canonicalized, and thus be invalid.
	// The actual platform values here may be overridden by configuration transitions from the buildroot.

	// Set default platforms to canonicalized values for mixed builds requests.
	// If these are set in the bazelrc, they will have values that are
	// non-canonicalized to @sourceroot labels, and thus be invalid when
	// referenced from the buildroot.
	//
	// The actual platform values here may be overridden by configuration
	// transitions from the buildroot.
	cmdFlags = append(cmdFlags,
		fmt.Sprintf("--platforms=%s", canonicalizeLabel("//build/bazel/platforms:generic_x86_64")))
		fmt.Sprintf("--platforms=%s", canonicalizeLabel("//build/bazel/platforms:android_x86_64")))
	cmdFlags = append(cmdFlags,
		fmt.Sprintf("--extra_toolchains=%s", canonicalizeLabel("//prebuilts/clang/host/linux-x86:all")))
	// This should be parameterized on the host OS, but let's restrict to linux
	// to keep things simple for now.
	cmdFlags = append(cmdFlags,
		fmt.Sprintf("--host_platform=%s", canonicalizeLabel("//build/bazel/platforms:linux_x86_64")))

	// Explicitly disable downloading rules (such as canonical C++ and Java rules) from the network.
	cmdFlags = append(cmdFlags, "--experimental_repository_disable_download")
	cmdFlags = append(cmdFlags, extraFlags...)
@@ -328,7 +338,7 @@ func (context *bazelContext) mainBzlFileContents() []byte {

def _config_node_transition_impl(settings, attr):
    return {
        "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:generic_%s" % attr.arch,
        "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:android_%s" % attr.arch,
    }

_config_node_transition = transition(
@@ -504,10 +514,10 @@ def get_arch(target):
  platform_name = build_options(target)["//command_line_option:platforms"][0].name
  if platform_name == "host":
    return "HOST"
  elif not platform_name.startswith("generic_"):
    fail("expected platform name of the form 'generic_<arch>', but was " + str(platforms))
  elif not platform_name.startswith("android_"):
    fail("expected platform name of the form 'android_<arch>', but was " + str(platforms))
    return "UNKNOWN"
  return platform_name[len("generic_"):]
  return platform_name[len("android_"):]

def format(target):
  id_string = str(target.label) + "|" + get_arch(target)
+126 −50
Original line number Diff line number Diff line
@@ -80,10 +80,19 @@ func UniqueBazelLabelList(originalLabelList LabelList) LabelList {
}

const (
	ARCH_X86    = "x86"
	ARCH_X86_64 = "x86_64"
	// ArchType names in arch.go
	ARCH_ARM    = "arm"
	ARCH_ARM64  = "arm64"
	ARCH_X86    = "x86"
	ARCH_X86_64 = "x86_64"

	// OsType names in arch.go
	OS_ANDROID      = "android"
	OS_DARWIN       = "darwin"
	OS_FUCHSIA      = "fuchsia"
	OS_LINUX        = "linux_glibc"
	OS_LINUX_BIONIC = "linux_bionic"
	OS_WINDOWS      = "windows"
)

var (
@@ -92,6 +101,36 @@ var (
	// android package depends on the bazel package, so a cyclic dependency
	// prevents using that here.
	selectableArchs = []string{ARCH_X86, ARCH_X86_64, ARCH_ARM, ARCH_ARM64}

	// Likewise, this is the list of target operating systems.
	selectableTargetOs = []string{
		OS_ANDROID,
		OS_DARWIN,
		OS_FUCHSIA,
		OS_LINUX,
		OS_LINUX_BIONIC,
		OS_WINDOWS,
	}

	// A map of architectures to the Bazel label of the constraint_value
	// for the @platforms//cpu:cpu constraint_setting
	PlatformArchMap = map[string]string{
		ARCH_ARM:    "//build/bazel/platforms/arch:arm",
		ARCH_ARM64:  "//build/bazel/platforms/arch:arm64",
		ARCH_X86:    "//build/bazel/platforms/arch:x86",
		ARCH_X86_64: "//build/bazel/platforms/arch:x86_64",
	}

	// A map of target operating systems to the Bazel label of the
	// constraint_value for the @platforms//os:os constraint_setting
	PlatformOsMap = map[string]string{
		OS_ANDROID:      "//build/bazel/platforms/os:android",
		OS_DARWIN:       "//build/bazel/platforms/os:darwin",
		OS_FUCHSIA:      "//build/bazel/platforms/os:fuchsia",
		OS_LINUX:        "//build/bazel/platforms/os:linux",
		OS_LINUX_BIONIC: "//build/bazel/platforms/os:linux_bionic",
		OS_WINDOWS:      "//build/bazel/platforms/os:windows",
	}
)

// Arch-specific label_list typed Bazel attribute values. This should correspond
@@ -101,8 +140,16 @@ type labelListArchValues struct {
	X86_64 LabelList
	Arm    LabelList
	Arm64  LabelList
	// TODO(b/181299724): this is currently missing the "common" arch, which
	// doesn't have an equivalent platform() definition yet.
	Common LabelList
}

type labelListOsValues struct {
	Android     LabelList
	Darwin      LabelList
	Fuchsia     LabelList
	Linux       LabelList
	LinuxBionic LabelList
	Windows     LabelList
}

// LabelListAttribute is used to represent a list of Bazel labels as an
@@ -115,6 +162,11 @@ type LabelListAttribute struct {
	// are generated in a select statement and appended to the non-arch specific
	// label list Value.
	ArchValues labelListArchValues

	// The os-specific attribute label list values. Optional. If used, these
	// are generated in a select statement and appended to the non-os specific
	// label list Value.
	OsValues labelListOsValues
}

// MakeLabelListAttribute initializes a LabelListAttribute with the non-arch specific value.
@@ -124,45 +176,75 @@ func MakeLabelListAttribute(value LabelList) LabelListAttribute {

// HasArchSpecificValues returns true if the attribute contains
// architecture-specific label_list values.
func (attrs *LabelListAttribute) HasArchSpecificValues() bool {
func (attrs *LabelListAttribute) HasConfigurableValues() bool {
	for _, arch := range selectableArchs {
		if len(attrs.GetValueForArch(arch).Includes) > 0 || len(attrs.GetValueForArch(arch).Excludes) > 0 {
		if len(attrs.GetValueForArch(arch).Includes) > 0 {
			return true
		}
	}

	for _, os := range selectableTargetOs {
		if len(attrs.GetValueForOS(os).Includes) > 0 {
			return true
		}
	}
	return false
}

func (attrs *LabelListAttribute) archValuePtrs() map[string]*LabelList {
	return map[string]*LabelList{
		ARCH_X86:    &attrs.ArchValues.X86,
		ARCH_X86_64: &attrs.ArchValues.X86_64,
		ARCH_ARM:    &attrs.ArchValues.Arm,
		ARCH_ARM64:  &attrs.ArchValues.Arm64,
	}
}

// GetValueForArch returns the label_list attribute value for an architecture.
func (attrs *LabelListAttribute) GetValueForArch(arch string) LabelList {
	switch arch {
	case ARCH_X86:
		return attrs.ArchValues.X86
	case ARCH_X86_64:
		return attrs.ArchValues.X86_64
	case ARCH_ARM:
		return attrs.ArchValues.Arm
	case ARCH_ARM64:
		return attrs.ArchValues.Arm64
	default:
	var v *LabelList
	if v = attrs.archValuePtrs()[arch]; v == nil {
		panic(fmt.Errorf("Unknown arch: %s", arch))
	}
	return *v
}

// SetValueForArch sets the label_list attribute value for an architecture.
func (attrs *LabelListAttribute) SetValueForArch(arch string, value LabelList) {
	switch arch {
	case "x86":
		attrs.ArchValues.X86 = value
	case "x86_64":
		attrs.ArchValues.X86_64 = value
	case "arm":
		attrs.ArchValues.Arm = value
	case "arm64":
		attrs.ArchValues.Arm64 = value
	default:
	var v *LabelList
	if v = attrs.archValuePtrs()[arch]; v == nil {
		panic(fmt.Errorf("Unknown arch: %s", arch))
	}
	*v = value
}

func (attrs *LabelListAttribute) osValuePtrs() map[string]*LabelList {
	return map[string]*LabelList{
		OS_ANDROID:      &attrs.OsValues.Android,
		OS_DARWIN:       &attrs.OsValues.Darwin,
		OS_FUCHSIA:      &attrs.OsValues.Fuchsia,
		OS_LINUX:        &attrs.OsValues.Linux,
		OS_LINUX_BIONIC: &attrs.OsValues.LinuxBionic,
		OS_WINDOWS:      &attrs.OsValues.Windows,
	}
}

// GetValueForOS returns the label_list attribute value for an OS target.
func (attrs *LabelListAttribute) GetValueForOS(os string) LabelList {
	var v *LabelList
	if v = attrs.osValuePtrs()[os]; v == nil {
		panic(fmt.Errorf("Unknown os: %s", os))
	}
	return *v
}

// SetValueForArch sets the label_list attribute value for an OS target.
func (attrs *LabelListAttribute) SetValueForOS(os string, value LabelList) {
	var v *LabelList
	if v = attrs.osValuePtrs()[os]; v == nil {
		panic(fmt.Errorf("Unknown os: %s", os))
	}
	*v = value
}

// StringListAttribute corresponds to the string_list Bazel attribute type with
@@ -182,13 +264,12 @@ type stringListArchValues struct {
	X86_64 []string
	Arm    []string
	Arm64  []string
	// TODO(b/181299724): this is currently missing the "common" arch, which
	// doesn't have an equivalent platform() definition yet.
	Common []string
}

// HasArchSpecificValues returns true if the attribute contains
// HasConfigurableValues returns true if the attribute contains
// architecture-specific string_list values.
func (attrs *StringListAttribute) HasArchSpecificValues() bool {
func (attrs *StringListAttribute) HasConfigurableValues() bool {
	for _, arch := range selectableArchs {
		if len(attrs.GetValueForArch(arch)) > 0 {
			return true
@@ -197,36 +278,31 @@ func (attrs *StringListAttribute) HasArchSpecificValues() bool {
	return false
}

func (attrs *StringListAttribute) archValuePtrs() map[string]*[]string {
	return map[string]*[]string{
		ARCH_X86:    &attrs.ArchValues.X86,
		ARCH_X86_64: &attrs.ArchValues.X86_64,
		ARCH_ARM:    &attrs.ArchValues.Arm,
		ARCH_ARM64:  &attrs.ArchValues.Arm64,
	}
}

// GetValueForArch returns the string_list attribute value for an architecture.
func (attrs *StringListAttribute) GetValueForArch(arch string) []string {
	switch arch {
	case ARCH_X86:
		return attrs.ArchValues.X86
	case ARCH_X86_64:
		return attrs.ArchValues.X86_64
	case ARCH_ARM:
		return attrs.ArchValues.Arm
	case ARCH_ARM64:
		return attrs.ArchValues.Arm64
	default:
	var v *[]string
	if v = attrs.archValuePtrs()[arch]; v == nil {
		panic(fmt.Errorf("Unknown arch: %s", arch))
	}
	return *v
}

// SetValueForArch sets the string_list attribute value for an architecture.
func (attrs *StringListAttribute) SetValueForArch(arch string, value []string) {
	switch arch {
	case ARCH_X86:
		attrs.ArchValues.X86 = value
	case ARCH_X86_64:
		attrs.ArchValues.X86_64 = value
	case ARCH_ARM:
		attrs.ArchValues.Arm = value
	case ARCH_ARM64:
		attrs.ArchValues.Arm64 = value
	default:
	var v *[]string
	if v = attrs.archValuePtrs()[arch]; v == nil {
		panic(fmt.Errorf("Unknown arch: %s", arch))
	}
	*v = value
}

// TryVariableSubstitution, replace string substitution formatting within each string in slice with
+2 −54
Original line number Diff line number Diff line
@@ -416,63 +416,11 @@ func prettyPrint(propertyValue reflect.Value, indent int) (string, error) {
		// Special cases where the bp2build sends additional information to the codegenerator
		// by wrapping the attributes in a custom struct type.
		if labels, ok := propertyValue.Interface().(bazel.LabelListAttribute); ok {
			// TODO(b/165114590): convert glob syntax
			ret, err := prettyPrint(reflect.ValueOf(labels.Value.Includes), indent)
			if err != nil {
				return ret, err
			}

			if !labels.HasArchSpecificValues() {
				// Select statement not needed.
				return ret, nil
			}

			ret += " + " + "select({\n"
			for _, arch := range android.ArchTypeList() {
				value := labels.GetValueForArch(arch.Name)
				if len(value.Includes) > 0 {
					ret += makeIndent(indent + 1)
					list, _ := prettyPrint(reflect.ValueOf(value.Includes), indent+1)
					ret += fmt.Sprintf("\"%s\": %s,\n", platformArchMap[arch], list)
				}
			}

			ret += makeIndent(indent + 1)
			ret += fmt.Sprintf("\"%s\": [],\n", "//conditions:default")

			ret += makeIndent(indent)
			ret += "})"
			return ret, err
			return prettyPrintLabelListAttribute(labels, indent)
		} else if label, ok := propertyValue.Interface().(bazel.Label); ok {
			return fmt.Sprintf("%q", label.Label), nil
		} else if stringList, ok := propertyValue.Interface().(bazel.StringListAttribute); ok {
			// A Bazel string_list attribute that may contain a select statement.
			ret, err := prettyPrint(reflect.ValueOf(stringList.Value), indent)
			if err != nil {
				return ret, err
			}

			if !stringList.HasArchSpecificValues() {
				// Select statement not needed.
				return ret, nil
			}

			ret += " + " + "select({\n"
			for _, arch := range android.ArchTypeList() {
				value := stringList.GetValueForArch(arch.Name)
				if len(value) > 0 {
					ret += makeIndent(indent + 1)
					list, _ := prettyPrint(reflect.ValueOf(value), indent+1)
					ret += fmt.Sprintf("\"%s\": %s,\n", platformArchMap[arch], list)
				}
			}

			ret += makeIndent(indent + 1)
			ret += fmt.Sprintf("\"%s\": [],\n", "//conditions:default")

			ret += makeIndent(indent)
			ret += "})"
			return ret, err
			return prettyPrintStringListAttribute(stringList, indent)
		}

		ret = "{\n"
Loading