1package main 2 3import ( 4 "flag" 5 "fmt" 6 "io" 7 "log" 8 "os" 9 "sort" 10 "strings" 11 "sync" 12 13 "android/soong/testing/code_metadata_internal_proto" 14 "android/soong/testing/code_metadata_proto" 15 "android/soong/testing/test_spec_proto" 16 "google.golang.org/protobuf/proto" 17) 18 19type keyToLocksMap struct { 20 locks sync.Map 21} 22 23func (kl *keyToLocksMap) GetLockForKey(key string) *sync.Mutex { 24 mutex, _ := kl.locks.LoadOrStore(key, &sync.Mutex{}) 25 return mutex.(*sync.Mutex) 26} 27 28// Define a struct to hold the combination of team ID and multi-ownership flag for validation 29type sourceFileAttributes struct { 30 TeamID string 31 MultiOwnership bool 32 Path string 33} 34 35func getSortedKeys(syncMap *sync.Map) []string { 36 var allKeys []string 37 syncMap.Range( 38 func(key, _ interface{}) bool { 39 allKeys = append(allKeys, key.(string)) 40 return true 41 }, 42 ) 43 44 sort.Strings(allKeys) 45 return allKeys 46} 47 48// writeProtoToFile marshals a protobuf message and writes it to a file 49func writeProtoToFile(outputFile string, message proto.Message) { 50 data, err := proto.Marshal(message) 51 if err != nil { 52 log.Fatal(err) 53 } 54 file, err := os.Create(outputFile) 55 if err != nil { 56 log.Fatal(err) 57 } 58 defer file.Close() 59 60 _, err = file.Write(data) 61 if err != nil { 62 log.Fatal(err) 63 } 64} 65 66func readFileToString(filePath string) string { 67 file, err := os.Open(filePath) 68 if err != nil { 69 log.Fatal(err) 70 } 71 defer file.Close() 72 73 data, err := io.ReadAll(file) 74 if err != nil { 75 log.Fatal(err) 76 } 77 return string(data) 78} 79 80func writeEmptyOutputProto(outputFile string, metadataRule string) { 81 file, err := os.Create(outputFile) 82 if err != nil { 83 log.Fatal(err) 84 } 85 var message proto.Message 86 if metadataRule == "test_spec" { 87 message = &test_spec_proto.TestSpec{} 88 } else if metadataRule == "code_metadata" { 89 message = &code_metadata_proto.CodeMetadata{} 90 } 91 data, err := proto.Marshal(message) 92 if err != nil { 93 log.Fatal(err) 94 } 95 defer file.Close() 96 97 _, err = file.Write([]byte(data)) 98 if err != nil { 99 log.Fatal(err) 100 } 101} 102 103func processTestSpecProtobuf( 104 filePath string, ownershipMetadataMap *sync.Map, keyLocks *keyToLocksMap, 105 errCh chan error, wg *sync.WaitGroup, 106) { 107 defer wg.Done() 108 109 fileContent := strings.TrimRight(readFileToString(filePath), "\n") 110 testData := test_spec_proto.TestSpec{} 111 err := proto.Unmarshal([]byte(fileContent), &testData) 112 if err != nil { 113 errCh <- err 114 return 115 } 116 117 ownershipMetadata := testData.GetOwnershipMetadataList() 118 for _, metadata := range ownershipMetadata { 119 key := metadata.GetTargetName() 120 lock := keyLocks.GetLockForKey(key) 121 lock.Lock() 122 123 value, loaded := ownershipMetadataMap.LoadOrStore( 124 key, []*test_spec_proto.TestSpec_OwnershipMetadata{metadata}, 125 ) 126 if loaded { 127 existingMetadata := value.([]*test_spec_proto.TestSpec_OwnershipMetadata) 128 isDuplicate := false 129 for _, existing := range existingMetadata { 130 if metadata.GetTrendyTeamId() != existing.GetTrendyTeamId() { 131 errCh <- fmt.Errorf( 132 "Conflicting trendy team IDs found for %s at:\n%s with teamId"+ 133 ": %s,\n%s with teamId: %s", 134 key, 135 metadata.GetPath(), metadata.GetTrendyTeamId(), existing.GetPath(), 136 existing.GetTrendyTeamId(), 137 ) 138 139 lock.Unlock() 140 return 141 } 142 if metadata.GetTrendyTeamId() == existing.GetTrendyTeamId() && metadata.GetPath() == existing.GetPath() { 143 isDuplicate = true 144 break 145 } 146 } 147 if !isDuplicate { 148 existingMetadata = append(existingMetadata, metadata) 149 ownershipMetadataMap.Store(key, existingMetadata) 150 } 151 } 152 153 lock.Unlock() 154 } 155} 156 157// processCodeMetadataProtobuf processes CodeMetadata protobuf files 158func processCodeMetadataProtobuf( 159 filePath string, ownershipMetadataMap *sync.Map, sourceFileMetadataMap *sync.Map, keyLocks *keyToLocksMap, 160 errCh chan error, wg *sync.WaitGroup, 161) { 162 defer wg.Done() 163 164 fileContent := strings.TrimRight(readFileToString(filePath), "\n") 165 internalCodeData := code_metadata_internal_proto.CodeMetadataInternal{} 166 err := proto.Unmarshal([]byte(fileContent), &internalCodeData) 167 if err != nil { 168 errCh <- err 169 return 170 } 171 172 // Process each TargetOwnership entry 173 for _, internalMetadata := range internalCodeData.GetTargetOwnershipList() { 174 key := internalMetadata.GetTargetName() 175 lock := keyLocks.GetLockForKey(key) 176 lock.Lock() 177 178 for _, srcFile := range internalMetadata.GetSourceFiles() { 179 srcFileKey := srcFile 180 srcFileLock := keyLocks.GetLockForKey(srcFileKey) 181 srcFileLock.Lock() 182 attributes := sourceFileAttributes{ 183 TeamID: internalMetadata.GetTrendyTeamId(), 184 MultiOwnership: internalMetadata.GetMultiOwnership(), 185 Path: internalMetadata.GetPath(), 186 } 187 188 existingAttributes, exists := sourceFileMetadataMap.Load(srcFileKey) 189 if exists { 190 existing := existingAttributes.(sourceFileAttributes) 191 if attributes.TeamID != existing.TeamID && (!attributes.MultiOwnership || !existing.MultiOwnership) { 192 errCh <- fmt.Errorf( 193 "Conflict found for source file %s covered at %s with team ID: %s. Existing team ID: %s and path: %s."+ 194 " If multi-ownership is required, multiOwnership should be set to true in all test_spec modules using this target. "+ 195 "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.", 196 srcFile, internalMetadata.GetPath(), attributes.TeamID, existing.TeamID, existing.Path, 197 ) 198 srcFileLock.Unlock() 199 lock.Unlock() 200 return 201 } 202 } else { 203 // Store the metadata if no conflict 204 sourceFileMetadataMap.Store(srcFileKey, attributes) 205 } 206 srcFileLock.Unlock() 207 } 208 209 value, loaded := ownershipMetadataMap.LoadOrStore( 210 key, []*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership{internalMetadata}, 211 ) 212 if loaded { 213 existingMetadata := value.([]*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership) 214 isDuplicate := false 215 for _, existing := range existingMetadata { 216 if internalMetadata.GetTrendyTeamId() == existing.GetTrendyTeamId() && internalMetadata.GetPath() == existing.GetPath() { 217 isDuplicate = true 218 break 219 } 220 } 221 if !isDuplicate { 222 existingMetadata = append(existingMetadata, internalMetadata) 223 ownershipMetadataMap.Store(key, existingMetadata) 224 } 225 } 226 227 lock.Unlock() 228 } 229} 230 231func main() { 232 inputFile := flag.String("inputFile", "", "Input file path") 233 outputFile := flag.String("outputFile", "", "Output file path") 234 rule := flag.String( 235 "rule", "", "Metadata rule (Hint: test_spec or code_metadata)", 236 ) 237 flag.Parse() 238 239 if *inputFile == "" || *outputFile == "" || *rule == "" { 240 fmt.Println("Usage: metadata -rule <rule> -inputFile <input file path> -outputFile <output file path>") 241 os.Exit(1) 242 } 243 244 inputFileData := strings.TrimRight(readFileToString(*inputFile), "\n") 245 filePaths := strings.Split(inputFileData, " ") 246 if len(filePaths) == 1 && filePaths[0] == "" { 247 writeEmptyOutputProto(*outputFile, *rule) 248 return 249 } 250 ownershipMetadataMap := &sync.Map{} 251 keyLocks := &keyToLocksMap{} 252 errCh := make(chan error, len(filePaths)) 253 var wg sync.WaitGroup 254 255 switch *rule { 256 case "test_spec": 257 for _, filePath := range filePaths { 258 wg.Add(1) 259 go processTestSpecProtobuf( 260 filePath, ownershipMetadataMap, keyLocks, errCh, &wg, 261 ) 262 } 263 264 wg.Wait() 265 close(errCh) 266 267 for err := range errCh { 268 log.Fatal(err) 269 } 270 271 allKeys := getSortedKeys(ownershipMetadataMap) 272 var allMetadata []*test_spec_proto.TestSpec_OwnershipMetadata 273 274 for _, key := range allKeys { 275 value, _ := ownershipMetadataMap.Load(key) 276 metadataList := value.([]*test_spec_proto.TestSpec_OwnershipMetadata) 277 allMetadata = append(allMetadata, metadataList...) 278 } 279 280 testSpec := &test_spec_proto.TestSpec{ 281 OwnershipMetadataList: allMetadata, 282 } 283 writeProtoToFile(*outputFile, testSpec) 284 break 285 case "code_metadata": 286 sourceFileMetadataMap := &sync.Map{} 287 for _, filePath := range filePaths { 288 wg.Add(1) 289 go processCodeMetadataProtobuf( 290 filePath, ownershipMetadataMap, sourceFileMetadataMap, keyLocks, errCh, &wg, 291 ) 292 } 293 294 wg.Wait() 295 close(errCh) 296 297 for err := range errCh { 298 log.Fatal(err) 299 } 300 301 sortedKeys := getSortedKeys(ownershipMetadataMap) 302 allMetadata := make([]*code_metadata_proto.CodeMetadata_TargetOwnership, 0) 303 for _, key := range sortedKeys { 304 value, _ := ownershipMetadataMap.Load(key) 305 metadata := value.([]*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership) 306 for _, m := range metadata { 307 targetName := m.GetTargetName() 308 path := m.GetPath() 309 trendyTeamId := m.GetTrendyTeamId() 310 311 allMetadata = append(allMetadata, &code_metadata_proto.CodeMetadata_TargetOwnership{ 312 TargetName: &targetName, 313 Path: &path, 314 TrendyTeamId: &trendyTeamId, 315 SourceFiles: m.GetSourceFiles(), 316 }) 317 } 318 } 319 320 finalMetadata := &code_metadata_proto.CodeMetadata{ 321 TargetOwnershipList: allMetadata, 322 } 323 writeProtoToFile(*outputFile, finalMetadata) 324 break 325 default: 326 log.Fatalf("No specific processing implemented for rule '%s'.\n", *rule) 327 } 328} 329