Add ability to specify target for project.
Summary: First bit of slices.
diff --git a/src/com/facebook/buck/cli/ProjectCommand.java b/src/com/facebook/buck/cli/ProjectCommand.java
index 55b4b43..008232e 100644
--- a/src/com/facebook/buck/cli/ProjectCommand.java
+++ b/src/com/facebook/buck/cli/ProjectCommand.java
@@ -91,7 +91,8 @@
exitCode = createIntellijProject(project,
tempFile,
executionContext.getProcessExecutor(),
- console.getStdOut());
+ console.getStdOut(),
+ console.getStdErr());
if (exitCode != 0) {
return exitCode;
}
@@ -133,9 +134,10 @@
int createIntellijProject(Project project,
File jsonTemplate,
ProcessExecutor processExecutor,
- PrintStream stdOut)
+ PrintStream stdOut,
+ PrintStream stdErr)
throws IOException {
- return project.createIntellijProject(jsonTemplate, processExecutor, stdOut);
+ return project.createIntellijProject(jsonTemplate, processExecutor, stdOut, stdErr);
}
/**
@@ -152,12 +154,26 @@
@VisibleForTesting
PartialGraph createPartialGraph(RawRulePredicate rulePredicate, ProjectCommandOptions options)
throws BuildFileParseException, BuildTargetException, IOException {
- return PartialGraph.createPartialGraph(
- rulePredicate,
- getProjectFilesystem(),
- options.getDefaultIncludes(),
- getParser(),
- getBuckEventBus());
+ List<String> argumentsAsBuildTargets = options.getArgumentsFormattedAsBuildTargets();
+
+ if (argumentsAsBuildTargets.isEmpty()) {
+ return PartialGraph.createPartialGraph(
+ rulePredicate,
+ getProjectFilesystem(),
+ options.getDefaultIncludes(),
+ getParser(),
+ getBuckEventBus());
+ } else {
+ // If build targets were specified, generate a partial intellij project that contains the
+ // files needed to build the build targets specified.
+ ImmutableList<BuildTarget> targets = getBuildTargets(argumentsAsBuildTargets);
+
+ return PartialGraph.createPartialGraphFromRoots(targets,
+ rulePredicate,
+ options.getDefaultIncludes(),
+ getParser(),
+ getBuckEventBus());
+ }
}
@Override
diff --git a/src/com/facebook/buck/cli/ProjectCommandOptions.java b/src/com/facebook/buck/cli/ProjectCommandOptions.java
index 1ec546c..78ab8a3 100644
--- a/src/com/facebook/buck/cli/ProjectCommandOptions.java
+++ b/src/com/facebook/buck/cli/ProjectCommandOptions.java
@@ -23,6 +23,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
+import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import java.util.List;
@@ -33,10 +34,25 @@
private String target;
+ @Argument
+ private List<String> arguments = Lists.newArrayList();
+
ProjectCommandOptions(BuckConfig buckConfig) {
super(buckConfig);
}
+ public List<String> getArguments() {
+ return arguments;
+ }
+
+ public void setArguments(List<String> arguments) {
+ this.arguments = arguments;
+ }
+
+ public List<String> getArgumentsFormattedAsBuildTargets() {
+ return getCommandLineBuildTargetNormalizer().normalizeAll(getArguments());
+ }
+
public String getTarget() {
return target;
}
diff --git a/src/com/facebook/buck/command/Project.java b/src/com/facebook/buck/command/Project.java
index 6e43b18..1cc14cc 100644
--- a/src/com/facebook/buck/command/Project.java
+++ b/src/com/facebook/buck/command/Project.java
@@ -48,6 +48,7 @@
import com.facebook.buck.util.KeystoreProperties;
import com.facebook.buck.util.ProcessExecutor;
import com.facebook.buck.util.ProjectFilesystem;
+import com.facebook.buck.util.Verbosity;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -160,14 +161,15 @@
public int createIntellijProject(File jsonTempFile,
ProcessExecutor processExecutor,
- PrintStream stdOut) throws IOException {
+ PrintStream stdOut,
+ PrintStream stdErr) throws IOException {
List<Module> modules = createModulesForProjectConfigs();
writeJsonConfig(jsonTempFile, modules);
List<String> modifiedFiles = Lists.newArrayList();
// Process the JSON config to generate the .xml and .iml files for IntelliJ.
- ExitCodeAndStdOut result = processJsonConfig(jsonTempFile);
+ ExitCodeAndOutput result = processJsonConfig(jsonTempFile);
if (result.exitCode != 0) {
return result.exitCode;
} else {
@@ -202,6 +204,8 @@
SortedSet<String> modifiedFilesInSortedForder = Sets.newTreeSet(modifiedFiles);
stdOut.printf("MODIFIED FILES:\n%s\n", Joiner.on('\n').join(modifiedFilesInSortedForder));
}
+ // Blit stderr from intellij.py to parent stderr.
+ stdErr.print(result.stdErr);
return 0;
}
@@ -905,7 +909,7 @@
}
}
- private ExitCodeAndStdOut processJsonConfig(File jsonTempFile) throws IOException {
+ private ExitCodeAndOutput processJsonConfig(File jsonTempFile) throws IOException {
final ImmutableList<String> args = ImmutableList.of(
pythonInterpreter, PATH_TO_INTELLIJ_PY, jsonTempFile.getAbsolutePath());
@@ -925,7 +929,7 @@
Console console = executionContext.getConsole();
Console childConsole = new Console(
- console.getVerbosity(),
+ Verbosity.SILENT,
console.getStdOut(),
console.getStdErr(),
Ansi.withoutTty());
@@ -934,15 +938,17 @@
.setConsole(childConsole)
.build();
int exitCode = command.execute(childContext);
- return new ExitCodeAndStdOut(exitCode, command.getStdout());
+ return new ExitCodeAndOutput(exitCode, command.getStdout(), command.getStderr());
}
- private static class ExitCodeAndStdOut {
+ private static class ExitCodeAndOutput {
private final int exitCode;
private final String stdOut;
- ExitCodeAndStdOut(int exitCode, String stdOut) {
+ private final String stdErr;
+ ExitCodeAndOutput(int exitCode, String stdOut, String stdErr) {
this.exitCode = exitCode;
this.stdOut = Preconditions.checkNotNull(stdOut);
+ this.stdErr = Preconditions.checkNotNull(stdErr);
}
}
diff --git a/src/com/facebook/buck/command/intellij.py b/src/com/facebook/buck/command/intellij.py
index 2c166da..c0d5185 100644
--- a/src/com/facebook/buck/command/intellij.py
+++ b/src/com/facebook/buck/command/intellij.py
@@ -1,6 +1,9 @@
import errno
+import fnmatch
import json
import os
+import re
+import subprocess
import sys
@@ -105,6 +108,9 @@
# and no files will need to be written.
MODIFIED_FILES = []
+# Files that are part of the project being run. We will delete all .iml files
+# that are not checked in and not in this set.
+PROJECT_FILES = set()
def write_modules(modules):
"""Writes one XML file for each module."""
@@ -348,6 +354,7 @@
def write_file_if_changed(path, content):
+ PROJECT_FILES.add(path)
if os.path.exists(path):
file_content_as_string = open(path, 'r').read()
needs_update = content.strip() != file_content_as_string.strip()
@@ -372,8 +379,31 @@
pass
else: raise
+def clean_old_files():
+ if os.path.isdir('.git'):
+ try:
+ files_to_clean = subprocess.check_output(['git', 'ls-files', '--other'])
+ for file_name in files_to_clean.splitlines():
+ if file_name.endswith('.iml') and file_name not in PROJECT_FILES:
+ os.remove(file_name)
+ return
+ except Exception as e:
+ pass
+
if __name__ == '__main__':
+ if not os.path.isdir('.git'):
+ for root, dirnames, filenames in os.walk('.'):
+ if fnmatch.filter(filenames, '*.iml'):
+ sys.stderr.write('\n'.join(
+ [ ' :: "buck project" run from a directory not under Git source',
+ ' :: control. If invoking buck project with an argument, we are',
+ ' :: not able to remove old .iml files, which can result in',
+ ' :: IntelliJ being in a bad state. Please close and re-open',
+ ' :: IntelliJ if it\'s open.' ]))
+ sys.stderr.flush()
+ break
+
json_file = sys.argv[1]
parsed_json = json.load(open(json_file, 'r'))
@@ -384,6 +414,8 @@
write_modules(modules)
write_all_modules(modules)
write_run_configs()
+ if PROJECT_FILES:
+ clean_old_files()
# Write the list of modified files to stdout
for path in MODIFIED_FILES: print path
diff --git a/src/com/facebook/buck/parser/Parser.java b/src/com/facebook/buck/parser/Parser.java
index 97c8f0b..740c7ac 100644
--- a/src/com/facebook/buck/parser/Parser.java
+++ b/src/com/facebook/buck/parser/Parser.java
@@ -605,6 +605,32 @@
return filterTargets(filter);
}
+
+ /**
+ * Takes a sequence of build targets and parses all of the build files that contain them and their
+ * transitive deps, producing a collection of "raw rules" that have been produced from the build
+ * files. The specified {@link RawRulePredicate} is applied to this collection. This method
+ * returns the collection of {@link BuildTarget}s that correspond to the raw rules that were
+ * matched by the predicate.
+ * <p>
+ * Because {@code project_config} rules are not transitive dependencies of rules such as
+ * {@code android_binary}, but are defined in the same build files as the transitive
+ * dependencies of an {@code android_binary}, this method is helpful in finding all of the
+ * {@code project_config} rules needed to produce an IDE configuration to build said
+ * {@code android_binary}. See {@link ProjectCommand#predicate} for an example of such
+ * a {@link RawRulePredicate}.
+ */
+ public synchronized List<BuildTarget> filterTargetsInProjectFromRoots(
+ Iterable<BuildTarget> roots,
+ Iterable<String> defaultIncludes,
+ BuckEventBus eventBus,
+ RawRulePredicate filter)
+ throws BuildFileParseException, BuildTargetException, IOException {
+ parseBuildFilesForTargets(roots, defaultIncludes, eventBus);
+
+ return filterTargets(filter);
+ }
+
/**
* Called when file change events are posted to the file change EventBus to invalidate cached
* build rules if required.
diff --git a/src/com/facebook/buck/parser/PartialGraph.java b/src/com/facebook/buck/parser/PartialGraph.java
index d37275e..f05262b 100644
--- a/src/com/facebook/buck/parser/PartialGraph.java
+++ b/src/com/facebook/buck/parser/PartialGraph.java
@@ -69,15 +69,44 @@
Iterable<String> includes,
Parser parser,
BuckEventBus eventBus) throws BuildTargetException, BuildFileParseException, IOException {
-
- Preconditions.checkNotNull(parser);
+ Preconditions.checkNotNull(predicate);
List<BuildTarget> targets = parser.filterAllTargetsInProject(filesystem, includes, predicate);
+ return parseAndCreateGraphFromTargets(targets, includes, parser, eventBus);
+ }
+
+ /**
+ * Creates a partial graph of all {@link BuildRule}s that are transitive dependencies of (rules
+ * that pass {@code predicate} and are contained in BUCK files that contain transitive
+ * dependencies of the {@link BuildTarget}s defined in {@code roots}).
+ */
+ public static PartialGraph createPartialGraphFromRoots(
+ Iterable<BuildTarget> roots,
+ RawRulePredicate predicate,
+ Iterable<String> includes,
+ Parser parser,
+ BuckEventBus eventBus) throws BuildTargetException, BuildFileParseException, IOException {
+ Preconditions.checkNotNull(predicate);
+
+ List<BuildTarget> targets = parser.filterTargetsInProjectFromRoots(
+ roots, includes, eventBus, predicate);
+
+ return parseAndCreateGraphFromTargets(targets, includes, parser, eventBus);
+ }
+
+ private static PartialGraph parseAndCreateGraphFromTargets(
+ Iterable<BuildTarget> targets,
+ Iterable<String> includes,
+ Parser parser,
+ BuckEventBus eventBus) throws BuildTargetException, BuildFileParseException, IOException {
+
+ Preconditions.checkNotNull(parser);
+
// Now that the Parser is loaded up with the set of all build rules, use it to create a
// DependencyGraph of only the targets we want to build.
DependencyGraph graph = parser.parseBuildFilesForTargets(targets, includes, eventBus);
- return new PartialGraph(graph, targets);
+ return new PartialGraph(graph, ImmutableList.copyOf(targets));
}
}
diff --git a/test/com/facebook/buck/cli/ProjectCommandTest.java b/test/com/facebook/buck/cli/ProjectCommandTest.java
index 4602a7d..a235e25 100644
--- a/test/com/facebook/buck/cli/ProjectCommandTest.java
+++ b/test/com/facebook/buck/cli/ProjectCommandTest.java
@@ -188,12 +188,14 @@
int createIntellijProject(Project project,
File jsonTemplate,
ProcessExecutor processExecutor,
- PrintStream stdOut)
+ PrintStream stdOut,
+ PrintStream stdErr)
throws IOException {
assertNotNull(project);
assertNotNull(jsonTemplate);
assertNotNull(processExecutor);
assertNotNull(stdOut);
+ assertNotNull(stdErr);
return 0;
}
diff --git a/test/com/facebook/buck/cli/ProjectIntegrationTest.java b/test/com/facebook/buck/cli/ProjectIntegrationTest.java
index ec55e3f..8457953 100644
--- a/test/com/facebook/buck/cli/ProjectIntegrationTest.java
+++ b/test/com/facebook/buck/cli/ProjectIntegrationTest.java
@@ -16,8 +16,10 @@
package com.facebook.buck.cli;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
-
+import static org.junit.Assert.assertThat;
import com.facebook.buck.testutil.integration.DebuggableTemporaryFolder;
import com.facebook.buck.testutil.integration.ProjectWorkspace;
import com.facebook.buck.testutil.integration.ProjectWorkspace.ProcessResult;
@@ -62,6 +64,57 @@
"modules/tip/module_modules_tip.iml"
) + '\n',
result.getStdout());
+
+ assertThat(
+ "`buck project` should contain warning about being run from directory without git.",
+ result.getStderr(),
+ not(containsString(Joiner.on('\n').join(
+ " :: \"buck project\" run from a directory not under Git source",
+ " :: control. If invoking buck project with an argument, we are",
+ " :: not able to remove old .iml files, which can result in",
+ " :: IntelliJ being in a bad state. Please close and re-open",
+ " :: IntelliJ if it's open."))));
+ }
+
+ /**
+ * Verify that if we build a project by specifying a target, the resulting project only contains
+ * the transitive deps of that target. In this example, that means everything except
+ * //modules/tip.
+ */
+ @Test
+ public void testBuckProjectSlice() throws IOException {
+ ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(
+ this, "project_slice", temporaryFolder);
+ workspace.setUp();
+
+ ProcessResult result = workspace.runBuckCommand("project", "//modules/dep1:dep1");
+ result.assertExitCode("buck project should exit cleanly", 0);
+
+ workspace.verify();
+
+ assertEquals(
+ "`buck project` should report the files it modified.",
+ Joiner.on('\n').join(
+ "MODIFIED FILES:",
+ ".idea/compiler.xml",
+ ".idea/libraries/libs_guava_jar.xml",
+ ".idea/libraries/libs_jsr305_jar.xml",
+ ".idea/libraries/libs_junit_jar.xml",
+ ".idea/modules.xml",
+ ".idea/runConfigurations/Debug_Buck_test.xml",
+ "modules/dep1/module_modules_dep1.iml"
+ ) + '\n',
+ result.getStdout());
+
+ assertThat(
+ "`buck project` should contain warning about being run from directory without git.",
+ result.getStderr(),
+ containsString(Joiner.on('\n').join(
+ " :: \"buck project\" run from a directory not under Git source",
+ " :: control. If invoking buck project with an argument, we are",
+ " :: not able to remove old .iml files, which can result in",
+ " :: IntelliJ being in a bad state. Please close and re-open",
+ " :: IntelliJ if it's open.")));
}
/**
diff --git a/test/com/facebook/buck/cli/testdata/project_slice/.buckconfig b/test/com/facebook/buck/cli/testdata/project_slice/.buckconfig
new file mode 100644
index 0000000..23528f4
--- /dev/null
+++ b/test/com/facebook/buck/cli/testdata/project_slice/.buckconfig
@@ -0,0 +1,2 @@
+[color]
+ ui = false
diff --git a/test/com/facebook/buck/cli/testdata/project_slice/.idea/modules.xml.expected b/test/com/facebook/buck/cli/testdata/project_slice/.idea/modules.xml.expected
new file mode 100644
index 0000000..0724339
--- /dev/null
+++ b/test/com/facebook/buck/cli/testdata/project_slice/.idea/modules.xml.expected
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/modules/dep1/module_modules_dep1.iml" filepath="$PROJECT_DIR$/modules/dep1/module_modules_dep1.iml" />
+ </modules>
+ </component>
+</project>
diff --git a/test/com/facebook/buck/cli/testdata/project_slice/libs/BUCK b/test/com/facebook/buck/cli/testdata/project_slice/libs/BUCK
new file mode 100644
index 0000000..5e53ca8
--- /dev/null
+++ b/test/com/facebook/buck/cli/testdata/project_slice/libs/BUCK
@@ -0,0 +1,23 @@
+prebuilt_jar(
+ name = 'guava',
+ binary_jar = 'guava.jar',
+ visibility = [
+ 'PUBLIC',
+ ],
+)
+
+prebuilt_jar(
+ name = 'jsr305',
+ binary_jar = 'jsr305.jar',
+ visibility = [
+ 'PUBLIC',
+ ],
+)
+
+prebuilt_jar(
+ name = 'junit',
+ binary_jar = 'junit.jar',
+ visibility = [
+ 'PUBLIC',
+ ],
+)
diff --git a/test/com/facebook/buck/cli/testdata/project_slice/libs/guava.jar b/test/com/facebook/buck/cli/testdata/project_slice/libs/guava.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/com/facebook/buck/cli/testdata/project_slice/libs/guava.jar
diff --git a/test/com/facebook/buck/cli/testdata/project_slice/libs/jsr305.jar b/test/com/facebook/buck/cli/testdata/project_slice/libs/jsr305.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/com/facebook/buck/cli/testdata/project_slice/libs/jsr305.jar
diff --git a/test/com/facebook/buck/cli/testdata/project_slice/libs/junit.jar b/test/com/facebook/buck/cli/testdata/project_slice/libs/junit.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/com/facebook/buck/cli/testdata/project_slice/libs/junit.jar
diff --git a/test/com/facebook/buck/cli/testdata/project_slice/modules/dep1/BUCK b/test/com/facebook/buck/cli/testdata/project_slice/modules/dep1/BUCK
new file mode 100644
index 0000000..32dc7e2
--- /dev/null
+++ b/test/com/facebook/buck/cli/testdata/project_slice/modules/dep1/BUCK
@@ -0,0 +1,28 @@
+android_library(
+ name = 'dep1',
+ srcs = glob(['src/**/*.java']),
+ deps = [
+ '//libs:guava',
+ '//libs:jsr305',
+ ],
+ visibility = [
+ 'PUBLIC',
+ ],
+)
+
+java_test(
+ name = 'test',
+ srcs = glob(['test/**/*Test.java']),
+ deps = [
+ ':dep1',
+ '//libs:guava',
+ '//libs:junit',
+ ],
+)
+
+project_config(
+ src_target = ':dep1',
+ src_roots = [ 'src' ],
+ test_target = ':test',
+ test_roots = [ 'test' ],
+)
diff --git a/test/com/facebook/buck/cli/testdata/project_slice/modules/dep1/module_modules_dep1.iml.expected b/test/com/facebook/buck/cli/testdata/project_slice/modules/dep1/module_modules_dep1.iml.expected
new file mode 100644
index 0000000..66d9ff0
--- /dev/null
+++ b/test/com/facebook/buck/cli/testdata/project_slice/modules/dep1/module_modules_dep1.iml.expected
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="android" name="Android">
+ <configuration>
+ <option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/../../buck-out/android/modules/dep1/gen" />
+ <option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/../../buck-out/android/modules/dep1/gen" />
+ <option name="MANIFEST_FILE_RELATIVE_PATH" value="/AndroidManifest.xml" />
+ <option name="RES_FOLDER_RELATIVE_PATH" value="/res" />
+ <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/assets" />
+ <option name="LIBS_FOLDER_RELATIVE_PATH" value="/libs" />
+ <option name="USE_CUSTOM_APK_RESOURCE_FOLDER" value="false" />
+ <option name="CUSTOM_APK_RESOURCE_FOLDER" value="" />
+ <option name="USE_CUSTOM_COMPILER_MANIFEST" value="false" />
+ <option name="CUSTOM_COMPILER_MANIFEST" value="" />
+ <option name="APK_PATH" value="" />
+ <option name="LIBRARY_PROJECT" value="true" />
+ <option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="true" />
+ <option name="GENERATE_UNSIGNED_APK" value="false" />
+ <option name="CUSTOM_DEBUG_KEYSTORE_PATH" value="" />
+ <option name="PACK_TEST_CODE" value="false" />
+ <option name="RUN_PROGUARD" value="false" />
+ <option name="PROGUARD_CFG_PATH" value="/proguard.cfg" />
+ <resOverlayFolders />
+ <includeSystemProguardFile>false</includeSystemProguardFile>
+ <includeAssetsFromLibraries>true</includeAssetsFromLibraries>
+ <additionalNativeLibs />
+ </configuration>
+ </facet>
+ </component>
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <content url="file://$MODULE_DIR$/../../buck-out/android/modules/dep1/gen">
+ <sourceFolder url="file://$MODULE_DIR$/../../buck-out/android/modules/dep1/gen" isTestSource="false" />
+ </content>
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" exported="" scope="TEST" name="libs_junit_jar" level="project" />
+ <orderEntry type="library" exported="" name="libs_guava_jar" level="project" />
+ <orderEntry type="library" exported="" name="libs_jsr305_jar" level="project" />
+ <orderEntry type="inheritedJdk" />
+ </component>
+</module>
diff --git a/test/com/facebook/buck/cli/testdata/project_slice/modules/tip/BUCK b/test/com/facebook/buck/cli/testdata/project_slice/modules/tip/BUCK
new file mode 100644
index 0000000..3dba30b
--- /dev/null
+++ b/test/com/facebook/buck/cli/testdata/project_slice/modules/tip/BUCK
@@ -0,0 +1,27 @@
+android_library(
+ name = 'tip',
+ srcs = glob(['src/**/*.java']),
+ deps = [
+ '//libs:guava',
+ '//libs:jsr305',
+ '//modules/dep1:dep1',
+ ],
+)
+
+java_test(
+ name = 'test',
+ srcs = glob(['test/**/*Test.java']),
+ deps = [
+ ':tip',
+ '//libs:guava',
+ '//libs:jsr305',
+ '//libs:junit',
+ ],
+)
+
+project_config(
+ src_target = ':tip',
+ src_roots = [ 'src' ],
+ test_target = ':test',
+ test_roots = [ 'test' ],
+)
diff --git a/test/com/facebook/buck/cli/testdata/project_slice/modules/tip/module_modules_tip.iml b/test/com/facebook/buck/cli/testdata/project_slice/modules/tip/module_modules_tip.iml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/com/facebook/buck/cli/testdata/project_slice/modules/tip/module_modules_tip.iml