blob: 03b7a3eba44a8c78fc022b87f06ef9ec7edce3a7 [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.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
// XXX should use DOM, not text manipulation
/**
* Moves classpath from properties to project.xml
* @author pzajac
*/
public class FixTestDependencies extends Task {
/** entries for unit testing in order to avoid scanning modules
*/
Set<ModuleListParser.Entry> cachedEntries ;
/** Creates a new instance of FixTestClassPath */
public FixTestDependencies() {
}
String cnb;
File projectXmlFile;
public void setProjectXml(File projectXml) {
projectXmlFile = projectXml;
}
File propertiesFile;
public void setPropertiesFile(File properties) {
this.propertiesFile = properties;
}
public void execute() throws BuildException {
// read project xml
try {
// test mode doesn't override project.xml
boolean testFix = getProject().getProperty("test.fix.dependencies") != null;
if (projectXmlFile == null || !projectXmlFile.isFile()) {
throw new BuildException("project.xml file doesn't exist.");
}
byte xmlBytes [] = new byte[(int)projectXmlFile.length()];
try (FileInputStream prjFis = new FileInputStream(projectXmlFile)) {
prjFis.read(xmlBytes);
}
String xml = new String(xmlBytes);
String oldXsd = "<data xmlns=\"http://www.netbeans.org/ns/nb-module-project/2";
int xsdIndex = xml.indexOf(oldXsd);
if (xsdIndex == -1) {
xsdIndex = xml.indexOf("<data xmlns=\"http://www.netbeans.org/ns/nb-module-project/3");
}
if (xsdIndex != -1 || testFix) {
// increase schema version
String part1 = xml.substring(0,xsdIndex + oldXsd.length() - 1);
String part2 = xml.substring(xsdIndex + oldXsd.length(), xml.length());
xml = part1 + "3" + part2;
ModuleType projectType = ModuleType.NB_ORG;
if (xml.contains("<suite-component/>")) {
projectType = ModuleType.SUITE;
} else if (xml.contains("<standalone/>")) {
projectType = ModuleType.STANDALONE;
}
//grrr
int typeStart = xml.indexOf("<code-name-base>");
int typeEnd = xml.indexOf("</code-name-base>");
if (typeStart <= 0 || typeEnd <= 0 || typeEnd <= typeStart) {
throw new BuildException("Parsing of project.xml failed.");
}
cnb = xml.substring(typeStart + "<code-name-base>".length(), typeEnd).trim();
if (cnb.length() <= 0) {
throw new BuildException("Invalid codename base:" + cnb);
}
final boolean td = xml.contains("<test-dependencies>");
// test if project.xml contains test-deps
if (td && !testFix) {
// yes -> exit
xml = fixOpenideUtil(fixCoreStartup(xml));
try (PrintStream ps = new PrintStream(projectXmlFile)) {
ps.print(xml);
}
return ;
}
Set<ModuleListParser.Entry> entries = getModuleList(projectType);
Set<String> allCnbs = getCNBsFromEntries(entries);
// read properties
// remove modules and test from properties and put it to project.xml
// unittest
//
Set<String> compileCNB = new TreeSet<>();
Set<String> compileTestCNB = new TreeSet<>();
Set<String> runtimeCNB = new TreeSet<>();
Set<String> runtimeTestCNB = new TreeSet<>();
Properties projectProperties = getTestProperties();
boolean found;
found = readCodeNameBases(compileCNB,compileTestCNB,projectProperties,"test.unit.cp",allCnbs,entries);
found |= readCodeNameBases(compileCNB,compileTestCNB,projectProperties,"test.unit.cp.extra",allCnbs,entries);
found |= readCodeNameBases(runtimeCNB,runtimeTestCNB,projectProperties,"test.unit.run.cp",allCnbs,entries);
found |= readCodeNameBases(runtimeCNB,runtimeTestCNB,projectProperties,"test.unit.run.cp.extra",allCnbs,entries);
if (!found && !td) {
return;
}
updateProperties(projectProperties,new String[]{"test.unit.cp","test.unit.cp.extra","test.unit.run.cp","test.unit.run.cp.extra"});
StringWriter writer = new StringWriter();
PrintWriter buffer = new PrintWriter(writer);
buffer.println("");
buffer.println(" <test-dependencies>");
buffer.println(" <test-type>");
buffer.println(" <name>unit</name>");
addDependency(buffer,cnb,true,true,false);
runtimeCNB.removeAll(compileCNB);
//compileCNB.removeAll(runtimeCNB);
compileCNB.addAll(compileTestCNB);
runtimeTestCNB.removeAll(compileTestCNB);
runtimeCNB.addAll(runtimeTestCNB);
addDependencies(buffer,compileCNB,compileTestCNB,true,false);
addDependencies(buffer,runtimeCNB,runtimeTestCNB,false,false);
buffer.println(" </test-type>");
// qa functional tests
compileCNB.clear();
runtimeCNB.clear();
compileTestCNB.clear();
runtimeTestCNB.clear();
readCodeNameBases(compileCNB,compileTestCNB,projectProperties,"test.qa-functional.cp",allCnbs,entries);
readCodeNameBases(compileCNB,compileTestCNB,projectProperties,"test.qa-functional.cp.extra",allCnbs,entries);
readCodeNameBases(runtimeCNB,runtimeTestCNB,projectProperties,"test.qa-functional.runtime.cp",allCnbs,entries);
readCodeNameBases(runtimeCNB,runtimeTestCNB,projectProperties,"test.qa-functional.runtime.extra",allCnbs,entries);
if (!compileTestCNB.isEmpty() || !compileCNB.isEmpty() || !runtimeTestCNB.isEmpty() || !runtimeCNB.isEmpty()) {
buffer.println(" <test-type>");
buffer.println(" <name>qa-functional</name>");
addDependencies(buffer,compileCNB,compileTestCNB,true,false);
addDependencies(buffer,runtimeCNB,runtimeTestCNB,false,false);
buffer.println(" </test-type>");
}
buffer.println(" </test-dependencies>");
updateProperties(projectProperties,new String[]{"test.qa-functional.cp","test.qa-functional.cp","test.qa-functional.runtime.cp","test.qa-functional.runtime.extra"});
// merge project properties
String MODULE_DEP_END = "</module-dependencies>";
int moduleDepEnd = xml.indexOf(MODULE_DEP_END);
if (moduleDepEnd == -1) {
MODULE_DEP_END = "<module-dependencies/>";
moduleDepEnd = xml.indexOf(MODULE_DEP_END);
}
if (moduleDepEnd == -1) {
throw new BuildException("No module dependency found.");
}
moduleDepEnd += MODULE_DEP_END.length();
StringBuffer resultXml = new StringBuffer();
resultXml.append(xml.substring(0,moduleDepEnd));
resultXml.append(writer.toString());
// windows
if (xml.charAt(moduleDepEnd) == '\r') {
moduleDepEnd++;
}
resultXml.append(xml.substring(moduleDepEnd + 1, xml.length()));
if (!testFix) {
try (PrintStream ps = new PrintStream(projectXmlFile)) {
ps.print(fixOpenideUtil(
fixCoreStartup(
resultXml.toString()
)));
}
} else {
System.out.println(resultXml);
}
}
} catch (IOException ex) {
throw new BuildException(ex);
}
// store project.properties and project.xml
}
private Set<ModuleListParser.Entry> getModuleList(final ModuleType projectType) throws IOException {
if (cachedEntries == null ) {
// scan for all modules
ModuleListParser listParser = new ModuleListParser(getProject().getProperties(), projectType, getProject());
return listParser.findAll();
} else {
// used by FixTestDependenciesTest
return cachedEntries;
}
}
private Set<String> getCNBsFromEntries(Set<ModuleListParser.Entry> entries) {
Set<String> cnbs = new HashSet<>();
for (ModuleListParser.Entry e : entries) {
cnbs.add(e.getCnb());
}
return cnbs;
}
/** parses all codenamebases from path
*/
boolean readCodeNameBases(Set<String> compileCNB,
Set <String> testsCNB,
Properties projectPropertis,
String property,
Set <String> allCnbs,
Set <ModuleListParser.Entry> entries) {
String prop = projectPropertis.getProperty(property);
StringBuffer newProp = new StringBuffer();
if (prop != null) {
StringTokenizer tokenizer = new StringTokenizer(prop,";:\\");
while(tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken().trim();
boolean found = false;
if (token.length() > 1) {
int lastSlash = token.lastIndexOf("/");
int lastDot = token.lastIndexOf(".");
// check if the file is module
//
// the boot.jar is org.netbeans.bootstrap
if (token.endsWith("lib/boot.jar")) {
compileCNB.add("org.netbeans.bootstrap");
found = true;
} else if (token.endsWith("core/core.jar")) {
compileCNB.add("org.netbeans.core.startup");
compileCNB.add("org.netbeans.core.startup.base");
found = true;
} else if (lastSlash != -1 && lastDot != -1 && lastSlash + 1< lastDot ) {
String codeBaseName = token.substring(lastSlash + 1, lastDot);
codeBaseName = codeBaseName.replace('-','.');
if (allCnbs.contains(codeBaseName)) {
compileCNB.add(codeBaseName);
found = true;
} else {
String name = token.substring(lastSlash + 1, token.length());
// check if the file is wrapped library
String wrapCNB = null;
for (ModuleListParser.Entry entry : entries) {
File extensions [] = entry.getClassPathExtensions();
if (extensions != null) {
for (File f : extensions) {
if (f.getPath().endsWith( name)) {
if (wrapCNB != null) {
// collision
found = false;
System.out.println("wrapped? " + entry.getCnb() + " -> " + token + " = " + f);
} else {
wrapCNB = entry.getCnb();
found = true;
}
}
}
}
}
if (found && wrapCNB != null && allCnbs.contains(wrapCNB)) {
compileCNB.add(wrapCNB);
}
}
// check if the dependency is dependency on test
} else {
int prjEnd = token.indexOf("/build/test/unit/class");
if (prjEnd != -1) {
// get the project folder
int firstSlash = token.indexOf("/");
if (firstSlash != -1 && firstSlash + 1 < prjEnd) {
String prjFolder = token.substring(firstSlash + 1, prjEnd);
String codebaseName = getCNBForFolder(prjFolder,entries);
if (codebaseName == null) {
log("No code name base found for file " + token);
} else {
testsCNB.add(codebaseName);
found = true;
}
}
}
}
// check if the file is file
//
if (found == false) {
if (newProp.length() > 0) {
newProp.append(":");
}
// windows platform
token = token.replace(File.separatorChar,'/');
newProp.append(token);
}
}
} // while
projectPropertis.setProperty(property,newProp.toString());
return true;
}
return false;
}
private void addDependencies(PrintWriter buffer, Set<String> moduleCNB, Set<String> testCNB, boolean compile, boolean recursive) {
for (String cnb : moduleCNB) {
addDependency(buffer,cnb,compile,recursive,testCNB.contains(cnb));
}
}
private void addDependency(PrintWriter buffer, String cnb, boolean compile, boolean recursive, boolean test) {
buffer.println(" <test-dependency>");
buffer.println(" <code-name-base>" + cnb + "</code-name-base>");
if (recursive) {
buffer.println(" <recursive/>");
}
if (compile) {
buffer.println(" <compile-dependency/>");
}
if (test) {
buffer.println(" <test/>");
}
buffer.println(" </test-dependency>");
}
private Properties getTestProperties() throws IOException {
if (propertiesFile == null || !propertiesFile.isFile()) {
return new Properties();
}
Properties props = new Properties();
try (FileInputStream fis = new FileInputStream(propertiesFile)) {
props.load(fis);
}
return props;
}
/** @return codeNameBase of project for relative folder prjFolder
*/
private String getCNBForFolder(String prjFolder, Set<ModuleListParser.Entry> entries) {
for (ModuleListParser.Entry elem : entries) {
if (prjFolder.equals(elem.getNetbeansOrgPath())) {
return elem.getCnb();
}
}
return null;
}
private void updateProperties(Properties projectProperties,String names[]) {
if (propertiesFile == null) {
return;
}
try {
List<String> lines = new ArrayList<>();
if (propertiesFile.isFile()) {
// read properties
try (BufferedReader reader = new BufferedReader (new FileReader(propertiesFile))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
}
// merge properties
for (String propName : names) {
String value = projectProperties.getProperty(propName);
lines = replaceProperty(propName,value,lines);
}
if (lines.isEmpty() && !propertiesFile.isFile()) {
return;
}
// store properties
try (PrintStream ps = new PrintStream(propertiesFile)) {
for (String l : lines) {
ps.println(l);
}
}
} catch (IOException ioe) {
throw new BuildException(ioe);
}
}
private List<String> replaceProperty(String name, String value, List<String> lines) {
List<String> retLines = new ArrayList<>();
for (int i = 0 ; i < lines.size() ; i++) {
String line = lines.get(i);
String trimmedLine = line.trim();
int eqIdx = trimmedLine.indexOf("=");
if (eqIdx != -1) {
String pName = line.substring(0,eqIdx).trim();
if (pName.equals(name)) {
// skip property
for (; i < lines.size() && lines.get(i).trim().endsWith("\\") ; i++) ;
// append new property
if (value != null && !value.trim().equals("")) {
retLines.add(name + "=" + value);
}
continue;
}
}
// either empty line, comment or other property
retLines.add(line);
}
return retLines;
}
private String fixCoreStartup(String xml) {
Pattern l = Pattern.compile(
"^ *<test-dependency>[^/]*" +
"<code-name-base>org.netbeans.core.startup.base</code-name-base>", Pattern.MULTILINE);
if (l.matcher(xml).find()) {
return xml;
}
Pattern p = Pattern.compile(
"^ *<test-dependency>[^/]*" +
"<code-name-base>org.netbeans.core.startup</code-name-base>", Pattern.MULTILINE);
Matcher m = p.matcher(xml);
if (m.find()) {
final String txt = "</test-dependency>";
final int s = m.start();
int end = xml.indexOf(txt, s);
if (end == -1) {
throw new BuildException("No end of dependency " + xml);
}
final int e = end + txt.length();
String dep = xml.substring(s, e);
return xml.substring(0, s) + dep + '\n' + dep.replace("org.netbeans.core.startup", "org.netbeans.core.startup.base") + xml.substring(e);
}
return xml;
}
private String fixOpenideUtil(String xml) {
Pattern l = Pattern.compile(
"^ *<test-dependency>[^/]*" +
"<code-name-base>org.openide.util.lookup</code-name-base>", Pattern.MULTILINE);
if (l.matcher(xml).find()) {
return xml;
}
Pattern p = Pattern.compile(
"^ *<test-dependency>[^/]*" +
"<code-name-base>org.openide.util</code-name-base>", Pattern.MULTILINE);
Matcher m = p.matcher(xml);
if (m.find()) {
final String txt = "</test-dependency>";
final int s = m.start();
int end = xml.indexOf(txt, s);
if (end == -1) {
throw new BuildException("No end of dependency " + xml);
}
final int e = end + txt.length();
String dep = xml.substring(s, e);
return xml.substring(0, s) + dep + '\n' + dep.replace("org.openide.util", "org.openide.util.lookup") + xml.substring(e);
}
return xml;
}
}