View Javadoc
1   package org.apache.maven.plugin.cxx;
2   
3   /*
4    * Copyright (C) 2011-2016, Neticoa SAS France - Tous droits réservés.
5    * Author(s) : Franck Bonin, Neticoa SAS France
6    *
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   *
19   */
20  
21  import org.apache.commons.io.IOUtils;
22  import org.codehaus.plexus.util.StringUtils;
23  import org.apache.commons.lang.text.StrSubstitutor;
24  import org.apache.maven.plugin.ContextEnabled;
25  import org.apache.maven.plugin.MojoExecutionException;
26  import org.apache.maven.plugin.MojoFailureException;
27  import org.apache.maven.plugins.annotations.Execute;
28  import org.apache.maven.plugins.annotations.LifecyclePhase;
29  import org.apache.maven.plugins.annotations.Mojo;
30  import org.apache.maven.plugins.annotations.Parameter;
31  import java.io.File;
32  import java.io.IOException;
33  import java.io.InputStream;
34  
35  import java.io.FileOutputStream;
36  import java.io.FileInputStream;
37  import java.nio.file.attribute.BasicFileAttributeView;
38  import java.nio.file.attribute.BasicFileAttributes;
39  import java.nio.file.Files;
40  import java.nio.file.Paths;
41  import java.net.URL;
42  import java.net.URISyntaxException;
43  import java.text.SimpleDateFormat;
44  import java.util.Date;
45  import java.util.Map;
46  import java.util.Collection;
47  import java.util.HashMap;
48  import java.util.jar.JarFile;
49  import java.util.jar.JarEntry;
50  import java.util.Enumeration;
51  import java.util.regex.Pattern;
52  
53  import org.apache.commons.io.FileUtils;
54  import org.apache.commons.io.filefilter.TrueFileFilter;
55  
56  import org.apache.maven.plugin.cxx.utils.svn.SvnService;
57  import org.apache.maven.plugin.cxx.utils.svn.SvnInfo;
58  
59  /**
60   * Generates a new project from an archetype, or updates the actual project if using a partial archetype.
61   * If the project is generated or updated in the current directory.
62   * mvn cxx:generate -DartifactName="an-id" -DartifactId="AnId"
63   * 
64   * @author Franck Bonin 
65   * @since 0.0.6
66   */
67  @Mojo( name = "generate", requiresProject = false )
68  @Execute( phase = LifecyclePhase.GENERATE_SOURCES )
69  public class GenerateMojo
70      extends AbstractCxxMojo
71      implements ContextEnabled
72  {
73      /**
74       * The archetype's artifactId 
75       * {cmake-cpp-project | source-project | aggregator-pom | project-parent-pom | cpp-super-pom }
76       * 
77       * @since 0.0.6
78       */
79      @Parameter( property = "archetypeArtifactId", defaultValue = "cmake-cpp-project" )
80      private String archetypeArtifactId;
81  
82      /**
83       * The archetype's groupId (not used, reserved for futur usage).
84       * 
85       * @since 0.0.6
86       */
87      @Parameter( property = "archetypeGroupId", defaultValue = "org.apache.maven.plugin.cxx" )
88      private String archetypeGroupId;
89  
90      /**
91       * The archetype's version (not used, reserved for futur usage).
92       * @since 0.0.6
93       */
94      @Parameter( property = "archetypeVersion", defaultValue = "0.1" )
95      private String archetypeVersion;
96      
97      /**
98       * The generated pom parent groupId.
99       * 
100      * @since 0.0.6
101      */
102     @Parameter( property = "parentGroupId", defaultValue = "fr.neticoa" )
103     private String parentGroupId;
104     
105     /**
106      * The generated pom parent artifactId.
107      * 
108      * @since 0.0.6
109      */
110     @Parameter( property = "parentArtifactId", defaultValue = "cpp-super-pom" ) 
111     private String parentArtifactId;
112     
113     /**
114      * The generated pom parent version.
115      * 
116      * @since 0.0.6
117      */
118     @Parameter( property = "parentVersion", defaultValue = "1.1.0.0" )
119     private String parentVersion;
120     
121     /**
122      * The generated pom groupId.
123      * 
124      * @since 0.0.6
125      */
126     @Parameter( property = "groupId", defaultValue = "fr.neticoa" )
127     private String groupId;
128     
129     /**
130      * The generated pom artifact Name.
131      * 
132      * @since 0.0.6
133      */
134     @Parameter( property = "artifactName", required = true )
135     private String artifactName;
136      
137     /**
138      * The generated pom artifactId
139      * 
140      * @since 0.0.6
141      */
142     @Parameter( property = "artifactId", required = true )
143     private String artifactId;
144     
145     /**
146      * The generated pom version.
147      * 
148      * @since 0.0.6
149      */
150     @Parameter( property = "version", defaultValue = "0.0.0.1" )
151     private String version;
152     
153     /**
154      * The generated CMakeLists file CMake min version.
155      * 
156      * @since 0.0.6
157      */
158     @Parameter( property = "cmakeMinVersion", defaultValue = "3.0.0" )
159     private String cmakeMinVersion;
160     
161     protected Map<String, String> listResourceFolderContent( String path, Map valuesMap )
162     {
163         String location = getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
164         final File jarFile = new File( location );
165         
166         HashMap<String, String> resources = new HashMap<String, String>();
167         StrSubstitutor substitutor = new StrSubstitutor( valuesMap );
168         
169         path = ( StringUtils.isEmpty( path ) ) ? "" : path + "/";
170         getLog().debug( "listResourceFolderContent : " + location + ", sublocation : " + path );
171         if ( jarFile.isFile() )
172         {
173             getLog().debug( "listResourceFolderContent : jar case" );
174             try
175             {
176                 final JarFile jar = new JarFile( jarFile );
177                 final Enumeration<JarEntry> entries = jar.entries();
178                 while ( entries.hasMoreElements() )
179                 {
180                     final String name = entries.nextElement().getName();
181                     if ( name.startsWith( path ) )
182                     { 
183                         String resourceFile = File.separator + name;
184                         if ( !( resourceFile.endsWith( "/" ) || resourceFile.endsWith( "\\" ) ) )
185                         {
186                             getLog().debug( "resource entry = " + resourceFile );
187                             String destFile = substitutor.replace( resourceFile );
188                             getLog().debug( "become entry = " + destFile );
189                             resources.put( resourceFile, destFile );
190                         }
191                     }
192                 }
193                 jar.close();
194             }
195             catch ( IOException ex )
196             {
197                 getLog().error( "unable to list jar content : " + ex );
198             }
199         }
200         else
201         {
202             getLog().debug( "listResourceFolderContent : file case" );
203             //final URL url = Launcher.class.getResource("/" + path);
204             final URL url = getClass().getResource( "/" + path );
205             if ( url != null )
206             {
207                 try
208                 {
209                     final File names = new File( url.toURI() );
210                     Collection<File> entries = 
211                         FileUtils.listFiles( names, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE );
212                     for ( File name : entries )
213                     {
214                         String resourceFile = name.getPath();
215                         if ( !( resourceFile.endsWith( "/" ) || resourceFile.endsWith( "\\" ) ) )
216                         {
217                             getLog().debug( "resource entry = " + resourceFile );
218                             String destFile = substitutor.replace( resourceFile );
219                             destFile = destFile.replaceFirst( Pattern.quote( location ), "/" );
220                             getLog().debug( "become entry = " + destFile );
221                             resources.put( resourceFile, destFile );
222                         }
223                     }
224                 }
225                 catch ( URISyntaxException ex )
226                 {
227                     // never happens
228                 }
229             }
230         }
231         return resources;
232     }
233 
234     @Override
235     public void execute()
236         throws MojoExecutionException, MojoFailureException
237     {
238         //Properties systemProperties = session.getSystemProperties();
239         //Properties userProperties = session.getUserProperties();
240         //Properties properties = session.getExecutionProperties();
241         
242         org.apache.maven.artifact.versioning.DefaultArtifactVersion defautCMakeVersion =
243             new org.apache.maven.artifact.versioning.DefaultArtifactVersion( "3.0.0" );
244         org.apache.maven.artifact.versioning.DefaultArtifactVersion askedCMakeVersion =
245             new org.apache.maven.artifact.versioning.DefaultArtifactVersion( cmakeMinVersion );
246         boolean bCMake3OrAbove = ( askedCMakeVersion.compareTo( defautCMakeVersion ) >= 0 );
247         
248         getLog().debug( "CMake 3 or above asked (" + cmakeMinVersion + ") ? " + ( bCMake3OrAbove ? "yes" : "no" ) );
249         
250         HashMap<String, String> valuesMap = new HashMap<String, String>();
251         valuesMap.put( "parentGroupId", parentGroupId );
252         valuesMap.put( "parentArtifactId", parentArtifactId );
253         valuesMap.put( "parentVersion", parentVersion );
254         valuesMap.put( "groupId", groupId );
255         valuesMap.put( "artifactId", artifactId );
256         valuesMap.put( "artifactName", artifactName );
257         valuesMap.put( "version", version );
258         valuesMap.put( "cmakeMinVersion", cmakeMinVersion );
259         valuesMap.put( "parentScope", bCMake3OrAbove ? "PARENT_SCOPE" : "" );
260         valuesMap.put( "projectVersion", bCMake3OrAbove ? "VERSION ${TARGET_VERSION}" : "" );
261         valuesMap.put( "scmConnection", "" );
262 
263 //1/ search for properties
264 // -DgroupId=fr.neticoa -DartifactName=QtUtils -DartifactId=qtutils -Dversion=1.0-SNAPSHOT
265 
266         if ( StringUtils.isEmpty( archetypeArtifactId ) )
267         {
268             throw new MojoExecutionException( "archetypeArtifactId is empty " );
269         }
270 
271         Map<String, String> resources = listResourceFolderContent( archetypeArtifactId, valuesMap );
272         
273         if ( null == resources || resources.size() == 0 )
274         {
275             throw new MojoExecutionException( "Unable to find archetype : " + archetypeArtifactId );
276         }
277 //1.1/ search potential scm location of current dir
278         // svn case
279         SvnInfo basedirSvnInfo = SvnService.getSvnInfo( basedir, null, basedir.getAbsolutePath(), getLog(), true );
280         if ( basedirSvnInfo.isValide() )
281         {
282             valuesMap.put( "scmConnection", "scm:svn:" + basedirSvnInfo.getSvnUrl() );
283         }
284         // todo : handle other scm : git (git remote -v; git log --max-count=1), etc.
285         
286 //2/ unpack resource to destdir 
287         getLog().info( "archetype " + archetypeArtifactId + " has " + resources.entrySet().size() + " item(s)" );
288         getLog().info( "basedir = " + basedir );
289                 
290         StrSubstitutor substitutor = new StrSubstitutor( valuesMap, "$(", ")" );
291         String sExecutionDate = new SimpleDateFormat( "yyyy-MM-dd-HH:mm:ss.SSS" ).format( new Date() );
292         for ( Map.Entry<String, String> entry : resources.entrySet() )
293         {
294             String curRes = entry.getKey();
295             String curDest = entry.getValue();
296             InputStream resourceStream = null;
297             resourceStream = getClass().getResourceAsStream( curRes );
298             if ( null == resourceStream )
299             {
300                 try
301                 { 
302                     resourceStream = new FileInputStream( new File( curRes ) );
303                 }
304                 catch ( Exception e )
305                 {
306                     // handled later
307                     resourceStream = null;
308                 }
309             }
310             
311             getLog().debug( "resource stream to open : " + curRes );
312             getLog().debug( "destfile pattern : " + curDest );
313             if ( null != resourceStream )
314             {
315                 String sRelativePath = curDest.replaceFirst( Pattern.quote( archetypeArtifactId
316                     + File.separator ), "" );
317                 File newFile = new File( basedir + File.separator + sRelativePath );
318                 
319 //3/ create empty dir struct; if needed using a descriptor 
320 //create all non exists folders
321                 File newDirs = new File( newFile.getParent() );
322                 if ( Files.notExists( Paths.get( newDirs.getPath() ) ) )
323                 {
324                     getLog().info( "dirs to generate : " + newDirs.getAbsoluteFile() );
325                     newDirs.mkdirs();
326                 }
327                 
328                 if ( !newFile.getName().equals( "empty.dir" ) )
329                 {
330                     getLog().info( "file to generate : " + newFile.getAbsoluteFile() );
331                     try
332                     { 
333                         if ( !newFile.createNewFile() )
334                         {
335                             // duplicate existing file
336                             FileInputStream inStream = new FileInputStream( newFile );
337                             File backFile = File.createTempFile( newFile.getName() + ".",
338                                 "." + sExecutionDate + ".back", newFile.getParentFile() );
339                             FileOutputStream outStream = new FileOutputStream( backFile );
340                             
341                             IOUtils.copy( inStream, outStream );
342                             // manage file times
343                             //backFile.setLastModified(newFile.lastModified());
344                             BasicFileAttributes attributesFrom = 
345                                 Files.getFileAttributeView( Paths.get( newFile.getPath() ),
346                                     BasicFileAttributeView.class ).readAttributes();
347                             BasicFileAttributeView attributesToView =
348                                 Files.getFileAttributeView( Paths.get( backFile.getPath() ),
349                                     BasicFileAttributeView.class );
350                             attributesToView.setTimes(
351                                 attributesFrom.lastModifiedTime(),
352                                 attributesFrom.lastAccessTime(),
353                                 attributesFrom.creationTime() );
354 
355                             inStream.close();
356                             outStream.close();
357                         }
358                         FileOutputStream outStream = new FileOutputStream( newFile );
359                         
360 //4/ variable substitution :
361 // change prefix and suffix to '$(' and ')'
362 // see https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/text/StrSubstitutor.html
363                         String content = IOUtils.toString( resourceStream, "UTF8" );
364                         content = substitutor.replace( content );
365 
366                         //IOUtils.copy( resourceStream, outStream );
367                         IOUtils.write( content, outStream, "UTF8" );
368 
369                         outStream.close();
370                         resourceStream.close();
371                     }
372                     catch ( IOException e )
373                     {
374                         getLog().error( "File " + newFile.getAbsoluteFile() + " can't be created : " + e );
375                     }
376                 }
377             }
378             else
379             {
380                 getLog().error( "Unable to open resource " + curRes );
381             }            
382         }
383     }
384 }