package org.codehaus.mojo.rpm; /* * 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.IOException; import java.io.PrintWriter; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; import org.codehaus.plexus.util.DirectoryScanner; /** * Utility to write spec file based on {@link AbstractRPMMojo} instance. * * @author Brett Okken * @version $Revision$ * @since 2.0 */ final class SpecWriter { private final AbstractRPMMojo mojo; private final PrintWriter spec; /** * Creates instance with the <i>mojo</i> to use and the <i>spec</i> to write to. * * @param mojo The mojo with the attributes to generate a spec file for. * @param spec The target to write the spec file to. */ public SpecWriter( AbstractRPMMojo mojo, PrintWriter spec ) { super(); this.mojo = mojo; this.spec = spec; } /** * Writes the spec file to <i>spec</i> using the attributes of <i>mojo</i>. * * @param mojo The mojo with the attributes to generate a spec file for. * @param spec The target to write the spec file to. * @throws MojoExecutionException * @throws IOException */ public void writeSpecFile() throws MojoExecutionException, IOException { writeList( mojo.getDefineStatements(), "%define " ); spec.println( "Name: " + mojo.getName() ); spec.println( "Version: " + mojo.getVersion() ); spec.println( "Release: " + mojo.getRelease() ); writeNonNullDirective( "Summary", mojo.getSummary() ); writeNonNullDirective( "License", mojo.getCopyright() ); writeNonNullDirective( "Distribution", mojo.getDistribution() ); if ( mojo.getIcon() != null ) { spec.println( "Icon: " + mojo.getIcon().getName() ); } writeNonNullDirective( "Vendor", mojo.getVendor() ); writeNonNullDirective( "URL", mojo.getUrl() ); writeNonNullDirective( "Group", mojo.getGroup() ); writeNonNullDirective( "Packager", mojo.getPackager() ); writeList( mojo.getProvides(), "Provides: " ); writeList( mojo.getRequires(), "Requires: " ); writeList( mojo.getPrereqs(), "PreReq: " ); writeList( mojo.getObsoletes(), "Obsoletes: " ); writeList( mojo.getConflicts(), "Conflicts: " ); spec.println( "autoprov: " + ( mojo.isAutoProvides() ? "yes" : "no" ) ); spec.println( "autoreq: " + ( mojo.isAutoRequires() ? "yes" : "no" ) ); if ( mojo.getPrefix() != null ) { spec.println( "Prefix: " + mojo.getPrefix() ); } spec.println( "BuildRoot: " + mojo.getRPMBuildroot().getAbsolutePath() ); spec.println(); spec.println( "%description" ); if ( mojo.getDescription() != null ) { spec.println( mojo.getDescription() ); } writeMove(); writeLinks(); if ( mojo.getInstallScriptlet() != null ) { spec.println(); mojo.getInstallScriptlet().writeContent( spec ); } writeFiles(); writeScripts(); if ( mojo.getTriggers() != null ) { for ( Iterator i = mojo.getTriggers().iterator(); i.hasNext(); ) { BaseTrigger trigger = (BaseTrigger) i.next(); trigger.writeTrigger( spec ); } } if ( mojo.getChangelog() != null ) { spec.println(); spec.println( "%changelog" ); spec.println( mojo.getChangelog() ); } } /** * Writes the %files directive based on {@link AbstractRPMMojo#mappings}. */ private void writeFiles() { final Log log = mojo.getLog(); spec.println(); spec.println( "%files" ); spec.println( getDefAttrString() ); for ( Iterator it = mojo.getMappings().iterator(); it.hasNext(); ) { Mapping map = (Mapping) it.next(); // For each mapping we need to determine which files in the destination were defined by this // mapping so that we can write the %attr statement correctly. final String destination = map.getDestination(); final File absoluteDestination = map.getAbsoluteDestination(); if ( map.hasSoftLinks() && !absoluteDestination.exists() ) { log.debug( "writing attribute string for directory created by soft link: " + destination ); final String attributes = map.getAttrString( mojo.getDefaultFilemode(), mojo.getDefaultGroupname(), mojo.getDefaultUsername() ); spec.print( attributes ); spec.print( ' ' ); spec.println( destination ); continue; } final List includes = map.getCopiedFileNamesRelativeToDestination(); final List links = map.getLinkedFileNamesRelativeToDestination(); final DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir( absoluteDestination ); // the linked files are not present yet (will be "installed" during rpm build) // so they cannot be "included" scanner.setIncludes( includes.isEmpty() ? null : (String[]) includes.toArray( new String[ includes.size() ] ) ); scanner.setExcludes( null ); scanner.scan(); final String attrString = map.getAttrString( mojo.getDefaultFilemode(), mojo.getDefaultGroupname(), mojo.getDefaultUsername() ); if ( scanner.isEverythingIncluded() && links.isEmpty() && map.isDirectoryIncluded() && !map.isRecurseDirectories() ) { log.debug( "writing attribute string for directory: " + destination ); spec.println( attrString + " " + destination ); } else { log.debug( "writing attribute string for identified files in directory: " + destination ); final String baseFileString = attrString + " " + destination + File.separatorChar; // only list files if requested (directoryIncluded == false) or we have to if ( !( map.isDirectoryIncluded() && scanner.isEverythingIncluded() && links.isEmpty() ) ) { final String[] files = scanner.getIncludedFiles(); for ( int i = 0; i < files.length; ++i ) { spec.print( baseFileString ); spec.println( files[i] ); } } if ( map.isRecurseDirectories() ) { final String[] dirs = scanner.getIncludedDirectories(); if ( map.isDirectoryIncluded() ) { // write out destination first spec.println( baseFileString ); } for ( int i = 0; i < dirs.length; ++i ) { // do not write out base file (destination) again if ( dirs[i].length() > 0 ) { spec.print( baseFileString ); spec.println( dirs[i] ); } } } // since the linked files are not present in directory (yet), the scanner will not find them for ( Iterator linkIter = links.iterator(); linkIter.hasNext(); ) { String link = (String) linkIter.next(); spec.print( baseFileString ); spec.println( link ); } } } } /** * Writes the beginning of the <i>%install</i> which includes moving all files from the * {@link AbstractRPMMojo#getBuildroot()} to {@link AbstractRPMMojo#getRPMBuildroot()}. */ private void writeMove() { final String tmpBuildRoot = mojo.getBuildroot().getAbsolutePath(); spec.println(); spec.println( "%install" ); spec.println( "if [ -e $RPM_BUILD_ROOT ];" ); spec.println( "then" ); spec.print( " mv " ); spec.print( tmpBuildRoot ); spec.println( "/* $RPM_BUILD_ROOT" ); spec.println( "else" ); spec.print( " mv " ); spec.print( tmpBuildRoot ); spec.println( " $RPM_BUILD_ROOT" ); spec.println( "fi" ); } /** * Writes the install commands to link files. */ private void writeLinks() { if ( !mojo.getLinkTargetToSources().isEmpty() ) { spec.println(); for ( Iterator entryIter = mojo.getLinkTargetToSources().entrySet().iterator(); entryIter.hasNext(); ) { final Map.Entry directoryToSourcesEntry = (Entry) entryIter.next(); String directory = (String) directoryToSourcesEntry.getKey(); if ( directory.startsWith( "/" ) ) { directory = directory.substring( 1 ); } if ( directory.endsWith( "/" ) ) { directory = directory.substring( 0, directory.length() - 1 ); } final List sources = (List) directoryToSourcesEntry.getValue(); final int sourceCnt = sources.size(); if ( sourceCnt == 1 ) { final SoftlinkSource linkSource = (SoftlinkSource) sources.get( 0 ); final String macroEvaluatedLocation = linkSource.getMacroEvaluatedLocation(); final File buildSourceLocation; if ( macroEvaluatedLocation.startsWith( "/" ) ) { buildSourceLocation = new File( mojo.getBuildroot(), macroEvaluatedLocation ); } else { buildSourceLocation = new File( mojo.getBuildroot(), directory + '/' + macroEvaluatedLocation ); } if ( buildSourceLocation.isDirectory() ) { final DirectoryScanner scanner = scanLinkSource( linkSource, buildSourceLocation ); if ( scanner.isEverythingIncluded() ) { final File destinationFile = linkSource.getSourceMapping().getAbsoluteDestination(); destinationFile.delete(); spec.print( "ln -s " ); spec.print( linkSource.getLocation() ); spec.print( " $RPM_BUILD_ROOT/" ); spec.print( directory ); final String dest = linkSource.getDestination(); if ( dest != null ) { spec.print( '/' ); spec.print( dest ); linkSource.getSourceMapping().addLinkedFileNameRelativeToDestination( dest ); } spec.println(); } else { linkScannedFiles( directory, linkSource, scanner ); } } else { linkSingleFile( directory, linkSource ); } } else { for ( Iterator sourceIter = sources.iterator(); sourceIter.hasNext(); ) { final SoftlinkSource linkSource = (SoftlinkSource) sourceIter.next(); final String sourceLocation = linkSource.getMacroEvaluatedLocation(); final File buildSourceLocation; if ( sourceLocation.startsWith( "/" ) ) { buildSourceLocation = new File( mojo.getBuildroot(), sourceLocation ); } else { buildSourceLocation = new File( mojo.getBuildroot(), directory + '/' + sourceLocation ); } if ( buildSourceLocation.isDirectory() ) { final DirectoryScanner scanner = scanLinkSource( linkSource, buildSourceLocation ); linkScannedFiles( directory, linkSource, scanner ); } else { linkSingleFile( directory, linkSource ); } } } } } } /** * Writes soft link from <i>linkSource</i> to <i>directory</i> for all files in the <i>scanner</i>. * * @param directory Directory to link to. * @param linkSource Source to link from. {@link SoftlinkSource#getLocation()} must be a {@link File#isDirectory() * directory}. * @param scanner Scanner used to scan the {@link SoftlinkSource#getLocation() linSource location}. */ private void linkScannedFiles( String directory, final SoftlinkSource linkSource, final DirectoryScanner scanner ) { final String[] files = scanner.getIncludedFiles(); final String sourceLocation = linkSource.getLocation(); final String targetPrefix = sourceLocation + File.separatorChar; final String sourcePrefix = directory + File.separatorChar; for ( int i = 0; i < files.length; ++i ) { spec.print( "ln -s " ); spec.print( targetPrefix + files[i] ); spec.print( " $RPM_BUILD_ROOT/" ); spec.println( sourcePrefix + files[i] ); linkSource.getSourceMapping().addLinkedFileNameRelativeToDestination( files[i] ); } } /** * {@link DirectoryScanner#scan() Scans} the <i>buildSourceLocation</i> using the * {@link SoftlinkSource#getIncludes()} and {@link SoftlinkSource#getExcludes()} from <i>linkSource</i>. Returns the * {@link DirectoryScanner} used for scanning. * * @param linkSource Source * @param buildSourceLocation Build location where content exists. * @return {@link DirectoryScanner} used for scanning. */ private static DirectoryScanner scanLinkSource( final SoftlinkSource linkSource, final File buildSourceLocation ) { final DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir( buildSourceLocation ); List includes = linkSource.getIncludes(); scanner.setIncludes( ( includes == null || includes.isEmpty() ) ? null : (String[]) includes.toArray( new String[includes.size()] ) ); List excludes = linkSource.getExcludes(); scanner.setExcludes( ( excludes == null || excludes.isEmpty() ) ? null : (String[]) excludes.toArray( new String[excludes.size()] ) ); scanner.scan(); return scanner; } /** * Assemble the RPM SPEC default file attributes. * * @return The attribute string for the SPEC file. */ private final String getDefAttrString() { final String defaultFilemode = mojo.getDefaultFilemode(); final String defaultUsername = mojo.getDefaultUsername(); final String defaultGroupname = mojo.getDefaultGroupname(); final String defaultDirmode = mojo.getDefaultDirmode(); /* do not include %defattr if no default attributes are specified */ if ( defaultFilemode == null && defaultUsername == null && defaultGroupname == null && mojo.getDefaultDirmode() == null && defaultDirmode == null ) { return ""; } StringBuffer sb = new StringBuffer(); if ( defaultFilemode != null ) { sb.append( "%defattr(" ).append( defaultFilemode ).append( "," ); } else { sb.append( "%defattr(-," ); } if ( defaultUsername != null ) { sb.append( defaultUsername ).append( "," ); } else { sb.append( "-," ); } if ( defaultGroupname != null ) { sb.append( defaultGroupname ).append( "," ); } else { sb.append( "-," ); } if ( defaultDirmode != null ) { sb.append( defaultDirmode ).append( ")" ); } else { sb.append( "-)" ); } return sb.toString(); } /** * Writes soft link from <i>linkSource</i> to <i>directory</i> using optional * {@link SoftlinkSource#getDestination()} as the name of the link in <i>directory</i> if present. * * @param directory Directory to link to. * @param linkSource Source to link from. */ private void linkSingleFile( String directory, final SoftlinkSource linkSource ) { spec.print( "ln -s " ); spec.print( linkSource.getLocation() ); spec.print( " $RPM_BUILD_ROOT/" ); spec.print( directory ); spec.print( '/' ); final String destination = linkSource.getDestination(); final String linkedFileName = destination == null ? new File( linkSource.getMacroEvaluatedLocation() ).getName() : destination; spec.println( linkedFileName ); linkSource.getSourceMapping().addLinkedFileNameRelativeToDestination( linkedFileName ); } /** * Writes all the scriptlets to the <i>spec</i>. */ private void writeScripts() throws IOException { // all scriptlets in order to write final Scriptlet[] scriptlets = new Scriptlet[] { mojo.getPrepareScriptlet(), mojo.getPretransScriptlet(), mojo.getPreinstallScriptlet(), mojo.getPostinstallScriptlet(), mojo.getPreremoveScriptlet(), mojo.getPostremoveScriptlet(), mojo.getPosttransScriptlet(), mojo.getVerifyScriptlet(), mojo.getCleanScriptlet() }; // all directives, in parallel to scriptlets final String[] directives = new String[] { "%prep", "%pretrans", "%pre", "%post", "%preun", "%postun", "%posttrans", "%verifyscript", "%clean" }; for ( int i = 0; i < scriptlets.length; ++i ) { if ( scriptlets[i] != null ) { scriptlets[i].write( spec, directives[i] ); } } } /** * If<i>value</i> is not <code>null</code>, writes the <i>value</i> to <i>spec</i>. * <p> * Writes in format: <code><i>directive</i>: <i>value</i></code> * </p> * * @param directive * @param value */ private final void writeNonNullDirective( final String directive, final String value ) { if ( value != null ) { spec.print( directive ); spec.print( ": " ); spec.println( value ); } } /** * Writes a new line for each element in <i>strings</i> to the <i>writer</i> with the <i>prefix</i>. * * @param strings <tt>List</tt> of <tt>String</tt>s to write. * @param prefix Prefix to write on each line before the string. */ private void writeList( Collection strings, String prefix ) { if ( strings != null ) { for ( Iterator it = strings.iterator(); it.hasNext(); ) { spec.print( prefix ); spec.println( it.next() ); } } } }