@Test public void testIgnorePaths() throws IOException { ProjectFilesystem filesystem = EasyMock.createMock(ProjectFilesystem.class); EasyMock.expect(filesystem.getPathRelativizer()) .andReturn(Functions.<String>identity()) .times(2); BuildTargetParser parser = EasyMock.createMock(BuildTargetParser.class); EasyMock.replay(filesystem, parser); Reader reader = new StringReader( Joiner.on('\n').join("[project]", "ignore = .git, foo, bar/, baz//, a/b/c")); BuckConfig config = BuckConfig.createFromReader(reader, filesystem, parser, Platform.detect()); ImmutableSet<String> ignorePaths = config.getIgnorePaths(); assertEquals( "Should ignore paths, sans trailing slashes", ignorePaths, ImmutableSet.of( BuckConstant.BUCK_OUTPUT_DIRECTORY, ".idea", System.getProperty(BuckConfig.BUCK_BUCKD_DIR_KEY, ".buckd"), config.getCacheDir(), ".git", "foo", "bar", "baz", "a/b/c")); EasyMock.verify(filesystem, parser); }
@Test public void replaceLocationOfFullyQualifiedBuildTarget() { ProjectFilesystem filesystem = EasyMock.createNiceMock(ProjectFilesystem.class); EasyMock.expect(filesystem.getPathRelativizer()).andStubReturn(relativeToAbsolutePathFunction); EasyMock.replay(filesystem); BuildRuleResolver ruleResolver = new BuildRuleResolver(); JavaBinaryRule javaBinary = createSampleJavaBinaryRule(ruleResolver); String originalCmd = String.format( "$(location :%s) $(location %s) $OUT", javaBinary.getBuildTarget().getShortName(), javaBinary.getBuildTarget().getFullyQualifiedName()); String contextBasePath = javaBinary.getBuildTarget().getBasePath(); Set<? extends BuildRule> deps = ImmutableSet.of(javaBinary); Genrule rule = createGenrule(ruleResolver, originalCmd, contextBasePath, deps); AbstractGenruleStep genruleStep = rule.createGenruleStep(); // Interpolate the build target in the genrule cmd string. String transformedString = genruleStep.replaceMatches(filesystem, originalCmd); // Verify that the correct cmd was created. Path pathToOutput = getAbsolutePathInBase(GEN_DIR + "/java/com/facebook/util/ManifestGenerator.jar"); String expectedCmd = String.format("%s %s $OUT", pathToOutput, pathToOutput); assertEquals(expectedCmd, transformedString); EasyMock.verify(filesystem); }
@Test public void testAmendBuilder() throws NoSuchBuildTargetException { Map<BuildTarget, BuildRule> buildRuleIndex = Maps.newHashMap(); BuildRuleResolver ruleResolver = new BuildRuleResolver(buildRuleIndex); // Set up mocks. ProjectFilesystem projectFilesystem = EasyMock.createMock(ProjectFilesystem.class); EasyMock.expect(projectFilesystem.getAbsolutifier()) .andReturn(IdentityPathAbsolutifier.getIdentityAbsolutifier()); BuildTargetParser buildTargetParser = new BuildTargetParser(projectFilesystem) { @Override public BuildTarget parse(String buildTargetName, ParseContext parseContext) throws NoSuchBuildTargetException { return BuildTargetFactory.newInstance(buildTargetName); } }; Map<String, ?> instance = ImmutableMap.of( "vm_args", ImmutableList.of("-Dbuck.robolectric_dir=javatests/com/facebook/base"), "source_under_test", ImmutableList.of("//java/com/facebook/base:base")); BuildTarget buildTarget = BuildTargetFactory.newInstance("//javatests/com/facebook/base:base"); BuildFileTree buildFileTree = EasyMock.createMock(BuildFileTree.class); EasyMock.replay(projectFilesystem, buildFileTree); BuildRuleFactoryParams params = new BuildRuleFactoryParams( instance, projectFilesystem, buildFileTree, buildTargetParser, buildTarget, new FakeRuleKeyBuilderFactory()); // Create a builder using the factory. RobolectricTestBuildRuleFactory factory = new RobolectricTestBuildRuleFactory(); RobolectricTestRule.Builder builder = factory.newBuilder(new FakeAbstractBuildRuleBuilderParams()).setBuildTarget(buildTarget); // Invoke the method under test. factory.amendBuilder(builder, params); // Create a build rule using the builder. BuildRule base = new FakeJavaLibraryRule( BuildRuleType.ANDROID_LIBRARY, buildTarget, ImmutableSortedSet.<BuildRule>of(), ImmutableSet.<BuildTargetPattern>of()); buildRuleIndex.put(BuildTargetFactory.newInstance("//java/com/facebook/base:base"), base); RobolectricTestRule robolectricRule = (RobolectricTestRule) ruleResolver.buildAndAddToIndex(builder); // Verify the build rule built from the builder. assertEquals( ImmutableList.of("-Dbuck.robolectric_dir=javatests/com/facebook/base"), robolectricRule.getVmArgs()); assertEquals(ImmutableSet.of(base), robolectricRule.getSourceUnderTest()); EasyMock.verify(projectFilesystem, buildFileTree); }
/** * If the source paths specified contains one source path to a non-generated file then we should * return the correct source tmp corresponding to that non-generated source path. Especially when * the generated file comes first in the ordered set. */ @Test public void testMixedSourceFile() { String pathToGenFile = (GEN_DIR + "/com/facebook/GeneratedFile.java"); String pathToNonGenFile1 = ("package/src/SourceFile1.java"); String pathToNonGenFile2 = ("package/src-gen/SourceFile2.java"); ImmutableSortedSet<String> javaSrcs = ImmutableSortedSet.of(pathToGenFile, pathToNonGenFile1, pathToNonGenFile2); File parentFile1 = createMock(File.class); expect(parentFile1.getName()).andReturn("src"); expect(parentFile1.getPath()).andReturn("package/src"); File sourceFile1 = createMock(File.class); expect(sourceFile1.getParentFile()).andReturn(parentFile1); File parentFile2 = createMock(File.class); expect(parentFile2.getName()).andReturn("src"); expect(parentFile2.getPath()).andReturn("package/src-gen"); File sourceFile2 = createMock(File.class); expect(sourceFile2.getParentFile()).andReturn(parentFile2); DefaultJavaPackageFinder defaultJavaPackageFinder = createMock(DefaultJavaPackageFinder.class); expect(defaultJavaPackageFinder.getPathsFromRoot()).andReturn(pathsFromRoot).times(2); expect(defaultJavaPackageFinder.getPathElements()).andReturn(pathElements).times(2); ProjectFilesystem projectFilesystem = createMock(ProjectFilesystem.class); expect(projectFilesystem.getFileForRelativePath(pathToNonGenFile1)).andReturn(sourceFile1); expect(projectFilesystem.getFileForRelativePath(pathToNonGenFile2)).andReturn(sourceFile2); JavaLibraryRule javaLibraryRule = new FakeJavaLibraryRule(new BuildTarget("//foo", "bar")).setJavaSrcs(javaSrcs); Object[] mocks = new Object[] { parentFile1, sourceFile1, parentFile2, sourceFile2, defaultJavaPackageFinder, projectFilesystem }; replay(mocks); ImmutableSet<String> result = TestCommand.getPathToSourceFolders( javaLibraryRule, Optional.of(defaultJavaPackageFinder), projectFilesystem); assertEquals( "The non-generated source files are under two different source folders.", ImmutableSet.of("package/src-gen/", "package/src/"), result); verify(mocks); }
/** * Finds the build file responsible for the given {@link Path} and invalidates all of the cached * rules dependent on it. * * @param path A {@link Path} "contained" within the build file to find and invalidate. */ private void invalidateContainingBuildFile(Path path) throws IOException { String packageBuildFilePath = buildFileTreeCache .getInput() .getBasePathOfAncestorTarget( projectFilesystem.getProjectRoot().toPath().relativize(path).toString()); invalidateDependents( projectFilesystem .getFileForRelativePath(packageBuildFilePath + '/' + BuckConstant.BUILD_RULES_FILE_NAME) .toPath()); }
@Test public void testIgnorePathsWithRelativeCacheDir() throws IOException { ProjectFilesystem filesystem = EasyMock.createMock(ProjectFilesystem.class); EasyMock.expect(filesystem.getPathRelativizer()).andReturn(Functions.<String>identity()); BuildTargetParser parser = EasyMock.createMock(BuildTargetParser.class); EasyMock.replay(filesystem, parser); Reader reader = new StringReader(Joiner.on('\n').join("[cache]", "dir = cache_dir")); BuckConfig config = BuckConfig.createFromReader(reader, filesystem, parser, Platform.detect()); ImmutableSet<String> ignorePaths = config.getIgnorePaths(); assertTrue( "Relative cache directory should be in set of ignored paths", ignorePaths.contains("cache_dir")); EasyMock.verify(filesystem, parser); }
@Before public void newFakeFilesystem() { fakeFilesystem = EasyMock.createNiceMock(ProjectFilesystem.class); EasyMock.expect(fakeFilesystem.getPathRelativizer()) .andReturn(relativeToAbsolutePathFunction) .times(0, 1); EasyMock.replay(fakeFilesystem); }
public int createIntellijProject( File jsonTempFile, ProcessExecutor processExecutor, boolean generateMinimalProject, 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. ExitCodeAndOutput result = processJsonConfig(jsonTempFile, generateMinimalProject); if (result.exitCode != 0) { return result.exitCode; } else { // intellij.py writes the list of modified files to stdout, so parse stdout and add the // resulting file paths to the modifiedFiles list. Iterable<String> paths = Splitter.on('\n').trimResults().omitEmptyStrings().split(result.stdOut); Iterables.addAll(modifiedFiles, paths); } // Write out the project.properties files. List<String> modifiedPropertiesFiles = generateProjectDotPropertiesFiles(modules); modifiedFiles.addAll(modifiedPropertiesFiles); // Write out the .idea/compiler.xml file (the .idea/ directory is guaranteed to exist). CompilerXml compilerXml = new CompilerXml(modules); final String pathToCompilerXml = ".idea/compiler.xml"; File compilerXmlFile = projectFilesystem.getFileForRelativePath(pathToCompilerXml); if (compilerXml.write(compilerXmlFile)) { modifiedFiles.add(pathToCompilerXml); } // If the user specified a post-processing script, then run it. if (pathToPostProcessScript.isPresent()) { String pathToScript = pathToPostProcessScript.get(); Process process = Runtime.getRuntime().exec(new String[] {pathToScript}); ProcessExecutor.Result postProcessResult = processExecutor.execute(process); int postProcessExitCode = postProcessResult.getExitCode(); if (postProcessExitCode != 0) { return postProcessExitCode; } } // If any files have been modified by `buck project`, then list them for the user. if (!modifiedFiles.isEmpty()) { 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; }
@Override public String getExecutableCommand(ProjectFilesystem projectFilesystem) { Preconditions.checkState( mainClass != null, "Must specify a main class for %s in order to to run it.", getBuildTarget().getFullyQualifiedName()); return String.format( "java -jar %s", projectFilesystem.getPathRelativizer().apply(getOutputFile())); }
/** * Populates the collection of known build targets that this Parser will use to construct a * dependency graph using all build files inside the given project root and returns an optionally * filtered set of build targets. * * @param filesystem The project filesystem. * @param includes A list of files that should be included by each build file. * @param filter if specified, applied to each rule in rules. All matching rules will be included * in the List returned by this method. If filter is null, then this method returns null. * @return The build targets in the project filtered by the given filter. */ public synchronized List<BuildTarget> filterAllTargetsInProject( ProjectFilesystem filesystem, Iterable<String> includes, @Nullable RawRulePredicate filter) throws BuildFileParseException, BuildTargetException, IOException { Preconditions.checkNotNull(filesystem); Preconditions.checkNotNull(includes); if (!projectFilesystem.getProjectRoot().equals(filesystem.getProjectRoot())) { throw new HumanReadableException( String.format( "Unsupported root path change from %s to %s", projectFilesystem.getProjectRoot(), filesystem.getProjectRoot())); } if (!isCacheComplete(includes)) { knownBuildTargets.clear(); parsedBuildFiles.clear(); parseRawRulesInternal( ProjectBuildFileParser.getAllRulesInProject(buildFileParserFactory, includes)); allBuildFilesParsed = true; } return filterTargets(filter); }
@Test public void testConstructorThrowsNonExistentBasePath() throws IOException { Reader reader = new StringReader(Joiner.on('\n').join("[alias]", "katana = //java/com/example:fb4a")); ProjectFilesystem projectFilesystem = EasyMock.createMock(ProjectFilesystem.class); EasyMock.expect(projectFilesystem.exists("java/com/example")).andReturn(false); EasyMock.replay(projectFilesystem); try { BuildTargetParser parser = new BuildTargetParser(projectFilesystem); createWithDefaultFilesystem(reader, parser); fail("Should have thrown HumanReadableException."); } catch (HumanReadableException e) { assertEquals( "No directory java/com/example when resolving target //java/com/example:fb4a " + "in context FULLY_QUALIFIED", e.getHumanReadableErrorMessage()); } EasyMock.verify(projectFilesystem); }
@Test public void testNonObfuscatedBuild() throws IOException { Path proguardConfigFile = Paths.get("the/configuration.txt"); Path proguardMappingFile = Paths.get("the/mapping.txt"); SplitZipStep splitZipStep = new SplitZipStep( /* inputPathsToSplit */ ImmutableSet.<Path>of(), /* secondaryJarMetaPath */ Paths.get(""), /* primaryJarPath */ Paths.get(""), /* secondaryJarDir */ Paths.get(""), /* secondaryJarPattern */ "", /* proguardFullConfigFile */ Optional.of(proguardConfigFile), /* proguardMappingFile */ Optional.of(proguardMappingFile), /* primaryDexPatterns */ ImmutableSet.<String>of("primary"), /* primaryDexClassesFile */ Optional.<Path>absent(), ZipSplitter.DexSplitStrategy.MAXIMIZE_PRIMARY_DEX_SIZE, DexStore.JAR, /* pathToReportDir */ Paths.get(""), /* useLinearAllocSplitDex */ true, /* linearAllocHardLimit */ 4 * 1024 * 1024); ProjectFilesystem projectFilesystem = EasyMock.createMock(ProjectFilesystem.class); EasyMock.expect(projectFilesystem.readLines(proguardConfigFile)) .andReturn(ImmutableList.<String>of("-dontobfuscate")); ExecutionContext context = EasyMock.createMock(ExecutionContext.class); EasyMock.expect(context.getProjectFilesystem()).andReturn(projectFilesystem).anyTimes(); EasyMock.replay(projectFilesystem, context); Predicate<String> requiredInPrimaryZipPredicate = splitZipStep.createRequiredInPrimaryZipPredicate(context); assertTrue( "Primary class should be in primary.", requiredInPrimaryZipPredicate.apply("primary.class")); assertFalse( "Secondary class should be in secondary.", requiredInPrimaryZipPredicate.apply("secondary.class")); EasyMock.verify(projectFilesystem, context); }
/** * Called when file change events are posted to the file change EventBus to invalidate cached * build rules if required. */ @Subscribe public synchronized void onFileSystemChange(WatchEvent<?> event) throws IOException { if (console.getVerbosity() == Verbosity.ALL) { console .getStdErr() .printf( "Parser watched event %s %s\n", event.kind(), projectFilesystem.createContextString(event)); } if (projectFilesystem.isPathChangeEvent(event)) { Path path = (Path) event.context(); if (isPathCreateOrDeleteEvent(event)) { if (path.endsWith(BuckConstant.BUILD_RULES_FILE_NAME)) { // If a build file has been added or removed, reconstruct the build file tree. buildFileTreeCache.invalidate(); } // Added or removed files can affect globs, so invalidate the package build file // "containing" {@code path} unless its filename matches a temp file pattern. if (!isTempFile(path)) { invalidateContainingBuildFile(path); } } // Invalidate the raw rules and targets dependent on this file. invalidateDependents(path); } else { // Non-path change event, likely an overflow due to many change events: invalidate everything. buildFileTreeCache.invalidate(); invalidateCache(); } }
/** * If the source paths specified are all for non-generated files then we should return the correct * source tmp corresponding to a non-generated source path. */ @Test public void testNonGeneratedSourceFile() { String pathToNonGenFile = "package/src/SourceFile1.java"; assertFalse(JavaTestRule.isGeneratedFile(pathToNonGenFile)); ImmutableSortedSet<String> javaSrcs = ImmutableSortedSet.of(pathToNonGenFile); JavaLibraryRule javaLibraryRule = new FakeJavaLibraryRule(new BuildTarget("//foo", "bar")).setJavaSrcs(javaSrcs); File parentFile = createMock(File.class); expect(parentFile.getName()).andReturn("src"); expect(parentFile.getPath()).andReturn("package/src"); File sourceFile = createMock(File.class); expect(sourceFile.getParentFile()).andReturn(parentFile); DefaultJavaPackageFinder defaultJavaPackageFinder = createMock(DefaultJavaPackageFinder.class); expect(defaultJavaPackageFinder.getPathsFromRoot()).andReturn(pathsFromRoot); expect(defaultJavaPackageFinder.getPathElements()).andReturn(pathElements); ProjectFilesystem projectFilesystem = createMock(ProjectFilesystem.class); expect(projectFilesystem.getFileForRelativePath(pathToNonGenFile)).andReturn(sourceFile); Object[] mocks = new Object[] {parentFile, sourceFile, defaultJavaPackageFinder, projectFilesystem}; replay(mocks); ImmutableSet<String> result = TestCommand.getPathToSourceFolders( javaLibraryRule, Optional.of(defaultJavaPackageFinder), projectFilesystem); assertEquals( "All non-generated source files are under one source tmp.", ImmutableSet.of("package/src/"), result); verify(mocks); }
public int createIntellijProject(File jsonTempFile, PrintStream stdOut) 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); if (result.exitCode != 0) { return result.exitCode; } else { // intellij.py writes the list of modified files to stdout, so parse stdout and add the // resulting file paths to the modifiedFiles list. Iterable<String> paths = Splitter.on('\n').trimResults().omitEmptyStrings().split(result.stdOut); Iterables.addAll(modifiedFiles, paths); } // Write out the project.properties files. List<String> modifiedPropertiesFiles = generateProjectDotPropertiesFiles(modules); modifiedFiles.addAll(modifiedPropertiesFiles); // Write out the .idea/compiler.xml file (the .idea/ directory is guaranteed to exist). CompilerXml compilerXml = new CompilerXml(modules); final String pathToCompilerXml = ".idea/compiler.xml"; File compilerXmlFile = projectFilesystem.getFileForRelativePath(pathToCompilerXml); if (compilerXml.write(compilerXmlFile)) { modifiedFiles.add(pathToCompilerXml); } // If any files have been modified by `buck project`, then list them for the user. if (!modifiedFiles.isEmpty()) { SortedSet<String> modifiedFilesInSortedForder = Sets.newTreeSet(modifiedFiles); stdOut.printf("MODIFIED FILES:\n%s\n", Joiner.on('\n').join(modifiedFilesInSortedForder)); } return 0; }
@VisibleForTesting static void addRootExcludes( Module module, BuildRule buildRule, ProjectFilesystem projectFilesystem) { // If in the root of the project, specify ignored paths. if (buildRule != null && buildRule.getBuildTarget().getBasePathWithSlash().isEmpty()) { for (Path path : projectFilesystem.getIgnorePaths()) { // It turns out that ignoring all of buck-out causes problems in IntelliJ: it forces an // extra "modules" folder to appear at the top of the navigation pane that competes with the // ordinary file tree, making navigation a real pain. The hypothesis is that this is because // there are files in buck-out/gen and buck-out/android that IntelliJ freaks out about if it // cannot find them. Therefore, if "buck-out" is listed in the default list of paths to // ignore (which makes sense for other parts of Buck, such as Watchman), then we will ignore // only the appropriate subfolders of buck-out instead. if (BuckConstant.BUCK_OUTPUT_PATH.equals(path)) { addRootExclude(module, BuckConstant.BIN_PATH); addRootExclude(module, BuckConstant.LOG_PATH); } else { addRootExclude(module, path); } } module.isRootModule = true; } }
@Test public void testRequiredInPrimaryZipPredicate() throws IOException { Path primaryDexClassesFile = Paths.get("the/manifest.txt"); SplitZipStep splitZipStep = new SplitZipStep( /* inputPathsToSplit */ ImmutableSet.<Path>of(), /* secondaryJarMetaPath */ Paths.get(""), /* primaryJarPath */ Paths.get(""), /* secondaryJarDir */ Paths.get(""), /* secondaryJarPattern */ "", /* proguardFullConfigFile */ Optional.<Path>absent(), /* proguardMappingFile */ Optional.<Path>absent(), /* primaryDexPatterns */ ImmutableSet.of("List"), Optional.of(primaryDexClassesFile), ZipSplitter.DexSplitStrategy.MAXIMIZE_PRIMARY_DEX_SIZE, DexStore.JAR, /* pathToReportDir */ Paths.get(""), /* useLinearAllocSplitDex */ true, /* linearAllocHardLimit */ 4 * 1024 * 1024); List<String> linesInManifestFile = ImmutableList.of( "com/google/common/collect/ImmutableSortedSet", " com/google/common/collect/ImmutableSet", "# com/google/common/collect/ImmutableMap"); ProjectFilesystem projectFilesystem = EasyMock.createMock(ProjectFilesystem.class); EasyMock.expect(projectFilesystem.readLines(primaryDexClassesFile)) .andReturn(linesInManifestFile); ExecutionContext context = EasyMock.createMock(ExecutionContext.class); EasyMock.expect(context.getProjectFilesystem()).andReturn(projectFilesystem); EasyMock.replay(projectFilesystem, context); Predicate<String> requiredInPrimaryZipPredicate = splitZipStep.createRequiredInPrimaryZipPredicate(context); assertTrue( "All non-.class files should be accepted.", requiredInPrimaryZipPredicate.apply("apples.txt")); assertTrue( "com/google/common/collect/ImmutableSortedSet.class is listed in the manifest verbatim.", requiredInPrimaryZipPredicate.apply("com/google/common/collect/ImmutableSortedSet.class")); assertTrue( "com/google/common/collect/ImmutableSet.class is in the manifest with whitespace.", requiredInPrimaryZipPredicate.apply("com/google/common/collect/ImmutableSet.class")); assertFalse( "com/google/common/collect/ImmutableSet.class cannot have whitespace as param.", requiredInPrimaryZipPredicate.apply(" com/google/common/collect/ImmutableSet.class")); assertFalse( "com/google/common/collect/ImmutableMap.class is commented out.", requiredInPrimaryZipPredicate.apply("com/google/common/collect/ImmutableMap.class")); assertFalse( "com/google/common/collect/Iterables.class is not even mentioned.", requiredInPrimaryZipPredicate.apply("com/google/common/collect/Iterables.class")); assertTrue( "java/awt/List.class matches the substring 'List'.", requiredInPrimaryZipPredicate.apply("java/awt/List.class")); assertFalse( "Substring matching is case-sensitive.", requiredInPrimaryZipPredicate.apply("shiny/Glistener.class")); EasyMock.verify(projectFilesystem, context); }
public File getProjectRoot() { return projectFilesystem.getProjectRoot(); }
@Test public void testRequiredInPrimaryZipPredicateWithProguard() throws IOException { Path proguardConfigFile = Paths.get("the/configuration.txt"); Path proguardMappingFile = Paths.get("the/mapping.txt"); Path primaryDexClassesFile = Paths.get("the/manifest.txt"); SplitZipStep splitZipStep = new SplitZipStep( /* inputPathsToSplit */ ImmutableSet.<Path>of(), /* secondaryJarMetaPath */ Paths.get(""), /* primaryJarPath */ Paths.get(""), /* secondaryJarDir */ Paths.get(""), /* secondaryJarPattern */ "", /* proguardFullConfigFile */ Optional.of(proguardConfigFile), /* proguardMappingFile */ Optional.of(proguardMappingFile), /* primaryDexPatterns */ ImmutableSet.of("/primary/", "x/"), Optional.of(primaryDexClassesFile), ZipSplitter.DexSplitStrategy.MAXIMIZE_PRIMARY_DEX_SIZE, DexStore.JAR, /* pathToReportDir */ Paths.get(""), /* useLinearAllocSplitDex */ true, /* linearAllocHardLimit */ 4 * 1024 * 1024); List<String> linesInMappingFile = ImmutableList.of( "foo.bar.MappedPrimary -> foo.bar.a:", "foo.bar.MappedSecondary -> foo.bar.b:", "foo.bar.UnmappedPrimary -> foo.bar.UnmappedPrimary:", "foo.bar.UnmappedSecondary -> foo.bar.UnmappedSecondary:", "foo.primary.MappedPackage -> x.a:", "foo.secondary.MappedPackage -> x.b:", "foo.primary.UnmappedPackage -> foo.primary.UnmappedPackage:"); List<String> linesInManifestFile = ImmutableList.of( // Actual primary dex classes. "foo/bar/MappedPrimary", "foo/bar/UnmappedPrimary", // Red herrings! "foo/bar/b", "x/b"); ProjectFilesystem projectFilesystem = EasyMock.createMock(ProjectFilesystem.class); EasyMock.expect(projectFilesystem.readLines(primaryDexClassesFile)) .andReturn(linesInManifestFile); EasyMock.expect(projectFilesystem.readLines(proguardConfigFile)) .andReturn(ImmutableList.<String>of()); EasyMock.expect(projectFilesystem.readLines(proguardMappingFile)).andReturn(linesInMappingFile); ExecutionContext context = EasyMock.createMock(ExecutionContext.class); EasyMock.expect(context.getProjectFilesystem()).andReturn(projectFilesystem).anyTimes(); EasyMock.replay(projectFilesystem, context); Predicate<String> requiredInPrimaryZipPredicate = splitZipStep.createRequiredInPrimaryZipPredicate(context); assertTrue( "Mapped class from primary list should be in primary.", requiredInPrimaryZipPredicate.apply("foo/bar/a.class")); assertTrue( "Unmapped class from primary list should be in primary.", requiredInPrimaryZipPredicate.apply("foo/bar/UnmappedPrimary.class")); assertTrue( "Mapped class from substring should be in primary.", requiredInPrimaryZipPredicate.apply("x/a.class")); assertTrue( "Unmapped class from substring should be in primary.", requiredInPrimaryZipPredicate.apply("foo/primary/UnmappedPackage.class")); assertFalse( "Mapped class with obfuscated name match should not be in primary.", requiredInPrimaryZipPredicate.apply("foo/bar/b.class")); assertFalse( "Unmapped class name should not randomly be in primary.", requiredInPrimaryZipPredicate.apply("foo/bar/UnmappedSecondary.class")); assertFalse( "Map class with obfuscated name substring should not be in primary.", requiredInPrimaryZipPredicate.apply("x/b.class")); EasyMock.verify(projectFilesystem, context); }
/** * @param buildTargetName either a fully-qualified name or relative to the {@link ParseContext}. * For example, inside {@code first-party/orca/orcaapp/BUILD}, which can be obtained by * calling {@code ParseContext.forBaseName("first-party/orca/orcaapp")}, {@code * //first-party/orca/orcaapp:assets} and {@code :assets} refer to the same target. However, * from the command line the context is obtained by calling {@link * ParseContext#fullyQualified()} and relative names are not recognized. * @param parseContext how targets should be interpreted, such in the context of a specific build * file or only as fully-qualified names (as is the case for targets from the command line). */ public BuildTarget parse(String buildTargetName, ParseContext parseContext) throws NoSuchBuildTargetException { Preconditions.checkNotNull(buildTargetName); Preconditions.checkNotNull(parseContext); for (String invalidSubstring : INVALID_BUILD_RULE_SUBSTRINGS) { if (buildTargetName.contains(invalidSubstring)) { throw new BuildTargetParseException( String.format("%s cannot contain %s", buildTargetName, invalidSubstring)); } } if (buildTargetName.endsWith(BUILD_RULE_SEPARATOR) && parseContext.getType() != ParseContext.Type.VISIBILITY) { throw new BuildTargetParseException( String.format("%s cannot end with a colon", buildTargetName)); } List<String> parts = ImmutableList.copyOf(BUILD_RULE_SEPARATOR_SPLITTER.split(buildTargetName)); if (parts.size() != 2) { throw new BuildTargetParseException( String.format( "%s must contain exactly one colon (found %d)", buildTargetName, parts.size() - 1)); } String baseName = parts.get(0).isEmpty() ? parseContext.getBaseName() : parts.get(0); String shortName = parts.get(1); String fullyQualifiedName = String.format("%s:%s", baseName, shortName); if (!fullyQualifiedName.startsWith(BUILD_RULE_PREFIX)) { throw new BuildTargetParseException( String.format("%s must start with %s", fullyQualifiedName, BUILD_RULE_PREFIX)); } // Make sure the directory that contains the build file exists. String buildFileDirectory = baseName.substring(BUILD_RULE_PREFIX.length()); String buildFilePath = (buildFileDirectory.isEmpty() ? "" : buildFileDirectory + "/") + BUILD_RULES_FILE_NAME; if (!projectFilesystem.exists(buildFileDirectory)) { if (parseContext.getType() == ParseContext.Type.BUILD_FILE && baseName.equals(parseContext.getBaseName())) { throw new BuildTargetParseException( String.format( "Internal error: Parsing in the context of %s, but %s does not exist", buildFilePath, buildFileDirectory)); } else { throw NoSuchBuildTargetException.createForMissingDirectory( buildFileDirectory, buildTargetName, parseContext); } } // Make sure the build file exists. if (!projectFilesystem.exists(buildFilePath)) { if (parseContext.getType() == ParseContext.Type.BUILD_FILE && baseName.equals(parseContext.getBaseName())) { throw new BuildTargetParseException( String.format( "Internal error: Parsing in the context of %s, but %s does not exist", buildFilePath, buildFilePath)); } else { throw NoSuchBuildTargetException.createForMissingBuildFile( buildFilePath, buildTargetName, parseContext); } } return new BuildTarget(baseName, shortName); }
private boolean addSourceFolders( Module module, @Nullable BuildRule buildRule, @Nullable ImmutableList<SourceRoot> sourceRoots, boolean isTestSource) { if (buildRule == null || sourceRoots == null) { return false; } if (buildRule instanceof AndroidBinaryRule && sourceRoots.isEmpty()) { return false; } if (sourceRoots.isEmpty()) { // When there is a src_target, but no src_roots were specified, then the current directory is // treated as the SourceRoot. This is the common case when a project contains one folder of // Java source code with a build file for each Java package. For example, if the project's // only source folder were named "java/" and a build file in java/com/example/base/ contained // the an extremely simple set of build rules: // // java_library( // name = 'base', // srcs = glob(['*.java']), // } // // project_config( // src_target = ':base', // ) // // then the corresponding .iml file (in the same directory) should contain: // // <content url="file://$MODULE_DIR$"> // <sourceFolder url="file://$MODULE_DIR$" isTestSource="false" // packagePrefix="com.example.base" /> // <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" /> // // <!-- It will have an <excludeFolder> for every "subpackage" of com.example.base. --> // <excludeFolder url="file://$MODULE_DIR$/util" /> // </content> // // Note to prevent the <excludeFolder> elements from being included, the project_config() // rule should be: // // project_config( // src_target = ':base', // src_root_includes_subdirectories = True, // ) // // Because developers who organize their code this way will have many build files, the default // values of project_config() assume this approach to help minimize the tedium in writing all // of those project_config() rules. String url = "file://$MODULE_DIR$"; String packagePrefix = javaPackageFinder.findJavaPackageForPath(module.pathToImlFile); SourceFolder sourceFolder = new SourceFolder(url, isTestSource, packagePrefix); module.sourceFolders.add(sourceFolder); } else { for (SourceRoot sourceRoot : sourceRoots) { SourceFolder sourceFolder = new SourceFolder( String.format("file://$MODULE_DIR$/%s", sourceRoot.getName()), isTestSource); module.sourceFolders.add(sourceFolder); } } // Include <excludeFolder> elements, as appropriate. for (String relativePath : this.buildFileTree.getChildPaths(buildRule.getBuildTarget())) { String excludeFolderUrl = "file://$MODULE_DIR$/" + relativePath; SourceFolder excludeFolder = new SourceFolder(excludeFolderUrl, /* isTestSource */ false); module.excludeFolders.add(excludeFolder); } // If in the root of the project, specify ignored paths. if ("".equals(buildRule.getBuildTarget().getBasePathWithSlash())) { for (String path : projectFilesystem.getIgnorePaths()) { module.excludeFolders.add( new SourceFolder(String.format("file://$MODULE_DIR$/%s", path), false)); } } return true; }
private Module createModuleForProjectConfig(ProjectConfig projectConfig) throws IOException { BuildRule projectRule = projectConfig.getProjectRule(); Buildable buildable = projectRule.getBuildable(); Preconditions.checkState( projectRule instanceof JavaLibrary || buildable instanceof JavaLibrary || buildable instanceof JavaBinary || buildable instanceof AndroidLibrary || buildable instanceof AndroidResource || buildable instanceof AndroidBinary || buildable instanceof NdkLibrary, "project_config() does not know how to process a src_target of type %s.", projectRule.getType().getName()); LinkedHashSet<DependentModule> dependencies = Sets.newLinkedHashSet(); final BuildTarget target = projectConfig.getBuildTarget(); Module module = new Module(projectRule, target); module.name = getIntellijNameForRule(projectRule); module.isIntelliJPlugin = projectConfig.getIsIntelliJPlugin(); String relativePath = projectConfig.getBuildTarget().getBasePathWithSlash(); module.pathToImlFile = String.format("%s%s.iml", relativePath, module.name); // List the module source as the first dependency. boolean includeSourceFolder = true; // Do the tests before the sources so they appear earlier in the classpath. When tests are run, // their classpath entries may be deliberately shadowing production classpath entries. // tests folder boolean hasSourceFoldersForTestRule = addSourceFolders( module, projectConfig.getTestRule(), projectConfig.getTestsSourceRoots(), true /* isTestSource */); // test dependencies BuildRule testRule = projectConfig.getTestRule(); if (testRule != null) { walkRuleAndAdd(testRule, true /* isForTests */, dependencies, projectConfig.getSrcRule()); } // src folder boolean hasSourceFoldersForSrcRule = addSourceFolders( module, projectConfig.getSrcRule(), projectConfig.getSourceRoots(), false /* isTestSource */); addRootExcludes(module, projectConfig.getSrcRule(), projectFilesystem); // At least one of src or tests should contribute a source folder unless this is an // non-library Android project with no source roots specified. if (!hasSourceFoldersForTestRule && !hasSourceFoldersForSrcRule) { includeSourceFolder = false; } // IntelliJ expects all Android projects to have a gen/ folder, even if there is no src/ // directory specified. boolean isAndroidRule = projectRule.getProperties().is(ANDROID); if (isAndroidRule) { boolean hasSourceFolders = !module.sourceFolders.isEmpty(); module.sourceFolders.add(SourceFolder.GEN); if (!hasSourceFolders) { includeSourceFolder = true; } } // src dependencies // Note that isForTests is false even if projectRule is the project_config's test_target. walkRuleAndAdd(projectRule, false /* isForTests */, dependencies, projectConfig.getSrcRule()); String basePathWithSlash = projectConfig.getBuildTarget().getBasePathWithSlash(); // Specify another path for intellij to generate gen/ for each android module, // so that it will not disturb our glob() rules. // To specify the location of gen, Intellij requires the relative path from // the base path of current build target. module.moduleGenPath = generateRelativeGenPath(basePathWithSlash).toString(); DependentModule jdkDependency; if (isAndroidRule) { // android details if (projectRule.getBuildable() instanceof NdkLibrary) { NdkLibrary ndkLibrary = (NdkLibrary) projectRule.getBuildable(); module.isAndroidLibraryProject = true; module.keystorePath = null; module.nativeLibs = Paths.get(relativePath).relativize(ndkLibrary.getLibraryPath()).toString(); } else if (projectRule.getBuildable() instanceof AndroidResource) { AndroidResource androidResource = (AndroidResource) projectRule.getBuildable(); module.resFolder = createRelativePath(androidResource.getRes(), target); module.isAndroidLibraryProject = true; module.keystorePath = null; } else if (projectRule.getBuildable() instanceof AndroidBinary) { AndroidBinary androidBinary = (AndroidBinary) projectRule.getBuildable(); module.resFolder = null; module.isAndroidLibraryProject = false; KeystoreProperties keystoreProperties = KeystoreProperties.createFromPropertiesFile( androidBinary.getKeystore().getPathToStore(), androidBinary.getKeystore().getPathToPropertiesFile(), projectFilesystem); // getKeystore() returns a path relative to the project root, but an IntelliJ module // expects the path to the keystore to be relative to the module root. module.keystorePath = Paths.get(relativePath).relativize(keystoreProperties.getKeystore()).toString(); } else { module.isAndroidLibraryProject = true; module.keystorePath = null; } module.hasAndroidFacet = true; module.proguardConfigPath = null; // If there is a default AndroidManifest.xml specified in .buckconfig, use it if // AndroidManifest.xml is not present in the root of the [Android] IntelliJ module. if (pathToDefaultAndroidManifest.isPresent()) { String androidManifest = basePathWithSlash + "AndroidManifest.xml"; if (!projectFilesystem.exists(androidManifest)) { String manifestPath = this.pathToDefaultAndroidManifest.get(); String rootPrefix = "//"; Preconditions.checkState( manifestPath.startsWith(rootPrefix), "Currently, we expect this option to start with '%s', " + "indicating that it is relative to the root of the repository.", rootPrefix); manifestPath = manifestPath.substring(rootPrefix.length()); String relativePathToManifest = Paths.get(basePathWithSlash).relativize(Paths.get(manifestPath)).toString(); // IntelliJ requires that the path start with a slash to indicate that it is relative to // the module. module.androidManifest = "/" + relativePathToManifest; } } // List this last so that classes from modules can shadow classes in the JDK. jdkDependency = DependentModule.newInheritedJdk(); } else { module.hasAndroidFacet = false; if (module.isIntelliJPlugin()) { jdkDependency = DependentModule.newIntelliJPluginJdk(); } else { jdkDependency = DependentModule.newStandardJdk(); } } // Assign the dependencies. module.dependencies = createDependenciesInOrder(includeSourceFolder, dependencies, jdkDependency); // Annotation processing generates sources for IntelliJ to consume, but does so outside // the module directory to avoid messing up globbing. JavaLibrary javaLibrary = null; if (projectRule.getBuildable() instanceof JavaLibrary) { javaLibrary = (JavaLibrary) projectRule.getBuildable(); } else if (projectRule instanceof JavaLibrary) { javaLibrary = (JavaLibrary) projectRule; } if (javaLibrary != null) { AnnotationProcessingData processingData = javaLibrary.getAnnotationProcessingData(); Path annotationGenSrc = processingData.getGeneratedSourceFolderName(); if (annotationGenSrc != null) { module.annotationGenPath = "/" + Paths.get(basePathWithSlash).relativize(annotationGenSrc).toString(); module.annotationGenIsForTest = !hasSourceFoldersForSrcRule; } } return module; }