Skip to content

Commit

Permalink
Support the type == all properly
Browse files Browse the repository at this point in the history
Summary:
This Diff introduces some changes in the CodeGen to properly generate the types in the right folder.

## Issue
The codegen on iOS defines the output folder once, before creating the generated code.
When the code we have to generate is just a TurboModule (TM) or a Fabric Component (FC), this mechanism works properly.

However, if a library has to generate both TM and FC, actually using the library type `all`, all the code is generated using the TurboModules' output folder.
(**Note:** Android only works in this way)

This generates invalid code because all the FC's `#import` directives assumes that the code is generated in the FC output path which, in this case, is not.

## Solution

The adopted solution moves the responsibility to decide where the files has to be generated to the CodeGen step instead of in the preparatory phases.

The two paths are precomputed in the `generate-artifacts.js` script (the entry point for the CodeGen) and they are passed to all the scripts that requires them.

Once they reach the `RNCodegen.js` file, the generators creates the files and save them in the proper paths.

## Changelog
[iOS][Changed] - CodeGen now supports the `"all"` library type.

Reviewed By: cortinico, dmitryrykun

Differential Revision: D35820848

fbshipit-source-id: ce7f5393936e2ae17f8b2c970f6a011d27f641f2
  • Loading branch information
Riccardo Cipolleschi authored and facebook-github-bot committed May 4, 2022
1 parent 3f09b48 commit 6718500
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 66 deletions.
98 changes: 64 additions & 34 deletions packages/react-native-codegen/src/generators/RNCodegen.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ type LibraryOptions = $ReadOnly<{
outputDirectory: string,
packageName?: string, // Some platforms have a notion of package, which should be configurable.
assumeNonnull: boolean,
componentsOutputDir?: string, // optional for backward compatibility
modulesOutputDir?: string, // optional for backward compatibility
}>;

type SchemasOptions = $ReadOnly<{
Expand Down Expand Up @@ -134,36 +136,39 @@ const SCHEMAS_GENERATORS = {
],
};

function writeMapToFiles(map: Map<string, string>, outputDir: string) {
type CodeGenFile = {
name: string,
content: string,
outputDir: string,
};

function writeMapToFiles(map: Array<CodeGenFile>) {
let success = true;
map.forEach((contents: string, fileName: string) => {
map.forEach(file => {
try {
const location = path.join(outputDir, fileName);
const location = path.join(file.outputDir, file.name);
const dirName = path.dirname(location);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName, {recursive: true});
}
fs.writeFileSync(location, contents);
fs.writeFileSync(location, file.content);
} catch (error) {
success = false;
console.error(`Failed to write ${fileName} to ${outputDir}`, error);
console.error(`Failed to write ${file.name} to ${file.outputDir}`, error);
}
});

return success;
}

function checkFilesForChanges(
map: Map<string, string>,
outputDir: string,
): boolean {
function checkFilesForChanges(generated: Array<CodeGenFile>): boolean {
let hasChanged = false;

map.forEach((contents: string, fileName: string) => {
const location = path.join(outputDir, fileName);
generated.forEach(file => {
const location = path.join(file.outputDir, file.name);
const currentContents = fs.readFileSync(location, 'utf8');
if (currentContents !== contents) {
console.error(`- ${fileName} has changed`);
if (currentContents !== file.content) {
console.error(`- ${file.name} has changed`);

hasChanged = true;
}
Expand All @@ -172,6 +177,16 @@ function checkFilesForChanges(
return !hasChanged;
}

function checkOrWriteFiles(
generatedFiles: Array<CodeGenFile>,
test: void | boolean,
): boolean {
if (test === true) {
return checkFilesForChanges(generatedFiles);
}
return writeMapToFiles(generatedFiles);
}

module.exports = {
generate(
{
Expand All @@ -180,27 +195,42 @@ module.exports = {
outputDirectory,
packageName,
assumeNonnull,
componentsOutputDir,
modulesOutputDir,
}: LibraryOptions,
{generators, test}: LibraryConfig,
): boolean {
schemaValidator.validate(schema);

const generatedFiles = [];
const outputFoldersForGenerators = {
componentsIOS: componentsOutputDir ?? outputDirectory, // fallback for backward compatibility
modulesIOS: modulesOutputDir ?? outputDirectory, // fallback for backward compatibility
descriptors: outputDirectory,
events: outputDirectory,
props: outputDirectory,
componentsAndroid: outputDirectory,
modulesAndroid: outputDirectory,
modulesCxx: outputDirectory,
tests: outputDirectory,
'shadow-nodes': outputDirectory,
};

const generatedFiles: Array<CodeGenFile> = [];

for (const name of generators) {
for (const generator of LIBRARY_GENERATORS[name]) {
generatedFiles.push(
...generator(libraryName, schema, packageName, assumeNonnull),
generator(libraryName, schema, packageName, assumeNonnull).forEach(
(contents: string, fileName: string) => {
generatedFiles.push({
name: fileName,
content: contents,
outputDir: outputFoldersForGenerators[name],
});
},
);
}
}

const filesToUpdate = new Map([...generatedFiles]);

if (test === true) {
return checkFilesForChanges(filesToUpdate, outputDirectory);
}

return writeMapToFiles(filesToUpdate, outputDirectory);
return checkOrWriteFiles(generatedFiles, test);
},
generateFromSchemas(
{schemas, outputDirectory}: SchemasOptions,
Expand All @@ -210,20 +240,20 @@ module.exports = {
schemaValidator.validate(schemas[libraryName]),
);

const generatedFiles = [];
const generatedFiles: Array<CodeGenFile> = [];

for (const name of generators) {
for (const generator of SCHEMAS_GENERATORS[name]) {
generatedFiles.push(...generator(schemas));
generator(schemas).forEach((contents: string, fileName: string) => {
generatedFiles.push({
name: fileName,
content: contents,
outputDir: outputDirectory,
});
});
}
}

const filesToUpdate = new Map([...generatedFiles]);

if (test === true) {
return checkFilesForChanges(filesToUpdate, outputDirectory);
}

return writeMapToFiles(filesToUpdate, outputDirectory);
return checkOrWriteFiles(generatedFiles, test);
},
generateViewConfig({libraryName, schema}: LibraryOptions): string {
schemaValidator.validate(schema);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

'use strict';

import type {SchemaType} from '../../CodegenSchema.js';

const SCHEMA_WITH_TM_AND_FC: SchemaType = {
modules: {
ColoredView: {
type: 'Component',
components: {
ColoredView: {
extendsProps: [
{
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
},
],
events: [],
props: [
{
name: 'color',
optional: false,
typeAnnotation: {
type: 'StringTypeAnnotation',
default: null,
},
},
],
commands: [],
},
},
},
NativeCalculator: {
type: 'NativeModule',
aliases: {},
spec: {
properties: [
{
name: 'add',
optional: false,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
returnTypeAnnotation: {
type: 'PromiseTypeAnnotation',
},
params: [
{
name: 'a',
optional: false,
typeAnnotation: {
type: 'NumberTypeAnnotation',
},
},
{
name: 'b',
optional: false,
typeAnnotation: {
type: 'NumberTypeAnnotation',
},
},
],
},
},
],
},
moduleNames: ['Calculator'],
},
},
};

module.exports = {
all: SCHEMA_WITH_TM_AND_FC,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+react_native
* @flow strict-local
* @format
*/

'use strict';

const rnCodegen = require('../RNCodegen.js');
const fixture = require('../__test_fixtures__/fixtures.js');
const path = require('path');

const invalidDirectory = 'invalid/';
const packageName = 'na';
const componentsOutputDir = 'react/renderer/components/library';
const modulesOutputDir = 'library';

describe('RNCodegen.generate', () => {
it('when type `all`', () => {
const expectedPaths = {
'library.h': modulesOutputDir,
'library-generated.mm': modulesOutputDir,
'ShadowNodes.h': componentsOutputDir,
'ShadowNodes.cpp': componentsOutputDir,
'Props.h': componentsOutputDir,
'Props.cpp': componentsOutputDir,
'RCTComponentViewHelpers.h': componentsOutputDir,
'EventEmitters.h': componentsOutputDir,
'EventEmitters.cpp': componentsOutputDir,
'ComponentDescriptors.h': componentsOutputDir,
};

jest.mock('fs', () => ({
existsSync: location => {
return true;
},
writeFileSync: (location, content) => {
let receivedDir = path.dirname(location);
let receivedBasename = path.basename(location);

let expectedPath = expectedPaths[receivedBasename];
expect(receivedDir).toEqual(expectedPath);
},
}));

const res = rnCodegen.generate(
{
libraryName: 'library',
schema: fixture.all,
outputDirectory: invalidDirectory,
packageName: packageName,
assumeNonnull: true,
componentsOutputDir: componentsOutputDir,
modulesOutputDir: modulesOutputDir,
},
{
generators: ['componentsIOS', 'modulesIOS'],
test: false,
},
);

expect(res).toBeTruthy();
});
});
Loading

0 comments on commit 6718500

Please sign in to comment.