2023-11-02 12:18:40 +01:00
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"sort"
"strings"
"sync"
2023-11-20 11:16:42 +01:00
"android/soong/testing/code_metadata_internal_proto"
"android/soong/testing/code_metadata_proto"
2023-11-02 12:18:40 +01:00
"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 )
}
2023-11-20 11:16:42 +01:00
// 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
}
2023-11-02 12:18:40 +01:00
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
}
2023-11-20 11:16:42 +01:00
// writeProtoToFile marshals a protobuf message and writes it to a file
func writeProtoToFile ( outputFile string , message proto . Message ) {
data , err := proto . Marshal ( message )
2023-11-02 12:18:40 +01:00
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 )
}
2024-01-11 12:12:44 +01:00
func writeEmptyOutputProto ( outputFile string , metadataRule string ) {
2023-11-16 20:52:44 +01:00
file , err := os . Create ( outputFile )
2024-01-11 12:12:44 +01:00
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 )
2023-11-16 20:52:44 +01:00
if err != nil {
log . Fatal ( err )
}
defer file . Close ( )
_ , err = file . Write ( [ ] byte ( data ) )
if err != nil {
log . Fatal ( err )
}
}
2023-11-15 12:02:37 +01:00
func processTestSpecProtobuf (
2024-01-11 12:12:44 +01:00
filePath string , ownershipMetadataMap * sync . Map , keyLocks * keyToLocksMap ,
errCh chan error , wg * sync . WaitGroup ,
2023-11-02 12:18:40 +01:00
) {
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" +
2024-01-11 12:12:44 +01:00
": %s,\n%s with teamId: %s" ,
2023-11-02 12:18:40 +01:00
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 ( )
}
}
2023-11-20 11:16:42 +01:00
// processCodeMetadataProtobuf processes CodeMetadata protobuf files
func processCodeMetadataProtobuf (
2024-01-11 12:12:44 +01:00
filePath string , ownershipMetadataMap * sync . Map , sourceFileMetadataMap * sync . Map , keyLocks * keyToLocksMap ,
errCh chan error , wg * sync . WaitGroup ,
2023-11-20 11:16:42 +01:00
) {
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." +
2024-01-11 12:12:44 +01:00
" 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." ,
2023-11-20 11:16:42 +01:00
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 ( )
}
}
2023-11-02 12:18:40 +01:00
func main ( ) {
inputFile := flag . String ( "inputFile" , "" , "Input file path" )
outputFile := flag . String ( "outputFile" , "" , "Output file path" )
2023-11-20 11:16:42 +01:00
rule := flag . String (
"rule" , "" , "Metadata rule (Hint: test_spec or code_metadata)" ,
)
2023-11-02 12:18:40 +01:00
flag . Parse ( )
2023-11-15 12:02:37 +01:00
if * inputFile == "" || * outputFile == "" || * rule == "" {
fmt . Println ( "Usage: metadata -rule <rule> -inputFile <input file path> -outputFile <output file path>" )
2023-11-02 12:18:40 +01:00
os . Exit ( 1 )
}
inputFileData := strings . TrimRight ( readFileToString ( * inputFile ) , "\n" )
2023-11-21 15:01:41 +01:00
filePaths := strings . Split ( inputFileData , " " )
2023-11-16 20:52:44 +01:00
if len ( filePaths ) == 1 && filePaths [ 0 ] == "" {
2024-01-11 12:12:44 +01:00
writeEmptyOutputProto ( * outputFile , * rule )
2023-11-16 20:52:44 +01:00
return
}
2023-11-02 12:18:40 +01:00
ownershipMetadataMap := & sync . Map { }
keyLocks := & keyToLocksMap { }
errCh := make ( chan error , len ( filePaths ) )
var wg sync . WaitGroup
2023-11-15 12:02:37 +01:00
switch * rule {
case "test_spec" :
for _ , filePath := range filePaths {
wg . Add ( 1 )
2023-11-20 11:16:42 +01:00
go processTestSpecProtobuf (
filePath , ownershipMetadataMap , keyLocks , errCh , & wg ,
)
2023-11-15 12:02:37 +01:00
}
2023-11-02 12:18:40 +01:00
2023-11-15 12:02:37 +01:00
wg . Wait ( )
close ( errCh )
2023-11-02 12:18:40 +01:00
2023-11-15 12:02:37 +01:00
for err := range errCh {
log . Fatal ( err )
}
2023-11-02 12:18:40 +01:00
2023-11-15 12:02:37 +01:00
allKeys := getSortedKeys ( ownershipMetadataMap )
var allMetadata [ ] * test_spec_proto . TestSpec_OwnershipMetadata
2023-11-02 12:18:40 +01:00
2023-11-15 12:02:37 +01:00
for _ , key := range allKeys {
value , _ := ownershipMetadataMap . Load ( key )
metadataList := value . ( [ ] * test_spec_proto . TestSpec_OwnershipMetadata )
allMetadata = append ( allMetadata , metadataList ... )
}
2023-11-02 12:18:40 +01:00
2023-11-20 11:16:42 +01:00
testSpec := & test_spec_proto . TestSpec {
OwnershipMetadataList : allMetadata ,
}
writeProtoToFile ( * outputFile , testSpec )
2023-11-15 12:02:37 +01:00
break
case "code_metadata" :
2023-11-20 11:16:42 +01:00
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
2023-11-15 12:02:37 +01:00
default :
log . Fatalf ( "No specific processing implemented for rule '%s'.\n" , * rule )
}
2023-11-02 12:18:40 +01:00
}