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 //TODO: look into supporting cross compilation
249 int rc = cli.system(buildDir, new String[]{"vcbuild", "/platform:"+platform, "vs2008.vcproj", configuration});
250 if( rc != 0 ) {
251 throw new MojoExecutionException("vcbuild failed with exit code: "+rc);
252 }
253
254 File libFile=FileUtils.resolveFile(buildDir, "target/"+platform+"-"+configuration+"/lib/"+library.getLibraryFileName());
255 if( !libFile.exists() ) {
256 throw new MojoExecutionException("vcbuild did not generate: "+libFile);
257 }
258
259 File target=FileUtils.resolveFile(libDirectory, library.getPlatformSpecifcResourcePath());
260 FileUtils.copyFile(libFile, target);
261 }
262
263
264 private void configureBasedBuild(File buildDir) throws IOException, MojoExecutionException, CommandLineException {
265
266 File configure = new File(packageDirectory, "configure");
267 if( configure.exists() ) {
268 FileUtils.copyDirectoryStructureIfModified(packageDirectory, buildDir);
269 } else if (downloadSourcePackage) {
270 downloadNativeSourcePackage(buildDir);
271 } else {
272 throw new MojoExecutionException("The configure script is missing from the generated native source package and downloadSourcePackage is disabled: "+configure);
273 }
274
275 configure = new File(buildDir, "configure");
276 File autogen = new File(buildDir, "autogen.sh");
277 File makefile = new File(buildDir, "Makefile");
278
279 File distDirectory = new File(buildDir, "target");
280 File distLibDirectory = new File(distDirectory, "lib");
281 distLibDirectory.mkdirs();
282
283 if( autogen.exists() && !skipAutogen ) {
284 if( (!configure.exists() && !CLI.IS_WINDOWS) || forceAutogen ) {
285 cli.setExecutable(autogen);
286 int rc = cli.system(buildDir, new String[] {"./autogen.sh"}, autogenArgs);
287 if( rc != 0 ) {
288 throw new MojoExecutionException("./autogen.sh failed with exit code: "+rc);
289 }
290 }
291 }
292
293 if( configure.exists() && !skipConfigure ) {
294 if( !makefile.exists() || forceConfigure ) {
295
296 File autotools = new File(buildDir, "autotools");
297 File[] listFiles = autotools.listFiles();
298 if( listFiles!=null ) {
299 for (File file : listFiles) {
300 cli.setExecutable(file);
301 }
302 }
303
304 cli.setExecutable(configure);
305 int rc = cli.system(buildDir, new String[]{"./configure", "--disable-ccache", "--prefix="+distDirectory.getCanonicalPath()}, configureArgs);
306 if( rc != 0 ) {
307 throw new MojoExecutionException("./configure failed with exit code: "+rc);
308 }
309 }
310 }
311
312 int rc = cli.system(buildDir, new String[]{"make", "install"});
313 if( rc != 0 ) {
314 throw new MojoExecutionException("make based build failed with exit code: "+rc);
315 }
316
317 Library library = new Library(name);
318
319 File libFile = new File(distLibDirectory, library.getLibraryFileName());
320 if( !libFile.exists() ) {
321 throw new MojoExecutionException("Make based build did not generate: "+libFile);
322 }
323
324 if( platform == null ) {
325 platform = library.getPlatform();
326 }
327
328 File target=FileUtils.resolveFile(libDirectory, library.getPlatformSpecifcResourcePath(platform));
329 FileUtils.copyFile(libFile, target);
330 }
331
332 public void downloadNativeSourcePackage(File buildDir) throws MojoExecutionException {
333 Artifact artifact = artifactFactory.createArtifactWithClassifier(project.getGroupId(), project.getArtifactId(), project.getVersion(), "zip", sourceClassifier);
334 try {
335 artifactResolver.resolve(artifact, remoteArtifactRepositories, localRepository);
336 } catch (ArtifactResolutionException e) {
337 throw new MojoExecutionException("Error downloading.", e);
338 } catch (ArtifactNotFoundException e) {
339 throw new MojoExecutionException("Requested download does not exist.", e);
340 }
341
342 File packageZipFile = artifact.getFile();
343
344 try {
345 File dest = new File(buildDirectory, "native-build-extracted");
346 getLog().info("Extracting "+packageZipFile+" to "+dest);
347
348 UnArchiver unArchiver = archiverManager.getUnArchiver("zip");
349 unArchiver.setSourceFile(packageZipFile);
350 unArchiver.extract("", dest);
351
352 String packageName = project.getArtifactId()+"-"+project.getVersion()+"-"+sourceClassifier;
353 File source = new File(dest, packageName);
354 FileUtils.copyDirectoryStructureIfModified(source, buildDir);
355
356 } catch (Throwable e) {
357 throw new MojoExecutionException("Could not extract the native source package.", e);
358 }
359 }
360
361 }