blob: b94786ca07309def360c76f466f2c2a2473ca5af [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.nbbuild;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Delete;
import org.apache.tools.ant.taskdefs.Javac;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.selectors.SelectorUtils;
import org.apache.tools.ant.util.JavaEnvUtils;
/**
* Could probably be replaced with a presetdef for javac,
* and a separate task for {@link #cleanUpStaleClasses}.
*/
public class CustomJavac extends Javac {
public CustomJavac() {}
private Path processorPath;
public void addProcessorPath(Path cp) {
processorPath = cp;
}
private boolean usingExplicitIncludes;
@Override
public void setIncludes(String includes) {
super.setIncludes(includes);
usingExplicitIncludes = true;
}
private File generatedClassesDir;
private String maybeFork;
@Override public void setFork(boolean f) {
super.setFork(f);
}
@Override public void setExecutable(String forkExec) {
maybeFork = forkExec;
}
@Override
public void execute() throws BuildException {
String tgr = getTarget();
if (tgr.matches("\\d+")) {
tgr = "1." + tgr;
}
if (!isBootclasspathOptionUsed()) {
setRelease(tgr.substring(2));
}
String src = getSource();
if (src.matches("\\d+")) {
src = "1." + src;
}
if (!JavaEnvUtils.isAtLeastJavaVersion(src)) {
log("Cannot handle -source " + src + " from this VM; forking " + maybeFork, Project.MSG_WARN);
super.setFork(true);
super.setExecutable(maybeFork);
}
generatedClassesDir = new File(getDestdir().getParentFile(), getDestdir().getName() + "-generated");
if (!usingExplicitIncludes) {
cleanUpStaleClasses();
}
cleanUpDependDebris();
super.execute();
}
@Override
protected void compile() {
if (processorPath != null && processorPath.size() > 0) {
createCompilerArg().setValue("-processorpath");
createCompilerArg().setPath(processorPath);
}
createCompilerArg().setValue("-implicit:class");
if (generatedClassesDir.isDirectory() || generatedClassesDir.mkdirs()) {
createCompilerArg().setValue("-s");
createCompilerArg().setFile(generatedClassesDir);
if (generatedClassesDir.isDirectory()) {
createSrc().setLocation(generatedClassesDir);
}
} else {
log("Warning: could not create " + generatedClassesDir, Project.MSG_WARN);
}
super.compile();
}
private boolean isBootclasspathOptionUsed() {
for (String arg : getCurrentCompilerArgs()) {
if (arg.contains("-Xbootclasspath")) {
return true;
}
}
return false;
}
/**
* See issue #166888. If there are any existing class files with no matching
* source file, assume this is an incremental build and the source file has
* since been deleted. In that case, delete the whole classes dir. (Could
* merely delete the stale class file, but if an annotation processor also
* created associated resources, these may be stale too. Kill them all and
* let JSR 269 sort it out.)
*/
private void cleanUpStaleClasses() {
File d = getDestdir();
if (!d.isDirectory()) {
return;
}
List<File> sources = new ArrayList<>();
for (String s : getSrcdir().list()) {
sources.add(new File(s));
}
if (generatedClassesDir.isDirectory()) {
sources.add(generatedClassesDir);
}
FileSet classes = new FileSet();
classes.setDir(d);
classes.setIncludes("**/*.class");
classes.setExcludes("**/*$*.class");
String startTimeProp = getProject().getProperty("module.build.started.time");
Date startTime;
try {
startTime = startTimeProp != null ? new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").parse(startTimeProp) : null;
} catch (ParseException x) {
throw new BuildException(x);
}
for (String clazz : classes.getDirectoryScanner(getProject()).getIncludedFiles()) {
if (startTime != null && new File(d, clazz).lastModified() > startTime.getTime()) {
// Ignore recently created classes. Hack to get contrib/j2ee.blueprints and the like
// to build; these generate classes and resources before main compilation step.
continue;
}
String java = clazz.substring(0, clazz.length() - ".class".length()) + ".java";
boolean found = false;
for (File source : sources) {
if (new File(source, java).isFile()) {
found = true;
break;
}
}
if (!found) {
// XXX might be a false negative in case this was a nonpublic outer class
// (could check for "ClassName.java" in bytecode to see)
log(new File(d, clazz) + " appears to be stale, rebuilding all module sources", Project.MSG_WARN);
Delete delete = new Delete();
delete.setProject(getProject());
delete.setOwningTarget(getOwningTarget());
delete.setLocation(getLocation());
FileSet deletables = new FileSet();
deletables.setDir(d);
delete.addFileset(deletables);
delete.init();
delete.execute();
break;
}
}
}
@Override
public void setErrorProperty(String errorProperty) {
throw new UnsupportedOperationException();
}
@Override
public void setUpdatedProperty(String updatedProperty) {
throw new UnsupportedOperationException();
}
@Override
public void setFailonerror(boolean fail) {
throw new UnsupportedOperationException();
}
/**
* See issue #196556. Sometimes {@code <depend>} leaves behind individual
* class files for nested classes when their enclosing classes do not exist.
* This can cause javac to try to read the nested classes and fail.
*/
private void cleanUpDependDebris() {
File d = getDestdir();
if (!d.isDirectory()) {
return;
}
FileSet classes = new FileSet();
classes.setDir(d);
classes.setIncludes("**/*$*.class");
final String whiteListRaw = getProject().getProperty("nbjavac.ignore.missing.enclosing"); //NOI18N
final String[] whiteList = whiteListRaw == null ? new String[0] : whiteListRaw.split("\\s*,\\s*"); //NOI18N
for (String clazz : classes.getDirectoryScanner(getProject()).getIncludedFiles()) {
if (isIgnored(whiteList, clazz)) {
log(clazz + " ignored from the enclosing check due to ignore list", Project.MSG_VERBOSE);
continue;
}
int i = clazz.indexOf('$');
File enclosing = new File(d, clazz.substring(0, i) + ".class");
if (!enclosing.isFile()) {
File enclosed = new File(d, clazz);
log(clazz + " will be deleted since " + enclosing.getName() + " is missing", Project.MSG_VERBOSE);
if (!enclosed.delete()) {
throw new BuildException("could not delete " + enclosed, getLocation());
}
}
}
}
private static boolean isIgnored(
final String[] patterns,
final String resource) {
for (String pattern : patterns) {
if (SelectorUtils.match(pattern, resource)) {
return true;
}
}
return false;
}
}