aboutsummaryrefslogtreecommitdiffstats
path: root/lint
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2011-12-13 18:22:07 -0800
committerTor Norbye <tnorbye@google.com>2012-01-06 12:14:12 -0800
commit4c68f3dc9f10b76f1a32ff9c86587adca385a6d7 (patch)
treecc55067d195c1b2f89821a4716329f3510182357 /lint
parent51823e55d86449d14533e6bee69215196a42a829 (diff)
downloadsdk-4c68f3dc9f10b76f1a32ff9c86587adca385a6d7.zip
sdk-4c68f3dc9f10b76f1a32ff9c86587adca385a6d7.tar.gz
sdk-4c68f3dc9f10b76f1a32ff9c86587adca385a6d7.tar.bz2
Lint Library Support
This changeset adds support for library projects to lint. Lint now checks all the library projects for errors as well, and projects that depend on global analysis (such as the unused resource detector) will properly handle resource declarations and references across projects. This changeset also cleans up the multi-project handling for the Lint window in Eclipse. The "Run Lint" toolbar action, in addition to operating on multiple selection, now has a dropdown menu for choosing which projects to check (and there are also actions for checking all projects, the current file, and clearing markers). Running lint on a project will also automatically include dependent library projects. Finally, some misc UI improvements: The Lint preference dialog includes buttons for quickly enabling and disabling all the checks; the Lint View includes a Project column which is shown when more than one project is checked, and the file and linenumber columns are now blank when the location does not correspond to a specific file. Change-Id: I733f5258102dfb0aebbc2b75cb02b9ba6ef974e8
Diffstat (limited to 'lint')
-rw-r--r--lint/cli/src/com/android/tools/lint/Main.java5
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java147
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java30
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/LintListener.java3
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java8
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java30
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java19
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java7
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java12
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java164
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java18
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java8
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java5
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java16
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java2
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java2
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/LintCliXmlParserTest.java10
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UnusedResourceDetectorTest.java21
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/WrongIdDetectorTest.java1
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/LibraryCode.java.txt7
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/MainCode.java.txt7
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/library-manifest.xml23
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/library.properties12
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/main-manifest.xml23
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/main.properties12
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/strings.xml9
26 files changed, 526 insertions, 75 deletions
diff --git a/lint/cli/src/com/android/tools/lint/Main.java b/lint/cli/src/com/android/tools/lint/Main.java
index e37f9c4..1c4a252 100644
--- a/lint/cli/src/com/android/tools/lint/Main.java
+++ b/lint/cli/src/com/android/tools/lint/Main.java
@@ -878,6 +878,11 @@ public class Main extends LintClient {
"\nScanning %1$s: ",
context.getProject().getDir().getName()));
break;
+ case SCANNING_LIBRARY_PROJECT:
+ System.out.print(String.format(
+ "\n - %1$s: ",
+ context.getProject().getDir().getName()));
+ break;
case SCANNING_FILE:
System.out.print('.');
break;
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
index ec962b8..1bb6fe3 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
@@ -48,6 +48,7 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -122,7 +123,7 @@ public class Lint {
} else if (name.endsWith(".xml")) {
mScope.add(Scope.RESOURCE_FILE);
} else if (name.equals(PROGUARD_CFG)) {
- mScope.add(Scope.PROGUARD);
+ mScope.add(Scope.PROGUARD_FILE);
} else if (name.equals(RES_FOLDER_NAME)
|| file.getParent().equals(RES_FOLDER_NAME)) {
mScope.add(Scope.ALL_RESOURCE_FILES);
@@ -212,12 +213,7 @@ public class Lint {
private void registerProjectFile(Map<File, Project> fileToProject, File file,
File projectDir, File rootDir) {
- Project project = fileToProject.get(projectDir);
- if (project == null) {
- project = new Project(mClient, projectDir, rootDir);
- project.setConfiguration(mClient.getConfiguration(project));
- }
- fileToProject.put(file, project);
+ fileToProject.put(file, mClient.getProject(projectDir, rootDir));
}
private Collection<Project> computeProjects(List<File> files) {
@@ -281,7 +277,36 @@ public class Lint {
}
}
- return fileToProject.values();
+ // Partition the projects up such that we only return projects that aren't
+ // included by other projects (e.g. because they are library projects)
+
+ Collection<Project> allProjects = fileToProject.values();
+ Set<Project> roots = new HashSet<Project>(allProjects);
+ for (Project project : allProjects) {
+ roots.removeAll(project.getAllLibraries());
+ }
+
+ if (LintUtils.assertionsEnabled()) {
+ // Make sure that all the project directories are unique. This ensures
+ // that we didn't accidentally end up with different project instances
+ // for a library project discovered as a directory as well as one
+ // initialized from the library project dependency list
+ IdentityHashMap<Project, Project> projects =
+ new IdentityHashMap<Project, Project>();
+ for (Project project : roots) {
+ projects.put(project, project);
+ for (Project library : project.getAllLibraries()) {
+ projects.put(library, library);
+ }
+ }
+ Set<File> dirs = new HashSet<File>();
+ for (Project project : projects.keySet()) {
+ assert !dirs.contains(project.getDir());
+ dirs.add(project.getDir());
+ }
+ }
+
+ return roots;
}
private void addProjects(File dir, Map<File, Project> fileToProject, File rootDir) {
@@ -311,7 +336,7 @@ public class Lint {
File projectDir = project.getDir();
- Context projectContext = new Context(mClient, project, projectDir, mScope);
+ Context projectContext = new Context(mClient, project, null, projectDir, mScope);
fireEvent(EventType.SCANNING_PROJECT, projectContext);
for (Detector check : mApplicableDetectors) {
@@ -321,7 +346,35 @@ public class Lint {
}
}
- runFileDetectors(project, projectDir);
+
+ runFileDetectors(project, project);
+
+ if (!Scope.checkSingleFile(mScope)) {
+ List<Project> libraries = project.getDirectLibraries();
+ for (Project library : libraries) {
+ Context libraryContext = new Context(mClient, library, project, projectDir, mScope);
+ fireEvent(EventType.SCANNING_LIBRARY_PROJECT, libraryContext);
+
+ for (Detector check : mApplicableDetectors) {
+ check.beforeCheckLibraryProject(libraryContext);
+ if (mCanceled) {
+ return;
+ }
+ }
+
+ runFileDetectors(library, project);
+ if (mCanceled) {
+ return;
+ }
+
+ for (Detector check : mApplicableDetectors) {
+ check.afterCheckLibraryProject(libraryContext);
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+ }
for (Detector check : mApplicableDetectors) {
check.afterCheckProject(projectContext);
@@ -342,11 +395,12 @@ public class Lint {
}
}
- private void runFileDetectors(Project project, File projectDir) {
- // Look up manifest information
+ private void runFileDetectors(Project project, Project main) {
+
+ // Look up manifest information (but not for library projects)
File manifestFile = new File(project.getDir(), ANDROID_MANIFEST_XML);
- if (manifestFile.exists()) {
- XmlContext context = new XmlContext(mClient, project, manifestFile, mScope);
+ if (!project.isLibrary() && manifestFile.exists()) {
+ XmlContext context = new XmlContext(mClient, project, main, manifestFile, mScope);
IDomParser parser = mClient.getDomParser();
context.document = parser.parseXml(context);
if (context.document != null) {
@@ -378,11 +432,12 @@ public class Lint {
}
if (xmlDetectors.size() > 0) {
if (project.getSubset() != null) {
- checkIndividualResources(project, xmlDetectors, project.getSubset());
+ checkIndividualResources(project, main, xmlDetectors,
+ project.getSubset());
} else {
- File res = new File(projectDir, RES_FOLDER_NAME);
+ File res = new File(project.getDir(), RES_FOLDER_NAME);
if (res.exists() && xmlDetectors.size() > 0) {
- checkResFolder(project, res, xmlDetectors);
+ checkResFolder(project, main, res, xmlDetectors);
}
}
}
@@ -398,7 +453,7 @@ public class Lint {
mScopeDetectors.get(Scope.ALL_JAVA_FILES));
if (checks.size() > 0) {
List<File> sourceFolders = project.getJavaSourceFolders();
- checkJava(project, sourceFolders, checks);
+ checkJava(project, main, sourceFolders, checks);
}
}
@@ -410,7 +465,7 @@ public class Lint {
List<Detector> detectors = mScopeDetectors.get(Scope.CLASS_FILE);
if (detectors != null && detectors.size() > 0) {
List<File> binFolders = project.getJavaClassFolders();
- checkClasses(project, binFolders, detectors);
+ checkClasses(project, main, binFolders, detectors);
}
}
@@ -418,12 +473,12 @@ public class Lint {
return;
}
- if (mScope.contains(Scope.PROGUARD)) {
- List<Detector> detectors = mScopeDetectors.get(Scope.PROGUARD);
+ if (project == main && mScope.contains(Scope.PROGUARD_FILE)) {
+ List<Detector> detectors = mScopeDetectors.get(Scope.PROGUARD_FILE);
if (detectors != null) {
File file = new File(project.getDir(), PROGUARD_CFG);
if (file.exists()) {
- Context context = new Context(mClient, project, file, mScope);
+ Context context = new Context(mClient, project, main, file, mScope);
fireEvent(EventType.SCANNING_FILE, context);
for (Detector detector : detectors) {
if (detector.appliesTo(context, file)) {
@@ -453,7 +508,8 @@ public class Lint {
return new ArrayList<Detector>(set);
}
- private void checkClasses(Project project, List<File> binFolders, List<Detector> checks) {
+ private void checkClasses(Project project, Project main,
+ List<File> binFolders, List<Detector> checks) {
if (binFolders.size() == 0) {
//mClient.log(null, "Warning: Class-file checks are enabled, but no " +
// "output folders found. Does the project need to be built first?");
@@ -470,8 +526,8 @@ public class Lint {
ClassReader reader = new ClassReader(bytes);
ClassNode classNode = new ClassNode();
reader.accept(classNode, 0 /*flags*/);
- ClassContext context = new ClassContext(mClient, project, file, mScope,
- binDir, bytes, classNode);
+ ClassContext context = new ClassContext(mClient, project, main, file,
+ mScope, binDir, bytes, classNode);
for (Detector detector : checks) {
if (detector.appliesTo(context, file)) {
@@ -515,8 +571,9 @@ public class Lint {
}
}
- private void checkJava(Project project, List<File> sourceFolders, List<Detector> checks) {
- Context context = new Context(mClient, project, project.getDir(), mScope);
+ private void checkJava(Project project, Project main,
+ List<File> sourceFolders, List<Detector> checks) {
+ Context context = new Context(mClient, project, main, project.getDir(), mScope);
fireEvent(EventType.SCANNING_FILE, context);
for (Detector detector : checks) {
@@ -561,7 +618,8 @@ public class Lint {
return mCurrentVisitor;
}
- private void checkResFolder(Project project, File res, List<ResourceXmlDetector> checks) {
+ private void checkResFolder(Project project, Project main, File res,
+ List<ResourceXmlDetector> checks) {
assert res.isDirectory();
File[] resourceDirs = res.listFiles();
if (resourceDirs == null) {
@@ -576,7 +634,7 @@ public class Lint {
for (File dir : resourceDirs) {
type = ResourceFolderType.getFolderType(dir.getName());
if (type != null) {
- checkResourceFolder(project, dir, type, checks);
+ checkResourceFolder(project, main, dir, type, checks);
}
if (mCanceled) {
@@ -585,8 +643,8 @@ public class Lint {
}
}
- private void checkResourceFolder(Project project, File dir, ResourceFolderType type,
- List<ResourceXmlDetector> checks) {
+ private void checkResourceFolder(Project project, Project main, File dir,
+ ResourceFolderType type, List<ResourceXmlDetector> checks) {
// Process the resource folder
File[] xmlFiles = dir.listFiles();
if (xmlFiles != null && xmlFiles.length > 0) {
@@ -594,7 +652,8 @@ public class Lint {
if (visitor != null) { // if not, there are no applicable rules in this folder
for (File file : xmlFiles) {
if (LintUtils.isXmlFile(file)) {
- XmlContext context = new XmlContext(mClient, project, file, mScope);
+ XmlContext context = new XmlContext(mClient, project, main,
+ file, mScope);
fireEvent(EventType.SCANNING_FILE, context);
visitor.visitFile(context, file);
if (mCanceled) {
@@ -607,7 +666,7 @@ public class Lint {
}
/** Checks individual resources */
- private void checkIndividualResources(Project project,
+ private void checkIndividualResources(Project project, Project main,
List<ResourceXmlDetector> xmlDetectors, List<File> files) {
for (File file : files) {
if (file.isDirectory()) {
@@ -615,10 +674,10 @@ public class Lint {
ResourceFolderType type = ResourceFolderType.getFolderType(file.getName());
if (type != null && new File(file.getParentFile(), RES_FOLDER_NAME).exists()) {
// Yes.
- checkResourceFolder(project, file, type, xmlDetectors);
+ checkResourceFolder(project, main, file, type, xmlDetectors);
} else if (file.getName().equals(RES_FOLDER_NAME)) { // Is it the res folder?
// Yes
- checkResFolder(project, file, xmlDetectors);
+ checkResFolder(project, main, file, xmlDetectors);
} else {
mClient.log(null, "Unexpected folder %1$s; should be project, " +
"\"res\" folder or resource folder", file.getPath());
@@ -631,7 +690,8 @@ public class Lint {
if (type != null) {
XmlVisitor visitor = getVisitor(type, xmlDetectors);
if (visitor != null) {
- XmlContext context = new XmlContext(mClient, project, file, mScope);
+ XmlContext context = new XmlContext(mClient, project, main, file,
+ mScope);
fireEvent(EventType.SCANNING_FILE, context);
visitor.visitFile(context, file);
}
@@ -745,5 +805,20 @@ public class Lint {
public IDomParser getDomParser() {
return mDelegate.getDomParser();
}
+
+ @Override
+ public Class<? extends Detector> replaceDetector(Class<? extends Detector> detectorClass) {
+ return mDelegate.replaceDetector(detectorClass);
+ }
+
+ @Override
+ public SdkInfo getSdkInfo(Project project) {
+ return mDelegate.getSdkInfo(project);
+ }
+
+ @Override
+ public Project getProject(File dir, File referenceDir) {
+ return mDelegate.getProject(dir, referenceDir);
+ }
}
}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java
index 1f37553..282ec48 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java
@@ -31,7 +31,9 @@ import org.xml.sax.InputSource;
import java.io.File;
import java.io.StringReader;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -195,4 +197,32 @@ public abstract class LintClient {
return folders;
}
+
+ /**
+ * A map from directory to existing projects, or null. Used to ensure that
+ * projects are unique for a directory (in case we process a library project
+ * before its including project for example)
+ */
+ private Map<File, Project> mDirToProject;
+
+ /**
+ * Returns a project for the given directory. This should return the same
+ * project for the same directory if called repeatedly.
+ *
+ * @param dir the directory containing the project
+ * @param referenceDir See {@link Project#getReferenceDir()}.
+ * @return a project, never null
+ */
+ public Project getProject(File dir, File referenceDir) {
+ if (mDirToProject == null) {
+ mDirToProject = new HashMap<File, Project>();
+ }
+ Project project = mDirToProject.get(dir);
+ if (project != null) {
+ return project;
+ }
+ project = Project.create(this, dir, referenceDir);
+ mDirToProject.put(dir, project);
+ return project;
+ }
}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintListener.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintListener.java
index cb5d6c9..d6ead4d 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintListener.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintListener.java
@@ -35,6 +35,9 @@ public interface LintListener {
/** Lint is about to check the given project, see {@link Context#getProject()} */
SCANNING_PROJECT,
+ /** Lint is about to check the given library project, see {@link Context#getProject()} */
+ SCANNING_LIBRARY_PROJECT,
+
/** Lint is about to check the given file, see {@link Context#file} */
SCANNING_FILE,
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
index a06ed1b..86534f3 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
@@ -53,15 +53,19 @@ public class ClassContext extends Context {
*
* @param client the client requesting a lint check
* @param project the project containing the file being checked
+ * @param main the main project if this project is a library project, or
+ * null if this is not a library project. The main project is
+ * the root project of all library projects, not necessarily the
+ * directly including project.
* @param file the file being checked
* @param scope the scope for the lint job
* @param binDir the root binary directory containing this .class file.
* @param bytes the bytecode raw data
* @param classNode the bytecode object model
*/
- public ClassContext(LintClient client, Project project, File file,
+ public ClassContext(LintClient client, Project project, Project main, File file,
EnumSet<Scope> scope, File binDir, byte[] bytes, ClassNode classNode) {
- super(client, project, file, scope);
+ super(client, project, main, file, scope);
mBinDir = binDir;
mBytes = bytes;
mClassNode = classNode;
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java
index d25dca2..db6cca9 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java
@@ -50,6 +50,18 @@ public class Context {
/** The project containing the file being checked */
private final Project mProject;
+ /**
+ * The "main" project. For normal projects, this is the same as {@link #mProject},
+ * but for library projects, it's the root project that includes (possibly indirectly)
+ * the various library projects and their library projects.
+ * <p>
+ * Note that this is a property on the {@link Context}, not the
+ * {@link Project}, since a library project can be included from multiple
+ * different top level projects, so there isn't <b>one</b> main project,
+ * just one per main project being analyzed with its library projects.
+ */
+ private final Project mMainProject;
+
/** The current configuration controlling which checks are enabled etc */
private final Configuration mConfiguration;
@@ -78,15 +90,20 @@ public class Context {
*
* @param client the client requesting a lint check
* @param project the project containing the file being checked
+ * @param main the main project if this project is a library project, or
+ * null if this is not a library project. The main project is
+ * the root project of all library projects, not necessarily the
+ * directly including project.
* @param file the file being checked
* @param scope the scope for the lint job
*/
- public Context(LintClient client, Project project, File file,
+ public Context(LintClient client, Project project, Project main, File file,
EnumSet<Scope> scope) {
this.file = file;
mClient = client;
mProject = project;
+ mMainProject = main;
mScope = scope;
mConfiguration = project.getConfiguration();
}
@@ -119,6 +136,17 @@ public class Context {
}
/**
+ * Returns the main project if this project is a library project, or self
+ * if this is not a library project. The main project is the root project
+ * of all library projects, not necessarily the directly including project.
+ *
+ * @return the main project, never null
+ */
+ public Project getMainProject() {
+ return mMainProject != null ? mMainProject : mProject;
+ }
+
+ /**
* Returns the lint client requesting the lint check
*
* @return the client, never null
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
index 2d86dce..c41a2b6 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
@@ -174,6 +174,25 @@ public abstract class Detector {
}
/**
+ * Analysis is about to begin for the given library project, perform any setup steps.
+ *
+ * @param context the context for the check referencing the project, lint
+ * client, etc
+ */
+ public void beforeCheckLibraryProject(Context context) {
+ }
+
+ /**
+ * Analysis has just been finished for the given library project, perform any
+ * cleanup or report issues that require library-project-wide analysis.
+ *
+ * @param context the context for the check referencing the project, lint
+ * client, etc
+ */
+ public void afterCheckLibraryProject(Context context) {
+ }
+
+ /**
* Analysis is about to be performed on a specific file, perform any setup
* steps.
*
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java
index 5ed2d78..6c7abb7 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java
@@ -192,6 +192,8 @@ public class LintConstants {
public static final String VALUE_FILL_PARENT = "fill_parent"; //$NON-NLS-1$
public static final String VALUE_MATCH_PARENT = "match_parent"; //$NON-NLS-1$
public static final String VALUE_VERTICAL = "vertical"; //$NON-NLS-1$
+ public static final String VALUE_TRUE = "true"; //$NON-NLS-1$
+
// Values: Resources
public static final String VALUE_ID = "id"; //$NON-NLS-1$
@@ -235,4 +237,9 @@ public class LintConstants {
// Packages
public static final String WIDGET_PKG_PREFIX = "android.widget."; //$NON-NLS-1$
public static final String VIEW_PKG_PREFIX = "android.view."; //$NON-NLS-1$
+
+ // Project properties
+ public static final String ANDROID_LIBRARY = "android.library"; //$NON-NLS-1$
+ public static final String ANDROID_LIBRARY_REFERENCE_FORMAT = "android.library.reference.%1$d";//$NON-NLS-1$
+ public static final String PROJECT_PROPERTIES = "project.properties";//$NON-NLS-1$
}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
index 4dbcbb3..f6cfcba 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
@@ -221,4 +221,16 @@ public class LintUtils {
return d[m][n];
}
+
+ /**
+ * Returns true if assertions are enabled
+ *
+ * @return true if assertions are enabled
+ */
+ @SuppressWarnings("all")
+ public static boolean assertionsEnabled() {
+ boolean assertionsEnabled = false;
+ assert assertionsEnabled = true; // Intentional side-effect
+ return assertionsEnabled;
+ }
}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java
index b5fab29..7303462 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java
@@ -16,23 +16,34 @@
package com.android.tools.lint.detector.api;
+import static com.android.tools.lint.detector.api.LintConstants.ANDROID_LIBRARY;
+import static com.android.tools.lint.detector.api.LintConstants.ANDROID_LIBRARY_REFERENCE_FORMAT;
import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI;
import static com.android.tools.lint.detector.api.LintConstants.ATTR_MIN_SDK_VERSION;
import static com.android.tools.lint.detector.api.LintConstants.ATTR_PACKAGE;
import static com.android.tools.lint.detector.api.LintConstants.ATTR_TARGET_SDK_VERSION;
+import static com.android.tools.lint.detector.api.LintConstants.PROJECT_PROPERTIES;
import static com.android.tools.lint.detector.api.LintConstants.TAG_USES_SDK;
+import static com.android.tools.lint.detector.api.LintConstants.VALUE_TRUE;
import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.LintClient;
import com.google.common.annotations.Beta;
+import com.google.common.io.Closeables;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
+import java.io.BufferedInputStream;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
+import java.util.Properties;
/**
* A project contains information about an Android project being scanned for
@@ -43,14 +54,14 @@ import java.util.List;
*/
@Beta
public class Project {
- /** The associated tool */
- private final LintClient mTool;
+ private final LintClient mClient;
private final File mDir;
private final File mReferenceDir;
private Configuration mConfiguration;
private String mPackage;
private int mMinSdk = -1;
private int mTargetSdk = -1;
+ private boolean mLibrary;
/**
* If non null, specifies a non-empty list of specific files under this
@@ -59,18 +70,81 @@ public class Project {
private List<File> mFiles;
private List<File> mJavaSourceFolders;
private List<File> mJavaClassFolders;
+ private List<Project> mDirectLibraries;
+ private List<Project> mAllLibraries;
/**
- * Creates a new Project.
+ * Creates a new {@link Project} for the given directory.
*
- * @param tool the tool running the lint check
+ * @param client the tool running the lint check
* @param dir the root directory of the project
* @param referenceDir See {@link #getReferenceDir()}.
+ * @return a new {@link Project}
*/
- public Project(LintClient tool, File dir, File referenceDir) {
- mTool = tool;
+ public static Project create(LintClient client, File dir, File referenceDir) {
+ return new Project(client, dir, referenceDir);
+ }
+
+ /** Creates a new Project. Use one of the factory methods to create. */
+ private Project(LintClient client, File dir, File referenceDir) {
+ mClient = client;
mDir = dir;
mReferenceDir = referenceDir;
+
+ try {
+ // Read properties file and initialize library state
+ Properties properties = new Properties();
+ File propFile = new File(dir, PROJECT_PROPERTIES);
+ if (propFile.exists()) {
+ BufferedInputStream is = new BufferedInputStream(new FileInputStream(propFile));
+ try {
+ properties.load(is);
+ String value = properties.getProperty(ANDROID_LIBRARY);
+ mLibrary = VALUE_TRUE.equals(value);
+
+ for (int i = 1; i < 1000; i++) {
+ String key = String.format(ANDROID_LIBRARY_REFERENCE_FORMAT, i);
+ String library = properties.getProperty(key);
+ if (library == null || library.length() == 0) {
+ // No holes in the numbering sequence is allowed
+ break;
+ }
+
+ File libraryDir = new File(dir, library).getCanonicalFile();
+
+ if (mDirectLibraries == null) {
+ mDirectLibraries = new ArrayList<Project>();
+ }
+
+ // Adjust the reference dir to be a proper prefix path of the
+ // library dir
+ File libraryReferenceDir = referenceDir;
+ if (!libraryDir.getPath().startsWith(referenceDir.getPath())) {
+ File f = libraryReferenceDir;
+ while (f != null && f.getPath().length() > 0) {
+ if (libraryDir.getPath().startsWith(f.getPath())) {
+ libraryReferenceDir = f;
+ break;
+ }
+ f = f.getParentFile();
+ }
+ }
+
+ mDirectLibraries.add(client.getProject(libraryDir, libraryReferenceDir));
+ }
+ } finally {
+ Closeables.closeQuietly(is);
+ }
+ }
+ } catch (IOException ioe) {
+ client.log(ioe, "Initializing project state");
+ }
+
+ if (mDirectLibraries != null) {
+ mDirectLibraries = Collections.unmodifiableList(mDirectLibraries);
+ } else {
+ mDirectLibraries = Collections.emptyList();
+ }
}
@Override
@@ -133,7 +207,7 @@ public class Project {
*/
public List<File> getJavaSourceFolders() {
if (mJavaSourceFolders == null) {
- mJavaSourceFolders = mTool.getJavaSourceFolders(this);
+ mJavaSourceFolders = mClient.getJavaSourceFolders(this);
}
return mJavaSourceFolders;
@@ -145,7 +219,7 @@ public class Project {
*/
public List<File> getJavaClassFolders() {
if (mJavaClassFolders == null) {
- mJavaClassFolders = mTool.getJavaClassFolders(this);
+ mJavaClassFolders = mClient.getJavaClassFolders(this);
}
return mJavaClassFolders;
}
@@ -222,24 +296,21 @@ public class Project {
* @return the configuration associated with this project
*/
public Configuration getConfiguration() {
+ if (mConfiguration == null) {
+ mConfiguration = mClient.getConfiguration(this);
+ }
return mConfiguration;
}
/**
- * Sets the configuration associated with this project
- *
- * @param configuration sets the configuration associated with this project
- */
- public void setConfiguration(Configuration configuration) {
- mConfiguration = configuration;
- }
-
- /**
* Returns the application package specified by the manifest
*
* @return the application package, or null if unknown
*/
public String getPackage() {
+ //assert !mLibrary; // Should call getPackage on the master project, not the library
+ // Assertion disabled because you might be running lint on a standalone library project.
+
return mPackage;
}
@@ -250,6 +321,9 @@ public class Project {
* @return the minimum API level or -1 if unknown
*/
public int getMinSdk() {
+ //assert !mLibrary; // Should call getMinSdk on the master project, not the library
+ // Assertion disabled because you might be running lint on a standalone library project.
+
return mMinSdk;
}
@@ -260,6 +334,9 @@ public class Project {
* @return the target API level or -1 if unknown
*/
public int getTargetSdk() {
+ //assert !mLibrary; // Should call getTargetSdk on the master project, not the library
+ // Assertion disabled because you might be running lint on a standalone library project.
+
return mTargetSdk;
}
@@ -269,6 +346,7 @@ public class Project {
* @param document the DOM document for the manifest XML document
*/
public void readManifest(Document document) {
+ assert !mLibrary; // Should call readManifest on the master project, not the library
Element root = document.getDocumentElement();
if (root == null) {
return;
@@ -309,4 +387,56 @@ public class Project {
}
}
}
+
+ /**
+ * Returns true if this project is an Android library project
+ *
+ * @return true if this project is an Android library project
+ */
+ public boolean isLibrary() {
+ return mLibrary;
+ }
+
+ /**
+ * Returns the list of library projects referenced by this project
+ *
+ * @return the list of library projects referenced by this project, never
+ * null
+ */
+ public List<Project> getDirectLibraries() {
+ return mDirectLibraries;
+ }
+
+ /**
+ * Returns the transitive closure of the library projects for this project
+ *
+ * @return the transitive closure of the library projects for this project
+ */
+ public List<Project> getAllLibraries() {
+ if (mAllLibraries == null) {
+ if (mDirectLibraries.size() == 0) {
+ return mDirectLibraries;
+ }
+
+ List<Project> all = new ArrayList<Project>();
+ addLibraryProjects(all);
+ mAllLibraries = all;
+ }
+
+ return mAllLibraries;
+ }
+
+ /**
+ * Adds this project's library project and their library projects
+ * recursively into the given collection of projects
+ *
+ * @param collection the collection to add the projects into
+ */
+ private void addLibraryProjects(Collection<Project> collection) {
+ for (Project library : mDirectLibraries) {
+ collection.add(library);
+ // Recurse
+ library.addLibraryProjects(collection);
+ }
+ }
}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
index e7198c5..e095498 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
@@ -73,13 +73,29 @@ public enum Scope {
MANIFEST,
/** The analysis considers the Proguard configuration file */
- PROGUARD,
+ PROGUARD_FILE,
/**
* The analysis considers classes in the libraries for this project.
*/
JAVA_LIBRARIES;
+ /**
+ * Returns true if the given scope set corresponds to scanning a single file
+ * rather than a whole project
+ *
+ * @param scopes the scope set to check
+ * @return true if the scope set references a single file
+ */
+ public static boolean checkSingleFile(EnumSet<Scope> scopes) {
+ return scopes.size() == 1 &&
+ (scopes.contains(JAVA_FILE)
+ || scopes.contains(CLASS_FILE)
+ || scopes.contains(RESOURCE_FILE)
+ || scopes.contains(PROGUARD_FILE)
+ || scopes.contains(MANIFEST));
+ }
+
/** All scopes: running lint on a project will check these scopes */
public static final EnumSet<Scope> ALL = EnumSet.allOf(Scope.class);
/** Scope-set used for detectors which are affected by a single resource file */
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java
index f456a63..c682cc8 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java
@@ -44,12 +44,16 @@ public class XmlContext extends Context {
*
* @param client the client requesting a lint check
* @param project the project containing the file being checked
+ * @param main the main project if this project is a library project, or
+ * null if this is not a library project. The main project is
+ * the root project of all library projects, not necessarily the
+ * directly including project.
* @param file the file being checked
* @param scope the scope for the lint job
*/
- public XmlContext(LintClient client, Project project, File file,
+ public XmlContext(LintClient client, Project project, Project main, File file,
EnumSet<Scope> scope) {
- super(client, project, file, scope);
+ super(client, project, main, file, scope);
}
/**
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index 5d31523..9618c5f 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -16,6 +16,7 @@
package com.android.tools.lint.checks;
+import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
import static com.android.tools.lint.detector.api.LintUtils.endsWith;
import com.android.prefs.AndroidLocation;
@@ -113,9 +114,7 @@ public class BuiltinIssueRegistry extends IssueRegistry {
sIssues = Collections.unmodifiableList(issues);
// Check that ids are unique
- boolean assertionsEnabled = false;
- assert assertionsEnabled = true; // Intentional side-effect
- if (assertionsEnabled) {
+ if (assertionsEnabled()) {
Set<String> ids = new HashSet<String>();
for (Issue issue : sIssues) {
String id = issue.getId();
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
index db394d3..6118991 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
@@ -268,9 +268,17 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
}
@Override
+ public void afterCheckLibraryProject(Context context) {
+ checkResourceFolder(context, context.getProject().getDir());
+ }
+
+ @Override
public void afterCheckProject(Context context) {
- // Make sure no
- File res = new File(context.getProject().getDir(), RES_FOLDER);
+ checkResourceFolder(context, context.getProject().getDir());
+ }
+
+ private void checkResourceFolder(Context context, File dir) {
+ File res = new File(dir, RES_FOLDER);
if (res.isDirectory()) {
File[] folders = res.listFiles();
if (folders != null) {
@@ -1058,7 +1066,7 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
* manifest is at least 11.
*/
private boolean isAndroid30(Context context, int folderVersion) {
- return folderVersion >= 11 || context.getProject().getMinSdk() >= 11;
+ return folderVersion >= 11 || context.getMainProject().getMinSdk() >= 11;
}
/**
@@ -1075,7 +1083,7 @@ public class IconDetector extends Detector implements Detector.XmlScanner {
return true;
}
- int minSdk = context.getProject().getMinSdk();
+ int minSdk = context.getMainProject().getMinSdk();
return minSdk == 9 || minSdk == 10;
}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java
index 6683a22..447653e 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java
@@ -227,7 +227,7 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca
return mManifestTheme;
}
- Project project = context.getProject();
+ Project project = context.getMainProject();
int apiLevel = project.getTargetSdk();
if (apiLevel == -1) {
apiLevel = project.getMinSdk();
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java
index 0d818e4..798cf26 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java
@@ -48,7 +48,7 @@ public class ProguardDetector extends Detector {
8,
Severity.ERROR,
ProguardDetector.class,
- EnumSet.of(Scope.PROGUARD)).setMoreInfo(
+ EnumSet.of(Scope.PROGUARD_FILE)).setMoreInfo(
"http://http://code.google.com/p/android/issues/detail?id=16384"); //$NON-NLS-1$
@Override
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/LintCliXmlParserTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/LintCliXmlParserTest.java
index 212b01e..7dca49e 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/LintCliXmlParserTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/LintCliXmlParserTest.java
@@ -68,9 +68,8 @@ public class LintCliXmlParserTest extends TestCase {
fw.write(xml);
fw.close();
LintClient client = new TestClient();
- Project project = new Project(client, file.getParentFile(), file.getParentFile());
- project.setConfiguration(client.getConfiguration(project));
- XmlContext context = new XmlContext(client, project, file,
+ Project project = Project.create(client, file.getParentFile(), file.getParentFile());
+ XmlContext context = new XmlContext(client, project, null, file,
EnumSet.of(Scope.RESOURCE_FILE));
Document document = parser.parseXml(context);
assertNotNull(document);
@@ -143,9 +142,8 @@ public class LintCliXmlParserTest extends TestCase {
fw.write(xml);
fw.close();
LintClient client = new TestClient();
- Project project = new Project(client, file.getParentFile(), file.getParentFile());
- project.setConfiguration(client.getConfiguration(project));
- XmlContext context = new XmlContext(client, project, file,
+ Project project = Project.create(client, file.getParentFile(), file.getParentFile());
+ XmlContext context = new XmlContext(client, project, null, file,
EnumSet.of(Scope.RESOURCE_FILE));
Document document = parser.parseXml(context);
assertNotNull(document);
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UnusedResourceDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
index e9e6f05..691cb60 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
@@ -100,4 +100,25 @@ public class UnusedResourceDetectorTest extends AbstractCheckTest {
"unusedR.java.txt=>gen/my/pkg/R.java",
"AndroidManifest.xml"));
}
+
+ public void testMultiProject() throws Exception {
+ assertEquals(
+ // string1 is defined and used in the library project
+ // string2 is defined in the library project and used in the master project
+ // string3 is defined in the library project and not used anywhere
+ "strings.xml:7: Warning: The resource R.string.string3 appears to be unused",
+
+ lintProject(
+ // Master project
+ "multiproject/main-manifest.xml=>AndroidManifest.xml",
+ "multiproject/main.properties=>project.properties",
+ "multiproject/MainCode.java.txt=>src/foo/main/MainCode.java",
+
+ // Library project
+ "multiproject/library-manifest.xml=>../LibraryProject/AndroidManifest.xml",
+ "multiproject/library.properties=>../LibraryProject/project.properties",
+ "multiproject/LibraryCode.java.txt=>../LibraryProject/src/foo/library/LibraryCode.java",
+ "multiproject/strings.xml=>../LibraryProject/res/values/strings.xml"
+ ));
+ }
}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/WrongIdDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/WrongIdDetectorTest.java
index 1b14c9b..337ac5d 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/WrongIdDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/WrongIdDetectorTest.java
@@ -48,5 +48,4 @@ public class WrongIdDetectorTest extends AbstractCheckTest {
lintFiles("wrongid/layout1.xml=>res/layout/layout1.xml"));
}
-
}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/LibraryCode.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/LibraryCode.java.txt
new file mode 100644
index 0000000..25a9d75
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/LibraryCode.java.txt
@@ -0,0 +1,7 @@
+package foo.library;
+
+public class LibraryCode {
+ static {
+ System.out.println(R.string.string1);
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/MainCode.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/MainCode.java.txt
new file mode 100644
index 0000000..7955e6b
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/MainCode.java.txt
@@ -0,0 +1,7 @@
+package foo.main;
+
+public class MainCode {
+ static {
+ System.out.println(R.string.string2);
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/library-manifest.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/library-manifest.xml
new file mode 100644
index 0000000..34ad6f5
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/library-manifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="foo.library"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".LibraryProjectActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/library.properties b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/library.properties
new file mode 100644
index 0000000..d525577
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/library.properties
@@ -0,0 +1,12 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-14
+android.library=true
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/main-manifest.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/main-manifest.xml
new file mode 100644
index 0000000..5c50721
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/main-manifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="foo.master"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MasterProjectActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/main.properties b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/main.properties
new file mode 100644
index 0000000..f366140
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/main.properties
@@ -0,0 +1,12 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-14
+android.library.reference.1=../LibraryProject
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/strings.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/strings.xml
new file mode 100644
index 0000000..f53e15f
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiproject/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">LibraryProject</string>
+ <string name="string1">String 1</string>
+ <string name="string2">String 2</string>
+ <string name="string3">String 3</string>
+
+</resources>