/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.felix.bundleplugin; import java.io.File; import java.io.FilenameFilter; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.jar.Manifest; import java.util.regex.Matcher; 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.artifact.resolver.ArtifactNotFoundException; import org.apache.maven.artifact.resolver.ArtifactResolutionException; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.plugin.MojoExecutionException; 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.project.MavenProjectBuilder; import org.apache.maven.project.ProjectBuildingException; import org.apache.maven.project.artifact.InvalidDependencyVersionException; import org.apache.maven.shared.dependency.tree.DependencyNode; import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder; import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException; import org.codehaus.plexus.util.FileUtils; import aQute.bnd.osgi.Analyzer; import aQute.bnd.osgi.Jar; /** * Build an OSGi bundle jar for all transitive dependencies. * * @deprecated The bundleall goal is no longer supported and may be removed in a future release */ @Deprecated @Mojo( name = "bundleall", requiresDependencyResolution = ResolutionScope.TEST, defaultPhase = LifecyclePhase.PACKAGE ) public class BundleAllPlugin extends ManifestPlugin { private static final String LS = System.getProperty( "line.separator" ); private static final Pattern SNAPSHOT_VERSION_PATTERN = Pattern.compile( "[0-9]{8}_[0-9]{6}_[0-9]+" ); /** * Local repository. */ @Parameter( defaultValue = "${localRepository}", readonly = true, required = true ) private ArtifactRepository localRepository; /** * Remote repositories. */ @Parameter( defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true ) private List remoteRepositories; /** * Import-Package to be used when wrapping dependencies. */ @Parameter( property = "wrapImportPackage", defaultValue = "*" ) private String wrapImportPackage; @Component private ArtifactFactory m_factory; @Component private ArtifactMetadataSource m_artifactMetadataSource; @Component private ArtifactCollector m_collector; /** * Artifact resolver, needed to download jars. */ @Component private ArtifactResolver m_artifactResolver; @Component private DependencyTreeBuilder m_dependencyTreeBuilder; @Component private MavenProjectBuilder m_mavenProjectBuilder; /** * Ignore missing artifacts that are not required by current project but are required by the * transitive dependencies. */ @Parameter private boolean ignoreMissingArtifacts; private Set m_artifactsBeingProcessed = new HashSet(); /** * Process up to some depth */ @Parameter private int depth = Integer.MAX_VALUE; @Override public void execute() throws MojoExecutionException { getLog().warn( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ); getLog().warn( "! The bundleall goal is no longer supported and may be removed in a future release !" ); getLog().warn( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ); BundleInfo bundleInfo = bundleAll( getProject() ); logDuplicatedPackages( bundleInfo ); } /** * Bundle a project and all its dependencies * * @param project * @throws MojoExecutionException */ private BundleInfo bundleAll( MavenProject project ) throws MojoExecutionException { return bundleAll( project, depth ); } /** * Bundle a project and its transitive dependencies up to some depth level * * @param project * @param maxDepth how deep to process the dependency tree * @throws MojoExecutionException */ protected BundleInfo bundleAll( MavenProject project, int maxDepth ) throws MojoExecutionException { if ( alreadyBundled( project.getArtifact() ) ) { getLog().debug( "Ignoring project already processed " + project.getArtifact() ); return null; } if ( m_artifactsBeingProcessed.contains( project.getArtifact() ) ) { getLog().warn( "Ignoring artifact due to dependency cycle " + project.getArtifact() ); return null; } m_artifactsBeingProcessed.add( project.getArtifact() ); DependencyNode dependencyTree; try { dependencyTree = m_dependencyTreeBuilder.buildDependencyTree( project, localRepository, m_factory, m_artifactMetadataSource, null, m_collector ); } catch ( DependencyTreeBuilderException e ) { throw new MojoExecutionException( "Unable to build dependency tree", e ); } BundleInfo bundleInfo = new BundleInfo(); if ( !dependencyTree.hasChildren() ) { /* no need to traverse the tree */ return bundleRoot( project, bundleInfo ); } getLog().debug( "Will bundle the following dependency tree" + LS + dependencyTree ); for ( Iterator it = dependencyTree.inverseIterator(); it.hasNext(); ) { DependencyNode node = ( DependencyNode ) it.next(); if ( !it.hasNext() ) { /* this is the root, current project */ break; } if ( node.getState() != DependencyNode.INCLUDED ) { continue; } if ( Artifact.SCOPE_SYSTEM.equals( node.getArtifact().getScope() ) ) { getLog().debug( "Ignoring system scoped artifact " + node.getArtifact() ); continue; } Artifact artifact; try { artifact = resolveArtifact( node.getArtifact() ); } catch ( ArtifactNotFoundException e ) { if ( ignoreMissingArtifacts ) { continue; } throw new MojoExecutionException( "Artifact was not found in the repo" + node.getArtifact(), e ); } node.getArtifact().setFile( artifact.getFile() ); int nodeDepth = node.getDepth(); if ( nodeDepth > maxDepth ) { /* node is deeper than we want */ getLog().debug( "Ignoring " + node.getArtifact() + ", depth is " + nodeDepth + ", bigger than " + maxDepth ); continue; } MavenProject childProject; try { childProject = m_mavenProjectBuilder.buildFromRepository( artifact, remoteRepositories, localRepository, true ); if ( childProject.getDependencyArtifacts() == null ) { childProject.setDependencyArtifacts( childProject.createArtifacts( m_factory, null, null ) ); } } catch ( InvalidDependencyVersionException e ) { throw new MojoExecutionException( "Invalid dependency version for artifact " + artifact ); } catch ( ProjectBuildingException e ) { throw new MojoExecutionException( "Unable to build project object for artifact " + artifact, e ); } childProject.setArtifact( artifact ); getLog().debug( "Child project artifact location: " + childProject.getArtifact().getFile() ); if ( ( Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) ) || ( Artifact.SCOPE_RUNTIME.equals( artifact.getScope() ) ) ) { BundleInfo subBundleInfo = bundleAll( childProject, maxDepth - 1 ); if ( subBundleInfo != null ) { bundleInfo.merge( subBundleInfo ); } } else { getLog().debug( "Not processing due to scope (" + childProject.getArtifact().getScope() + "): " + childProject.getArtifact() ); } } return bundleRoot( project, bundleInfo ); } /** * Bundle the root of a dependency tree after all its children have been bundled * * @param project * @param bundleInfo * @return * @throws MojoExecutionException */ private BundleInfo bundleRoot( MavenProject project, BundleInfo bundleInfo ) throws MojoExecutionException { /* do not bundle the project the mojo was called on */ if ( getProject() != project ) { getLog().debug( "Project artifact location: " + project.getArtifact().getFile() ); BundleInfo subBundleInfo = bundle( project ); if ( subBundleInfo != null ) { bundleInfo.merge( subBundleInfo ); } } return bundleInfo; } /** * Bundle one project only without building its childre * * @param project * @throws MojoExecutionException */ protected BundleInfo bundle( MavenProject project ) throws MojoExecutionException { Artifact artifact = project.getArtifact(); getLog().info( "Bundling " + artifact ); try { Map instructions = new LinkedHashMap(); instructions.put( Analyzer.IMPORT_PACKAGE, wrapImportPackage ); project.getArtifact().setFile( getFile( artifact ) ); File outputFile = getOutputFile(artifact); if ( project.getArtifact().getFile().equals( outputFile ) ) { /* TODO find the cause why it's getting here */ return null; // getLog().error( // "Trying to read and write " + artifact + " to the same file, try cleaning: " // + outputFile ); // throw new IllegalStateException( "Trying to read and write " + artifact // + " to the same file, try cleaning: " + outputFile ); } org.apache.maven.shared.dependency.graph.DependencyNode dependencyGraph = buildDependencyGraph( project ); Analyzer analyzer = getAnalyzer( project, dependencyGraph, instructions, new Properties(), getClasspath( project, dependencyGraph ) ); Jar osgiJar = new Jar( project.getArtifactId(), project.getArtifact().getFile() ); outputFile.getAbsoluteFile().getParentFile().mkdirs(); Collection exportedPackages; if ( isOsgi( osgiJar ) ) { /* if it is already an OSGi jar copy it as is */ getLog().info( "Using existing OSGi bundle for " + project.getGroupId() + ":" + project.getArtifactId() + ":" + project.getVersion() ); String exportHeader = osgiJar.getManifest().getMainAttributes().getValue( Analyzer.EXPORT_PACKAGE ); exportedPackages = analyzer.parseHeader( exportHeader ).keySet(); FileUtils.copyFile( project.getArtifact().getFile(), outputFile ); } else { /* else generate the manifest from the packages */ exportedPackages = analyzer.getExports().keySet(); Manifest manifest = analyzer.getJar().getManifest(); osgiJar.setManifest( manifest ); osgiJar.write( outputFile ); } BundleInfo bundleInfo = addExportedPackages( project, exportedPackages ); // cleanup... analyzer.close(); osgiJar.close(); return bundleInfo; } /* too bad Jar.write throws Exception */ catch ( Exception e ) { throw new MojoExecutionException( "Error generating OSGi bundle for project " + getArtifactKey( project.getArtifact() ), e ); } } private boolean isOsgi( Jar jar ) throws Exception { if ( jar.getManifest() != null ) { return jar.getManifest().getMainAttributes().getValue( Analyzer.BUNDLE_NAME ) != null; } return false; } private BundleInfo addExportedPackages( MavenProject project, Collection packages ) { BundleInfo bundleInfo = new BundleInfo(); for ( Iterator it = packages.iterator(); it.hasNext(); ) { String packageName = ( String ) it.next(); bundleInfo.addExportedPackage( packageName, project.getArtifact() ); } return bundleInfo; } private String getArtifactKey( Artifact artifact ) { return artifact.getGroupId() + ":" + artifact.getArtifactId(); } private String getBundleName( Artifact artifact ) { return getMaven2OsgiConverter().getBundleFileName( artifact ); } private boolean alreadyBundled( Artifact artifact ) { return getBuiltFile( artifact ) != null; } /** * Use previously built bundles when available. * * @param artifact */ @Override protected File getFile( final Artifact artifact ) { File bundle = getBuiltFile( artifact ); if ( bundle != null ) { getLog().debug( "Using previously built OSGi bundle for " + artifact + " in " + bundle ); return bundle; } return super.getFile( artifact ); } private File getBuiltFile( final Artifact artifact ) { File bundle = null; /* if bundle was already built use it instead of jar from repo */ File outputFile = getOutputFile( artifact ); if ( outputFile.exists() ) { bundle = outputFile; } /* * Find snapshots in output folder, eg. 2.1-SNAPSHOT will match 2.1.0.20070207_193904_2 * TODO there has to be another way to do this using Maven libs */ if ( ( bundle == null ) && artifact.isSnapshot() ) { final File buildDirectory = new File( getBuildDirectory() ); if ( !buildDirectory.exists() ) { buildDirectory.mkdirs(); } File[] files = buildDirectory.listFiles( new FilenameFilter() { public boolean accept( File dir, String name ) { if ( dir.equals( buildDirectory ) && snapshotMatch( artifact, name ) ) { return true; } return false; } } ); if ( files.length > 1 ) { throw new RuntimeException( "More than one previously built bundle matches for artifact " + artifact + " : " + Arrays.asList( files ) ); } if ( files.length == 1 ) { bundle = files[0]; } } return bundle; } /** * Check that the bundleName provided correspond to the artifact provided. * Used to determine when the bundle name is a timestamped snapshot and the artifact is a snapshot not timestamped. * * @param artifact artifact with snapshot version * @param bundleName bundle file name * @return if both represent the same artifact and version, forgetting about the snapshot timestamp */ protected boolean snapshotMatch( Artifact artifact, String bundleName ) { String artifactBundleName = getBundleName( artifact ); int i = artifactBundleName.indexOf( "SNAPSHOT" ); if ( i < 0 ) { return false; } artifactBundleName = artifactBundleName.substring( 0, i ); if ( bundleName.startsWith( artifactBundleName ) ) { /* it's the same artifact groupId and artifactId */ String timestamp = bundleName.substring( artifactBundleName.length(), bundleName.lastIndexOf( ".jar" ) ); Matcher m = SNAPSHOT_VERSION_PATTERN.matcher( timestamp ); return m.matches(); } return false; } protected File getOutputFile( Artifact artifact ) { return new File( getOutputDirectory(), getBundleName( artifact ) ); } private Artifact resolveArtifact( Artifact artifact ) throws MojoExecutionException, ArtifactNotFoundException { VersionRange versionRange; if ( artifact.getVersion() != null ) { versionRange = VersionRange.createFromVersion( artifact.getVersion() ); } else { versionRange = artifact.getVersionRange(); } /* * there's a bug with ArtifactFactory#createDependencyArtifact(String, String, VersionRange, * String, String, String) that ignores the scope parameter, that's why we use the one with * the extra null parameter */ Artifact resolvedArtifact = m_factory.createDependencyArtifact( artifact.getGroupId(), artifact.getArtifactId(), versionRange, artifact.getType(), artifact.getClassifier(), artifact.getScope(), null ); try { m_artifactResolver.resolve( resolvedArtifact, remoteRepositories, localRepository ); } catch ( ArtifactResolutionException e ) { throw new MojoExecutionException( "Error resolving artifact " + resolvedArtifact, e ); } return resolvedArtifact; } /** * Log what packages are exported in more than one bundle */ protected void logDuplicatedPackages( BundleInfo bundleInfo ) { Map duplicatedExports = bundleInfo.getDuplicatedExports(); for ( Iterator it = duplicatedExports.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = ( Map.Entry ) it.next(); String packageName = ( String ) entry.getKey(); Collection artifacts = ( Collection ) entry.getValue(); getLog().warn( "Package " + packageName + " is exported in more than a bundle: " ); for ( Iterator it2 = artifacts.iterator(); it2.hasNext(); ) { Artifact artifact = ( Artifact ) it2.next(); getLog().warn( " " + artifact ); } } } }