#include "Builders.h" #include "AndroidBuilder.h" #include "BuilderUtils.h" #define METHOD_NAME "AndroidBuilder::" << UPP_FUNCTION_NAME << "(): " const String AndroidBuilder::RES_PKG_FLAG = "ANDROID_RESOURCES_PACKAGE"; Index AndroidBuilder::GetBuildersNames() { Index builders; builders.Add("ANDROID"); return builders; } AndroidBuilder::AndroidBuilder() : wspc(GetIdeWorkspace()) { } void AndroidBuilder::SetJdk(One jdk) { this->jdk = pick(jdk); } String AndroidBuilder::GetTargetExt() const { return ".apk"; } bool AndroidBuilder::BuildPackage( const String& package, Vector& linkfile, Vector& immfile, String& linkoptions, const Vector& all_uses, const Vector& all_libraries, int) { Logd() << METHOD_NAME; InitProject(); if(!ValidateBuilderEnviorement()) return false; const bool isMainPackage = HasFlag("MAIN"); const bool isResourcesPackage = HasFlag(RES_PKG_FLAG); const bool isBlitz = HasFlag("BLITZ") || ndk_blitz; String uppManifestPath = PackagePath(package); String packageDir = GetFileFolder(uppManifestPath); String assemblyDir = AndroidBuilderUtils::GetAssemblyDir(packageDir, package); ChDir(packageDir); PutVerbose("cd " + packageDir); Package pkg; pkg.Load(uppManifestPath); Vector javaFiles; Vector nativeSources; Vector nativeSourcesOptions; Vector nativeObjects; Index noBlitzNativeSourceFiles; String javaSourcesDir = project->GetJavaDir(); String jniSourcesDir = project->GetJniDir(); String pkgJavaSourcesDir = javaSourcesDir + DIR_SEPS + package; for(int i = 0; i < pkg.GetCount(); i++) { if(!IdeIsBuilding()) return false; if(pkg[i].separator) continue; String globalOptions = Gather(pkg[i].option, config.GetKeys()); String filePath = SourcePath(package, pkg[i]); String fileName = NormalizePathSeparator(pkg[i]); String packageFile = AppendFileName(package, fileName); String packageFileDir = GetFileFolder(packageFile); if(isResourcesPackage) { if(packageFileDir.Find(package + DIR_SEPS) != -1) packageFileDir.Remove(0, String(package + DIR_SEPS).GetCount()); String filePathInAndroidProject = GetFilePathInAndroidProject(project->GetResDir(), packageFileDir, fileName); if(!MovePackageFileToAndroidProject(filePath, filePathInAndroidProject)) return false; } else if(BuilderUtils::IsJavaFile(filePath)) { String filePathInAndroidProject = GetFilePathInAndroidProject(javaSourcesDir, packageFileDir, fileName); if(!RealizePackageJavaSourcesDirectory(package)) return false; if(!MovePackageFileToAndroidProject(filePath, filePathInAndroidProject)) return false; javaFiles.Add(filePathInAndroidProject); } else if(BuilderUtils::IsCppOrCFile(filePath)) { nativeSourcesOptions.Add(globalOptions); if(pkg[i].noblitz) { if (isBlitz) { noBlitzNativeSourceFiles.Add(filePath); continue; } } nativeSources.Add(filePath); } else if(BuilderUtils::IsXmlFile(filePath)) { if(isMainPackage && fileName == AndroidManifest::FILE_NAME) { if (manifest) { PutConsole("Manifest file already exists. There should be only one AndroidManifest.xml in the project."); return false; } if(!FileCopy(filePath, project->GetManifestPath())) { return false; } manifest.Create(filePath); if (!manifest->Parse()) { PutConsole("Failed to parse AndroidManifest.xml."); return false; } } } else if(BuilderUtils::IsObjectFile(filePath)) { String filePathInAndroidProject = GetFilePathInAndroidProject(jniSourcesDir, packageFileDir, fileName); if(!MovePackageFileToAndroidProject(filePath, filePathInAndroidProject)) return false; nativeObjects.Add(filePathInAndroidProject); } } if(isMainPackage && !manifest) { PutConsole("Failed to find Android manifest file in. Make sure AndroidManifest.xml is present in main package."); return false; } DeleteUnusedSourceFiles(pkgJavaSourcesDir, javaFiles, ".java"); if(!isResourcesPackage && !javaFiles.IsEmpty()) { if(!RealizeDirectory(project->GetClassesDir())) return false; linkfile.Add(commands->PreperCompileJavaSourcesCommand(javaFiles)); } if(isResourcesPackage || nativeSources.IsEmpty()) { Logd() << METHOD_NAME << "There are not native files in the following package " << package << "."; return true; } if(isBlitz) { Logd() << METHOD_NAME << "Creating blitz step for package " << package << "."; BlitzBuilderComponent bc(this); bc.SetWorkingDir(project->GetJniDir() + DIR_SEPS + package); bc.SetBlitzFileName("blitz"); Blitz blitz = bc.MakeBlitzStep(*this, nativeSources, nativeSourcesOptions, nativeObjects, immfile, ".o", noBlitzNativeSourceFiles); if(!FileExists(blitz.path)) { Loge() << METHOD_NAME << "Blitz was enable, but no blitz file generated."; } else { nativeSources.Add(package + DIR_SEPS + GetFileName(blitz.path)); } } AndroidModuleMakeFileCreator creator(config); creator.SetModuleName(NormalizeModuleName(package)); creator.AddSources(nativeSources); creator.AddSources(noBlitzNativeSourceFiles); creator.AddInclude(assemblyDir); creator.AddIncludes(pkg.uses); creator.AddFlags(pkg.flag); creator.AddLdLibraries(pkg.library); creator.AddStaticModuleLibrary(pkg.static_library); creator.AddSharedLibraries(pkg.uses); return creator.Save(GetModuleMakeFilePath(package)); } bool AndroidBuilder::Link( const Vector& linkfile, const String& linkoptions, bool createmap) { InitProject(); if(!ValidateBuilderEnviorement()) { return false; } ManageProjectCohesion(); PutConsole("Building Android Project"); if(!manifest) { PutConsole("Android manifest has not been detected. Make sure your main package contains AndroidManifest.xml."); return false; } if(!GenerateRFile()) { return false; } if(!RealizeLinkDirectories()) { return false; } // We need to compile java packages in this place, because we need to generate "R.java" file before... // We don't know which packages contain resources. int time; StringStream ss; if(linkfile.GetCount()) { PutConsole("-----"); PutConsole("Compiling java sources..."); time = msecs(); for(int i = 0; i < linkfile.GetCount(); i++) { if(Execute(linkfile[i], ss) != 0) { PutConsole(ss.GetResult()); return false; } } PutConsole("Java sources compiled in " + GetPrintTime(time) + "."); } // Now, we are going to start compiling c/c++ sources if(DirectoryExists(project->GetJniDir())) { if(!ndk.Validate()) { PutErrorOnConsole("Android NDK was not detected"); return false; } time = msecs(); PutConsole("-----"); PutConsole("Compiling native sources..."); GenerateApplicationMakeFile(); GenerateMakeFile(); NDKBuild ndkBuild(ndk.GetNdkBuildPath()); if(IsVerbose()) { ndkBuild.EnableVerbose(); } ndkBuild.SetWorkingDir(project->GetDir()); ndkBuild.SetJobs(IdeGetHydraThreads()); if(host->Execute(ndkBuild.MakeCmd()) != 0 ) { return false; } PutConsole("Native sources compiled in " + GetPrintTime(time) + "."); } if (!GenerateDexFile()) { return false; } PutConsole("Creating apk file..."); String unsignedApkPath = GetSandboxDir() + DIR_SEPS + GetFileTitle(target) + ".unsigned.apk"; DeleteFile(unsignedApkPath); String apkCmd; apkCmd << NormalizeExePath(sdk.AaptPath()); apkCmd << " package -v -f"; if(DirectoryExists(project->GetResDir())) apkCmd << " -S " << project->GetResDir(); apkCmd << " -M " << project->GetManifestPath(); apkCmd << " -I " << NormalizeExePath(sdk.AndroidJarPath()); apkCmd << " -F " << unsignedApkPath; apkCmd << " " << project->GetBinDir(); // PutConsole(apkCmd); if(Execute(apkCmd, ss) != 0) { PutConsole(ss.GetResult()); return false; } if(DirectoryExists(project->GetLibsDir())) { PutConsole("Adding native libraries to apk..."); if(!AddSharedLibsToApk(unsignedApkPath)) { return false; } } String unsignedAlignedApkPath = GetSandboxDir() + DIR_SEPS + GetFileTitle(target) + ".aligned.unsigned.apk"; DeleteFile(unsignedAlignedApkPath); if(!AlignApk(unsignedAlignedApkPath, unsignedApkPath)) { return false; } if(!SignApk(target, unsignedAlignedApkPath)) { return false; } return true; } bool AndroidBuilder::Preprocess( const String& package, const String& file, const String& target, bool asmout) { InitProject(); String ext = GetFileExt(file); if(ext == ".java") return PreprocessJava(package, file, target); return false; } void AndroidBuilder::AddFlags(Index& cfg) { cfg.Add("POSIX"); cfg.Add("LINUX"); cfg.Add("ANDROID"); } void AndroidBuilder::CleanPackage(const String& package, const String& outdir) { InitProject(); Vector pkgDirs; Vector pkgFiles; if(HasFlag(RES_PKG_FLAG)) pkgDirs.Add(project->GetResDir()); else { // TODO: handle deletetion of (.class)es pkgDirs.Add(project->GetJavaDir(package)); PutConsole(project->GetJavaDir(package)); pkgDirs.Add(project->GetJniDir(package)); for(FindFile ff(AppendFileName(project->GetObjLocalDir(), "*")); ff; ff.Next()) { if(!ff.IsHidden() && !ff.IsSymLink() && ff.IsFolder()) { pkgFiles.Add(ff.GetPath() + DIR_SEPS + "lib" + package + ".so"); pkgDirs.Add(ff.GetPath() + DIR_SEPS + "objs" + DIR_SEPS + package); } } for(FindFile ff(AppendFileName(project->GetLibsDir(), "*")); ff; ff.Next()) if(!ff.IsHidden() && !ff.IsSymLink() && ff.IsFolder()) pkgFiles.Add(ff.GetPath() + DIR_SEPS + "lib" + package + ".so"); } for(int i = 0; i < pkgDirs.GetCount(); i++) { String dir = pkgDirs[i]; if(DirectoryExists(dir)) DeleteFolderDeep(dir); } for(int i = 0; i < pkgFiles.GetCount(); i++) { String file = pkgFiles[i]; if(FileExists(file)) DeleteFile(file); } } void AndroidBuilder::AfterClean() { InitProject(); String sandboxDir = GetSandboxDir(); if(DirectoryExists(sandboxDir)) DeleteFolderDeep(sandboxDir); } void AndroidBuilder::ManageProjectCohesion() { Index packages; for(int i = 0; i < wspc.GetCount(); i++) packages.Add(wspc[i]); DetectAndManageUnusedPackages(project->GetJavaDir(), packages); DetectAndManageUnusedPackages(project->GetJniDir(), packages); } void AndroidBuilder::DetectAndManageUnusedPackages( const String& nest, const Index& packages) { for(FindFile ff(AppendFileName(nest, "*")); ff; ff.Next()) { if(!ff.IsHidden() && ff.IsFolder()) { String name = ff.GetName(); if(packages.Find(name) == -1) CleanPackage(name, ""); } } } void AndroidBuilder::DeleteUnusedSourceFiles( const String& nest, const Vector& files, String exts, String excludedFiles) { exts.Replace(" ", ""); excludedFiles.Replace(" ", ""); Index extsIdx = Index(Split(exts, ",")); Index excludedFilesIdx = Index(Split(excludedFiles, ",")); Vector dirs { nest }; for(int i = 0; i < dirs.GetCount(); i++) { for(FindFile ff(AppendFileName(dirs[i], "*")); ff; ff.Next()) { if(ff.IsHidden()) { continue; } String path = ff.GetPath(); String name = ff.GetName(); if(ff.IsFolder() && ff.IsSymLink()) dirs.Add(ff.GetPath()); else if(extsIdx.Find(GetFileExt(path)) == -1) continue; else if(excludedFilesIdx.Find(name) > -1) continue; else { bool exists = false; for(int j = 0; j < files.GetCount(); j++) { if(files[j] == path) { exists = true; break; } } if(!exists) DeleteFile(path); } } } } bool AndroidBuilder::MovePackageFileToAndroidProject(const String& src, const String& dst) { String directory = GetFileDirectory(dst); if(!RealizeDirectory(directory)) { Loge() << METHOD_NAME << "Cannot relize following directory: \"" << directory << "\"."; return false; } if(FileExists(dst)) { if(GetFileTime(dst) > GetFileTime(src)) return true; } // TODO: Generic host should return bool flag. return ::SaveFile(dst, LoadFile(src)); } bool AndroidBuilder::RealizePackageJavaSourcesDirectory(const String& packageName) { String dir = project->GetJavaDir() + DIR_SEPS + packageName; return DirectoryExists(dir) || RealizeDirectory(dir); } bool AndroidBuilder::RealizeLinkDirectories() const { if(!RealizeDirectory(project->GetBinDir())) return false; return true; } bool AndroidBuilder::AlignApk(const String& target, const String& unsignedApkPath) { StringStream ss; PutConsole("Aliging apk file..."); DeleteFile(target); String zipalignCmd; zipalignCmd << NormalizeExePath(sdk.ZipalignPath()) << " -f 4 "; zipalignCmd << unsignedApkPath << " " << target; if(Execute(zipalignCmd, ss) != 0) { PutConsole(ss.GetResult()); return false; } return true; } bool AndroidBuilder::SignApk(const String& target, const String& unsignedApkPath) { if(!HasFlag("DEBUG")) { return false; } StringStream ss; String signedApkPath = GetSandboxDir() + DIR_SEPS + GetFileTitle(target) + ".signed.apk"; String keystorePath = GetSandboxDir() + DIR_SEPS + "debug.keystore"; if(!GenerateDebugKey(keystorePath)) { return false; } PutConsole("Signing apk file..."); DeleteFile(signedApkPath); String cmd; cmd << NormalizeExePath(sdk.ApksignerPath()); cmd << " sign --ks " << keystorePath << " --ks-pass pass:android" << " --out " << signedApkPath << " " << unsignedApkPath; if(Execute(cmd, ss) != 0) { PutConsole(ss.GetResult()); return false; } CopyFile(target, signedApkPath); return true; } bool AndroidBuilder::GenerateDebugKey(const String& keystorePath) { StringStream ss; if(!FileExists(keystorePath)) { PutConsole("Generating debug key..."); String keytoolCmd; keytoolCmd << NormalizeExePath(jdk->GetKeytoolPath()); keytoolCmd << " -genkeypair -alias androiddebugkey -keypass android"; keytoolCmd << " -keystore " << keystorePath; keytoolCmd << " -storepass android -dname \"CN=Android Debug,O=Android,C=US\""; keytoolCmd << " -keyalg RSA"; keytoolCmd << " -validity 100000"; //PutConsole(keytoolCmd); if(Execute(keytoolCmd, ss) != 0) { PutConsole(ss.GetResult()); return false; } } return true; } bool AndroidBuilder::AddSharedLibsToApk(const String& apkPath) { // TODO: Consider using environment variable NDK_LIBS_OUT form NDK r9 instead of // creating temporary lib directory that will be deleted after inserting files to APK. const String libDir = project->GetDir() + DIR_SEPS + "lib"; Vector sharedLibsToAdd; for(FindFile ff(AppendFileName(project->GetLibsDir(), "*")); ff; ff.Next()) { if (!ff.IsHidden () && !ff.IsSymLink () && ff.IsDirectory()) { for(FindFile ffa(AppendFileName (ff.GetPath(), "*")); ffa; ffa.Next ()) { if(!ffa.IsHidden() && !ffa.IsSymLink() && !ffa.IsDirectory()) { // TODO: in libs directory we can find another java libs (.jar) String fileExt = ToLower(GetFileExt(ffa.GetPath())); if(fileExt == ".so") { const String libPath = String("lib") + DIR_SEPS + ff.GetName() + DIR_SEPS + ffa.GetName(); const String destPath = project->GetDir() + DIR_SEPS + libPath; if(!RealizePath(destPath) || !FileCopy(ffa.GetPath(), destPath)) return false; sharedLibsToAdd.Add(libPath); } } } } } ChDir(project->GetDir()); String aaptAddCmd; aaptAddCmd << NormalizeExePath(sdk.AaptPath()); aaptAddCmd << " add " << apkPath; for(int i = 0; i < sharedLibsToAdd.GetCount(); i++) { #ifdef PLATFORM_WIN32 // NOTE: Without conversion to UNIX directory format libraries will be added at the top // of APK file and APK will be broken. sharedLibsToAdd[i].Replace("\\", "/"); #endif aaptAddCmd << " " << sharedLibsToAdd[i] << ""; } StringStream ss; if(Execute(aaptAddCmd, ss) != 0) { PutConsole(ss.GetResult()); return false; } if(!DeleteFolderDeep(libDir)) { return false; } return true; } bool AndroidBuilder::ValidateBuilderEnviorement() { if(!sdk.Validate()) { PutErrorOnConsole("Android SDK was not detected"); return false; } if(!sdk.ValidateBuildTools()) { PutErrorOnConsole("Android SDK build tools was not detected"); return false; } if(!sdk.ValidatePlatform()) { PutErrorOnConsole("Android SDK platform was not detected"); return false; } if(!jdk->Validate()) { PutErrorOnConsole("JDK was not detected"); return false; } return true; } void AndroidBuilder::PutErrorOnConsole(const String& msg) { PutConsole("Error: " + msg + "."); } bool AndroidBuilder::FileNeedsUpdate(const String& path, const String& data) { return !(FileExists(path) && LoadFile(path).Compare(data) == 0); } void AndroidBuilder::UpdateFile(const String& path, const String& data) { if(!FileNeedsUpdate(path, data)) return; SaveFile(path, data); } void AndroidBuilder::GenerateApplicationMakeFile() { String platform; AndroidApplicationMakeFile makeFile; if (manifest && manifest->uses_sdk && !IsNull(manifest->uses_sdk->minSdkVersion)) { String platform = "android-" + IntStr(manifest->uses_sdk->minSdkVersion); makeFile.SetPlatform(platform); } makeFile.SetArchitectures(ndkArchitectures); makeFile.SetCppRuntime(ndkCppRuntime); makeFile.SetCppFlags(ndkCppFlags); makeFile.SetCFlags(ndkCFlags); makeFile.SetOptim(HasFlag("DEBUG") ? "debug" : "release"); makeFile.SetToolchain(ndkToolchain); PutVerbose("Architectures: " + AsString(ndkArchitectures)); PutVerbose("CppRuntime: " + ndkCppRuntime); PutVerbose("CppFlags: " + ndkCppFlags); PutVerbose("CFlags: " + ndkCFlags); PutVerbose("Toolchain: " + ndkToolchain); PutVerbose("Platform: " + platform); UpdateFile(project->GetJniApplicationMakeFilePath(), makeFile.ToString()); } void AndroidBuilder::GenerateMakeFile() { const String makeFileName = "Android.mk"; const String baseDir = project->GetJniDir(); Vector modules; BiVector dirs; dirs.AddHead(baseDir); while(!dirs.IsEmpty()) { String currentDir = dirs.Head(); for(FindFile ff1(AppendFileName(currentDir, "*")); ff1; ff1.Next()) { if(!ff1.IsHidden() && !ff1.IsSymLink() && ff1.IsFolder()) { bool isEmpty = true; for(FindFile ff2(AppendFileName(ff1.GetPath(), "*")); ff2; ff2.Next()) { if(!ff2.IsHidden() && !ff2.IsSymLink() && ff2.IsFile()) { isEmpty = false; break; } } if(!isEmpty) { String moduleName = ff1.GetPath(); moduleName.Remove(moduleName.Find(baseDir), baseDir.GetCount() + 1); modules.Add(moduleName); } else { dirs.AddTail(ff1.GetPath()); } } } dirs.DropHead(); } AndroidMakeFile makeFile; makeFile.AddHeader(); for(const String& module : modules) makeFile.AddInclusion(module + DIR_SEPS + makeFileName); UpdateFile(project->GetJniMakeFilePath(), makeFile.ToString()); } bool AndroidBuilder::GenerateRFile() { // TODO: gen in gen folder if(DirectoryExists(project->GetResDir())) { StringStream ss; String aaptCmd; aaptCmd << NormalizeExePath(sdk.AaptPath()); aaptCmd << " package -v -f -m"; aaptCmd << " -S " << project->GetResDir(); aaptCmd << " -J " << project->GetJavaDir(); aaptCmd << " -M " << project->GetManifestPath(); aaptCmd << " -I " << NormalizeExePath(sdk.AndroidJarPath()); if(Execute(aaptCmd, ss) != 0) { PutConsole(ss.GetResult()); return false; } } return true; } bool AndroidBuilder::GenerateDexFile() { if(!DirectoryExists(project->GetClassesDir())) { return true; } PutConsole("-----"); if (sdk.HasD8()) { return GenerateDexFileUsingD8(); } return GenerateDexFileUsingDx(); } bool AndroidBuilder::GenerateDexFileUsingD8() { PutConsole("Creating dex file using d8..."); String cmd; StringStream ss; const auto outputFile = project->GetIntermediatesDir() << DIR_SEPS << "classes.jar"; cmd << NormalizeExePath(sdk.D8Path()); cmd << " --output " << outputFile << " "; auto classesFiles = project->GetClassessFiles(); for (const auto& file : classesFiles) { cmd << file << " "; } if(Execute(cmd, ss) != 0) { PutConsole(ss.GetResult()); return false; } cmd.Clear(); { // TODO: Replace with ScopeExit once it will be present in upp core.. String currentDir = GetCurrentDirectory(); ChangeCurrentDirectory(project->GetBinDir()); cmd << NormalizeExePath(jdk->GetJarPath()) << " xf " << outputFile; if(Execute(cmd, ss) != 0) { ChangeCurrentDirectory(currentDir); PutConsole(ss.GetResult()); return false; } ChangeCurrentDirectory(currentDir); } return true; } bool AndroidBuilder::GenerateDexFileUsingDx() { PutConsole("Creating dex file using dx..."); String cmd; StringStream ss; cmd << NormalizeExePath(sdk.DxPath()); cmd << " --dex "; cmd << "--output=" << project->GetBinDir() << DIR_SEPS << "classes.dex "; cmd << project->GetClassesDir(); if(Execute(cmd, ss) != 0) { PutConsole(ss.GetResult()); return false; } return true; } bool AndroidBuilder::PreprocessJava(const String& package, const String& file, const String& target) { StringStream ss; String ext = GetFileExt(file); if(ext != ".java") return false; String fileName = GetFileName(file); fileName.Replace(ext, ""); String targetDir = GetFileDirectory(target); String classesDir = targetDir + "classes"; String classFileName = fileName + ".class"; if(DirectoryExists(classesDir)) DeleteFolderDeep(classesDir); RealizeDirectory(classesDir); String compileCmd; compileCmd << NormalizeExePath(jdk->GetJavacPath()); compileCmd << " -d "<< classesDir; compileCmd << " -classpath "; compileCmd << NormalizeExePath(sdk.AndroidJarPath()) << Java::GetDelimiter(); compileCmd << project->GetBuildDir(); compileCmd << " -sourcepath " << project->GetJavaDir(); compileCmd << " " << file; if(Execute(compileCmd) != 0) return false; String classFileDir; BiVector paths; paths.AddTail(classesDir); while(!paths.IsEmpty()) { for(FindFile ff(AppendFileName(paths.Head(), "*")); ff; ff.Next()) { if(!ff.IsHidden()) { if(!ff.IsSymLink() && ff.IsDirectory()) paths.AddTail(ff.GetPath()); else if(ff.GetName().Compare(classFileName) == 0) { classFileDir = GetFileDirectory(ff.GetPath()); paths.Clear(); break; } } } if(!paths.IsEmpty()) paths.DropHead(); } if(classFileDir.IsEmpty()) return false; String relativeClassFileDir = classFileDir; relativeClassFileDir.Replace(classesDir + DIR_SEPS, ""); String className = relativeClassFileDir + fileName; className.Replace(DIR_SEPS, "."); String javahCmd; javahCmd << NormalizeExePath(jdk->GetJavahPath()); javahCmd << " -classpath "; javahCmd << classesDir << Java::GetDelimiter(); javahCmd << NormalizeExePath(sdk.AndroidJarPath()) << Java::GetDelimiter(); javahCmd << project->GetBuildDir(); javahCmd << " -o " << target; javahCmd << " " << className; // PutConsole(javahCmd); if(Execute(javahCmd, ss) != 0) { PutConsole(ss.GetResult()); return false; } return true; } // ------------------------------------------------------------------- void AndroidBuilder::InitProject() { if(!project) { project.Create(GetAndroidProjectDir(), HasFlag("DEBUG")); commands.Create(project.Get(), &sdk, jdk.Get()); } } String AndroidBuilder::GetSandboxDir() const { if(target.IsEmpty()) return String(); int targetExtLen = GetTargetExt().GetCount(); String mainPackageName = GetFileName(target); mainPackageName.Remove(mainPackageName.GetCount() - targetExtLen, targetExtLen); return GetFileFolder(target) + DIR_SEPS + "Sandbox" + DIR_SEPS + mainPackageName; } String AndroidBuilder::GetAndroidProjectDir() const { return GetSandboxDir() + DIR_SEPS + "AndroidProject"; } // ------------------------------------------------------------------- String AndroidBuilder::GetFilePathInAndroidProject( const String& nestDir, const String& packageName, const String& fileName) const { return nestDir + DIR_SEPS + packageName + DIR_SEPS + RemoveDirNameFromFileName(fileName); } String AndroidBuilder::RemoveDirNameFromFileName(String fileName) const { int idx = fileName.ReverseFind(DIR_SEPS); if(idx >= 0) fileName.Remove(0, idx); return fileName; } String AndroidBuilder::NormalizeModuleName(String moduleName) const { moduleName.Replace(DIR_SEPS, "_"); return moduleName; } String AndroidBuilder::GetModuleMakeFilePath(const String& package) const { return project->GetJniDir() + DIR_SEPS + package + DIR_SEPS + "Android.mk"; } // ------------------------------------------------------------------- Builder *CreateAndroidBuilder() { return new AndroidBuilder(); } INITIALIZER(AndroidBuilder) { Index builders = AndroidBuilder::GetBuildersNames(); for(int i = 0; i < builders.GetCount(); i++) RegisterBuilder(builders[i], &CreateAndroidBuilder); }