| /* |
| * 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.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.util.SortedMap; |
| import java.util.TreeMap; |
| import java.util.jar.Attributes; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.taskdefs.Jar; |
| import org.apache.tools.ant.taskdefs.Manifest; |
| |
| /** |
| * Task just like <jar> but predefines various module attributes. |
| * Cf. projectized.xml#jar |
| * @author Jesse Glick |
| */ |
| public class JarWithModuleAttributes extends Jar { |
| |
| public JarWithModuleAttributes() {} |
| |
| private static final Pattern COMMA_SPACE = Pattern.compile(", *"); |
| private static final Pattern IMPL_DEP = Pattern.compile(" *([a-zA-Z0-9_.]+)(/[0-9]+)? *= *(.+) *"); |
| |
| private File stamp; |
| /** Location of a stamp file to create and/or make newer than the JAR file. |
| * |
| * @param stamp the file to create and update |
| */ |
| public void setStamp(File stamp) { |
| this.stamp = stamp; |
| } |
| |
| |
| static String extractCodeName(Attributes attr) { |
| return extractCodeName(attr, null); |
| } |
| static String extractCodeName(Attributes attr, boolean[] osgi) { |
| String codename = attr.getValue("OpenIDE-Module"); |
| if (codename != null) { |
| return codename; |
| } |
| codename = attr.getValue("Bundle-SymbolicName"); |
| if (codename == null) { |
| return null; |
| } |
| codename = codename.replace('-', '_'); |
| if (osgi != null) { |
| osgi[0] = true; |
| } |
| int params = codename.indexOf(';'); |
| if (params >= 0) { |
| return codename.substring(0, params); |
| } else { |
| return codename; |
| } |
| } |
| |
| @Override |
| public void setManifest(File manifestFile) throws BuildException { |
| Manifest added = new Manifest(); |
| try { |
| // Check to see if OpenIDE-Module-Implementation-Version is already defined. |
| String implVers; |
| String specVer; |
| String ownCnb; |
| Manifest staticManifest; |
| InputStream is = new FileInputStream(manifestFile); |
| boolean isOSGiMode = false; |
| try { |
| staticManifest = new Manifest(new InputStreamReader(is, "UTF-8")); |
| Manifest.Section mainSection = staticManifest.getMainSection(); |
| implVers = mainSection.getAttributeValue("OpenIDE-Module-Implementation-Version"); |
| specVer = mainSection.getAttributeValue("OpenIDE-Module-Specification-Version"); |
| String myself = mainSection.getAttributeValue("OpenIDE-Module"); |
| if (myself == null) { |
| myself = mainSection.getAttributeValue("Bundle-SymbolicName"); |
| isOSGiMode = myself != null; |
| } |
| if (myself == null) { |
| throw new BuildException("No OpenIDE-Module in " + manifestFile); |
| } |
| int slash = myself.indexOf('/'); |
| if (slash == -1) { |
| ownCnb = myself; |
| } else { |
| ownCnb = myself.substring(0, slash); |
| } |
| String cnbs = getProject().getProperty("code.name.base.slashes"); |
| String cnbDots = (cnbs != null) ? cnbs.replace('/', '.') : null; |
| if (!ownCnb.equals(cnbDots)) { |
| // #58248: make sure these stay in synch. |
| throw new BuildException("Mismatch in module code name base: manifest says " + ownCnb + |
| " but project.xml says " + cnbDots, getLocation()); |
| } |
| } finally { |
| is.close(); |
| } |
| |
| if (isOSGiMode) { |
| added.addConfiguredAttribute(new Manifest.Attribute("Bundle-ManifestVersion", "2")); // NOI18N |
| } |
| |
| String pubPkgs = getProject().getProperty("public.packages"); |
| if (pubPkgs == null) { |
| throw new BuildException("Must have defined 'public.packages'", getLocation()); |
| } |
| if (isOSGiMode) { |
| if (pubPkgs != null && !pubPkgs.equals("-")) { |
| added.addConfiguredAttribute(new Manifest.Attribute("Export-Package", pubPkgs.replaceAll("\\.\\*", ""))); // NOI18N |
| } |
| } else { |
| added.addConfiguredAttribute(new Manifest.Attribute("OpenIDE-Module-Public-Packages", pubPkgs)); |
| } |
| String friends = getProject().getProperty("friends"); |
| if (friends != null) { |
| if (isOSGiMode) { |
| throw new BuildException("friends defined, yet OSGi does not support that " + friends); |
| } |
| added.addConfiguredAttribute(new Manifest.Attribute("OpenIDE-Module-Friends", friends)); |
| } |
| // #52354: define Class-Path in the manifest automatically. |
| String javahelpClassPathExtension = getProject().getProperty("javahelp.class.path.extension"); |
| String classPathExtensions = getProject().getProperty("class.path.extensions"); |
| String cp; |
| if (javahelpClassPathExtension != null) { |
| if (classPathExtensions != null) { |
| cp = classPathExtensions + " " + javahelpClassPathExtension; |
| } else { |
| cp = javahelpClassPathExtension; |
| } |
| } else { |
| cp = classPathExtensions; |
| } |
| if (cp != null) { |
| added.addConfiguredAttribute(new Manifest.Attribute("Class-Path", cp)); |
| } |
| String moduleDeps = getProject().getProperty("module.dependencies"); |
| if (moduleDeps != null) { |
| if (isOSGiMode) { |
| StringBuilder sb = new StringBuilder(); |
| String sep = ""; |
| for (String one : moduleDeps.split(",")) { |
| if (one.indexOf('=') != -1) { |
| throw new BuildException("Implementation dependencies not supported in Netigso mode: " + one); |
| } |
| String versionS, codename; |
| int greaterThan = one.indexOf('>'); |
| if (greaterThan == -1) { |
| versionS = "0"; |
| codename = one.trim(); |
| } else { |
| versionS = one.substring(greaterThan + 1).trim(); |
| codename = one.substring(0, greaterThan).trim(); |
| } |
| if (codename.equals("org.netbeans.libs.osgi")) { |
| // #184434: do not include a dep on a NB pseudomodule. |
| // apisupport's BundleActivator wizard anyway adds to manifest: |
| // Import-Package: org.osgi.framework |
| continue; |
| } |
| int[] version = parseDecimal(versionS, 3); |
| int slash = codename.indexOf('/'); |
| int hiMajor; |
| String cnb; |
| if (slash >= 0) { |
| cnb = codename.substring(0, slash).trim(); |
| String range = codename.substring(slash + 1).trim(); |
| int dash = range.indexOf('-'); |
| if (dash == -1) { |
| hiMajor = Integer.parseInt(range); |
| version[0] += 100 * hiMajor; |
| } else { |
| hiMajor = Integer.parseInt(range.substring(dash + 1)); |
| version[0] += 100 * Integer.parseInt(range.substring(0, dash)); |
| } |
| } else { |
| cnb = codename; |
| hiMajor = 0; |
| } |
| sb.append(sep); |
| sep = ", "; |
| sb.append(cnb).append(";bundle-version=\"["); |
| String conditionalDot = ""; |
| for (int i = 0; i < version.length; i++) { |
| sb.append(conditionalDot); |
| sb.append(version[i]); |
| conditionalDot = "."; |
| } |
| sb.append(",").append((hiMajor + 1) * 100).append(")\""); |
| } |
| added.addConfiguredAttribute(new Manifest.Attribute("Require-Bundle", sb.toString())); // NOI18N |
| } else { |
| added.addConfiguredAttribute(new Manifest.Attribute("OpenIDE-Module-Module-Dependencies", moduleDeps)); |
| } |
| } |
| String javaDep = getProject().getProperty("javac.target"); |
| if (javaDep != null && javaDep.matches("[0-9]+(\\.[0-9]+)*")) { |
| if (isOSGiMode) { |
| if (javaDep.matches("1\\.[0-5]")) { |
| added.addConfiguredAttribute(new Manifest.Attribute("Bundle-RequiredExecutionEnvironment", "J2SE-" + javaDep)); |
| } else { |
| added.addConfiguredAttribute(new Manifest.Attribute("Bundle-RequiredExecutionEnvironment", "JavaSE-" + javaDep)); |
| } |
| } else { |
| added.addConfiguredAttribute(new Manifest.Attribute("OpenIDE-Module-Java-Dependencies", "Java > " + javaDep)); |
| } |
| } |
| String buildNumber = getProject().getProperty("buildnumber"); |
| if (buildNumber == null) { |
| throw new BuildException("Must have defined 'buildnumber'", getLocation()); |
| } |
| String attrToAdd = implVers != null ? "OpenIDE-Module-Build-Version" : "OpenIDE-Module-Implementation-Version"; |
| added.addConfiguredAttribute(new Manifest.Attribute(attrToAdd, buildNumber)); |
| // If spec.version.base is defined, use it, after tacking on any numeric impl deps (sorted by CNB of the dep for stability), |
| // and also using the implementation version of this module as well if it is numeric. |
| // This trick makes sure that if you have an impl dep on some module which changes its (numeric) impl version, |
| // your spec version will also change correspondingly, so e.g. Auto Update will see a new version of your module too. |
| String specVersBase = getProject().getProperty("spec.version.base"); |
| if (specVersBase != null) { |
| boolean edited = false; |
| if (implVers != null) { |
| try { |
| parseInt(implVers); |
| specVersBase += "." + implVers; |
| edited = true; |
| } catch (NumberFormatException e) { |
| // OK, ignore it, not numeric. |
| specVersBaseWarning(manifestFile, "use of spec.version.base with non-integer OpenIDE-Module-Implementation-Version"); |
| } |
| } |
| SortedMap<String,Integer> additions = new TreeMap<>(); |
| if (moduleDeps != null) { |
| for (String individualDep : COMMA_SPACE.split(moduleDeps)) { |
| Matcher m = IMPL_DEP.matcher(individualDep); |
| if (m.matches()) { |
| String cnb = m.group(1); |
| String version = m.group(3); |
| try { |
| additions.put(cnb, parseInt(version)); |
| } catch (NumberFormatException e) { |
| // OK, ignore this one, not numeric. |
| specVersBaseWarning(manifestFile, |
| "use of spec.version.base with non-integer OpenIDE-Module-Implementation-Version from " + cnb); |
| } |
| } |
| } |
| } |
| for (int version : additions.values()) { |
| specVersBase += "." + version; |
| edited = true; |
| } |
| |
| String versionTag = isOSGiMode ? "Bundle-Version" : "OpenIDE-Module-Specification-Version"; |
| |
| if (edited) { |
| log("Computed " + versionTag + ": " + specVersBase); |
| } else { |
| specVersBaseWarning(manifestFile, |
| "using spec.version.base for no reason; could just use " + versionTag + " statically in the manifest"); |
| } |
| if (staticManifest.getMainSection().getAttributeValue(versionTag) != null) { |
| specVersBaseWarning(manifestFile, |
| "attempting to use spec.version.base while some " + versionTag + " is statically defined in manifest.mf; this cannot work"); |
| } else { |
| added.addConfiguredAttribute(new Manifest.Attribute(versionTag, specVersBase)); |
| } |
| } else if (moduleDeps != null && moduleDeps.indexOf('=') != -1) { |
| specVersBaseWarning(manifestFile, |
| "not using spec.version.base, yet declaring implementation dependencies; may lead to problems with Auto Update"); |
| } else if (implVers != null) { |
| if (specVersBase == null) { |
| specVersBaseWarning(manifestFile, |
| "not using spec.version.base, yet declaring implementation version; may lead to problems with Auto Update"); |
| } else { |
| try { |
| parseInt(implVers); |
| } catch (NumberFormatException e) { |
| specVersBaseWarning(manifestFile, |
| "use of non-integer OpenIDE-Module-Implementation-Version may be problematic for clients trying to use spec.version.base"); |
| } |
| } |
| } |
| if (isOSGiMode && added.getMainSection().getAttribute("Bundle-Version") == null && specVer != null) { |
| added.getMainSection().addConfiguredAttribute(new Manifest.Attribute("Bundle-Version", specVer)); |
| } |
| |
| boolean old = false; // #110661 |
| String destDir = getProject().getProperty("netbeans.dest.dir"); |
| if (destDir != null) { |
| for (File cluster : getProject().resolveFile(destDir).listFiles()) { |
| if (new File(cluster, "modules/org-netbeans-modules-autoupdate.jar").isFile()) { |
| old = true; |
| break; |
| } |
| } |
| } |
| if (!old) { |
| added.addConfiguredAttribute(new Manifest.Attribute("AutoUpdate-Show-In-Client", Boolean.toString( // #110572 |
| !Project.toBoolean(getProject().getProperty("is.autoload")) && !Project.toBoolean(getProject().getProperty("is.eager")) && |
| "modules".equals(getProject().getProperty("module.jar.dir"))))); |
| } |
| // Now ask the regular <jar> task to add all this stuff to the regular manifest.mf. |
| added.merge(staticManifest); |
| if (!"lib".equals (getProject().getProperty("module.jar.dir")) && !isOSGiMode) { |
| // modules in lib cannot request this token |
| String key = "OpenIDE-Module-Requires"; |
| String token = "org.openide.modules.ModuleFormat1"; |
| String requires = staticManifest.getMainSection().getAttributeValue(key); |
| String newRequires; |
| if (requires != null) { |
| // #59671: have to modify it, not just use super.setManifest(manifestFile). |
| added.getMainSection().removeAttribute(key); |
| newRequires = requires + ", " + token; |
| } else { |
| newRequires = token; |
| } |
| added.addConfiguredAttribute(new Manifest.Attribute(key, newRequires)); |
| } |
| addConfiguredManifest(added); |
| } catch (Exception e) { |
| throw new BuildException(e, getLocation()); |
| } |
| } |
| |
| private int[] parseDecimal(String trim, int max) { |
| String[] segments = trim.split("\\."); |
| int[] arr = new int[segments.length > max ? max : segments.length]; |
| for (int i = 0; i < arr.length; i++) { |
| arr[i] = parseInt(segments[i]); |
| } |
| return arr; |
| } |
| private static int parseInt(String v) throws NumberFormatException { |
| if (!v.matches("0|[1-9][0-9]*")) { // 050123 is a date, -12 is illegal, etc. |
| throw new NumberFormatException(v); |
| } |
| return Integer.parseInt(v); |
| } |
| |
| private void specVersBaseWarning(File manifestFile, String message) throws BuildException { |
| message = manifestFile + ": " + message + "\n(see http://wiki.netbeans.org/DevFaqImplementationDependency)" + |
| "\n(define spec.version.base.fatal.warning=false in project.properties to make this be a nonfatal warning)"; |
| if (Project.toBoolean(getProject().getProperty("spec.version.base.fatal.warning"))) { |
| throw new BuildException(message); |
| } else { |
| log(message, Project.MSG_WARN); |
| } |
| } |
| |
| @Override |
| public void execute() throws BuildException { |
| super.execute(); |
| if (stamp != null) { |
| log("Stamp " + stamp + " against " + zipFile, Project.MSG_DEBUG); |
| if (stamp.lastModified() < zipFile.lastModified()) { |
| try { |
| stamp.getParentFile().mkdirs(); |
| stamp.createNewFile(); |
| stamp.setLastModified(zipFile.lastModified()); |
| } catch (IOException ex) { |
| throw new BuildException(ex); |
| } |
| } |
| } |
| } |
| |
| |
| } |