blob: 0cac63d87420b61a4775e96618ce22186b9da3f4 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Main entry point for the font service.
#![warn(missing_docs)]
mod font_service;
use {
self::font_service::{FontServiceBuilder, ProviderRequestStream},
anyhow::{format_err, Context, Result},
argh::FromArgs,
config::Config,
fuchsia_component::server::ServiceFs,
fuchsia_inspect::component::inspector,
fuchsia_trace as trace, fuchsia_trace_provider as trace_provider,
std::path::PathBuf,
tracing::{debug, error, info, warn},
};
const FONT_MANIFEST_PATH: &str = "/config/data/all.font_manifest.json";
/// TODO(https://fxbug.dev/42120295): Remove after Chromium tests are made hermetic.
const TEST_COMPATIBILITY_FONT_MANIFEST_PATH: &str =
"/config/data/downstream_test_fonts.font_manifest.json";
/// Default capacity of the in-memory font cache when not specified in manifest.
///
/// 4 MB is enough to fit several smaller fonts; large fonts will never be cached.
///
/// TODO(https://fxbug.dev/42125535): Listen for memory pressure events and trim cache.
const DEFAULT_CACHE_CAPACITY_BYTES: u64 = 4_000_000;
#[derive(FromArgs)]
/// Font Server
struct Args {
/// load fonts from the specified font manifest file instead of the default
#[argh(option, short = 'm')]
font_manifest: Option<String>,
/// no-op, deprecated
#[argh(switch, short = 'n')]
no_default_fonts: bool,
}
#[fuchsia::main(logging_tags = ["fonts"])]
async fn main() -> Result<()> {
trace_provider::trace_provider_create_with_fdio();
trace::instant!(c"fonts", c"startup", trace::Scope::Process);
// We have to convert legacy uses of "--font-manifest=<PATH>" to "--font-manifest <PATH>".
let arg_strings: Vec<String> = std::env::args()
.collect::<Vec<String>>()
.iter()
.flat_map(|s| s.split("=").map(|s| s.to_owned()))
.collect();
let arg_strs: Vec<&str> = arg_strings.iter().map(|s| s.as_str()).collect();
let args: Args = Args::from_args(&[arg_strs[0]], &arg_strs[1..])
.map_err(|early_exit| format_err!("{}", early_exit.output))
.context("malformed args, this is a fatal error")?;
if args.no_default_fonts {
warn!("--no-default-fonts is deprecated and is treated as a no-op")
}
let structured_config = font_service::config::as_ref();
let font_manifest_paths = select_manifests(&args, structured_config)
.context("no usable font manifests, this is a fatal error")?;
let mut service_builder = FontServiceBuilder::with_default_asset_loader(
DEFAULT_CACHE_CAPACITY_BYTES,
inspector().root(),
);
debug!("Building service with manifest(s) {:?}", &font_manifest_paths);
for path in &font_manifest_paths {
service_builder.add_manifest_from_file(path);
}
let service = service_builder.build().await.map_err(|err| {
error!("Failed to build service with manifest(s) {:?}: {:#?}", &font_manifest_paths, &err);
err
})?;
debug!("Built service with manifest(s) {:?}", &font_manifest_paths);
debug!("Adding FIDL services");
let mut fs = ServiceFs::new();
fs.dir("svc")
.add_fidl_service(ProviderRequestStream::Stable)
.add_fidl_service(ProviderRequestStream::Experimental);
fs.take_and_serve_directory_handle()
.context("could not serve directory handle, this is a fatal error")?;
let _inspect_server_task =
inspect_runtime::publish(inspector(), inspect_runtime::PublishOptions::default());
let fs = fs;
service.run(fs).await;
Ok(())
}
/// Negotiate which manifest(s) to load.
/// TODO(https://fxbug.dev/42120295): Remove compatibility manifest after Chromium tests are made hermetic.
fn select_manifests(args: &Args, config: &Config) -> Result<Vec<PathBuf>> {
select_manifests_for_test(args, config, /*check_files=*/ true)
}
// If `check_files` == false, the code will not go out to the filesystem to verify
// that a file exists; useful for lightweight testing.
fn select_manifests_for_test(
args: &Args,
config: &Config,
check_files: bool,
) -> Result<Vec<PathBuf>> {
let mut manifest_paths: Vec<PathBuf> = vec![];
// Load a font manifest from structured config.
let manifest_from_config = PathBuf::from(&config.font_manifest);
let manifest_str = manifest_from_config.as_os_str();
if manifest_str.len() != 0 {
if !check_files || manifest_from_config.is_file() {
info!("Adding manifest file: {}", &manifest_from_config.display());
manifest_paths.push(manifest_from_config);
} else {
// A file name was specified in the structured config, but that file
// could not be found. This is most likely not expected.
warn!(
"Structured config requested font manifest that was not found: {}",
&manifest_from_config.display()
);
}
}
// Also try loading a font manifest from the command line args.
let main_manifest_path = match &args.font_manifest {
Some(path) => PathBuf::from(path),
None => PathBuf::from(FONT_MANIFEST_PATH),
};
if (main_manifest_path.is_file() || !check_files) && !main_manifest_path.as_os_str().is_empty()
{
manifest_paths.push(main_manifest_path);
} else {
warn!(
concat!(
"Specified manifest file {:?} does not exist. ",
"Looking for test compatibility manifest instead."
),
main_manifest_path
);
}
// Support legacy non-hermetic tests (e.g. Chromium) that expect some minimum set of fonts but
// don't specify what it should be. This happens when a font manifest is specified but is not
// found under the specified path.
if manifest_paths.is_empty() {
let compatibility_manifest_path = PathBuf::from(TEST_COMPATIBILITY_FONT_MANIFEST_PATH);
if compatibility_manifest_path.is_file() || !check_files {
manifest_paths.push(compatibility_manifest_path);
}
}
// The ordering of manifests should not matter.
manifest_paths.sort();
if manifest_paths.is_empty() {
Err(format_err!("Either no font manifests were specified, or they do not exist"))
} else {
Ok(manifest_paths)
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_case::test_case;
#[test_case(
Args { font_manifest: Some("".into()), no_default_fonts: false },
Config { font_manifest: "".into(), verbose_logging: true },
vec![TEST_COMPATIBILITY_FONT_MANIFEST_PATH]; "empty means default manifest is used")]
#[test_case(
Args { font_manifest: Some("/test/foo.txt".into()), no_default_fonts: false },
Config { font_manifest: "".into(), verbose_logging: true },
vec!["/test/foo.txt"]; "from args only")]
#[test_case(
Args { font_manifest: None, no_default_fonts: false },
Config { font_manifest: "/test/foo.txt".into(), verbose_logging: true },
vec![FONT_MANIFEST_PATH, "/test/foo.txt" ]; "from config only")]
#[test_case(
Args { font_manifest: Some("/test/foo.txt".into()), no_default_fonts: false },
Config { font_manifest: "/test/foo2.txt".into(), verbose_logging: true },
vec!["/test/foo.txt", "/test/foo2.txt"]; "from both")]
fn test_manifest_selection(args: Args, config: Config, expected: Vec<&str>) {
let result = select_manifests_for_test(&args, &config, /*check_files=*/ false).unwrap();
let actual: Vec<&str> = result.iter().map(|p| p.as_os_str().to_str().unwrap()).collect();
assert_eq!(expected, actual);
}
}