OSDN Git Service

Improved RenderScript support in SDK build.
authorXavier Ducrohet <xav@android.com>
Tue, 11 Oct 2011 00:53:21 +0000 (17:53 -0700)
committerXavier Ducrohet <xav@android.com>
Tue, 11 Oct 2011 02:07:48 +0000 (19:07 -0700)
- renderscript output is now in bin/res/ instead of res/
- Ant build system properly handle dependencies to only recompile
  files that need it.

Change-Id: Ic2cd4487a26e7a7fcb0b475ee52fa0ccf8a07c0b

anttasks/src/com/android/ant/AaptExecTask.java
anttasks/src/com/android/ant/AidlExecTask.java
anttasks/src/com/android/ant/ApkBuilderTask.java
anttasks/src/com/android/ant/DexExecTask.java
anttasks/src/com/android/ant/MultiFilesTask.java [new file with mode: 0644]
anttasks/src/com/android/ant/RenderScriptTask.java
anttasks/src/com/android/ant/SingleDependencyTask.java [moved from anttasks/src/com/android/ant/BaseTask.java with 96% similarity]
files/ant/build.xml

index 504640b..45adc7c 100644 (file)
@@ -52,7 +52,7 @@ import java.util.List;
  * <tr><td></td><td></td><td></td></tr>
  * </table>
  */
-public final class AaptExecTask extends BaseTask {
+public final class AaptExecTask extends SingleDependencyTask {
 
     /**
      * Class representing a &lt;nocompress&gt; node in the main task XML.
index ab01c71..651cc66 100644 (file)
@@ -18,19 +18,11 @@ package com.android.ant;
 
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
 import org.apache.tools.ant.taskdefs.ExecTask;
-import org.apache.tools.ant.types.FileSet;
 import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.types.PatternSet.NameEntry;
 
-import java.io.File;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Task to execute aidl.
@@ -44,13 +36,72 @@ import java.util.Set;
  * It also expects one or more inner elements called "source" which are identical to {@link Path}
  * elements.
  */
-public class AidlExecTask extends Task {
+public class AidlExecTask extends MultiFilesTask {
 
     private String mExecutable;
     private String mFramework;
     private String mGenFolder;
     private final ArrayList<Path> mPaths = new ArrayList<Path>();
 
+    private class AidlProcessor implements SourceProcessor {
+
+        public String getSourceFileExtension() {
+            return "aidl";
+        }
+
+        public void process(String filePath, String sourceFolder,
+                List<String> sourceFolders, Project taskProject) {
+            ExecTask task = new ExecTask();
+            task.setProject(taskProject);
+            task.setOwningTarget(getOwningTarget());
+            task.setExecutable(mExecutable);
+            task.setTaskName("aidl");
+            task.setFailonerror(true);
+
+            task.createArg().setValue("-p" + mFramework);
+            task.createArg().setValue("-o" + mGenFolder);
+            // add all the source folders as import in case an aidl file in a source folder
+            // imports a parcelable from another source folder.
+            for (String importFolder : sourceFolders) {
+                task.createArg().setValue("-I" + importFolder);
+            }
+
+            // set auto dependency file creation
+            task.createArg().setValue("-a");
+
+            task.createArg().setValue(filePath);
+
+            // execute it.
+            task.execute();
+        }
+
+        public void displayMessage(DisplayType type, int count) {
+            switch (type) {
+                case FOUND:
+                    System.out.println(String.format("Found %1$d AIDL files.", count));
+                    break;
+                case COMPILING:
+                    if (count > 0) {
+                        System.out.println(String.format("Compiling %1$d AIDL files.",
+                                count));
+                    } else {
+                        System.out.println("No AIDL files to compile.");
+                    }
+                    break;
+                case REMOVE_OUTPUT:
+                    System.out.println(String.format("Found %1$d obsolete output files to remove.",
+                            count));
+                    break;
+                case REMOVE_DEP:
+                    System.out.println(
+                            String.format("Found %1$d obsolete dependency files to remove.",
+                                    count));
+                    break;
+            }
+        }
+
+    }
+
     /**
      * Sets the value of the "executable" attribute.
      * @param executable the value.
@@ -85,144 +136,7 @@ public class AidlExecTask extends Task {
             throw new BuildException("AidlExecTask's 'genFolder' is required.");
         }
 
-        Project taskProject = getProject();
-
-        // build a list of all the source folders
-        ArrayList<String> sourceFolders = new ArrayList<String>();
-        for (Path p : mPaths) {
-            String[] values = p.list();
-            if (values != null) {
-                sourceFolders.addAll(Arrays.asList(values));
-            }
-        }
-
-        // gather all the aidl files from all the source folders.
-        Set<String> sourceFiles = getFileListByExtension(taskProject, sourceFolders, "**/*.aidl");
-        if (sourceFiles.size() > 0) {
-            System.out.println(String.format("Found %d aidl files.", sourceFiles.size()));
-        }
-
-        // go look for all dependency files in the gen folder.
-        Set<String> depFiles = getFileListByExtension(taskProject, mGenFolder, "**/*.d");
-
-        // parse all the dep files and keep the ones that are aidl and check if they require
-        // compilation again.
-        ArrayList<String> toCompile = new ArrayList<String>();
-        ArrayList<File> toRemove = new ArrayList<File>();
-        ArrayList<String> depsToRemove = new ArrayList<String>();
-        for (String depFile : depFiles) {
-            DependencyGraph graph = new DependencyGraph(depFile, null /*watchPaths*/);
-
-            // get the source file. it's the first item in the pre-reqs
-            File sourceFile = graph.getFirstPrereq();
-            String sourceFilePath = sourceFile.getAbsolutePath();
-
-            // The gen folder may contain other dependency files not generated by aidl.
-            // We only care if the first pre-rep is an aidl file.
-            if (sourceFilePath.toLowerCase().endsWith(".aidl")) {
-                // remove from the list of sourceFiles to mark as "processed" (but not compiled
-                // yet, that'll be done by adding it to toCompile)
-                if (sourceFiles.remove(sourceFilePath) == false) {
-                    // looks like the source file does not exist anymore!
-                    // we'll have to remove the output!
-                    Set<File> outputFiles = graph.getTargets();
-                    toRemove.addAll(outputFiles);
-
-                    // also need to remove the dep file.
-                    depsToRemove.add(depFile);
-                } else if (graph.dependenciesHaveChanged(false /*printStatus*/)) {
-                    // need to recompile!
-                    toCompile.add(sourceFilePath);
-                }
-            }
-        }
-
-        // add to the list of files to compile, whatever is left in sourceFiles. Those are
-        // new files that have never been compiled.
-        toCompile.addAll(sourceFiles);
-
-        if (toCompile.size() > 0) {
-            System.out.println(String.format("Compiling %d aidl files.", toCompile.size()));
-
-            for (String toCompilePath : toCompile) {
-                ExecTask task = new ExecTask();
-                task.setProject(taskProject);
-                task.setOwningTarget(getOwningTarget());
-                task.setExecutable(mExecutable);
-                task.setTaskName("aidl");
-                task.setFailonerror(true);
-
-                task.createArg().setValue("-p" + mFramework);
-                task.createArg().setValue("-o" + mGenFolder);
-                // add all the source folders as import in case an aidl file in a source folder
-                // imports a parcelable from another source folder.
-                for (String importFolder : sourceFolders) {
-                    task.createArg().setValue("-I" + importFolder);
-                }
-
-                // set auto dependency file creation
-                task.createArg().setValue("-a");
-
-                task.createArg().setValue(toCompilePath);
-
-                // execute it.
-                task.execute();
-            }
-        } else {
-            System.out.println(String.format("No aidl files to compile."));
-        }
-
-        if (toRemove.size() > 0) {
-            System.out.println(String.format("%d obsolete output files to remove.",
-                    toRemove.size()));
-            for (File toRemoveFile : toRemove) {
-                if (toRemoveFile.delete() == false) {
-                    System.err.println("Failed to remove " + toRemoveFile.getAbsolutePath());
-                }
-            }
-        }
-
-        // remove the dependency files that are obsolete
-        if (depsToRemove.size() > 0) {
-            System.out.println(String.format("%d obsolete dependency files to remove.",
-                    depsToRemove.size()));
-            for (String path : depsToRemove) {
-                if (new File(path).delete() == false) {
-                    System.err.println("Failed to remove " + path);
-                }
-            }
-        }
-    }
-
-    private Set<String> getFileListByExtension(Project taskProject,
-            List<String> sourceFolders, String filter) {
-        HashSet<String> sourceFiles = new HashSet<String>();
-        for (String sourceFolder : sourceFolders) {
-            sourceFiles.addAll(getFileListByExtension(taskProject, sourceFolder, filter));
-        }
-
-        return sourceFiles;
+        processFiles(new AidlProcessor(), mPaths, mGenFolder);
     }
 
-    private Set<String> getFileListByExtension(Project taskProject,
-            String sourceFolder, String filter) {
-        HashSet<String> sourceFiles = new HashSet<String>();
-
-        // create a fileset to find all the files in the folder
-        FileSet fs = new FileSet();
-        fs.setProject(taskProject);
-        fs.setDir(new File(sourceFolder));
-        NameEntry include = fs.createInclude();
-        include.setName(filter);
-
-        // loop through the results of the file set
-        Iterator<?> iter = fs.iterator();
-        while (iter.hasNext()) {
-            sourceFiles.add(iter.next().toString());
-        }
-
-        return sourceFiles;
-    }
-
-
 }
index 88bd59c..1cf1c0d 100644 (file)
@@ -31,7 +31,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.regex.Pattern;
 
-public class ApkBuilderTask extends BaseTask {
+public class ApkBuilderTask extends SingleDependencyTask {
 
     private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$",
             Pattern.CASE_INSENSITIVE);
index 4016417..6be0a98 100644 (file)
@@ -30,7 +30,7 @@ import java.util.List;
 /**
  * Custom task to execute dx while handling dependencies.
  */
-public class DexExecTask extends BaseTask {
+public class DexExecTask extends SingleDependencyTask {
 
     private String mExecutable;
     private String mOutput;
diff --git a/anttasks/src/com/android/ant/MultiFilesTask.java b/anttasks/src/com/android/ant/MultiFilesTask.java
new file mode 100644 (file)
index 0000000..4fb1795
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ant;
+
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.PatternSet.NameEntry;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+class MultiFilesTask extends Task {
+
+    static enum DisplayType {
+        FOUND, COMPILING, REMOVE_OUTPUT, REMOVE_DEP;
+    }
+
+    interface SourceProcessor {
+        String getSourceFileExtension();
+        void process(String filePath, String sourceFolder,
+                List<String> sourceFolders, Project taskProject);
+        void displayMessage(DisplayType type, int count);
+    }
+
+    protected void processFiles(SourceProcessor processor, List<Path> paths, String genFolder) {
+
+        Project taskProject = getProject();
+
+        String extension = processor.getSourceFileExtension();
+
+        // build a list of all the source folders
+        ArrayList<String> sourceFolders = new ArrayList<String>();
+        for (Path p : paths) {
+            String[] values = p.list();
+            if (values != null) {
+                sourceFolders.addAll(Arrays.asList(values));
+            }
+        }
+
+        // gather all the source files from all the source folders.
+        Map<String, String> sourceFiles = getFileListByExtension(taskProject, sourceFolders,
+                "**/*." + extension);
+        if (sourceFiles.size() > 0) {
+            processor.displayMessage(DisplayType.FOUND, sourceFiles.size());
+        }
+
+        // go look for all dependency files in the gen folder. This will have all dependency
+        // files but we can filter them based on the first pre-req file.
+        Map<String, String> depFiles = getFileListByExtension(taskProject, genFolder, "**/*.d");
+
+        // parse all the dep files and keep the ones that are of the proper type and check if
+        // they require compilation again.
+        Map<String, String> toCompile = new HashMap<String, String>();
+        ArrayList<File> toRemove = new ArrayList<File>();
+        ArrayList<String> depsToRemove = new ArrayList<String>();
+        for (String depFile : depFiles.keySet()) {
+            DependencyGraph graph = new DependencyGraph(depFile, null /*watchPaths*/);
+
+            // get the source file. it's the first item in the pre-reqs
+            File sourceFile = graph.getFirstPrereq();
+            String sourceFilePath = sourceFile.getAbsolutePath();
+
+            // The gen folder may contain other dependency files not generated by this particular
+            // processor.
+            // We only care if the first pre-rep is of the right extension.
+            if (sourceFilePath.toLowerCase().endsWith("." + extension)) {
+                // remove from the list of sourceFiles to mark as "processed" (but not compiled
+                // yet, that'll be done by adding it to toCompile)
+                String sourceFolder = sourceFiles.get(sourceFilePath);
+                if (sourceFolder == null) {
+                    // looks like the source file does not exist anymore!
+                    // we'll have to remove the output!
+                    Set<File> outputFiles = graph.getTargets();
+                    toRemove.addAll(outputFiles);
+
+                    // also need to remove the dep file.
+                    depsToRemove.add(depFile);
+                } else {
+                    // Source file is present. remove it from the list as being processed.
+                    sourceFiles.remove(sourceFilePath);
+
+                    // check if it needs to be recompiled.
+                    if (graph.dependenciesHaveChanged(false /*printStatus*/)) {
+                        toCompile.put(sourceFilePath, sourceFolder);
+                    }
+                }
+            }
+        }
+
+        // add to the list of files to compile, whatever is left in sourceFiles. Those are
+        // new files that have never been compiled.
+        toCompile.putAll(sourceFiles);
+
+        processor.displayMessage(DisplayType.COMPILING, toCompile.size());
+        if (toCompile.size() > 0) {
+            for (Entry<String, String> toCompilePath : toCompile.entrySet()) {
+                processor.process(toCompilePath.getKey(), toCompilePath.getValue(),
+                        sourceFolders, taskProject);
+            }
+        }
+
+        if (toRemove.size() > 0) {
+            processor.displayMessage(DisplayType.REMOVE_OUTPUT, toRemove.size());
+
+            for (File toRemoveFile : toRemove) {
+                if (toRemoveFile.delete() == false) {
+                    System.err.println("Failed to remove " + toRemoveFile.getAbsolutePath());
+                }
+            }
+        }
+
+        // remove the dependency files that are obsolete
+        if (depsToRemove.size() > 0) {
+            processor.displayMessage(DisplayType.REMOVE_DEP, toRemove.size());
+
+            for (String path : depsToRemove) {
+                if (new File(path).delete() == false) {
+                    System.err.println("Failed to remove " + path);
+                }
+            }
+        }
+    }
+
+    private Map<String, String> getFileListByExtension(Project taskProject,
+            List<String> sourceFolders, String filter) {
+        HashMap<String, String> sourceFiles = new HashMap<String, String>();
+        for (String sourceFolder : sourceFolders) {
+            sourceFiles.putAll(getFileListByExtension(taskProject, sourceFolder, filter));
+        }
+
+        return sourceFiles;
+    }
+
+    private Map<String, String> getFileListByExtension(Project taskProject,
+            String sourceFolder, String filter) {
+        HashMap<String, String> sourceFiles = new HashMap<String, String>();
+
+        // create a fileset to find all the files in the folder
+        FileSet fs = new FileSet();
+        fs.setProject(taskProject);
+        fs.setDir(new File(sourceFolder));
+        NameEntry include = fs.createInclude();
+        include.setName(filter);
+
+        // loop through the results of the file set
+        Iterator<?> iter = fs.iterator();
+        while (iter.hasNext()) {
+            sourceFiles.put(iter.next().toString(), sourceFolder);
+        }
+
+        return sourceFiles;
+    }
+
+}
index 8264aac..96cf62e 100644 (file)
@@ -18,16 +18,11 @@ package com.android.ant;
 
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
 import org.apache.tools.ant.taskdefs.ExecTask;
-import org.apache.tools.ant.types.FileSet;
 import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.types.PatternSet.NameEntry;
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -43,7 +38,7 @@ import java.util.List;
  * It also expects one or more inner elements called "source" which are identical to {@link Path}
  * elements for where to find .rs files.
  */
-public class RenderScriptTask extends Task {
+public class RenderScriptTask extends MultiFilesTask {
 
     private String mExecutable;
     private Path mFramework;
@@ -52,6 +47,98 @@ public class RenderScriptTask extends Task {
     private final List<Path> mPaths = new ArrayList<Path>();
     private int mTargetApi = 0;
 
+    private class RenderScriptProcessor implements SourceProcessor {
+
+        private final String mTargetApiStr;
+
+        public RenderScriptProcessor(int targetApi) {
+            // get the target api value. Must be 11+ or llvm-rs-cc complains.
+            mTargetApiStr = Integer.toString(mTargetApi < 11 ? 11 : mTargetApi);
+        }
+
+        public String getSourceFileExtension() {
+            return "rs";
+        }
+
+        public void process(String filePath, String sourceFolder, List<String> sourceFolders,
+                Project taskProject) {
+            File exe = new File(mExecutable);
+            String execTaskName = exe.getName();
+
+            ExecTask task = new ExecTask();
+            task.setTaskName(execTaskName);
+            task.setProject(taskProject);
+            task.setOwningTarget(getOwningTarget());
+            task.setExecutable(mExecutable);
+            task.setFailonerror(true);
+
+            for (String path : mFramework.list()) {
+                File res = new File(path);
+                if (res.isDirectory()) {
+                    task.createArg().setValue("-I");
+                    task.createArg().setValue(path);
+                } else {
+                    System.out.println(String.format(
+                            "WARNING: RenderScript include directory '%s' does not exist!",
+                            res.getAbsolutePath()));
+                }
+
+            }
+
+            task.createArg().setValue("-target-api");
+            task.createArg().setValue(mTargetApiStr);
+
+            task.createArg().setValue("-d");
+            task.createArg().setValue(getDependencyFolder(filePath, sourceFolder));
+            task.createArg().setValue("-MD");
+
+            task.createArg().setValue("-p");
+            task.createArg().setValue(mGenFolder);
+            task.createArg().setValue("-o");
+            task.createArg().setValue(mResFolder);
+            task.createArg().setValue(filePath);
+
+            // execute it.
+            task.execute();
+        }
+
+        public void displayMessage(DisplayType type, int count) {
+            switch (type) {
+                case FOUND:
+                    System.out.println(String.format("Found %1$d RenderScript files.", count));
+                    break;
+                case COMPILING:
+                    if (count > 0) {
+                        System.out.println(String.format(
+                                "Compiling %1$d RenderScript files with -target-api %2$d",
+                                count, mTargetApi));
+                    } else {
+                        System.out.println("No RenderScript files to compile.");
+                    }
+                    break;
+                case REMOVE_OUTPUT:
+                    System.out.println(String.format("Found %1$d obsolete output files to remove.",
+                            count));
+                    break;
+                case REMOVE_DEP:
+                    System.out.println(
+                            String.format("Found %1$d obsolete dependency files to remove.",
+                                    count));
+                    break;
+            }
+        }
+
+        private String getDependencyFolder(String filePath, String sourceFolder) {
+            String relative = filePath.substring(sourceFolder.length());
+            if (relative.charAt(0) == '/') {
+                relative = relative.substring(1);
+            }
+
+            return new File(mGenFolder, relative).getParent();
+        }
+    }
+
+
     /**
      * Sets the value of the "executable" attribute.
      * @param executable the value.
@@ -107,78 +194,6 @@ public class RenderScriptTask extends Task {
             throw new BuildException("RenderScriptTask's 'targetApi' is required.");
         }
 
-        Project taskProject = getProject();
-
-        // build a list of all the source folders
-        ArrayList<String> sourceFolders = new ArrayList<String>();
-        for (Path p : mPaths) {
-            String[] values = p.list();
-            if (values != null) {
-                sourceFolders.addAll(Arrays.asList(values));
-            }
-        }
-
-        File exe = new File(mExecutable);
-        String execTaskName = exe.getName();
-
-        int count = 0;
-
-        // get the target api value. Must be 11+ or llvm-rs-cc complains.
-        String targetApiStr = Integer.toString(mTargetApi < 11 ? 11 : mTargetApi);
-
-        // now loop on all the source folders to find all the renderscript to compile
-        // and compile them
-        for (String sourceFolder : sourceFolders) {
-            // create a fileset to find all the aidl files in the current source folder
-            FileSet fs = new FileSet();
-            fs.setProject(taskProject);
-            fs.setDir(new File(sourceFolder));
-            NameEntry include = fs.createInclude();
-            include.setName("**/*.rs");
-
-            // loop through the results of the file set
-            Iterator<?> iter = fs.iterator();
-            while (iter.hasNext()) {
-                Object next = iter.next();
-
-                ExecTask task = new ExecTask();
-                task.setTaskName(execTaskName);
-                task.setProject(taskProject);
-                task.setOwningTarget(getOwningTarget());
-                task.setExecutable(mExecutable);
-                task.setFailonerror(true);
-
-                for (String path : mFramework.list()) {
-                    File res = new File(path);
-                    if (res.isDirectory()) {
-                        task.createArg().setValue("-I");
-                        task.createArg().setValue(path);
-                    }
-                }
-
-                task.createArg().setValue("-target-api");
-                task.createArg().setValue(targetApiStr);
-
-                task.createArg().setValue("-p");
-                task.createArg().setValue(mGenFolder);
-                task.createArg().setValue("-o");
-                task.createArg().setValue(mResFolder);
-                task.createArg().setValue(next.toString());
-
-                // execute it.
-                task.execute();
-
-                count++;
-            }
-        }
-
-        if (count > 0) {
-            System.out.println(String.format(
-                    "Compiled %d renderscript files (with -target-api set to %s)",
-                    count, mTargetApi));
-        } else {
-            System.out.println("No renderscript files to compile.");
-        }
-
+        processFiles(new RenderScriptProcessor(mTargetApi), mPaths, mGenFolder);
     }
 }
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,9 +27,9 @@ import java.util.List;
 import java.util.Set;
 
 /**
- * A base class for the ant task that contains logic for handling dependency files
+ * A base class for ant tasks that use a single dependency files to control (re)execution.
  */
-public abstract class BaseTask extends Task {
+public abstract class SingleDependencyTask extends Task {
 
     private DependencyGraph mDependencies;
     private String mPreviousBuildType;
index 9315a5e..cc8f398 100644 (file)
             <renderscript executable="${renderscript}"
                     framework="${android.rs}"
                     genFolder="${gen.absolute.dir}"
-                    resFolder="${resource.absolute.dir}/raw"
+                    resFolder="${out.res.absolute.dir}/raw"
                     targetApi="${target.api}">
                 <source path="${source.absolute.dir}"/>
             </renderscript>
                     nonConstantId="${android.library}"
                     projectLibrariesResName="project.libraries.res"
                     projectLibrariesPackageName="project.libraries.package">
+                <res path="${out.res.absolute.dir}" />
                 <res path="${resource.absolute.dir}" />
             </aapt>
         </do-only-if-manifest-hasCode>