Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manifest (de)serialization #106

Merged
merged 17 commits into from
Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cargo-apk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- **Breaking:** uses `ndk-build`'s new (de)serialized `Manifest` struct to properly serialize a toml's `[package.metadata.android]` to an `AndroidManifest.xml`. The `[package.metadata.android]` now closely resembles the structure of [an android manifest file](https://developer.android.com/guide/topics/manifest/manifest-element). See [README](README.md) for an example of the new `[package.metadata.android]` structure and all manifest attributes that are currently supported.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the README for...?


# 0.5.6 (2020-11-25)

- Use `dunce::simplified` when extracting the manifest's assets and resource folder
Expand Down
155 changes: 98 additions & 57 deletions cargo-apk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,81 +26,122 @@ cargo install --path .
Following configuration options are supported by `cargo apk` under `[package.metadata.android]`:

```toml
# Name of your APK as shown in the app drawer and in the app switcher
apk_label = "APK Name"

# The target Android API level.
target_sdk_version = 29
min_sdk_version = 26

# Virtual path your application's icon for any mipmap level.
# If not specified, an icon will not be included in the APK.
icon = "@mipmap/ic_launcher"

# If set to true, makes the app run in full-screen, by adding the following line
# as an XML attribute to the manifest's <application> tag :
# android:theme="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen
# Defaults to false.
fullscreen = false
[package.metadata.android]
# Specifies the array of targets to build for.
build_targets = [ "armv7-linux-androideabi", "aarch64-linux-android", "i686-linux-android", "x86_64-linux-android" ]

# Set the instruction of how the activity should be launched
# See https://developer.android.com/guide/topics/manifest/activity-element#lmode
# Defaults to standard.
launch_mode = "standard"
# Path to your application's resources folder.
# If not specified, resources will not be included in the APK.
resources = "path/to/resources_folder"

# Set the minimum required OpenGL ES version.
# Defaults to [3, 1]
opengles_version = [3, 0]
# Path to the folder containing your application's assets.
# If not specified, assets will not be included in the APK.
assets = "path/to/assets_folder"

# Sets the applications screenOrientation.
# See https://developer.android.com/guide/topics/manifest/activity-element
# and look for `android:screenOrientation` for possible values
# Defaults to "unspecified" which makes the system pick an orientation and
# doesn't give you help with rotation.
orientation = "sensorLandscape"
# See https://developer.android.com/guide/topics/manifest/uses-sdk-element
#
# Defaults to a `min_sdk_version` of 23 and `target_sdk_version` is based on the ndk's default platform.
[package.metadata.android.sdk]
min_sdk_version = 16
target_sdk_version = 29
max_sdk_version = 29

# Adds a uses-feature element to the manifest
# Supported keys: name, required
# See https://developer.android.com/guide/topics/manifest/uses-feature-element
[[package.metadata.android.feature]]
name = "android.hardware.camera"

[[package.metadata.android.feature]]
#
# Note: there can be multiple .uses_feature entries.
[[package.metadata.android.uses_feature]]
name = "android.hardware.vulkan.level"
version = "1"
required = true
version = 1

# Adds a uses-permission element to the manifest.
# Note that android_version 23 and higher, Android requires the application to request permissions at runtime.
# There is currently no way to do this using a pure NDK based application.
# See https://developer.android.com/guide/topics/manifest/uses-permission-element
[[package.metadata.android.permission]]
#
# Note: there can be multiple .uses_permission entries.
[[package.metadata.android.uses_permission]]
name = "android.permission.WRITE_EXTERNAL_STORAGE"
max_sdk_version = 18

# Specifies the array of targets to build for.
build_targets = [ "armv7-linux-androideabi", "aarch64-linux-android", "i686-linux-android", "x86_64-linux-android" ]
# See https://developer.android.com/guide/topics/manifest/application-element
[package.metadata.android.application]

# Path to your application's resources folder.
# If not specified, resources will not be included in the APK
res = "path/to/res_folder"
# See https://developer.android.com/guide/topics/manifest/application-element#debug
#
# Defaults to false.
debuggable = false

# Path to the folder containing your application's assets.
# If not specified, assets will not be included in the APK
assets = "path/to/assets_folder"
# See https://developer.android.com/guide/topics/manifest/application-element#theme
#
# Example shows setting the theme of an application to fullscreen.
theme = "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"

# Adds application metadata to the manifest
# Note that there can be several application_metadatas entries
# this will add: <meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only"/>
[[package.metadata.android.application_metadatas]]
# Virtual path your application's icon for any mipmap level.
# If not specified, an icon will not be included in the APK.
icon = "@mipmap/ic_launcher"

# See https://developer.android.com/guide/topics/manifest/application-element#label
#
# Defaults to the compiled artifact's name.
label = "Application Name"

# See https://developer.android.com/guide/topics/manifest/meta-data-element
#
# Note: there can be several .meta_data entries.
# Note: the `resource` attribute is currently not supported.
[[package.metadata.android.application.meta_data]]
name = "com.samsung.android.vr.application.mode"
value = "vr_only"

# Adds activity metadata to the manifest
# Note that there can be several activity_metadatas entries
# this will add: <meta-data android:name="com.oculus.vr.focusaware" android:value="true"/>
[[package.metadata.android.activity_metadatas]]
# See https://developer.android.com/guide/topics/manifest/activity-element
[package.metadata.android.application.activity]

# See https://developer.android.com/guide/topics/manifest/activity-element#config
#
# Defaults to "orientation|keyboardHidden|screenSize".
config_changes = "orientation"

# See https://developer.android.com/guide/topics/manifest/activity-element#label
#
# Defaults to the application's label.
label = "Activity Name"

# See https://developer.android.com/guide/topics/manifest/activity-element#lmode
#
# Defaults to "standard".
launch_mode = "singleTop"

# See https://developer.android.com/guide/topics/manifest/activity-element#screen
#
# Defaults to "unspecified".
orientation = "landscape"

# See https://developer.android.com/guide/topics/manifest/meta-data-element
#
# Note: there can be several .meta_data entries.
# Note: the `resource` attribute is currently not supported.
[[package.metadata.android.application.activity.meta_data]]
name = "com.oculus.vr.focusaware"
value = "true"

# See https://developer.android.com/guide/topics/manifest/intent-filter-element
#
# Note: there can be several .intent_filter entries.
[[package.metadata.android.application.activity.intent_filter]]
# See https://developer.android.com/guide/topics/manifest/action-element
actions = ["android.intent.action.VIEW", "android.intent.action.WEB_SEARCH"]
# See https://developer.android.com/guide/topics/manifest/category-element
categories = ["android.intent.category.DEFAULT", "android.intent.category.BROWSABLE"]

# See https://developer.android.com/guide/topics/manifest/data-element
#
# Note: there can be several .data entries.
# Note: not specifying an attribute excludes it from the final data specification.
[[package.metadata.android.application.activity.intent_filter.data]]
scheme = "https"
host = "github.com"
port = "8080"
path = "/rust-windowing/android-ndk-rs/tree/master/cargo-apk"
path_prefix = "/rust-windowing/"
mime_type = "image/jpeg"
```

TODO: intent filters
If a manifest attribute is not supported by `cargo apk` feel free to create a PR that adds the missing attribute.
126 changes: 83 additions & 43 deletions cargo-apk/src/apk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use crate::manifest::Manifest;
use cargo_subcommand::{Artifact, CrateType, Profile, Subcommand};
use ndk_build::apk::{Apk, ApkConfig};
use ndk_build::cargo::{cargo_apk, VersionCode};
use ndk_build::config::Config;
use ndk_build::dylibs::get_libs_search_paths;
use ndk_build::error::NdkError;
use ndk_build::manifest::MetaData;
use ndk_build::ndk::Ndk;
use ndk_build::target::Target;
use std::path::PathBuf;
Expand All @@ -22,7 +22,7 @@ pub struct ApkBuilder<'a> {
impl<'a> ApkBuilder<'a> {
pub fn from_subcommand(cmd: &'a Subcommand) -> Result<Self, Error> {
let ndk = Ndk::from_env()?;
let manifest = Manifest::parse_from_toml(cmd.manifest())?;
let mut manifest = Manifest::parse_from_toml(cmd.manifest())?;
let build_targets = if let Some(target) = cmd.target() {
vec![Target::from_rust_triple(target)?]
} else if !manifest.build_targets.is_empty() {
Expand All @@ -33,6 +33,50 @@ impl<'a> ApkBuilder<'a> {
let build_dir = dunce::simplified(cmd.target_dir())
.join(cmd.profile())
.join("apk");

// Set default Android manifest values
assert!(manifest.android_manifest.package.is_empty());

if manifest
.android_manifest
.version_name
.replace(manifest.version.clone())
.is_some()
{
panic!("version_name should not be set in TOML");
}

if manifest
.android_manifest
.version_code
.replace(VersionCode::from_semver(&manifest.version)?.to_code(1))
.is_some()
{
panic!("version_code should not be set in TOML");
}

manifest
.android_manifest
.sdk
.target_sdk_version
.get_or_insert(ndk.default_platform());

manifest
.android_manifest
.application
.debuggable
.get_or_insert(*cmd.profile() == Profile::Dev);

manifest
.android_manifest
.application
.activity
.meta_data
.push(MetaData {
name: "android.app.lib_name".to_string(),
value: cmd.artifacts()[0].name().replace("-", "_"),
});

Ok(Self {
cmd,
ndk,
Expand All @@ -47,49 +91,44 @@ impl<'a> ApkBuilder<'a> {
Artifact::Root(name) => format!("rust.{}", name.replace("-", "_")),
Artifact::Example(name) => format!("rust.example.{}", name.replace("-", "_")),
};
let package_label = self
.manifest
.metadata
.apk_label
.as_deref()
.unwrap_or(artifact.name())
.to_string();

let config = Config {
// Set artifact specific manifest default values.
let mut manifest = self.manifest.android_manifest.clone();
manifest.package = package_name;
if manifest.application.label.is_empty() {
manifest.application.label = artifact.name().to_string();
}

let assets = self.manifest.assets.as_ref().map(|assets| {
dunce::simplified(
&self
.cmd
.manifest()
.parent()
.expect("invalid manifest path")
.join(&assets),
)
.to_owned()
});
let resources = self.manifest.resources.as_ref().map(|res| {
dunce::simplified(
&self
.cmd
.manifest()
.parent()
.expect("invalid manifest path")
.join(&res),
)
.to_owned()
});

let config = ApkConfig {
ndk: self.ndk.clone(),
build_dir: self.build_dir.join(artifact),
package_name,
package_label,
version_name: self.manifest.version.clone(),
version_code: VersionCode::from_semver(&self.manifest.version)?.to_code(1),
split: None,
target_name: artifact.name().replace("-", "_"),
debuggable: *self.cmd.profile() == Profile::Dev,
assets: self.manifest.assets.as_ref().map(|assets| {
dunce::simplified(
&self
.cmd
.manifest()
.parent()
.expect("invalid manifest path")
.join(&assets),
)
.to_owned()
}),
res: self.manifest.res.as_ref().map(|res| {
dunce::simplified(
&self
.cmd
.manifest()
.parent()
.expect("invalid manifest path")
.join(&res),
)
.to_owned()
}),
assets,
resources,
manifest,
};

let config = ApkConfig::from_config(config, self.manifest.metadata.clone());
let apk = config.create_apk()?;

for target in &self.build_targets {
Expand All @@ -101,7 +140,7 @@ impl<'a> ApkBuilder<'a> {
.join(artifact)
.join(artifact.file_name(CrateType::Cdylib, &triple));

let target_sdk_version = config.manifest.target_sdk_version;
let target_sdk_version = config.manifest.sdk.target_sdk_version.unwrap();

let mut cargo = cargo_apk(&config.ndk, *target, target_sdk_version)?;
cargo.arg("build");
Expand Down Expand Up @@ -153,7 +192,8 @@ impl<'a> ApkBuilder<'a> {
let ndk = Ndk::from_env()?;
let target_sdk_version = self
.manifest
.metadata
.android_manifest
.sdk
.target_sdk_version
.unwrap_or_else(|| ndk.default_platform());
for target in &self.build_targets {
Expand Down
Loading