| 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 java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.PrintWriter; |
| import java.io.Reader; |
| import java.net.URL; |
| import java.text.BreakIterator; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TimeZone; |
| import java.util.regex.Pattern; |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.artifact.factory.ArtifactFactory; |
| import org.apache.maven.artifact.metadata.ArtifactMetadataSource; |
| import org.apache.maven.artifact.repository.ArtifactRepository; |
| import org.apache.maven.artifact.resolver.ArtifactCollector; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| 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.maven.shared.dependency.analyzer.DefaultClassAnalyzer; |
| import org.apache.maven.shared.dependency.analyzer.asm.ASMDependencyAnalyzer; |
| import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder; |
| import org.apache.maven.shared.dependency.graph.DependencyNode; |
| import org.apache.netbeans.nbm.model.Dependency; |
| import org.apache.netbeans.nbm.model.NetBeansModule; |
| import org.apache.netbeans.nbm.utils.ExamineManifest; |
| import org.apache.tools.ant.taskdefs.Manifest; |
| import org.apache.tools.ant.taskdefs.ManifestException; |
| import org.codehaus.plexus.util.IOUtil; |
| |
| /** |
| * Goal for generating NetBeans module system specific manifest entries, part of <code>nbm</code> lifecycle/packaging. |
| * |
| * In order to have the generated manifest picked up by the maven-jar-plugin, |
| * one shall add the following configuration snippet to maven-jar-plugin. |
| * |
| * <pre> |
| * {@code |
| <plugin> |
| <groupId>org.apache.maven.plugins</groupId> |
| <artifactId>maven-jar-plugin</artifactId> |
| <version>3.0.2</version> |
| <configuration> |
| <archive> |
| <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile> |
| </archive> |
| </configuration> |
| </plugin> |
| * } |
| * </pre> |
| * |
| * @author Milos Kleint |
| */ |
| @Mojo( name = "manifest", |
| defaultPhase = LifecyclePhase.PROCESS_CLASSES, |
| requiresProject = true, |
| threadSafe = true, |
| requiresDependencyResolution = ResolutionScope.RUNTIME ) |
| public class NetBeansManifestUpdateMojo |
| extends AbstractNbmMojo |
| { |
| |
| /** |
| * NetBeans module assembly build directory. |
| * directory where the the NetBeans jar and nbm file get constructed. |
| */ |
| @Parameter( defaultValue = "${project.build.directory}/nbm", property = "maven.nbm.buildDir" ) |
| protected File nbmBuildDir; |
| |
| /** |
| * a NetBeans module descriptor containing dependency information and more |
| * @deprecated all content from the module descriptor can be defined as plugin configuration now, will be removed in 4.0 entirely |
| */ |
| @Parameter( defaultValue = "${basedir}/src/main/nbm/module.xml" ) |
| protected File descriptor; |
| |
| /** |
| * maven project |
| */ |
| @Parameter( required = true, readonly = true, property = "project" ) |
| private MavenProject project; |
| |
| /** |
| * The location of JavaHelp sources for the project. The documentation |
| * itself is expected to be in the directory structure based on codenamebase of the module. |
| * eg. if your codenamebase is "org.netbeans.modules.apisupport", then the actual docs |
| * files shall go to ${basedir}/src/main/javahelp/org/netbeans/modules/apisupport/docs. |
| * Obsolete as of NetBeans 7.0 with @HelpSetRegistration. |
| * @since 2.7 |
| */ |
| @Parameter( defaultValue = "${basedir}/src/main/javahelp" ) |
| protected File nbmJavahelpSource; |
| |
| /** |
| * Path to manifest file that will be used as base and enhanced with generated content. Any entry specified in the original file |
| * will not be overwritten |
| * @since 3.0 |
| */ |
| @Parameter( required = true, defaultValue = "${basedir}/src/main/nbm/manifest.mf" ) |
| private File sourceManifestFile; |
| |
| /** |
| * Path to the generated MANIFEST file to use. It will be used by jar:jar plugin. |
| * @since 3.0 |
| */ |
| @Parameter( required = true, readonly = true, defaultValue = "${project.build.outputDirectory}/META-INF/MANIFEST.MF" ) |
| private File targetManifestFile; |
| |
| /** |
| * Verify the runtime NetBeans module dependencies and Class-Path items |
| * generated from Maven dependencies. The check is done by matching classes used |
| * in current project. Allowed values for the parameter are <code>fail</code>, <code>warn</code> and <code>skip</code>. |
| * The default is <code>fail</code> in which case the validation failure results in a failed build, |
| * in the vast majority of cases the module would fail at runtime anyway. |
| * |
| * @since 3.0 |
| */ |
| @Parameter( property = "maven.nbm.verify", defaultValue = "fail" ) |
| private String verifyRuntime; |
| |
| private static final String FAIL = "fail"; |
| private static final String WARN = "warn"; |
| private static final String SKIP = "skip"; |
| |
| /** |
| * A list of module's public packages. If not defined, no packages are exported as public. |
| * Allowed values are single package names |
| * or package names ending with .* which represent the package and all subpackages. |
| * <p/> |
| * Eg. "org.kleint.milos.api" designates just the one package, while "org.kleint.milos.spi.*" |
| * denotes the spi package an all it's subpackages. |
| * @since 3.0 |
| */ |
| @Parameter |
| private List<String> publicPackages; |
| |
| /** |
| * When encountering an OSGi bundle among dependencies, the plugin will generate a direct dependency |
| * on the bundle and will not include the bundle's jar into the nbm. Will only work with NetBeans 6.9+ runtime. |
| * Therefore it is off by default. |
| * WARNING: Additionally existing applications/modules need to check modules wrapping |
| * external libraries for library jars that are also OSGi bundles. Such modules will no longer include the OSGi bundles |
| * as part of the module but will include a modular dependency on the bundle. Modules depending on these old wrappers |
| * shall depend directly on the bundle, eventually rendering the old library wrapper module obsolete. |
| * |
| * @since 3.2 |
| */ |
| @Parameter( defaultValue = "false" ) |
| private boolean useOSGiDependencies; |
| |
| /** |
| * codename base of the module, uniquely identifying the module within the NetBeans runtime. usually the package name equivalent. |
| * Can include the major release version. |
| * See <a href="http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/doc-files/api.html#how-manifest"> NetBeans Module system docs</a> |
| * @since 3.8 |
| */ |
| @Parameter( defaultValue = "${project.groupId}.${project.artifactId}" ) |
| private String codeNameBase; |
| |
| /** |
| * List of explicit module dependency declarations overriding the default specification dependency. Useful when depending on a range of major versions, |
| * depending on implementation version etc. |
| * <p>The format is: |
| * <pre> |
| * <dependency> |
| * <id>groupId:artifactId</id> |
| * <type>spec|impl|loose</type> |
| * <explicitValue>the entire dependency token</explicitValue> |
| * </dependency> |
| * </pre> |
| * </p> |
| * <p> |
| * where <code>id</code> is composed of grouId and artifactId of a dependency defined in effective pom, separated by double colon. This is mandatory.</p> |
| * <p> |
| * Then there are 2 exclusively optional fields <code>type</code> and <code>explicitValue</code>, if both are defined <code>explicitValue</code> gets applied. |
| * </p> |
| * <p><code>type</code> values: <code>spec</code> means specification dependency.That's the default. |
| * <code>impl</code> means implementation dependency, only the exact version match will satisfy the constraint. |
| * <code>loose</code> means loose dependency, no requirement on version, the module just has to be present. Not very common option. |
| * |
| * @since 3.8 |
| */ |
| @Parameter |
| private Dependency[] moduleDependencies; |
| |
| /** |
| * Deployment type of the module, allowed values are <code>normal</code>,<code>eager</code>,<code>autoload</code>, |
| * <code>disabled</code>. |
| * <p> |
| * <code>autoload</code> - Such a module is |
| * automatically enabled when some other module requires it and |
| * automatically disabled otherwise.</p> |
| * <p><code>eager</code> - This module type gets |
| * automatically enabled when all it's dependencies are |
| * satisfied. Disabled otherwise.</p> |
| * <p><code>normal</code> - This is the default |
| * value. This kind of module is enabled/disabled manually by |
| * the user. It installs enabled.</p> |
| * <p><code>disabled</code> - This kind of module is enabled/disabled manually by |
| * the user. It installs disabled. Since 3.11</p> |
| * |
| * For details, see <a href="http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/doc-files/api.html#enablement">Netbeans Module system docs</a> |
| * |
| * Since 3.14, for autoload and eager modules, we automatically set AutoUpdate-Show-In-Client manifest entry to false, if not defined already otherwise in the manifest. |
| * See issue <a href="http://jira.codehaus.org/browse/MNBMODULE-194">MNBMODULE-194</a> |
| * |
| * @since 3.8 (3.14 in manifest goal) |
| */ |
| @Parameter( defaultValue = "normal" ) |
| protected String moduleType; |
| |
| // <editor-fold defaultstate="collapsed" desc="Component parameters"> |
| |
| /** |
| * The artifact repository to use. |
| |
| */ |
| @Parameter( required = true, readonly = true, defaultValue = "${localRepository}" ) |
| private ArtifactRepository localRepository; |
| |
| /** |
| * The artifact factory to use. |
| */ |
| @Component |
| private ArtifactFactory artifactFactory; |
| |
| /** |
| * The artifact metadata source to use. |
| */ |
| @Component |
| private ArtifactMetadataSource artifactMetadataSource; |
| |
| /** |
| * The artifact collector to use. |
| */ |
| @Component |
| private ArtifactCollector artifactCollector; |
| |
| /** |
| * The dependency tree builder to use. |
| */ |
| @Component( hint = "default" ) |
| private DependencyGraphBuilder dependencyGraphBuilder; |
| |
| // end of component params custom code folding |
| // </editor-fold> |
| |
| /** |
| * execute plugin |
| * @throws MojoExecutionException if an unexpected problem occurs |
| * @throws MojoFailureException if an expected problem occurs |
| */ |
| public void execute() |
| throws MojoExecutionException, MojoFailureException |
| |
| { |
| //need to do this to chekc for javahelp on CP. |
| super.registerNbmAntTasks(); |
| NetBeansModule module; |
| if ( descriptor != null && descriptor.exists() ) |
| { |
| module = readModuleDescriptor( descriptor ); |
| getLog().warn( "descriptor parameter is deprecated, use equivalent mojo parameters instead." ); |
| } |
| else |
| { |
| module = createDefaultDescriptor( project, false ); |
| } |
| |
| String mtype = moduleType; |
| //same moduleType related code in CreateNetBeansFileStructure.java |
| if ( "normal".equals( mtype ) && module.getModuleType() != null ) |
| { |
| mtype = module.getModuleType(); |
| getLog().warn( "moduleType in module descriptor is deprecated, use the plugin's parameter moduleType" ); |
| } |
| if ( !"normal".equals( mtype ) && !"autoload".equals( mtype ) && !"eager".equals( mtype ) && !"disabled".equals( mtype ) ) |
| { |
| getLog().error( "Only 'normal,autoload,eager,disabled' are allowed values in the moduleType parameter" ); |
| } |
| boolean autoload = "autoload".equals( mtype ); |
| boolean eager = "eager".equals( mtype ); |
| |
| |
| String moduleName = codeNameBase; |
| if ( module.getCodeNameBase() != null ) |
| { |
| moduleName = module.getCodeNameBase(); |
| getLog().warn( "codeNameBase in module descriptor is deprecated, use the plugin's parameter codeNameBase" ); |
| } |
| moduleName = moduleName.replaceAll( "-", "." ); |
| //<!-- if a NetBeans specific manifest is defined, examine this one, otherwise the already included one. |
| // ignoring the case when some of the NetBeans attributes are already defined in the jar and more is included. |
| File specialManifest = sourceManifestFile; |
| File nbmManifest = ( module.getManifest() != null ? new File( |
| project.getBasedir(), module.getManifest() ) : null ); |
| if ( nbmManifest != null && nbmManifest.exists() ) |
| { |
| //deprecated, but if actually defined, will use it. |
| specialManifest = nbmManifest; |
| } |
| ExamineManifest examinator = new ExamineManifest( getLog() ); |
| if ( specialManifest != null && specialManifest.exists() ) |
| { |
| examinator.setManifestFile( specialManifest ); |
| examinator.checkFile(); |
| } |
| else |
| { |
| // examinator.setJarFile( jarFile ); |
| } |
| |
| getLog().info( "NBM Plugin generates manifest" ); |
| |
| Manifest manifest = null; |
| if ( specialManifest != null && specialManifest.exists() ) |
| { |
| Reader reader = null; |
| try |
| { |
| reader = new InputStreamReader( new FileInputStream( specialManifest ) ); |
| manifest = new Manifest( reader ); |
| } |
| catch ( IOException exc ) |
| { |
| manifest = new Manifest(); |
| getLog().warn( "Error reading manifest at " + specialManifest, exc ); |
| } |
| catch ( ManifestException ex ) |
| { |
| getLog().warn( "Error reading manifest at " + specialManifest, ex ); |
| manifest = new Manifest(); |
| } |
| finally |
| { |
| IOUtil.close( reader ); |
| } |
| } |
| else |
| { |
| manifest = new Manifest(); |
| } |
| Date date = new Date(); |
| String specVersion = AdaptNbVersion.adaptVersion( project.getVersion(), |
| AdaptNbVersion.TYPE_SPECIFICATION, date ); |
| String implVersion = AdaptNbVersion.adaptVersion( project.getVersion(), |
| AdaptNbVersion.TYPE_IMPLEMENTATION, date ); |
| Manifest.Section mainSection = manifest.getMainSection(); |
| conditionallyAddAttribute( mainSection, |
| "OpenIDE-Module-Specification-Version", specVersion ); |
| conditionallyAddAttribute( mainSection, |
| "OpenIDE-Module-Implementation-Version", implVersion ); |
| if ( autoload || eager ) |
| { //MNBMODULE-194 |
| conditionallyAddAttribute( mainSection, "AutoUpdate-Show-In-Client", "false" ); |
| } |
| final String timestamp = createTimestamp( date ); |
| conditionallyAddAttribute( mainSection, "OpenIDE-Module-Build-Version", |
| timestamp ); |
| String projectCNB = conditionallyAddAttribute( mainSection, "OpenIDE-Module", moduleName ); |
| String packagesValue; |
| if ( publicPackages != null && publicPackages.size() > 0 ) |
| { |
| StringBuilder sb = new StringBuilder(); |
| for ( String pub : publicPackages ) |
| { |
| if ( pub == null ) |
| { //#MNBMODULE-237 |
| continue; |
| } |
| if ( pub.endsWith( ".**" ) ) |
| { |
| // well, just sort of wrong value but accept |
| sb.append( pub ); |
| } |
| else if ( pub.endsWith( ".*" ) ) |
| { |
| //multipackage value |
| sb.append( pub ).append( "*" ); |
| } |
| else |
| { |
| sb.append( pub ).append( ".*" ); |
| } |
| sb.append( ", " ); |
| } |
| if ( sb.length() > 1 ) |
| { //if only item is null, we have empty builder |
| sb.setLength( sb.length() - 2 ); //cut the last 2 ", " characters |
| packagesValue = sb.toString(); |
| } |
| else |
| { |
| // no packages available |
| packagesValue = "-"; |
| } |
| } |
| else |
| { |
| // no packages available |
| packagesValue = "-"; |
| } |
| conditionallyAddAttribute( mainSection, "OpenIDE-Module-Public-Packages", packagesValue ); |
| |
| //See http://www.netbeans.org/download/dev/javadoc/org-openide-modules/apichanges.html#split-of-openide-jar |
| conditionallyAddAttribute( mainSection, "OpenIDE-Module-Requires", |
| "org.openide.modules.ModuleFormat1" ); |
| // conditionallyAddAttribute(mainSection, "OpenIDE-Module-IDE-Dependencies", "IDE/1 > 3.40"); |
| // localization items |
| if ( !examinator.isLocalized() ) |
| { |
| conditionallyAddAttribute( mainSection, |
| "OpenIDE-Module-Display-Category", project.getGroupId() ); |
| conditionallyAddAttribute( mainSection, "OpenIDE-Module-Name", |
| project.getName() ); |
| conditionallyAddAttribute( mainSection, |
| "OpenIDE-Module-Short-Description", shorten( project.getDescription() ) ); |
| conditionallyAddAttribute( mainSection, |
| "OpenIDE-Module-Long-Description", project.getDescription() ); |
| } |
| getLog().debug( "module =" + module ); |
| |
| DependencyNode treeroot = createDependencyTree( project, dependencyGraphBuilder, "compile" ); |
| Map<Artifact, ExamineManifest> examinerCache = new HashMap<Artifact, ExamineManifest>(); |
| @SuppressWarnings( "unchecked" ) |
| List<Artifact> libArtifacts = getLibraryArtifacts( treeroot, module, project.getRuntimeArtifacts(), |
| examinerCache, getLog(), useOSGiDependencies ); |
| List<ModuleWrapper> moduleArtifacts = getModuleDependencyArtifacts( treeroot, module, moduleDependencies, project, examinerCache, |
| libArtifacts, getLog(), useOSGiDependencies ); |
| StringBuilder classPath = new StringBuilder(); |
| StringBuilder mavenClassPath = new StringBuilder(); |
| String dependencies = ""; |
| String depSeparator = " "; |
| |
| for ( Artifact a : libArtifacts ) |
| { |
| if ( classPath.length() > 0 ) |
| { |
| classPath.append( ' ' ); |
| } |
| classPath.append( artifactToClassPathEntry( a, codeNameBase ) ); |
| if ( mavenClassPath.length() > 0 ) |
| { |
| mavenClassPath.append( ' ' ); |
| } |
| mavenClassPath.append( a.getGroupId() ). |
| append( ':' ). |
| append( a.getArtifactId() ). |
| append( ':' ). |
| append( a.getBaseVersion() ); |
| |
| if ( a.getClassifier() != null ) |
| { |
| mavenClassPath.append( ":" ).append( a.getClassifier() ); |
| } |
| } |
| |
| for ( ModuleWrapper wr : moduleArtifacts ) |
| { |
| if ( wr.transitive ) |
| { |
| continue; |
| } |
| Dependency dep = wr.dependency; |
| Artifact artifact = wr.artifact; |
| ExamineManifest depExaminator = examinerCache.get( artifact ); |
| String type = dep.getType(); |
| String depToken = dep.getExplicitValue(); |
| if ( depToken == null ) |
| { |
| if ( "loose".equals( type ) ) |
| { |
| depToken = depExaminator.getModuleWithRelease(); |
| } |
| else if ( "spec".equals( type ) ) |
| { |
| depToken = |
| depExaminator.getModuleWithRelease() |
| + " > " |
| + ( depExaminator.isNetBeansModule() ? depExaminator.getSpecVersion() |
| : AdaptNbVersion.adaptVersion( depExaminator.getSpecVersion(), |
| AdaptNbVersion.TYPE_SPECIFICATION, date ) ); |
| } |
| else if ( "impl".equals( type ) ) |
| { |
| depToken = |
| depExaminator.getModuleWithRelease() |
| + " = " |
| + ( depExaminator.isNetBeansModule() ? depExaminator.getImplVersion() |
| : AdaptNbVersion.adaptVersion( depExaminator.getImplVersion(), |
| AdaptNbVersion.TYPE_IMPLEMENTATION, date ) ); |
| } |
| else |
| { |
| throw new MojoExecutionException( |
| "Wrong type of NetBeans dependency: " + type + " Allowed values are: loose, spec, impl." ); |
| } |
| } |
| if ( depToken == null ) |
| { |
| //TODO report |
| getLog().error( |
| "Cannot properly resolve the NetBeans dependency for " + dep.getId() ); |
| } |
| else |
| { |
| dependencies = dependencies + depSeparator + depToken; |
| depSeparator = ", "; |
| } |
| } |
| if ( !verifyRuntime.equalsIgnoreCase( SKIP ) ) |
| { |
| try |
| { |
| checkModuleClassPath( treeroot, libArtifacts, examinerCache, moduleArtifacts, projectCNB ); |
| } |
| catch ( IOException ex ) |
| { |
| throw new MojoExecutionException( "Error while checking runtime dependencies", ex ); |
| } |
| } |
| |
| if ( nbmJavahelpSource.exists() ) |
| { |
| String moduleJarName = stripVersionFromCodebaseName( moduleName ).replace( ".", "-" ); |
| classPath.append( " docs/" ).append( moduleJarName ).append( ".jar" ); |
| } |
| |
| if ( classPath.length() > 0 ) |
| { |
| conditionallyAddAttribute( mainSection, "X-Class-Path", classPath.toString().trim() ); |
| } |
| if ( mavenClassPath.length() > 0 ) |
| { |
| conditionallyAddAttribute( mainSection, "Maven-Class-Path", mavenClassPath.toString() ); |
| } |
| if ( dependencies.length() > 0 ) |
| { |
| conditionallyAddAttribute( mainSection, "OpenIDE-Module-Module-Dependencies", dependencies ); |
| } |
| // if ( librList.size() > 0 ) |
| // { |
| // String list = ""; |
| // for ( int i = 0; i < librList.size(); i++ ) |
| // { |
| // list = list + " " + librList.get( i ); |
| // } |
| // getLog().warn( |
| // "Some libraries could not be found in the dependency chain: " + list ); |
| // } |
| PrintWriter writer = null; |
| try |
| { |
| if ( !targetManifestFile.exists() ) |
| { |
| targetManifestFile.getParentFile().mkdirs(); |
| targetManifestFile.createNewFile(); |
| } |
| writer = new PrintWriter( targetManifestFile, "UTF-8" ); //TODO really UTF-8?? |
| manifest.write( writer ); |
| } |
| catch ( IOException ex ) |
| { |
| throw new MojoExecutionException( ex.getMessage(), ex ); |
| } |
| finally |
| { |
| IOUtil.close( writer ); |
| } |
| } |
| |
| //MNBMODULE-137 |
| static String artifactToClassPathEntry( Artifact a, String codenamebase ) |
| { |
| return "ext/" + codenamebase + "/" + a.getGroupId().replace( '.', '-' ) + "/" + a.getArtifactId() + ( a.getClassifier() != null ? "-" + a.getClassifier() : "" ) + "." + a.getArtifactHandler().getExtension(); |
| } |
| |
| /** |
| * Create a timestamp for <code>OpenIDE-Module-Build-Version</code> manifest |
| * entry. |
| * |
| * It's created from the current time and formatted using a UTC timezone |
| * explicitly which makes created timestamp timezone-independent. |
| * |
| * @return timestamp represented as <code>201012292045</code> |
| */ |
| private static String createTimestamp( Date date ) |
| { |
| final SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyyMMddHHmm" ); |
| dateFormat.setTimeZone( TimeZone.getTimeZone( "UTC" ) ); |
| final String timestamp = dateFormat.format( date ); |
| return timestamp; |
| } |
| |
| static String stripVersionFromCodebaseName( String cnb ) |
| { |
| // it can happen the moduleName is in format org.milos/1 |
| String base = cnb; |
| int index = base.indexOf( '/' ); |
| if ( index > -1 ) |
| { |
| base = base.substring( 0, index ).trim(); |
| } |
| return base; |
| } |
| |
| String conditionallyAddAttribute( Manifest.Section section, String key, String value ) |
| { |
| Manifest.Attribute attr = section.getAttribute( key ); |
| if ( attr == null ) |
| { |
| attr = new Manifest.Attribute(); |
| attr.setName( key ); |
| attr.setValue( value != null ? value.replaceAll( "\\s+", " " ).trim() : "<undefined>" ); |
| try |
| { |
| section.addConfiguredAttribute( attr ); |
| } |
| catch ( ManifestException ex ) |
| { |
| getLog().error( "Cannot update manifest (key=" + key + ")" ); |
| ex.printStackTrace(); |
| } |
| } |
| return attr.getValue(); |
| } |
| |
| /** |
| * Pick out the first sentence of a paragraph. |
| * @param paragraph some text (may be null) |
| * @return the first sentence (may be null) |
| */ |
| static String shorten( String paragraph ) |
| { |
| if ( paragraph == null || paragraph.length() == 0 ) |
| { |
| return null; |
| } |
| BreakIterator breaker = BreakIterator.getSentenceInstance(); |
| breaker.setText( paragraph ); |
| return paragraph.substring( 0, breaker.following( 0 ) ).trim(); |
| } |
| |
| //---------------------------------------------------------------------------------- |
| // classpat checking related. |
| //---------------------------------------------------------------------------------- |
| private void checkModuleClassPath( DependencyNode treeroot, |
| List<Artifact> libArtifacts, |
| Map<Artifact, ExamineManifest> examinerCache, List<ModuleWrapper> moduleArtifacts, String projectCodeNameBase ) |
| throws IOException, MojoExecutionException, MojoFailureException |
| { |
| Set<String> deps = buildProjectDependencyClasses( project, libArtifacts ); |
| deps.retainAll( allProjectClasses( project ) ); |
| |
| Set<String> own = projectModuleOwnClasses( project, libArtifacts ); |
| deps.removeAll( own ); |
| CollectModuleLibrariesNodeVisitor visitor = new CollectModuleLibrariesNodeVisitor( |
| project.getRuntimeArtifacts(), examinerCache, getLog(), treeroot, useOSGiDependencies ); |
| treeroot.accept( visitor ); |
| Map<String, List<Artifact>> modules = visitor.getDeclaredArtifacts(); |
| Map<Artifact, Set<String>> moduleAllClasses = new HashMap<Artifact, Set<String>>(); |
| |
| for ( ModuleWrapper wr : moduleArtifacts ) |
| { |
| if ( modules.containsKey( wr.artifact.getDependencyConflictId() ) ) |
| { |
| ExamineManifest man = examinerCache.get( wr.artifact ); |
| List<Artifact> arts = modules.get( wr.artifact.getDependencyConflictId() ); |
| Set<String>[] classes = visibleModuleClasses( arts, man, wr.dependency, projectCodeNameBase, false ); |
| deps.removeAll( classes[0] ); |
| moduleAllClasses.put( wr.artifact, classes[1] ); |
| } |
| } |
| |
| //now we have the classes that are not in public packages of declared modules, |
| //but are being used |
| if ( !deps.isEmpty() ) |
| { |
| Map<String, List<Artifact>> transmodules = visitor.getTransitiveArtifacts(); |
| for ( ModuleWrapper wr : moduleArtifacts ) |
| { |
| if ( transmodules.containsKey( wr.artifact.getDependencyConflictId() ) ) |
| { |
| ExamineManifest man = examinerCache.get( wr.artifact ); |
| List<Artifact> arts = transmodules.get( wr.artifact.getDependencyConflictId() ); |
| Set<String>[] classes = visibleModuleClasses( arts, man, wr.dependency, projectCodeNameBase, true ); |
| classes[0].retainAll( deps ); |
| if ( classes[0].size() > 0 ) |
| { |
| String module = wr.osgi ? "OSGi bundle" : "module"; |
| getLog().error( |
| "Project uses classes from transitive " + module + " " + wr.artifact.getId() + " which will not be accessible at runtime." ); |
| getLog().info( " To fix the problem, add this module as direct dependency. For OSGi bundles that are supposed to be wrapped in NetBeans modules, use the useOSGiDependencies=false parameter" ); |
| deps.removeAll( classes[0] ); |
| } |
| classes[1].retainAll( deps ); |
| if ( classes[1].size() > 0 ) |
| { |
| getLog().info( "Private classes referenced in transitive module: " + Arrays.toString( classes[1].toArray() ) ); |
| getLog().error( |
| "Project depends on packages not accessible at runtime in transitive module " + wr.artifact.getId() + " which will not be accessible at runtime." ); |
| deps.removeAll( classes[1] ); |
| } |
| } |
| } |
| for ( Map.Entry<Artifact, Set<String>> e : moduleAllClasses.entrySet() ) |
| { |
| List<String> strs = new ArrayList<String>( deps ); |
| if ( deps.removeAll( e.getValue() ) ) |
| { |
| strs.retainAll( e.getValue() ); |
| getLog().info( "Private classes referenced in module: " + Arrays.toString( strs.toArray() ) ); |
| getLog().error( "Project depends on packages not accessible at runtime in module " + e.getKey().getId() ); |
| } |
| } |
| if ( verifyRuntime.equalsIgnoreCase( FAIL ) ) |
| { |
| if ( !deps.isEmpty() ) |
| { |
| throw new MojoFailureException( "Uncategorized problems with NetBeans dependency verification (maybe MNBMODULE-102 or wrong maven dependency metadata). Supposedly external classes are used in the project's binaries but the classes are not found on classpath. Class usages: " + deps ); |
| } |
| else |
| { |
| throw new MojoFailureException( "See above for failures in runtime NetBeans dependencies verification." ); |
| } |
| } |
| } |
| } |
| |
| /** |
| * The current projects's dependencies, includes classes used in teh module itself |
| * and the classpath libraries as well. |
| * @param project |
| * @param libraries |
| * @return |
| * @throws java.io.IOException |
| */ |
| private Set<String> buildProjectDependencyClasses( MavenProject project, List<Artifact> libraries ) |
| throws IOException |
| { |
| Set<String> dependencyClasses = new HashSet<String>(); |
| |
| String outputDirectory = project.getBuild().getOutputDirectory(); |
| dependencyClasses.addAll( buildDependencyClasses( outputDirectory ) ); |
| |
| for ( Artifact lib : libraries ) |
| { |
| dependencyClasses.addAll( buildDependencyClasses( lib.getFile().getAbsolutePath() ) ); |
| } |
| return dependencyClasses; |
| } |
| |
| @SuppressWarnings( "unchecked" ) |
| private Set<String> projectModuleOwnClasses( MavenProject project, List<Artifact> libraries ) |
| throws IOException |
| { |
| Set<String> projectClasses = new HashSet<String>(); |
| DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer(); |
| |
| String outputDirectory = project.getBuild().getOutputDirectory(); |
| URL fl = new File( outputDirectory ).toURI().toURL(); |
| projectClasses.addAll( analyzer.analyze( fl ) ); |
| |
| for ( Artifact lib : libraries ) |
| { |
| URL url = lib.getFile().toURI().toURL(); |
| projectClasses.addAll( analyzer.analyze( url ) ); |
| } |
| |
| return projectClasses; |
| } |
| |
| /** |
| * complete list of classes on project runtime classpath (excluding |
| * jdk bit) |
| * @param project |
| * @return |
| * @throws java.io.IOException |
| */ |
| @SuppressWarnings( "unchecked" ) |
| private Set<String> allProjectClasses( MavenProject project ) |
| throws IOException |
| { |
| Set<String> projectClasses = new HashSet<String>(); |
| DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer(); |
| |
| String outputDirectory = project.getBuild().getOutputDirectory(); |
| URL fl = new File( outputDirectory ).toURI().toURL(); |
| projectClasses.addAll( analyzer.analyze( fl ) ); |
| |
| List<Artifact> libs = project.getRuntimeArtifacts(); |
| |
| for ( Artifact lib : libs ) |
| { |
| URL url = lib.getFile().toURI().toURL(); |
| projectClasses.addAll( analyzer.analyze( url ) ); |
| } |
| |
| return projectClasses; |
| } |
| |
| private Set<String>[] visibleModuleClasses( List<Artifact> moduleLibraries, |
| ExamineManifest manifest, Dependency dep, String projectCodeNameBase, |
| boolean transitive ) |
| throws IOException, MojoFailureException |
| { |
| Set<String> moduleClasses = new HashSet<String>(); |
| Set<String> visibleModuleClasses = new HashSet<String>(); |
| DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer(); |
| String type = dep.getType(); |
| if ( dep.getExplicitValue() != null ) |
| { |
| if ( dep.getExplicitValue().contains( "=" ) ) |
| { |
| type = "impl"; |
| } |
| } |
| if ( type == null || "loose".equals( type ) ) |
| { |
| type = "spec"; |
| } |
| |
| for ( Artifact lib : moduleLibraries ) |
| { |
| URL url = lib.getFile().toURI().toURL(); |
| moduleClasses.addAll( analyzer.analyze( url ) ); |
| } |
| |
| if ( "spec".equals( type ) ) |
| { |
| String cnb = stripVersionFromCodebaseName( projectCodeNameBase ); |
| if ( !transitive && manifest.hasFriendPackages() && !manifest.getFriends().contains( cnb ) ) |
| { |
| String message = "Module has friend dependency on " + manifest.getModule() + " but is not listed as a friend."; |
| if ( verifyRuntime.equalsIgnoreCase( FAIL ) ) |
| { |
| throw new MojoFailureException( message ); |
| } |
| else |
| { |
| getLog().warn( message ); |
| } |
| } |
| List<Pattern> compiled = createCompiledPatternList( manifest.getPackages() ); |
| if ( useOSGiDependencies && manifest.isOsgiBundle() ) |
| { |
| // TODO how to extract the public packages in osgi bundles easily.. |
| compiled = Collections.singletonList( Pattern.compile( "(.+)" ) ); |
| } |
| for ( String clazz : moduleClasses ) |
| { |
| for ( Pattern patt : compiled ) |
| { |
| if ( patt.matcher( clazz ).matches() ) |
| { |
| visibleModuleClasses.add( clazz ); |
| break; |
| } |
| } |
| } |
| |
| } |
| else if ( "impl".equals( type ) ) |
| { |
| visibleModuleClasses.addAll( moduleClasses ); |
| } |
| else |
| { |
| //HUH? |
| throw new MojoFailureException( "Wrong type of module dependency " + type ); |
| } |
| |
| return new Set[] |
| { |
| visibleModuleClasses, |
| moduleClasses |
| }; |
| } |
| |
| static List<Pattern> createCompiledPatternList( List<String> packages ) |
| { |
| List<Pattern> toRet = new ArrayList<Pattern>(); |
| for ( String token : packages ) |
| { |
| if ( token.endsWith( ".**" ) ) |
| { |
| String patt = "^" + Pattern.quote( token.substring( 0, token.length() - 2 ) ) + "(.+)"; |
| toRet.add( 0, Pattern.compile( patt ) ); |
| } |
| else |
| { |
| String patt = "^" + Pattern.quote( token.substring( 0, token.length() - 1 ) ) + "([^\\.]+)"; |
| toRet.add( Pattern.compile( patt ) ); |
| } |
| } |
| return toRet; |
| } |
| |
| @SuppressWarnings( "unchecked" ) |
| private Set<String> buildDependencyClasses( String path ) |
| throws IOException |
| { |
| URL url = new File( path ).toURI().toURL(); |
| ASMDependencyAnalyzer dependencyAnalyzer = new ASMDependencyAnalyzer(); |
| return dependencyAnalyzer.analyze( url ); |
| } |
| } |