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