blob: a09df0de0d82f071a5ea60d0831b413a968eb8aa [file] [log] [blame]
#!/usr/bin/env python
# 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.
### generate Rust FIDL facade crates
import argparse
import datetime
import json
import os
import re
import subprocess
import shutil
import sys
ROOT_PATH = os.path.abspath(__file__ + "/../../..")
TOML_DEFAULT_PATH = os.path.join(ROOT_PATH, "garnet", "rust_fidl_crates.json")
WORKSPACE_TOML_DEFAULT_PATH = os.path.join(ROOT_PATH, "garnet", "Cargo.toml")
OUT_PUB_DEFAULT_PATH = os.path.join(ROOT_PATH, "garnet", "public", "rust", "fidl_crates")
OUT_PRIV_DEFAULT_PATH = os.path.join(ROOT_PATH, "garnet", "lib", "rust", "fidl_crates")
BUILD_GN_CONTENTS = '''\
# Copyright %(year)s 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.
import("//build/rust/rust_library.gni")
import("//build/rust/toolchain.gni")
rust_library("%(crate_name)s") {
deps = [
"//%(fidl_target)s:%(fidl_label)s($rust_toolchain)",
]
}
'''
CARGO_TOML_CONTENTS = '''\
# Copyright %(year)s 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.
[package]
name = "%(crate_name)s"
version = "0.1.0"
license = "BSD-3-Clause"
authors = ["rust-fuchsia@fuchsia.com"]
description = "Generated FIDL interface"
repository = "https://fuchsia.googlesource.com/garnet/"
[dependencies]
fidl = "0.1"
fuchsia-zircon = "0.3"
futures = { version = "0.2.1", features = ["nightly"] }
fuchsia-async = "0.1"
%(deps)s
'''
LIB_RS_CONTENTS = '''\
// Copyright %(year)s 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.
include!(concat!(env!("FIDL_GEN_ROOT"), "/%(path)s"));
'''
def cur_year():
return datetime.datetime.now().year
def rm_left_slashes(path):
if path.startswith("//"):
return path[len("//"):]
else:
return path
def ensure_dir(path):
if not os.path.exists(path):
os.makedirs(path)
def write_lib_rs(crate_path, lib_name, fidl_target_fs_path):
ensure_dir(crate_path)
src_path = os.path.join(crate_path, "src")
ensure_dir(src_path)
lib_rs_path = os.path.join(src_path, "lib.rs")
path_to_generated_code = "%s/%s.fidl.rs" % (fidl_target_fs_path, lib_name)
with open(lib_rs_path, "w") as f:
f.write(LIB_RS_CONTENTS % { "year": cur_year(), "path": path_to_generated_code })
def write_cargo_toml_file(crate_path, crate_name, deps):
cargo_toml_path = os.path.join(crate_path, "Cargo.toml")
cargo_deps = "\n".join(map(lambda x: 'fidl_%s = "0.1"' % x, deps))
with open(cargo_toml_path, "w") as f:
f.write(CARGO_TOML_CONTENTS % {
"crate_name": crate_name,
"deps": cargo_deps,
"year": cur_year()
})
def write_build_gn_file(crate_path, crate_name, fidl_target_fs_path, fidl_target_label):
build_gn_path = os.path.join(crate_path, "BUILD.gn")
with open(build_gn_path, "w") as f:
f.write(BUILD_GN_CONTENTS % {
"crate_name": crate_name,
"fidl_target": fidl_target_fs_path,
"fidl_label": fidl_target_label,
"year": cur_year(),
})
# Format the file
command_args = [ os.path.join(ROOT_PATH, "scripts", "fx"), "gn", "format", build_gn_path]
subprocess.check_call(command_args)
def write_facade(folder_path, lib_name, fidl_target, deps):
ensure_dir(folder_path)
fidl_target_sections = fidl_target.split(":")
assert len(fidl_target_sections) == 2
fidl_target_gn_path = fidl_target_sections[0]
fidl_target_label = fidl_target_sections[1]
fidl_target_fs_path = rm_left_slashes(fidl_target_gn_path)
crate_name = "fidl_" + lib_name
crate_path = os.path.join(folder_path, crate_name)
write_lib_rs(crate_path, lib_name, fidl_target_fs_path)
write_cargo_toml_file(crate_path, crate_name, deps)
write_build_gn_file(crate_path, crate_name, fidl_target_fs_path, fidl_target_label)
return (crate_name, crate_path)
def insert_alphabetically_between(lines, line_to_add, first_line_stripped, last_line_stripped):
# Find the start of the area to insert
i = 0
while True:
if len(lines) == i:
assert False, "Start of alphabetical list not found"
if "".join(lines[i].split()) == first_line_stripped:
first_line = i + 1
break
i += 1
line_to_add_stripped = "".join(line_to_add.split())
i = first_line
while i < len(lines):
stripped_line = "".join(lines[i].split())
if stripped_line == line_to_add_stripped:
# If our line has already been added, we can return
return lines
if stripped_line == last_line_stripped or stripped_line > line_to_add_stripped:
# We've found the first line that should be after our line alphabetically
# Insert the line at the position just before the last line read
lines.insert(i, line_to_add)
# Remove any non-alphabetical copies of the entry we just added
i += 1
while i < len(lines):
stripped_line = "".join(lines[i].split())
if stripped_line == line_to_add_stripped:
lines.pop(i)
else:
i += 1
return lines
i += 1
if last_line_stripped != "":
assert False, "Reached end of file before end of alphabetical list found"
else:
lines.append(line_to_add)
return lines
def update_workspace_cargo_toml(workspace_toml_path, crate_names_and_paths):
with open(workspace_toml_path, "r+") as f:
content = f.readlines()
for (crate_name, crate_path) in crate_names_and_paths:
crate_rel_path = os.path.relpath(crate_path, os.path.join(workspace_toml_path, os.pardir))
content = insert_alphabetically_between(
content, ' "%s",\n' % crate_rel_path, "members=[", "]")
content = insert_alphabetically_between(
content, '%s = { path = "%s" }\n' % (crate_name, crate_rel_path), "[patch.crates-io]", "")
f.seek(0)
f.write("".join(content))
f.truncate()
def main():
parser = argparse.ArgumentParser("Generate Rust FIDL facade crates")
parser.add_argument("--target-json",
help="Path to the JSON file listing the crates to generate",
default=TOML_DEFAULT_PATH)
parser.add_argument("--out-pub",
help="Path to the output directory for public crates",
default=OUT_PUB_DEFAULT_PATH)
parser.add_argument("--out-priv",
help="Path to the output directory for private crates",
default=OUT_PRIV_DEFAULT_PATH)
parser.add_argument("--workspace-toml",
help="Path to the workspace Cargo.toml file",
default=WORKSPACE_TOML_DEFAULT_PATH)
args = parser.parse_args()
with open(args.target_json, "r") as file:
target_json = json.load(file)
shutil.rmtree(args.out_pub, ignore_errors=True, onerror=None)
shutil.rmtree(args.out_priv, ignore_errors=True, onerror=None)
crate_names_and_paths = []
crate_name_to_direct_deps = {}
for crate in target_json:
crate_name_to_direct_deps[crate["library_name"]] = set(crate["library_deps"])
crate_name_to_transitive_deps = {}
for crate in target_json:
deps = set()
to_insert_deps_of = set()
for dep in crate_name_to_direct_deps[crate["library_name"]]:
deps.add(dep)
to_insert_deps_of.update(crate_name_to_direct_deps[dep])
while len(to_insert_deps_of) != 0:
dep = to_insert_deps_of.pop()
if not dep in deps:
deps.add(dep)
to_insert_deps_of.update(crate_name_to_direct_deps[dep])
crate_name_to_transitive_deps[crate["library_name"]] = deps
for crate in target_json:
out_dir = args.out_pub if crate["public"] else args.out_priv
crate_names_and_paths.append(
write_facade(
out_dir,
crate["library_name"],
crate["fidl_target"],
crate_name_to_transitive_deps[crate["library_name"]]))
update_workspace_cargo_toml(args.workspace_toml, crate_names_and_paths)
return 0
if __name__ == '__main__':
sys.exit(main())