blob: e2e0cb5893cc5fffee0b4913b631dfdb7d5a161a [file] [log] [blame]
/*
* Copyright 2013-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.java;
import com.facebook.buck.event.ThrowableLogEvent;
import com.facebook.buck.java.classes.ClasspathTraversal;
import com.facebook.buck.java.classes.DefaultClasspathTraverser;
import com.facebook.buck.java.classes.FileLike;
import com.facebook.buck.step.AbstractExecutionStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
import com.google.common.io.InputSupplier;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;
/**
* {@link Step} that takes a directory or zip of {@code .class} files and traverses it to get the
* total set of {@code .class} files included by the directory or zip.
*/
public class AccumulateClassNamesStep extends AbstractExecutionStep
implements Supplier<ImmutableSortedMap<String, HashCode>> {
/**
* In the generated {@code classes.txt} file, each line will contain the path to a {@code .class}
* file (without its suffix) and the SHA-1 hash of its contents, separated by this separator.
*/
static final String CLASS_NAME_HASH_CODE_SEPARATOR = " ";
/**
* Entries in the {@link #pathToJarOrClassesDirectory} that end with this suffix will be written
* to {@link #whereClassNamesShouldBeWritten}. Since they all share the same suffix, the suffix
* will be stripped when written to {@link #whereClassNamesShouldBeWritten}.
*/
private static final String CLASS_NAME_SUFFIX = ".class";
private final Path pathToJarOrClassesDirectory;
private final Path whereClassNamesShouldBeWritten;
/**
* Map of names of {@code .class} files to hashes of their contents.
* <p>
* This is not set until this step is executed.
*/
@Nullable
private ImmutableSortedMap<String, HashCode> classNames;
public AccumulateClassNamesStep(Path pathToJarOrClassesDirectory,
Path whereClassNamesShouldBeWritten) {
super("get_class_names " + pathToJarOrClassesDirectory + " > " + whereClassNamesShouldBeWritten);
this.pathToJarOrClassesDirectory = Preconditions.checkNotNull(pathToJarOrClassesDirectory);
this.whereClassNamesShouldBeWritten = Preconditions.checkNotNull(whereClassNamesShouldBeWritten);
}
@Override
public int execute(ExecutionContext context) {
final ImmutableSortedMap.Builder<String, HashCode> classNamesBuilder =
ImmutableSortedMap.naturalOrder();
Path path = context.getProjectFilesystem().resolve(pathToJarOrClassesDirectory);
ClasspathTraversal traversal = new ClasspathTraversal(Collections.singleton(path)) {
@Override
public void visit(final FileLike fileLike) throws IOException {
String name = fileLike.getRelativePath();
// When traversing a JAR file, it may have resources or directory entries that do not end
// in .class, which should be ignored.
if (name.endsWith(CLASS_NAME_SUFFIX)) {
String key = name.substring(0, name.length() - CLASS_NAME_SUFFIX.length());
InputSupplier<InputStream> input = new InputSupplier<InputStream>() {
@Override
public InputStream getInput() throws IOException {
return fileLike.getInput();
}
};
HashCode value = ByteStreams.hash(input, Hashing.sha1());
classNamesBuilder.put(key, value);
}
}
};
try {
new DefaultClasspathTraverser().traverse(traversal);
} catch (IOException e) {
e.printStackTrace(context.getStdErr());
return 1;
}
ImmutableSortedMap<String, HashCode> classNames = classNamesBuilder.build();
try {
context.getProjectFilesystem().writeLinesToPath(
Iterables.transform(classNames.entrySet(),
new Function<Map.Entry<String, HashCode>, String>() {
@Override
public String apply(Entry<String, HashCode> entry) {
return entry.getKey() + CLASS_NAME_HASH_CODE_SEPARATOR + entry.getValue();
}
}),
whereClassNamesShouldBeWritten);
} catch (IOException e) {
context.getBuckEventBus().post(ThrowableLogEvent.create(e,
"There was an error writing the list of .class files to %s.",
whereClassNamesShouldBeWritten));
return 1;
}
this.classNames = classNames;
return 0;
}
@Override
public ImmutableSortedMap<String, HashCode> get() {
Preconditions.checkNotNull(classNames,
"Step must be executed successfully before invoking this method.");
return classNames;
}
}