| package org.apache.netbeans.nbm; |
| |
| /* |
| * 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. |
| */ |
| |
| import com.google.common.collect.Sets; |
| import java.io.BufferedOutputStream; |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| import java.util.jar.JarOutputStream; |
| import java.util.jar.Pack200; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.zip.CRC32; |
| import java.util.zip.GZIPInputStream; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.artifact.factory.ArtifactFactory; |
| import org.apache.maven.artifact.repository.ArtifactRepository; |
| import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException; |
| import org.apache.maven.artifact.resolver.ArtifactNotFoundException; |
| import org.apache.maven.artifact.resolver.ArtifactResolutionException; |
| import org.apache.maven.artifact.resolver.ArtifactResolver; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.plugin.logging.Log; |
| import org.apache.maven.plugins.annotations.Component; |
| import org.apache.maven.plugins.annotations.LifecyclePhase; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.plugins.annotations.ResolutionScope; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.netbeans.nbm.utils.ExamineManifest; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.filters.StringInputStream; |
| import org.apache.tools.ant.taskdefs.Chmod; |
| import org.apache.tools.ant.types.FileSet; |
| import org.codehaus.plexus.util.FileUtils; |
| import org.codehaus.plexus.util.IOUtil; |
| import org.codehaus.plexus.util.StringUtils; |
| import org.codehaus.plexus.util.io.InputStreamFacade; |
| import org.netbeans.nbbuild.MakeListOfNBM; |
| |
| /** |
| * Create the NetBeans module clusters/application for the 'nbm-application' packaging projects |
| * |
| * @author Milos Kleint |
| */ |
| @Mojo( name = "cluster-app", |
| defaultPhase = LifecyclePhase.PACKAGE, |
| requiresProject = true, |
| threadSafe = true, |
| requiresDependencyResolution = ResolutionScope.RUNTIME ) |
| public class CreateClusterAppMojo |
| extends AbstractNbmMojo |
| { |
| |
| /** |
| * output directory where the the NetBeans application will be created. |
| */ |
| @Parameter( defaultValue = "${project.build.directory}", required = true ) |
| private File outputDirectory; |
| |
| /** |
| * The Maven Project. |
| */ |
| @Parameter( required = true, readonly = true, property = "project" ) |
| private MavenProject project; |
| |
| /** |
| * The branding token for the application based on NetBeans platform. |
| */ |
| @Parameter( property = "netbeans.branding.token", required = true ) |
| protected String brandingToken; |
| |
| /** |
| * Optional path to custom etc/${brandingToken}.conf file. If not defined, a default template will be used. |
| */ |
| @Parameter( property = "netbeans.conf.file" ) |
| private File etcConfFile; |
| |
| /** |
| * Optional path to custom etc/${brandingToken}.clusters file. If not defined, a default one will be generated. |
| */ |
| @Parameter( property = "netbeans.clusters.file" ) |
| private File etcClustersFile; |
| |
| /** |
| * Directory which contains the executables that will be copied to the final application's bin/ directory. Please |
| * note that the name of the executables shall generally match the brandingToken parameter. Otherwise the |
| * application can be wrongly branded. |
| */ |
| @Parameter( property = "netbeans.bin.directory" ) |
| private File binDirectory; |
| |
| /** |
| * If the depending NBM file doesn't contain any application cluster information, use this value as default location |
| * for such module NBMs. |
| * |
| * @since 3.2 |
| */ |
| @Parameter( defaultValue = "extra" ) |
| private String defaultCluster; |
| |
| /** |
| * attempts to verify the integrity of module artifacts making sure that all dependencies are included and that all |
| * required tokens are provided |
| * |
| * @since 3.10 |
| */ |
| @Parameter( defaultValue = "true", property = "netbeans.verify.integrity" ) |
| private boolean verifyIntegrity; |
| |
| /** |
| * @since 4.12 |
| */ |
| @Parameter( defaultValue = "org.netbeans", property = "groupIdPrefix" ) |
| private String groupIdPrefix; |
| |
| private final Collection<String> defaultPlatformTokens = Arrays.asList( new String[] |
| { |
| "org.openide.modules.os.Windows", |
| "org.openide.modules.os.Unix", |
| "org.openide.modules.os.MacOSX", |
| "org.openide.modules.os.OS2", |
| "org.openide.modules.os.PlainUnix", |
| "org.openide.modules.os.Linux", |
| "org.openide.modules.os.Solaris", |
| "org.openide.modules.ModuleFormat1", |
| "org.openide.modules.ModuleFormat2", |
| "org.openide.modules.jre.JavaFX" //MNBMODULE-234 |
| } ); |
| |
| // <editor-fold defaultstate="collapsed" desc="Component parameters"> |
| @Component |
| private ArtifactFactory artifactFactory; |
| |
| @Component |
| private ArtifactResolver artifactResolver; |
| |
| /** |
| * Local maven repository. |
| * |
| */ |
| @Parameter( required = true, readonly = true, property = "localRepository" ) |
| protected ArtifactRepository localRepository; |
| |
| // end of component params custom code folding |
| // </editor-fold> |
| @Override |
| public void execute() |
| throws MojoExecutionException, MojoFailureException |
| { |
| |
| File nbmBuildDirFile = new File( outputDirectory, brandingToken ); |
| if ( !nbmBuildDirFile.exists() ) |
| { |
| nbmBuildDirFile.mkdirs(); |
| } |
| |
| if ( "nbm-application".equals( project.getPackaging() ) ) |
| { |
| Project antProject = registerNbmAntTasks(); |
| |
| Set<String> wrappedBundleCNBs = new HashSet<>( 100 ); |
| Map<String, Set<String>> clusterDependencies = new HashMap<>(); |
| Map<String, Set<String>> clusterModules = new HashMap<>(); |
| |
| //verify integrity |
| Set<String> modulesCNBs = new HashSet<>( SET_INITIAL_SIZE ); |
| Set<String> dependencyCNBs = new HashSet<>( SET_INITIAL_SIZE ); |
| Map<String, Set<String>> dependencyCNBBacktraces = new HashMap<>( MAP_INITIALSIZE ); |
| Set<String> requireTokens = new HashSet<>( MAP_INITIALSIZE ); |
| Map<String, Set<String>> requireTokensBacktraces = new HashMap<>( MAP_INITIALSIZE ); |
| Set<String> provideTokens = new HashSet<>( MAP_INITIALSIZE ); |
| Set<String> osgiImports = new HashSet<>( MAP_INITIALSIZE ); |
| Map<String, Set<String>> osgiImportsBacktraces = new HashMap<>( MAP_INITIALSIZE ); |
| Set<String> osgiExports = new HashSet<>( MAP_INITIALSIZE ); |
| //a way to deal with nb module declaring xxx.** (subpackages) declaration that is consumed by osgi imports |
| Set<String> osgiExportsSubs = new HashSet<>( MAP_INITIALSIZE ); |
| |
| List<BundleTuple> bundles = new ArrayList<>(); |
| |
| @SuppressWarnings( "unchecked" ) |
| Set<Artifact> artifacts = project.getArtifacts(); |
| for ( Artifact art : artifacts ) |
| { |
| ArtifactResult res |
| = turnJarToNbmFile( art, artifactFactory, artifactResolver, project, |
| localRepository ); |
| if ( res.hasConvertedArtifact() ) |
| { |
| art = res.getConvertedArtifact(); |
| } |
| |
| if ( art.getType().equals( "nbm-file" ) ) |
| { |
| try |
| { |
| JarFile jf = new JarFile( art.getFile() ); |
| try |
| { |
| String clusterName = findCluster( jf ); |
| ClusterTuple cluster = processCluster( clusterName, nbmBuildDirFile, art ); |
| |
| getLog().debug( "Copying " + art.getId() + " to cluster " + clusterName ); |
| Enumeration<JarEntry> enu = jf.entries(); |
| |
| // we need to trigger this ant task to generate the update_tracking file. |
| MakeListOfNBM makeTask = (MakeListOfNBM) antProject.createTask( "genlist" ); |
| antProject.setNewProperty( "module.name", art.getFile().getName() ); // TODO |
| antProject.setProperty( "cluster.dir", clusterName ); |
| FileSet set = makeTask.createFileSet(); |
| set.setDir( cluster.location ); |
| makeTask.setOutputfiledir( cluster.location ); |
| String[] executables = null; |
| File classpathRoot = null; |
| String classPath = null; |
| while ( enu.hasMoreElements() ) |
| { |
| JarEntry ent = enu.nextElement(); |
| String name = ent.getName(); |
| //MNBMODULE-176 |
| if ( name.equals( "Info/executables.list" ) ) |
| { |
| if ( cluster.newer ) |
| { |
| InputStream is = jf.getInputStream( ent ); |
| executables = StringUtils.split( IOUtil.toString( is, "UTF-8" ), "\n" ); |
| } |
| } |
| else if ( name.startsWith( "netbeans/" ) ) |
| { // ignore everything else. |
| String path = clusterName + name.substring( "netbeans".length() ); |
| boolean ispack200 = path.endsWith( ".jar.pack.gz" ); |
| if ( ispack200 ) |
| { |
| path = path.replace( ".jar.pack.gz", ".jar" ); |
| } |
| File fl = new File( nbmBuildDirFile, path.replace( "/", File.separator ) ); |
| String part = name.substring( "netbeans/".length() ); |
| if ( ispack200 ) |
| { |
| part = part.replace( ".jar.pack.gz", ".jar" ); |
| } |
| if ( cluster.newer ) |
| { |
| if ( ent.isDirectory() ) |
| { |
| fl.mkdirs(); |
| } |
| else if ( path.endsWith( ".external" ) ) // MNBMODULE-138 |
| { |
| InputStream is = jf.getInputStream( ent ); |
| try |
| { |
| externalDownload( new File( fl.getParentFile(), |
| fl.getName().replaceFirst( "[.]external$", |
| "" ) ), is ); |
| } |
| finally |
| { |
| is.close(); |
| } |
| //MNBMODULE-192 |
| set.appendIncludes( new String[] |
| { |
| name.substring( "netbeans/".length(), name.length() - ".external". |
| length() ) |
| } ); |
| } |
| else |
| { |
| set.appendIncludes( new String[] |
| { |
| part |
| } ); |
| |
| fl.getParentFile().mkdirs(); |
| fl.createNewFile(); |
| BufferedOutputStream outstream = null; |
| try |
| { |
| outstream = new BufferedOutputStream( new FileOutputStream( fl ) ); |
| InputStream instream = jf.getInputStream( ent ); |
| if ( ispack200 ) |
| { |
| Pack200.Unpacker unp = Pack200.newUnpacker(); |
| JarOutputStream jos = new JarOutputStream( outstream ); |
| GZIPInputStream gzip = new GZIPInputStream( instream ); |
| try |
| { |
| unp.unpack( gzip, jos ); |
| } |
| finally |
| { |
| jos.close(); |
| } |
| } |
| else |
| { |
| IOUtil.copy( instream, outstream ); |
| } |
| } |
| finally |
| { |
| IOUtil.close( outstream ); |
| } |
| } |
| } |
| |
| // TODO examine netbeans/config/Modules to see if the module is autoload/eager |
| // in verifyIntegrity these could be handled more gracefully than regular modules. |
| // eager is simpler, does not need to have module dependencies satisfied. |
| // autoload needs checking if any of the other modules declares a dependency on it. |
| // if not, also safe to ignore? |
| // now figure which one of the jars is the module jar.. |
| if ( part.matches( "(modules|core|lib)/[^/]+[.]jar" ) ) |
| { |
| ExamineManifest ex = new ExamineManifest( getLog() ); |
| ex.setJarFile( fl ); |
| ex.setPopulateDependencies( true ); |
| ex.checkFile(); |
| if ( ex.isNetBeansModule() ) |
| { |
| makeTask.setModule( part ); |
| addToMap( clusterDependencies, clusterName, ex.getDependencyTokens() ); |
| addToMap( clusterModules, clusterName, Collections.singletonList( ex. |
| getModule() ) ); |
| if ( ex.getClasspath().length() > 0 ) |
| { //MNBMODULE-220 |
| classPath = ex.getClasspath(); |
| classpathRoot = fl.getParentFile(); |
| } |
| } |
| if ( verifyIntegrity ) |
| { |
| dependencyCNBs.addAll( ex.getDependencyTokens() ); |
| modulesCNBs.add( ex.getModule() ); |
| for ( String d : ex.getDependencyTokens() ) |
| { |
| addToMap( dependencyCNBBacktraces, d, Collections.singletonList( ex. |
| getModule() ) ); |
| } |
| if ( ex.isNetBeansModule() ) |
| { |
| requireTokens.addAll( ex.getNetBeansRequiresTokens() ); |
| for ( String r : ex.getNetBeansRequiresTokens() ) |
| { |
| addToMap( requireTokensBacktraces, r, Collections.singletonList( ex. |
| getModule() ) ); |
| } |
| provideTokens.addAll( ex.getNetBeansProvidesTokens() ); |
| for ( String pack : ex.getPackages() ) |
| { |
| if ( pack.endsWith( ".**" ) ) |
| { |
| //what to do with subpackages? |
| pack = pack.substring( 0, pack.length() - ".**".length() ); |
| osgiExportsSubs.add( pack ); |
| } |
| else if ( pack.endsWith( ".*" ) ) |
| { |
| pack = pack.substring( 0, pack.length() - ".*".length() ); |
| osgiExports.add( pack ); |
| } |
| } |
| |
| } |
| } |
| } |
| } |
| } |
| if ( classPath != null ) |
| { // MNBMODULE-220 collect wrappedbundleCNBs, later useful in assignClustersToBundles(), |
| // these get removed from list of bundles. |
| String[] paths = StringUtils.split( classPath, " " ); |
| for ( String path : paths ) |
| { |
| path = path.trim(); |
| File classpathFile = new File( classpathRoot, path ); |
| if ( path.equals( "${java.home}/lib/ext/jfxrt.jar" ) ) |
| { //MNBMODULE-228 |
| String jhm = System.getProperty( "java.home" ); |
| classpathFile = new File( new File( new File( new File( jhm ), "lib" ), "ext" ), |
| "jfxrt.jar" ); |
| if ( !classpathFile.exists() ) |
| { |
| File jdk7 = new File( new File( new File( jhm ), "lib" ), "jfxrt.jar" ); |
| if ( jdk7.exists() ) |
| { |
| classpathFile = jdk7; |
| } |
| } |
| } |
| if ( !classpathFile.isFile() ) |
| { |
| getLog().warn( "Could not resolve Class-Path item in " + art.getId() |
| + ", path is:" + path + ", skipping" ); |
| continue; //try to guard against future failures |
| } |
| ExamineManifest ex = new ExamineManifest( getLog() ); |
| ex.setJarFile( classpathFile ); |
| //ex.setPopulateDependencies( true ); |
| ex.checkFile(); |
| if ( ex.isOsgiBundle() ) |
| { |
| if ( art.getId().contains( groupIdPrefix |
| + ".modules:org-netbeans-modules-maven-embedder" ) ) |
| { |
| // in this case we dont want module-maven-embedder to be considered as |
| // wrapper for his libs guava is provided but ide have it also |
| } |
| else |
| { |
| getLog().info( ex.getModule() + " added by " + art.getId() + "" |
| + classpathFile ); |
| wrappedBundleCNBs.add( ex.getModule() ); |
| } |
| } |
| } |
| } |
| if ( cluster.newer ) |
| { |
| try |
| { |
| makeTask.execute(); |
| } |
| catch ( BuildException e ) |
| { |
| getLog().error( "Cannot Generate update_tracking XML file from " + art.getFile() ); |
| throw new MojoExecutionException( e.getMessage(), e ); |
| } |
| |
| if ( executables != null ) |
| { |
| //MNBMODULE-176 |
| for ( String exec : executables ) |
| { |
| exec = exec.replace( "/", File.separator ); |
| File execFile = new File( cluster.location, exec ); |
| if ( execFile.exists() ) |
| { |
| execFile.setExecutable( true, false ); |
| } |
| } |
| } |
| } |
| |
| } |
| finally |
| { |
| jf.close(); |
| } |
| } |
| catch ( IOException ex ) |
| { |
| getLog().error( art.getFile().getAbsolutePath(), ex ); |
| } |
| } |
| if ( res.isOSGiBundle() ) |
| { |
| ExamineManifest ex = res.getExaminedManifest(); |
| bundles.add( new BundleTuple( art, ex ) ); |
| if ( verifyIntegrity ) |
| { |
| dependencyCNBs.addAll( ex.getDependencyTokens() ); |
| for ( String d : ex.getDependencyTokens() ) |
| { |
| addToMap( dependencyCNBBacktraces, d, Collections.singletonList( ex.getModule() ) ); |
| } |
| modulesCNBs.add( ex.getModule() ); |
| osgiImports.addAll( ex.getOsgiImports() ); |
| for ( String d : ex.getOsgiImports() ) |
| { |
| addToMap( osgiImportsBacktraces, d, Collections.singletonList( ex.getModule() ) ); |
| } |
| |
| osgiExports.addAll( ex.getOsgiExports() ); |
| } |
| } |
| } |
| |
| if ( verifyIntegrity ) |
| { |
| if ( getLog().isDebugEnabled() ) |
| { |
| getLog().debug( "All found codenamebases:" + Arrays.toString( modulesCNBs.toArray() ) ); |
| getLog().debug( "All found OSGI exports:" + Arrays.toString( osgiExports.toArray() ) ); |
| getLog().debug( "All found provided tokens:" + Arrays.toString( provideTokens.toArray() ) ); |
| } |
| dependencyCNBs.removeAll( modulesCNBs ); |
| if ( modulesCNBs.contains( "org.netbeans.modules.netbinox" ) ) |
| { |
| dependencyCNBs.remove( "org.eclipse.osgi" ); //this is special. |
| } |
| osgiImports.removeAll( osgiExports ); |
| Iterator<String> it = osgiImports.iterator(); |
| while ( it.hasNext() ) |
| { |
| String s = it.next(); |
| if ( s.startsWith( "java." ) |
| || s.startsWith( "javax." ) |
| || s.startsWith( "sun." ) |
| || s.startsWith( "org.xml.sax" ) |
| || s.startsWith( "org.w3c.dom" ) |
| || s.startsWith( "org.ietf.jgss" ) ) |
| { |
| it.remove(); |
| continue; |
| } |
| for ( String sub : osgiExportsSubs ) |
| { |
| if ( s.startsWith( sub ) ) |
| { |
| it.remove(); |
| break; |
| } |
| } |
| } |
| requireTokens.removeAll( provideTokens ); |
| requireTokens.removeAll( defaultPlatformTokens ); |
| if ( !dependencyCNBs.isEmpty() || !osgiImports.isEmpty() || !requireTokens.isEmpty() ) |
| { |
| if ( !dependencyCNBs.isEmpty() ) |
| { |
| getLog().error( |
| "Some included modules/bundles depend on these codenamebases but they are not included." |
| + " The application will fail starting up. The missing codenamebases are:" ); |
| for ( String s : dependencyCNBs ) |
| { |
| Set<String> back = dependencyCNBBacktraces.get( s ); |
| getLog().error( " " + s + ( back != null ? " ref: " + Arrays.toString( back. |
| toArray() ) : "" ) ); |
| } |
| } |
| if ( !osgiImports.isEmpty() ) |
| { |
| getLog().error( |
| "Some OSGi imports are not satisfied by included bundles' exports. " |
| + "The application will fail starting up. The missing imports are:" ); |
| for ( String s : osgiImports ) |
| { |
| Set<String> back = osgiImportsBacktraces.get( s ); |
| getLog().error( " " + s + ( back != null ? " ref: " + Arrays.toString( back. |
| toArray() ) : "" ) ); |
| } |
| } |
| if ( !requireTokens.isEmpty() ) |
| { |
| getLog().error( |
| "Some tokens required by included modules are not provided by included modules. " |
| + "The application will fail starting up. The missing tokens are:" ); |
| for ( String s : requireTokens ) |
| { |
| Set<String> back = requireTokensBacktraces.get( s ); |
| getLog().error( " " + s + ( back != null ? " ref: " + Arrays.toString( back. |
| toArray() ) : "" ) ); |
| } |
| } |
| throw new MojoFailureException( |
| "See above for consistency validation check failures. " |
| + " Either fix those by adding the relevant dependencies to the application or " |
| + "disable the check by setting the verifyIntegrity parameter to false or by running with " |
| + "-Dnetbeans.verify.integrity=false cmd line parameter." ); |
| } |
| else |
| { |
| getLog().info( "Integrity verification passed." ); |
| } |
| } |
| else |
| { |
| getLog().info( "Integrity verification skipped." ); |
| } |
| |
| //attempt to sort clusters based on the dependencies and cluster content. |
| Map<String, Set<String>> cluster2depClusters = |
| computeClusterOrdering( clusterDependencies, clusterModules ); |
| clusterModules.clear(); |
| |
| //now assign the cluster to bundles based on dependencies.. |
| assignClustersToBundles( bundles, wrappedBundleCNBs, clusterDependencies, cluster2depClusters, getLog() ); |
| |
| for ( BundleTuple ent : bundles ) |
| { |
| Artifact art = ent.artifact; |
| final ExamineManifest ex = ent.manifest; |
| |
| String clstr = ent.cluster; |
| if ( clstr == null ) |
| { |
| clstr = defaultCluster; |
| } |
| |
| ClusterTuple cluster = processCluster( clstr, nbmBuildDirFile, art ); |
| if ( cluster.newer ) |
| { |
| getLog().info( "Copying " + art.getId() + " to cluster " + clstr ); |
| File modules = new File( cluster.location, "modules" ); |
| modules.mkdirs(); |
| File config = new File( cluster.location, "config" ); |
| File confModules = new File( config, "Modules" ); |
| confModules.mkdirs(); |
| File updateTracking = new File( cluster.location, "update_tracking" ); |
| updateTracking.mkdirs(); |
| final String cnb = ex.getModule(); |
| final String cnbDashed = cnb.replace( ".", "-" ); |
| //do we need the file in some canotical name pattern in moduleArt? |
| final File moduleArt = new File( modules, cnbDashed + ".jar" ); |
| final String specVer = ex.getSpecVersion(); |
| try |
| { |
| FileUtils.copyFile( art.getFile(), moduleArt ); |
| final File moduleConf = new File( confModules, cnbDashed + ".xml" ); |
| FileUtils.copyStreamToFile( new InputStreamFacade() |
| { |
| @Override |
| public InputStream getInputStream() throws IOException |
| { |
| return new StringInputStream( createBundleConfigFile( cnb, ex.isBundleAutoload() ), |
| "UTF-8" ); |
| } |
| }, moduleConf ); |
| FileUtils.copyStreamToFile( new InputStreamFacade() |
| { |
| @Override |
| public InputStream getInputStream() throws IOException |
| { |
| return new StringInputStream( createBundleUpdateTracking( cnb, moduleArt, moduleConf, |
| specVer ), "UTF-8" ); |
| } |
| }, new File( updateTracking, cnbDashed + ".xml" ) ); |
| } |
| catch ( IOException exc ) |
| { |
| getLog().error( exc ); |
| } |
| } |
| } |
| |
| getLog().info( |
| "Created NetBeans module cluster(s) at " + nbmBuildDirFile.getAbsoluteFile() ); |
| |
| } |
| else |
| { |
| throw new MojoExecutionException( |
| "This goal only makes sense on project with nbm-application packaging" ); |
| } |
| //in 6.1 the rebuilt modules will be cached if the timestamp is not touched. |
| File[] files = nbmBuildDirFile.listFiles(); |
| for ( File file : files ) |
| { |
| if ( file.isDirectory() ) |
| { |
| File stamp = new File( file, ".lastModified" ); |
| if ( !stamp.exists() ) |
| { |
| try |
| { |
| stamp.createNewFile(); |
| } |
| catch ( IOException ex ) |
| { |
| ex.printStackTrace(); |
| } |
| } |
| stamp.setLastModified( new Date().getTime() ); |
| } |
| } |
| try |
| { |
| createBinEtcDir( nbmBuildDirFile, brandingToken ); |
| } |
| catch ( IOException ex ) |
| { |
| throw new MojoExecutionException( |
| "Cannot process etc folder content creation.", ex ); |
| } |
| } |
| private static final int SET_INITIAL_SIZE = 200; |
| private static final int MAP_INITIALSIZE = 50; |
| private static final Pattern PATT = Pattern.compile( |
| ".*targetcluster=\"([a-zA-Z0-9_\\.\\-]+)\".*", Pattern.DOTALL ); |
| |
| private String findCluster( JarFile jf ) |
| throws MojoFailureException, IOException |
| { |
| ZipEntry entry = jf.getEntry( "Info/info.xml" ); |
| InputStream ins = jf.getInputStream( entry ); |
| String str = IOUtil.toString( ins, "UTF8" ); |
| Matcher m = PATT.matcher( str ); |
| if ( !m.matches() ) |
| { |
| getLog().info( "Cannot find cluster for " + jf.getName() + " Falling back to default value - '" |
| + defaultCluster + "'." ); |
| return defaultCluster; |
| } |
| else |
| { |
| return m.group( 1 ); |
| } |
| } |
| |
| /** |
| * |
| * @param buildDir Directory where the platform bundle is built |
| * @param brandingToken |
| * |
| * @throws java.io.IOException |
| */ |
| private void createBinEtcDir( File buildDir, String brandingToken ) |
| throws IOException, MojoExecutionException |
| { |
| File etcDir = new File( buildDir + File.separator + "etc" ); |
| etcDir.mkdir(); |
| |
| // create app.clusters which contains a list of clusters to include in the application |
| File clusterConf = new File( etcDir + File.separator + brandingToken + ".clusters" ); |
| String clustersString; |
| if ( etcClustersFile != null ) |
| { |
| clustersString = FileUtils.fileRead( etcClustersFile, "UTF-8" ); |
| } |
| else |
| { |
| clusterConf.createNewFile(); |
| StringBuilder buffer = new StringBuilder(); |
| File[] clusters = buildDir.listFiles( new FileFilter() |
| { |
| |
| @Override |
| public boolean accept( File pathname ) |
| { |
| return new File( pathname, ".lastModified" ).exists(); |
| } |
| } ); |
| for ( File cluster : clusters ) |
| { |
| buffer.append( cluster.getName() ); |
| buffer.append( "\n" ); |
| } |
| clustersString = buffer.toString(); |
| } |
| |
| FileUtils.fileWrite( clusterConf.getAbsolutePath(), clustersString ); |
| |
| File confFile = etcConfFile; |
| String str; |
| if ( confFile == null ) |
| { |
| File harnessDir = new File( buildDir, "harness" ); |
| // app.conf contains default options and other settings |
| confFile = new File( |
| harnessDir.getAbsolutePath() + File.separator + "etc" + File.separator + "app.conf" ); |
| if ( confFile.exists() ) |
| { |
| str = FileUtils.fileRead( confFile, "UTF-8" ); |
| } |
| else |
| { |
| getLog().debug( "Using fallback app.conf shipping with the nbm-maven-plugin." ); |
| InputStream instream = null; |
| try |
| { |
| instream = getClass().getClassLoader().getResourceAsStream( "harness/etc/app.conf" ); |
| str = IOUtil.toString( instream, "UTF-8" ); |
| } |
| finally |
| { |
| IOUtil.close( instream ); |
| } |
| } |
| } |
| else |
| { |
| str = FileUtils.fileRead( confFile, "UTF-8" ); |
| } |
| File confDestFile = new File( |
| etcDir.getAbsolutePath() + File.separator + brandingToken + ".conf" ); |
| |
| str = str.replace( "${branding.token}", brandingToken ); |
| FileUtils.fileWrite( confDestFile.getAbsolutePath(), "UTF-8", str ); |
| |
| File destBinDir = new File( buildDir + File.separator + "bin" ); |
| destBinDir.mkdir(); |
| |
| File binDir; |
| File destExeW = new File( destBinDir, brandingToken + "_w.exe" ); |
| File destExe = new File( destBinDir, brandingToken + ".exe" ); |
| File destExe64 = new File( destBinDir, brandingToken + "64.exe" ); |
| File destSh = new File( destBinDir, brandingToken ); |
| |
| File harnessDir = new File( buildDir, "harness" ); |
| //we have org-netbeans-modules-apisupport-harness in target area, just use it's own launchers. |
| binDir = new File( |
| harnessDir.getAbsolutePath() + File.separator + "launchers" ); |
| if ( binDir.exists() ) |
| { |
| File exe = new File( binDir, "app.exe" ); |
| FileUtils.copyFile( exe, destExe ); |
| File exe64 = new File( binDir, "app64.exe" ); |
| if ( exe64.isFile() ) |
| { |
| FileUtils.copyFile( exe64, destExe64 ); |
| } |
| File exew = new File( binDir, "app_w.exe" ); |
| if ( exew.exists() ) //in 6.7 the _w.exe file is no more. |
| { |
| FileUtils.copyFile( exew, destExeW ); |
| } |
| File sh = new File( binDir, "app.sh" ); |
| FileUtils.copyFile( sh, destSh ); |
| } |
| else |
| { |
| File nbm = getHarnessNbm(); |
| try ( ZipFile zip = new ZipFile( nbm ) ) |
| { |
| getLog().debug( |
| "Using fallback executables from downloaded org-netbeans-modules-apisupport-harness nbm file." ); |
| writeFromZip( zip, "netbeans/launchers/app.sh", destSh, true ); |
| writeFromZip( zip, "netbeans/launchers/app.exe", destExe, true ); |
| writeFromZip( zip, "netbeans/launchers/app64.exe", destExe64, false ); |
| writeFromZip( zip, "netbeans/launchers/app_w.exe", destExeW, false ); |
| } |
| } |
| |
| if ( binDirectory != null ) |
| { |
| //we have custom launchers, only overwrite the ones the user provided. |
| binDir = binDirectory; |
| File[] fls = binDir.listFiles(); |
| if ( fls == null ) |
| { |
| throw new MojoExecutionException( "Parameter 'binDirectory' has to point to an existing folder." ); |
| } |
| for ( File fl : fls ) |
| { |
| String name = fl.getName(); |
| File dest = null; |
| if ( name.endsWith( "_w.exe" ) ) |
| { |
| dest = destExeW; |
| } |
| else if ( name.endsWith( "64.exe" ) ) |
| { |
| dest = destExe64; |
| } |
| else if ( name.endsWith( ".exe" ) ) |
| { |
| dest = destExe; |
| } |
| else if ( !name.contains( "." ) || name.endsWith( ".sh" ) ) |
| { |
| dest = destSh; |
| } |
| if ( dest != null && fl.exists() ) //in 6.7 the _w.exe file is no more. |
| { |
| FileUtils.copyFile( fl, dest ); |
| } |
| else |
| { |
| //warn about file not being copied |
| } |
| } |
| } |
| |
| Project antProject = antProject(); |
| |
| Chmod chmod = (Chmod) antProject.createTask( "chmod" ); |
| FileSet fs = new FileSet(); |
| fs.setDir( destBinDir ); |
| fs.setIncludes( "*" ); |
| chmod.addFileset( fs ); |
| chmod.setPerm( "755" ); |
| chmod.execute(); |
| } |
| |
| private void writeFile( String path, File destSh ) |
| throws IOException |
| { |
| InputStream instream = null; |
| OutputStream output = null; |
| try |
| { |
| instream = getClass().getClassLoader().getResourceAsStream( path ); |
| if ( instream == null ) |
| { |
| throw new FileNotFoundException( path ); |
| } |
| destSh.createNewFile(); |
| output = new BufferedOutputStream( new FileOutputStream( destSh ) ); |
| IOUtil.copy( instream, output ); |
| } |
| finally |
| { |
| IOUtil.close( instream ); |
| IOUtil.close( output ); |
| } |
| } |
| |
| private ClusterTuple processCluster( String cluster, File nbmBuildDirFile, Artifact art ) |
| { |
| File clusterFile = new File( nbmBuildDirFile, cluster ); |
| boolean newer = false; |
| if ( !clusterFile.exists() ) |
| { |
| clusterFile.mkdir(); |
| newer = true; |
| } |
| else |
| { |
| File stamp = new File( clusterFile, ".lastModified" ); |
| if ( stamp.lastModified() < art.getFile().lastModified() ) |
| { |
| newer = true; |
| } |
| } |
| return new ClusterTuple( clusterFile, newer ); |
| } |
| |
| private void externalDownload( File f, InputStream is ) |
| throws IOException |
| { |
| // Cf. org.netbeans.nbbuild.AutoUpdate |
| BufferedReader r = new BufferedReader( new InputStreamReader( is, "UTF-8" ) ); |
| long crc = -1; |
| long size = -1; |
| boolean found = false; |
| String line; |
| while ( ( line = r.readLine() ) != null ) |
| { |
| if ( line.startsWith( "CRC:" ) ) |
| { |
| crc = Long.parseLong( line.substring( 4 ).trim() ); |
| } |
| else if ( line.startsWith( "URL:" ) ) |
| { |
| String rest = line.substring( 4 ).trim(); |
| if ( rest.startsWith( "m2:/" ) ) |
| { |
| if ( !found ) |
| { |
| String[] coords = rest.substring( 4 ).trim().split( ":" ); |
| Artifact artifact; |
| if ( coords.length == 4 ) |
| { |
| artifact = artifactFactory. |
| createArtifact( coords[0], coords[1], coords[2], null, coords[3] ); |
| } |
| else |
| { |
| artifact = artifactFactory.createArtifactWithClassifier( coords[0], coords[1], coords[2], |
| coords[3], coords[4] ); |
| } |
| try |
| { |
| artifactResolver. |
| resolve( artifact, project.getRemoteArtifactRepositories(), localRepository ); |
| FileUtils.copyFile( artifact.getFile(), f ); |
| found = true; |
| } |
| catch ( AbstractArtifactResolutionException x ) |
| { |
| getLog().warn( "Cannot find " + line.substring( 8 ), x ); |
| } |
| } |
| } |
| else if ( !found ) |
| { |
| String url = line.substring( 4 ).trim(); |
| try |
| { |
| // XXX use Wagon API instead |
| FileUtils.copyURLToFile( new URL( url ), f ); |
| found = true; |
| } |
| catch ( IOException x ) |
| { |
| getLog().warn( "Cannot download " + url, x ); |
| } |
| } |
| } |
| else if ( line.startsWith( "SIZE:" ) ) |
| { |
| size = Long.parseLong( line.substring( 5 ).trim() ); |
| } |
| else |
| { |
| getLog().warn( "Unrecognized line: " + line ); |
| } |
| } |
| if ( !found ) |
| { |
| throw new IOException( "Could not download " + f ); |
| } |
| if ( crc != -1 && crc != crcForFile( f ).getValue() ) |
| { |
| throw new IOException( "CRC-32 of " + f + " does not match declared " + crc ); |
| } |
| if ( size != -1 && size != f.length() ) |
| { |
| throw new IOException( "Size of " + f + " does not match declared " + size ); |
| } |
| } |
| |
| private File getHarnessNbm() throws MojoExecutionException |
| { |
| @SuppressWarnings( "unchecked" ) |
| Set<Artifact> artifacts = project.getArtifacts(); |
| String version = null; |
| for ( Artifact a : artifacts ) |
| { |
| if ( ( groupIdPrefix + ".modules" ).equals( a.getGroupId() ) && "org-netbeans-bootstrap".equals( a. |
| getArtifactId() ) ) |
| { |
| //base version in non-snapshot should equals version, in snapshots to X-SNAPSHOT, not timestamp |
| version = a.getBaseVersion(); |
| break; |
| } |
| } |
| if ( version == null ) |
| { |
| throw new MojoExecutionException( |
| "We could not find org-netbeans-bootstrap among the modules in the application. " |
| + "Launchers could not be found." ); |
| } |
| Artifact nbmArt = artifactFactory.createArtifact( |
| groupIdPrefix + ".modules", |
| "org-netbeans-modules-apisupport-harness", |
| version, |
| "compile", |
| "nbm-file" ); |
| try |
| { |
| artifactResolver.resolve( nbmArt, project.getRemoteArtifactRepositories(), localRepository ); |
| } |
| |
| catch ( ArtifactResolutionException | ArtifactNotFoundException ex ) |
| { |
| throw new MojoExecutionException( "Failed to retrieve the nbm file from repository", ex ); |
| } |
| return nbmArt.getFile(); |
| } |
| |
| private void writeFromZip( final ZipFile zip, String zipPath, File destFile, boolean mandatory ) throws |
| MojoExecutionException, IOException |
| { |
| final ZipEntry path = zip.getEntry( zipPath ); |
| if ( path == null ) |
| { |
| if ( mandatory ) |
| { |
| throw new MojoExecutionException( zipPath + " not found in " + zip.getName() ); |
| } |
| getLog().debug( zipPath + " is not present in " + zip.getName() ); |
| return; |
| } |
| FileUtils.copyStreamToFile( new InputStreamFacade() |
| { |
| |
| @Override |
| public InputStream getInputStream() throws IOException |
| { |
| return zip.getInputStream( path ); |
| } |
| }, destFile ); |
| } |
| |
| private static void addToMap( Map<String, Set<String>> map, String clusterName, List<String> newValues ) |
| { |
| Set<String> lst = map.get( clusterName ); |
| if ( lst == null ) |
| { |
| lst = new HashSet<>(); |
| map.put( clusterName, lst ); |
| } |
| if ( newValues != null ) |
| { |
| lst.addAll( newValues ); |
| } |
| } |
| |
| private static List<String> findByDependencies( Map<String, Set<String>> clusterDependencies, String spec ) |
| { |
| List<String> toRet = new ArrayList<>(); |
| for ( Map.Entry<String, Set<String>> entry : clusterDependencies.entrySet() ) |
| { |
| if ( entry.getValue().contains( spec ) ) |
| { |
| toRet.add( entry.getKey() ); |
| } |
| } |
| return toRet; |
| } |
| |
| //the basic idea is that bundle's cluster can be determined by who depends on it. |
| //simplest case is when a module depends on it. If there are more, we need to pick one that is "lower in the stack, |
| //that's what cluster2depClusters is for. |
| //the rest needs to be determined in more sofisticated manner. |
| //start from bundles with known cluster and see what other bundles they depend on. |
| //stamp all these with the same cluster. do it recursively. |
| //At the end process the remaining bundles in reverse order. |
| //Check if *they* depend on a bundle with known cluster and so on.. |
| //A few unsolved cases: |
| // - we never update the cluster information once a match was found, |
| // but there is a possibility that later in the processing the cluster could be "lowered". |
| // - 2 or more modules from unrelated clusters we cannot easily decide, |
| // most likely should be in common denominator cluster but our cluster2depClusters map is not transitive, |
| // only lists direct dependencies |
| static void assignClustersToBundles( List<BundleTuple> bundles, Set<String> wrappedBundleCNBs, |
| Map<String, Set<String>> clusterDependencies, |
| Map<String, Set<String>> cluster2depClusters, Log log ) |
| { |
| List<BundleTuple> toProcess = new ArrayList<>(); |
| List<BundleTuple> known = new ArrayList<>(); |
| for ( Iterator<BundleTuple> it = bundles.iterator(); it.hasNext(); ) |
| { |
| BundleTuple ent = it.next(); |
| Artifact art = ent.artifact; |
| ExamineManifest ex = ent.manifest; |
| String spec = ex.getModule(); |
| //null check for tests |
| //have a way to force inclusion of osgi items. Direct dependency is never wrapped by modules. |
| if ( art != null && art.getDependencyTrail().size() > 2 && wrappedBundleCNBs.contains( spec ) ) |
| { |
| // we already have this one as a wrapped module. |
| log.debug( "Not including bundle " + art.getDependencyConflictId() |
| + ". It is already included in a NetBeans module" ); |
| it.remove(); |
| continue; |
| } |
| List<String> depclusters = findByDependencies( clusterDependencies, spec ); |
| if ( depclusters.size() == 1 ) |
| { |
| ent.cluster = depclusters.get( 0 ); |
| known.add( ent ); |
| } |
| else if ( depclusters.isEmpty() ) |
| { |
| toProcess.add( ent ); |
| } |
| else |
| { |
| //more results.. from 2 dependent clusters pick the one that is lower in the stack. |
| for ( Iterator<String> it2 = depclusters.iterator(); it2.hasNext(); ) |
| { |
| String s = it2.next(); |
| Set<String> depsCs = cluster2depClusters.get( s ); |
| boolean removeS = false; |
| for ( String sDep : depclusters ) |
| { |
| if ( s.equals( sDep ) ) |
| { |
| continue; |
| } |
| if ( depsCs != null && depsCs.contains( sDep ) ) |
| { |
| removeS = true; |
| } |
| } |
| if ( removeS ) |
| { |
| it2.remove(); |
| } |
| } |
| //TODO still some free room there, |
| //what if they don't directly depend on each other but still are related |
| ent.cluster = depclusters.get( 0 ); |
| known.add( ent ); |
| } |
| } |
| if ( !toProcess.isEmpty() ) |
| { |
| walkKnownBundleDependenciesDown( known, toProcess ); |
| } |
| if ( !toProcess.isEmpty() ) |
| { |
| walkKnownBundleDependenciesUp( known, toProcess ); |
| } |
| } |
| |
| private static void walkKnownBundleDependenciesDown( List<BundleTuple> known, List<BundleTuple> toProcess ) |
| { |
| boolean atLeastOneWasFound = false; |
| for ( Iterator<BundleTuple> it = toProcess.iterator(); it.hasNext(); ) |
| { |
| BundleTuple bundleTuple = it.next(); |
| boolean found = false; |
| for ( BundleTuple knownBT : known ) |
| { |
| Sets.SetView<String> is = Sets.intersection( bundleTuple.manifest.getOsgiExports(), knownBT.manifest. |
| getOsgiImports() ); |
| if ( !is.isEmpty() ) |
| { |
| found = true; |
| bundleTuple.cluster = knownBT.cluster; |
| break; |
| } |
| //dependencyTokens are requireBundle - matches the module property |
| is = Sets.intersection( Collections.singleton( bundleTuple.manifest.getModule() ), new HashSet( |
| knownBT.manifest.getDependencyTokens() ) ); |
| if ( !is.isEmpty() ) |
| { |
| found = true; |
| bundleTuple.cluster = knownBT.cluster; |
| break; |
| } |
| |
| } |
| if ( found ) |
| { |
| atLeastOneWasFound = true; |
| it.remove(); |
| known.add( bundleTuple ); |
| } |
| |
| } |
| if ( !toProcess.isEmpty() && atLeastOneWasFound ) |
| { |
| walkKnownBundleDependenciesDown( known, toProcess ); |
| } |
| } |
| |
| private static void walkKnownBundleDependenciesUp( List<BundleTuple> known, List<BundleTuple> toProcess ) |
| { |
| boolean atLeastOneWasFound = false; |
| for ( Iterator<BundleTuple> it = toProcess.iterator(); it.hasNext(); ) |
| { |
| BundleTuple bundleTuple = it.next(); |
| boolean found = false; |
| for ( BundleTuple knownBT : known ) |
| { |
| Sets.SetView<String> is = Sets.intersection( bundleTuple.manifest.getOsgiImports(), knownBT.manifest. |
| getOsgiExports() ); |
| if ( !is.isEmpty() ) |
| { |
| found = true; |
| bundleTuple.cluster = knownBT.cluster; |
| break; |
| } |
| //dependencyTokens are requireBundle - matches the module property |
| is = Sets.intersection( Collections.singleton( knownBT.manifest.getModule() ), new HashSet( |
| bundleTuple.manifest.getDependencyTokens() ) ); |
| if ( !is.isEmpty() ) |
| { |
| found = true; |
| bundleTuple.cluster = knownBT.cluster; |
| break; |
| } |
| |
| } |
| if ( found ) |
| { |
| atLeastOneWasFound = true; |
| it.remove(); |
| known.add( bundleTuple ); |
| } |
| |
| } |
| if ( !toProcess.isEmpty() && atLeastOneWasFound ) |
| { |
| walkKnownBundleDependenciesDown( known, toProcess ); |
| } |
| if ( !toProcess.isEmpty() && atLeastOneWasFound ) |
| { |
| walkKnownBundleDependenciesUp( known, toProcess ); |
| } |
| } |
| |
| //static and default for tests.. |
| static Map<String, Set<String>> computeClusterOrdering( Map<String, Set<String>> clusterDependencies, |
| Map<String, Set<String>> clusterModules ) |
| { |
| Map<String, Set<String>> cluster2depClusters = new HashMap<>(); |
| for ( Map.Entry<String, Set<String>> entry : clusterDependencies.entrySet() ) |
| { |
| String cluster = entry.getKey(); |
| Set<String> deps = entry.getValue(); |
| for ( Map.Entry<String, Set<String>> subEnt : clusterModules.entrySet() ) |
| { |
| if ( subEnt.getKey().equals( cluster ) ) |
| { |
| continue; |
| } |
| Sets.SetView<String> is = Sets.intersection( subEnt.getValue(), deps ); |
| if ( !is.isEmpty() ) |
| { |
| addToMap( cluster2depClusters, cluster, Collections.singletonList( subEnt.getKey() ) ); |
| } |
| } |
| } |
| return cluster2depClusters; |
| } |
| |
| static class BundleTuple |
| { |
| |
| final Artifact artifact; |
| final ExamineManifest manifest; |
| String cluster; |
| |
| BundleTuple( Artifact artifact, ExamineManifest manifest ) |
| { |
| this.artifact = artifact; |
| this.manifest = manifest; |
| } |
| |
| } |
| |
| private static class ClusterTuple |
| { |
| |
| final File location; |
| final boolean newer; |
| |
| private ClusterTuple( File clusterFile, boolean newer ) |
| { |
| location = clusterFile; |
| this.newer = newer; |
| } |
| } |
| |
| static String createBundleConfigFile( String cnb, boolean autoload ) |
| { |
| return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" |
| + "<!DOCTYPE module PUBLIC \"-//NetBeans//DTD Module Status 1.0//EN\"\n" |
| + " \"http://www.netbeans.org/dtds/module-status-1_0.dtd\">\n" |
| + "<module name=\"" + cnb + "\">\n" |
| + " <param name=\"autoload\">" + autoload + "</param>\n" |
| + " <param name=\"eager\">false</param>\n" |
| + ( autoload ? "" : " <param name=\"enabled\">true</param>\n" ) |
| + " <param name=\"jar\">modules/" + cnb.replace( ".", "-" ) + ".jar</param>\n" |
| + " <param name=\"reloadable\">false</param>\n" |
| + "</module>\n"; |
| } |
| |
| static String createBundleUpdateTracking( String cnb, File moduleArt, File moduleConf, String specVersion ) |
| throws FileNotFoundException, IOException |
| { |
| |
| return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" |
| + "<module codename=\"" + cnb + "\">\n" |
| + " <module_version install_time=\"" + System.currentTimeMillis() |
| + "\" last=\"true\" origin=\"installer\" specification_version=\"" + specVersion + "\">\n" |
| + " <file crc=\"" + crcForFile( moduleConf ).getValue() + "\" name=\"config/Modules/" + cnb. |
| replace( ".", "-" ) + ".xml\"/>\n" |
| + " <file crc=\"" + crcForFile( moduleArt ).getValue() + "\" name=\"modules/" + cnb.replace( ".", |
| "-" ) |
| + ".jar\"/>\n" |
| + " </module_version>\n" |
| + "</module>"; |
| |
| } |
| |
| static CRC32 crcForFile( File inFile ) |
| throws FileNotFoundException, IOException |
| { |
| CRC32 crc = new CRC32(); |
| try ( InputStream inFileStream = new FileInputStream( inFile ) ) |
| { |
| byte[] array = new byte[(int) inFile.length()]; |
| int len = inFileStream.read( array ); |
| if ( len != array.length ) |
| { |
| throw new IOException( "Cannot fully read " + inFile ); |
| } |
| crc.update( array ); |
| } |
| return crc; |
| } |
| |
| } |