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.
This commit is contained in:
Damien Elmes 2023-07-03 13:42:20 +10:00
parent ffadbf577b
commit f3b6deefe9
20 changed files with 74 additions and 116 deletions

View file

@ -26,6 +26,7 @@ pub fn build_pylib(build: &mut Build) -> Result<()> {
proto_files: inputs![glob!["proto/anki/*.proto"]], proto_files: inputs![glob!["proto/anki/*.proto"]],
}, },
)?; )?;
build.add_dependency("pylib:anki:proto", ":rslib:proto:py".into());
build.add_action( build.add_action(
"pylib:anki:_fluent.py", "pylib:anki:_fluent.py",

View file

@ -109,10 +109,6 @@ impl BuildAction for GenPythonProto {
build.add_inputs("protoc", inputs![":protoc_binary"]); build.add_inputs("protoc", inputs![":protoc_binary"]);
build.add_inputs("protoc-gen-mypy", inputs![":pyenv:protoc-gen-mypy"]); build.add_inputs("protoc-gen-mypy", inputs![":pyenv:protoc-gen-mypy"]);
build.add_outputs("", python_outputs); 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"]);
} }
} }

View file

@ -21,7 +21,7 @@ use crate::platform::overriden_rust_target_triple;
pub fn build_rust(build: &mut Build) -> Result<()> { pub fn build_rust(build: &mut Build) -> Result<()> {
prepare_translations(build)?; prepare_translations(build)?;
prepare_proto_descriptors(build)?; build_proto_descriptors_and_interfaces(build)?;
build_rsbridge(build) build_rsbridge(build)
} }
@ -81,16 +81,18 @@ fn prepare_translations(build: &mut Build) -> Result<()> {
Ok(()) Ok(())
} }
fn prepare_proto_descriptors(build: &mut Build) -> Result<()> { fn build_proto_descriptors_and_interfaces(build: &mut Build) -> Result<()> {
// build anki_proto and spit out descriptors/Python interface 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( build.add_action(
"rslib:proto", "rslib:proto",
CargoBuild { CargoBuild {
inputs: inputs![glob!["{proto,rslib/proto}/**"], ":protoc_binary",], inputs: inputs![glob!["{proto,rslib/proto}/**"], ":protoc_binary",],
outputs: &[RustOutput::Data( outputs: &outputs,
"descriptors.bin",
"$builddir/rslib/proto/descriptors.bin",
)],
target: None, target: None,
extra_args: "-p anki_proto", extra_args: "-p anki_proto",
release_override: None, release_override: None,
@ -110,8 +112,8 @@ fn build_rsbridge(build: &mut Build) -> Result<()> {
CargoBuild { CargoBuild {
inputs: inputs![ inputs: inputs![
glob!["{pylib/rsbridge/**,rslib/**}"], glob!["{pylib/rsbridge/**,rslib/**}"],
// declare a dependency on i18n/proto so it gets built first, allowing // declare a dependency on i18n/proto so they get built first, allowing
// things depending on strings.json to build faster, and ensuring // things depending on them to build faster, and ensuring
// changes to the ftl files trigger a rebuild // changes to the ftl files trigger a rebuild
":rslib:i18n", ":rslib:i18n",
":rslib:proto", ":rslib:proto",

View file

@ -149,7 +149,7 @@ fn build_and_check_tslib(build: &mut Build) -> Result<()> {
}, },
)?; )?;
// ensure _service files are generated by rslib // 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 // 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 // to import the .ts file, so we need to generate a .js file for it
build.add_action( build.add_action(

View file

@ -3,81 +3,72 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt::Write as WriteFmt; use std::fmt::Write as WriteFmt;
use std::io::BufWriter;
use std::io::Write;
use std::path::Path; use std::path::Path;
use anki_io::create_dir_all; 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::BackendService;
use anki_proto_gen::Method; use anki_proto_gen::Method;
use anyhow::Result; use anyhow::Result;
use inflections::Inflect; use inflections::Inflect;
pub(crate) fn write_ts_interface(services: &[BackendService]) -> Result<()> { 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)?; 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 { for service in services {
if service.name == "BackendAnkidroidService" { if service.name == "BackendAnkidroidService" {
continue; continue;
} }
let service_name = service for method in service.all_methods() {
.name let method = MethodDetails::from_method(method);
.replace("Service", "") record_referenced_type(&mut referenced_packages, &method.input_type);
.replace("Backend", "") record_referenced_type(&mut referenced_packages, &method.output_type);
.to_snake_case(); write_dts_method(&method, &mut dts_out);
write_js_method(&method, &mut js_out);
write_dts_file(root, &service_name, service)?; }
write_js_file(root, &service_name, service)?;
} }
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(()) Ok(())
} }
fn write_dts_file(root: &Path, service_name: &str, service: &BackendService) -> Result<()> { fn dts_header() -> String {
let output_path = root.join(format!("{service_name}_service.d.ts")); r#"// Copyright: Ankitects Pty Ltd and contributors
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
// License: GNU AGPL, version 3 or later; https://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; https://www.gnu.org/licenses/agpl.html
import type { PlainMessage } from "@bufbuild/protobuf"; import type { PlainMessage } from "@bufbuild/protobuf";
import type { PostProtoOptions } from "../post"; import type { PostProtoOptions } from "./post";
"#, "#
)?; .into()
Ok(())
} }
fn write_imports(referenced_packages: HashSet<String>, out: &mut impl Write) -> Result<()> { fn imports(referenced_packages: HashSet<String>) -> String {
let mut out = String::new();
for package in referenced_packages { for package in referenced_packages {
writeln!( writeln!(
out, &mut out,
"import * as {} from \"./{}_pb\";", "import * as {} from \"./anki/{}_pb\";",
package, package,
package.to_snake_case() package.to_snake_case()
)?; )
.unwrap();
} }
Ok(()) out
} }
fn write_dts_method( fn write_dts_method(
@ -88,43 +79,21 @@ fn write_dts_method(
comments, comments,
}: &MethodDetails, }: &MethodDetails,
out: &mut String, out: &mut String,
) -> Result<()> { ) {
let comments = format_comments(comments); let comments = format_comments(comments);
writeln!( writeln!(
out, out,
r#"{comments}export declare function {method_name}(input: PlainMessage<{input_type}>, options?: PostProtoOptions): Promise<{output_type}>;"# r#"{comments}export declare function {method_name}(input: PlainMessage<{input_type}>, options?: PostProtoOptions): Promise<{output_type}>;"#
)?; ).unwrap()
Ok(())
} }
fn write_js_file(root: &Path, service_name: &str, service: &BackendService) -> Result<()> { fn js_header() -> String {
let output_path = root.join(format!("{service_name}_service.js")); r#"// Copyright: Ankitects Pty Ltd and contributors
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
// License: GNU AGPL, version 3 or later; https://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; https://www.gnu.org/licenses/agpl.html
import { postProto } from "../post"; import { postProto } from "./post";
"#, "#
)?; .into()
Ok(())
} }
fn write_js_method( fn write_js_method(
@ -135,15 +104,15 @@ fn write_js_method(
.. ..
}: &MethodDetails, }: &MethodDetails,
out: &mut String, out: &mut String,
) -> Result<()> { ) {
write!( write!(
out, out,
r#"export async function {method_name}(input, options = {{}}) {{ r#"export async function {method_name}(input, options = {{}}) {{
return await postProto("{method_name}", new {input_type}(input), {output_type}, options); return await postProto("{method_name}", new {input_type}(input), {output_type}, options);
}} }}
"# "#
)?; )
Ok(()) .unwrap();
} }
fn format_comments(comments: &Option<String>) -> String { fn format_comments(comments: &Option<String>) -> String {
@ -175,12 +144,8 @@ impl MethodDetails {
} }
} }
fn record_referenced_type( fn record_referenced_type(referenced_packages: &mut HashSet<String>, type_name: &str) {
referenced_packages: &mut HashSet<String>,
type_name: &str,
) -> Result<()> {
referenced_packages.insert(type_name.split('.').next().unwrap().to_string()); referenced_packages.insert(type_name.split('.').next().unwrap().to_string());
Ok(())
} }
// e.g. anki.import_export.ImportResponse -> // e.g. anki.import_export.ImportResponse ->

View file

@ -7,7 +7,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
CardStatsResponse, CardStatsResponse,
CardStatsResponse_StatsRevlogEntry, CardStatsResponse_StatsRevlogEntry,
} from "@tslib/anki/stats_pb"; } from "@tslib/anki/stats_pb";
import { cardStats } from "@tslib/anki/stats_service"; import { cardStats } from "@tslib/backend";
import Container from "../components/Container.svelte"; import Container from "../components/Container.svelte";
import Row from "../components/Row.svelte"; import Row from "../components/Row.svelte";

View file

@ -3,7 +3,7 @@
import "./change-notetype-base.scss"; 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 { ModuleName, setupI18n } from "@tslib/i18n";
import { checkNightMode } from "@tslib/nightmode"; import { checkNightMode } from "@tslib/nightmode";

View file

@ -2,7 +2,7 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // 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 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 * as tr from "@tslib/ftl";
import { isEqual } from "lodash-es"; import { isEqual } from "lodash-es";
import type { Readable } from "svelte/store"; import type { Readable } from "svelte/store";

View file

@ -3,7 +3,7 @@
import "./congrats-base.scss"; import "./congrats-base.scss";
import { congratsInfo } from "@tslib/anki/scheduler_service"; import { congratsInfo } from "@tslib/backend";
import { ModuleName, setupI18n } from "@tslib/i18n"; import { ModuleName, setupI18n } from "@tslib/i18n";
import { checkNightMode } from "@tslib/nightmode"; import { checkNightMode } from "@tslib/nightmode";

View file

@ -46,7 +46,7 @@ export async function setupDeckOptions(did_: number): Promise<DeckOptionsPage> {
}); });
} }
import { getDeckConfigsForUpdate } from "@tslib/anki/deck_config_service"; import { getDeckConfigsForUpdate } from "@tslib/backend";
import TitledContainer from "../components/TitledContainer.svelte"; import TitledContainer from "../components/TitledContainer.svelte";
import EnumSelectorRow from "./EnumSelectorRow.svelte"; import EnumSelectorRow from "./EnumSelectorRow.svelte";

View file

@ -8,7 +8,7 @@ import type {
UpdateDeckConfigsRequest, UpdateDeckConfigsRequest,
} from "@tslib/anki/deck_config_pb"; } from "@tslib/anki/deck_config_pb";
import { DeckConfig, DeckConfig_Config, DeckConfigsForUpdate_CurrentDeck_Limits } 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 { localeCompare } from "@tslib/i18n";
import { cloneDeep, isEqual } from "lodash-es"; import { cloneDeep, isEqual } from "lodash-es";
import type { Readable, Writable } from "svelte/store"; import type { Readable, Writable } from "svelte/store";

View file

@ -4,11 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
import type { GraphsResponse } from "@tslib/anki/stats_pb"; import type { GraphsResponse } from "@tslib/anki/stats_pb";
import { import { getGraphPreferences, graphs, setGraphPreferences } from "@tslib/backend";
getGraphPreferences,
graphs,
setGraphPreferences,
} from "@tslib/anki/stats_service";
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store";
import { autoSavingPrefs } from "../sveltelib/preferences"; import { autoSavingPrefs } from "../sveltelib/preferences";

View file

@ -2,7 +2,7 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { OpChanges } from "@tslib/anki/collection_pb"; import type { OpChanges } from "@tslib/anki/collection_pb";
import { addImageOcclusionNote, updateImageOcclusionNote } from "@tslib/anki/image_occlusion_service"; import { addImageOcclusionNote, updateImageOcclusionNote } from "@tslib/backend";
import * as tr from "@tslib/ftl"; import * as tr from "@tslib/ftl";
import { get } from "svelte/store"; import { get } from "svelte/store";

View file

@ -2,7 +2,7 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { protoBase64 } from "@bufbuild/protobuf"; import { protoBase64 } from "@bufbuild/protobuf";
import { getImageForOcclusion, getImageOcclusionNote } from "@tslib/anki/image_occlusion_service"; import { getImageForOcclusion, getImageOcclusionNote } from "@tslib/backend";
import * as tr from "@tslib/ftl"; import * as tr from "@tslib/ftl";
import { fabric } from "fabric"; import { fabric } from "fabric";
import type { PanZoom } from "panzoom"; import type { PanZoom } from "panzoom";

View file

@ -4,7 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
import type { CsvMetadata_MappedNotetype } from "@tslib/anki/import_export_pb"; import type { CsvMetadata_MappedNotetype } from "@tslib/anki/import_export_pb";
import { getFieldNames } from "@tslib/anki/notetypes_service"; import { getFieldNames } from "@tslib/backend";
import * as tr from "@tslib/ftl"; import * as tr from "@tslib/ftl";
import Spacer from "../components/Spacer.svelte"; import Spacer from "../components/Spacer.svelte";

View file

@ -11,8 +11,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
CsvMetadata_MappedNotetype, CsvMetadata_MappedNotetype,
CsvMetadata_MatchScope, CsvMetadata_MatchScope,
} from "@tslib/anki/import_export_pb"; } from "@tslib/anki/import_export_pb";
import { getCsvMetadata, importCsv } from "@tslib/anki/import_export_service";
import type { NotetypeNameId } from "@tslib/anki/notetypes_pb"; import type { NotetypeNameId } from "@tslib/anki/notetypes_pb";
import { getCsvMetadata, importCsv } from "@tslib/backend";
import * as tr from "@tslib/ftl"; import * as tr from "@tslib/ftl";
import Col from "../components/Col.svelte"; import Col from "../components/Col.svelte";

View file

@ -3,9 +3,7 @@
import "./import-csv-base.scss"; import "./import-csv-base.scss";
import { getDeckNames } from "@tslib/anki/decks_service"; import { getCsvMetadata, getDeckNames, getNotetypeNames } from "@tslib/backend";
import { getCsvMetadata } from "@tslib/anki/import_export_service";
import { getNotetypeNames } from "@tslib/anki/notetypes_service";
import { ModuleName, setupI18n } from "@tslib/i18n"; import { ModuleName, setupI18n } from "@tslib/i18n";
import { checkNightMode } from "@tslib/nightmode"; import { checkNightMode } from "@tslib/nightmode";

View file

@ -4,7 +4,7 @@
import "intl-pluralrules"; import "intl-pluralrules";
import { FluentBundle, FluentResource } from "@fluent/bundle"; import { FluentBundle, FluentResource } from "@fluent/bundle";
import { i18nResources } from "@tslib/anki/i18n_service"; import { i18nResources } from "@tslib/backend";
import { firstLanguage, setBundles } from "./bundles"; import { firstLanguage, setBundles } from "./bundles";
import type { ModuleName } from "./modules"; import type { ModuleName } from "./modules";

View file

@ -4,7 +4,7 @@
import type { JsonValue } from "@bufbuild/protobuf"; import type { JsonValue } from "@bufbuild/protobuf";
import type { SchedulingContext, SchedulingStatesWithContext } from "@tslib/anki/scheduler_pb"; import type { SchedulingContext, SchedulingStatesWithContext } from "@tslib/anki/scheduler_pb";
import { SchedulingStates } from "@tslib/anki/scheduler_pb"; import { SchedulingStates } from "@tslib/anki/scheduler_pb";
import { getSchedulingStatesWithContext, setSchedulingStates } from "@tslib/anki/scheduler_service"; import { getSchedulingStatesWithContext, setSchedulingStates } from "@tslib/backend";
interface CustomDataStates { interface CustomDataStates {
again: Record<string, unknown>; again: Record<string, unknown>;

View file

@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { createEventDispatcher, setContext, tick } from "svelte"; import { createEventDispatcher, setContext, tick } from "svelte";
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { completeTag } from "@tslib/anki/tags_service"; import { completeTag } from "@tslib/backend";
import Shortcut from "../components/Shortcut.svelte"; import Shortcut from "../components/Shortcut.svelte";
import { execCommand } from "../domlib"; import { execCommand } from "../domlib";
import { tagActionsShortcutsKey } from "@tslib/context-keys"; import { tagActionsShortcutsKey } from "@tslib/context-keys";