Skip to content

Commit

Permalink
Manifest (de)serialization using serde (#106)
Browse files Browse the repository at this point in the history
* Implement proper (de)serialization of the new manifest structure

* Add Android manifest links to Manifest.rs

* Move default deserialization values to `cargo-apk`

* Move default Android namespace implementation to `cargo-apk`

* Moved default values back to `ndk-build` to better match previous implementation + minor improvements

* Implement missing `MAIN` action intent serialization

* - Mention what the Vulkan version numbers represent.
- Moved manifest default overrides to `fn from_subcommand`

* Use bufwriter
  • Loading branch information
maxded authored Apr 20, 2021
1 parent dd54b81 commit 6703767
Show file tree
Hide file tree
Showing 12 changed files with 486 additions and 532 deletions.
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.

# 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

0 comments on commit 6703767

Please sign in to comment.