From 68f084e4ed817da0c25cefa1772cadf97b8cfe5e Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Fri, 10 Oct 2014 19:35:44 +0300 Subject: [PATCH] A bunch of new tools and targets to build Android packages Currently can only build apks using NativeActivity and debug key, and will probably break if the package has a version. Still, it's useful in certain configurations so I'll commit this version now and continue improving it. --- source/androidapplicationcomponent.cpp | 79 ++++++++++ source/androidapplicationcomponent.h | 14 ++ source/androidassetpackagingtool.cpp | 84 +++++++++++ source/androidassetpackagingtool.h | 20 +++ source/androidmanifestfile.cpp | 19 +++ source/androidmanifestfile.h | 26 ++++ source/androidmanifestgenerator.cpp | 58 +++++++ source/androidmanifestgenerator.h | 31 ++++ source/androidpackagefile.cpp | 16 ++ source/androidpackagefile.h | 16 ++ source/androidresourcebundle.cpp | 16 ++ source/androidresourcebundle.h | 16 ++ source/androidresourcefile.cpp | 19 +++ source/androidresourcefile.h | 20 +++ source/androidtools.cpp | 199 +++++++++++++++++-------- source/androidtools.h | 46 +++++- source/apkbuilder.cpp | 79 ++++++++++ source/apkbuilder.h | 21 +++ source/jarsigner.cpp | 43 ++++++ source/jarsigner.h | 15 ++ source/sourcepackage.cpp | 2 + 21 files changed, 772 insertions(+), 67 deletions(-) create mode 100644 source/androidapplicationcomponent.cpp create mode 100644 source/androidapplicationcomponent.h create mode 100644 source/androidassetpackagingtool.cpp create mode 100644 source/androidassetpackagingtool.h create mode 100644 source/androidmanifestfile.cpp create mode 100644 source/androidmanifestfile.h create mode 100644 source/androidmanifestgenerator.cpp create mode 100644 source/androidmanifestgenerator.h create mode 100644 source/androidpackagefile.cpp create mode 100644 source/androidpackagefile.h create mode 100644 source/androidresourcebundle.cpp create mode 100644 source/androidresourcebundle.h create mode 100644 source/androidresourcefile.cpp create mode 100644 source/androidresourcefile.h create mode 100644 source/apkbuilder.cpp create mode 100644 source/apkbuilder.h create mode 100644 source/jarsigner.cpp create mode 100644 source/jarsigner.h diff --git a/source/androidapplicationcomponent.cpp b/source/androidapplicationcomponent.cpp new file mode 100644 index 0000000..3c07fea --- /dev/null +++ b/source/androidapplicationcomponent.cpp @@ -0,0 +1,79 @@ +#include +#include "androidapplicationcomponent.h" +#include "androidmanifestfile.h" +#include "androidresourcefile.h" +#include "builder.h" +#include "installedfile.h" +#include "sharedlibrary.h" +#include "sourcepackage.h" +#include "tool.h" +#include "toolchain.h" + +using namespace std; +using namespace Msp; + +AndroidApplicationComponent::AndroidApplicationComponent(SourcePackage &p, const string &n): + Component(p, n) +{ } + +void AndroidApplicationComponent::create_targets() const +{ + Builder &builder = package.get_builder(); + BuildGraph &build_graph = builder.get_build_graph(); + + const BuildGraph::TargetMap &targets = build_graph.get_targets(); + list contents; + for(BuildGraph::TargetMap::const_iterator i=targets.begin(); i!=targets.end(); ++i) + if(i->second->get_package()==&package) + if(InstalledFile *inst = dynamic_cast(i->second)) + contents.push_back(inst->get_real_target()); + + AndroidManifestFile *manifest = new AndroidManifestFile(builder, *this); + + list resource_sources; + resource_sources.push_back(manifest); + + const Toolchain &toolchain = builder.get_toolchain(); + Tool © = toolchain.get_tool("CP"); + SourceList source_filenames = collect_source_files(); + for(SourceList::const_iterator i=source_filenames.begin(); i!=source_filenames.end(); ++i) + { + Target *tgt = new AndroidResourceFile(builder, *this, *i); + resource_sources.push_back(copy.create_target(*tgt, "//")); + } + + Tool &aapt = toolchain.get_tool("AAPT"); + Target *resource_bundle = aapt.create_target(resource_sources); + + list apk_sources; + apk_sources.push_back(resource_bundle); + + const Architecture &arch = package.get_builder().get_current_arch(); + string lib_dir = "//"+name+"/lib/"; + if(arch.get_type()=="arm") + { + lib_dir += "armeabi"; + if(arch.get_cpu()=="armv7a") + lib_dir += "-v7a"; + } + else + lib_dir += arch.get_type(); + + string assets_dir = "//"+name+"/assets"; + for(list::const_iterator i=contents.begin(); i!=contents.end(); ++i) + { + Target *staged = 0; + if(SharedLibrary *shlib = dynamic_cast(*i)) + { + manifest->set_native_library(shlib); + staged = copy.create_target(**i, lib_dir); + } + else + staged = copy.create_target(**i, assets_dir); + apk_sources.push_back(staged); + } + + Tool &apk_builder = toolchain.get_tool("APK"); + Target *apk = apk_builder.create_target(apk_sources); + builder.get_build_graph().add_primary_target(*apk); +} diff --git a/source/androidapplicationcomponent.h b/source/androidapplicationcomponent.h new file mode 100644 index 0000000..8d02375 --- /dev/null +++ b/source/androidapplicationcomponent.h @@ -0,0 +1,14 @@ +#ifndef ANDROIDAPPLICATIONCOMPONENT_H_ +#define ANDROIDAPPLICATIONCOMPONENT_H_ + +#include "component.h" + +class AndroidApplicationComponent: public Component +{ +public: + AndroidApplicationComponent(SourcePackage &, const std::string &); + + virtual void create_targets() const; +}; + +#endif diff --git a/source/androidassetpackagingtool.cpp b/source/androidassetpackagingtool.cpp new file mode 100644 index 0000000..cca30b8 --- /dev/null +++ b/source/androidassetpackagingtool.cpp @@ -0,0 +1,84 @@ +#include +#include "androidassetpackagingtool.h" +#include "androidmanifestfile.h" +#include "androidresourcebundle.h" +#include "androidtools.h" +#include "component.h" +#include "externaltask.h" +#include "sourcepackage.h" + +using namespace std; +using namespace Msp; + +AndroidAssetPackagingTool::AndroidAssetPackagingTool(Builder &b, const AndroidSdk &s): + Tool(b, "AAPT"), + sdk(s) +{ + set_command((sdk.get_build_tools_dir()/"aapt").str()); +} + +Target *AndroidAssetPackagingTool::create_target(const list &sources, const string &) +{ + AndroidManifestFile *manifest = 0; + list resources; + for(list::const_iterator i=sources.begin(); i!=sources.end(); ++i) + { + if(AndroidManifestFile *m = dynamic_cast(*i)) + manifest = m; + else if(FileTarget *f = dynamic_cast(*i)) + resources.push_back(f); + } + + if(!manifest) + throw invalid_argument("AndroidAssetPackagingTool::create_target"); + + AndroidResourceBundle *res = new AndroidResourceBundle(builder, *manifest->get_component(), *manifest, resources); + res->set_tool(*this); + return res; +} + +Task *AndroidAssetPackagingTool::run(const Target &tgt) const +{ + const AndroidResourceBundle &res = dynamic_cast(tgt); + + ExternalTask::Arguments argv; + argv.push_back(executable->get_path().str()); + argv.push_back("package"); + + FS::Path work_dir = res.get_component()->get_package().get_source_directory(); + + argv.push_back("-I"); + argv.push_back(sdk.get_platform_jar().str()); + + argv.push_back("-F"); + argv.push_back(FS::relative(res.get_path(), work_dir).str()); + + const Target::Dependencies &depends = res.get_dependencies(); + list resource_dirs; + for(Target::Dependencies::const_iterator i=depends.begin(); i!=depends.end(); ++i) + { + FileTarget *file = dynamic_cast(*i); + Target *real = (*i)->get_real_target(); + + if(dynamic_cast(real)) + { + argv.push_back("-M"); + argv.push_back(FS::relative(file->get_path(), work_dir).str()); + } + else if(real->get_package()==res.get_package()) + { + const FS::Path &path = file->get_path(); + resource_dirs.push_back(path.subpath(0, path.size()-2)); + } + } + + set seen_dirs; + for(list::const_iterator i=resource_dirs.begin(); i!=resource_dirs.end(); ++i) + if(seen_dirs.insert(i->str()).second) + { + argv.push_back("-S"); + argv.push_back(FS::relative(*i, work_dir).str()); + } + + return new ExternalTask(argv, work_dir); +} diff --git a/source/androidassetpackagingtool.h b/source/androidassetpackagingtool.h new file mode 100644 index 0000000..1366087 --- /dev/null +++ b/source/androidassetpackagingtool.h @@ -0,0 +1,20 @@ +#ifndef ANDROIDASSETPACKAGINGTOOL_H_ +#define ANDROIDASSETPACKAGINGTOOL_H_ + +#include "tool.h" + +class AndroidSdk; + +class AndroidAssetPackagingTool: public Tool +{ +private: + const AndroidSdk &sdk; + +public: + AndroidAssetPackagingTool(Builder &, const AndroidSdk &); + + virtual Target *create_target(const std::list &, const std::string &); + virtual Task *run(const Target &) const; +}; + +#endif diff --git a/source/androidmanifestfile.cpp b/source/androidmanifestfile.cpp new file mode 100644 index 0000000..55503de --- /dev/null +++ b/source/androidmanifestfile.cpp @@ -0,0 +1,19 @@ +#include "androidapplicationcomponent.h" +#include "androidmanifestfile.h" +#include "builder.h" +#include "sourcepackage.h" + +AndroidManifestFile::AndroidManifestFile(Builder &b, const AndroidApplicationComponent &a): + FileTarget(b, a.get_package(), a.get_package().get_temp_directory()/a.get_name()/"AndroidManifest.xml"), + native_lib(0) +{ + component = &a; + tool = &builder.get_toolchain().get_tool("AMG"); + + add_dependency(package->get_build_file()); +} + +void AndroidManifestFile::set_native_library(SharedLibrary *lib) +{ + native_lib = lib; +} diff --git a/source/androidmanifestfile.h b/source/androidmanifestfile.h new file mode 100644 index 0000000..e421adf --- /dev/null +++ b/source/androidmanifestfile.h @@ -0,0 +1,26 @@ +#ifndef ANDROIDMANIFESTFILE_H_ +#define ANDROIDMANIFESTFILE_H_ + +#include "filetarget.h" + +class AndroidApplicationComponent; +class SharedLibrary; + +/** +Metadata file for an Android application. +*/ +class AndroidManifestFile: public FileTarget +{ +private: + SharedLibrary *native_lib; + +public: + AndroidManifestFile(Builder &, const AndroidApplicationComponent &); + + virtual const char *get_type() const { return "AndroidManifestFile"; } + + void set_native_library(SharedLibrary *); + SharedLibrary *get_native_library() const { return native_lib; } +}; + +#endif diff --git a/source/androidmanifestgenerator.cpp b/source/androidmanifestgenerator.cpp new file mode 100644 index 0000000..b0c7f8f --- /dev/null +++ b/source/androidmanifestgenerator.cpp @@ -0,0 +1,58 @@ +#include +#include +#include "androidmanifestfile.h" +#include "androidmanifestgenerator.h" +#include "component.h" +#include "sharedlibrary.h" +#include "sourcepackage.h" + +using namespace std; +using namespace Msp; + +AndroidManifestGenerator::AndroidManifestGenerator(Builder &b): + Tool(b, "AMG") +{ } + +Target *AndroidManifestGenerator::create_target(const list &, const string &) +{ + throw logic_error("not implemented"); +} + +Task *AndroidManifestGenerator::run(const Target &target) const +{ + const AndroidManifestFile &manifest = dynamic_cast(target); + Worker *worker = new Worker(manifest); + return new InternalTask(worker); +} + + +AndroidManifestGenerator::Worker::Worker(const AndroidManifestFile &m): + manifest(m) +{ } + +void AndroidManifestGenerator::Worker::main() +{ + const Component &comp = *manifest.get_component(); + const SourcePackage &pkg = comp.get_package(); + + IO::BufferedFile out(manifest.get_path().str(), IO::M_WRITE); + out.write("\n"); + IO::print(out, "\n", comp.get_name()); + out.write("\t\n"); + // TODO Make the icon name configurable + IO::print(out, "\t\n", pkg.get_label()); + if(SharedLibrary *native_lib = manifest.get_native_library()) + { + out.write("\t\t\n"); + IO::print(out, "\t\t\t\n", native_lib->get_libname()); + out.write("\t\t\t\n"); + out.write("\t\t\t\t\n"); + out.write("\t\t\t\t\n"); + out.write("\t\t\t\n"); + out.write("\t\t\n"); + } + out.write("\t\n"); + out.write("\n"); + + status = Task::SUCCESS; +} diff --git a/source/androidmanifestgenerator.h b/source/androidmanifestgenerator.h new file mode 100644 index 0000000..f0d4269 --- /dev/null +++ b/source/androidmanifestgenerator.h @@ -0,0 +1,31 @@ +#ifndef ANDROIDMANIFESTGENERATOR_H_ +#define ANDROIDMANIFESTGENERATOR_H_ + +#include "internaltask.h" +#include "tool.h" + +class AndroidManifestFile; + +class AndroidManifestGenerator: public Tool +{ +private: + class Worker: public InternalTask::Worker + { + private: + const AndroidManifestFile &manifest; + + public: + Worker(const AndroidManifestFile &); + + private: + virtual void main(); + }; + +public: + AndroidManifestGenerator(Builder &); + + virtual Target *create_target(const std::list &, const std::string &); + virtual Task *run(const Target &) const; +}; + +#endif diff --git a/source/androidpackagefile.cpp b/source/androidpackagefile.cpp new file mode 100644 index 0000000..1400d0f --- /dev/null +++ b/source/androidpackagefile.cpp @@ -0,0 +1,16 @@ +#include "androidpackagefile.h" +#include "androidresourcebundle.h" +#include "component.h" +#include "sourcepackage.h" + +using namespace std; + +AndroidPackageFile::AndroidPackageFile(Builder &b, const Component &c, AndroidResourceBundle &resource_bundle, const list &other_files): + FileTarget(b, c.get_package(), c.get_package().get_output_directory()/(c.get_name()+".apk")) +{ + component = &c; + + add_dependency(resource_bundle); + for(list::const_iterator i=other_files.begin(); i!=other_files.end(); ++i) + add_dependency(**i); +} diff --git a/source/androidpackagefile.h b/source/androidpackagefile.h new file mode 100644 index 0000000..4f1c692 --- /dev/null +++ b/source/androidpackagefile.h @@ -0,0 +1,16 @@ +#ifndef ANDROIDPACKAGEFILE_H_ +#define ANDROIDPACKAGEFILE_H_ + +#include "filetarget.h" + +class AndroidResourceBundle; + +class AndroidPackageFile: public FileTarget +{ +public: + AndroidPackageFile(Builder &, const Component &, AndroidResourceBundle &, const std::list &); + + virtual const char *get_type() const { return "AndroidPackageFile"; } +}; + +#endif diff --git a/source/androidresourcebundle.cpp b/source/androidresourcebundle.cpp new file mode 100644 index 0000000..d327126 --- /dev/null +++ b/source/androidresourcebundle.cpp @@ -0,0 +1,16 @@ +#include "androidmanifestfile.h" +#include "androidresourcebundle.h" +#include "component.h" +#include "sourcepackage.h" + +using namespace std; + +AndroidResourceBundle::AndroidResourceBundle(Builder &b, const Component &c, AndroidManifestFile &manifest, const list &resources): + FileTarget(b, c.get_package(), c.get_package().get_temp_directory()/c.get_name()/(c.get_name()+".ap_")) +{ + component = &c; + + add_dependency(manifest); + for(list::const_iterator i=resources.begin(); i!=resources.end(); ++i) + add_dependency(**i); +} diff --git a/source/androidresourcebundle.h b/source/androidresourcebundle.h new file mode 100644 index 0000000..253b44b --- /dev/null +++ b/source/androidresourcebundle.h @@ -0,0 +1,16 @@ +#ifndef ANDROIDRESOURCEBUNDLE_H_ +#define ANDROIDRESOURCEBUNDLE_H_ + +#include "filetarget.h" + +class AndroidManifestFile; + +class AndroidResourceBundle: public FileTarget +{ +public: + AndroidResourceBundle(Builder &, const Component &, AndroidManifestFile &, const std::list &); + + virtual const char *get_type() const { return "AndroidResourceBundle"; } +}; + +#endif diff --git a/source/androidresourcefile.cpp b/source/androidresourcefile.cpp new file mode 100644 index 0000000..d88f786 --- /dev/null +++ b/source/androidresourcefile.cpp @@ -0,0 +1,19 @@ +#include +#include "androidresourcefile.h" +#include "component.h" + +using namespace std; +using namespace Msp; + +AndroidResourceFile::AndroidResourceFile(Builder &b, const Component &c, const FS::Path &p): + FileTarget(b, c.get_package(), p) +{ + string ext = FS::extpart(FS::basename(p)); + if(ext==".png" || ext==".jpg") + resource_type = "drawable"; + // TODO recognize various xml files; must inspect contents + else + resource_type = "raw"; + + install_location = "res/"+resource_type; +} diff --git a/source/androidresourcefile.h b/source/androidresourcefile.h new file mode 100644 index 0000000..5f3cd3a --- /dev/null +++ b/source/androidresourcefile.h @@ -0,0 +1,20 @@ +#ifndef ANDROIDRESOURCEFILE_H_ +#define ANDROIDRESOURCEFILE_H_ + +#include "filetarget.h" + +/** +A file destined to be used as a resource in an Android application. +*/ +class AndroidResourceFile: public FileTarget +{ +private: + std::string resource_type; + +public: + AndroidResourceFile(Builder &, const Component &, const Msp::FS::Path &); + + virtual const char *get_type() const { return "AndroidResourceFile"; } +}; + +#endif diff --git a/source/androidtools.cpp b/source/androidtools.cpp index 13ebc25..122dc23 100644 --- a/source/androidtools.cpp +++ b/source/androidtools.cpp @@ -1,75 +1,140 @@ #include #include +#include #include #include +#include "androidassetpackagingtool.h" #include "androidccompiler.h" #include "androidcxxcompiler.h" #include "androidlinker.h" +#include "androidmanifestgenerator.h" #include "androidtools.h" +#include "apkbuilder.h" #include "architecture.h" #include "builder.h" +#include "jarsigner.h" using namespace std; using namespace Msp; -AndroidTools::AndroidTools(Builder &builder, const Architecture &arch): - ndk(builder, arch) +// TODO Mark problems somewhere instead of throwing exceptions + +unsigned parse_version(const std::string &version_str) { - add_tool(new AndroidCCompiler(builder, arch, ndk)); - add_tool(new AndroidCxxCompiler(builder, arch, ndk)); - add_tool(new AndroidLinker(builder, arch, ndk)); + vector version_parts = split(version_str, '.'); + unsigned version = lexical_cast(version_parts[0])<<16; + if(version_parts.size()>1) + version += lexical_cast(version_parts[1])<<8; + if(version_parts.size()>2) + version += lexical_cast(version_parts[2]); + return version; } -AndroidNdk::AndroidNdk(Builder &b, const Architecture &a): - builder(b), - architecture(a) +AndroidDevKit::AndroidDevKit(Builder &b, const string &type): + builder(b) { - if(const char *env_ndk_root = getenv("ANDROID_NDK_ROOT")) - ndk_root = env_ndk_root; + string var = format("ANDROID_%s_ROOT", type); + if(const char *value = getenv(var.c_str())) + root = value; else - throw runtime_error("ANDROID_NDK_ROOT must be set"); + throw runtime_error(var+" must be set"); + + FS::Path platforms_dir = root/"platforms"; + builder.get_logger().log("files", format("Traversing %s", platforms_dir.str())); + list platforms = list_filtered(platforms_dir, "^android-[1-9][0-9]*$"); + + for(list::const_iterator i=platforms.begin(); i!=platforms.end(); ++i) + supported_api_levels.insert(lexical_cast(i->substr(8))); +} + +void AndroidDevKit::select_api_level(unsigned api) +{ + if(!supported_api_levels.count(api)) + throw invalid_argument("AndroidDevKit::select_api_level"); + + init_api_level(api); +} + + +AndroidSdk::AndroidSdk(Builder &b): + AndroidDevKit(b, "SDK") +{ + find_build_tools_dir(); +} + +void AndroidSdk::find_build_tools_dir() +{ + FS::Path bt_dir = root/"build-tools"; + builder.get_logger().log("files", format("Traversing %s", bt_dir.str())); + list tool_versions = list_files(bt_dir); + + string use_tools; + unsigned latest_version = 0; + for(list::const_iterator i=tool_versions.begin(); i!=tool_versions.end(); ++i) + { + unsigned version = parse_version(*i); + if(version>latest_version) + { + use_tools = *i; + latest_version = version; + } + } + + if(use_tools.empty()) + throw runtime_error("No build tools found"); + + build_tools_dir = bt_dir/use_tools; + builder.get_logger().log("tools", format("Android build tools are in %s", build_tools_dir.str())); +} + +void AndroidSdk::init_api_level(unsigned api) +{ + if(!supported_api_levels.count(api)) + throw invalid_argument("AndroidSdk::select_api_level"); + + platform_jar = root/"platforms"/format("android-%d", api)/"android.jar"; +} + +AndroidNdk::AndroidNdk(Builder &b, const Architecture &a): + AndroidDevKit(b, "NDK"), + architecture(a) +{ find_toolchain_dir(); - find_platform_dir(); } void AndroidNdk::find_toolchain_dir() { - FS::Path toolchains_dir = ndk_root/"toolchains"; + FS::Path toolchains_dir = root/"toolchains"; builder.get_logger().log("files", format("Traversing %s", toolchains_dir.str())); - list toolchains = list_files(toolchains_dir); - string prefix = architecture.get_cross_prefix()+"-"; + list toolchains = list_filtered(toolchains_dir, "^"+prefix); + string use_toolchain; unsigned latest_version = 0; for(list::const_iterator i=toolchains.begin(); i!=toolchains.end(); ++i) - if(!i->compare(0, prefix.size(), prefix)) + { + string version_str = i->substr(prefix.size()); + string compiler = "gcc"; + if(!isdigit(version_str[0])) + { + unsigned j; + for(j=1; (jlatest_version) { - string version_str = i->substr(prefix.size()); - string compiler = "gcc"; - if(!isdigit(version_str[0])) - { - unsigned j; - for(j=1; (j version_parts = split(version_str, '.'); - unsigned version = (lexical_cast(version_parts[0])<<8); - if(version_parts.size()>1) - version += lexical_cast(version_parts[1]); - - if(version>latest_version) - { - use_toolchain = *i; - latest_version = version; - } + use_toolchain = *i; + latest_version = version; } + } if(use_toolchain.empty()) throw runtime_error("No applicable toolchains found"); @@ -87,29 +152,12 @@ void AndroidNdk::find_toolchain_dir() builder.get_logger().log("tools", format("Android toolchain binaries are in %s", bin_dir.str())); } -void AndroidNdk::find_platform_dir() +void AndroidNdk::init_api_level(unsigned api) { - FS::Path platforms_dir = ndk_root/"platforms"; - builder.get_logger().log("files", format("Traversing %s", platforms_dir.str())); - list platforms = list_files(platforms_dir); + if(!supported_api_levels.count(api)) + throw invalid_argument("AndroidSdk::select_api_level"); - string use_platform; - unsigned highest_level = 0; - for(list::const_iterator i=platforms.begin(); i!=platforms.end(); ++i) - if(!i->compare(0, 8, "android-")) - { - unsigned level = lexical_cast(i->substr(8)); - if(level>highest_level) - { - use_platform = *i; - highest_level = level; - } - } - - if(use_platform.empty()) - throw runtime_error("No applicable platforms found"); - - FS::Path platform_archs_dir = platforms_dir/use_platform; + FS::Path platform_archs_dir = root/"platforms"/format("android-%d", api); builder.get_logger().log("files", format("Traversing %s", platform_archs_dir.str())); list platform_archs = list_filtered(platform_archs_dir, "^arch-"); for(list::iterator i=platform_archs.begin(); i!=platform_archs.end(); ++i) @@ -119,6 +167,33 @@ void AndroidNdk::find_platform_dir() if(use_arch.empty()) throw runtime_error("No matching platform found"); - platform_sysroot = platforms_dir/use_platform/use_arch; - builder.get_logger().log("tools", format("Android platform sysroot is %s", platform_sysroot.str())); + platform_sysroot = platform_archs_dir/("arch-"+use_arch); + builder.get_logger().log("tools", format("Android NDK sysroot is %s", platform_sysroot.str())); +} + + +AndroidTools::AndroidTools(Builder &builder, const Architecture &arch): + sdk(builder), + ndk(builder, arch) +{ + const set &sdk_api_levels = sdk.get_supported_api_levels(); + const set &ndk_api_levels = ndk.get_supported_api_levels(); + unsigned highest_common = 0; + for(set::const_reverse_iterator i=sdk_api_levels.rbegin(); (!highest_common && i!=sdk_api_levels.rend()); ++i) + if(ndk_api_levels.count(*i)) + highest_common = *i; + + if(!highest_common) + throw runtime_error("No usable Android platforms found"); + + sdk.select_api_level(highest_common); + ndk.select_api_level(highest_common); + + add_tool(new AndroidCCompiler(builder, arch, ndk)); + add_tool(new AndroidCxxCompiler(builder, arch, ndk)); + add_tool(new AndroidLinker(builder, arch, ndk)); + add_tool(new AndroidManifestGenerator(builder)); + add_tool(new AndroidAssetPackagingTool(builder, sdk)); + add_tool(new ApkBuilder(builder)); + add_tool(new JarSigner(builder)); } diff --git a/source/androidtools.h b/source/androidtools.h index 3ccedcb..16f157d 100644 --- a/source/androidtools.h +++ b/source/androidtools.h @@ -1,18 +1,54 @@ #ifndef ANDROIDTOOLS_H_ #define ANDROIDTOOLS_H_ +#include #include #include "toolchain.h" class Architecture; class Builder; -class AndroidNdk +class AndroidDevKit { -private: +protected: Builder &builder; + Msp::FS::Path root; + std::set supported_api_levels; + + AndroidDevKit(Builder &, const std::string &); + ~AndroidDevKit() { } + +public: + const Msp::FS::Path &get_root_dir() const { return root; } + const std::set &get_supported_api_levels() const { return supported_api_levels; } + void select_api_level(unsigned); +protected: + virtual void init_api_level(unsigned) = 0; +}; + +class AndroidSdk: public AndroidDevKit +{ +private: + Msp::FS::Path build_tools_dir; + // TODO use a FileTarget for the jar + Msp::FS::Path platform_jar; + +public: + AndroidSdk(Builder &); + +private: + void find_build_tools_dir(); + virtual void init_api_level(unsigned); + +public: + const Msp::FS::Path &get_build_tools_dir() const { return build_tools_dir; } + const Msp::FS::Path &get_platform_jar() const { return platform_jar; } +}; + +class AndroidNdk: public AndroidDevKit +{ +private: const Architecture &architecture; - Msp::FS::Path ndk_root; Msp::FS::Path bin_dir; Msp::FS::Path platform_sysroot; @@ -21,10 +57,9 @@ public: private: void find_toolchain_dir(); - void find_platform_dir(); + virtual void init_api_level(unsigned); public: - const Msp::FS::Path &get_root_dir() const { return ndk_root; } const Msp::FS::Path &get_bin_dir() const { return bin_dir; } const Msp::FS::Path &get_platform_sysroot() const { return platform_sysroot; } }; @@ -33,6 +68,7 @@ public: class AndroidTools: public Toolchain { private: + AndroidSdk sdk; AndroidNdk ndk; public: diff --git a/source/apkbuilder.cpp b/source/apkbuilder.cpp new file mode 100644 index 0000000..9cd1a16 --- /dev/null +++ b/source/apkbuilder.cpp @@ -0,0 +1,79 @@ +#include +#include "androidpackagefile.h" +#include "androidresourcebundle.h" +#include "apkbuilder.h" +#include "builder.h" +#include "chainedtask.h" +#include "component.h" +#include "externaltask.h" +#include "filetarget.h" +#include "sourcepackage.h" + +using namespace std; +using namespace Msp; + +// TODO Separate jar into its own tool and have this one just chain the two + +ApkBuilder::ApkBuilder(Builder &b): + Tool(b, "APK"), + jarsigner(0) +{ + set_command("jar"); +} + +Target *ApkBuilder::create_target(const list &sources, const string &) +{ + AndroidResourceBundle *resource_bundle = 0; + list other_files; + for(list::const_iterator i=sources.begin(); i!=sources.end(); ++i) + { + if(AndroidResourceBundle *r = dynamic_cast(*i)) + resource_bundle = r; + else if(FileTarget *f = dynamic_cast(*i)) + other_files.push_back(f); + } + AndroidPackageFile *apk = new AndroidPackageFile(builder, *resource_bundle->get_component(), *resource_bundle, other_files); + apk->set_tool(*this); + return apk; +} + +void ApkBuilder::do_prepare() +{ + jarsigner = &builder.get_toolchain().get_tool("JSGN"); + jarsigner->prepare(); +} + +Task *ApkBuilder::run(const Target &tgt) const +{ + const AndroidPackageFile &apk = dynamic_cast(tgt); + + ExternalTask::Arguments argv; + argv.push_back(executable->get_path().str()); + argv.push_back("u"); + + const Target::Dependencies &depends = apk.get_dependencies(); + FS::Path input_path; + list files; + for(Target::Dependencies::const_iterator i=depends.begin(); i!=depends.end(); ++i) + { + FileTarget *file = dynamic_cast(*i); + Target *real = (*i)->get_real_target(); + + if(dynamic_cast(real)) + input_path = file->get_path(); + else if(real->get_package()==apk.get_package()) + files.push_back(file->get_path()); + } + + FS::Path work_dir = FS::dirname(input_path); + + for(list::const_iterator i=files.begin(); i!=files.end(); ++i) + argv.push_back(FS::relative(*i, work_dir).str()); + + ExternalTask *task = new ExternalTask(argv, work_dir); + task->set_stdin(FS::basename(input_path)); + task->set_stdout(FS::relative(apk.get_path(), work_dir)); + ChainedTask *chain = new ChainedTask(task); + chain->add_task(jarsigner->run(tgt)); + return chain; +} diff --git a/source/apkbuilder.h b/source/apkbuilder.h new file mode 100644 index 0000000..7b73aa7 --- /dev/null +++ b/source/apkbuilder.h @@ -0,0 +1,21 @@ +#ifndef APKBUILDER_H_ +#define APKBUILDER_H_ + +#include "tool.h" + +class ApkBuilder: public Tool +{ +private: + Tool *jarsigner; + +public: + ApkBuilder(Builder &); + + virtual Target *create_target(const std::list &, const std::string &); +protected: + virtual void do_prepare(); +public: + virtual Task *run(const Target &) const; +}; + +#endif diff --git a/source/jarsigner.cpp b/source/jarsigner.cpp new file mode 100644 index 0000000..b8c9a50 --- /dev/null +++ b/source/jarsigner.cpp @@ -0,0 +1,43 @@ +#include +#include +#include "component.h" +#include "externaltask.h" +#include "filetarget.h" +#include "jarsigner.h" +#include "sourcepackage.h" + +using namespace std; +using namespace Msp; + +JarSigner::JarSigner(Builder &b): + Tool(b, "JSGN") +{ + set_command("jarsigner"); +} + +Target *JarSigner::create_target(const list &, const string &) +{ + throw logic_error("not implemented"); +} + +Task *JarSigner::run(const Target &tgt) const +{ + const FileTarget &file = dynamic_cast(tgt); + + ExternalTask::Arguments argv; + argv.push_back(executable->get_path().str()); + + // TODO Make this generic + FS::Path home_dir = getenv("HOME"); + argv.push_back("-keystore"); + argv.push_back((home_dir/".android"/"debug.keystore").str()); + argv.push_back("-storepass"); + argv.push_back("android"); + + FS::Path work_dir = file.get_component()->get_package().get_source_directory(); + + argv.push_back(FS::relative(file.get_path(), work_dir).str()); + argv.push_back("androiddebugkey"); + + return new ExternalTask(argv, work_dir); +} diff --git a/source/jarsigner.h b/source/jarsigner.h new file mode 100644 index 0000000..cc773ea --- /dev/null +++ b/source/jarsigner.h @@ -0,0 +1,15 @@ +#ifndef JARSIGNER_H_ +#define JARSIGNER_H_ + +#include "tool.h" + +class JarSigner: public Tool +{ +public: + JarSigner(Builder &); + + virtual Target *create_target(const std::list &, const std::string &); + virtual Task *run(const Target &) const; +}; + +#endif diff --git a/source/sourcepackage.cpp b/source/sourcepackage.cpp index 2742300..942e5a3 100644 --- a/source/sourcepackage.cpp +++ b/source/sourcepackage.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "androidapplicationcomponent.h" #include "binarycomponent.h" #include "binarypackage.h" #include "booleanevaluator.h" @@ -158,6 +159,7 @@ SourcePackage::Loader::Loader(SourcePackage &p, const Config::InputOptions &o): void SourcePackage::Loader::init(const Config::InputOptions *o) { options = o; + add("android_application", &Loader::component); add("build_info", &Loader::build_info); add("datapack", &Loader::component); add("description", &SourcePackage::description); -- 2.43.0