blob: 405b9eb234d0545fb02491058da02b2f8f3aea16 [file] [log] [blame]
// Copyright 2021 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.
//! This is a migration tool to help move from C-macro style bind rules to using the bind compiler.
mod common;
mod composite_bind;
mod composite_device_desc;
pub mod library;
use regex::Regex;
use std::collections::HashSet;
use std::fmt::Write as FmtWrite;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
struct Opt {
#[structopt(parse(from_os_str))]
input: PathBuf,
}
fn process_build_file(input: PathBuf) -> Result<Vec<PathBuf>, &'static str> {
let mut output_path = input.clone();
let mut file = File::open(input).map_err(|_| "Failed to open build file")?;
let mut contents = String::new();
file.read_to_string(&mut contents).map_err(|_| "Failed to read build file")?;
let fuchsia_driver_re = Regex::new("fuchsia_driver").unwrap();
let sources_re = Regex::new(r"sources = \[([^\]]*)\]").unwrap();
let module =
fuchsia_driver_re.find(&contents).ok_or("Couldn't find fuchsia_driver in build file")?;
let sources = sources_re
.captures(&contents[module.start()..])
.ok_or("Couldn't find sources in fuchsia_driver target")?;
let source_files = sources.get(1).ok_or("Couldn't find sources in fuchsia_driver target")?;
let mut result = vec![];
for source in source_files.as_str().split(",") {
let trimmed = source.trim();
let unquoted = trimmed.trim_matches('"');
if !unquoted.is_empty() {
output_path.set_file_name(unquoted);
result.push(output_path.clone());
}
}
Ok(result)
}
fn insert_build_rule(
file_path: PathBuf,
libraries: HashSet<library::Library>,
device_name: &str,
) -> Result<(), &'static str> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(file_path)
.map_err(|_| "Failed to open build file")?;
let mut contents = String::new();
file.read_to_string(&mut contents).map_err(|_| "Failed to read build file")?;
// Find the import list and store the location in the BUILD file.
let import_re = Regex::new(r"import\([^\)]*\)\n").unwrap();
let mut iter = import_re.find_iter(&contents);
let first_import = iter.next().ok_or("Couldn't find import list in build file")?;
let last_import = iter.last().unwrap_or(first_import);
// Check for a fuchsia_driver.
let fuchsia_driver_re = Regex::new("fuchsia_driver").unwrap();
let module =
fuchsia_driver_re.find(&contents).ok_or("Couldn't find fuchsia_driver in build file")?;
// Find the dependencies and store the location in the BUILD file.
let deps_re = Regex::new(r"deps = \[").unwrap();
let deps =
deps_re.find(&contents[module.start()..]).ok_or("Couldn't find fuchsia_driver deps")?;
let deps_start = module.start() + deps.start();
let deps_end = module.start() + deps.end();
let mut output = String::new();
// Add the list of imports. Push the bind import if it's missing.
output.push_str(&contents[..first_import.start()]);
if !contents.contains("import(\"//build/bind/bind.gni\")") {
output.push_str("import(\"//build/bind/bind.gni\")\n");
}
output.push_str(&contents[first_import.start()..last_import.end()]);
output.push_str("\n");
// Add the bind_rules.
output.push_str(format!("driver_bind_rules(\"{}_bind\") {{\n", device_name).as_str());
output.push_str(format!(" rules = \"{}.bind\"\n", device_name).as_str());
output.push_str(format!(" header_output = \"{}_bind.h\"\n", device_name).as_str());
output.push_str(format!(" bind_output = \"{}_bind.bc\"\n", device_name).as_str());
if !libraries.is_empty() {
output.push_str(" deps = [\n");
for library in &libraries {
output.push_str(format!(" \"{}\",\n", library.build_target()).as_str());
}
output.push_str(" ]\n");
}
output.push_str("}\n");
// Add the bind header to the fuchsia_driver's dependency.
output.push_str(&contents[last_import.end()..deps_start]);
output.push_str(format!("deps = [\n \":{}_bind_header\",", device_name).as_str());
output.push_str(&contents[deps_end..]);
file.seek(SeekFrom::Start(0)).map_err(|_| "Failed to seek to beginning of build file")?;
file.set_len(0).map_err(|_| "Failed to truncate build file")?;
file.write_all(output.as_bytes()).map_err(|_| "Failed to write back to build file")?;
Ok(())
}
fn write_bind_rules(
build_file_path: PathBuf,
source_file_path: PathBuf,
data: composite_bind::CompositeDeviceData,
) -> Result<String, &'static str> {
let mut nodes_str = String::new();
let mut primary_node_str = String::new();
for node in data.nodes {
let mut node_output = String::new();
node_output
.write_fmt(format_args!(
include_str!("templates/composite_node.template"),
node_name = node.name,
instructions = node.bind_rules,
))
.map_err(|_| "Failed to format output")?;
if node.name.eq(&data.desc.primary_node) {
primary_node_str = node_output;
} else {
nodes_str.push_str(&format!("\n{}", node_output));
}
}
if primary_node_str.is_empty() {
return Err("Missing primary node instructions");
}
let mut libraries_str = String::new();
if !data.libraries.is_empty() {
libraries_str.push_str("\n");
for library in data.libraries.iter() {
libraries_str.push_str(format!("using {};\n", library.name()).as_str());
}
}
let mut output = String::new();
output
.write_fmt(format_args!(
include_str!("templates/composite_bind.template"),
libraries = libraries_str,
device_name = str::replace(&data.desc.device_name, "-", "_"),
primary_node = primary_node_str,
additional_nodes = nodes_str,
))
.map_err(|_| "Failed to format output")?;
let mut bind_file_path = source_file_path;
let file_name = format!("{}.bind", data.desc.device_name);
bind_file_path.set_file_name(file_name.as_str());
let mut bind_file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(bind_file_path)
.map_err(|_| "Failed to open bind file")?;
bind_file.write_all(output.as_bytes()).map_err(|_| "Failed to write to bind file")?;
// Insert the dependency in the BUILD file.
insert_build_rule(build_file_path, data.libraries, &data.desc.device_name)?;
Ok(file_name.to_string())
}
fn main() -> Result<(), &'static str> {
let opt = Opt::from_iter(std::env::args());
let mut migrate_success: Vec<(String, String)> = vec![];
let mut migrate_failure: Vec<(String, String)> = vec![];
for source_file in process_build_file(opt.input.clone())? {
if let Some(source_file_str) = source_file.to_str() {
println!("\nMigrating {}", source_file_str);
let src_file_str = source_file_str.to_string();
let result = composite_bind::process_source_file(source_file.clone())
.and_then(|data| write_bind_rules(opt.input.clone(), source_file, data));
match result {
Err(e) => {
println!("Unable to migrate: {}", e);
migrate_failure.push((src_file_str, e.to_string()));
}
Ok(file_name) => {
println!("Migration successful! Generated {}", file_name);
migrate_success.push((src_file_str, file_name));
}
};
}
}
println!("\n========================================================");
println!("\nUnable to migrate: ");
for (src_file, err) in migrate_failure {
println!(" {} \n => {}", src_file, err);
}
println!("\nSuccessfully migrated: ");
for (src_file, bind_file) in migrate_success {
println!(" {} => {}", src_file, bind_file);
}
Ok(())
}