blob: 04f402de70a648bec6067bdda5b5e2cefb089109 [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.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.jar.Attributes.Name;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.apache.tools.ant.*;
import org.apache.tools.ant.types.*;
import org.apache.tools.ant.util.FileUtils;
/**
* Replace paths prefixes with variables.
* Designed for netbeans.dest dir and test.dist.dir variables
*/
public class ShorterPaths extends Task {
/** dir is prefix and name is name of variable
* <shorterpaths in="inputpropname" out="outpropNames">
* <replacement name="property_name" dir="directory"/>
* </shorterpaths>
*/
public static class Replacement {
String name;
File dir;
File excluded;
public void setName(String name) {
this.name = name;
}
public void setDir(File dir) {
this.dir = dir;
}
public void setExcluded(File excluded) {
this.excluded = excluded;
}
@Override
public String toString() {
return dir + (excluded != null ? " - " + excluded : "") + " => ${" + name + "}";
}
}
private List<Replacement> replacements = new LinkedList<>(); // List<Nestme>
public Replacement createReplacement() {
Replacement r = new Replacement();
replacements.add(r);
return r;
}
public void addReplacement(Replacement r) {
replacements.add(r);
}
private Path in;
public void setIn(Path p) {
if (in == null) {
in = p.createPath();
}
in.append(p);
}
public Path createIn() {
if (in == null) {
in = new Path(getProject());
}
return in;
}
public void setinRef(Reference r) {
createIn().setRefid(r);
}
// <customtask path="foo:bar"/>
// <customtask>
// <path>
// <pathelement location="foo"/>
// </path>
// </customtask>
// Etc.
String out;
public void setOut(String out) {
this.out = out;
}
String extraLibs;
public void setExtraLibs(String extraLibs) {
this.extraLibs = extraLibs;
}
File extraLibsDir;
public void setExtraLibsDir(File extraLibsDir) {
this.extraLibsDir = extraLibsDir;
}
File testProperties;
public void setTestProperties(File testProperties) {
this.testProperties = testProperties;
}
@Override
public void execute() throws BuildException {
// TODO code here what the task actually does:
String paths[] = in.list();
StringBuffer nbLibBuff = new StringBuffer();
// Path nbLibPath = new Path(getProject());
StringBuffer externalLibBuf = new StringBuffer();
try {
for (int i = 0; i < paths.length; i++) {
String path = paths[i];
File file = new File(path);
// check if file exists
if (file.exists()) {
// add it on classpath
path = file.getCanonicalPath();
simplyPath(path, externalLibBuf, nbLibBuff);
} else {
log("Path element " + file + " doesn't exist.", Project.MSG_VERBOSE);
}
}
if (out != null) {
define(out, nbLibBuff.toString());
}
if (this.extraLibs != null) {
define(extraLibs, externalLibBuf.toString());
}
if (testProperties != null) {
// create properties file
try (PrintWriter pw = new PrintWriter(testProperties)) {
// copy extra unit.test.properties
Map<String,Object> properties = getProject().getProperties();
StringBuffer outProp = new StringBuffer();
for (String name : properties.keySet()) {
if (name.matches("test-(unit|qa-functional)-sys-prop\\..+")) {
if (name.equals("test-unit-sys-prop.xtest.data")) {
// ignore overring xtest.data.dir, data.zip placed to standard location
continue;
}
//
outProp.setLength(0);
for (String path : tokenizePath(properties.get(name).toString())) {
replacePath(path, outProp);
}
pw.println(name.replaceFirst("^test-(unit|qa-functional)-sys-prop\\.", "test-sys-prop.") + "=" + outProp);
} else if (name.startsWith("test.config")) {
pw.println(name + "=" + properties.get(name));
} else if ("requires.nb.javac".equals(name)) {
pw.println(name + "=" + properties.get(name));
}
}
pw.println("extra.test.libs=" + externalLibBuf.toString());
pw.println("test.run.cp=" + nbLibBuff.toString());
}
}
} catch (IOException ex) {
throw new BuildException(ex);
}
}
/** Replace absolute path with ${a.prop}/relpath and concatenate resulting
* path to nbLibBuff. If replacement does not exist copy file on the path
* to extra lib folder and add the path to externalLibBuf.
* @param path path to be replaced
* @param externalLibBuf extra lib paths buffer
* @param nbLibBuff nb lib paths buffer
* @throws IOException
*/
private void simplyPath(String path, final StringBuffer externalLibBuf, final StringBuffer nbLibBuff) throws IOException {
boolean bAppend = false;
File file = new File(path);
if (file.exists()) {
// file exists, try to to replace the path with ${a.prop}/relpath
//
path = file.getAbsolutePath();
for (Replacement repl : replacements) {
String dirCan = repl.dir.getCanonicalPath();
if (path.startsWith(dirCan) && (repl.excluded == null || !path.startsWith(repl.excluded.getCanonicalPath()))) {
if (nbLibBuff.length() > 0) {
nbLibBuff.append(":\\\n");
}
nbLibBuff.append("${").append(repl.name).append("}");
// postfix + unify file separators to '/'
nbLibBuff.append(path.substring(dirCan.length()).replace(File.separatorChar, '/'));
bAppend = true;
break;
}
}
if (!bAppend) {
String fName = copyExtraLib(file);
if (fName != null) {
if (externalLibBuf.length() > 0) {
externalLibBuf.append(":\\\n");
}
externalLibBuf.append("${extra.test.libs.dir}/").append(fName);
}
}
} else {
if (nbLibBuff.length() > 0) {
nbLibBuff.append(":\\\n");
}
nbLibBuff.append(path);
}
}
/** Replace absolute path with ${a.prop}/relpath and concatenate resulting
* path to pathsBuff.
* @param path path to be replaced by one of registered replacements
* @param pathsBuff buffer containing whole path
*/
private void replacePath(String path, final StringBuffer pathsBuff) throws IOException {
File file = new File(path);
if (file.exists()) {
path = file.getAbsolutePath();
}
boolean replacementFound = false;
if (pathsBuff.length() > 0) {
pathsBuff.append(":\\\n");
}
for (Replacement repl : replacements) {
String dirCan = repl.dir.getCanonicalPath();
if (path.startsWith(dirCan)) {
pathsBuff.append("${").append(repl.name).append("}");
// postfix + unify file separators to '/'
pathsBuff.append(path.substring(dirCan.length()).replace(File.separatorChar, '/'));
replacementFound = true;
break;
}
}
if (!replacementFound) {
// append without property replacement
pathsBuff.append(path.replace(File.separatorChar, '/'));
}
}
private void define(String prop, String val) {
log("Setting " + prop + "=" + val, Project.MSG_VERBOSE);
String old = getProject().getProperty(prop);
if (old != null && !old.equals(val)) {
getProject().log("Warning: " + prop + " was already set to " + old, Project.MSG_WARN);
}
getProject().setNewProperty(prop, val);
}
private String copyExtraLib(File file) throws IOException {
if (extraLibsDir == null || !extraLibsDir.isDirectory() || !file.isFile()) {
return null;
}
File copy = new File(extraLibsDir, file.getName());
boolean wasCopied = copyMissing(file, copy);
// copy Class-Path extensions if available
if (wasCopied && file.getName().endsWith(".jar")) {
String cp;
try {
try (JarFile jf = new JarFile(file)) {
Manifest manifest = jf.getManifest();
cp = manifest != null ? manifest.getMainAttributes().getValue(Name.CLASS_PATH) : null;
}
} catch (IOException x) {
log("Could not parse " + file + " for Class-Path", Project.MSG_WARN);
cp = null;
}
if (cp != null) {
for (String ext : cp.split(" ")) {
// copy CP extension with relative path to keep link dependency from manifest
copyMissing(new File(file.getParentFile(), ext), new File(extraLibsDir, ext));
}
}
}
return copy.getName();
}
/** Copies source file to target location only if it is missing there.
* @param file source file
* @param copy target file
* @return true if file was successfully copied, false if source is the same
* as target
* @throws IOException if target file exists and it is not the same as
* source file
*/
private boolean copyMissing(File file, File copy) throws IOException {
if (FileUtils.getFileUtils().contentEquals(file, copy)) {
return false;
} else if (copy.isFile()) {
// Could try to copy to a different name, but this is probably something that should be fixed anyway:
throw new IOException(file + " is not the same as " + copy + "; will not overwrite");
}
log("Copying " + file + " to extralibs despite " + replacements);
FileUtils.getFileUtils().copyFile(file, copy);
return true;
}
/**
* Split an Ant-style path specification into components.
* Tokenizes on <code>:</code> and <code>;</code>, paying
* attention to DOS-style components such as <samp>C:\FOO</samp>.
* Also removes any empty components.
* Copied from org.netbeans.spi.project.support.ant.PropertyUtils.
* @param path an Ant-style path (elements arbitrary) using DOS or Unix separators
* @return a tokenization of that path into components
*/
public static String[] tokenizePath(String path) {
List<String> l = new ArrayList<>();
StringTokenizer tok = new StringTokenizer(path, ":;", true); // NOI18N
char dosHack = '\0';
char lastDelim = '\0';
int delimCount = 0;
while (tok.hasMoreTokens()) {
String s = tok.nextToken();
if (s.length() == 0) {
// Strip empty components.
continue;
}
if (s.length() == 1) {
char c = s.charAt(0);
if (c == ':' || c == ';') {
// Just a delimiter.
lastDelim = c;
delimCount++;
continue;
}
}
if (dosHack != '\0') {
// #50679 - "C:/something" is also accepted as DOS path
if (lastDelim == ':' && delimCount == 1 && (s.charAt(0) == '\\' || s.charAt(0) == '/')) {
// We had a single letter followed by ':' now followed by \something or /something
s = "" + dosHack + ':' + s;
// and use the new token with the drive prefix...
} else {
// Something else, leave alone.
l.add(Character.toString(dosHack));
// and continue with this token too...
}
dosHack = '\0';
}
// Reset count of # of delimiters in a row.
delimCount = 0;
if (s.length() == 1) {
char c = s.charAt(0);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
// Probably a DOS drive letter. Leave it with the next component.
dosHack = c;
continue;
}
}
l.add(s);
}
if (dosHack != '\0') {
//the dosHack was the last letter in the input string (not followed by the ':')
//so obviously not a drive letter.
//Fix for issue #57304
l.add(Character.toString(dosHack));
}
return l.toArray(new String[l.size()]);
}
}