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.io.Reader;
022 import java.net.URL;
023 import java.util.ArrayList;
024 import java.util.HashMap;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.Set;
028
029 import org.apache.maven.artifact.Artifact;
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.interpolation.InterpolatorFilterReader;
034 import org.codehaus.plexus.interpolation.MapBasedValueSource;
035 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
036 import org.codehaus.plexus.util.FileUtils;
037 import org.codehaus.plexus.util.FileUtils.FilterWrapper;
038 import org.fusesource.hawtjni.generator.HawtJNI;
039 import org.fusesource.hawtjni.generator.ProgressMonitor;
040
041 /**
042 * This goal generates the native source code and a
043 * autoconf/msbuild based build system needed to
044 * build a JNI library for any HawtJNI annotated
045 * classes in your maven project.
046 *
047 * @goal generate
048 * @phase process-classes
049 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
050 */
051 public class GenerateMojo extends AbstractMojo {
052
053 /**
054 * The maven project.
055 *
056 * @parameter expression="${project}"
057 * @required
058 * @readonly
059 */
060 protected MavenProject project;
061
062 /**
063 * The directory where the generated native source files are located.
064 *
065 * @parameter default-value="${project.build.directory}/generated-sources/hawtjni/native-src"
066 */
067 private File generatedNativeSourceDirectory;
068
069 /**
070 * The base name of the library, used to determine generated file names.
071 *
072 * @parameter default-value="${project.artifactId}"
073 */
074 private String name;
075
076 /**
077 * The copyright header template that will be added to the generated source files.
078 * Use the '%END_YEAR%' token to have it replaced with the current year.
079 *
080 * @parameter default-value=""
081 */
082 private String copyright;
083
084 /**
085 * Restrict looking for JNI classes to the specified package.
086 *
087 * @parameter
088 */
089 private List<String> packages = new ArrayList<String>();
090
091 /**
092 * The directory where the java classes files are located.
093 *
094 * @parameter default-value="${project.build.outputDirectory}"
095 */
096 private File classesDirectory;
097
098 /**
099 * The directory where the generated build package is located..
100 *
101 * @parameter default-value="${project.build.directory}/generated-sources/hawtjni/native-package"
102 */
103 private File packageDirectory;
104
105 /**
106 * The list of additional files to be included in the package will be
107 * placed.
108 *
109 * @parameter default-value="${basedir}/src/main/native-package"
110 */
111 private File customPackageDirectory;
112
113 /**
114 * The text encoding of the files.
115 *
116 * @parameter default-value="UTF-8"
117 */
118 private String encoding;
119
120 /**
121 * Should we skip executing the autogen.sh file.
122 *
123 * @parameter default-value="${skip-autogen}"
124 */
125 private boolean skipAutogen;
126
127 /**
128 * Should we force executing the autogen.sh file.
129 *
130 * @parameter default-value="${force-autogen}"
131 */
132 private boolean forceAutogen;
133
134 /**
135 * Should we display all the native build output?
136 *
137 * @parameter default-value="${hawtjni-verbose}"
138 */
139 private boolean verbose;
140
141 /**
142 * Extra arguments you want to pass to the autogen.sh command.
143 *
144 * @parameter
145 */
146 private List<String> autogenArgs;
147
148 /**
149 * Set this value to false to disable the callback support in HawtJNI.
150 * Disabling callback support can substantially reduce the size
151 * of the generated native library.
152 *
153 * @parameter default-value="true"
154 */
155 private boolean callbacks;
156
157 private File targetSrcDir;
158
159 private CLI cli = new CLI();
160
161 public void execute() throws MojoExecutionException {
162 cli.verbose = verbose;
163 cli.log = getLog();
164 generateNativeSourceFiles();
165 generateBuildSystem();
166 }
167
168 private void generateNativeSourceFiles() throws MojoExecutionException {
169 HawtJNI generator = new HawtJNI();
170 generator.setClasspaths(getClasspath());
171 generator.setName(name);
172 generator.setCopyright(copyright);
173 generator.setNativeOutput(generatedNativeSourceDirectory);
174 generator.setPackages(packages);
175 generator.setCallbacks(callbacks);
176 generator.setProgress(new ProgressMonitor() {
177 public void step() {
178 }
179 public void setTotal(int total) {
180 }
181 public void setMessage(String message) {
182 getLog().info(message);
183 }
184 });
185 try {
186 generator.generate();
187 } catch (Exception e) {
188 throw new MojoExecutionException("Native source code generation failed: "+e, e);
189 }
190 }
191
192 private void generateBuildSystem() throws MojoExecutionException {
193 try {
194 packageDirectory.mkdirs();
195 new File(packageDirectory, "m4").mkdirs();
196 targetSrcDir = new File(packageDirectory, "src");
197 targetSrcDir.mkdirs();
198
199 if( customPackageDirectory!=null && customPackageDirectory.isDirectory() ) {
200 FileUtils.copyDirectoryStructureIfModified(customPackageDirectory, packageDirectory);
201 }
202
203 if( generatedNativeSourceDirectory!=null && generatedNativeSourceDirectory.isDirectory() ) {
204 FileUtils.copyDirectoryStructureIfModified(generatedNativeSourceDirectory, targetSrcDir);
205 }
206
207 copyTemplateResource("readme.md", false);
208 copyTemplateResource("configure.ac", true);
209 copyTemplateResource("Makefile.am", true);
210 copyTemplateResource("m4/custom.m4", false);
211 copyTemplateResource("m4/jni.m4", false);
212 copyTemplateResource("m4/osx-universal.m4", false);
213
214 // To support windows based builds..
215 copyTemplateResource("vs2008.vcproj", true);
216
217 File autogen = new File(packageDirectory, "autogen.sh");
218 File configure = new File(packageDirectory, "configure");
219 if( !autogen.exists() ) {
220 copyTemplateResource("autogen.sh", false);
221 cli.setExecutable(autogen);
222 }
223 if( !skipAutogen ) {
224 if( (!configure.exists() && !CLI.IS_WINDOWS) || forceAutogen ) {
225 try {
226 cli.system(packageDirectory, new String[] {"./autogen.sh"}, autogenArgs);
227 } catch (Exception e) {
228 e.printStackTrace();
229 }
230 }
231 }
232
233
234 } catch (Exception e) {
235 throw new MojoExecutionException("Native build system generation failed: "+e, e);
236 }
237 }
238
239 @SuppressWarnings("unchecked")
240 private ArrayList<String> getClasspath() throws MojoExecutionException {
241 ArrayList<String> artifacts = new ArrayList<String>();
242 try {
243 artifacts.add(classesDirectory.getCanonicalPath());
244 for (Artifact artifact : (Set<Artifact>) project.getArtifacts()) {
245 File file = artifact.getFile();
246 getLog().debug("Including: " + file);
247 artifacts.add(file.getCanonicalPath());
248 }
249 } catch (IOException e) {
250 throw new MojoExecutionException("Could not determine project classath.", e);
251 }
252 return artifacts;
253 }
254
255 private void copyTemplateResource(String file, boolean filter) throws MojoExecutionException {
256 try {
257 File target = FileUtils.resolveFile(packageDirectory, file);
258 if( target.isFile() && target.canRead() ) {
259 return;
260 }
261 URL source = getClass().getClassLoader().getResource("project-template/" + file);
262 File tmp = FileUtils.createTempFile("tmp", "txt", new File(project.getBuild().getDirectory()));
263 try {
264 FileUtils.copyURLToFile(source, tmp);
265 FileUtils.copyFile(tmp, target, encoding, filters(filter), true);
266 } finally {
267 tmp.delete();
268 }
269 } catch (IOException e) {
270 throw new MojoExecutionException("Could not extract template resource: "+file, e);
271 }
272 }
273
274 @SuppressWarnings("unchecked")
275 private FilterWrapper[] filters(boolean filter) throws IOException {
276 if( !filter ) {
277 return new FilterWrapper[0];
278 }
279
280 final String startExp = "@";
281 final String endExp = "@";
282 final String escapeString = "\\";
283 final Map<String,String> values = new HashMap<String,String>();
284 values.put("PROJECT_NAME", name);
285 values.put("PROJECT_NAME_UNDER_SCORE", name.replaceAll("\\W", "_"));
286 values.put("VERSION", project.getVersion());
287
288 List<String> files = new ArrayList<String>();
289 files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.c", null, false));
290 files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.cpp", null, false));
291 files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.cxx", null, false));
292 String sources = "";
293 String xml_sources = "";
294 boolean first = true;
295 for (String f : files) {
296 if( !first ) {
297 sources += "\\\n";
298 } else {
299 values.put("FIRST_SOURCE_FILE", "src/"+f.replace('\\', '/'));
300 first=false;
301 }
302 sources += " src/"+f;
303
304 xml_sources+=" <File RelativePath=\".\\src\\"+ (f.replace('/', '\\')) +"\"/>\n";
305 }
306
307 values.put("PROJECT_SOURCES", sources);
308 values.put("PROJECT_XML_SOURCES", xml_sources);
309
310
311 FileUtils.FilterWrapper wrapper = new FileUtils.FilterWrapper() {
312 public Reader getReader(Reader reader) {
313 StringSearchInterpolator propertiesInterpolator = new StringSearchInterpolator(startExp, endExp);
314 propertiesInterpolator.addValueSource(new MapBasedValueSource(values));
315 propertiesInterpolator.setEscapeString(escapeString);
316 InterpolatorFilterReader interpolatorFilterReader = new InterpolatorFilterReader(reader, propertiesInterpolator, startExp, endExp);
317 interpolatorFilterReader.setInterpolateWithPrefixPattern(false);
318 return interpolatorFilterReader;
319 }
320 };
321 return new FilterWrapper[] { wrapper };
322 }
323
324
325 }