Skip to content

Commit

Permalink
Enable ability to codegen JSI C++ TurboModule specs (#10909)
Browse files Browse the repository at this point in the history
* Example of using Meta's c++ JSI codegen

* Make codegen of jsispec's optional

* Use codegen in MS.RN.IntegrationTests tests of JSI turbomodules

* cleanup

* Change files

* build fix

* typo

* build fix

* build fix

* attributes

* update

* formatting

* lint:fix

* Try to disable padding warning in tests

* disable another warning in test code

* remove jsi from samples

* minor change
  • Loading branch information
acoates-ms authored Nov 20, 2022
1 parent 4995375 commit ce7f7ac
Show file tree
Hide file tree
Showing 23 changed files with 7,311 additions and 275 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# Spec Codegen uses LF
*Spec.g.h eol=lf
/vnext/codegen/** eol=lf
/vnext/Microsoft.ReactNative.IntegrationTests/codegen/** eol=lf
/packages/sample-apps/codegen/** eol=lf

# Force Visual Studio project files (mostly XML) to CRLF
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Enable ability to codegen JSI C++ TurboModule specs",
"packageName": "@react-native-windows/cli",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Enable ability to codegen JSI C++ TurboModule specs",
"packageName": "@react-native-windows/codegen",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Update codegen version",
"packageName": "react-native-windows",
"email": "[email protected]",
"dependentChangeType": "patch"
}
15 changes: 11 additions & 4 deletions packages/@react-native-windows/cli/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,25 @@ export class CodeGenWindows {
? path.join(this.root, pkgJson.codegenConfig.jsSrcsDir)
: this.root;

const generators = pkgJson.codegenConfig.windows.generators ?? [
'modulesWindows',
];

const jsRootPathRelative = path.relative(process.cwd(), jsRootDir);
const options = {
files: [
`${jsRootPathRelative}${
jsRootPathRelative ? '/' : ''
}**/*Native*.[jt]s`,
],
namespace: projectNamespace,
libraryName: projectName,
outdir: path.join(this.root, 'codegen'),
methodonly: false,
ts: false,
methodOnly: false,
modulesCxx: generators.indexOf('modulesCxx') !== -1,
modulesTypeScriptTypes:
generators.indexOf('modulesTypeScriptTypes') !== -1,
modulesWindows: generators.indexOf('modulesWindows') !== -1,
namespace: projectNamespace,
outputDirectory: path.join(this.root, 'codegen'),
test: !!this.options.check,
};

Expand Down
16 changes: 13 additions & 3 deletions packages/@react-native-windows/codegen/src/Cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,27 @@ const argv = yargs.options({
array: true,
describe: 'glob patterns for files which contains specs',
},
ts: {
modulesTypeScriptTypes: {
type: 'boolean',
describe: 'generate turbo module definition files in TypeScript',
default: false,
},
methodonly: {
modulesCxx: {
type: 'boolean',
describe: 'generate C++ JSI turbo module spec files',
default: false,
},
modulesWindows: {
type: 'boolean',
describe: 'generate turbo module spec files for REACT_MODULE',
default: false,
},
methodOnly: {
type: 'boolean',
describe: 'generate only method metadata in C++ turbo module spec',
default: false,
},
outdir: {
outputDirectory: {
type: 'string',
describe: 'output directory',
default: 'codegen',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ struct ::_MODULE_NAME_::Spec : winrt::Microsoft::ReactNative::TurboModuleSpec {
`;

export function createNM2Generator({
methodOnly,
namespace,
methodonly,
}: {
methodOnly: boolean;
namespace: string;
methodonly: boolean;
}) {
return (
_libraryName: string,
Expand Down Expand Up @@ -85,7 +85,7 @@ ${methods[0]}

// prepare constants
const constants = generateValidateConstants(nativeModule, aliases);
if (constants !== undefined && !methodonly) {
if (constants !== undefined && !methodOnly) {
tuples = `
static constexpr auto constants = std::tuple{
${constants[0]}
Expand Down
100 changes: 66 additions & 34 deletions packages/@react-native-windows/codegen/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ const schemaValidator = require(path.resolve(

interface Options {
libraryName: string;
schema: SchemaType;
outputDirectory: string;
methodOnly: boolean;
modulesCxx: boolean;
moduleSpecName: string;
modulesTypeScriptTypes: boolean;
modulesWindows: boolean;
namespace: string;
methodonly: boolean;
ts: boolean;
outputDirectory: string;
schema: SchemaType;
}

interface Config {
Expand Down Expand Up @@ -179,12 +181,14 @@ export function combineSchemas(files: string[]): SchemaType {
export function generate(
{
libraryName,
schema,
outputDirectory,
methodOnly,
modulesCxx,
moduleSpecName,
modulesTypeScriptTypes,
modulesWindows,
namespace,
methodonly,
ts,
outputDirectory,
schema,
}: Options,
{/*generators,*/ test}: Config,
): boolean {
Expand All @@ -204,10 +208,18 @@ export function generate(
);

const generateNM2 = createNM2Generator({
methodOnly,
namespace,
methodonly,
});

const generateJsiModuleH = require(path.resolve(
rncodegenPath,
'lib/generators/modules/GenerateModuleH',
)).generate;
const generateJsiModuleCpp = require(path.resolve(
rncodegenPath,
'lib/generators/modules/GenerateModuleCpp',
)).generate;
const generatorPropsH = require(path.resolve(
rncodegenPath,
'lib/generators/components/GeneratePropsH',
Expand Down Expand Up @@ -237,33 +249,43 @@ export function generate(
'lib/generators/components/GenerateEventEmitterCpp',
)).generate;

normalizeFileMap(
generateNM2(libraryName, schema, moduleSpecName),
outputDirectory,
generatedFiles,
);
const moduleGenerators = [];

if (ts) {
normalizeFileMap(
generateTypeScript(libraryName, schema, moduleSpecName),
outputDirectory,
generatedFiles,
);
if (modulesWindows) {
moduleGenerators.push(generateNM2);
}

if (modulesCxx) {
moduleGenerators.push(generateJsiModuleH);
moduleGenerators.push(generateJsiModuleCpp);
}

if (modulesTypeScriptTypes) {
moduleGenerators.push(generateTypeScript);
}

moduleGenerators.forEach(generator => {
const generated: Map<string, string> = generator(
libraryName,
schema,
moduleSpecName,
);
normalizeFileMap(generated, outputDirectory, generatedFiles);
});

if (
Object.keys(schema.modules).some(
moduleName => schema.modules[moduleName].type === 'Component',
)
) {
const componentGenerators = [
generatorPropsH,
generatorPropsCPP,
generatorShadowNodeH,
generatorShadowNodeCPP,
generatorComponentDescriptorH,
generatorEventEmitterH,
generatorEventEmitterCPP,
generatorEventEmitterH,
generatorPropsCPP,
generatorPropsH,
generatorShadowNodeCPP,
generatorShadowNodeH,
];

componentGenerators.forEach(generator => {
Expand All @@ -287,10 +309,12 @@ export type CodeGenOptions = {
file?: string;
files?: string[];
libraryName: string;
outdir: string;
methodOnly: boolean;
modulesCxx: boolean;
modulesTypeScriptTypes: boolean;
modulesWindows: boolean;
namespace: string;
methodonly: boolean;
ts: boolean;
outputDirectory: string;
test: boolean;
};

Expand All @@ -304,17 +328,25 @@ export function runCodeGen(options: CodeGenOptions): boolean {

const libraryName = options.libraryName;
const moduleSpecName = 'moduleSpecName';
const outputDirectory = options.outdir;
const {namespace, methodonly, ts} = options;
const {
methodOnly,
modulesCxx,
modulesTypeScriptTypes,
modulesWindows,
namespace,
outputDirectory,
} = options;
return generate(
{
libraryName,
schema,
outputDirectory,
methodOnly,
modulesCxx,
moduleSpecName,
modulesTypeScriptTypes,
modulesWindows,
namespace,
methodonly,
ts,
outputDirectory,
schema,
},
{generators: [], test: options.test},
);
Expand Down
2 changes: 1 addition & 1 deletion packages/sample-apps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@
"engines": {
"node": ">= 14"
}
}
}
4 changes: 3 additions & 1 deletion packages/sample-apps/windows/SampleAppCPP/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ App::App() noexcept {
InstanceSettings().UseFastRefresh(false);
#else
JavaScriptBundleFile(L"index");
InstanceSettings().UseWebDebugger(true);
InstanceSettings().UseWebDebugger(false);
InstanceSettings().UseFastRefresh(true);
#endif

#if _DEBUG
InstanceSettings().UseDeveloperSupport(true);
InstanceSettings().UseDirectDebugger(true);
#else
InstanceSettings().UseDirectDebugger(false);
InstanceSettings().UseDeveloperSupport(false);
#endif

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,53 +11,26 @@
#include "TestEventService.h"
#include "TestReactNativeHostHolder.h"

#pragma pack(push)
#pragma warning(disable : 4100 4127 4324)
#include "codegen/msrnIntegrationTestsJSI-generated.cpp"
#include "codegen/msrnIntegrationTestsJSI.h"
#pragma pack(pop)

using namespace facebook;
using namespace winrt;
using namespace Microsoft::ReactNative;

namespace ReactNativeIntegrationTests {

// Use anonymous namespace to avoid any linking conflicts
namespace {

// In this test we put spec definition that normally must be generated.
// >>>> Start generated

// The spec from .h file
struct MyTrivialTurboModuleSpec : react::TurboModule {
virtual void startFromJS(jsi::Runtime &rt) = 0;

protected:
MyTrivialTurboModuleSpec(std::shared_ptr<react::CallInvoker> jsInvoker);
};

// The spec from .cpp file

static jsi::Value MyTrivialTurboModuleSpec_startFromJS(
jsi::Runtime &rt,
react::TurboModule &turboModule,
[[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 0);
static_cast<MyTrivialTurboModuleSpec *>(&turboModule)->startFromJS(rt);
return jsi::Value::undefined();
}

MyTrivialTurboModuleSpec::MyTrivialTurboModuleSpec(std::shared_ptr<react::CallInvoker> jsInvoker)
: react::TurboModule("MyTrivialTurboModuleSpec", std::move(jsInvoker)) {
methodMap_.try_emplace("startFromJS", MethodMetadata{0, MyTrivialTurboModuleSpec_startFromJS});
}

// <<<< End generated

struct MyTrivialTurboModule : MyTrivialTurboModuleSpec {
struct MyTrivialTurboModule : react::NativeMyTrivialTurboModuleCxxSpecJSI {
MyTrivialTurboModule(std::shared_ptr<react::CallInvoker> jsInvoker);

void startFromJS(jsi::Runtime &rt) override;
};

MyTrivialTurboModule::MyTrivialTurboModule(std::shared_ptr<react::CallInvoker> jsInvoker)
: MyTrivialTurboModuleSpec(std::move(jsInvoker)) {}
: NativeMyTrivialTurboModuleCxxSpecJSI(std::move(jsInvoker)) {}

void MyTrivialTurboModule::startFromJS(jsi::Runtime & /*rt*/) {
TestEventService::LogEvent("startFromJS called", nullptr);
Expand All @@ -70,8 +43,6 @@ struct MyTrivialTurboModulePackageProvider
}
};

} // namespace

TEST_CLASS (JsiSimpleTurboModuleTests) {
TEST_METHOD(TestInstanceReload) {
TestEventService::Initialize();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { TurboModuleRegistry } from 'react-native';
const myTrivialTurboModule = TurboModuleRegistry.getEnforcing('MyTrivialTurboModule');
/**
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*
* @flow
* @format
*/

import {default as myTrivialTurboModule} from './NativeMyTrivialTurboModule';

myTrivialTurboModule.startFromJS();
Loading

0 comments on commit ce7f7ac

Please sign in to comment.