From 52b5dfd2efbc576a3073fe335e7694f5d4d4c143 Mon Sep 17 00:00:00 2001 From: atkawa7 Date: Mon, 6 Apr 2020 23:36:17 +0200 Subject: [PATCH] Android Support: Exporting to Android Archive (AAR) (#10271) * added android triplets * added android support to vcpkg * added export directories to git ignore * fix libraries naming * added vckpg sources to visual studio project files * rename file location * issue with std::string fs:path copy initialization * format path on VStudio * fix checks format cannot work on fs::path * support header only libraries * support using architecture instead of triplets * added prefab support * added debug logs and prefab debug flag * added support for empty packages i.e openssl --- .gitignore | 6 + docs/specifications/prefab.md | 124 +++++ toolsrc/include/vcpkg/export.prefab.h | 82 +++ toolsrc/include/vcpkg/triplet.h | 5 + toolsrc/include/vcpkg/vcpkgpaths.h | 2 + toolsrc/src/vcpkg/export.cpp | 51 +- toolsrc/src/vcpkg/export.prefab.cpp | 695 ++++++++++++++++++++++++++ toolsrc/src/vcpkg/triplet.cpp | 14 +- toolsrc/vcpkglib/vcpkglib.vcxproj | 2 + 9 files changed, 972 insertions(+), 9 deletions(-) create mode 100644 docs/specifications/prefab.md create mode 100644 toolsrc/include/vcpkg/export.prefab.h create mode 100644 toolsrc/src/vcpkg/export.prefab.cpp diff --git a/.gitignore b/.gitignore index 86fd538f6..eba62b6f6 100644 --- a/.gitignore +++ b/.gitignore @@ -304,6 +304,10 @@ __pycache__/ !triplets/community/x86-windows-static.cmake !triplets/community/x86-windows-static-md.cmake !triplets/community/x64-osx-dynamic.cmake +!triplets/community/x64-android.cmake +!triplets/community/x86-android.cmake +!triplets/community/arm-android.cmake +!triplets/community/arm64-android.cmake !triplets/arm-uwp.cmake !triplets/x64-uwp.cmake !triplets/x64-windows.cmake @@ -320,3 +324,5 @@ __pycache__/ # vcpkg - End ############################################################ archives +.DS_Store +prefab/ \ No newline at end of file diff --git a/docs/specifications/prefab.md b/docs/specifications/prefab.md new file mode 100644 index 000000000..bb0479c4c --- /dev/null +++ b/docs/specifications/prefab.md @@ -0,0 +1,124 @@ +## Exporting to Android Archives (AAR files) + +Vcpkg current supports exporting to android archive files([AAR files](https://developer.android.com/studio/projects/android-library)). Once the archive is created it can imported in Android Studio as a native dependent. The archive is automatically consumed using [android studio's prefab tool](https://github.com/google/prefab). For more information on Prefab checkout the following article ["Native Dependencies in Android Studio 4.0"](https://android-developers.googleblog.com/2020/02/native-dependencies-in-android-studio-40.html) and the documentation on how to use prefab on [https://google.github.io/prefab/](https://google.github.io/prefab). + +#### To support export to android the following tools should be available; + +- `maven ` +- `ndk ` +- `7zip ` or `zip ` + +**Android triplets that support the following architectures arm64-v8a, armeabi-v7a, x86_64 x86 must be present** + +#### An example of a triplet configuration targeting android would be + +```cmake +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE dynamic) +set(VCPKG_CMAKE_SYSTEM_NAME Android) +``` + +The following table outlines the mapping from vcpkg architectures to android architectures + +|vcpkg architecture | android architecture | +|-------------------|----------------------| +|arm64 | arm64-v8a | +|arm | armeabi-v7a | +|x64 | x86_64 | +|x86 | x86 | + +**Please note the four architectures are required. If any is missing the export will fail** +**To export the following environment `ANDROID_NDK_HOME` variable is required for exporting** + +#### Example exporting [jsoncpp] +The `--prefab-maven` flag is option. Only call it when you have maven installed +``` +./vcpkg export --triplet x64-android jsoncpp --prefab --prefab-maven +``` + +``` +The following packages are already built and will be exported: + jsoncpp:x86-android +Exporting package jsoncpp... +[INFO] Scanning for projects... +[INFO] +[INFO] ------------------< org.apache.maven:standalone-pom >------------------- +[INFO] Building Maven Stub Project (No POM) 1 +[INFO] --------------------------------[ pom ]--------------------------------- +[INFO] +[INFO] --- maven-install-plugin:2.4:install-file (default-cli) @ standalone-pom --- +[INFO] Installing/prefab/jsoncpp/jsoncpp-1.9.2.aar to /.m2/repository/com/vcpkg/ndk/support/jsoncpp/1.9.2/jsoncpp-1.9.2.aar +[INFO] Installing /prefab/jsoncpp/pom.xml to /.m2/repository/com/vcpkg/ndk/support/jsoncpp/1.9.2/jsoncpp-1.9.2.pom +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 0.301 s +[INFO] Finished at: 2020-03-01T10:18:15Z +[INFO] ------------------------------------------------------------------------ +In app/build.gradle + + com.vcpkg.ndk.support:jsoncpp:1.9.2 + +And cmake flags + + externalNativeBuild { + cmake { + arguments '-DANDROID_STL=c++_shared' + cppFlags "-std=c++17" + } + } + +In gradle.properties + + android.enablePrefab=true + android.enableParallelJsonGen=false + android.prefabVersion=${prefab.version} + +Successfuly exported jsoncpp. Checkout /prefab/jsoncpp/aar +``` + +#### The output directory after export +``` +prefab +└── jsoncpp + ├── aar + │   ├── AndroidManifest.xml + │   ├── META-INF + │   │   └── LICENCE + │   └── prefab + │   ├── modules + │   │   └── jsoncpp + │   │   ├── include + │   │   │   └── json + │   │   │   ├── allocator.h + │   │   │   ├── assertions.h + │   │   │   ├── autolink.h + │   │   │   ├── config.h + │   │   │   ├── forwards.h + │   │   │   ├── json.h + │   │   │   ├── json_features.h + │   │   │   ├── reader.h + │   │   │   ├── value.h + │   │   │   ├── version.h + │   │   │   └── writer.h + │   │   ├── libs + │   │   │   ├── android.arm64-v8a + │   │   │   │   ├── abi.json + │   │   │   │   └── libjsoncpp.so + │   │   │   ├── android.armeabi-v7a + │   │   │   │   ├── abi.json + │   │   │   │   └── libjsoncpp.so + │   │   │   ├── android.x86 + │   │   │   │   ├── abi.json + │   │   │   │   └── libjsoncpp.so + │   │   │   └── android.x86_64 + │   │   │   ├── abi.json + │   │   │   └── libjsoncpp.so + │   │   └── module.json + │   └── prefab.json + ├── jsoncpp-1.9.2.aar + └── pom.xml + +13 directories, 25 files +``` diff --git a/toolsrc/include/vcpkg/export.prefab.h b/toolsrc/include/vcpkg/export.prefab.h new file mode 100644 index 000000000..56a5ba371 --- /dev/null +++ b/toolsrc/include/vcpkg/export.prefab.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include + + +#include + +namespace vcpkg::Export::Prefab +{ + constexpr int kFragmentSize = 3; + + struct Options + { + Optional maybe_group_id; + Optional maybe_artifact_id; + Optional maybe_version; + Optional maybe_min_sdk; + Optional maybe_target_sdk; + bool enable_maven; + bool enable_debug; + }; + struct NdkVersion + { + NdkVersion(int _major, int _minor, int _patch) : m_major{_major}, + m_minor{_minor}, + m_patch{_patch}{ + } + int major() { return this->m_major; } + int minor() { return this->m_minor; } + int patch() { return this->m_patch; } + std::string to_string(); + void to_string(std::string& out); + + private: + int m_major; + int m_minor; + int m_patch; + }; + + struct ABIMetadata + { + std::string abi; + int api; + int ndk; + std::string stl; + std::string to_string(); + }; + + struct PlatformModuleMetadata + { + std::vector export_libraries; + std::string library_name; + std::string to_json(); + }; + + struct ModuleMetadata + { + std::vector export_libraries; + std::string library_name; + PlatformModuleMetadata android; + std::string to_json(); + }; + + struct PackageMetadata + { + std::string name; + int schema; + std::vector dependencies; + std::string version; + std::string to_json(); + }; + + + + void do_export(const std::vector& export_plan, + const VcpkgPaths& paths, + const Options& prefab_options, const Triplet& triplet); + Optional find_ndk_version(const std::string &content); + Optional to_version(const std::string &version); +} diff --git a/toolsrc/include/vcpkg/triplet.h b/toolsrc/include/vcpkg/triplet.h index d836dd230..92ea10175 100644 --- a/toolsrc/include/vcpkg/triplet.h +++ b/toolsrc/include/vcpkg/triplet.h @@ -23,6 +23,11 @@ namespace vcpkg static const Triplet X64_UWP; static const Triplet ARM_UWP; static const Triplet ARM64_UWP; + + static const Triplet ARM_ANDROID; + static const Triplet ARM64_ANDROID; + static const Triplet X86_ANDROID; + static const Triplet X64_ANDROID; const std::string& canonical_name() const; const std::string& to_string() const; diff --git a/toolsrc/include/vcpkg/vcpkgpaths.h b/toolsrc/include/vcpkg/vcpkgpaths.h index 63d19af2c..31d8374f1 100644 --- a/toolsrc/include/vcpkg/vcpkgpaths.h +++ b/toolsrc/include/vcpkg/vcpkgpaths.h @@ -14,6 +14,8 @@ namespace vcpkg namespace Tools { static const std::string SEVEN_ZIP = "7zip"; + static const std::string SEVEN_ZIP_ALT = "7z"; + static const std::string MAVEN = "mvn"; static const std::string CMAKE = "cmake"; static const std::string GIT = "git"; static const std::string NINJA = "ninja"; diff --git a/toolsrc/src/vcpkg/export.cpp b/toolsrc/src/vcpkg/export.cpp index a9dd3bba2..f6226ead6 100644 --- a/toolsrc/src/vcpkg/export.cpp +++ b/toolsrc/src/vcpkg/export.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -196,6 +197,7 @@ namespace vcpkg::Export { constexpr const ArchiveFormat ZIP(ArchiveFormat::BackingEnum::ZIP, "zip", "zip"); constexpr const ArchiveFormat SEVEN_ZIP(ArchiveFormat::BackingEnum::SEVEN_ZIP, "7z", "7zip"); + constexpr const ArchiveFormat AAR(ArchiveFormat::BackingEnum::ZIP, "aar", "zip"); } static fs::path do_archive_export(const VcpkgPaths& paths, @@ -263,6 +265,7 @@ namespace vcpkg::Export bool zip = false; bool seven_zip = false; bool chocolatey = false; + bool prefab = false; bool all_installed = false; Optional maybe_output; @@ -271,6 +274,7 @@ namespace vcpkg::Export Optional maybe_nuget_version; IFW::Options ifw_options; + Prefab::Options prefab_options; Chocolatey::Options chocolatey_options; std::vector specs; }; @@ -293,8 +297,20 @@ namespace vcpkg::Export static constexpr StringLiteral OPTION_CHOCOLATEY_MAINTAINER = "--x-maintainer"; static constexpr StringLiteral OPTION_CHOCOLATEY_VERSION_SUFFIX = "--x-version-suffix"; static constexpr StringLiteral OPTION_ALL_INSTALLED = "--x-all-installed"; + + static constexpr StringLiteral OPTION_PREFAB = "--prefab"; + static constexpr StringLiteral OPTION_PREFAB_GROUP_ID = "--prefab-group-id"; + static constexpr StringLiteral OPTION_PREFAB_ARTIFACT_ID = "--prefab-artifact-id"; + static constexpr StringLiteral OPTION_PREFAB_VERSION = "--prefab-version"; + static constexpr StringLiteral OPTION_PREFAB_SDK_MIN_VERSION = "--prefab-min-sdk"; + static constexpr StringLiteral OPTION_PREFAB_SDK_TARGET_VERSION = "--prefab-target-sdk"; + static constexpr StringLiteral OPTION_PREFAB_ENABLE_MAVEN = "--prefab-maven"; + static constexpr StringLiteral OPTION_PREFAB_ENABLE_DEBUG = "--prefab-debug"; + - static constexpr std::array EXPORT_SWITCHES = {{ + + + static constexpr std::array EXPORT_SWITCHES = {{ {OPTION_DRY_RUN, "Do not actually export"}, {OPTION_RAW, "Export to an uncompressed directory"}, {OPTION_NUGET, "Export a NuGet package"}, @@ -302,10 +318,13 @@ namespace vcpkg::Export {OPTION_ZIP, "Export to a zip file"}, {OPTION_SEVEN_ZIP, "Export to a 7zip (.7z) file"}, {OPTION_CHOCOLATEY, "Export a Chocolatey package (experimental feature)"}, + {OPTION_PREFAB, "Export to Prefab format"}, + {OPTION_PREFAB_ENABLE_MAVEN, "Enable maven"}, + {OPTION_PREFAB_ENABLE_DEBUG, "Enable prefab debug"}, {OPTION_ALL_INSTALLED, "Export all installed packages"}, }}; - static constexpr std::array EXPORT_SETTINGS = {{ + static constexpr std::array EXPORT_SETTINGS = {{ {OPTION_OUTPUT, "Specify the output name (used to construct filename)"}, {OPTION_NUGET_ID, "Specify the id for the exported NuGet package (overrides --output)"}, {OPTION_NUGET_VERSION, "Specify the version for the exported NuGet package"}, @@ -318,6 +337,11 @@ namespace vcpkg::Export "Specify the maintainer for the exported Chocolatey package (experimental feature)"}, {OPTION_CHOCOLATEY_VERSION_SUFFIX, "Specify the version suffix to add for the exported Chocolatey package (experimental feature)"}, + {OPTION_PREFAB_GROUP_ID, "GroupId uniquely identifies your project according maven specifications"}, + {OPTION_PREFAB_ARTIFACT_ID, "Artifact Id is the name of the project according maven specifications"}, + {OPTION_PREFAB_VERSION, "Version is the name of the project according maven specifications"}, + {OPTION_PREFAB_SDK_MIN_VERSION, "Android minimum supported sdk version"}, + {OPTION_PREFAB_SDK_TARGET_VERSION, "Android target sdk version"}, }}; const CommandStructure COMMAND_STRUCTURE = { @@ -343,8 +367,11 @@ namespace vcpkg::Export ret.zip = options.switches.find(OPTION_ZIP) != options.switches.cend(); ret.seven_zip = options.switches.find(OPTION_SEVEN_ZIP) != options.switches.cend(); ret.chocolatey = options.switches.find(OPTION_CHOCOLATEY) != options.switches.cend(); - ret.all_installed = options.switches.find(OPTION_ALL_INSTALLED) != options.switches.end(); + ret.prefab = options.switches.find(OPTION_PREFAB) != options.switches.cend(); + ret.prefab_options.enable_maven = options.switches.find(OPTION_PREFAB_ENABLE_MAVEN) != options.switches.cend(); + ret.prefab_options.enable_debug = options.switches.find(OPTION_PREFAB_ENABLE_DEBUG) != options.switches.cend(); ret.maybe_output = maybe_lookup(options.settings, OPTION_OUTPUT); + ret.all_installed = options.switches.find(OPTION_ALL_INSTALLED) != options.switches.end(); if (ret.all_installed) { @@ -363,10 +390,10 @@ namespace vcpkg::Export }); } - if (!ret.raw && !ret.nuget && !ret.ifw && !ret.zip && !ret.seven_zip && !ret.dry_run && !ret.chocolatey) + if (!ret.raw && !ret.nuget && !ret.ifw && !ret.zip && !ret.seven_zip && !ret.dry_run && !ret.chocolatey && !ret.prefab) { System::print2(System::Color::error, - "Must provide at least one export type: --raw --nuget --ifw --zip --7zip --chocolatey\n"); + "Must provide at least one export type: --raw --nuget --ifw --zip --7zip --chocolatey --prefab\n"); System::print2(COMMAND_STRUCTURE.example_text); Checks::exit_fail(VCPKG_LINE_INFO); } @@ -417,6 +444,16 @@ namespace vcpkg::Export {OPTION_IFW_CONFIG_FILE_PATH, ret.ifw_options.maybe_config_file_path}, {OPTION_IFW_INSTALLER_FILE_PATH, ret.ifw_options.maybe_installer_file_path}, }); + + options_implies(OPTION_PREFAB, + ret.prefab, + { + {OPTION_PREFAB_ARTIFACT_ID, ret.prefab_options.maybe_artifact_id}, + {OPTION_PREFAB_GROUP_ID, ret.prefab_options.maybe_group_id}, + {OPTION_PREFAB_SDK_MIN_VERSION, ret.prefab_options.maybe_min_sdk}, + {OPTION_PREFAB_SDK_TARGET_VERSION, ret.prefab_options.maybe_target_sdk}, + {OPTION_PREFAB_VERSION, ret.prefab_options.maybe_version}, + }); options_implies(OPTION_CHOCOLATEY, ret.chocolatey, @@ -605,6 +642,10 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console Chocolatey::do_export(export_plan, paths, opts.chocolatey_options); } + if(opts.prefab){ + Prefab::do_export(export_plan, paths, opts.prefab_options, default_triplet); + } + Checks::exit_success(VCPKG_LINE_INFO); } } diff --git a/toolsrc/src/vcpkg/export.prefab.cpp b/toolsrc/src/vcpkg/export.prefab.cpp new file mode 100644 index 000000000..57b7ea99d --- /dev/null +++ b/toolsrc/src/vcpkg/export.prefab.cpp @@ -0,0 +1,695 @@ +#include "pch.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace vcpkg::Export::Prefab +{ + using Dependencies::ExportPlanAction; + using Dependencies::ExportPlanType; + using Install::InstallDir; + using System::CPUArchitecture; + + + + std::vector find_modules(const VcpkgPaths& system, const fs::path& root, const std::string& ext) + { + std::vector paths; + Files::Filesystem& utils = system.get_filesystem(); + std::error_code error_code; + if (!utils.exists(root, error_code) || !utils.is_directory(root)) return paths; + + fs::stdfs::recursive_directory_iterator it(root); + fs::stdfs::recursive_directory_iterator endit; + + while (it != endit) + { + if (utils.is_regular_file(*it) && it->path().extension() == ext) + { + paths.push_back(it->path().filename()); + } + ++it; + } + return paths; + } + + std::string NdkVersion::to_string() + { + std::string ret; + this->to_string(ret); + return ret; + } + void NdkVersion::to_string(std::string& out) + { + out.append("NdkVersion{major=") + .append(std::to_string(major())) + .append(",minor=") + .append(std::to_string(minor())) + .append(",patch=") + .append(std::to_string(patch())) + .append("}"); + } + + std::string jsonify(const std::vector& dependencies) + { + std::vector deps; + for (const auto& dep : dependencies) + { + deps.push_back("\"" + dep + "\""); + } + return Strings::join(",", deps); + } + + std::string null_if_empty(const std::string& str) + { + std::string copy = str; + if (copy.size() == 0) + { + copy = "null"; + } + else + { + copy = "\"" + copy + "\""; + } + return copy; + } + + std::string null_if_empty_array(const std::string& str) + { + std::string copy = str; + if (copy.size() == 0) + { + copy = "null"; + } + else + { + copy = "[" + copy + "]"; + } + return copy; + } + + std::string ABIMetadata::to_string() + { + std::string TEMPLATE = R"({ + "abi":"@ABI@", + "api":@API@, + "ndk":@NDK@, + "stl":"@STL@" +})"; + std::string json = Strings::replace_all(std::move(TEMPLATE), "@ABI@", abi); + json = Strings::replace_all(std::move(json), "@API@", std::to_string(api)); + json = Strings::replace_all(std::move(json), "@NDK@", std::to_string(ndk)); + json = Strings::replace_all(std::move(json), "@STL@", stl); + return json; + } + + std::string PlatformModuleMetadata::to_json() + { + std::string TEMPLATE = R"({ + "export_libraries": @LIBRARIES@, + "library_name": @LIBRARY_NAME@ +})"; + + std::string json = Strings::replace_all(std::move(TEMPLATE), "@LIBRARY_NAME@", null_if_empty(library_name)); + json = Strings::replace_all(std::move(json), "@LIBRARIES@", null_if_empty_array(jsonify(export_libraries))); + return json; + } + + std::string ModuleMetadata::to_json() + { + std::string TEMPLATE = R"({ + "export_libraries": [@LIBRARIES@], + "library_name":@LIBRARY_NAME@, + "android": @ANDROID_METADATA@ +})"; + + std::string json = Strings::replace_all(std::move(TEMPLATE), "@LIBRARY_NAME@", null_if_empty(library_name)); + json = Strings::replace_all(std::move(json), "@LIBRARIES@", jsonify(export_libraries)); + json = Strings::replace_all(std::move(json), "@ANDROID_METADATA@", android.to_json()); + return json; + } + + std::string PackageMetadata::to_json() + { + std::string deps = jsonify(dependencies); + + std::string TEMPLATE = R"({ + "name":"@PACKAGE_NAME@", + "schema_version": @PACKAGE_SCHEMA@, + "dependencies":[@PACKAGE_DEPS@], + "version":"@PACKAGE_VERSION@" +})"; + std::string json = Strings::replace_all(std::move(TEMPLATE), "@PACKAGE_NAME@", name); + json = Strings::replace_all(std::move(json), "@PACKAGE_SCHEMA@", std::to_string(schema)); + json = Strings::replace_all(std::move(json), "@PACKAGE_DEPS@", deps); + json = Strings::replace_all(std::move(json), "@PACKAGE_VERSION@", version); + return json; + } + + Optional find_ndk_version(const std::string& content) + { + std::smatch pkg_match; + std::regex pkg_regex(R"(Pkg\.Revision\s*=\s*(\d+)(\.\d+)(\.\d+)\s*)"); + + if (std::regex_search(content, pkg_match, pkg_regex)) + { + for (const auto& p : pkg_match) + { + std::string delimiter = "="; + std::string s = p.str(); + auto it = s.find(delimiter); + if (it != std::string::npos) + { + std::string token = (s.substr(s.find(delimiter) + 1, s.size())); + return Strings::trim(std::move(token)); + } + } + } + return {}; + } + + Optional to_version(const std::string& version) + { + if (version.size() > 100) return {}; + size_t last = 0; + size_t next = 0; + std::vector fragments(0); + + while ((next = version.find(".", last)) != std::string::npos) + { + fragments.push_back(std::stoi(version.substr(last, next - last))); + last = next + 1; + } + fragments.push_back(std::stoi(version.substr(last))); + if (fragments.size() == kFragmentSize) + { + return NdkVersion(fragments[0], fragments[1], fragments[2]); + } + return {}; + } + + static void compress_directory(const VcpkgPaths& paths, const fs::path& source, const fs::path& destination) + { + auto& fs = paths.get_filesystem(); + + std::error_code ec; + + fs.remove(destination, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !fs.exists(destination), "Could not remove file: %s", destination.u8string()); +#if defined(_WIN32) + auto&& seven_zip_exe = paths.get_tool_exe(Tools::SEVEN_ZIP); + + System::cmd_execute_and_capture_output( + Strings::format( + R"("%s" a "%s" "%s\*")", seven_zip_exe.u8string(), destination.u8string(), source.u8string()), + System::get_clean_environment()); +#else + System::cmd_execute_clean( + Strings::format(R"(cd '%s' && zip --quiet -r '%s' *)", source.u8string(), destination.u8string())); +#endif + } + + void maven_install(const fs::path& aar, const fs::path& pom, const Options& prefab_options) + { + if(prefab_options.enable_debug){ + System::print2("\n[DEBUG] Installing POM and AAR file to ~/.m2\n\n"); + } + const char* cmd_line_format = prefab_options.enable_debug ? R"("%s" "install:install-file" "-Dfile=%s" "-DpomFile=%s")" + : R"("%s" "-q" "install:install-file" "-Dfile=%s" "-DpomFile=%s")"; + + const auto cmd_line = Strings::format(cmd_line_format, + Tools::MAVEN, + aar.u8string(), + pom.u8string()); + const int exit_code = System::cmd_execute_clean(cmd_line); + Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Error: %s installing maven file", aar.generic_u8string()); + } + + Build::PreBuildInfo build_info_from_triplet(const VcpkgPaths& paths, + const std::unique_ptr& provider, + const Triplet& triplet) + { + provider->load_generic_triplet_vars(triplet); + const Build::PreBuildInfo pre_build_info( + paths, triplet, provider->get_generic_triplet_vars(triplet).value_or_exit(VCPKG_LINE_INFO)); + return pre_build_info; + } + + bool is_supported(const Build::PreBuildInfo& info) + { + return Strings::case_insensitive_ascii_equals(info.cmake_system_name, "android"); + } + + void do_export(const std::vector& export_plan, + const VcpkgPaths& paths, + const Options& prefab_options, + const Triplet& default_triplet) + { + auto provider = CMakeVars::make_triplet_cmake_var_provider(paths); + + auto build_info = build_info_from_triplet(paths, provider, default_triplet); + Checks::check_exit(VCPKG_LINE_INFO, is_supported(build_info), "Currenty supported on android triplets"); + + std::vector available_triplets = paths.get_available_triplets(); + + std::unordered_map required_archs = { + {CPUArchitecture::ARM, "armeabi-v7a"}, + {CPUArchitecture::ARM64, "arm64-v8a"}, + {CPUArchitecture::X86, "x86"}, + {CPUArchitecture::X64, "x86_64"}}; + + std::unordered_map cpu_architecture_api_map = {{CPUArchitecture::ARM64, 21}, + {CPUArchitecture::ARM, 16}, + {CPUArchitecture::X64, 21}, + {CPUArchitecture::X86, 16}}; + + + std::vector triplets; + std::unordered_map triplet_abi_map; + std::unordered_map triplet_api_map; + + for (auto& triplet_file : available_triplets){ + if (triplet_file.name.size() > 0){ + Triplet triplet = Triplet::from_canonical_name(std::move(triplet_file.name)); + auto build_info = build_info_from_triplet(paths, provider, triplet); + if (is_supported(build_info)){ + auto cpu_architecture =System::to_cpu_architecture(build_info.target_architecture).value_or_exit(VCPKG_LINE_INFO); + auto required_arch = required_archs.find(cpu_architecture); + if (required_arch != required_archs.end()){ + triplets.push_back(triplet); + triplet_abi_map[triplet] = required_archs[cpu_architecture]; + triplet_api_map[triplet] = cpu_architecture_api_map[cpu_architecture]; + required_archs.erase(required_arch); + } + } + } + } + + + Checks::check_exit( + VCPKG_LINE_INFO, required_archs.empty(), "Export requires the following architectures arm64-v8a, armeabi-v7a, x86_64, x86 to be present"); + + Optional android_ndk_home = System::get_environment_variable("ANDROID_NDK_HOME"); + + Checks::check_exit( + VCPKG_LINE_INFO, android_ndk_home.has_value(), "Error: ANDROID_NDK_HOME environment missing"); + + Files::Filesystem& utils = paths.get_filesystem(); + + const fs::path ndk_location = android_ndk_home.value_or_exit(VCPKG_LINE_INFO); + + Checks::check_exit(VCPKG_LINE_INFO, + utils.exists(ndk_location), + "Error: ANDROID_NDK_HOME Directory does not exists %s", + ndk_location.generic_u8string()); + const fs::path source_properties_location = ndk_location / "source.properties"; + + Checks::check_exit(VCPKG_LINE_INFO, + utils.exists(ndk_location), + "Error: source.properties missing in ANDROID_NDK_HOME directory %s", + source_properties_location.generic_u8string()); + + std::string content = utils.read_contents(source_properties_location, VCPKG_LINE_INFO); + + Optional version_opt = find_ndk_version(content); + + Checks::check_exit(VCPKG_LINE_INFO, + version_opt.has_value(), + "Error: NDK version missing %s", + source_properties_location.generic_u8string()); + + NdkVersion version = to_version(version_opt.value_or_exit(VCPKG_LINE_INFO)).value_or_exit(VCPKG_LINE_INFO); + + const fs::path vcpkg_root_path = paths.root; + const fs::path raw_exported_dir_path = vcpkg_root_path / "prefab"; + + utils.remove_all(raw_exported_dir_path, VCPKG_LINE_INFO); + + /* + prefab + └── + ├── aar + │   ├── AndroidManifest.xml + │   ├── META-INF + │   │   └── LICENCE + │   └── prefab + │   ├── modules + │   │   └── + │   │   ├── include + │   │   ├── libs + │   │   │   ├── android.arm64-v8a + │   │   │   │   ├── abi.json + │   │   │   │   └── lib.so + │   │   │   ├── android.armeabi-v7a + │   │   │   │   ├── abi.json + │   │   │   │   └── lib.so + │   │   │   ├── android.x86 + │   │   │   │   ├── abi.json + │   │   │   │   └── lib.so + │   │   │   └── android.x86_64 + │   │   │   ├── abi.json + │   │   │   └── lib.so + │   │   └── module.json + │   └── prefab.json + ├── -.aar + └── pom.xml + */ + + std::unordered_map version_map; + + std::error_code error_code; + + std::unordered_map> empty_package_dependencies; + + // + + for (const auto& action : export_plan) + { + + const std::string name = action.spec.name(); + auto dependencies = action.dependencies(default_triplet); + + const auto build_info = Build::read_build_info(utils, paths.build_info_file_path(action.spec)); + const bool is_empty_package = build_info.policies.is_enabled(Build::BuildPolicy::EMPTY_PACKAGE); + + + if(is_empty_package){ + empty_package_dependencies[name] = std::set(); + for(auto dependency : dependencies){ + if(empty_package_dependencies.find(dependency.name()) != empty_package_dependencies.end()){ + auto& child_deps = empty_package_dependencies[name]; + auto& parent_deps = empty_package_dependencies[dependency.name()]; + for(auto parent_dep: parent_deps){ + child_deps.insert(parent_dep); + } + } + else { + empty_package_dependencies[name].insert(dependency); + } + } + continue; + } + + const fs::path per_package_dir_path = raw_exported_dir_path / name; + + const auto& binary_paragraph = action.core_paragraph().value_or_exit(VCPKG_LINE_INFO); + const std::string norm_version = binary_paragraph.version; + + version_map[name] = norm_version; + + System::print2("\nExporting package ", name, "...\n"); + + fs::path package_directory = per_package_dir_path / "aar"; + fs::path prefab_directory = package_directory / "prefab"; + fs::path modules_directory = prefab_directory / "modules"; + + utils.create_directories(modules_directory, error_code); + + std::string artifact_id = prefab_options.maybe_artifact_id.value_or(name); + std::string group_id = prefab_options.maybe_group_id.value_or("com.vcpkg.ndk.support"); + std::string sdk_min_version = prefab_options.maybe_min_sdk.value_or("16"); + std::string sdk_target_version = prefab_options.maybe_target_sdk.value_or("29"); + + std::string MANIFEST_TEMPLATE = + R"( + +)"; + std::string manifest = Strings::replace_all(std::move(MANIFEST_TEMPLATE), "@GROUP_ID@", group_id); + manifest = Strings::replace_all(std::move(manifest), "@ARTIFACT_ID@", artifact_id); + manifest = Strings::replace_all(std::move(manifest), "@MIN_SDK_VERSION@", sdk_min_version); + manifest = Strings::replace_all(std::move(manifest), "@SDK_TARGET_VERSION@", sdk_target_version); + + fs::path manifest_path = package_directory / "AndroidManifest.xml"; + fs::path prefab_path = prefab_directory / "prefab.json"; + + fs::path meta_dir = package_directory / "META-INF"; + + utils.create_directories(meta_dir, error_code); + + const fs::path share_root = + vcpkg_root_path / "packages" / Strings::format("%s_%s", name, action.spec.triplet()); + + utils.copy_file(share_root / "share" / name / "copyright", + meta_dir / "LICENSE", + fs::copy_options::overwrite_existing, + error_code); + + PackageMetadata pm; + pm.name = artifact_id; + pm.schema = 1; + pm.version = norm_version; + + std::set dependencies_minus_empty_packages; + + for(auto dependency: dependencies){ + if(empty_package_dependencies.find(dependency.name()) != empty_package_dependencies.end()){ + for(auto& empty_package_dep: empty_package_dependencies[dependency.name()]){ + dependencies_minus_empty_packages.insert(empty_package_dep); + } + } + else { + dependencies_minus_empty_packages.insert(dependency); + } + } + + std::vector pom_dependencies; + + if (dependencies_minus_empty_packages.size() > 0) + { + pom_dependencies.push_back("\n"); + } + + for (const auto& it : dependencies_minus_empty_packages) + { + std::string maven_pom = R"( + @GROUP_ID@ + @ARTIFACT_ID@ + @VERSION@ + aar + runtime + )"; + std::string pom = Strings::replace_all(std::move(maven_pom), "@GROUP_ID@", group_id); + pom = Strings::replace_all(std::move(pom), "@ARTIFACT_ID@", it.name()); + pom = Strings::replace_all(std::move(pom), "@VERSION@", version_map[it.name()]); + pom_dependencies.push_back(pom); + pm.dependencies.push_back(it.name()); + } + + if (dependencies_minus_empty_packages.size() > 0) + { + pom_dependencies.push_back("\n"); + } + + if(prefab_options.enable_debug){ + System::print2(Strings::format( + "[DEBUG]\n\tWriting manifest\n\tTo %s\n\tWriting prefab meta data\n\tTo %s\n\n", + manifest_path.generic_u8string(), prefab_path.generic_u8string())); + } + + utils.write_contents(manifest_path, manifest, VCPKG_LINE_INFO); + utils.write_contents(prefab_path, pm.to_json(), VCPKG_LINE_INFO); + + if(prefab_options.enable_debug){ + std::vector triplet_names; + for(auto triplet: triplets){ + triplet_names.push_back(triplet.canonical_name()); + } + System::print2(Strings::format("[DEBUG] Found %d triplets\n\t%s\n\n", triplets.size(), + Strings::join("\n\t", triplet_names))); + } + + for (const auto& triplet : triplets) + { + const fs::path listfile = vcpkg_root_path / "installed" / "vcpkg" / "info" / + (Strings::format("%s_%s_%s", name, norm_version, triplet) + ".list"); + const fs::path installed_dir = vcpkg_root_path / "packages" / Strings::format("%s_%s", name, triplet); + Checks::check_exit(VCPKG_LINE_INFO, + utils.exists(listfile), + "Error: Packages not installed %s:%s %s", + name, + triplet, + listfile.generic_u8string()); + + fs::path libs = installed_dir / "lib"; + + std::vector modules; + + std::vector modules_shared = find_modules(paths, libs, ".so"); + + for (const auto& module : modules_shared) + { + modules.push_back(module); + } + + std::vector modules_static = find_modules(paths, libs, ".a"); + for (const auto& module : modules_static) + { + modules.push_back(module); + } + + // header only libs + if (modules.empty()) + { + fs::path module_dir = modules_directory / name; + fs::path module_libs_dir = module_dir / "libs"; + utils.create_directories(module_libs_dir, error_code); + fs::path installed_headers_dir = installed_dir / "include"; + fs::path exported_headers_dir = module_dir / "include"; + + ModuleMetadata meta; + fs::path module_meta_path = module_dir / "module.json"; + utils.write_contents(module_meta_path, meta.to_json(), VCPKG_LINE_INFO); + + utils.copy(installed_headers_dir, exported_headers_dir, fs::copy_options::recursive); + break; + } + else + { + for (const auto& module : modules) + { + std::string module_name = module.stem().generic_u8string(); + std::string extension = module.extension().generic_u8string(); + + ABIMetadata ab; + ab.abi = triplet_abi_map[triplet]; + ab.api = triplet_api_map[triplet]; + + ab.stl = Strings::contains(extension, "a") ?"c++_static": "c++_shared"; + ab.ndk = version.major(); + + if(prefab_options.enable_debug){ + System::print2(Strings::format("[DEBUG] Found module %s:%s\n", module_name, ab.abi)); + } + + module_name = Strings::trim(std::move(module_name)); + + if (Strings::starts_with(module_name, "lib")) + { + module_name = module_name.substr(3); + } + fs::path module_dir = (modules_directory / module_name); + fs::path module_libs_dir = + module_dir / "libs" / Strings::format("android.%s", ab.abi); + utils.create_directories(module_libs_dir, error_code); + + fs::path abi_path = module_libs_dir / "abi.json"; + + if(prefab_options.enable_debug){ + System::print2(Strings::format("\tWriting abi metadata\n\tTo %s\n", + abi_path.generic_u8string())); + } + utils.write_contents(abi_path, ab.to_string(), VCPKG_LINE_INFO); + + fs::path installed_module_path = libs / module.filename(); + fs::path exported_module_path = module_libs_dir / module.filename(); + + utils.copy_file(installed_module_path, + exported_module_path, + fs::copy_options::overwrite_existing, + error_code); + if(prefab_options.enable_debug){ + System::print2(Strings::format("\tCopying libs\n\tFrom %s\n\tTo %s\n", + installed_module_path.generic_u8string(), exported_module_path.generic_u8string())); + } + fs::path installed_headers_dir = installed_dir / "include"; + fs::path exported_headers_dir = module_libs_dir / "include"; + + + if(prefab_options.enable_debug){ + System::print2(Strings::format("\tCopying headers\n\tFrom %s\n\tTo %s\n", + installed_headers_dir.generic_u8string(), exported_headers_dir.generic_u8string())); + } + + utils.copy(installed_headers_dir, exported_headers_dir, fs::copy_options::recursive); + + ModuleMetadata meta; + + fs::path module_meta_path = module_dir / "module.json"; + + if(prefab_options.enable_debug){ + System::print2(Strings::format("\tWriting module metadata\n\tTo %s\n\n", + module_meta_path.generic_u8string())); + } + + utils.write_contents(module_meta_path, meta.to_json(), VCPKG_LINE_INFO); + } + } + } + + fs::path exported_archive_path = per_package_dir_path / Strings::format("%s-%s.aar", name, norm_version); + fs::path pom_path = per_package_dir_path / "pom.xml"; + + if(prefab_options.enable_debug){ + System::print2(Strings::format("[DEBUG] Exporting AAR And POM\n\tAAR Path %s\n\tPOM Path %s\n", + exported_archive_path.generic_u8string(), pom_path.generic_u8string())); + } + + compress_directory(paths, package_directory, exported_archive_path); + + std::string POM = R"( + + 4.0.0 + + + @GROUP_ID@ + @ARTIFACT_ID@ + @VERSION@ + aar + The Vcpkg AAR for @ARTIFACT_ID@ + https://github.com/microsoft/vcpkg.git + @DEPENDENCIES@ +)"; + + std::string pom = Strings::replace_all(std::move(POM), "@GROUP_ID@", group_id); + pom = Strings::replace_all(std::move(pom), "@ARTIFACT_ID@", artifact_id); + pom = Strings::replace_all(std::move(pom), "@DEPENDENCIES@", Strings::join("\n", pom_dependencies)); + pom = Strings::replace_all(std::move(pom), "@VERSION@", norm_version); + + utils.write_contents(pom_path, pom, VCPKG_LINE_INFO); + + if (prefab_options.enable_maven) + { + + maven_install(exported_archive_path, pom_path, prefab_options); + if(prefab_options.enable_debug){ + System::print2( + Strings::format("\n\n[DEBUG] Configuration properties in Android Studio\nIn app/build.gradle\n\n\t%s:%s:%s\n\n", + group_id, artifact_id, norm_version)); + + System::print2(R"(And cmake flags + + externalNativeBuild { + cmake { + arguments '-DANDROID_STL=c++_shared' + cppFlags "-std=c++17" + } + } + +)"); + + System::print2(R"(In gradle.properties + + android.enablePrefab=true + android.enableParallelJsonGen=false + android.prefabVersion=${prefab.version} + +)");} + } + System::print2(System::Color::success, + Strings::format("Successfuly exported %s. Checkout %s \n", + name, + raw_exported_dir_path.generic_u8string())); + } + } +} diff --git a/toolsrc/src/vcpkg/triplet.cpp b/toolsrc/src/vcpkg/triplet.cpp index 8026baa8a..47b824989 100644 --- a/toolsrc/src/vcpkg/triplet.cpp +++ b/toolsrc/src/vcpkg/triplet.cpp @@ -39,6 +39,12 @@ namespace vcpkg const Triplet Triplet::ARM_UWP = from_canonical_name("arm-uwp"); const Triplet Triplet::ARM64_UWP = from_canonical_name("arm64-uwp"); + // + const Triplet Triplet::ARM_ANDROID = from_canonical_name("arm-android"); + const Triplet Triplet::ARM64_ANDROID = from_canonical_name("arm64-android"); + const Triplet Triplet::X86_ANDROID = from_canonical_name("x86-android"); + const Triplet Triplet::X64_ANDROID = from_canonical_name("x64-android"); + Triplet Triplet::from_canonical_name(std::string&& triplet_as_string) { std::string s(Strings::ascii_to_lowercase(std::move(triplet_as_string))); @@ -55,19 +61,19 @@ namespace vcpkg Optional Triplet::guess_architecture() const noexcept { using System::CPUArchitecture; - if (*this == X86_WINDOWS || *this == X86_UWP) + if (*this == X86_WINDOWS || *this == X86_UWP || *this == X86_ANDROID) { return CPUArchitecture::X86; } - else if (*this == X64_WINDOWS || *this == X64_UWP) + else if (*this == X64_WINDOWS || *this == X64_UWP || *this ==X64_ANDROID) { return CPUArchitecture::X64; } - else if (*this == ARM_WINDOWS || *this == ARM_UWP) + else if (*this == ARM_WINDOWS || *this == ARM_UWP || *this == ARM_ANDROID) { return CPUArchitecture::ARM; } - else if (*this == ARM64_WINDOWS || *this == ARM64_UWP) + else if (*this == ARM64_WINDOWS || *this == ARM64_UWP || *this == ARM64_ANDROID) { return CPUArchitecture::ARM64; } diff --git a/toolsrc/vcpkglib/vcpkglib.vcxproj b/toolsrc/vcpkglib/vcpkglib.vcxproj index fc6d37de7..78a2cc322 100644 --- a/toolsrc/vcpkglib/vcpkglib.vcxproj +++ b/toolsrc/vcpkglib/vcpkglib.vcxproj @@ -179,6 +179,7 @@ + @@ -258,6 +259,7 @@ +