Search in sources :

Example 1 with RDotTxtEntry

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...
}
Also used : RType(com.facebook.buck.android.aapt.RDotTxtEntry.RType) RDotTxtEntry(com.facebook.buck.android.aapt.RDotTxtEntry) Test(org.junit.Test)

Example 2 with RDotTxtEntry

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("}");
        }
    }
}
Also used : SourcePath(com.facebook.buck.rules.SourcePath) Path(java.nio.file.Path) ImmutableList(com.google.common.collect.ImmutableList) RDotTxtEntry(com.facebook.buck.android.aapt.RDotTxtEntry) RType(com.facebook.buck.android.aapt.RDotTxtEntry.RType) PrintWriter(java.io.PrintWriter) VisibleForTesting(com.google.common.annotations.VisibleForTesting)

Example 3 with RDotTxtEntry

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);
    }
}
Also used : SourcePath(com.facebook.buck.rules.SourcePath) Path(java.nio.file.Path) ArrayList(java.util.ArrayList) ImmutableMap(com.google.common.collect.ImmutableMap) RDotTxtEntry(com.facebook.buck.android.aapt.RDotTxtEntry) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) Map(java.util.Map) ImmutableMap(com.google.common.collect.ImmutableMap)

Example 4 with RDotTxtEntry

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;
}
Also used : RDotTxtEntry(com.facebook.buck.android.aapt.RDotTxtEntry) LinkedHashMap(java.util.LinkedHashMap)

Example 5 with RDotTxtEntry

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;
}
Also used : SourcePath(com.facebook.buck.rules.SourcePath) Path(java.nio.file.Path) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) ArrayList(java.util.ArrayList) IOException(java.io.IOException) SourcePath(com.facebook.buck.rules.SourcePath) RDotTxtEntry(com.facebook.buck.android.aapt.RDotTxtEntry) Collection(java.util.Collection) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) Map(java.util.Map) ImmutableMap(com.google.common.collect.ImmutableMap) VisibleForTesting(com.google.common.annotations.VisibleForTesting)

Aggregations

RDotTxtEntry (com.facebook.buck.android.aapt.RDotTxtEntry)6 RType (com.facebook.buck.android.aapt.RDotTxtEntry.RType)3 SourcePath (com.facebook.buck.rules.SourcePath)3 Path (java.nio.file.Path)3 ArrayList (java.util.ArrayList)3 LinkedHashMap (java.util.LinkedHashMap)3 VisibleForTesting (com.google.common.annotations.VisibleForTesting)2 ImmutableMap (com.google.common.collect.ImmutableMap)2 HashMap (java.util.HashMap)2 Map (java.util.Map)2 Test (org.junit.Test)2 FakeRDotTxtEntryWithID (com.facebook.buck.android.aapt.FakeRDotTxtEntryWithID)1 ImmutableList (com.google.common.collect.ImmutableList)1 IOException (java.io.IOException)1 PrintWriter (java.io.PrintWriter)1 Collection (java.util.Collection)1