use of com.facebook.buck.android.aapt.RDotTxtEntry in project buck by facebook.
the class MergeAndroidResourcesStepTest method testGenerateRDotJavaForMultipleSymbolsFiles.
@Test
public void testGenerateRDotJavaForMultipleSymbolsFiles() throws IOException, DuplicateResourceException {
RDotTxtEntryBuilder entriesBuilder = new RDotTxtEntryBuilder();
// Merge everything into the same package space.
String sharedPackageName = "com.facebook.abc";
entriesBuilder.add(new RDotTxtFile(sharedPackageName, "a-R.txt", ImmutableList.of("int id a1 0x7f010001", "int id a2 0x7f010002", "int string a1 0x7f020001")));
entriesBuilder.add(new RDotTxtFile(sharedPackageName, "b-R.txt", ImmutableList.of("int id b1 0x7f010001", "int id b2 0x7f010002", "int string a1 0x7f020001")));
entriesBuilder.add(new RDotTxtFile(sharedPackageName, "c-R.txt", ImmutableList.of("int attr c1 0x7f010001", "int[] styleable c1 { 0x7f010001 }")));
SortedSetMultimap<String, RDotTxtEntry> packageNameToResources = MergeAndroidResourcesStep.sortSymbols(entriesBuilder.buildFilePathToPackageNameSet(), Optional.empty(), ImmutableMap.of(), /* bannedDuplicateResourceTypes */
EnumSet.noneOf(RType.class), entriesBuilder.getProjectFilesystem(), false);
assertEquals(1, packageNameToResources.keySet().size());
SortedSet<RDotTxtEntry> resources = packageNameToResources.get(sharedPackageName);
assertEquals(7, resources.size());
Set<String> uniqueEntries = Sets.newHashSet();
for (RDotTxtEntry resource : resources) {
if (!resource.type.equals(RDotTxtEntry.RType.STYLEABLE)) {
assertFalse("Duplicate ids should be fixed by renumerate=true; duplicate was: " + resource.idValue, uniqueEntries.contains(resource.idValue));
uniqueEntries.add(resource.idValue);
}
}
assertEquals(6, uniqueEntries.size());
// All good, no need to further test whether we can write the Java file correctly...
}
use of com.facebook.buck.android.aapt.RDotTxtEntry in project buck by facebook.
the class MergeAndroidResourcesStep method writePerPackageRDotJava.
@VisibleForTesting
void writePerPackageRDotJava(SortedSetMultimap<String, RDotTxtEntry> packageToResources, ProjectFilesystem filesystem) throws IOException {
for (String rDotJavaPackage : packageToResources.keySet()) {
Path outputFile = getPathToRDotJava(rDotJavaPackage);
filesystem.mkdirs(outputFile.getParent());
try (PrintWriter writer = new PrintWriter(filesystem.newFileOutputStream(outputFile))) {
writer.format("package %s;\n\n", rDotJavaPackage);
writer.format("public class %s {\n", rName);
ImmutableList.Builder<String> customDrawablesBuilder = ImmutableList.builder();
ImmutableList.Builder<String> grayscaleImagesBuilder = ImmutableList.builder();
RDotTxtEntry.RType lastType = null;
for (RDotTxtEntry res : packageToResources.get(rDotJavaPackage)) {
RDotTxtEntry.RType type = res.type;
if (!type.equals(lastType)) {
// If the previous type needs to be closed, close it.
if (lastType != null) {
writer.println(" }\n");
}
// Now start the block for the new type.
writer.format(" public static class %s {\n", type);
lastType = type;
}
// Write out the resource.
// Write as an int.
writer.format(" public static%s%s %s=%s;\n", forceFinalResourceIds ? " final " : " ", res.idType, res.name, res.idValue);
if (type == RDotTxtEntry.RType.DRAWABLE && res.customType == RDotTxtEntry.CustomDrawableType.CUSTOM) {
customDrawablesBuilder.add(res.idValue);
} else if (type == RDotTxtEntry.RType.DRAWABLE && res.customType == RDotTxtEntry.CustomDrawableType.GRAYSCALE_IMAGE) {
grayscaleImagesBuilder.add(res.idValue);
}
}
// closed.
if (lastType != null) {
writer.println(" }\n");
}
ImmutableList<String> customDrawables = customDrawablesBuilder.build();
if (customDrawables.size() > 0) {
// Add a new field for the custom drawables.
writer.format(" public static final int[] custom_drawables = ");
writer.format("{ %s };\n", Joiner.on(",").join(customDrawables));
writer.format("\n");
}
ImmutableList<String> grayscaleImages = grayscaleImagesBuilder.build();
if (grayscaleImages.size() > 0) {
// Add a new field for the custom drawables.
writer.format(" public static final int[] grayscale_images = ");
writer.format("{ %s };\n", Joiner.on(",").join(grayscaleImages));
writer.format("\n");
}
// Close the class definition.
writer.println("}");
}
}
}
use of com.facebook.buck.android.aapt.RDotTxtEntry in project buck by facebook.
the class MergeAndroidResourcesStep method doExecute.
private void doExecute() throws IOException, DuplicateResourceException {
// In order to convert a symbols file to R.java, all resources of the same type are grouped
// into a static class of that name. The static class contains static values that correspond to
// the resource (type, name, value) tuples. See RDotTxtEntry.
//
// The first step is to merge symbol files of the same package type and resource type/name.
// That is, within a package type, each resource type/name pair must be unique. If there are
// multiple pairs, only one will be written to the R.java file.
//
// Because the resulting files do not match their respective resources.arsc, the values are
// meaningless and do not represent the usable final result. This is why the R.java file is
// written without using final so that javac will not inline the values. Unfortunately,
// though Robolectric doesn't read resources.arsc, it does assert that all the R.java resource
// ids are unique. This forces us to re-enumerate new unique ids.
ImmutableMap.Builder<Path, String> rDotTxtToPackage = ImmutableMap.builder();
ImmutableMap.Builder<Path, HasAndroidResourceDeps> symbolsFileToResourceDeps = ImmutableMap.builder();
for (HasAndroidResourceDeps res : androidResourceDeps) {
// TODO(shs96c): These have to be absolute for this all to work with multi-repo.
// This is because each `androidResourceDeps` might be from a different repo, so we can't
// assume that they exist in the calling rule's projectfilesystem.
Path rDotTxtPath = pathResolver.getRelativePath(res.getPathToTextSymbolsFile());
rDotTxtToPackage.put(rDotTxtPath, res.getRDotJavaPackage());
symbolsFileToResourceDeps.put(rDotTxtPath, res);
}
Optional<ImmutableMap<RDotTxtEntry, String>> uberRDotTxtIds;
if (uberRDotTxt.isPresent()) {
// re-assign Ids
uberRDotTxtIds = Optional.of(FluentIterable.from(RDotTxtEntry.readResources(filesystem, uberRDotTxt.get())).toMap(input -> input.idValue));
} else {
uberRDotTxtIds = Optional.empty();
}
ImmutableMap<Path, String> symbolsFileToRDotJavaPackage = rDotTxtToPackage.build();
SortedSetMultimap<String, RDotTxtEntry> rDotJavaPackageToResources = sortSymbols(symbolsFileToRDotJavaPackage, uberRDotTxtIds, symbolsFileToResourceDeps.build(), bannedDuplicateResourceTypes, filesystem, useOldStyleableFormat);
// unless they are already present.
if (unionPackage.isPresent()) {
String unionPackageName = unionPackage.get();
// Create a temporary list to avoid concurrent modification problems.
for (Map.Entry<String, RDotTxtEntry> entry : new ArrayList<>(rDotJavaPackageToResources.entries())) {
if (rDotJavaPackageToResources.containsEntry(unionPackageName, entry.getValue())) {
continue;
}
rDotJavaPackageToResources.put(unionPackageName, entry.getValue());
}
}
writePerPackageRDotJava(rDotJavaPackageToResources, filesystem);
Set<String> emptyPackages = Sets.difference(ImmutableSet.copyOf(symbolsFileToRDotJavaPackage.values()), rDotJavaPackageToResources.keySet());
if (!emptyPackages.isEmpty()) {
writeEmptyRDotJavaForPackages(emptyPackages, filesystem);
}
}
use of com.facebook.buck.android.aapt.RDotTxtEntry in project buck by facebook.
the class MergeAndroidResourcesStep method getStyleableResources.
private static Map<RDotTxtEntry, String> getStyleableResources(Map<RDotTxtEntry, String> resourceToIdValuesMap, List<String> linesInSymbolsFile, String resourceName, int index) {
Map<RDotTxtEntry, String> styleableResourceMap = new LinkedHashMap<>();
for (int styleableIndex = 0; styleableIndex + index < linesInSymbolsFile.size(); styleableIndex++) {
RDotTxtEntry styleableResource = getResourceAtIndex(linesInSymbolsFile, styleableIndex + index);
String styleablePrefix = resourceName + "_";
if (styleableResource.idType == IdType.INT && styleableResource.type == RType.STYLEABLE && styleableResource.name.startsWith(styleablePrefix)) {
String attrName = styleableResource.name.substring(styleablePrefix.length());
RDotTxtEntry attrResource = new RDotTxtEntry(IdType.INT, RType.ATTR, attrName, "");
String attrIdValue = resourceToIdValuesMap.get(attrResource);
if (attrIdValue == null) {
// If not value is found just put the index.
// The attribute is coming from android R.java
attrIdValue = String.valueOf(styleableIndex);
}
styleableResourceMap.put(styleableResource.copyWithNewIdValue(String.valueOf(styleableIndex)), attrIdValue);
} else {
break;
}
}
return styleableResourceMap;
}
use of com.facebook.buck.android.aapt.RDotTxtEntry in project buck by facebook.
the class MergeAndroidResourcesStep method sortSymbols.
@VisibleForTesting
static SortedSetMultimap<String, RDotTxtEntry> sortSymbols(Map<Path, String> symbolsFileToRDotJavaPackage, Optional<ImmutableMap<RDotTxtEntry, String>> uberRDotTxtIds, ImmutableMap<Path, HasAndroidResourceDeps> symbolsFileToResourceDeps, EnumSet<RType> bannedDuplicateResourceTypes, ProjectFilesystem filesystem, boolean useOldStyleableFormat) throws DuplicateResourceException {
// If we're reenumerating, start at 0x7f01001 so that the resulting file is human readable.
// This value range (0x7f010001 - ...) is easier to spot as an actual resource id instead of
// other values in styleable which can be enumerated integers starting at 0.
Map<RDotTxtEntry, String> finalIds = null;
IntEnumerator enumerator = null;
if (uberRDotTxtIds.isPresent()) {
finalIds = uberRDotTxtIds.get();
} else {
enumerator = new IntEnumerator(0x7f01001);
}
SortedSetMultimap<String, RDotTxtEntry> rDotJavaPackageToSymbolsFiles = TreeMultimap.create();
SortedSetMultimap<RDotTxtEntry, Path> bannedDuplicateResourceToSymbolsFiles = TreeMultimap.create();
HashMap<RDotTxtEntry, String> resourceToIdValuesMap = new HashMap<>();
for (Map.Entry<Path, String> entry : symbolsFileToRDotJavaPackage.entrySet()) {
Path symbolsFile = entry.getKey();
// Read the symbols file and parse each line as a Resource.
List<String> linesInSymbolsFile;
try {
linesInSymbolsFile = FluentIterable.from(filesystem.readLines(symbolsFile)).filter(input -> !Strings.isNullOrEmpty(input)).toList();
} catch (IOException e) {
throw new RuntimeException(e);
}
String packageName = entry.getValue();
for (int index = 0; index < linesInSymbolsFile.size(); index++) {
RDotTxtEntry resource = getResourceAtIndex(linesInSymbolsFile, index);
if (uberRDotTxtIds.isPresent()) {
Preconditions.checkNotNull(finalIds);
if (!finalIds.containsKey(resource)) {
LOG.debug("Cannot find resource '%s' in the uber R.txt.", resource);
continue;
}
resource = resource.copyWithNewIdValue(finalIds.get(resource));
} else if (useOldStyleableFormat && resource.idValue.startsWith("0x7f")) {
Preconditions.checkNotNull(enumerator);
resource = resource.copyWithNewIdValue(String.format("0x%08x", enumerator.next()));
} else if (useOldStyleableFormat) {
// NOPMD more readable this way, IMO.
// Nothing extra to do in this case.
} else if (resourceToIdValuesMap.get(resource) != null) {
resource = resource.copyWithNewIdValue(resourceToIdValuesMap.get(resource));
} else if (resource.idType == IdType.INT_ARRAY && resource.type == RType.STYLEABLE) {
Map<RDotTxtEntry, String> styleableResourcesMap = getStyleableResources(resourceToIdValuesMap, linesInSymbolsFile, resource.name, index + 1);
for (RDotTxtEntry styleableResource : styleableResourcesMap.keySet()) {
resourceToIdValuesMap.put(styleableResource, styleableResource.idValue);
}
// int[] styleable entry is not added to the cache as
// the number of child can differ in dependent libraries
resource = resource.copyWithNewIdValue(String.format("{ %s }", Joiner.on(RDotTxtEntry.INT_ARRAY_SEPARATOR).join(styleableResourcesMap.values())));
} else {
Preconditions.checkNotNull(enumerator);
resource = resource.copyWithNewIdValue(String.format("0x%08x", enumerator.next()));
// Add resource to cache so that the id value is consistent across all R.txt
resourceToIdValuesMap.put(resource, resource.idValue);
}
if (bannedDuplicateResourceTypes.contains(resource.type)) {
bannedDuplicateResourceToSymbolsFiles.put(resource, symbolsFile);
}
rDotJavaPackageToSymbolsFiles.put(packageName, resource);
}
}
StringBuilder duplicateResourcesMessage = new StringBuilder();
for (Map.Entry<RDotTxtEntry, Collection<Path>> resourceAndSymbolsFiles : bannedDuplicateResourceToSymbolsFiles.asMap().entrySet()) {
Collection<Path> paths = resourceAndSymbolsFiles.getValue();
if (paths.size() > 1) {
RDotTxtEntry resource = resourceAndSymbolsFiles.getKey();
duplicateResourcesMessage.append(String.format("Resource '%s' (%s) is duplicated across: ", resource.name, resource.type));
List<SourcePath> resourceDirs = new ArrayList<>(paths.size());
for (Path path : paths) {
SourcePath res = symbolsFileToResourceDeps.get(path).getRes();
if (res != null) {
resourceDirs.add(res);
}
}
duplicateResourcesMessage.append(Joiner.on(", ").join(resourceDirs));
duplicateResourcesMessage.append("\n");
}
}
if (duplicateResourcesMessage.length() > 0) {
throw new DuplicateResourceException(duplicateResourcesMessage.toString());
}
return rDotJavaPackageToSymbolsFiles;
}
Aggregations