blob: 5732f8e1fb468b376e67295895abab841a71df9e [file] [log] [blame]
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.bazel.repository;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.bazel.repository.cache.RepositoryCache;
import com.google.devtools.build.lib.bazel.repository.cache.RepositoryCache.KeyType;
import com.google.devtools.build.lib.bazel.repository.downloader.HttpDownloader;
import com.google.devtools.build.lib.rules.repository.WorkspaceAttributeMapper;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.vfs.Path;
import java.io.IOException;
import java.util.Map;
import java.util.StringJoiner;
import javax.annotation.Nullable;
import org.apache.maven.settings.Server;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.repository.AuthenticationContext;
import org.eclipse.aether.repository.AuthenticationDigest;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
/**
* Downloader for JAR files from Maven repositories.
* TODO(jingwen): standardize interface between this and HttpDownloader
*/
public class MavenDownloader extends HttpDownloader {
public MavenDownloader(RepositoryCache repositoryCache) {
super(repositoryCache);
}
/**
* Download the Maven artifact to the output directory. Returns the path to the jar (and the
* srcjar if available).
*/
public JarPaths download(String name, WorkspaceAttributeMapper mapper, Path outputDirectory,
MavenServerValue serverValue) throws IOException, EvalException {
String url = serverValue.getUrl();
Server server = serverValue.getServer();
// Initialize maven artifacts
Artifact artifact;
String artifactCoords = mapper.get("artifact", Type.STRING);
String sha1 =
mapper.isAttributeValueExplicitlySpecified("sha1") ? mapper.get("sha1", Type.STRING) : null;
if (sha1 != null && !KeyType.SHA1.isValid(sha1)) {
throw new IOException("Invalid SHA-1 for maven_jar " + name + ": '" + sha1 + "'");
}
try {
artifact = new DefaultArtifact(artifactCoords);
} catch (IllegalArgumentException e) {
throw new IOException(e.getMessage());
}
boolean isCaching = repositoryCache.isEnabled() && KeyType.SHA1.isValid(sha1);
if (isCaching) {
Path downloadPath = getDownloadDestination(outputDirectory, artifact);
Path cachedDestination = repositoryCache.get(sha1, downloadPath, KeyType.SHA1);
if (cachedDestination != null) {
return new JarPaths(cachedDestination, Optional.absent());
}
}
// Setup env for fetching jars
MavenConnector connector = new MavenConnector(outputDirectory.getPathString());
RepositorySystem system = connector.newRepositorySystem();
RepositorySystemSession session = connector.newRepositorySystemSession(system);
RemoteRepository repository =
new RemoteRepository.Builder(name, MavenServerValue.DEFAULT_ID, url)
.setAuthentication(new MavenAuthentication(server))
.build();
// Try fetching jar.
final Path jarDownload;
try {
artifact = downloadArtifact(artifact, repository, session, system);
} catch (ArtifactResolutionException e) {
throw new IOException("Failed to fetch Maven dependency: " + e.getMessage());
}
// Try also fetching srcjar.
Artifact artifactWithSrcs = srcjarCoords(artifact);
try {
artifactWithSrcs = downloadArtifact(artifactWithSrcs, repository, session, system);
} catch (ArtifactResolutionException e) {
// Intentionally ignored - missing srcjar is not an error.
}
jarDownload = outputDirectory.getRelative(artifact.getFile().getAbsolutePath());
// Verify checksum.
if (!Strings.isNullOrEmpty(sha1)) {
RepositoryCache.assertFileChecksum(sha1, jarDownload, KeyType.SHA1);
}
if (isCaching) {
repositoryCache.put(sha1, jarDownload, KeyType.SHA1);
}
if (artifactWithSrcs.getFile() != null) {
Path srcjarDownload =
outputDirectory.getRelative(artifactWithSrcs.getFile().getAbsolutePath());
return new JarPaths(jarDownload, Optional.fromNullable(srcjarDownload));
} else {
return new JarPaths(jarDownload, Optional.absent());
}
}
private Path getDownloadDestination(Path outputDirectory, Artifact artifact) {
String groupIdPath = artifact.getGroupId().replace('.', '/');
String artifactId = artifact.getArtifactId();
String version = artifact.getVersion();
String filename = artifactId + '-' + version + '.' + artifact.getExtension();
StringJoiner joiner = new StringJoiner("/");
joiner.add(groupIdPath).add(artifactId).add(version).add(filename);
return outputDirectory.getRelative(joiner.toString());
}
private Artifact srcjarCoords(Artifact jar) {
return new DefaultArtifact(
jar.getGroupId(), jar.getArtifactId(), "sources", jar.getExtension(), jar.getVersion());
}
/*
* Set up request for and resolve (retrieve to local repo) artifact
*/
private Artifact downloadArtifact(
Artifact artifact,
RemoteRepository repository,
RepositorySystemSession session,
RepositorySystem system)
throws ArtifactResolutionException {
ArtifactRequest artifactRequest = new ArtifactRequest();
artifactRequest.setArtifact(artifact);
artifactRequest.setRepositories(ImmutableList.of(repository));
ArtifactResult artifactResult = system.resolveArtifact(session, artifactRequest);
return artifactResult.getArtifact();
}
/*
* Class for packaging srcjar and jar paths together when srcjar is available.
*/
static class JarPaths {
final Path jar;
@Nullable final Optional<Path> srcjar;
private JarPaths(Path jar, Optional<Path> srcjar) {
this.jar = jar;
this.srcjar = srcjar;
}
}
private static class MavenAuthentication implements Authentication {
private final Map<String, String> authenticationInfo;
private MavenAuthentication(Server server) {
ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder();
// From https://maven.apache.org/settings.html: "If you use a private key to login to the
// server, make sure you omit the <password> element. Otherwise, the key will be ignored."
if (server.getPassword() != null) {
builder.put(AuthenticationContext.USERNAME, server.getUsername());
builder.put(AuthenticationContext.PASSWORD, server.getPassword());
} else if (server.getPrivateKey() != null) {
// getPrivateKey sounds like it returns the key, but it actually returns a path to it.
builder.put(AuthenticationContext.PRIVATE_KEY_PATH, server.getPrivateKey());
builder.put(AuthenticationContext.PRIVATE_KEY_PASSPHRASE, server.getPassphrase());
}
authenticationInfo = builder.build();
}
@Override
public void fill(
AuthenticationContext authenticationContext, String s, Map<String, String> map) {
for (Map.Entry<String, String> entry : authenticationInfo.entrySet()) {
authenticationContext.put(entry.getKey(), entry.getValue());
}
}
@Override
public void digest(AuthenticationDigest authenticationDigest) {
// No-op.
}
}
}