From f3b6deefe92d51b29173dd99e75106bf5a34d8ec Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 3 Jul 2023 13:42:20 +1000 Subject: [PATCH] Combine all backend methods into a single js/d.ts file, like in Python Easier to import from, and allows us to declare the output of the build action without having to iterate over all the proto filenames. Have confirmed it doesn't break esbuild's tree shaking. --- build/configure/src/pylib.rs | 1 + build/configure/src/python.rs | 4 - build/configure/src/rust.rs | 20 ++-- build/configure/src/web.rs | 2 +- rslib/proto/ts.rs | 127 ++++++++--------------- ts/card-info/CardInfo.svelte | 2 +- ts/change-notetype/index.ts | 2 +- ts/change-notetype/lib.ts | 2 +- ts/congrats/index.ts | 2 +- ts/deck-options/index.ts | 2 +- ts/deck-options/lib.ts | 2 +- ts/graphs/WithGraphData.svelte | 6 +- ts/image-occlusion/add-or-update-note.ts | 2 +- ts/image-occlusion/mask-editor.ts | 2 +- ts/import-csv/FieldMapper.svelte | 2 +- ts/import-csv/ImportCsvPage.svelte | 2 +- ts/import-csv/index.ts | 4 +- ts/lib/i18n/utils.ts | 2 +- ts/reviewer/answering.ts | 2 +- ts/tag-editor/TagEditor.svelte | 2 +- 20 files changed, 74 insertions(+), 116 deletions(-) diff --git a/build/configure/src/pylib.rs b/build/configure/src/pylib.rs index 9b1822be2..2e01d1a23 100644 --- a/build/configure/src/pylib.rs +++ b/build/configure/src/pylib.rs @@ -26,6 +26,7 @@ pub fn build_pylib(build: &mut Build) -> Result<()> { proto_files: inputs![glob!["proto/anki/*.proto"]], }, )?; + build.add_dependency("pylib:anki:proto", ":rslib:proto:py".into()); build.add_action( "pylib:anki:_fluent.py", diff --git a/build/configure/src/python.rs b/build/configure/src/python.rs index 68dae1a76..1d1a1bc7b 100644 --- a/build/configure/src/python.rs +++ b/build/configure/src/python.rs @@ -109,10 +109,6 @@ impl BuildAction for GenPythonProto { build.add_inputs("protoc", inputs![":protoc_binary"]); build.add_inputs("protoc-gen-mypy", inputs![":pyenv:protoc-gen-mypy"]); build.add_outputs("", python_outputs); - // not a direct dependency, but we include the output interface in our declared - // outputs - build.add_inputs("", inputs![":rslib:proto"]); - build.add_outputs("", vec!["pylib/anki/_backend_generated.py"]); } } diff --git a/build/configure/src/rust.rs b/build/configure/src/rust.rs index 3765e3003..32d9795a7 100644 --- a/build/configure/src/rust.rs +++ b/build/configure/src/rust.rs @@ -21,7 +21,7 @@ use crate::platform::overriden_rust_target_triple; pub fn build_rust(build: &mut Build) -> Result<()> { prepare_translations(build)?; - prepare_proto_descriptors(build)?; + build_proto_descriptors_and_interfaces(build)?; build_rsbridge(build) } @@ -81,16 +81,18 @@ fn prepare_translations(build: &mut Build) -> Result<()> { Ok(()) } -fn prepare_proto_descriptors(build: &mut Build) -> Result<()> { - // build anki_proto and spit out descriptors/Python interface +fn build_proto_descriptors_and_interfaces(build: &mut Build) -> Result<()> { + let outputs = vec![ + RustOutput::Data("descriptors.bin", "rslib/proto/descriptors.bin"), + RustOutput::Data("py", "pylib/anki/_backend_generated.py"), + RustOutput::Data("ts", "ts/lib/backend.d.ts"), + RustOutput::Data("ts", "ts/lib/backend.js"), + ]; build.add_action( "rslib:proto", CargoBuild { inputs: inputs![glob!["{proto,rslib/proto}/**"], ":protoc_binary",], - outputs: &[RustOutput::Data( - "descriptors.bin", - "$builddir/rslib/proto/descriptors.bin", - )], + outputs: &outputs, target: None, extra_args: "-p anki_proto", release_override: None, @@ -110,8 +112,8 @@ fn build_rsbridge(build: &mut Build) -> Result<()> { CargoBuild { inputs: inputs![ glob!["{pylib/rsbridge/**,rslib/**}"], - // declare a dependency on i18n/proto so it gets built first, allowing - // things depending on strings.json to build faster, and ensuring + // declare a dependency on i18n/proto so they get built first, allowing + // things depending on them to build faster, and ensuring // changes to the ftl files trigger a rebuild ":rslib:i18n", ":rslib:proto", diff --git a/build/configure/src/web.rs b/build/configure/src/web.rs index dc033c769..4bec355e1 100644 --- a/build/configure/src/web.rs +++ b/build/configure/src/web.rs @@ -149,7 +149,7 @@ fn build_and_check_tslib(build: &mut Build) -> Result<()> { }, )?; // ensure _service files are generated by rslib - build.add_dependency("ts:lib:proto", inputs![":rslib:proto"]); + build.add_dependency("ts:lib:proto", inputs![":rslib:proto:ts"]); // the generated _service.js files import @tslib/post, and esbuild won't be able // to import the .ts file, so we need to generate a .js file for it build.add_action( diff --git a/rslib/proto/ts.rs b/rslib/proto/ts.rs index cb79e6f45..d241bcd53 100644 --- a/rslib/proto/ts.rs +++ b/rslib/proto/ts.rs @@ -3,81 +3,72 @@ use std::collections::HashSet; use std::fmt::Write as WriteFmt; -use std::io::BufWriter; -use std::io::Write; use std::path::Path; use anki_io::create_dir_all; -use anki_io::create_file; +use anki_io::write_file_if_changed; use anki_proto_gen::BackendService; use anki_proto_gen::Method; use anyhow::Result; use inflections::Inflect; pub(crate) fn write_ts_interface(services: &[BackendService]) -> Result<()> { - let root = Path::new("../../out/ts/lib/anki"); + let root = Path::new("../../out/ts/lib"); create_dir_all(root)?; + let mut dts_out = String::new(); + let mut js_out = String::new(); + let mut referenced_packages = HashSet::new(); + for service in services { if service.name == "BackendAnkidroidService" { continue; } - let service_name = service - .name - .replace("Service", "") - .replace("Backend", "") - .to_snake_case(); - - write_dts_file(root, &service_name, service)?; - write_js_file(root, &service_name, service)?; + for method in service.all_methods() { + let method = MethodDetails::from_method(method); + record_referenced_type(&mut referenced_packages, &method.input_type); + record_referenced_type(&mut referenced_packages, &method.output_type); + write_dts_method(&method, &mut dts_out); + write_js_method(&method, &mut js_out); + } } + let imports = imports(referenced_packages); + write_file_if_changed( + root.join("backend.d.ts"), + format!("{}{}{}", dts_header(), imports, dts_out), + )?; + write_file_if_changed( + root.join("backend.js"), + format!("{}{}{}", js_header(), imports, js_out), + )?; + Ok(()) } -fn write_dts_file(root: &Path, service_name: &str, service: &BackendService) -> Result<()> { - let output_path = root.join(format!("{service_name}_service.d.ts")); - let mut out = BufWriter::new(create_file(output_path)?); - write_dts_header(&mut out)?; - - let mut referenced_packages = HashSet::new(); - let mut method_text = String::new(); - - for method in service.all_methods() { - let method = MethodDetails::from_method(method); - record_referenced_type(&mut referenced_packages, &method.input_type)?; - record_referenced_type(&mut referenced_packages, &method.output_type)?; - write_dts_method(&method, &mut method_text)?; - } - - write_imports(referenced_packages, &mut out)?; - write!(out, "{}", method_text)?; - Ok(()) -} - -fn write_dts_header(out: &mut impl std::io::Write) -> Result<()> { - out.write_all( - br#"// Copyright: Ankitects Pty Ltd and contributors +fn dts_header() -> String { + r#"// Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; https://www.gnu.org/licenses/agpl.html import type { PlainMessage } from "@bufbuild/protobuf"; -import type { PostProtoOptions } from "../post"; -"#, - )?; - Ok(()) +import type { PostProtoOptions } from "./post"; +"# + .into() } -fn write_imports(referenced_packages: HashSet, out: &mut impl Write) -> Result<()> { +fn imports(referenced_packages: HashSet) -> String { + let mut out = String::new(); for package in referenced_packages { writeln!( - out, - "import * as {} from \"./{}_pb\";", + &mut out, + "import * as {} from \"./anki/{}_pb\";", package, package.to_snake_case() - )?; + ) + .unwrap(); } - Ok(()) + out } fn write_dts_method( @@ -88,43 +79,21 @@ fn write_dts_method( comments, }: &MethodDetails, out: &mut String, -) -> Result<()> { +) { let comments = format_comments(comments); writeln!( out, r#"{comments}export declare function {method_name}(input: PlainMessage<{input_type}>, options?: PostProtoOptions): Promise<{output_type}>;"# - )?; - Ok(()) + ).unwrap() } -fn write_js_file(root: &Path, service_name: &str, service: &BackendService) -> Result<()> { - let output_path = root.join(format!("{service_name}_service.js")); - let mut out = BufWriter::new(create_file(output_path)?); - write_js_header(&mut out)?; - - let mut referenced_packages = HashSet::new(); - let mut method_text = String::new(); - for method in service.all_methods() { - let method = MethodDetails::from_method(method); - record_referenced_type(&mut referenced_packages, &method.input_type)?; - record_referenced_type(&mut referenced_packages, &method.output_type)?; - write_js_method(&method, &mut method_text)?; - } - - write_imports(referenced_packages, &mut out)?; - write!(out, "{}", method_text)?; - Ok(()) -} - -fn write_js_header(out: &mut impl std::io::Write) -> Result<()> { - out.write_all( - br#"// Copyright: Ankitects Pty Ltd and contributors +fn js_header() -> String { + r#"// Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; https://www.gnu.org/licenses/agpl.html -import { postProto } from "../post"; -"#, - )?; - Ok(()) +import { postProto } from "./post"; +"# + .into() } fn write_js_method( @@ -135,15 +104,15 @@ fn write_js_method( .. }: &MethodDetails, out: &mut String, -) -> Result<()> { +) { write!( out, r#"export async function {method_name}(input, options = {{}}) {{ return await postProto("{method_name}", new {input_type}(input), {output_type}, options); }} "# - )?; - Ok(()) + ) + .unwrap(); } fn format_comments(comments: &Option) -> String { @@ -175,12 +144,8 @@ impl MethodDetails { } } -fn record_referenced_type( - referenced_packages: &mut HashSet, - type_name: &str, -) -> Result<()> { +fn record_referenced_type(referenced_packages: &mut HashSet, type_name: &str) { referenced_packages.insert(type_name.split('.').next().unwrap().to_string()); - Ok(()) } // e.g. anki.import_export.ImportResponse -> diff --git a/ts/card-info/CardInfo.svelte b/ts/card-info/CardInfo.svelte index 4ac7e5f44..e47f78cd7 100644 --- a/ts/card-info/CardInfo.svelte +++ b/ts/card-info/CardInfo.svelte @@ -7,7 +7,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html CardStatsResponse, CardStatsResponse_StatsRevlogEntry, } from "@tslib/anki/stats_pb"; - import { cardStats } from "@tslib/anki/stats_service"; + import { cardStats } from "@tslib/backend"; import Container from "../components/Container.svelte"; import Row from "../components/Row.svelte"; diff --git a/ts/change-notetype/index.ts b/ts/change-notetype/index.ts index b2426cf10..dbbe00686 100644 --- a/ts/change-notetype/index.ts +++ b/ts/change-notetype/index.ts @@ -3,7 +3,7 @@ import "./change-notetype-base.scss"; -import { getChangeNotetypeInfo, getNotetypeNames } from "@tslib/anki/notetypes_service"; +import { getChangeNotetypeInfo, getNotetypeNames } from "@tslib/backend"; import { ModuleName, setupI18n } from "@tslib/i18n"; import { checkNightMode } from "@tslib/nightmode"; diff --git a/ts/change-notetype/lib.ts b/ts/change-notetype/lib.ts index 5c6d4878b..662f7fe70 100644 --- a/ts/change-notetype/lib.ts +++ b/ts/change-notetype/lib.ts @@ -2,7 +2,7 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import type { ChangeNotetypeInfo, ChangeNotetypeRequest, NotetypeNames } from "@tslib/anki/notetypes_pb"; -import { changeNotetype, getChangeNotetypeInfo } from "@tslib/anki/notetypes_service"; +import { changeNotetype, getChangeNotetypeInfo } from "@tslib/backend"; import * as tr from "@tslib/ftl"; import { isEqual } from "lodash-es"; import type { Readable } from "svelte/store"; diff --git a/ts/congrats/index.ts b/ts/congrats/index.ts index 13fc8d3b6..a87f1acef 100644 --- a/ts/congrats/index.ts +++ b/ts/congrats/index.ts @@ -3,7 +3,7 @@ import "./congrats-base.scss"; -import { congratsInfo } from "@tslib/anki/scheduler_service"; +import { congratsInfo } from "@tslib/backend"; import { ModuleName, setupI18n } from "@tslib/i18n"; import { checkNightMode } from "@tslib/nightmode"; diff --git a/ts/deck-options/index.ts b/ts/deck-options/index.ts index 21b2417c5..7d8551295 100644 --- a/ts/deck-options/index.ts +++ b/ts/deck-options/index.ts @@ -46,7 +46,7 @@ export async function setupDeckOptions(did_: number): Promise { }); } -import { getDeckConfigsForUpdate } from "@tslib/anki/deck_config_service"; +import { getDeckConfigsForUpdate } from "@tslib/backend"; import TitledContainer from "../components/TitledContainer.svelte"; import EnumSelectorRow from "./EnumSelectorRow.svelte"; diff --git a/ts/deck-options/lib.ts b/ts/deck-options/lib.ts index 8141121bd..16a89faf3 100644 --- a/ts/deck-options/lib.ts +++ b/ts/deck-options/lib.ts @@ -8,7 +8,7 @@ import type { UpdateDeckConfigsRequest, } from "@tslib/anki/deck_config_pb"; import { DeckConfig, DeckConfig_Config, DeckConfigsForUpdate_CurrentDeck_Limits } from "@tslib/anki/deck_config_pb"; -import { updateDeckConfigs } from "@tslib/anki/deck_config_service"; +import { updateDeckConfigs } from "@tslib/backend"; import { localeCompare } from "@tslib/i18n"; import { cloneDeep, isEqual } from "lodash-es"; import type { Readable, Writable } from "svelte/store"; diff --git a/ts/graphs/WithGraphData.svelte b/ts/graphs/WithGraphData.svelte index 0ae211ca3..191470d1a 100644 --- a/ts/graphs/WithGraphData.svelte +++ b/ts/graphs/WithGraphData.svelte @@ -4,11 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -->