| /* |
| * 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><license></samp> or |
| * <samp><description></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><file></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><text></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(""", "\""); |
| line = line.replaceAll(" ", " "); |
| line = line.replaceAll("©", "\u00A9"); |
| line = line.replaceAll("'", "'"); |
| line = line.replaceAll("<", "<"); |
| line = line.replaceAll(">", ">"); |
| line = line.replaceAll("&", "&"); |
| } |
| 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><signature></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() ; |
| } |
| } |
| |
| } |