Skip to content

Commit

Permalink
apk: Embed configurable assets in asset/ directory (#122)
Browse files Browse the repository at this point in the history
Android apps might want to ship with extra files - assets - to
complement the native build.  These could be single files or folders
that are included recursively.  the `file_name()` of the given path is
added directly to the `assets/` directory, and any directory structure
beyond that point is maintained.

Assets are specified in the following way in `manifest.yaml`:

    android:
      assets:
        - some/path
        - { path: some/path } # Same as above
        - { path: some/path, optional: true } # Do not error if this  path doesn't exist
        - { path: some/path, alignment: 4096 } # Page-aligned for optimal use with AASSET_MODE_BUFFER and AAsset_getBuffer
        - { path: some/path, alignment: unaligned }
        - { path: some/path, alignment: compressed }
  • Loading branch information
MarijnS95 authored Aug 22, 2023
1 parent 4e2c8e9 commit 165e3c0
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 22 deletions.
29 changes: 16 additions & 13 deletions apk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::res::Chunk;
use anyhow::{Context, Result};
use std::io::Cursor;
use std::path::{Path, PathBuf};
use xcommon::{Scaler, ScalerOpts, Zip, ZipFile, ZipFileOptions};
use xcommon::{Scaler, ScalerOpts, Zip, ZipFileOptions};

mod compiler;
pub mod manifest;
Expand Down Expand Up @@ -77,16 +77,27 @@ impl Apk {
Ok(())
}

pub fn add_asset(&mut self, asset: &Path, opts: ZipFileOptions) -> Result<()> {
let file_name = asset
.file_name()
.context("Asset must have file_name component")?;
let dest = Path::new("assets").join(file_name);
if asset.is_dir() {
tracing::info!("Embedding asset directory `{}`", asset.display());
self.zip.add_directory(asset, &dest, opts)
} else {
tracing::info!("Embedding asset file `{}`", asset.display());
self.zip.add_file(asset, &dest, opts)
}
.with_context(|| format!("While embedding asset `{}`", asset.display()))
}

pub fn add_dex(&mut self, dex: &Path) -> Result<()> {
self.zip
.add_file(dex, Path::new("classes.dex"), ZipFileOptions::Compressed)?;
Ok(())
}

pub fn add_zip_file(&mut self, f: ZipFile) -> Result<()> {
self.zip.add_zip_file(f)
}

pub fn add_lib(&mut self, target: Target, path: &Path) -> Result<()> {
let name = path
.file_name()
Expand All @@ -100,14 +111,6 @@ impl Apk {
)
}

pub fn add_file(&mut self, source: &Path, dest: &Path, opts: ZipFileOptions) -> Result<()> {
self.zip.add_file(source, dest, opts)
}

pub fn add_directory(&mut self, source: &Path, dest: &Path) -> Result<()> {
self.zip.add_directory(source, dest)
}

pub fn finish(self, signer: Option<Signer>) -> Result<()> {
self.zip.finish()?;
crate::sign::sign(&self.path, signer)?;
Expand Down
9 changes: 7 additions & 2 deletions msix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,13 @@ impl Msix {
self.zip.add_file(source, dest, opts)
}

pub fn add_directory(&mut self, source: &Path, dest: &Path) -> Result<()> {
self.zip.add_directory(source, dest)
pub fn add_directory(
&mut self,
source: &Path,
dest: &Path,
opts: ZipFileOptions,
) -> Result<()> {
self.zip.add_directory(source, dest, opts)
}

pub fn finish(mut self, signer: Option<Signer>) -> Result<()> {
Expand Down
9 changes: 9 additions & 0 deletions xbuild/src/command/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ pub fn build(env: &BuildEnv) -> Result<()> {
)?;
apk.add_res(env.icon(), &env.android_jar())?;

for asset in &env.config().android().assets {
let path = env.cargo().package_root().join(asset.path());

if !asset.optional() || path.exists() {
apk.add_asset(&path, asset.alignment().to_zip_file_options())?
}
}

if has_lib {
for target in env.target().compile_targets() {
let arch_dir = platform_dir.join(target.arch().to_string());
Expand Down Expand Up @@ -256,6 +264,7 @@ pub fn build(env: &BuildEnv) -> Result<()> {
ipa.add_directory(
&app,
&Path::new("Payload").join(format!("{}.app", env.name())),
ZipFileOptions::Compressed,
)?;
ipa.finish()?;
}
Expand Down
87 changes: 87 additions & 0 deletions xbuild/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use msix::AppxManifest;
use serde::Deserialize;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use xcommon::ZipFileOptions;

#[derive(Clone, Debug, Default)]
pub struct Config {
Expand Down Expand Up @@ -313,7 +314,84 @@ impl Config {
}
}

#[derive(Clone, Copy, Debug, Default, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UnalignedCompressed {
/// Don't align this file
Unaligned,
/// Compressed files do not need to be aligned, as they have to be unpacked and decompressed anyway
#[default]
Compressed,
}

#[derive(Clone, Copy, Debug, Deserialize)]
#[serde(untagged)]
pub enum ZipAlignmentOptions {
/// Align this file to the given number of bytes
Aligned(u16),
/// Used to wrap a tagged enum with an untagged alignment value
UnalignedCompressed(UnalignedCompressed),
}

impl Default for ZipAlignmentOptions {
fn default() -> Self {
Self::UnalignedCompressed(UnalignedCompressed::Compressed)
}
}

impl ZipAlignmentOptions {
pub fn to_zip_file_options(self) -> ZipFileOptions {
match self {
Self::Aligned(a) => ZipFileOptions::Aligned(a),
Self::UnalignedCompressed(UnalignedCompressed::Unaligned) => ZipFileOptions::Unaligned,
Self::UnalignedCompressed(UnalignedCompressed::Compressed) => {
ZipFileOptions::Compressed
}
}
}
}

#[derive(Clone, Debug, Deserialize)]
#[serde(untagged, deny_unknown_fields)]
pub enum AssetPath {
Path(PathBuf),
Extended {
path: PathBuf,
#[serde(default)]
optional: bool,
#[serde(default)]
alignment: ZipAlignmentOptions,
},
}

impl AssetPath {
#[inline]
pub fn path(&self) -> &Path {
match self {
AssetPath::Path(path) => path,
AssetPath::Extended { path, .. } => path,
}
}

#[inline]
pub fn optional(&self) -> bool {
match self {
AssetPath::Path(_) => false,
AssetPath::Extended { optional, .. } => *optional,
}
}

#[inline]
pub fn alignment(&self) -> ZipAlignmentOptions {
match self {
AssetPath::Path(_) => Default::default(),
AssetPath::Extended { alignment, .. } => *alignment,
}
}
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct RawConfig {
#[serde(flatten)]
generic: Option<GenericConfig>,
Expand All @@ -325,13 +403,15 @@ struct RawConfig {
}

#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GenericConfig {
icon: Option<PathBuf>,
#[serde(default)]
runtime_libs: Vec<PathBuf>,
}

#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AndroidDebugConfig {
/// Forward remote (phone) socket connection to local (host)
#[serde(default)]
Expand All @@ -342,6 +422,7 @@ pub struct AndroidDebugConfig {
}

#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AndroidConfig {
#[serde(flatten)]
generic: GenericConfig,
Expand All @@ -353,12 +434,15 @@ pub struct AndroidConfig {
pub gradle: bool,
#[serde(default)]
pub wry: bool,
#[serde(default)]
pub assets: Vec<AssetPath>,
/// Debug configuration for `x run`
#[serde(default)]
pub debug: AndroidDebugConfig,
}

#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct IosConfig {
#[serde(flatten)]
generic: GenericConfig,
Expand All @@ -367,19 +451,22 @@ pub struct IosConfig {
}

#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MacosConfig {
#[serde(flatten)]
generic: GenericConfig,
pub info: InfoPlist,
}

#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LinuxConfig {
#[serde(flatten)]
generic: GenericConfig,
}

#[derive(Clone, Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WindowsConfig {
#[serde(flatten)]
generic: GenericConfig,
Expand Down
22 changes: 15 additions & 7 deletions xcommon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,20 @@ impl Zip {
}

pub fn add_file(&mut self, source: &Path, dest: &Path, opts: ZipFileOptions) -> Result<()> {
let mut f = File::open(source)?;
let mut f = File::open(source)
.with_context(|| format!("While opening file `{}`", source.display()))?;
self.start_file(dest, opts)?;
std::io::copy(&mut f, &mut self.zip)?;
Ok(())
}

pub fn add_directory(&mut self, source: &Path, dest: &Path) -> Result<()> {
add_recursive(self, source, dest)?;
pub fn add_directory(
&mut self,
source: &Path,
dest: &Path,
opts: ZipFileOptions,
) -> Result<()> {
add_recursive(self, source, dest, opts)?;
Ok(())
}

Expand Down Expand Up @@ -335,17 +341,19 @@ impl Zip {
}
}

fn add_recursive(zip: &mut Zip, source: &Path, dest: &Path) -> Result<()> {
for entry in std::fs::read_dir(source)? {
fn add_recursive(zip: &mut Zip, source: &Path, dest: &Path, opts: ZipFileOptions) -> Result<()> {
for entry in std::fs::read_dir(source)
.with_context(|| format!("While reading directory `{}`", source.display()))?
{
let entry = entry?;
let file_name = entry.file_name();
let source = source.join(&file_name);
let dest = dest.join(&file_name);
let file_type = entry.file_type()?;
if file_type.is_dir() {
add_recursive(zip, &source, &dest)?;
add_recursive(zip, &source, &dest, opts)?;
} else if file_type.is_file() {
zip.add_file(&source, &dest, ZipFileOptions::Compressed)?;
zip.add_file(&source, &dest, opts)?;
}
}
Ok(())
Expand Down

0 comments on commit 165e3c0

Please sign in to comment.