Permalink
Browse files

Validate that JS and Native code versions match for RN releases

Summary:
Basic implementation of the proposal in #15271

Note that this should not affect facebook internally since they are not using OSS releases.

Points to consider:
- How strict should the version match be, right now I just match exact versions.
- Wasn't able to use haste for ReactNativeVersion because I was getting duplicate module provider caused by the template file in scripts/versiontemplates. I tried adding the scripts folder to modulePathIgnorePatterns in package.json but that didn't help.
- Redscreen vs. warning, I think warning is useless because if the app crashes you won't have time to see the warning.
- Should the check and native modules be __DEV__ only?

**Test plan**
Tested that it works when version match and that it redscreens when versions don't before getting other errors on Android and iOS.
Closes #15518

Differential Revision: D5813551

Pulled By: hramos

fbshipit-source-id: 901757e25724b0f22bf39de172b56309d0dd5a95
  • Loading branch information...
janicduplessis authored and facebook-github-bot committed Sep 28, 2017
1 parent 9b3cc30 commit 1af645b2fd9ba98d740df70eaf69dd58fba273d1
@@ -116,6 +116,23 @@ if (!global.__fbDisableExceptionsManager) {
ErrorUtils.setGlobalHandler(handleError);
}
const formatVersion = version =>
`${version.major}.${version.minor}.${version.patch}` +
(version.prerelease !== null ? `-${version.prerelease}` : '');
const ReactNativeVersion = require('ReactNativeVersion');
const nativeVersion = require('NativeModules').PlatformConstants.reactNativeVersion;
if (ReactNativeVersion.version.major !== nativeVersion.major ||
ReactNativeVersion.version.minor !== nativeVersion.minor) {
throw new Error(
`React Native version mismatch.\n\nJavaScript version: ${formatVersion(ReactNativeVersion.version)}\n` +
`Native version: ${formatVersion(nativeVersion)}\n\n` +
'Make sure that you have rebuilt the native code. If the problem persists ' +
'try clearing the watchman and packager caches with `watchman watch-del-all ' +
'&& react-native start --reset-cache`.'
);
}
// Set up collections
const _shouldPolyfillCollection = require('_shouldPolyfillES6Collection');
if (_shouldPolyfillCollection('Map')) {
@@ -0,0 +1,20 @@
/**
* @generated by scripts/bump-oss-version.js
*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @providesModule ReactNativeVersion
*/
exports.version = {
major: 0,
minor: 0,
patch: 0,
prerelease: null,
};
View
@@ -12,6 +12,7 @@
#import <UIKit/UIKit.h>
#import "RCTUtils.h"
#import "RCTVersion.h"
static NSString *interfaceIdiom(UIUserInterfaceIdiom idiom) {
switch(idiom) {
@@ -46,6 +47,7 @@ + (BOOL)requiresMainQueueSetup
@"systemName": [device systemName],
@"interfaceIdiom": interfaceIdiom([device userInterfaceIdiom]),
@"isTesting": @(RCTRunningInTestEnvironment()),
@"reactNativeVersion": REACT_NATIVE_VERSION,
};
}
View
@@ -0,0 +1,17 @@
/**
* @generated by scripts/bump-oss-version.js
*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#define REACT_NATIVE_VERSION @{ \
@"major": @(0), \
@"minor": @(0), \
@"patch": @(0), \
@"prerelease": [NSNull null], \
}
@@ -203,6 +203,8 @@
14F7A0F01BDA714B003C6C10 /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */; };
191E3EBE1C29D9AF00C180A6 /* RCTRefreshControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */; };
191E3EC11C29DC3800C180A6 /* RCTRefreshControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */; };
199B8A6F1F44DB16005DEF67 /* RCTVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 199B8A6E1F44DB16005DEF67 /* RCTVersion.h */; };
199B8A761F44DEDA005DEF67 /* RCTVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 199B8A6E1F44DB16005DEF67 /* RCTVersion.h */; };
19F61BFA1E8495CD00571D81 /* bignum-dtoa.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 139D7E3A1E25C5A300323FB7 /* bignum-dtoa.h */; };
19F61BFB1E8495CD00571D81 /* bignum.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 139D7E3C1E25C5A300323FB7 /* bignum.h */; };
19F61BFC1E8495CD00571D81 /* cached-powers.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 139D7E3E1E25C5A300323FB7 /* cached-powers.h */; };
@@ -1921,6 +1923,7 @@
191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRefreshControlManager.m; sourceTree = "<group>"; };
191E3EBF1C29DC3800C180A6 /* RCTRefreshControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRefreshControl.h; sourceTree = "<group>"; };
191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRefreshControl.m; sourceTree = "<group>"; };
199B8A6E1F44DB16005DEF67 /* RCTVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVersion.h; sourceTree = "<group>"; };
19DED2281E77E29200F089BB /* systemJSCWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = systemJSCWrapper.cpp; sourceTree = "<group>"; };
27B958731E57587D0096647A /* JSBigString.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSBigString.cpp; sourceTree = "<group>"; };
2D2A28131D9B038B00D4039D /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -2750,6 +2753,7 @@
1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */,
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
199B8A6E1F44DB16005DEF67 /* RCTVersion.h */,
);
path = Base;
sourceTree = "<group>";
@@ -2903,6 +2907,7 @@
3D302F411DF828F800D6DDAE /* RCTModuleMethod.h in Headers */,
3D302F421DF828F800D6DDAE /* RCTMultipartDataTask.h in Headers */,
3D302F431DF828F800D6DDAE /* RCTMultipartStreamReader.h in Headers */,
199B8A761F44DEDA005DEF67 /* RCTVersion.h in Headers */,
3D302F441DF828F800D6DDAE /* RCTNullability.h in Headers */,
3D302F451DF828F800D6DDAE /* RCTParserUtils.h in Headers */,
3D302F461DF828F800D6DDAE /* RCTPerformanceLogger.h in Headers */,
@@ -3145,6 +3150,7 @@
3D80DA191DF820620028D040 /* RCTImageLoader.h in Headers */,
C654505E1F3BD9280090799B /* RCTManagedPointer.h in Headers */,
13134C941E296B2A00B9F3CB /* RCTObjcExecutor.h in Headers */,
199B8A6F1F44DB16005DEF67 /* RCTVersion.h in Headers */,
3D80DA1A1DF820620028D040 /* RCTImageStoreManager.h in Headers */,
130443A11E3FEAA900D93A67 /* RCTFollyConvert.h in Headers */,
59FBEFB41E46D91C0095D885 /* RCTScrollContentViewManager.h in Headers */,
@@ -38,6 +38,7 @@ public String getName() {
constants.put("Version", Build.VERSION.SDK_INT);
constants.put("ServerHost", AndroidInfoHelpers.getServerHost());
constants.put("isTesting", "true".equals(System.getProperty(IS_TESTING)));
constants.put("reactNativeVersion", ReactNativeVersion.VERSION);
return constants;
}
}
@@ -4,6 +4,7 @@ android_library(
name = "systeminfo",
srcs = [
"AndroidInfoModule.java",
"ReactNativeVersion.java",
],
exported_deps = [
":systeminfo-moduleless",
@@ -0,0 +1,24 @@
/**
* @generated by scripts/bump-oss-version.js
*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.modules.systeminfo;
import com.facebook.react.common.MapBuilder;
import java.util.Map;
public class ReactNativeVersion {
public static final Map<String, Object> VERSION = MapBuilder.<String, Object>of(
"major", 0,
"minor", 0,
"patch", 0,
"prerelease", null);
}
View
@@ -34,7 +34,7 @@ jest
jest.setMock('ErrorUtils', require('ErrorUtils'));
jest
.mock('InitializeCore')
.mock('InitializeCore', () => {})
.mock('Image', () => mockComponent('Image'))
.mock('Text', () => mockComponent('Text'))
.mock('TextInput', () => mockComponent('TextInput'))
@@ -46,10 +46,39 @@ let versionMajor = branch.slice(0, branch.indexOf(`-stable`));
// e.g. 0.33.1 or 0.33.0-rc4
let version = argv._[0];
if (!version || version.indexOf(versionMajor) !== 0) {
echo(`You must pass a tag like ${versionMajor}.[X]-rc[Y] to bump a version`);
echo(`You must pass a tag like 0.${versionMajor}.[X]-rc[Y] to bump a version`);
exit(1);
}
// Generate version files to detect mismatches between JS and native.
let match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
if (!match) {
echo(`You must pass a correctly formatted version; couldn't parse ${version}`);
exit(1);
}
let [, major, minor, patch, prerelease] = match;
cat('scripts/versiontemplates/ReactNativeVersion.java.template')
.replace('${major}', major)
.replace('${minor}', minor)
.replace('${patch}', patch)
.replace('${prerelease}', prerelease !== undefined ? `"${prerelease}"` : 'null')
.to('ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java');
cat('scripts/versiontemplates/RCTVersion.h.template')
.replace('${major}', `@(${major})`)
.replace('${minor}', `@(${minor})`)
.replace('${patch}', `@(${patch})`)
.replace('${prerelease}', prerelease !== undefined ? `@"${prerelease}"` : '[NSNull null]')
.to('React/Base/RCTVersion.h');
cat('scripts/versiontemplates/ReactNativeVersion.js.template')
.replace('${major}', major)
.replace('${minor}', minor)
.replace('${patch}', patch)
.replace('${prerelease}', prerelease !== undefined ? `'${prerelease}'` : 'null')
.to('Libraries/Core/ReactNativeVersion.js');
let packageJson = JSON.parse(cat(`package.json`));
packageJson.version = version;
JSON.stringify(packageJson, null, 2).to(`package.json`);
@@ -0,0 +1,17 @@
/**
* @generated by scripts/bump-oss-version.js
*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#define REACT_NATIVE_VERSION @{ \
@"major": ${major}, \
@"minor": ${minor}, \
@"patch": ${patch}, \
@"prerelease": ${prerelease}, \
}
@@ -0,0 +1,24 @@
/**
* @generated by scripts/bump-oss-version.js
*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.modules.systeminfo;
import com.facebook.react.common.MapBuilder;
import java.util.Map;
public class ReactNativeVersion {
public static final Map<String, Object> VERSION = MapBuilder.<String, Object>of(
"major", ${major},
"minor", ${minor},
"patch", ${patch},
"prerelease", ${prerelease});
}
@@ -0,0 +1,20 @@
/**
* @generated by scripts/bump-oss-version.js
*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @providesModule ReactNativeVersion
*/
exports.version = {
major: ${major},
minor: ${minor},
patch: ${patch},
prerelease: ${prerelease},
};

0 comments on commit 1af645b

Please sign in to comment.