blob: 018157f6cd546bc8cd73586032e014166c97a908 [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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.function.Supplier;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.taskdefs.ExecTask;
import org.apache.tools.ant.taskdefs.Jar;
import org.apache.tools.ant.taskdefs.SignJar;
import org.apache.tools.ant.types.Commandline;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.ZipFileSet;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/** Makes a <code>.nbm</code> (<b>N</b>et<b>B</b>eans <b>M</b>odule) file.
*
* @author Jesse Glick
*/
public class MakeNBM extends Task {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat ("yyyy/MM/dd");
/** The same syntax may be used for either <samp>&lt;license&gt;</samp> or
* <samp>&lt;description&gt;</samp> subelements.
* <p>By setting the property <code>makenbm.nocdata</code> to <code>true</code>,
* you can avoid using XML <code>CDATA</code> (for compatibility with older versions
* of Auto Update which could not handle it).
*/
public class Blurb {
/** You may embed a <samp>&lt;file&gt;</samp> element inside the blurb.
* If there is text on either side of it, that will be separated
* with a line of dashes automatically.
* But use nested <samp>&lt;text&gt;</samp> for this purpose.
*/
public class FileInsert {
/** File location. */
public void setLocation (File file) throws BuildException {
boolean html = file.getName().endsWith(".html") || file.getName().endsWith(".htm");
log("Including contents of " + file + " (HTML mode: " + html + ")", Project.MSG_VERBOSE);
long lmod = file.lastModified ();
if (lmod > mostRecentInput) mostRecentInput = lmod;
addSeparator ();
try {
try (InputStream is = new FileInputStream (file)) {
BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String line;
while ((line = r.readLine()) != null) {
if (html) {
// Clean out any markup first. First tags:
line = line.replaceAll("</?[a-zA-Z0-9_.:-]+( +[a-zA-Z0-9_.:-]+( *= *([^ \"]+|\"[^\"]*\"))?)*/?>", "");
// DOCTYPE:
line = line.replaceAll("<![a-zA-Z]+[^>]*>", "");
// Comments (single-line only at the moment):
line = line.replaceAll("<!--([^-]|-[^-])*-->", "");
// Common named character entities:
line = line.replaceAll("&quot;", "\"");
line = line.replaceAll("&nbsp;", " ");
line = line.replaceAll("&copy;", "\u00A9");
line = line.replaceAll("&apos;", "'");
line = line.replaceAll("&lt;", "<");
line = line.replaceAll("&gt;", ">");
line = line.replaceAll("&amp;", "&");
}
line = line.replaceAll("[\\p{Cntrl}&&[^\t]]", ""); // #74546
text.append(line);
text.append('\n');
}
}
} catch (IOException ioe) {
throw new BuildException ("Exception reading blurb from " + file, ioe, getLocation ());
}
}
}
private StringBuffer text = new StringBuffer ();
private String name = null;
/** There may be freeform text inside the element. Prefer to use nested elements. */
public void addText (String t) {
addSeparator ();
// Strips indentation. Needed because of common style:
// <description>
// Some text here.
// And another line.
// </description>
t = getProject().replaceProperties(t.trim());
int min = Integer.MAX_VALUE;
StringTokenizer tok = new StringTokenizer (t, "\n");
boolean first = true;
while (tok.hasMoreTokens ()) {
String line = tok.nextToken ();
if (first) {
first = false;
} else {
int i;
for (i = 0; i < line.length() && Character.isWhitespace(line.charAt(i)); i++) {}
if (i < min) min = i;
}
}
if (min == 0) {
text.append (t);
} else {
tok = new StringTokenizer (t, "\n");
first = true;
while (tok.hasMoreTokens ()) {
String line = tok.nextToken ();
if (first) {
first = false;
} else {
text.append ('\n');
line = line.substring (min);
}
text.append (line);
}
}
}
/** Contents of a file to include. */
public FileInsert createFile () {
return new FileInsert ();
}
/** Text to include literally. */
public class Text {
public void addText(String t) {
Blurb.this.addText(t);
}
}
// At least on Ant 1.3, mixed content does not work: all the text is added
// first, then all the file inserts. Need to use subelements to be sure.
/** Include nested literal text. */
public Text createText() {
return new Text();
}
private void addSeparator () {
if (text.length () > 0) {
// some sort of separator
if (text.charAt (text.length () - 1) != '\n')
text.append ('\n');
text.append ("-----------------------------------------------------\n");
}
}
public org.w3c.dom.Text getTextNode(Document ownerDoc) {
// XXX Current XMLUtil.write anyway does not preserve CDATA sections, it seems.
String nocdata = getProject().getProperty("makenbm.nocdata");
if (nocdata != null && Project.toBoolean(nocdata)) {
return ownerDoc.createTextNode(text.toString());
} else {
return ownerDoc.createCDATASection(text.toString());
}
}
/** @deprecated */
@Deprecated
public void setName(String name) {
getProject().log(getLocation() + ": the 'name' attribute on <license> is deprecated", Project.MSG_WARN);
}
public String getName () {
if (name == null) {
name = crcOf(text);
}
return name;
}
private String crcOf(StringBuffer text) {
CRC32 crc = new CRC32();
try {
crc.update(text.toString().replaceAll("\\s+", " ").trim().getBytes("UTF-8"));
} catch (UnsupportedEncodingException ex) {
throw new BuildException(ex);
}
return Long.toHexString(crc.getValue()).toUpperCase(Locale.ENGLISH);
}
/** Include a file (and set the license name according to its basename). */
public void setFile (File file) {
// This actually adds the text and so on:
new FileInsert ().setLocation (file);
}
}
public class ExternalPackage {
String name = null;
String targetName = null;
String startUrl = null;
String description = null;
public void setName(String n) {
this.name = n;
}
public void setTargetName(String t) {
this.targetName = t;
}
public void setStartURL(String u) {
this.startUrl = u;
}
public void setDescription(String d) {
this.description = d;
}
}
/** <samp>&lt;signature&gt;</samp> subelement for signing the NBM. */
public /*static*/ class Signature {
public File keystore;
public String storepass, alias;
public String tsaurl, tsacert;
/** Path to the keystore (private key). */
public void setKeystore(File f) {
keystore = f;
}
/** Password for the keystore.
* If a question mark (<samp>?</samp>), the NBM will not be signed
* and a warning will be printed.
*/
public void setStorepass(String s) {
storepass = s;
}
/** Alias for the private key. */
public void setAlias(String s) {
alias = s;
}
/** Time Stamping Authority (TSA) URL */
public void setTsaurl(String s) {
tsaurl = s;
}
/** Alias for the TSA's public key certificate */
public void setTsacert(String s) {
tsacert = s;
}
}
private File productDir = null;
private File file = null;
private File manifest = null;
/** see #13850 for explanation */
private String moduleName = null;
private String homepage = null;
private String distribution = "";
private String needsrestart = null;
private String moduleauthor = null;
private String releasedate = null;
private String global = null;
private String preferredupdate = null;
private String targetcluster = null;
private String jarSignerMaxMemory = "96m";
private Blurb license = null;
private Blurb desc = null;
private Blurb notification = null;
private Signature signature = null;
private long mostRecentInput = 0L;
private boolean isStandardInclude = true;
private ArrayList<ExternalPackage> externalPackages = null;
private ArrayList<String> locales = null;
private Attributes englishAttr = null;
private Path updaterJar;
private FileSet executablesSet;
private ZipFileSet extraNBMFiles;
private boolean usePack200;
private String pack200excludes;
private boolean alwaysCreateNBM;
/** Try to find and create localized info.xml files */
public void setLocales(String s) {
locales = new ArrayList<>();
for (String st : s.split("[, ]+")) {
if (! st.trim().isEmpty()) {
locales.add(st);
}
}
}
/** Include netbeans directory - default is true */
public void setIsStandardInclude(boolean isStandardInclude) {
this.isStandardInclude = isStandardInclude;
}
/** Directory of the product's files */
public void setProductDir( File dir ) {
productDir = dir;
}
/** Name of resulting NBM file. */
public void setFile(File file) {
this.file = file;
}
public void setUsePack200(boolean usePack200) {
this.usePack200 = usePack200;
}
public void setPack200Excludes(String pack200excludes) {
this.pack200excludes = pack200excludes;
}
public void setAlwaysCreateNBM(boolean alwaysCreateNBM) {
this.alwaysCreateNBM = alwaysCreateNBM;
}
/** List of executable files in NBM concatinated by ${line.separator}. */
public FileSet createExecutables() {
return (executablesSet = new FileSet());
}
public ZipFileSet createExtraNBMFiles() {
return (extraNBMFiles = new ZipFileSet());
}
/** Module manifest needed for versioning.
* @deprecated Use {@link #setModule} instead.
*/
@Deprecated
public void setManifest(File manifest) {
this.manifest = manifest;
long lmod = manifest.lastModified();
if (lmod > mostRecentInput) mostRecentInput = lmod;
log(getLocation() + "The 'manifest' attr on <makenbm> is deprecated, please use 'module' instead", Project.MSG_WARN);
}
/** Module JAR needed for generating the info file.
* Information may be gotten either from its manifest,
* or if it declares OpenIDE-Module-Localizing-Bundle in its
* manifest, from that bundle.
* The base locale variant, if any, is also checked if necessary
* for the named bundle.
* Currently no other locale variants of the module are examined;
* the information is available but there is no published specification
* of what the resulting variant NBMs (or variant information within
* the NBM) should look like.
*/
public void setModule(String module) {
this.moduleName = module;
// mostRecentInput updated below...
}
/** URL to a home page describing the module. */
public void setHomepage (String homepage) {
this.homepage = homepage;
}
/** Does module need IDE restart to be installed? */
public void setNeedsrestart (String needsrestart) {
this.needsrestart = needsrestart;
}
/** Sets name of module author */
public void setModuleauthor (String author) {
this.moduleauthor = author;
}
/** Install globally? */
public void setGlobal (String isGlobal) {
this.global = isGlobal;
}
/** Is preferred update? */
public void setPreferredupdate(String isPreferred) {
this.preferredupdate = isPreferred;
}
/** Sets pattern for target cluster */
public void setTargetcluster (String targetCluster) {
this.targetcluster = targetCluster;
}
/** Maximum memory allowed to be used by jarsigner task. Default is 96 MB. */
public void setJarSignerMaxMemory (String jsmm) {
this.jarSignerMaxMemory = jsmm;
}
/** Release date of NBM. */
public void setReleasedate (String date) {
this.releasedate = date;
}
/** URL where this NBM file is expected to be downloadable from. */
public void setDistribution (String distribution) throws BuildException {
this.distribution = distribution;
if (!(this.distribution.equals(""))) {
// check the URL
try {
URI uri = java.net.URI.create(this.distribution);
} catch (IllegalArgumentException ile) {
throw new BuildException("Distribution URL \"" + this.distribution + "\" is not a valid URI", ile, getLocation());
}
}
}
public Blurb createLicense () {
return (license = new Blurb ());
}
public Blurb createNotification () {
return (notification = new Blurb ());
}
public Blurb createDescription () {
log(getLocation() + "The <description> subelement in <makenbm> is deprecated except for emergency patches, please ensure your module has an OpenIDE-Module-Long-Description instead", Project.MSG_WARN);
return desc = new Blurb();
}
public Signature createSignature () {
return (signature = new Signature ());
}
public ExternalPackage createExternalPackage(){
ExternalPackage externalPackage = new ExternalPackage ();
if (externalPackages == null)
externalPackages = new ArrayList<>();
externalPackages.add( externalPackage );
return externalPackage;
}
private ZipFileSet main = null;
public ZipFileSet createMain () {
return (main = new ZipFileSet());
}
/** Fileset for platform/modules/ext/updater.jar, to be used in DTD validation. */
public Path createUpdaterJar() {
return updaterJar = new Path(getProject());
}
private Attributes getModuleAttributesForLocale(String locale) throws BuildException {
if (locale == null) {
throw new BuildException("Unknown locale: null",getLocation());
}
log("Processing module attributes for locale '"+locale+"'", Project.MSG_VERBOSE);
Attributes attr;
if ((!locale.equals("")) && (englishAttr != null)) {
attr = new Attributes(englishAttr);
attr.putValue("locale", locale);
log("Copying English module attributes to localized attributes in locale "+locale,Project.MSG_VERBOSE);
String om = attr.getValue("OpenIDE-Module");
String omn = attr.getValue("OpenIDE-Module-Name");
String omdc = attr.getValue("OpenIDE-Module-Display-Category");
String omsd = attr.getValue("OpenIDE-Module-Short-Description");
String omld = attr.getValue("OpenIDE-Module-Long-Description");
if (om != null) log("OpenIDE-Module"+(locale.equals("")?"":"_"+locale)+" is "+om,Project.MSG_DEBUG);
if (omn != null) log("OpenIDE-Module-Name"+(locale.equals("")?"":"_"+locale)+" is "+omn,Project.MSG_DEBUG);
if (omdc != null) log("OpenIDE-Module-Display-Category"+(locale.equals("")?"":"_"+locale)+" is "+omdc,Project.MSG_DEBUG);
if (omsd != null) log("OpenIDE-Module-Short-Description"+(locale.equals("")?"":"_"+locale)+" is "+omsd,Project.MSG_DEBUG);
if (omld != null) log("OpenIDE-Module-Long-Description"+(locale.equals("")?"":"_"+locale)+" is "+omld,Project.MSG_DEBUG);
} else {
attr = new Attributes();
attr.putValue("locale", locale);
}
moduleName = moduleName.replace(File.separatorChar, '/');
String jarName = moduleName;
String filename; String fname; String fext;
if (!locale.equals("")) {
// update file name for current locale
filename = moduleName.substring(moduleName.lastIndexOf('/')+1);
fname = filename.substring(0,filename.lastIndexOf('.'));
fext = filename.substring(filename.lastIndexOf('.'));
jarName = moduleName.substring(0,moduleName.lastIndexOf('/')) + "/locale/" + fname + "_" + locale + fext;
}
log("Going to open jarfile "+jarName,Project.MSG_VERBOSE);
File mfile = new File(productDir, jarName );
if (!mfile.exists()) {
// localizing jarfile does not exist, try to return english data
if (englishAttr != null) {
Attributes xattr = new Attributes(englishAttr);
xattr.putValue("locale", locale);
return xattr;
} else {
throw new BuildException("Unable to find English/localized data about module (locale is '"+locale+"')", getLocation());
}
}
try {
try (JarFile mjar = new JarFile(mfile)) {
if (mjar.getManifest().getMainAttributes().getValue("Bundle-SymbolicName") != null) {
englishAttr = new Attributes();
englishAttr.putValue("OpenIDE-Module", JarWithModuleAttributes.extractCodeName(mjar.getManifest().getMainAttributes()));
// #181025: treat bundles specially.
return null;
}
if (attr.getValue("OpenIDE-Module") == null ) {
attr = mjar.getManifest().getMainAttributes();
attr.putValue("locale", locale);
}
String bundlename = mjar.getManifest().getMainAttributes().getValue("OpenIDE-Module-Localizing-Bundle");
if ((bundlename == null) && (englishAttr != null)) {
String bname = englishAttr.getValue("OpenIDE-Module-Localizing-Bundle");
String bfname; String bfext;
if (bname != null) {
bname = bname.replace(File.separatorChar, '/');
bfname = bname.substring(0,bname.lastIndexOf('.'));
bfext = bname.substring(bname.lastIndexOf('.'));
bundlename = bfname + "_" + locale + bfext;
log("Determined ("+locale+") localizing bundle name: "+bundlename,Project.MSG_VERBOSE);
}
}
if (bundlename != null) {
Properties p = new Properties();
ZipEntry bundleentry = mjar.getEntry(bundlename);
if (bundleentry != null) {
try (InputStream is = mjar.getInputStream(bundleentry)) {
p.load(is);
}
// Now pick up attributes from the bundle.
for(String name: p.stringPropertyNames()) {
if (! name.startsWith("OpenIDE-Module-")) continue;
attr.putValue(name, p.getProperty(name));
}
}
}
}
} catch (IOException ioe) {
throw new BuildException("exception while reading " + mfile.getName(), ioe, getLocation());
}
if (locale.equals("") && (englishAttr == null)) {
log("Populating English module attributes", Project.MSG_VERBOSE);
englishAttr = new Attributes(attr);
}
String om = attr.getValue("OpenIDE-Module");
String omn = attr.getValue("OpenIDE-Module-Name");
String omdc = attr.getValue("OpenIDE-Module-Display-Category");
String omsd = attr.getValue("OpenIDE-Module-Short-Description");
String omld = attr.getValue("OpenIDE-Module-Long-Description");
if (om != null) log("OpenIDE-Module"+(locale.equals("")?"":"_"+locale)+" is "+om,Project.MSG_VERBOSE);
if (omn != null) log("OpenIDE-Module-Name"+(locale.equals("")?"":"_"+locale)+" is "+omn,Project.MSG_VERBOSE);
if (omdc != null) log("OpenIDE-Module-Display-Category"+(locale.equals("")?"":"_"+locale)+" is "+omdc,Project.MSG_VERBOSE);
if (omsd != null) log("OpenIDE-Module-Short-Description"+(locale.equals("")?"":"_"+locale)+" is "+omsd,Project.MSG_VERBOSE);
if (omld != null) log("OpenIDE-Module-Long-Description"+(locale.equals("")?"":"_"+locale)+" is "+omld,Project.MSG_VERBOSE);
return attr;
}
@Override
public void execute () throws BuildException {
if (productDir == null) {
throw new BuildException("must set directory of compiled product", getLocation());
}
if (file == null) {
throw new BuildException("must set file for makenbm", getLocation());
}
if (manifest == null && moduleName == null) {
throw new BuildException("must set module for makenbm", getLocation());
}
if (manifest != null && moduleName != null) {
throw new BuildException("cannot set both manifest and module for makenbm", getLocation());
}
if (locales == null) {
locales = new ArrayList<>();
}
File nbm;
String rootDir = getProject ().getProperty ("nbm.target.dir");
if (rootDir != null && !rootDir.equals ("")) {
nbm = new File(rootDir, this.file.getName());
} else {
nbm = this.file;
}
// If desired, override the license and/or URL. //
overrideURLIfNeeded() ;
overrideLicenseIfNeeded() ;
Map<String, Supplier<Document>> moduleAttributes = new LinkedHashMap<>();
File module = new File( productDir, moduleName );
Attributes attr = getModuleAttributesForLocale("");
if (attr == null) {
if (!alwaysCreateNBM) {
// #181025: OSGi bundle, copy unmodified.
Copy copy = new Copy();
copy.setProject(getProject());
copy.setOwningTarget(getOwningTarget());
copy.setFile(module);
copy.setTofile(new File(nbm.getAbsolutePath().replaceFirst("[.]nbm$", ".jar")));
copy.execute();
// XXX possibly sign it
// XXX could try to run pack200, though not if it was signed
return;
} else {
moduleAttributes.put("", () -> createFakeOSGiInfo(module));
}
} else {
moduleAttributes.put("", () -> createInfoXml(attr));
}
for (String locale : locales) {
Attributes a = getModuleAttributesForLocale(locale);
if (a != null) moduleAttributes.put(locale, () -> createInfoXml(a));
}
// Will create a file Info/info.xml to be stored in tmp
// The normal case; read attributes from its manifest and maybe bundle.
long mMod = module.lastModified();
if (mostRecentInput < mMod) mostRecentInput = mMod;
if (mostRecentInput < nbm.lastModified()) {
log("Skipping NBM creation as most recent input is younger: " + mostRecentInput + " than the target file: " + nbm.lastModified(), Project.MSG_VERBOSE);
return;
} else {
log("Most recent input: " + mostRecentInput + " file: " + nbm.lastModified(), Project.MSG_DEBUG);
}
ArrayList<ZipFileSet> infoXMLFileSets = new ArrayList<>();
for (Map.Entry<String, Supplier<Document>> modAttr : moduleAttributes.entrySet()) {
Document infoXmlContents = modAttr.getValue().get();
File infofile;
String loc = modAttr.getKey();
if (loc == null)
throw new BuildException("Found attributes without assigned locale code", getLocation());
try {
infofile = File.createTempFile("info_"+loc,".xml");
try (OutputStream infoStream = new FileOutputStream (infofile)) {
XMLUtil.write(infoXmlContents, infoStream);
}
} catch (IOException e) {
throw new BuildException("exception when creating Info/info.xml for locale '"+loc+"'", e, getLocation());
}
infofile.deleteOnExit();
ZipFileSet infoXML = new ZipFileSet();
infoXML.setFile( infofile );
if (loc.equals("")) {
infoXML.setFullpath("Info/info.xml");
} else {
infoXML.setFullpath("Info/locale/info_"+loc+".xml");
log("Adding Info/locale/info_"+loc+".xml file", Project.MSG_VERBOSE);
}
infoXMLFileSets.add(infoXML);
}
String codename = englishAttr.getValue("OpenIDE-Module");
if (codename == null)
new BuildException( "Can't get codenamebase" );
UpdateTracking tracking = new UpdateTracking(productDir.getAbsolutePath());
Set<String> _files = new LinkedHashSet<>(Arrays.asList(tracking.getListOfNBM(codename)));
List<String> __files = new ArrayList<>(_files);
for (String f : _files) {
if (f.endsWith(".external")) { // #195041
__files.remove(f.substring(0, f.length() - 9));
}
}
String[] files = __files.toArray(new String[__files.size()]);
ZipFileSet fs = new ZipFileSet();
List <String> moduleFiles = new ArrayList <>();
fs.setDir( productDir );
String [] filesForPackaging = null;
if(usePack200 && pack200excludes!=null && !pack200excludes.equals("")) {
FileSet pack200Files = new FileSet();
pack200Files.setDir(productDir);
pack200Files.setExcludes(pack200excludes);
pack200Files.setProject(getProject());
for (int i=0; i < files.length; i++) {
pack200Files.createInclude().setName( files[i] );
}
DirectoryScanner ds = pack200Files.getDirectoryScanner();
ds.scan();
filesForPackaging = ds.getIncludedFiles();
}
List<File> packedFiles = new ArrayList<>();
for (int i = 0; i < files.length; i++) {
if (usePack200) {
File sourceFile = new File(productDir, files[i]);
if (sourceFile.isFile() && sourceFile.getName().endsWith(".jar")) {
boolean doPackage = true;
if (filesForPackaging != null) {
doPackage = false;
for (String f : filesForPackaging) {
if (new File(productDir, f).equals(sourceFile)) {
doPackage = true;
break;
}
}
}
if(doPackage) {
//if both <filename>.jar and <filename>.jad exist - skip it
//if both <filename>.jar and <filename>.jar.pack.gz exist - skip it
for (String f : files) {
if(f.equals(files[i].substring(0, files[i].lastIndexOf(".jar")) + ".jad") ||
f.equals(files[i] + ".pack.gz")) {
doPackage = false;
break;
}
}
}
if (doPackage) {
File targetFile = new File(productDir, files[i] + ".pack.gz");
try {
if (pack200(sourceFile, targetFile)) {
packedFiles.add(targetFile);
files[i] = files[i] + ".pack.gz";
}
} catch (IOException e) {
if(targetFile.exists()) {
targetFile.delete();
}
log("Cannot pack file " + sourceFile, e, Project.MSG_WARN);
}
}
}
}
fs.createInclude().setName(files[i]);
moduleFiles.add(files[i]);
}
fs.setPrefix("netbeans/");
// JAR it all up together.
long jarModified = nbm.lastModified(); // may be 0
//log ("Ensuring existence of NBM file " + file);
Jar jar = (Jar) getProject().createTask("jar");
jar.setDestFile(nbm);
jar.addZipfileset(fs);
for (ZipFileSet zfs : infoXMLFileSets) {
jar.addFileset(zfs);
}
if (extraNBMFiles != null) {
jar.addZipfileset(extraNBMFiles);
}
if (main != null) { // Add the main dir
main.setPrefix("main"); // use main prefix
jar.addZipfileset(main);
DirectoryScanner ds = main.getDirectoryScanner();
ds.scan();
String [] mainFiles = ds.getIncludedFiles();
for(String m : mainFiles) {
moduleFiles.add(m);
}
}
if (executablesSet != null) {
DirectoryScanner eds = executablesSet.getDirectoryScanner();
eds.scan();
String [] executables = eds.getIncludedFiles();
if(executables.length > 0) {
ZipFileSet executablesList = new ZipFileSet();
File executablesFile;
StringBuilder sb = new StringBuilder("");
String ls = System.getProperty("line.separator");
for(int i=0;i < executables.length;i++) {
if(i!=0) {
sb.append(ls);
}
sb.append(executables[i].replace("\\","/"));
}
try {
executablesFile = File.createTempFile("executables",".list");
try (OutputStream infoStream = new FileOutputStream (executablesFile)) {
infoStream.write(sb.toString().getBytes("UTF-8"));
}
} catch (IOException e) {
throw new BuildException("exception when creating Info/executables.list", e, getLocation());
}
executablesFile.deleteOnExit();
executablesList.setFile(executablesFile);
executablesList.setFullpath("Info/executables.list");
jar.addZipfileset(executablesList);
}
}
jar.setCompress(true);
jar.setLocation(getLocation());
jar.init ();
jar.execute ();
for(File f : packedFiles) {
f.delete();
}
// Print messages if we overrode anything. //
if (nbm.lastModified() != jarModified) {
if( overrideLicense()) {
log( "Overriding license with: " + getLicenseOverride()) ;
}
if( overrideURL()) {
log( "Overriding homepage URL with: " + getURLOverride()) ;
}
}
// Maybe sign it.
if (signature != null && nbm.lastModified() != jarModified) {
if (signature.keystore == null)
throw new BuildException ("must define keystore attribute on <signature/>");
if (signature.storepass == null)
throw new BuildException ("must define storepass attribute on <signature/>");
if (signature.alias == null)
throw new BuildException ("must define alias attribute on <signature/>");
if (signature.storepass.equals ("?") || signature.storepass.indexOf("${") != -1 || !signature.keystore.exists()) {
log("Not signing NBM file " + nbm + "; no stored-key password provided or keystore ("
+ signature.keystore.toString() + ") doesn't exist", Project.MSG_WARN);
} else {
log("Signing NBM file " + nbm);
SignJar signjar = (SignJar) getProject().createTask("signjar");
try { // Signatures changed in various Ant versions.
try {
SignJar.class.getMethod("setKeystore", File.class).invoke(signjar, signature.keystore);
} catch (NoSuchMethodException x) {
SignJar.class.getMethod("setKeystore", String.class).invoke(signjar, signature.keystore.getAbsolutePath());
}
try {
SignJar.class.getMethod("setJar", File.class).invoke(signjar, nbm);
} catch (NoSuchMethodException x) {
SignJar.class.getMethod("setJar", String.class).invoke(signjar, nbm.getAbsolutePath());
}
} catch (BuildException x) {
throw x;
} catch (Exception x) {
throw new BuildException(x);
}
if(signature.tsaurl != null && !signature.tsaurl.isEmpty()) {
signjar.setTsaurl(signature.tsaurl);
}
if(signature.tsacert != null && !signature.tsacert.isEmpty()) {
signjar.setTsacert(signature.tsacert);
}
signjar.setStorepass (signature.storepass);
signjar.setAlias (signature.alias);
signjar.setLocation(getLocation());
signjar.setMaxmemory(this.jarSignerMaxMemory);
signjar.init ();
signjar.execute ();
}
}
}
private boolean isSigned(final JarFile jar) throws IOException {
Enumeration<JarEntry> entries = jar.entries();
boolean signatureInfoPresent = false;
boolean signatureFilePresent = false;
while (entries.hasMoreElements()) {
String entryName = entries.nextElement().getName();
if (entryName.startsWith("META-INF/")) {
if (entryName.endsWith(".RSA") || entryName.endsWith(".DSA")) {
signatureFilePresent = true;
if (signatureInfoPresent) {
break;
}
} else if (entryName.endsWith(".SF")) {
signatureInfoPresent = true;
if (signatureFilePresent) {
break;
}
}
}
}
return signatureFilePresent && signatureInfoPresent;
}
public static boolean isWindows() {
String os = System.getProperty("os.name"); // NOI18N
return (os != null && os.toLowerCase().startsWith("windows"));//NOI18N
}
private boolean pack200(final File sourceFile, final File targetFile) throws IOException {
try (JarFile jarFile = new JarFile(sourceFile)) {
if (isSigned(jarFile)) {
return false;
}
try {
String pack200Executable = new File(System.getProperty("java.home"),
"bin/pack200" + (isWindows() ? ".exe" : "")).getAbsolutePath();
ExecTask et = (ExecTask) getProject().createTask("exec");
et.setExecutable(pack200Executable);
et.createArg().setFile(targetFile);
et.createArg().setFile(sourceFile);
et.setTaskName("pack200");
et.setDir(sourceFile.getParentFile());
et.execute();
return true;
} finally {
if (targetFile.exists()) {
targetFile.setLastModified(sourceFile.lastModified());
}
}
}
}
private Document createInfoXml(final Attributes attr) throws BuildException {
DOMImplementation domimpl;
try {
domimpl = DocumentBuilderFactory.newInstance().newDocumentBuilder().getDOMImplementation();
} catch (ParserConfigurationException x) {
throw new BuildException(x, getLocation());
}
String loc = attr.getValue("locale");
if (loc == null) {
throw new BuildException("Got module attributes for undefined locale", getLocation());
} else {
log("Creating info.xml from module attributes for locale '"+loc+"'", Project.MSG_VERBOSE);
}
String pub, sys;
if (preferredupdate != null && !("".equals(preferredupdate))) {
pub = "-//NetBeans//DTD Autoupdate Module Info 2.7//EN";
sys = "http://www.netbeans.org/dtds/autoupdate-info-2_7.dtd";
} else if (attr.getValue("AutoUpdate-Show-In-Client") != null || attr.getValue("AutoUpdate-Essential-Module") != null ||
attr.getValue("OpenIDE-Module-Recommends") != null || attr.getValue("OpenIDE-Module-Needs") != null) {
pub = "-//NetBeans//DTD Autoupdate Module Info 2.5//EN";
sys = "http://www.netbeans.org/dtds/autoupdate-info-2_5.dtd";
} else if (targetcluster != null && !("".equals(targetcluster))) {
pub = "-//NetBeans//DTD Autoupdate Module Info 2.4//EN";
sys = "http://www.netbeans.org/dtds/autoupdate-info-2_4.dtd";
} else {
// #74866: no need for targetcluster, so keep compat w/ 5.0 AU.
pub = "-//NetBeans//DTD Autoupdate Module Info 2.3//EN";
sys = "http://www.netbeans.org/dtds/autoupdate-info-2_3.dtd";
}
Document doc = domimpl.createDocument(null, "module", domimpl.createDocumentType("module", pub, sys));
String codenamebase = attr.getValue("OpenIDE-Module");
if (codenamebase == null) {
Iterator<Object> it = attr.keySet().iterator();
Name key; String val;
while (it.hasNext()) {
key = (Name) it.next();
val = attr.getValue(key);
log(key+" is '"+val+"'", Project.MSG_VERBOSE);
}
throw new BuildException("invalid manifest, does not contain OpenIDE-Module", getLocation());
}
// Strip major release number if any.
int idx = codenamebase.lastIndexOf('/');
if (idx != -1) codenamebase = codenamebase.substring(0, idx);
Element module = doc.getDocumentElement();
module.setAttribute("codenamebase", codenamebase);
if (homepage != null) {
module.setAttribute("homepage", homepage);
}
if (distribution != null) {
module.setAttribute("distribution", distribution);
} else {
throw new BuildException("NBM distribution URL is not set", getLocation());
}
maybeAddLicenseName(module);
module.setAttribute("downloadsize", "0");
if (needsrestart != null) {
module.setAttribute("needsrestart", needsrestart);
}
if (global != null && !("".equals(global))) {
module.setAttribute("global", global);
}
if (preferredupdate != null && !("".equals(preferredupdate))) {
module.setAttribute("preferredupdate", preferredupdate);
}
if (targetcluster != null && !("".equals(targetcluster))) {
module.setAttribute("targetcluster", targetcluster);
}
if (moduleauthor != null) {
module.setAttribute("moduleauthor", moduleauthor);
}
if (releasedate == null || "".equals(releasedate)) {
// if date is null, set today
releasedate = DATE_FORMAT.format(new Date(System.currentTimeMillis()));
}
module.setAttribute("releasedate", releasedate);
if (desc != null) {
module.appendChild(doc.createElement("description")).appendChild(desc.getTextNode(doc));
}
if (notification != null) {
module.appendChild(doc.createElement("module_notification")).appendChild(notification.getTextNode(doc));
}
if (externalPackages != null) {
Iterator<ExternalPackage> exp = externalPackages.iterator();
while (exp.hasNext()) {
ExternalPackage externalPackage = exp.next();
if (externalPackage.name == null ||
externalPackage.targetName == null ||
externalPackage.startUrl == null)
throw new BuildException("Must define name, targetname, starturl for external package");
Element el = doc.createElement("external_package");
el.setAttribute("name", externalPackage.name);
el.setAttribute("target_name", externalPackage.targetName);
el.setAttribute("start_url", externalPackage.startUrl);
if (externalPackage.description != null) {
el.setAttribute("description", externalPackage.description);
}
module.appendChild(el);
}
}
// Write manifest attributes.
Element el = doc.createElement("manifest");
List<String> attrNames = new ArrayList<>(attr.size());
for(Object key: attr.keySet()) {
attrNames.add(((Attributes.Name)key).toString());
}
Collections.sort(attrNames);
for(String name: attrNames) {
if (name.matches("OpenIDE-Module(|-(Name|(Specification|Implementation)-Version|(Module|Package|Java|IDE)-Dependencies|" +
"(Short|Long)-Description|Display-Category|Provides|Requires|Recommends|Needs|Fragment-Host))|AutoUpdate-(Show-In-Client|Essential-Module)")) {
el.setAttribute(name, attr.getValue(name));
}
}
module.appendChild(el);
maybeAddLicense(module);
if (updaterJar != null && updaterJar.size() > 0) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XMLUtil.write(doc, baos);
validateAgainstAUDTDs(new InputSource(new ByteArrayInputStream(baos.toByteArray())), updaterJar, this);
} catch (Exception x) {
throw new BuildException("Could not validate Info.xml before writing: " + x, x, getLocation());
}
} else {
log("No updater.jar specified, cannot validate Info.xml against DTD", Project.MSG_WARN);
}
return doc;
}
private Document createFakeOSGiInfo(File osgiJar) {
DOMImplementation domimpl;
try {
domimpl = DocumentBuilderFactory.newInstance().newDocumentBuilder().getDOMImplementation();
} catch (ParserConfigurationException x) {
throw new BuildException(x, getLocation());
}
log("Creating fake info.xml for OSGi bundle", Project.MSG_VERBOSE);
String pub = "-//NetBeans//DTD Autoupdate Module Info 2.5//EN";
String sys = "http://www.netbeans.org/dtds/autoupdate-info-2_5.dtd";
Document doc = domimpl.createDocument(null, "module", domimpl.createDocumentType("module", pub, sys));
try (JarFile jf = new JarFile(osgiJar)) {
MakeUpdateDesc.fakeOSGiInfoXml(jf, osgiJar, doc);
maybeAddLicenseName(doc.getDocumentElement());
maybeAddLicense(doc.getDocumentElement());
} catch (IOException x) {
throw new BuildException(x, getLocation());
}
return doc;
}
private void maybeAddLicenseName(Element module) {
// Here we only write a name for the license.
if (license != null) {
String name = license.getName();
if (name == null) {
throw new BuildException("Every license must have a name or file attribute", getLocation());
}
module.setAttribute("license", name);
}
}
private void maybeAddLicense(Element module) {
// Maybe write out license text.
if (license != null) {
Document doc = module.getOwnerDocument();
Element el = doc.createElement("license");
el.setAttribute("name", license.getName());
el.appendChild(license.getTextNode(doc));
module.appendChild(el);
}
}
static void validateAgainstAUDTDs(InputSource input, final Path updaterJar, final Task task) throws IOException, SAXException {
XMLUtil.parse(input, true, false, XMLUtil.rethrowHandler(), new EntityResolver() {
ClassLoader loader = new AntClassLoader(task.getProject(), updaterJar);
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
String remote = "http://www.netbeans.org/dtds/";
if (systemId.startsWith(remote)) {
String rsrc = "org/netbeans/updater/resources/" + systemId.substring(remote.length());
URL u = loader.getResource(rsrc);
if (u != null) {
return new InputSource(u.toString());
} else {
task.log(rsrc + " not found in " + updaterJar, Project.MSG_WARN);
}
}
return null;
}
});
}
/** This returns true if the license should be overridden. */
protected boolean overrideLicense() {
return( getLicenseOverride() != null) ;
}
/** Get the license to use if the license should be overridden,
* otherwise return null.
*/
protected String getLicenseOverride() {
String s = getProject().getProperty( "makenbm.override.license") ;
if( s != null) {
if( s.equals( "")) {
s = null ;
}
}
return( s) ;
}
/** This returns true if the homepage URL should be overridden. */
protected boolean overrideURL() {
return( getURLOverride() != null) ;
}
/** Get the homepage URL to use if it should be overridden,
* otherwise return null.
*/
protected String getURLOverride() {
String s = getProject().getProperty( "makenbm.override.url") ;
if( s != null) {
if( s.equals( "")) {
s = null ;
}
}
return( s) ;
}
/** If required, this will create a new license using the override
* license file.
*/
protected void overrideLicenseIfNeeded() {
if( overrideLicense()) {
license = new Blurb() ;
license.setFile( getProject().resolveFile( getLicenseOverride())) ;
}
}
/** If required, this will set the homepage URL using the
* override value.
*/
protected void overrideURLIfNeeded() {
if( overrideURL()) {
homepage = getURLOverride() ;
}
}
}