]> git.tdb.fi Git - builder.git/commitdiff
A bunch of new tools and targets to build Android packages
authorMikko Rasa <tdb@tdb.fi>
Fri, 10 Oct 2014 16:35:44 +0000 (19:35 +0300)
committerMikko Rasa <tdb@tdb.fi>
Fri, 10 Oct 2014 16:35:44 +0000 (19:35 +0300)
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.

21 files changed:
source/androidapplicationcomponent.cpp [new file with mode: 0644]
source/androidapplicationcomponent.h [new file with mode: 0644]
source/androidassetpackagingtool.cpp [new file with mode: 0644]
source/androidassetpackagingtool.h [new file with mode: 0644]
source/androidmanifestfile.cpp [new file with mode: 0644]
source/androidmanifestfile.h [new file with mode: 0644]
source/androidmanifestgenerator.cpp [new file with mode: 0644]
source/androidmanifestgenerator.h [new file with mode: 0644]
source/androidpackagefile.cpp [new file with mode: 0644]
source/androidpackagefile.h [new file with mode: 0644]
source/androidresourcebundle.cpp [new file with mode: 0644]
source/androidresourcebundle.h [new file with mode: 0644]
source/androidresourcefile.cpp [new file with mode: 0644]
source/androidresourcefile.h [new file with mode: 0644]
source/androidtools.cpp
source/androidtools.h
source/apkbuilder.cpp [new file with mode: 0644]
source/apkbuilder.h [new file with mode: 0644]
source/jarsigner.cpp [new file with mode: 0644]
source/jarsigner.h [new file with mode: 0644]
source/sourcepackage.cpp

diff --git a/source/androidapplicationcomponent.cpp b/source/androidapplicationcomponent.cpp
new file mode 100644 (file)
index 0000000..3c07fea
--- /dev/null
@@ -0,0 +1,79 @@
+#include <msp/fs/utils.h>
+#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<Target *> contents;
+       for(BuildGraph::TargetMap::const_iterator i=targets.begin(); i!=targets.end(); ++i)
+               if(i->second->get_package()==&package)
+                       if(InstalledFile *inst = dynamic_cast<InstalledFile *>(i->second))
+                               contents.push_back(inst->get_real_target());
+
+       AndroidManifestFile *manifest = new AndroidManifestFile(builder, *this);
+
+       list<Target *> resource_sources;
+       resource_sources.push_back(manifest);
+
+       const Toolchain &toolchain = builder.get_toolchain();
+       Tool &copy = 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<Target *> 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<Target *>::const_iterator i=contents.begin(); i!=contents.end(); ++i)
+       {
+               Target *staged = 0;
+               if(SharedLibrary *shlib = dynamic_cast<SharedLibrary *>(*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 (file)
index 0000000..8d02375
--- /dev/null
@@ -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 (file)
index 0000000..cca30b8
--- /dev/null
@@ -0,0 +1,84 @@
+#include <msp/fs/utils.h>
+#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<Target *> &sources, const string &)
+{
+       AndroidManifestFile *manifest = 0;
+       list<FileTarget *> resources;
+       for(list<Target *>::const_iterator i=sources.begin(); i!=sources.end(); ++i)
+       {
+               if(AndroidManifestFile *m = dynamic_cast<AndroidManifestFile *>(*i))
+                       manifest = m;
+               else if(FileTarget *f = dynamic_cast<FileTarget *>(*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<const AndroidResourceBundle &>(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<FS::Path> resource_dirs;
+       for(Target::Dependencies::const_iterator i=depends.begin(); i!=depends.end(); ++i)
+       {
+               FileTarget *file = dynamic_cast<FileTarget *>(*i);
+               Target *real = (*i)->get_real_target();
+
+               if(dynamic_cast<AndroidManifestFile *>(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<string> seen_dirs;
+       for(list<FS::Path>::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 (file)
index 0000000..1366087
--- /dev/null
@@ -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<Target *> &, const std::string &);
+       virtual Task *run(const Target &) const;
+};
+
+#endif
diff --git a/source/androidmanifestfile.cpp b/source/androidmanifestfile.cpp
new file mode 100644 (file)
index 0000000..55503de
--- /dev/null
@@ -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 (file)
index 0000000..e421adf
--- /dev/null
@@ -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 (file)
index 0000000..b0c7f8f
--- /dev/null
@@ -0,0 +1,58 @@
+#include <msp/io/file.h>
+#include <msp/io/print.h>
+#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<Target *> &, const string &)
+{
+       throw logic_error("not implemented");
+}
+
+Task *AndroidManifestGenerator::run(const Target &target) const
+{
+       const AndroidManifestFile &manifest = dynamic_cast<const AndroidManifestFile &>(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("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+       IO::print(out, "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"%s\">\n", comp.get_name());
+       out.write("\t<uses-sdk android:minSdkVersion=\"9\" />\n");
+       // TODO Make the icon name configurable
+       IO::print(out, "\t<application android:icon=\"@drawable/icon\" android:label=\"%s\" android:hasCode=\"false\">\n", pkg.get_label());
+       if(SharedLibrary *native_lib = manifest.get_native_library())
+       {
+               out.write("\t\t<activity android:name=\"android.app.NativeActivity\">\n");
+               IO::print(out, "\t\t\t<meta-data android:name=\"android.app.lib_name\" android:value=\"%s\" />\n", native_lib->get_libname());
+               out.write("\t\t\t<intent-filter>\n");
+               out.write("\t\t\t\t<action android:name=\"android.intent.action.MAIN\" />\n");
+               out.write("\t\t\t\t<category android:name=\"android.intent.category.LAUNCHER\" />\n");
+               out.write("\t\t\t</intent-filter>\n");
+               out.write("\t\t</activity>\n");
+       }
+       out.write("\t</application>\n");
+       out.write("</manifest>\n");
+
+       status = Task::SUCCESS;
+}
diff --git a/source/androidmanifestgenerator.h b/source/androidmanifestgenerator.h
new file mode 100644 (file)
index 0000000..f0d4269
--- /dev/null
@@ -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<Target *> &, const std::string &);
+       virtual Task *run(const Target &) const;
+};
+
+#endif
diff --git a/source/androidpackagefile.cpp b/source/androidpackagefile.cpp
new file mode 100644 (file)
index 0000000..1400d0f
--- /dev/null
@@ -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<FileTarget *> &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<FileTarget *>::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 (file)
index 0000000..4f1c692
--- /dev/null
@@ -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<FileTarget *> &);
+
+       virtual const char *get_type() const { return "AndroidPackageFile"; }
+};
+
+#endif
diff --git a/source/androidresourcebundle.cpp b/source/androidresourcebundle.cpp
new file mode 100644 (file)
index 0000000..d327126
--- /dev/null
@@ -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<FileTarget *> &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<FileTarget *>::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 (file)
index 0000000..253b44b
--- /dev/null
@@ -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<FileTarget *> &);
+
+       virtual const char *get_type() const { return "AndroidResourceBundle"; }
+};
+
+#endif
diff --git a/source/androidresourcefile.cpp b/source/androidresourcefile.cpp
new file mode 100644 (file)
index 0000000..d88f786
--- /dev/null
@@ -0,0 +1,19 @@
+#include <msp/fs/utils.h>
+#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 (file)
index 0000000..5f3cd3a
--- /dev/null
@@ -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
index 13ebc2530556413b97afd0fc57ab31ccc35eacd8..122dc23392f43017643be5c632454aaf14f45284 100644 (file)
 #include <cstdlib>
 #include <msp/fs/dir.h>
+#include <msp/strings/format.h>
 #include <msp/strings/lexicalcast.h>
 #include <msp/strings/utils.h>
+#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<string> version_parts = split(version_str, '.');
+       unsigned version = lexical_cast<unsigned>(version_parts[0])<<16;
+       if(version_parts.size()>1)
+               version += lexical_cast<unsigned>(version_parts[1])<<8;
+       if(version_parts.size()>2)
+               version += lexical_cast<unsigned>(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<string> platforms = list_filtered(platforms_dir, "^android-[1-9][0-9]*$");
+
+       for(list<string>::const_iterator i=platforms.begin(); i!=platforms.end(); ++i)
+               supported_api_levels.insert(lexical_cast<unsigned>(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<string> tool_versions = list_files(bt_dir);
+
+       string use_tools;
+       unsigned latest_version = 0;
+       for(list<string>::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<string> toolchains = list_files(toolchains_dir);
-
        string prefix = architecture.get_cross_prefix()+"-";
+       list<string> toolchains = list_filtered(toolchains_dir, "^"+prefix);
+
        string use_toolchain;
        unsigned latest_version = 0;
        for(list<string>::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; (j<version_str.size() && !isdigit(version_str[j])); ++j) ;
+                       compiler = version_str.substr(0, j);
+                       version_str = version_str.substr(j);
+               }
+
+               if(compiler!="gcc")
+                       continue;
+
+               unsigned version = parse_version(version_str);
+               if(version>latest_version)
                {
-                       string version_str = i->substr(prefix.size());
-                       string compiler = "gcc";
-                       if(!isdigit(version_str[0]))
-                       {
-                               unsigned j;
-                               for(j=1; (j<version_str.size() && !isdigit(version_str[j])); ++j) ;
-                               compiler = version_str.substr(0, j);
-                               version_str = version_str.substr(j);
-                       }
-
-                       if(compiler!="gcc")
-                               continue;
-
-                       vector<string> version_parts = split(version_str, '.');
-                       unsigned version = (lexical_cast<unsigned>(version_parts[0])<<8);
-                       if(version_parts.size()>1)
-                               version += lexical_cast<unsigned>(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<string> 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<string>::const_iterator i=platforms.begin(); i!=platforms.end(); ++i)
-               if(!i->compare(0, 8, "android-"))
-               {
-                       unsigned level = lexical_cast<unsigned>(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<string> platform_archs = list_filtered(platform_archs_dir, "^arch-");
        for(list<string>::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<unsigned> &sdk_api_levels = sdk.get_supported_api_levels();
+       const set<unsigned> &ndk_api_levels = ndk.get_supported_api_levels();
+       unsigned highest_common = 0;
+       for(set<unsigned>::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));
 }
index 3ccedcbdb9ef44ac1417abd0de8a6e8bdb9ae496..16f157d54dadbb2770572c80b90e83b99c8be29f 100644 (file)
@@ -1,18 +1,54 @@
 #ifndef ANDROIDTOOLS_H_
 #define ANDROIDTOOLS_H_
 
+#include <set>
 #include <msp/fs/path.h>
 #include "toolchain.h"
 
 class Architecture;
 class Builder;
 
-class AndroidNdk
+class AndroidDevKit
 {
-private:
+protected:
        Builder &builder;
+       Msp::FS::Path root;
+       std::set<unsigned> supported_api_levels;
+
+       AndroidDevKit(Builder &, const std::string &);
+       ~AndroidDevKit() { }
+
+public:
+       const Msp::FS::Path &get_root_dir() const { return root; }
+       const std::set<unsigned> &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 (file)
index 0000000..9cd1a16
--- /dev/null
@@ -0,0 +1,79 @@
+#include <msp/fs/utils.h>
+#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<Target *> &sources, const string &)
+{
+       AndroidResourceBundle *resource_bundle = 0;
+       list<FileTarget *> other_files;
+       for(list<Target *>::const_iterator i=sources.begin(); i!=sources.end(); ++i)
+       {
+               if(AndroidResourceBundle *r = dynamic_cast<AndroidResourceBundle *>(*i))
+                       resource_bundle = r;
+               else if(FileTarget *f = dynamic_cast<FileTarget *>(*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<const AndroidPackageFile &>(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<FS::Path> files;
+       for(Target::Dependencies::const_iterator i=depends.begin(); i!=depends.end(); ++i)
+       {
+               FileTarget *file = dynamic_cast<FileTarget *>(*i);
+               Target *real = (*i)->get_real_target();
+
+               if(dynamic_cast<AndroidResourceBundle *>(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<FS::Path>::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 (file)
index 0000000..7b73aa7
--- /dev/null
@@ -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<Target *> &, 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 (file)
index 0000000..b8c9a50
--- /dev/null
@@ -0,0 +1,43 @@
+#include <cstdlib>
+#include <msp/fs/utils.h>
+#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<Target *> &, const string &)
+{
+       throw logic_error("not implemented");
+}
+
+Task *JarSigner::run(const Target &tgt) const
+{
+       const FileTarget &file = dynamic_cast<const FileTarget &>(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 (file)
index 0000000..cc773ea
--- /dev/null
@@ -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<Target *> &, const std::string &);
+       virtual Task *run(const Target &) const;
+};
+
+#endif
index 274230076599f9a78e890514126dbdedc9e5a3ce..942e5a3bcca4c6250a2564a6ca9aedf00a623e83 100644 (file)
@@ -5,6 +5,7 @@
 #include <msp/io/print.h>
 #include <msp/strings/lexicalcast.h>
 #include <msp/strings/utils.h>
+#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<AndroidApplicationComponent>);
        add("build_info",  &Loader::build_info);
        add("datapack",    &Loader::component<DataPackComponent>);
        add("description", &SourcePackage::description);