mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
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:
parent
ffadbf577b
commit
f3b6deefe9
20 changed files with 74 additions and 116 deletions
|
@ -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",
|
||||
|
|
|
@ -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"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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<String>, out: &mut impl Write) -> Result<()> {
|
||||
fn imports(referenced_packages: HashSet<String>) -> 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>) -> String {
|
||||
|
@ -175,12 +144,8 @@ impl MethodDetails {
|
|||
}
|
||||
}
|
||||
|
||||
fn record_referenced_type(
|
||||
referenced_packages: &mut HashSet<String>,
|
||||
type_name: &str,
|
||||
) -> Result<()> {
|
||||
fn record_referenced_type(referenced_packages: &mut HashSet<String>, type_name: &str) {
|
||||
referenced_packages.insert(type_name.split('.').next().unwrap().to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// e.g. anki.import_export.ImportResponse ->
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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 EnumSelectorRow from "./EnumSelectorRow.svelte";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -4,11 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
-->
|
||||
<script lang="ts">
|
||||
import type { GraphsResponse } from "@tslib/anki/stats_pb";
|
||||
import {
|
||||
getGraphPreferences,
|
||||
graphs,
|
||||
setGraphPreferences,
|
||||
} from "@tslib/anki/stats_service";
|
||||
import { getGraphPreferences, graphs, setGraphPreferences } from "@tslib/backend";
|
||||
import type { Writable } from "svelte/store";
|
||||
|
||||
import { autoSavingPrefs } from "../sveltelib/preferences";
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
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 { get } from "svelte/store";
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
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 { fabric } from "fabric";
|
||||
import type { PanZoom } from "panzoom";
|
||||
|
|
|
@ -4,7 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
-->
|
||||
<script lang="ts">
|
||||
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 Spacer from "../components/Spacer.svelte";
|
||||
|
|
|
@ -11,8 +11,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
CsvMetadata_MappedNotetype,
|
||||
CsvMetadata_MatchScope,
|
||||
} from "@tslib/anki/import_export_pb";
|
||||
import { getCsvMetadata, importCsv } from "@tslib/anki/import_export_service";
|
||||
import type { NotetypeNameId } from "@tslib/anki/notetypes_pb";
|
||||
import { getCsvMetadata, importCsv } from "@tslib/backend";
|
||||
import * as tr from "@tslib/ftl";
|
||||
|
||||
import Col from "../components/Col.svelte";
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
|
||||
import "./import-csv-base.scss";
|
||||
|
||||
import { getDeckNames } from "@tslib/anki/decks_service";
|
||||
import { getCsvMetadata } from "@tslib/anki/import_export_service";
|
||||
import { getNotetypeNames } from "@tslib/anki/notetypes_service";
|
||||
import { getCsvMetadata, getDeckNames, getNotetypeNames } from "@tslib/backend";
|
||||
import { ModuleName, setupI18n } from "@tslib/i18n";
|
||||
import { checkNightMode } from "@tslib/nightmode";
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import "intl-pluralrules";
|
||||
|
||||
import { FluentBundle, FluentResource } from "@fluent/bundle";
|
||||
import { i18nResources } from "@tslib/anki/i18n_service";
|
||||
import { i18nResources } from "@tslib/backend";
|
||||
|
||||
import { firstLanguage, setBundles } from "./bundles";
|
||||
import type { ModuleName } from "./modules";
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import type { JsonValue } from "@bufbuild/protobuf";
|
||||
import type { SchedulingContext, SchedulingStatesWithContext } 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 {
|
||||
again: Record<string, unknown>;
|
||||
|
|
|
@ -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 type { 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 { execCommand } from "../domlib";
|
||||
import { tagActionsShortcutsKey } from "@tslib/context-keys";
|
||||
|
|
Loading…
Reference in a new issue