001    /**
002     * Copyright (C) 2009 Progress Software, Inc.
003     * http://fusesource.com
004     * 
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     * 
009     *    http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.fusesource.hawtjni.maven;
018    
019    import java.io.File;
020    import java.io.IOException;
021    import java.util.List;
022    
023    import org.apache.maven.artifact.Artifact;
024    import org.apache.maven.artifact.factory.ArtifactFactory;
025    import org.apache.maven.artifact.repository.ArtifactRepository;
026    import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
027    import org.apache.maven.artifact.resolver.ArtifactResolutionException;
028    import org.apache.maven.artifact.resolver.ArtifactResolver;
029    import org.apache.maven.model.Resource;
030    import org.apache.maven.plugin.AbstractMojo;
031    import org.apache.maven.plugin.MojoExecutionException;
032    import org.apache.maven.project.MavenProject;
033    import org.codehaus.plexus.archiver.UnArchiver;
034    import org.codehaus.plexus.archiver.manager.ArchiverManager;
035    import org.codehaus.plexus.util.FileUtils;
036    import org.codehaus.plexus.util.cli.CommandLineException;
037    import org.fusesource.hawtjni.runtime.Library;
038    
039    /**
040     * This goal builds the JNI module which was previously
041     * generated with the generate goal.  It adds the JNI module
042     * to the test resource path so that unit tests can load 
043     * the freshly built JNI library.
044     * 
045     * @goal build
046     * @phase generate-test-resources
047     * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
048     */
049    public class BuildMojo extends AbstractMojo {
050    
051        /**
052         * The maven project.
053         * 
054         * @parameter expression="${project}"
055         * @required
056         * @readonly
057         */
058        protected MavenProject project;
059        
060        /**
061         * Remote repositories
062         *
063         * @parameter expression="${project.remoteArtifactRepositories}"
064         * @required
065         * @readonly
066         */
067        protected List remoteArtifactRepositories;
068    
069        /**
070         * Local maven repository.
071         *
072         * @parameter expression="${localRepository}"
073         * @required
074         * @readonly
075         */
076        protected ArtifactRepository localRepository;
077    
078        /**
079         * Artifact factory, needed to download the package source file
080         *
081         * @component role="org.apache.maven.artifact.factory.ArtifactFactory"
082         * @required
083         * @readonly
084         */
085        protected ArtifactFactory artifactFactory;
086    
087        /**
088         * Artifact resolver, needed to download the package source file
089         *
090         * @component role="org.apache.maven.artifact.resolver.ArtifactResolver"
091         * @required
092         * @readonly
093         */
094        protected ArtifactResolver artifactResolver;
095        
096        /**
097         * @component
098         * @required
099         * @readonly
100         */
101        private ArchiverManager archiverManager;    
102    
103        /**
104         * The base name of the library, used to determine generated file names.
105         * 
106         * @parameter default-value="${project.artifactId}"
107         */
108        private String name;
109        
110        /**
111         * Where the unpacked build package is located.
112         * 
113         * @parameter default-value="${project.build.directory}/generated-sources/hawtjni/native-package"
114         */
115        private File packageDirectory;
116    
117        /**
118         * The output directory where the built JNI library will placed.  This directory will be added
119         * to as a test resource path so that unit tests can verify the built JNI library.
120         * 
121         * The library will placed under the META-INF/native/${platform} directory that the HawtJNI
122         * Library uses to find JNI libraries as classpath resources.
123         * 
124         * @parameter default-value="${project.build.directory}/generated-sources/hawtjni/lib"
125         */
126        private File libDirectory;
127    
128        /**
129         * The directory where the build will be produced.  It creates a native-build and native-dist directory
130         * under the specified directory.
131         * 
132         * @parameter default-value="${project.build.directory}"
133         */
134        private File buildDirectory;
135    
136        /**
137         * Should we skip executing the autogen.sh file.
138         * 
139         * @parameter default-value="${skip-autogen}"
140         */
141        private boolean skipAutogen;
142        
143        /**
144         * Should we force executing the autogen.sh file.
145         * 
146         * @parameter default-value="${force-autogen}"
147         */
148        private boolean forceAutogen;
149        
150        /**
151         * Extra arguments you want to pass to the autogen.sh command.
152         * 
153         * @parameter
154         */
155        private List<String> autogenArgs;
156    
157        /**
158         * Should we skip executing the configure command.
159         * 
160         * @parameter default-value="${skip-configure}"
161         */
162        private boolean skipConfigure;
163    
164        /**
165         * Should we force executing the configure command.
166         * 
167         * @parameter default-value="${force-configure}"
168         */
169        private boolean forceConfigure;
170        
171        /**
172         * Should we display all the native build output?
173         * 
174         * @parameter default-value="${hawtjni-verbose}"
175         */
176        private boolean verbose;
177    
178        /**
179         * Extra arguments you want to pass to the configure command.
180         * 
181         * @parameter
182         */
183        private List<String> configureArgs;
184        
185        /**
186         * The platform identifier of this build.  If not specified,
187         * it will be automatically detected.
188         * 
189         * @parameter
190         */
191        private String platform;    
192        
193        /**
194         * The classifier of the package archive that will be created.
195         * 
196         * @parameter default-value="native-src"
197         */
198        private String sourceClassifier;  
199        
200        /**
201         * If the source build could not be fully generated, perhaps the autotools
202         * were not available on this platform, should we attempt to download
203         * a previously deployed source package and build that?
204         * 
205         * @parameter default-value="true"
206         */
207        private boolean downloadSourcePackage = true;  
208    
209        private final CLI cli = new CLI();
210    
211        public void execute() throws MojoExecutionException {
212            cli.verbose = verbose;
213            cli.log = getLog();
214            try {
215                File buildDir = new File(buildDirectory, "native-build");
216                buildDir.mkdirs();
217                if ( CLI.IS_WINDOWS ) {
218                    vsBasedBuild(buildDir);
219                } else {
220                    configureBasedBuild(buildDir);
221                }
222                
223                getLog().info("Adding test resource root: "+libDirectory.getAbsolutePath());
224                Resource testResource = new Resource();
225                testResource.setDirectory(libDirectory.getAbsolutePath());
226                this.project.addTestResource(testResource); //();
227                
228            } catch (Exception e) {
229                throw new MojoExecutionException("build failed: "+e, e);
230            } 
231        }
232    
233        private void vsBasedBuild(File buildDir) throws CommandLineException, MojoExecutionException, IOException {
234            
235            FileUtils.copyDirectoryStructureIfModified(packageDirectory, buildDir);
236    
237            Library library = new Library(name);
238            String platform;
239            String configuration="release";
240            if( "windows32".equals(library.getPlatform()) ) {
241                    platform = "Win32";
242            } else if( "windows64".equals(library.getPlatform()) ) {
243                    platform = "x64";
244            } else {
245                    throw new MojoExecutionException("Usupported platform: "+library.getPlatform());
246            }
247    
248            String toolset = System.getenv("PlatformToolset");
249            if( "Windows7.1SDK".equals(toolset) ) {
250                // vcbuild was removed.. use the msbuild tool instead.
251                int rc = cli.system(buildDir, new String[]{"msbuild", "vs2010.vcxproj", "/property:Platform="+platform, "/property:Configuration="+configuration});
252                if( rc != 0 ) {
253                    throw new MojoExecutionException("vcbuild failed with exit code: "+rc);
254                }
255            } else {
256                // try to use a vcbuild..
257                int rc = cli.system(buildDir, new String[]{"vcbuild", "/platform:"+platform, "vs2008.vcproj", configuration});
258                if( rc != 0 ) {
259                    throw new MojoExecutionException("vcbuild failed with exit code: "+rc);
260                }
261            }
262    
263    
264    
265            File libFile=FileUtils.resolveFile(buildDir, "target/"+platform+"-"+configuration+"/lib/"+library.getLibraryFileName());
266            if( !libFile.exists() ) {
267                throw new MojoExecutionException("vcbuild did not generate: "+libFile);
268            }        
269    
270            File target=FileUtils.resolveFile(libDirectory, library.getPlatformSpecifcResourcePath());
271            FileUtils.copyFile(libFile, target);
272            }
273    
274        
275            private void configureBasedBuild(File buildDir) throws IOException, MojoExecutionException, CommandLineException {
276            
277            File configure = new File(packageDirectory, "configure");
278            if( configure.exists() ) {
279                FileUtils.copyDirectoryStructureIfModified(packageDirectory, buildDir);            
280            } else if (downloadSourcePackage) {
281                downloadNativeSourcePackage(buildDir);
282            } else {
283                throw new MojoExecutionException("The configure script is missing from the generated native source package and downloadSourcePackage is disabled: "+configure);
284            }
285    
286            configure = new File(buildDir, "configure");
287            File autogen = new File(buildDir, "autogen.sh");
288            File makefile = new File(buildDir, "Makefile");
289            
290            File distDirectory = new File(buildDir, "target");
291            File distLibDirectory = new File(distDirectory, "lib");
292                    distLibDirectory.mkdirs();
293            
294            if( autogen.exists() && !skipAutogen ) {
295                if( (!configure.exists() && !CLI.IS_WINDOWS) || forceAutogen ) {
296                    cli.setExecutable(autogen);
297                    int rc = cli.system(buildDir, new String[] {"./autogen.sh"}, autogenArgs);
298                    if( rc != 0 ) {
299                        throw new MojoExecutionException("./autogen.sh failed with exit code: "+rc);
300                    }
301                }
302            }
303            
304            if( configure.exists() && !skipConfigure ) {
305                if( !makefile.exists() || forceConfigure ) {
306                    
307                    File autotools = new File(buildDir, "autotools");
308                    File[] listFiles = autotools.listFiles();
309                    if( listFiles!=null ) {
310                        for (File file : listFiles) {
311                            cli.setExecutable(file);
312                        }
313                    }
314                    
315                    cli.setExecutable(configure);
316                    int rc = cli.system(buildDir, new String[]{"./configure", "--disable-ccache", "--prefix="+distDirectory.getCanonicalPath()}, configureArgs);
317                    if( rc != 0 ) {
318                        throw new MojoExecutionException("./configure failed with exit code: "+rc);
319                    }
320                }
321            }
322            
323            int rc = cli.system(buildDir, new String[]{"make", "install"});
324            if( rc != 0 ) {
325                throw new MojoExecutionException("make based build failed with exit code: "+rc);
326            }
327            
328            Library library = new Library(name);
329            
330            File libFile = new File(distLibDirectory, library.getLibraryFileName());
331            if( !libFile.exists() ) {
332                throw new MojoExecutionException("Make based build did not generate: "+libFile);
333            }
334            
335            if( platform == null ) {
336                platform = library.getPlatform();
337            }
338            
339            File target=FileUtils.resolveFile(libDirectory, library.getPlatformSpecifcResourcePath(platform));
340            FileUtils.copyFile(libFile, target);
341        }
342        
343        public void downloadNativeSourcePackage(File buildDir) throws MojoExecutionException  {
344            Artifact artifact = artifactFactory.createArtifactWithClassifier(project.getGroupId(), project.getArtifactId(), project.getVersion(), "zip", sourceClassifier);
345            try {
346                artifactResolver.resolve(artifact, remoteArtifactRepositories, localRepository);
347            } catch (ArtifactResolutionException e) {
348                throw new MojoExecutionException("Error downloading.", e);
349            } catch (ArtifactNotFoundException e) {
350                throw new MojoExecutionException("Requested download does not exist.", e);
351            }
352            
353            File packageZipFile = artifact.getFile();
354    
355            try {
356                File dest = new File(buildDirectory, "native-build-extracted");
357                getLog().info("Extracting "+packageZipFile+" to "+dest);
358                
359                UnArchiver unArchiver = archiverManager.getUnArchiver("zip");
360                unArchiver.setSourceFile(packageZipFile);
361                unArchiver.setDestDirectory(dest);
362                unArchiver.extract();
363    
364                String packageName = project.getArtifactId()+"-"+project.getVersion()+"-"+sourceClassifier;
365                File source = new File(dest, packageName);
366                FileUtils.copyDirectoryStructureIfModified(source, buildDir);            
367                
368            } catch (Throwable e) {
369                throw new MojoExecutionException("Could not extract the native source package.", e);
370            }            
371        }    
372        
373    }