--- a/apisupport.harness/manifest.mf +++ a/apisupport.harness/manifest.mf @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.apisupport.harness -OpenIDE-Module-Specification-Version: 1.15 +OpenIDE-Module-Specification-Version: 1.16 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/apisupport/harness/Bundle.properties --- a/apisupport.harness/nbproject/project.properties +++ a/apisupport.harness/nbproject/project.properties @@ -110,3 +110,5 @@ test.unit.cp.extra=${cluster}/jnlp/jnlp-launcher.jar javadoc.arch=${basedir}/arch.xml + +nbm.executable.files=launchers/app.sh --- a/apisupport.harness/release/README +++ a/apisupport.harness/release/README @@ -725,6 +725,10 @@ nbm.distribution - optional URL to location where your NBM can be downloaded from. +nbm.executable.files [since 6.8] - space separated patterns of files relative to +cluster directory for which executable permissions should be set on Unix system +when building the module and on NBM installation/update in runtime. + nbm.homepage - optional URL to HTML homepage for background about the module. nbm.is.global - set to "true" if NBM cannot be installed in user directory. --- a/apisupport.harness/taskdefs.properties +++ a/apisupport.harness/taskdefs.properties @@ -45,6 +45,7 @@ makemasterjnlp=org.netbeans.nbbuild.MakeMasterJNLP parseprojectxml=org.netbeans.nbbuild.ParseProjectXml genlist=org.netbeans.nbbuild.MakeListOfNBM +genexecutables=org.netbeans.nbbuild.MakeExecutableFilesList arch=org.netbeans.nbbuild.Arch fixdependencies=org.netbeans.nbbuild.FixDependencies sortsuitemodules=org.netbeans.nbbuild.SortSuiteModules --- a/autoupdate.services/libsrc/org/netbeans/updater/ModuleUpdater.java +++ a/autoupdate.services/libsrc/org/netbeans/updater/ModuleUpdater.java @@ -42,6 +42,7 @@ package org.netbeans.updater; import java.io.*; +import java.lang.reflect.Method; import java.util.*; import java.util.jar.*; @@ -97,6 +98,8 @@ public static final String UPDATER_JAR = "updater.jar"; // NOI18N public static final String AUTOUPDATE_UPDATER_JAR_PATH = "netbeans/modules/ext/" + UPDATER_JAR; // NOI18N + + public static final String EXECUTABLE_FILES_ENTRY = "Info/executable.list"; /** files that are supposed to be installed (when running inside the ide) */ private Collection forInstall; @@ -354,6 +357,8 @@ try { jarFile = new JarFile (nbm); Enumeration entries = jarFile.entries(); + List executableFiles = readExecutableFilesList(jarFile); + List filesToChmod = new ArrayList (); while( entries.hasMoreElements() ) { JarEntry entry = (JarEntry) entries.nextElement(); checkStop(); @@ -383,19 +388,26 @@ destFile.getParentFile ().mkdirs (); } bytesRead = copyStreams( jarFile.getInputStream( entry ), new FileOutputStream( destFile ), bytesRead ); + if(executableFiles.contains(pathTo)) { + filesToChmod.add(destFile); + } UpdaterFrame.setProgressValue( bytesRead ); } } else if ( entry.getName().startsWith( UPDATE_MAIN_DIR )&& !entry.isDirectory() ) { - // run main - File destFile = new File (getMainDirectory (cluster), - entry.getName().substring(UPDATE_MAIN_DIR.length() + 1) ); + // run main + String pathTo = entry.getName().substring(UPDATE_MAIN_DIR.length() + 1); + File destFile = new File (getMainDirectory (cluster), pathTo); + if(executableFiles.contains(pathTo)) { + filesToChmod.add(destFile); + } destFile.getParentFile ().mkdirs (); hasMainClass = true; bytesRead = copyStreams( jarFile.getInputStream( entry ), new FileOutputStream( destFile ), bytesRead ); UpdaterFrame.setProgressValue( bytesRead ); } } + chmod(filesToChmod); if ( hasMainClass ) { MainConfig mconfig = new MainConfig (getMainDirString (cluster) + UpdateTracking.FILE_SEPARATOR + JVM_PARAMS_FILE, cluster); if (mconfig.isValid()) { @@ -455,7 +467,82 @@ t.deleteUnusedFiles (); } } - + + private List readExecutableFilesList(JarFile jarFile) { + List list = new ArrayList(); + JarEntry executableFilesEntry = jarFile.getJarEntry(EXECUTABLE_FILES_ENTRY); + if (executableFilesEntry != null) { + BufferedInputStream bis = null; + try { + bis = new BufferedInputStream(jarFile.getInputStream(executableFilesEntry)); + byte[] buffer = new byte[8192]; + int c = 0; + StringBuilder sb = new StringBuilder(); + while ((c = bis.read(buffer)) != -1) { + sb.append(new String(buffer, 0, c)); + } + String[] lines = sb.toString().trim().split("(?:\r\n|\n|\r)"); + for (String s : lines) { + if (s.trim().length() > 0) { + list.add(s); + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (bis != null) { + try { + bis.close(); + } catch (IOException e) { + } + } + } + } + return list; + } + + private void chmod(List executableFiles) { + if(isWindows() || executableFiles.isEmpty()) { + return; + } + if (System.getProperty("java.version").startsWith("1.5")) { + // Find chmod + File chmod = new File("/bin/chmod"); // NOI18N + if (!chmod.isFile()) { + chmod = new File("/usr/bin/chmod"); // NOI18N + } + if (chmod.isFile()) { + Process process = null; + try { + List command = new ArrayList(); + command.add(chmod.getAbsolutePath()); + command.add("a+x"); + for(File executableFile : executableFiles) { + command.add(executableFile.getAbsolutePath()); + } + process = new ProcessBuilder(command).start(); + process.waitFor(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (process != null) { + process.destroy(); + } + } + } + } else { + // Determine if java.io.File.setExecutable method is supported + try { + Method setExecutableMethod = File.class.getMethod("setExecutable", Boolean.TYPE, Boolean.TYPE); + for(File executableFile : executableFiles) { + setExecutableMethod.invoke(executableFile, true, false); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + public static boolean trickyDeleteOnWindows(File destFile) { assert isWindows() : "Call it only on Windows but system is " + System.getProperty("os.name"); File f = new File(destFile.getParentFile(), destFile.getName()); --- a/autoupdate.services/manifest.mf +++ a/autoupdate.services/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.autoupdate.services OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/autoupdate/services/resources/Bundle.properties -OpenIDE-Module-Specification-Version: 1.9 +OpenIDE-Module-Specification-Version: 1.10 AutoUpdate-Show-In-Client: false AutoUpdate-Essential-Module: true --- a/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/ExecutablePermissionsTest.java +++ a/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/ExecutablePermissionsTest.java @@ -0,0 +1,174 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun + * Microsystems, Inc. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.modules.autoupdate.services; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import org.netbeans.api.autoupdate.UpdateUnit; +import org.netbeans.spi.autoupdate.UpdateItem; +import org.netbeans.spi.autoupdate.UpdateProvider; + +/** + * + * @author Dmitry Lipin + */ +public class ExecutablePermissionsTest extends NbmAdvancedTestCase { + + private UpdateProvider p = null; + private String testModuleVersion = "1.0"; + private String testModuleName = "org.yourorghere.executable.permissions"; + + public ExecutablePermissionsTest(String name) { + super(name); + } + + @Override + protected void setUp() throws IOException, Exception { + super.setUp(); + } + + @Override + public boolean canRun() { + return super.canRun() && !System.getProperty("os.name").startsWith("Windows"); + } + + private String generateExecutablePermissionsModuleElement() { + String res = "\n\n"; + res += "\n"; + res += ""; + return res; + } + + public void testExecutablePermissionsModule() throws IOException { + String os = !org.openide.util.Utilities.isUnix() ? "Windows" : "Unix"; + String catalog = generateCatalog(generateExecutablePermissionsModuleElement()); + + p = createUpdateProvider(catalog); + p.refresh(true); + + Map unitImpls = new HashMap(); + Map updates = p.getUpdateItems(); + assertNotNull("Some modules are installed.", updates); + assertFalse("Some modules are installed.", updates.isEmpty()); + assertTrue(testModuleName + " found in parsed items.", updates.keySet().contains(testModuleName + "_" + testModuleVersion)); + + Map newImpls = UpdateUnitFactory.getDefault().appendUpdateItems(unitImpls, p); + assertNotNull("Some units found.", newImpls); + assertFalse("Some units found.", newImpls.isEmpty()); + + UpdateUnit u1 = newImpls.get(testModuleName); + installUpdateUnit(u1); + File f = new File(userDir, "bin/start.sh"); + assertTrue("File " + f + " should exist after module installation", f.exists()); + if (System.getProperty("java.version").startsWith("1.5")) { + File ls = new File("/bin/ls"); + if (!ls.isFile()) { + ls = new File("/usr/bin/ls"); + } + if (ls.isFile()) { + String output = readCommandOutput(ls.getAbsolutePath(), "-la", f.getAbsolutePath()).trim(); + int index = output.indexOf(" "); + assertFalse("Can`t read permissions from output:\n" + output, index == -1); + String permissions = output.substring(0, index); + assertTrue("File " + f + " does not have executable permissions after installation, actual perms : " + permissions, + permissions.matches(".*x.*x.*x.*")); + } + } else { + Method canExecuteMethod = null; + try { + canExecuteMethod = File.class.getMethod("canExecute", new Class[]{}); + } catch (Exception e) { + assertTrue("java.io.File.canExecute method is not accessible", false); + } + boolean canExecute = false; + try { + canExecute = (Boolean) canExecuteMethod.invoke(f); + } catch (Exception e) { + assertTrue("File " + f + " is not executable after module installation", canExecute); + e.printStackTrace(); + } + } + } + + private String readCommandOutput(String... command) { + ProcessBuilder builder = new ProcessBuilder(command); + boolean doRun = true; + StringBuilder sb = new StringBuilder(); + byte[] bytes = new byte[8192]; + int c = 0; + + try { + Process process = builder.start(); + while (doRun) { + try { + Thread.sleep(20); + } catch (InterruptedException e) { + } + try { + process.exitValue(); + doRun = false; + } catch (IllegalThreadStateException e) { + ; // do nothing - the process is still running + } + InputStream is = process.getInputStream(); + while ((c = is.read(bytes)) != -1) { + sb.append(new String(bytes, 0, c)); + } + } + return sb.toString(); + } catch (IOException e) { + e.printStackTrace(); + return new String(); + } + } +} --- a/nbbuild/antsrc/org/netbeans/nbbuild/MakeExecutableFilesList.java +++ a/nbbuild/antsrc/org/netbeans/nbbuild/MakeExecutableFilesList.java @@ -0,0 +1,150 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ +package org.netbeans.nbbuild; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +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.types.FileSet; + +/** + * Create the list of files which require executable permissions to be set. + * @author Dmitry Lipin + */ +public class MakeExecutableFilesList extends Task { + + File targetDir = null; + ModuleFileSet mfs = null; + String executableIncludes = null; + //String propertyName = null; + String refid; + private static final String ALL_FILES_PATTERN = "**"; + + + //String sep = null; + + public MakeExecutableFilesList() { + } + + @Override + public void execute() throws BuildException { + if (targetDir == null) { + throw new BuildException("You have to specify target directoty"); + } + //if (propertyName == null) { + // throw new BuildException("You have to specify property name for storing executable files list"); + //} + if (refid == null) { + throw new BuildException("You have to specify reference id for storing executable files list"); + } + + DirectoryScanner moduleFilesDs = mfs.getDirectoryScanner(this.getProject()); + moduleFilesDs.scan(); + FileSet efs = new FileSet(); + efs.setDir(targetDir); + efs.setProject(getProject()); + if(executableIncludes==null || executableIncludes.length() == 0) { + efs.setExcludes(ALL_FILES_PATTERN); + } else { + efs.setIncludes(executableIncludes); + } + DirectoryScanner executableDs = efs.getDirectoryScanner(this.getProject()); + executableDs.scan(); + String[] moduleIncludes = moduleFilesDs.getIncludedFiles(); + + List moduleExecutables = new ArrayList(); + for (String executable : executableDs.getIncludedFiles()) { + for (String include : moduleIncludes) { + if (executable.equals(include)) { + moduleExecutables.add(executable); + break; + } + } + } + + if (!moduleExecutables.isEmpty()) { + log("Executable files in module: " + moduleExecutables, Project.MSG_VERBOSE); + } else { + log("No executable files in module: " + moduleExecutables, Project.MSG_VERBOSE); + } + FileSet fs = new FileSet(); + fs.setDir(targetDir); + fs.setProject(getProject()); + StringBuilder sb = new StringBuilder(""); + for (int i = 0; i < moduleExecutables.size(); i++) { + if (i != 0) { + sb.append(" "); + } + sb.append(moduleExecutables.get(i).replace("\\", "/")); + } + if(sb.length()>0) { + fs.setIncludes(sb.toString()); + } else { + fs.setExcludes(ALL_FILES_PATTERN); + } + DirectoryScanner ds = fs.getDirectoryScanner(); + ds.scan(); + log("Executable files length: " + ds.getIncludedFilesCount()); + getProject().addReference(refid, fs); + } + + public void setRefid(String refid) { + this.refid = refid; + } + + public void setTargetDir(File s) { + this.targetDir = s; + } + + public void setIncludes(String executableIncludes) { + this.executableIncludes = executableIncludes; + } + public ModuleFileSet createModuleFileSet() { + return (mfs = new ModuleFileSet()); + } + + + + public static class ModuleFileSet extends FileSet { + } +} --- a/nbbuild/antsrc/org/netbeans/nbbuild/MakeNBM.java +++ a/nbbuild/antsrc/org/netbeans/nbbuild/MakeNBM.java @@ -73,10 +73,12 @@ 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.Jar; import org.apache.tools.ant.taskdefs.SignJar; +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; @@ -320,7 +322,8 @@ private ArrayList moduleAttributes = null; private Attributes englishAttr = null; private Path updaterJar; - + private FileSet executablesSet; + /** Try to find and create localized info.xml files */ public void setLocales(String s) { locales = new ArrayList(); @@ -342,6 +345,11 @@ public void setFile(File file) { this.file = file; } + + /** List of executable files in NBM concatinated by ${line.separator}. */ + public FileSet createExecutables() { + return (executablesSet = new FileSet()); + } /** Module manifest needed for versioning. * @deprecated Use {@link #setModule} instead. @@ -653,12 +661,43 @@ for (ZipFileSet zfs : infoXMLFileSets) { jar.addFileset(zfs); } + DirectoryScanner ds = executablesSet.getDirectoryScanner(); + ds.scan(); + String [] executables = ds.getIncludedFiles(); + + if(executables!=null && 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"); + OutputStream infoStream = new FileOutputStream (executablesFile); + try { + infoStream.write(sb.toString().getBytes()); + } finally { + infoStream.close (); + } + } 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); + } if (main != null) { // Add the main dir main.setPrefix("main"); // use main prefix jar.addZipfileset(main); } - + jar.setCompress(true); jar.setLocation(getLocation()); jar.init (); --- a/nbbuild/default.xml +++ a/nbbuild/default.xml @@ -60,6 +60,9 @@ + --- a/nbbuild/templates/common.xml +++ a/nbbuild/templates/common.xml @@ -252,6 +252,14 @@ + + + + + + + + @@ -332,7 +340,17 @@ - + + + + + + + + + + + @@ -354,6 +372,7 @@ +