1use cargo::core::{Package, TargetKind};
2use cargo::util::CargoResult;
3use cargo::CliError;
4use failure::format_err;
5use itertools::Itertools;
6use serde::Deserialize;
7use std::collections::btree_map::BTreeMap;
8use std::env;
9use std::fs;
10use std::fs::File;
11use std::io::Read;
12use std::path::Path;
13use std::path::PathBuf;
14use toml;
15
16#[derive(Clone)]
17pub struct AndroidConfig {
18 pub cargo_package_name: String,
20
21 pub cargo_package_version: String,
23
24 pub manifest_path: PathBuf,
26 pub sdk_path: PathBuf,
28 pub ndk_path: PathBuf,
30
31 pub build_targets: Vec<AndroidBuildTarget>,
33
34 pub android_jar_path: PathBuf,
36
37 pub target_sdk_version: u32,
39 pub min_sdk_version: u32,
41
42 pub build_tools_version: String,
44
45 pub release: bool,
47
48 pub default_target_config: TomlAndroidTarget,
50
51 target_configs: BTreeMap<(TargetKind, String), TomlAndroidTarget>,
53}
54
55impl AndroidConfig {
56 pub fn resolve(&self, target: (TargetKind, String)) -> CargoResult<AndroidTargetConfig> {
58 let primary_config = self.target_configs.get(&target);
59 let target_name = target.1;
60 let is_default_target = target_name == self.cargo_package_name;
61 let example = target.0 == TargetKind::ExampleBin;
62
63 Ok(AndroidTargetConfig {
64 package_name: primary_config
65 .and_then(|a| a.package_name.clone())
66 .or_else(|| {
67 if is_default_target {
68 self.default_target_config.package_name.clone()
69 } else {
70 None
71 }
72 })
73 .unwrap_or_else(|| {
74 if example {
75 format!("rust.{}.example.{}", self.cargo_package_name, target_name)
76 } else {
77 format!("rust.{}", target_name)
78 }
79 }),
80 package_label: primary_config
81 .and_then(|a| a.label.clone())
82 .or_else(|| {
83 if is_default_target {
84 self.default_target_config.label.clone()
85 } else {
86 None
87 }
88 })
89 .unwrap_or_else(|| target_name.clone()),
90 version_code: primary_config
91 .and_then(|a| a.version_code)
92 .or_else(|| self.default_target_config.version_code)
93 .unwrap_or(1),
94 version_name: primary_config
95 .and_then(|a| a.version_name.clone())
96 .or_else(|| self.default_target_config.version_name.clone())
97 .unwrap_or_else(|| self.cargo_package_version.clone()),
98 package_icon: primary_config
99 .and_then(|a| a.icon.clone())
100 .or_else(|| self.default_target_config.icon.clone()),
101 assets_path: primary_config
102 .and_then(|a| a.assets.as_ref())
103 .or_else(|| self.default_target_config.assets.as_ref())
104 .map(|p| self.manifest_path.parent().unwrap().join(p)),
105 res_path: primary_config
106 .and_then(|a| a.res.as_ref())
107 .or_else(|| self.default_target_config.res.as_ref())
108 .map(|p| self.manifest_path.parent().unwrap().join(p)),
109 fullscreen: primary_config
110 .and_then(|a| a.fullscreen)
111 .or_else(|| self.default_target_config.fullscreen)
112 .unwrap_or(false),
113 application_attributes: primary_config
114 .and_then(|a| a.application_attributes.clone())
115 .or_else(|| self.default_target_config.application_attributes.clone())
116 .map(build_attribute_string),
117 activity_attributes: primary_config
118 .and_then(|a| a.activity_attributes.clone())
119 .or_else(|| self.default_target_config.activity_attributes.clone())
120 .map(build_attribute_string),
121 opengles_version_major: primary_config
122 .and_then(|a| a.opengles_version_major)
123 .or_else(|| self.default_target_config.opengles_version_major)
124 .unwrap_or(2),
125 opengles_version_minor: primary_config
126 .and_then(|a| a.opengles_version_minor)
127 .or_else(|| self.default_target_config.opengles_version_minor)
128 .unwrap_or(0),
129 features: primary_config
130 .and_then(|a| a.feature.clone())
131 .or_else(|| self.default_target_config.feature.clone())
132 .unwrap_or_else(Vec::new)
133 .into_iter()
134 .map(AndroidFeature::from)
135 .collect(),
136 permissions: primary_config
137 .and_then(|a| a.permission.clone())
138 .or_else(|| self.default_target_config.permission.clone())
139 .unwrap_or_else(Vec::new)
140 .into_iter()
141 .map(AndroidPermission::from)
142 .collect(),
143 })
144 }
145}
146
147#[derive(Debug, Copy, Clone, Deserialize)]
149pub enum AndroidBuildTarget {
150 #[serde(rename(deserialize = "armv7-linux-androideabi"))]
151 ArmV7a,
152 #[serde(rename(deserialize = "aarch64-linux-android"))]
153 Arm64V8a,
154 #[serde(rename(deserialize = "i686-linux-android"))]
155 X86,
156 #[serde(rename(deserialize = "x86_64-linux-android"))]
157 X86_64,
158}
159
160#[derive(Clone)]
161pub struct AndroidFeature {
162 pub name: String,
163 pub required: bool,
164 pub version: Option<String>,
165}
166
167impl From<TomlFeature> for AndroidFeature {
168 fn from(f: TomlFeature) -> Self {
169 AndroidFeature {
170 name: f.name,
171 required: f.required.unwrap_or(true),
172 version: f.version,
173 }
174 }
175}
176
177#[derive(Clone)]
178pub struct AndroidPermission {
179 pub name: String,
180 pub max_sdk_version: Option<u32>,
181}
182
183impl From<TomlPermission> for AndroidPermission {
184 fn from(p: TomlPermission) -> Self {
185 AndroidPermission {
186 name: p.name,
187 max_sdk_version: p.max_sdk_version,
188 }
189 }
190}
191
192pub struct AndroidTargetConfig {
194 pub package_name: String,
198
199 pub package_label: String,
201
202 pub version_code: i32,
204
205 pub version_name: String,
207
208 pub package_icon: Option<String>,
211
212 pub assets_path: Option<PathBuf>,
216
217 pub res_path: Option<PathBuf>,
222
223 pub fullscreen: bool,
225
226 pub application_attributes: Option<String>,
228
229 pub activity_attributes: Option<String>,
231
232 pub opengles_version_major: u8,
234
235 pub opengles_version_minor: u8,
237
238 pub features: Vec<AndroidFeature>,
240
241 pub permissions: Vec<AndroidPermission>,
243}
244
245pub fn load(package: &Package) -> Result<AndroidConfig, CliError> {
246 let manifest_content = {
248 let content = {
250 let mut file = File::open(package.manifest_path()).unwrap();
251 let mut content = String::new();
252 file.read_to_string(&mut content).unwrap();
253 content
254 };
255 let config: TomlConfig = toml::from_str(&content).map_err(failure::Error::from)?;
256 config.package.metadata.and_then(|m| m.android)
257 };
258
259 let ndk_path = env::var("NDK_HOME").map_err(|_| {
261 format_err!(
262 "Please set the path to the Android NDK with the \
263 $NDK_HOME environment variable."
264 )
265 })?;
266
267 let sdk_path = {
268 let mut sdk_path = env::var("ANDROID_SDK_HOME").ok();
269
270 if sdk_path.is_none() {
271 sdk_path = env::var("ANDROID_HOME").ok();
272 }
273
274 sdk_path.ok_or_else(|| {
275 format_err!(
276 "Please set the path to the Android SDK with either the $ANDROID_SDK_HOME or \
277 the $ANDROID_HOME environment variable."
278 )
279 })?
280 };
281
282 let build_tools_version = {
284 let dir = fs::read_dir(Path::new(&sdk_path).join("build-tools"))
285 .map_err(|_| format_err!("Android SDK has no build-tools directory"))?;
286
287 let mut versions = Vec::new();
288 for next in dir {
289 let next = next.unwrap();
290
291 let meta = next.metadata().unwrap();
292 if !meta.is_dir() {
293 if !meta.is_file() {
294 let meta = next.path().metadata().unwrap();
296
297 if !meta.is_dir() {
298 continue;
299 }
300 } else {
301 continue;
302 }
303 }
304
305 let file_name = next.file_name().into_string().unwrap();
306 if !file_name.chars().next().unwrap().is_digit(10) {
307 continue;
308 }
309
310 versions.push(file_name);
311 }
312
313 versions.sort_by(|a, b| b.cmp(&a));
314 versions
315 .into_iter()
316 .next()
317 .ok_or_else(|| format_err!("Unable to determine build tools version"))?
318 };
319
320 let android_version = manifest_content
322 .as_ref()
323 .and_then(|a| a.android_version)
324 .unwrap_or(29);
325
326 let android_jar_path = Path::new(&sdk_path)
328 .join("platforms")
329 .join(format!("android-{}", android_version))
330 .join("android.jar");
331 if !android_jar_path.exists() {
332 Err(format_err!(
333 "'{}' does not exist",
334 android_jar_path.to_string_lossy()
335 ))?;
336 }
337
338 let target_sdk_version = manifest_content
339 .as_ref()
340 .and_then(|a| a.target_sdk_version)
341 .unwrap_or(android_version);
342 let min_sdk_version = manifest_content
343 .as_ref()
344 .and_then(|a| a.min_sdk_version)
345 .unwrap_or(18);
346
347 let default_target_config = manifest_content
348 .as_ref()
349 .map(|a| a.default_target_config.clone())
350 .unwrap_or_else(Default::default);
351
352 let mut target_configs = BTreeMap::new();
353 manifest_content
354 .as_ref()
355 .and_then(|a| a.bin.as_ref())
356 .unwrap_or(&Vec::new())
357 .iter()
358 .for_each(|t| {
359 target_configs.insert((TargetKind::Bin, t.name.clone()), t.config.clone());
360 });
361 manifest_content
362 .as_ref()
363 .and_then(|a| a.example.as_ref())
364 .unwrap_or(&Vec::new())
365 .iter()
366 .for_each(|t| {
367 target_configs.insert((TargetKind::ExampleBin, t.name.clone()), t.config.clone());
368 });
369
370 Ok(AndroidConfig {
372 cargo_package_name: package.name().to_string(),
373 cargo_package_version: package.version().to_string(),
374 manifest_path: package.manifest_path().to_owned(),
375 sdk_path: Path::new(&sdk_path).to_owned(),
376 ndk_path: Path::new(&ndk_path).to_owned(),
377 android_jar_path,
378 target_sdk_version,
379 min_sdk_version,
380 build_tools_version,
381 release: false,
382 build_targets: manifest_content
383 .as_ref()
384 .and_then(|a| a.build_targets.clone())
385 .unwrap_or_else(|| {
386 vec![
387 AndroidBuildTarget::ArmV7a,
388 AndroidBuildTarget::Arm64V8a,
389 AndroidBuildTarget::X86,
390 ]
391 }),
392 default_target_config,
393 target_configs,
394 })
395}
396
397fn build_attribute_string(input_map: BTreeMap<String, String>) -> String {
398 input_map
399 .iter()
400 .map(|(key, val)| format!("\n{}=\"{}\"", key, val))
401 .join("")
402}
403
404#[derive(Debug, Clone, Deserialize)]
405struct TomlConfig {
406 package: TomlPackage,
407}
408
409#[derive(Debug, Clone, Deserialize)]
410struct TomlPackage {
411 name: String,
412 metadata: Option<TomlMetadata>,
413}
414
415#[derive(Debug, Clone, Deserialize)]
416struct TomlMetadata {
417 android: Option<TomlAndroid>,
418}
419
420#[derive(Debug, Clone, Deserialize)]
421#[serde(deny_unknown_fields)]
422struct TomlAndroid {
423 android_version: Option<u32>,
424 target_sdk_version: Option<u32>,
425 min_sdk_version: Option<u32>,
426 build_targets: Option<Vec<AndroidBuildTarget>>,
427
428 #[serde(flatten)]
429 default_target_config: TomlAndroidTarget,
430
431 bin: Option<Vec<TomlAndroidSpecificTarget>>,
432 example: Option<Vec<TomlAndroidSpecificTarget>>,
433}
434
435#[derive(Debug, Clone, Deserialize)]
436#[serde(deny_unknown_fields)]
437pub struct TomlFeature {
438 name: String,
439 required: Option<bool>,
440 version: Option<String>,
441}
442
443#[derive(Debug, Clone, Deserialize)]
444#[serde(deny_unknown_fields)]
445pub struct TomlPermission {
446 name: String,
447 max_sdk_version: Option<u32>,
448}
449
450#[derive(Debug, Clone, Deserialize)]
452#[serde(deny_unknown_fields)]
453struct TomlAndroidSpecificTarget {
454 name: String,
455
456 #[serde(flatten)]
457 config: TomlAndroidTarget,
458}
459
460#[derive(Debug, Default, Clone, Deserialize)]
461pub struct TomlAndroidTarget {
462 pub package_name: Option<String>,
463 pub label: Option<String>,
464 pub version_code: Option<i32>,
465 pub version_name: Option<String>,
466 pub icon: Option<String>,
467 pub assets: Option<String>,
468 pub res: Option<String>,
469 pub fullscreen: Option<bool>,
470 pub application_attributes: Option<BTreeMap<String, String>>,
471 pub activity_attributes: Option<BTreeMap<String, String>>,
472 pub opengles_version_major: Option<u8>,
473 pub opengles_version_minor: Option<u8>,
474 pub feature: Option<Vec<TomlFeature>>,
475 pub permission: Option<Vec<TomlPermission>>,
476}