Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bindgen: Generate spec schema if missing #7636

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

### Internals
* Follow on to ([PR #7300](https://github.com/realm/realm-core/pull/7300)) to allow SDKs to construct a fake user for testing SyncManager::get_user -> App::create_fake_user_for_testing ([PR #7632](https://github.com/realm/realm-core/pull/7632))
* Bindgen: Generating spec schema if missing. ([PR #7636](https://github.com/realm/realm-core/pull/7636))

----------------------------------------------

Expand Down
10 changes: 2 additions & 8 deletions bindgen/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ set(SCHEMA_FILE ${CMAKE_CURRENT_SOURCE_DIR}/generated/spec.schema.json)
add_custom_command(
OUTPUT ${SCHEMA_FILE}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${NPX} typescript-json-schema ${CMAKE_CURRENT_SOURCE_DIR}/tsconfig.json RelaxedSpec --include ${CMAKE_CURRENT_SOURCE_DIR}/src/spec/relaxed-model.ts --out ${SCHEMA_FILE} --required --noExtraProps --aliasRefs
COMMAND ${NPX} tsx -- "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/realm-bindgen.ts" generate-schema
VERBATIM
MAIN_DEPENDENCY src/spec/relaxed-model.ts
DEPENDS
Expand Down Expand Up @@ -48,12 +48,6 @@ function(bindgen)
endif()
endforeach()

# Don't want to set cmake_minimum_required() here because it has other effects and needs to be done early.
# Remove this once https://github.com/realm/realm-core/issues/6537 bumps realm-core's minimum.
if(NOT CMAKE_CURRENT_FUNCTION_LIST_DIR)
message(FATAL_ERROR "bindgen() requires cmake 3.17+")
endif()

get_property(BINDGEN_LIB_TS_FILES TARGET BindgenSpecJsonSchema PROPERTY BINDGEN_LIB_TS_FILES)
get_property(NPX TARGET BindgenSpecJsonSchema PROPERTY NPX)

Expand All @@ -62,7 +56,7 @@ function(bindgen)
add_custom_command(
OUTPUT ${BINDGEN_OUTPUTS}
WORKING_DIRECTORY ${CMAKE_CURRENT_FUNCTION_LIST_DIR}
COMMAND ${NPX} tsx -- "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/realm-bindgen.ts"
COMMAND ${NPX} tsx -- "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/realm-bindgen.ts" generate
--spec "$<JOIN:${CORE_SPEC_FILE};${BINDGEN_SPECS},;--spec;>"
"$<$<BOOL:${BINDGEN_OPTIN}>:--opt-in;${BINDGEN_OPTIN}>"
--template "${BINDGEN_TEMPLATE}"
Expand Down
18 changes: 13 additions & 5 deletions bindgen/src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import path from "path";

import { debug, enableDebugging } from "./debug";
import { generate } from "./generator";
import { OptInSpec, InvalidSpecError, parseOptInSpec, parseSpecs } from "./spec";
import { OptInSpec, InvalidSpecError, parseOptInSpec, parseSpecs, generateSchema } from "./spec";
import { Template, importTemplate } from "./templates";

type GenerateOptions = {
Expand Down Expand Up @@ -109,13 +109,22 @@ program
optInSpec = parseOptInSpec(optInPath);
}
generate({ rawSpec, optInSpec, template: await template, outputPath });
process.exit(0);
} catch (err) {
printError(err);
process.exit(1);
process.exitCode = 1;
}
});

program.command("generate-schema").action(() => {
try {
console.log("Generating spec.schema.json");
generateSchema();
} catch (err) {
printError(err);
process.exitCode = 1;
}
});

program
.command("validate")
.addOption(specOption)
Expand All @@ -129,9 +138,8 @@ program
try {
parseSpecs(specPaths);
console.log(chalk.green("Validation passed!"));
process.exit(0);
} catch (err) {
printError(err);
process.exit(1);
process.exitCode = 1;
}
});
44 changes: 40 additions & 4 deletions bindgen/src/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
//
////////////////////////////////////////////////////////////////////////////

import Ajv, { ErrorObject } from "ajv";
import Ajv, { ErrorObject, ValidateFunction } from "ajv";
import { strict as assert } from "assert";
import chalk from "chalk";
import fs from "fs";
import yaml from "yaml";
import cp from "child_process";
import path from "path";

import {
ClassSpec,
Expand Down Expand Up @@ -65,9 +67,42 @@ export class InvalidSpecError extends Error {
}

const ajv = new Ajv({ allowUnionTypes: true });
const schemaFile = new URL("../generated/spec.schema.json", import.meta.url);
const schemaJson = JSON.parse(fs.readFileSync(schemaFile, { encoding: "utf8" }));
export const validate = ajv.compile<RelaxedSpec>(schemaJson);
const rootPath = new URL("../..", import.meta.url).pathname;
const schemaFile = path.resolve(rootPath, "bindgen/generated/spec.schema.json");

export function generateSchema() {
cp.spawnSync(
"typescript-json-schema",
[
path.resolve(rootPath, "bindgen/tsconfig.json"),
"RelaxedSpec",
"--include",
path.resolve(rootPath, "bindgen/src/spec/relaxed-model.ts"),
"--out",
schemaFile,
"--required",
"--noExtraProps",
"--aliasRefs",
],
{ stdio: "inherit" },
);
}

let validate: ValidateFunction<RelaxedSpec> | null;

function compileValidate(): ValidateFunction<RelaxedSpec> {
if (validate) {
return validate;
} else {
if (!fs.existsSync(schemaFile)) {
console.log("Generating spec.schema.json");
generateSchema();
}
const schemaJson = JSON.parse(fs.readFileSync(schemaFile, { encoding: "utf8" }));
validate = ajv.compile<RelaxedSpec>(schemaJson);
return validate;
}
}

function parseYaml(filePath: string): unknown {
const text = fs.readFileSync(filePath, { encoding: "utf8" });
Expand Down Expand Up @@ -103,6 +138,7 @@ export function parseSpecs(specs: ReadonlyArray<string>): Spec {

export function parseSpec(filePath: string): AnySpec {
const parsed = parseYaml(filePath);
const validate = compileValidate();
const isValid = validate(parsed);
if (isValid) {
return normalizeSpec(parsed);
Expand Down
Loading