/*
* Copyright 2015-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.maven;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.facebook.buck.cli.BuckConfig;
import com.facebook.buck.cli.FakeBuckConfig;
import com.facebook.buck.event.BuckEventBusFactory;
import com.facebook.buck.file.ExplodingDownloader;
import com.facebook.buck.file.RemoteFileDescription;
import com.facebook.buck.io.ExecutableFinder;
import com.facebook.buck.io.MorePaths;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.json.BuildFileParseException;
import com.facebook.buck.json.ProjectBuildFileParser;
import com.facebook.buck.json.ProjectBuildFileParserOptions;
import com.facebook.buck.jvm.java.PrebuiltJarDescription;
import com.facebook.buck.parser.ParserConfig;
import com.facebook.buck.python.PythonBuckConfig;
import com.facebook.buck.rules.Description;
import com.facebook.buck.rules.coercer.DefaultTypeCoercerFactory;
import com.facebook.buck.testutil.FakeProjectFilesystem;
import com.facebook.buck.testutil.TestConsole;
import com.facebook.buck.testutil.integration.HttpdForTests;
import com.facebook.buck.testutil.integration.TemporaryPaths;
import com.facebook.buck.testutil.integration.TestDataHelper;
import com.facebook.buck.util.DefaultProcessExecutor;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.aether.RepositoryException;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
public class ResolverIntegrationTest {
@Rule public TemporaryPaths temp = new TemporaryPaths();
private static HttpdForTests httpd;
private static ProjectBuildFileParser buildFileParser;
private static Path repo;
private Path buckRepoRoot;
private Path thirdParty;
private Path thirdPartyRelative;
private Path localRepo;
@BeforeClass
public static void setUpFakeMavenRepo() throws Exception {
repo = TestDataHelper.getTestDataDirectory(ResolverIntegrationTest.class);
httpd = new HttpdForTests();
httpd.addHandler(new HttpdForTests.FileDispenserRequestHandler(repo));
httpd.start();
}
@AfterClass
public static void shutDownHttpd() throws Exception {
httpd.close();
}
@BeforeClass
public static void createParser() {
ProjectFilesystem filesystem = new FakeProjectFilesystem();
BuckConfig buckConfig = FakeBuckConfig.builder().build();
ParserConfig parserConfig = buckConfig.getView(ParserConfig.class);
PythonBuckConfig pythonBuckConfig = new PythonBuckConfig(buckConfig, new ExecutableFinder());
ImmutableSet<Description<?>> descriptions =
ImmutableSet.of(
new RemoteFileDescription(new ExplodingDownloader()), new PrebuiltJarDescription());
buildFileParser =
new ProjectBuildFileParser(
ProjectBuildFileParserOptions.builder()
.setProjectRoot(filesystem.getRootPath())
.setPythonInterpreter(pythonBuckConfig.getPythonInterpreter())
.setAllowEmptyGlobs(parserConfig.getAllowEmptyGlobs())
.setIgnorePaths(filesystem.getIgnorePaths())
.setBuildFileName(parserConfig.getBuildFileName())
.setDefaultIncludes(parserConfig.getDefaultIncludes())
.setDescriptions(descriptions)
.setBuildFileImportWhitelist(parserConfig.getBuildFileImportWhitelist())
.build(),
new DefaultTypeCoercerFactory(),
ImmutableMap.of(),
BuckEventBusFactory.newInstance(),
new DefaultProcessExecutor(new TestConsole()));
}
@AfterClass
public static void closeParser()
throws BuildFileParseException, InterruptedException, IOException {
buildFileParser.close();
}
@Before
public void setUpRepos() throws Exception {
buckRepoRoot = temp.newFolder();
thirdPartyRelative = Paths.get("third-party").resolve("java");
thirdParty = buckRepoRoot.resolve(thirdPartyRelative);
localRepo = temp.newFolder();
}
private ArtifactConfig newConfig() {
ArtifactConfig config = new ArtifactConfig();
config.buckRepoRoot = buckRepoRoot.toString();
config.thirdParty = thirdPartyRelative.toString();
config.mavenLocalRepo = localRepo.toString();
return config;
}
private void resolveWithArtifacts(String... artifacts)
throws URISyntaxException, IOException, RepositoryException, ExecutionException,
InterruptedException {
ArtifactConfig config = newConfig();
config.repositories.add(new ArtifactConfig.Repository(httpd.getUri("/").toString()));
config.artifacts.addAll(Arrays.asList(artifacts));
config.visibility.add("PUBLIC");
new Resolver(config).resolve(config.artifacts);
}
@Test
public void shouldResolveTransitiveDependencyAndIncludeLibraryOnlyOnce() throws Exception {
resolveWithArtifacts("com.example:A-depends-on-B-and-C:jar:1.0");
Path groupDir = thirdParty.resolve("example");
assertTrue(Files.exists(groupDir));
assertTrue(Files.exists(groupDir.resolve("D-depends-on-none-2.0.jar")));
assertFalse(Files.exists(groupDir.resolve("D-depends-on-none-1.0.jar")));
}
@Test
public void shouldResolveTransitiveDependencyAndIncludeLibraryOnlyOnceViaPom() throws Exception {
resolveWithArtifacts(
repo.resolve("com/example/A-depends-on-B-and-C/1.0/A-depends-on-B-and-C-1.0.pom")
.toString());
Path groupDir = thirdParty.resolve("example");
assertTrue(Files.exists(groupDir));
assertTrue(Files.exists(groupDir.resolve("D-depends-on-none-2.0.jar")));
assertFalse(Files.exists(groupDir.resolve("D-depends-on-none-1.0.jar")));
}
@Test
public void shouldSetUpAPrivateLibraryIfGivenAMavenCoordWithoutDeps() throws Exception {
resolveWithArtifacts("com.example:no-deps:jar:1.0");
Path groupDir = thirdParty.resolve("example");
assertTrue(Files.exists(groupDir));
Path original = repo.resolve("com/example/no-deps/1.0/no-deps-1.0.jar");
HashCode expected = MorePaths.asByteSource(original).hash(Hashing.sha1());
Path jarFile = groupDir.resolve("no-deps-1.0.jar");
HashCode seen = MorePaths.asByteSource(jarFile).hash(Hashing.sha1());
assertEquals(expected, seen);
List<Map<String, Object>> rules =
buildFileParser.getAll(groupDir.resolve("BUCK"), new AtomicLong());
assertEquals(1, rules.size());
Map<String, Object> rule = rules.get(0);
// Name is derived from the project identifier
assertEquals("no-deps", rule.get("name"));
// The binary jar should be set
assertEquals("no-deps-1.0.jar", rule.get("binaryJar"));
// There was no source jar in the repo
assertNull(rule.get("sourceJar"));
// It's a library that's requested on the CLI, so it gets full visibility.
assertEquals(ImmutableList.of("PUBLIC"), rule.get("visibility"));
// And it doesn't depend on anything
assertNull(rule.get("deps"));
}
@Test
public void shouldIncludeSourceJarIfOneIsPresent() throws Exception {
resolveWithArtifacts("com.example:with-sources:jar:1.0");
Path groupDir = thirdParty.resolve("example");
List<Map<String, Object>> rules =
buildFileParser.getAll(groupDir.resolve("BUCK"), new AtomicLong());
Map<String, Object> rule = rules.get(0);
assertEquals("with-sources-1.0-sources.jar", rule.get("sourceJar"));
}
@Test
public void shouldSetVisibilityOfTargetToGiveDependenciesAccess() throws Exception {
resolveWithArtifacts("com.example:with-deps:jar:1.0");
Path exampleDir = thirdPartyRelative.resolve("example");
Map<String, Object> withDeps =
buildFileParser
.getAll(buckRepoRoot.resolve(exampleDir).resolve("BUCK"), new AtomicLong())
.get(0);
Path otherDir = thirdPartyRelative.resolve("othercorp");
Map<String, Object> noDeps =
buildFileParser
.getAll(buckRepoRoot.resolve(otherDir).resolve("BUCK"), new AtomicLong())
.get(0);
@SuppressWarnings("unchecked")
List<String> visibility = (List<String>) noDeps.get("visibility");
assertEquals(1, visibility.size());
assertEquals(
ImmutableList.of(
String.format("//%s:with-deps", MorePaths.pathWithUnixSeparators(exampleDir))),
visibility);
assertNull(noDeps.get("deps"));
assertEquals(ImmutableList.of("PUBLIC"), withDeps.get("visibility"));
@SuppressWarnings("unchecked")
List<String> deps = (List<String>) withDeps.get("deps");
assertEquals(1, deps.size());
assertEquals(
ImmutableList.of(String.format("//%s:no-deps", MorePaths.pathWithUnixSeparators(otherDir))),
deps);
}
@Test
public void shouldOmitTargetsInTheSameBuildFileInVisibilityArguments() throws Exception {
resolveWithArtifacts("com.example:deps-in-same-project:jar:1.0");
Path exampleDir = thirdPartyRelative.resolve("example");
List<Map<String, Object>> allTargets =
buildFileParser.getAll(buckRepoRoot.resolve(exampleDir).resolve("BUCK"), new AtomicLong());
assertEquals(2, allTargets.size());
Map<String, Object> noDeps = null;
for (Map<String, Object> target : allTargets) {
if ("no-deps".equals(target.get("name"))) {
noDeps = target;
break;
}
}
assertNotNull(noDeps);
// Although the "deps-in-same-project" could be in the visibility param, it doesn't need to be
// because it's declared in the same build file.
assertNull(noDeps.get("visibility"));
}
@Test
public void shouldNotDownloadOlderJar() throws Exception {
Path existingNewerJar = thirdParty.resolve("example/no-deps-1.1.jar");
Files.createDirectories(existingNewerJar.getParent());
Files.copy(repo.resolve("com/example/no-deps/1.0/no-deps-1.0.jar"), existingNewerJar);
Path groupDir = thirdParty.resolve("example");
Path repoOlderJar = groupDir.resolve("no-deps-1.0.jar");
assertFalse(Files.exists(repoOlderJar));
resolveWithArtifacts("com.example:no-deps:jar:1.0");
assertTrue(Files.exists(groupDir));
// assert newer jar is in the third-party dir
assertTrue(Files.exists(existingNewerJar));
assertFalse(Files.exists(repoOlderJar));
// assert BUCK file was created
assertTrue(Files.exists(groupDir.resolve("BUCK")));
}
@Test
public void shouldDetectNewestJar() throws Exception {
Path groupDir = thirdParty.resolve("example");
Path existingNewerJar = groupDir.resolve("no-deps-1.1.jar");
Path existingNewestJar = groupDir.resolve("no-deps-1.2.jar");
Files.createDirectories(groupDir);
Path sourceJar = repo.resolve("com/example/no-deps/1.0/no-deps-1.0.jar");
Files.copy(sourceJar, existingNewerJar);
Files.copy(sourceJar, existingNewestJar);
Artifact artifact = new DefaultArtifact("com.example", "no-deps", "jar", "1.0");
Optional<Path> result = new Resolver(newConfig()).getNewerVersionFile(artifact, groupDir);
assertTrue(result.isPresent());
assertThat(result.get(), equalTo(existingNewestJar));
}
}