blob: b7668be44f1f52f837ee567a5edf34600e7b099a [file] [log] [blame]
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"sort"
"strings"
"sync"
"android/soong/testing/code_metadata_internal_proto"
"android/soong/testing/code_metadata_proto"
"android/soong/testing/test_spec_proto"
"google.golang.org/protobuf/proto"
)
type keyToLocksMap struct {
locks sync.Map
}
func (kl *keyToLocksMap) GetLockForKey(key string) *sync.Mutex {
mutex, _ := kl.locks.LoadOrStore(key, &sync.Mutex{})
return mutex.(*sync.Mutex)
}
// Define a struct to hold the combination of team ID and multi-ownership flag for validation
type sourceFileAttributes struct {
TeamID string
MultiOwnership bool
Path string
}
func getSortedKeys(syncMap *sync.Map) []string {
var allKeys []string
syncMap.Range(
func(key, _ interface{}) bool {
allKeys = append(allKeys, key.(string))
return true
},
)
sort.Strings(allKeys)
return allKeys
}
// writeProtoToFile marshals a protobuf message and writes it to a file
func writeProtoToFile(outputFile string, message proto.Message) {
data, err := proto.Marshal(message)
if err != nil {
log.Fatal(err)
}
file, err := os.Create(outputFile)
if err != nil {
log.Fatal(err)
}
defer file.Close()
_, err = file.Write(data)
if err != nil {
log.Fatal(err)
}
}
func readFileToString(filePath string) string {
file, err := os.Open(filePath)
if err != nil {
log.Fatal(err)
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
log.Fatal(err)
}
return string(data)
}
func writeEmptyOutputProto(outputFile string, metadataRule string) {
file, err := os.Create(outputFile)
if err != nil {
log.Fatal(err)
}
var message proto.Message
if metadataRule == "test_spec" {
message = &test_spec_proto.TestSpec{}
} else if metadataRule == "code_metadata" {
message = &code_metadata_proto.CodeMetadata{}
}
data, err := proto.Marshal(message)
if err != nil {
log.Fatal(err)
}
defer file.Close()
_, err = file.Write([]byte(data))
if err != nil {
log.Fatal(err)
}
}
func processTestSpecProtobuf(
filePath string, ownershipMetadataMap *sync.Map, keyLocks *keyToLocksMap,
errCh chan error, wg *sync.WaitGroup,
) {
defer wg.Done()
fileContent := strings.TrimRight(readFileToString(filePath), "\n")
testData := test_spec_proto.TestSpec{}
err := proto.Unmarshal([]byte(fileContent), &testData)
if err != nil {
errCh <- err
return
}
ownershipMetadata := testData.GetOwnershipMetadataList()
for _, metadata := range ownershipMetadata {
key := metadata.GetTargetName()
lock := keyLocks.GetLockForKey(key)
lock.Lock()
value, loaded := ownershipMetadataMap.LoadOrStore(
key, []*test_spec_proto.TestSpec_OwnershipMetadata{metadata},
)
if loaded {
existingMetadata := value.([]*test_spec_proto.TestSpec_OwnershipMetadata)
isDuplicate := false
for _, existing := range existingMetadata {
if metadata.GetTrendyTeamId() != existing.GetTrendyTeamId() {
errCh <- fmt.Errorf(
"Conflicting trendy team IDs found for %s at:\n%s with teamId"+
": %s,\n%s with teamId: %s",
key,
metadata.GetPath(), metadata.GetTrendyTeamId(), existing.GetPath(),
existing.GetTrendyTeamId(),
)
lock.Unlock()
return
}
if metadata.GetTrendyTeamId() == existing.GetTrendyTeamId() && metadata.GetPath() == existing.GetPath() {
isDuplicate = true
break
}
}
if !isDuplicate {
existingMetadata = append(existingMetadata, metadata)
ownershipMetadataMap.Store(key, existingMetadata)
}
}
lock.Unlock()
}
}
// processCodeMetadataProtobuf processes CodeMetadata protobuf files
func processCodeMetadataProtobuf(
filePath string, ownershipMetadataMap *sync.Map, sourceFileMetadataMap *sync.Map, keyLocks *keyToLocksMap,
errCh chan error, wg *sync.WaitGroup,
) {
defer wg.Done()
fileContent := strings.TrimRight(readFileToString(filePath), "\n")
internalCodeData := code_metadata_internal_proto.CodeMetadataInternal{}
err := proto.Unmarshal([]byte(fileContent), &internalCodeData)
if err != nil {
errCh <- err
return
}
// Process each TargetOwnership entry
for _, internalMetadata := range internalCodeData.GetTargetOwnershipList() {
key := internalMetadata.GetTargetName()
lock := keyLocks.GetLockForKey(key)
lock.Lock()
for _, srcFile := range internalMetadata.GetSourceFiles() {
srcFileKey := srcFile
srcFileLock := keyLocks.GetLockForKey(srcFileKey)
srcFileLock.Lock()
attributes := sourceFileAttributes{
TeamID: internalMetadata.GetTrendyTeamId(),
MultiOwnership: internalMetadata.GetMultiOwnership(),
Path: internalMetadata.GetPath(),
}
existingAttributes, exists := sourceFileMetadataMap.Load(srcFileKey)
if exists {
existing := existingAttributes.(sourceFileAttributes)
if attributes.TeamID != existing.TeamID && (!attributes.MultiOwnership || !existing.MultiOwnership) {
errCh <- fmt.Errorf(
"Conflict found for source file %s covered at %s with team ID: %s. Existing team ID: %s and path: %s."+
" If multi-ownership is required, multiOwnership should be set to true in all test_spec modules using this target. "+
"Multiple-ownership in general is discouraged though as it make infrastructure around android relying on this information pick up a random value when it needs only one.",
srcFile, internalMetadata.GetPath(), attributes.TeamID, existing.TeamID, existing.Path,
)
srcFileLock.Unlock()
lock.Unlock()
return
}
} else {
// Store the metadata if no conflict
sourceFileMetadataMap.Store(srcFileKey, attributes)
}
srcFileLock.Unlock()
}
value, loaded := ownershipMetadataMap.LoadOrStore(
key, []*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership{internalMetadata},
)
if loaded {
existingMetadata := value.([]*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership)
isDuplicate := false
for _, existing := range existingMetadata {
if internalMetadata.GetTrendyTeamId() == existing.GetTrendyTeamId() && internalMetadata.GetPath() == existing.GetPath() {
isDuplicate = true
break
}
}
if !isDuplicate {
existingMetadata = append(existingMetadata, internalMetadata)
ownershipMetadataMap.Store(key, existingMetadata)
}
}
lock.Unlock()
}
}
func main() {
inputFile := flag.String("inputFile", "", "Input file path")
outputFile := flag.String("outputFile", "", "Output file path")
rule := flag.String(
"rule", "", "Metadata rule (Hint: test_spec or code_metadata)",
)
flag.Parse()
if *inputFile == "" || *outputFile == "" || *rule == "" {
fmt.Println("Usage: metadata -rule <rule> -inputFile <input file path> -outputFile <output file path>")
os.Exit(1)
}
inputFileData := strings.TrimRight(readFileToString(*inputFile), "\n")
filePaths := strings.Split(inputFileData, " ")
if len(filePaths) == 1 && filePaths[0] == "" {
writeEmptyOutputProto(*outputFile, *rule)
return
}
ownershipMetadataMap := &sync.Map{}
keyLocks := &keyToLocksMap{}
errCh := make(chan error, len(filePaths))
var wg sync.WaitGroup
switch *rule {
case "test_spec":
for _, filePath := range filePaths {
wg.Add(1)
go processTestSpecProtobuf(
filePath, ownershipMetadataMap, keyLocks, errCh, &wg,
)
}
wg.Wait()
close(errCh)
for err := range errCh {
log.Fatal(err)
}
allKeys := getSortedKeys(ownershipMetadataMap)
var allMetadata []*test_spec_proto.TestSpec_OwnershipMetadata
for _, key := range allKeys {
value, _ := ownershipMetadataMap.Load(key)
metadataList := value.([]*test_spec_proto.TestSpec_OwnershipMetadata)
allMetadata = append(allMetadata, metadataList...)
}
testSpec := &test_spec_proto.TestSpec{
OwnershipMetadataList: allMetadata,
}
writeProtoToFile(*outputFile, testSpec)
break
case "code_metadata":
sourceFileMetadataMap := &sync.Map{}
for _, filePath := range filePaths {
wg.Add(1)
go processCodeMetadataProtobuf(
filePath, ownershipMetadataMap, sourceFileMetadataMap, keyLocks, errCh, &wg,
)
}
wg.Wait()
close(errCh)
for err := range errCh {
log.Fatal(err)
}
sortedKeys := getSortedKeys(ownershipMetadataMap)
allMetadata := make([]*code_metadata_proto.CodeMetadata_TargetOwnership, 0)
for _, key := range sortedKeys {
value, _ := ownershipMetadataMap.Load(key)
metadata := value.([]*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership)
for _, m := range metadata {
targetName := m.GetTargetName()
path := m.GetPath()
trendyTeamId := m.GetTrendyTeamId()
allMetadata = append(allMetadata, &code_metadata_proto.CodeMetadata_TargetOwnership{
TargetName: &targetName,
Path: &path,
TrendyTeamId: &trendyTeamId,
SourceFiles: m.GetSourceFiles(),
})
}
}
finalMetadata := &code_metadata_proto.CodeMetadata{
TargetOwnershipList: allMetadata,
}
writeProtoToFile(*outputFile, finalMetadata)
break
default:
log.Fatalf("No specific processing implemented for rule '%s'.\n", *rule)
}
}