-
Notifications
You must be signed in to change notification settings - Fork 24.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor generate-specs-cli and add tests
Summary: This Diff splits the `generate-specs-cli.js` script into: * `generate-specs-cli-executor.js`: which contains the logic to generate the code for iOS. * `generate-specs-cli.js`: which contains the argument parsing logic and invokes the executor. Finally it introduces some tests. ## Changelog [iOS][Changed] - Refactor part of the codegen scripts and add tests. Differential Revision: https://www.internalfb.com/diff/D35892576?entry_point=27 fbshipit-source-id: 8a94f9ee7fafe8e3e7ba980b5d78dd87c0d6e4a3
- Loading branch information
1 parent
de17b4d
commit 5205c4a
Showing
5 changed files
with
343 additions
and
121 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/** | ||
* 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 | ||
* @format | ||
*/ | ||
|
||
'use-strict'; | ||
|
||
const SCHEMA_TEXT = ` | ||
{ | ||
"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" | ||
] | ||
} | ||
} | ||
} | ||
`; | ||
|
||
const SCHEMA = JSON.parse(SCHEMA_TEXT); | ||
|
||
module.exports = { | ||
schemaText: SCHEMA_TEXT, | ||
schema: SCHEMA, | ||
}; |
90 changes: 90 additions & 0 deletions
90
scripts/codegen/__tests__/generate-specs-cli-executor-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/** | ||
* 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 | ||
* @format | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const sut = require('../generate-specs-cli-executor'); | ||
const fixtures = require('../__test_fixtures__/fixtures'); | ||
|
||
describe('generateSpec', () => { | ||
it('invokes RNCodegen with the right params', () => { | ||
const platform = 'ios'; | ||
const libraryType = 'all'; | ||
const schemaPath = './'; | ||
const componentsOutputDir = | ||
'app/ios/build/generated/ios/react/renderer/components/library'; | ||
const modulesOutputDir = 'app/ios/build/generated/ios/./library'; | ||
const outputDirectory = 'app/ios/build/generated/ios'; | ||
const libraryName = 'library'; | ||
const packageName = 'com.library'; | ||
const generators = ['componentsIOS', 'modulesIOS']; | ||
|
||
jest.mock('fs', () => ({ | ||
readFileSync: (path, encoding) => { | ||
expect(path).toBe(schemaPath); | ||
expect(encoding).toBe('utf-8'); | ||
return fixtures.schemaText; | ||
}, | ||
})); | ||
|
||
let mkdirpSyncInvoked = 0; | ||
jest.mock('mkdirp', () => ({ | ||
sync: folder => { | ||
if (mkdirpSyncInvoked === 0) { | ||
expect(folder).toBe(componentsOutputDir); | ||
} | ||
|
||
if (mkdirpSyncInvoked === 1) { | ||
expect(folder).toBe(modulesOutputDir); | ||
} | ||
|
||
if (mkdirpSyncInvoked === 2) { | ||
expect(folder).toBe(outputDirectory); | ||
} | ||
|
||
mkdirpSyncInvoked += 1; | ||
}, | ||
})); | ||
|
||
// We cannot mock directly the `RNCodegen` object because the | ||
// code access the `lib` folder directly and request a file explicitly. | ||
// This makes testing harder than usually. To overcome this, we created a utility | ||
// to retrieve the `Codegen`. By doing that, we can mock the wrapper so that it returns | ||
// an object with the same interface of the `RNCodegen` object. | ||
jest.mock('../codegen-utils', () => ({ | ||
getCodegen: () => ({ | ||
generate: (libraryConfig, generatorConfigs) => { | ||
expect(libraryConfig.libraryName).toBe(libraryName); | ||
expect(libraryConfig.schema).toStrictEqual(fixtures.schema); | ||
expect(libraryConfig.outputDirectory).toBe(outputDirectory); | ||
expect(libraryConfig.packageName).toBe(packageName); | ||
expect(libraryConfig.componentsOutputDir).toBe(componentsOutputDir); | ||
expect(libraryConfig.modulesOutputDir).toBe(modulesOutputDir); | ||
|
||
expect(generatorConfigs.generators).toStrictEqual(generators); | ||
expect(generatorConfigs.test).toBeUndefined(); | ||
}, | ||
}), | ||
})); | ||
|
||
sut.execute( | ||
platform, | ||
schemaPath, | ||
outputDirectory, | ||
libraryName, | ||
packageName, | ||
libraryType, | ||
componentsOutputDir, | ||
modulesOutputDir, | ||
); | ||
|
||
expect(mkdirpSyncInvoked).toBe(3); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/** | ||
* 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. | ||
* | ||
* @format | ||
*/ | ||
|
||
'use strict'; | ||
|
||
/** | ||
* Wrapper required to abstract away from the actual codegen. | ||
* This is needed because, when running tests in Sandcastle, not everything is setup as usually. | ||
* For example, the `react-native-codegen` lib is not present. | ||
* | ||
* Thanks to this wrapper, we are able to mock the getter for the codegen in a way that allow us to return | ||
* a custom object which mimics the Codegen interface. | ||
* | ||
* @return an object that can generate the code for the New Architecture. | ||
*/ | ||
function getCodegen() { | ||
let RNCodegen; | ||
try { | ||
RNCodegen = require('../../packages/react-native-codegen/lib/generators/RNCodegen.js'); | ||
} catch (e) { | ||
RNCodegen = require('../react-native-codegen/lib/generators/RNCodegen.js'); | ||
} | ||
if (!RNCodegen) { | ||
throw 'RNCodegen not found.'; | ||
} | ||
return RNCodegen; | ||
} | ||
|
||
module.exports = { | ||
getCodegen: getCodegen, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/** | ||
* 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. | ||
* | ||
* @format | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const fs = require('fs'); | ||
const mkdirp = require('mkdirp'); | ||
const path = require('path'); | ||
const utils = require('./codegen-utils'); | ||
const RNCodegen = utils.getCodegen(); | ||
|
||
const GENERATORS = { | ||
all: { | ||
android: ['componentsAndroid', 'modulesAndroid'], | ||
ios: ['componentsIOS', 'modulesIOS'], | ||
}, | ||
components: { | ||
android: ['componentsAndroid'], | ||
ios: ['componentsIOS'], | ||
}, | ||
modules: { | ||
android: ['modulesAndroid'], | ||
ios: ['modulesIOS'], | ||
}, | ||
}; | ||
|
||
function deprecated_createOutputDirectoryIfNeeded( | ||
outputDirectory, | ||
libraryName, | ||
) { | ||
if (!outputDirectory) { | ||
outputDirectory = path.resolve(__dirname, '..', 'Libraries', libraryName); | ||
} | ||
mkdirp.sync(outputDirectory); | ||
} | ||
|
||
function createFolderIfDefined(folder) { | ||
if (folder) { | ||
mkdirp.sync(folder); | ||
} | ||
} | ||
|
||
/** | ||
* This function read a JSON schema from a path and parses it. | ||
* It throws if the schema don't exists or it can't be parsed. | ||
* | ||
* @parameter schemaPath: the path to the schema | ||
* @return a valid schema | ||
* @throw an Error if the schema doesn't exists in a given path or if it can't be parsed. | ||
*/ | ||
function readAndParseSchema(schemaPath) { | ||
const schemaText = fs.readFileSync(schemaPath, 'utf-8'); | ||
|
||
if (schemaText == null) { | ||
throw new Error(`Can't find schema at ${schemaPath}`); | ||
} | ||
|
||
try { | ||
return JSON.parse(schemaText); | ||
} catch (err) { | ||
throw new Error(`Can't parse schema to JSON. ${schemaPath}`); | ||
} | ||
} | ||
|
||
function validateLibraryType(libraryType) { | ||
if (GENERATORS[libraryType] == null) { | ||
throw new Error(`Invalid library type. ${libraryType}`); | ||
} | ||
} | ||
|
||
function generateSpec( | ||
platform, | ||
schemaPath, | ||
outputDirectory, | ||
libraryName, | ||
packageName, | ||
libraryType, | ||
componentsOutputDir, | ||
modulesOutputDir, | ||
) { | ||
validateLibraryType(libraryType); | ||
|
||
let schema = readAndParseSchema(schemaPath); | ||
|
||
createFolderIfDefined(componentsOutputDir); | ||
createFolderIfDefined(modulesOutputDir); | ||
deprecated_createOutputDirectoryIfNeeded(outputDirectory, libraryName); | ||
|
||
RNCodegen.generate( | ||
{ | ||
libraryName, | ||
schema, | ||
outputDirectory, | ||
packageName, | ||
componentsOutputDir, | ||
modulesOutputDir, | ||
}, | ||
{ | ||
generators: GENERATORS[libraryType][platform], | ||
}, | ||
); | ||
|
||
if (platform === 'android') { | ||
// Move all components C++ files to a structured jni folder for now. | ||
// Note: this should've been done by RNCodegen's generators, but: | ||
// * the generators don't support platform option yet | ||
// * this subdir structure is Android-only, not applicable to iOS | ||
const files = fs.readdirSync(outputDirectory); | ||
const jniOutputDirectory = `${outputDirectory}/jni/react/renderer/components/${libraryName}`; | ||
mkdirp.sync(jniOutputDirectory); | ||
files | ||
.filter(f => f.endsWith('.h') || f.endsWith('.cpp')) | ||
.forEach(f => { | ||
fs.renameSync(`${outputDirectory}/${f}`, `${jniOutputDirectory}/${f}`); | ||
}); | ||
} | ||
} | ||
|
||
module.exports = { | ||
execute: generateSpec, | ||
}; |
Oops, something went wrong.