• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.jnigen;
18 
19 import java.util.ArrayList;
20 
21 import com.badlogic.gdx.jnigen.BuildTarget.TargetOs;
22 import com.badlogic.gdx.jnigen.FileDescriptor.FileType;
23 
24 /** Generates Ant scripts for multiple native build targets based on the given {@link BuildConfig}.</p>
25  *
26  * For each build target, an Ant build script is created that will compile C/C++ files to a shared library for a specific
27  * platform. A master build script is generated that will execute the build scripts for each platform and bundles their shared
28  * libraries into a Jar file containing all shared libraries for all desktop platform targets, and armeabi/ and armeabi-v7a/
29  * folders containing the shard libraries for Android. The scripts can be executed from the command line or via the
30  * {@link BuildExecutor}. The resulting shared libraries can be loaded with the {@link JniGenSharedLibraryLoader} which will load
31  * the correct shared library from the natives jar/arm folders based on the platform the application is running on</p>
32  *
33  * A common use case looks like this:
34  *
35  * <pre>
36  * BuildTarget win32 = BuildTarget.newDefaultBuildTarget(TargetOs.Windows, false);
37  * BuildTarget win64 = BuildTarget.newDefaultBuildTarget(TargetOs.Windows, true);
38  * BuildTarget linux32 = BuildTarget.newDefaultBuildTarget(TargetOs.Linux, false);
39  * BuildTarget linux64 = BuildTarget.newDefaultBuildTarget(TargetOs.Linux, true);
40  * BuildTarget mac = BuildTarget.newDefaultBuildTarget(TargetOs.MacOsX, false);
41  * BuildTarget android = BuildTarget.newDefaultBuildTarget(TargetOs.Android, false);
42  * BuildConfig config = new BuildConfig("mysharedlibrary");
43  *
44  * new AntScriptGenerator().generate(config, win32, win64, linux32, linux64, mac, android);
45  * BuildExecutor.executeAnt("jni/build.xml", "clean all -v");
46  *
47  * // assuming the natives jar is on the classpath of the application
48  * new SharedLibraryLoader().load("mysharedlibrary)
49  * </pre>
50  *
51  * This will create the build scripts and execute the build of the shared libraries for each platform, provided that the compilers
52  * are available on the system. Mac OS X might have to be treated separately as there are no cross-compilers for it.</p>
53  *
54  * The generator will also copy the necessary JNI headers to the jni/jni-headers folder for Windows, Linux and Mac OS X.</p>
55  *
56  * @author mzechner */
57 public class AntScriptGenerator {
58 	/** Creates a master build script and one build script for each target to generated native shared libraries.
59 	 * @param config the {@link BuildConfig}
60 	 * @param targets list of {@link BuildTarget} instances */
generate(BuildConfig config, BuildTarget... targets)61 	public void generate (BuildConfig config, BuildTarget... targets) {
62 		// create all the directories for outputing object files, shared libs and natives jar as well as build scripts.
63 		if (!config.libsDir.exists()) {
64 			if (!config.libsDir.mkdirs())
65 				throw new RuntimeException("Couldn't create directory for shared library files in '" + config.libsDir + "'");
66 		}
67 		if (!config.jniDir.exists()) {
68 			if (!config.jniDir.mkdirs())
69 				throw new RuntimeException("Couldn't create native code directory '" + config.jniDir + "'");
70 		}
71 
72 		// copy jni headers
73 		copyJniHeaders(config.jniDir.path());
74 
75 		// copy memcpy_wrap.c, needed if your build platform uses the latest glibc, e.g. Ubuntu 12.10
76 		if (config.jniDir.child("memcpy_wrap.c").exists() == false) {
77 			new FileDescriptor("com/badlogic/gdx/jnigen/resources/scripts/memcpy_wrap.c", FileType.Classpath).copyTo(config.jniDir
78 				.child("memcpy_wrap.c"));
79 		}
80 
81 		ArrayList<String> buildFiles = new ArrayList<String>();
82 		ArrayList<String> libsDirs = new ArrayList<String>();
83 		ArrayList<String> sharedLibFiles = new ArrayList<String>();
84 
85 		// generate an Ant build script for each target
86 		for (BuildTarget target : targets) {
87 			String buildFile = generateBuildTargetTemplate(config, target);
88 			FileDescriptor libsDir = new FileDescriptor(getLibsDirectory(config, target));
89 
90 			if (!libsDir.exists()) {
91 				if (!libsDir.mkdirs()) throw new RuntimeException("Couldn't create libs directory '" + libsDir + "'");
92 			}
93 
94 			String buildFileName = "build-" + target.os.toString().toLowerCase() + (target.is64Bit ? "64" : "32") + ".xml";
95 			if (target.buildFileName != null) buildFileName = target.buildFileName;
96 			config.jniDir.child(buildFileName).writeString(buildFile, false);
97 			System.out.println("Wrote target '" + target.os + (target.is64Bit ? "64" : "") + "' build script '"
98 				+ config.jniDir.child(buildFileName) + "'");
99 
100 			if (!target.excludeFromMasterBuildFile) {
101 				if (target.os != TargetOs.MacOsX && target.os != TargetOs.IOS) {
102 					buildFiles.add(buildFileName);
103 				}
104 
105 				String sharedLibFilename = target.libName;
106 				if (sharedLibFilename == null)
107 					sharedLibFilename = getSharedLibFilename(target.os, target.is64Bit, config.sharedLibName);
108 
109 				sharedLibFiles.add(sharedLibFilename);
110 				if (target.os != TargetOs.Android && target.os != TargetOs.IOS) {
111 					libsDirs.add("../" + libsDir.path().replace('\\', '/'));
112 				}
113 			}
114 		}
115 
116 		// generate the master build script
117 		String template = new FileDescriptor("com/badlogic/gdx/jnigen/resources/scripts/build.xml.template", FileType.Classpath)
118 			.readString();
119 		StringBuffer clean = new StringBuffer();
120 		StringBuffer compile = new StringBuffer();
121 		StringBuffer pack = new StringBuffer();
122 
123 		for (int i = 0; i < buildFiles.size(); i++) {
124 			clean.append("\t\t<ant antfile=\"" + buildFiles.get(i) + "\" target=\"clean\"/>\n");
125 			compile.append("\t\t<ant antfile=\"" + buildFiles.get(i) + "\"/>\n");
126 		}
127 		for (int i = 0; i < libsDirs.size(); i++) {
128 			pack.append("\t\t\t<fileset dir=\"" + libsDirs.get(i) + "\" includes=\"" + sharedLibFiles.get(i) + "\"/>\n");
129 		}
130 
131 		if (config.sharedLibs != null) {
132 			for (String sharedLib : config.sharedLibs) {
133 				pack.append("\t\t\t<fileset dir=\"" + sharedLib + "\"/>\n");
134 			}
135 		}
136 
137 		template = template.replace("%projectName%", config.sharedLibName + "-natives");
138 		template = template.replace("<clean/>", clean.toString());
139 		template = template.replace("<compile/>", compile.toString());
140 		template = template.replace("%packFile%", "../" + config.libsDir.path().replace('\\', '/') + "/" + config.sharedLibName
141 			+ "-natives.jar");
142 		template = template.replace("<pack/>", pack);
143 
144 		config.jniDir.child("build.xml").writeString(template, false);
145 		System.out.println("Wrote master build script '" + config.jniDir.child("build.xml") + "'");
146 	}
147 
copyJniHeaders(String jniDir)148 	private void copyJniHeaders (String jniDir) {
149 		final String pack = "com/badlogic/gdx/jnigen/resources/headers";
150 		String files[] = {"classfile_constants.h", "jawt.h", "jdwpTransport.h", "jni.h", "linux/jawt_md.h", "linux/jni_md.h",
151 			"mac/jni_md.h", "win32/jawt_md.h", "win32/jni_md.h"};
152 
153 		for (String file : files) {
154 			new FileDescriptor(pack, FileType.Classpath).child(file).copyTo(
155 				new FileDescriptor(jniDir).child("jni-headers").child(file));
156 		}
157 	}
158 
getSharedLibFilename(TargetOs os, boolean is64Bit, String sharedLibName)159 	private String getSharedLibFilename (TargetOs os, boolean is64Bit, String sharedLibName) {
160 		// generate shared lib prefix and suffix, determine jni platform headers directory
161 		String libPrefix = "";
162 		String libSuffix = "";
163 		if (os == TargetOs.Windows) {
164 			libSuffix = (is64Bit ? "64" : "") + ".dll";
165 		}
166 		if (os == TargetOs.Linux || os == TargetOs.Android) {
167 			libPrefix = "lib";
168 			libSuffix = (is64Bit ? "64" : "") + ".so";
169 		}
170 		if (os == TargetOs.MacOsX) {
171 			libPrefix = "lib";
172 			libSuffix = (is64Bit ? "64" : "") + ".dylib";
173 		}
174 		if (os == TargetOs.IOS) {
175 			libPrefix = "lib";
176 			libSuffix = ".a";
177 		}
178 		return libPrefix + sharedLibName + libSuffix;
179 	}
180 
getJniPlatform(TargetOs os)181 	private String getJniPlatform (TargetOs os) {
182 		if (os == TargetOs.Windows) return "win32";
183 		if (os == TargetOs.Linux) return "linux";
184 		if (os == TargetOs.MacOsX) return "mac";
185 		return "";
186 	}
187 
getLibsDirectory(BuildConfig config, BuildTarget target)188 	private String getLibsDirectory (BuildConfig config, BuildTarget target) {
189 		String targetName = target.osFileName;
190 		if (targetName == null) targetName = target.os.toString().toLowerCase() + (target.is64Bit ? "64" : "32");
191 		return config.libsDir.child(targetName).path().replace('\\', '/');
192 	}
193 
generateBuildTargetTemplate(BuildConfig config, BuildTarget target)194 	private String generateBuildTargetTemplate (BuildConfig config, BuildTarget target) {
195 		// special case for android
196 		if (target.os == TargetOs.Android) {
197 			new AndroidNdkScriptGenerator().generate(config, target);
198 			String template = new FileDescriptor("com/badlogic/gdx/jnigen/resources/scripts/build-android.xml.template",
199 				FileType.Classpath).readString();
200 			template = template.replace("%precompile%", target.preCompileTask == null ? "" : target.preCompileTask);
201 			template = template.replace("%postcompile%", target.postCompileTask == null ? "" : target.postCompileTask);
202 			return template;
203 		}
204 
205 		// read template file from resources
206 		String template = null;
207 		if (target.os == TargetOs.IOS) {
208 			template = new FileDescriptor("com/badlogic/gdx/jnigen/resources/scripts/build-ios.xml.template", FileType.Classpath)
209 				.readString();
210 		} else {
211 			template = new FileDescriptor("com/badlogic/gdx/jnigen/resources/scripts/build-target.xml.template", FileType.Classpath)
212 				.readString();
213 		}
214 
215 		// generate shared lib filename and jni platform headers directory name
216 		String libName = target.libName;
217 		if (libName == null) libName = getSharedLibFilename(target.os, target.is64Bit, config.sharedLibName);
218 		String jniPlatform = getJniPlatform(target.os);
219 
220 		// generate include and exclude fileset Ant description for C/C++
221 		// append memcpy_wrap.c to list of files to be build
222 		StringBuffer cIncludes = new StringBuffer();
223 		cIncludes.append("\t\t<include name=\"memcpy_wrap.c\"/>\n");
224 		for (String cInclude : target.cIncludes) {
225 			cIncludes.append("\t\t<include name=\"" + cInclude + "\"/>\n");
226 		}
227 		StringBuffer cppIncludes = new StringBuffer();
228 		for (String cppInclude : target.cppIncludes) {
229 			cppIncludes.append("\t\t<include name=\"" + cppInclude + "\"/>\n");
230 		}
231 		StringBuffer cExcludes = new StringBuffer();
232 		for (String cExclude : target.cExcludes) {
233 			cExcludes.append("\t\t<exclude name=\"" + cExclude + "\"/>\n");
234 		}
235 		StringBuffer cppExcludes = new StringBuffer();
236 		for (String cppExclude : target.cppExcludes) {
237 			cppExcludes.append("\t\t<exclude name=\"" + cppExclude + "\"/>\n");
238 		}
239 
240 		// generate C/C++ header directories
241 		StringBuffer headerDirs = new StringBuffer();
242 		for (String headerDir : target.headerDirs) {
243 			headerDirs.append("\t\t\t<arg value=\"-I" + headerDir + "\"/>\n");
244 		}
245 
246 		String targetFolder = target.osFileName;
247 		if (targetFolder == null) targetFolder = target.os.toString().toLowerCase() + (target.is64Bit ? "64" : "32");
248 
249 		// replace template vars with proper values
250 		template = template.replace("%projectName%", config.sharedLibName + "-" + target.os + "-" + (target.is64Bit ? "64" : "32"));
251 		template = template.replace("%buildDir%", config.buildDir.child(targetFolder).path().replace('\\', '/'));
252 		template = template.replace("%libsDir%", "../" + getLibsDirectory(config, target));
253 		template = template.replace("%libName%", libName);
254 		template = template.replace("%jniPlatform%", jniPlatform);
255 		template = template.replace("%compilerPrefix%", target.compilerPrefix);
256 		template = template.replace("%cFlags%", target.cFlags);
257 		template = template.replace("%cppFlags%", target.cppFlags);
258 		template = template.replace("%linkerFlags%", target.linkerFlags);
259 		template = template.replace("%libraries%", target.libraries);
260 		template = template.replace("%cIncludes%", cIncludes);
261 		template = template.replace("%cExcludes%", cExcludes);
262 		template = template.replace("%cppIncludes%", cppIncludes);
263 		template = template.replace("%cppExcludes%", cppExcludes);
264 		template = template.replace("%headerDirs%", headerDirs);
265 		template = template.replace("%precompile%", target.preCompileTask == null ? "" : target.preCompileTask);
266 		template = template.replace("%postcompile%", target.postCompileTask == null ? "" : target.postCompileTask);
267 
268 		return template;
269 	}
270 }
271