From 33733f65beda6940b9b4200c1b153f11909482ac Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 19 May 2017 15:10:27 +0200 Subject: [PATCH 01/13] Automatically update xcode project files --- scripts/postlink | 177 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 172 insertions(+), 5 deletions(-) diff --git a/scripts/postlink b/scripts/postlink index b3796f0ad9..4a5a57c7f5 100644 --- a/scripts/postlink +++ b/scripts/postlink @@ -1,7 +1,9 @@ let glob = require('glob'); let fs = require('fs'); let inquirer = require('inquirer'); +let xcode = require('xcode'); +let PLATFORMS = ['android', 'ios']; let OBJC_HEADER = '\ #if __has_include()\n\ #import // This is used for versions of react >= 0.40\n\ @@ -10,16 +12,21 @@ let OBJC_HEADER = '\ #endif'; let cachedDsn = null; +let cachedProps = {}; let patchedAny = false; +function getPlatformName(platform) { + return { + 'android': 'Android', + 'ios': 'iOS', + }[platform] || platform; +} + function getDsn(platform) { return inquirer.prompt([{ type: 'input', default: cachedDsn || process.env.SENTRY_DSN || 'YOUR_DSN_HERE', - message: 'The DSN for ' + { - 'android': 'Android', - 'ios': 'iOS', - }[platform] || platform, + message: 'The DSN for ' + getPlatformName(platform), name: 'dsn', }]).then(function(answers) { cachedDsn = answers.dsn; @@ -27,6 +34,57 @@ function getDsn(platform) { }); } +function getDefaultUrl() { + if (cachedDsn) { + let match = cachedDsn.match(/^(https?).*?@(.*?)\//); + if (match) { + return match[1] + '://' + match[2] + '/'; + } + } + return 'https://sentry.io/'; +} + +function getProperties(platform) { + return inquirer.prompt([{ + type: 'input', + default: cachedProps['defaults/url'] || process.env.SENTRY_URL || getDefaultUrl(), + message: 'The Sentry Server URL for ' + getPlatformName(platform), + name: 'defaults/url', + }, { + type: 'input', + default: cachedProps['defaults/org'] || process.env.SENTRY_ORG || 'your-org-slug', + message: 'The Organization for ' + getPlatformName(platform), + name: 'defaults/org', + }, { + type: 'input', + default: cachedProps['defaults/project'] || process.env.SENTRY_PROJECT || 'your-project-slug', + message: 'The Project for ' + getPlatformName(platform), + name: 'defaults/project', + }, { + type: 'password', + default: cachedProps['auth/token'] || process.env.SENTRY_AUTH_TOKEN || 'YOUR_AUTH_TOKEN', + message: 'The Auth-Token for ' + getPlatformName(platform), + name: 'auth/token', + }]).then(function(answers) { + cachedProps = answers; + return Promise.resolve(answers); + }); +} + +function dumpProperties(props) { + let rv = []; + for (let key in props) { + let value = props[key]; + key = key.replace(/\//g, '.'); + if (value === undefined || value === null) { + rv.push('#' + key + '='); + } else { + rv.push(key + '=' + value); + } + } + return rv.join('\n'); +} + function patchAppDelegate(contents) { // add the header if it's not there yet. if (!contents.match(/#import "RNSentry.h"/)) { @@ -56,7 +114,7 @@ function patchAppDelegate(contents) { function patchIndexJs(contents, filename) { // since the init call could live in other places too, we really only // want to do this if we managed to patch any of the other files as well. - if (contents.match(/Sentry.config\(/) && patchedAny) { + if (contents.match(/Sentry.config\(/) || !patchedAny) { return Promise.resolve(contents); } @@ -83,6 +141,91 @@ function patchBuildGradle(contents) { )); } +function patchExistingXcodeBuildScripts(buildScripts) { + for (let script of buildScripts) { + if (!script.shellScript.match(/packager\/react-native-xcode\.sh\b/) || + script.shellScript.match(/sentry-cli\s+react-native-xcode/)) { + continue; + } + let code = JSON.parse(script.shellScript); + code = ( + 'export SENTRY_PROPERTIES=sentry.properties\n' + + code.replace(/^.*?\/packager\/react-native-xcode\.sh\s*/m, function(match) { + return '../node_modules/sentry-cli-binary/bin/sentry-cli react-native-xcode ' + match; + }) + ); + script.shellScript = JSON.stringify(code); + } +} + +function addNewXcodeBuildPhaseForSymbols(buildScripts, proj) { + for (let script of buildScripts) { + if (script.shellScript.match(/sentry-cli\s+upload-dsym/)) { + return; + } + } + + proj.addBuildPhase( + [], + 'PBXShellScriptBuildPhase', + 'Upload Debug Symbols to Sentry', + null, + { + shellPath: '/bin/sh', + shellScript: ( + 'export SENTRY_PROPERTIES=sentry.properties\n' + + '../node_modules/sentry-cli-binary/bin/sentry-cli upload-dsym' + ) + } + ); +} + +function addNewXcodeBuildPhaseForBundleFw(buildScripts, proj) { + for (let script of buildScripts) { + if (script.shellScript.match(/react-native-sentry\/bin\/bundle-frameworks/)) { + return; + } + } + + proj.addBuildPhase( + [], + 'PBXShellScriptBuildPhase', + 'Bundle react-native-sentry Frameworks', + null, + { + shellPath: '/bin/sh', + shellScript: ( + '../node_modules/react-native-sentry/bin/bundle-frameworks' + ) + } + ); +} + +function patchXcodeProj(contents, filename) { + let proj = xcode.project(filename); + return new Promise(function(resolve, reject) { + proj.parse(function(err) { + if (err) { + reject(err); + return; + } + + proj.addBuildProperty('ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES', 'YES'); + + let buildScripts = Object.values( + proj.hash.project.objects.PBXShellScriptBuildPhase || {}) + .filter((val) => val.isa); + resolve(Promise.resolve() + .then(patchExistingXcodeBuildScripts(buildScripts)) + .then(addNewXcodeBuildPhaseForSymbols(buildScripts, proj)) + .then(addNewXcodeBuildPhaseForBundleFw(buildScripts, proj)) + .then(() => { + return proj.writeSync(); + })); + }); + }); +} + function patchMatchingFile(pattern, func) { let matches = glob.sync(pattern, { ignore: 'node_modules/**' @@ -102,10 +245,34 @@ function patchMatchingFile(pattern, func) { return rv; } +function addSentryProperties() { + let rv = null; + + for (let platform of PLATFORMS) { + let fn = platform + '/sentry.properties'; + if (fs.existsSync(fn)) { + continue; + } + + let p = () => getProperties(platform).then((props) => { + fs.writeFileSync(fn, dumpProperties(props)); + }); + if (rv === null) { + rv = p(); + } else { + rv = rv.then(p); + } + } + + return rv; +} + Promise.resolve() + .then(() => patchMatchingFile('**/*.xcodeproj/project.pbxproj', patchXcodeProj)) .then(() => patchMatchingFile('**/AppDelegate.m', patchAppDelegate)) .then(() => patchMatchingFile('**/app/build.gradle', patchBuildGradle)) .then(() => patchMatchingFile('index.*.js', patchIndexJs)) + .then(() => addSentryProperties()) .catch(function(e) { console.log('Could not link react-native-sentry: ' + e); return Promise.resolve(); From a2b9c1a88f8282d83ffafdd57a5714fcf3ca8b0f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 20 May 2017 00:22:57 +0200 Subject: [PATCH 02/13] Heavily improved the unlinking --- package.json | 3 +- scripts/postlink | 48 +++++++++++++++++++++++-------- scripts/postunlink | 71 +++++++++++++++++++++++++++++++++++++++------- 3 files changed, 99 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 186b0e63a3..27f839081c 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "raven-js": "^3.15.0", "sentry-cli-binary": "^1.9.0", "inquirer": "3.0.6", - "glob": "7.1.1" + "glob": "7.1.1", + "xcode": "0.9.3" }, "rnpm": { "commands": { diff --git a/scripts/postlink b/scripts/postlink index 4a5a57c7f5..20c0d54ddb 100644 --- a/scripts/postlink +++ b/scripts/postlink @@ -141,6 +141,32 @@ function patchBuildGradle(contents) { )); } +function patchAlwaysIncludeSwift(proj) { + let nativeTargets = proj.hash.project.objects.PBXNativeTarget; + let buildConfigs = proj.pbxXCBuildConfigurationSection(); + + for (let key in nativeTargets) { + let data = nativeTargets[key]; + if (typeof data === 'string') { + continue; + } + + if (!data.productReference_comment.match(/\.app$/)) { + continue; + } + + let cfgList = proj.pbxXCConfigurationList()[data.buildConfigurationList]; + if (!cfgList) { + continue; + } + + for (let cfgRef of cfgList.buildConfigurations) { + let cfg = buildConfigs[cfgRef.value]; + cfg.buildSettings.ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = 'YES'; + } + } +} + function patchExistingXcodeBuildScripts(buildScripts) { for (let script of buildScripts) { if (!script.shellScript.match(/packager\/react-native-xcode\.sh\b/) || @@ -173,8 +199,8 @@ function addNewXcodeBuildPhaseForSymbols(buildScripts, proj) { { shellPath: '/bin/sh', shellScript: ( - 'export SENTRY_PROPERTIES=sentry.properties\n' + - '../node_modules/sentry-cli-binary/bin/sentry-cli upload-dsym' + '"export SENTRY_PROPERTIES=sentry.properties\\n' + + '../node_modules/sentry-cli-binary/bin/sentry-cli upload-dsym"' ) } ); @@ -195,7 +221,7 @@ function addNewXcodeBuildPhaseForBundleFw(buildScripts, proj) { { shellPath: '/bin/sh', shellScript: ( - '../node_modules/react-native-sentry/bin/bundle-frameworks' + '"../node_modules/react-native-sentry/bin/bundle-frameworks"' ) } ); @@ -210,18 +236,16 @@ function patchXcodeProj(contents, filename) { return; } - proj.addBuildProperty('ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES', 'YES'); - let buildScripts = Object.values( proj.hash.project.objects.PBXShellScriptBuildPhase || {}) .filter((val) => val.isa); - resolve(Promise.resolve() - .then(patchExistingXcodeBuildScripts(buildScripts)) - .then(addNewXcodeBuildPhaseForSymbols(buildScripts, proj)) - .then(addNewXcodeBuildPhaseForBundleFw(buildScripts, proj)) - .then(() => { - return proj.writeSync(); - })); + + patchAlwaysIncludeSwift(proj); + patchExistingXcodeBuildScripts(buildScripts); + addNewXcodeBuildPhaseForSymbols(buildScripts, proj); + addNewXcodeBuildPhaseForBundleFw(buildScripts, proj); + + resolve(proj.writeSync()); }); }); } diff --git a/scripts/postunlink b/scripts/postunlink index 23a1b6009f..607962ba02 100644 --- a/scripts/postunlink +++ b/scripts/postunlink @@ -1,24 +1,75 @@ let glob = require('glob'); let fs = require('fs'); +let xcode = require('xcode'); function unpatchAppDelegate(contents) { return Promise.resolve(contents - .replace(/^#if __has_include\()[^]*?\#endif\r?\n/m, '') - .replace(/^\s*\[RNSentry\s+installWithRootView:.*?\];\r?\n/m, '')); -} - -function unpatchIndexJs(contents, filename) { - return Promise.resolve(contents - .replace(/^\s*Sentry\.config\((.*?)\);?\s*\r?\n/mg, ''); - .replace(/(\r?\n){,2}import\s*\{[^]*?\}\s*from\s+['"]react-native-sentry['"];\s*(\r?\n){,2}/mg, '')); + .replace(/^#if __has_include\(\)[^]*?\#endif\r?\n/m, '') + .replace(/(\r?\n|^)\s*\[RNSentry\s+installWithRootView:.*?\];\s*?\r?\n/m, '')); } function unpatchBuildGradle(contents) { return Promise.resolve(contents.replace( - /^\s*apply from: ["']..\/..\/node_modules\/react-native-sentry\/sentry.gradle["'];\s*\r?\n/m, + /^\s*apply from: ["']..\/..\/node_modules\/react-native-sentry\/sentry.gradle["'];?\s*?\r?\n/m, '')); } +function unpatchXcodeBuildScripts(proj) { + let scripts = proj.hash.project.objects.PBXShellScriptBuildPhase || {}; + let firstTarget = proj.getFirstTarget().uuid; + let nativeTargets = proj.hash.project.objects.PBXNativeTarget; + + for (let key of Object.keys(scripts)) { + let script = scripts[key]; + + // ignore comments and keys that got deleted + if (typeof script === 'string' || script === undefined) { + continue; + } + + // scripts to kill entirely. + if (script.shellScript.match(/react-native-sentry\/bin\/bundle-frameworks\b/) || + script.shellScript.match(/sentry-cli-binary\/bin\/sentry-cli\s+upload-dsym\b/)) { + delete scripts[key]; + delete scripts[key + '_comment']; + let phases = nativeTargets[firstTarget].buildPhases; + if (phases) { + for (let i = 0; i < phases.length; i++) { + if (phases[i].value === key) { + phases.splice(i, 1); + break; + } + } + } + continue; + } + + // scripts to patch partially + script.shellScript = script.shellScript + .replace( + /^export SENTRY_PROPERTIES=sentry.properties\r?\n/m, + '') + .replace( + /^..\/node_modules\/sentry-cli-binary\/bin\/sentry-cli\s+react-native-xcode\s+(.*?)$/m, + '$1'); + } +} + +function unpatchXcodeProj(contents, filename) { + let proj = xcode.project(filename); + return new Promise(function(resolve, reject) { + proj.parse(function(err) { + if (err) { + reject(err); + return; + } + + unpatchXcodeBuildScripts(proj); + resolve(proj.writeSync()); + }); + }); +} + function patchMatchingFile(pattern, func) { let matches = glob.sync(pattern, { ignore: 'node_modules/**' @@ -38,8 +89,8 @@ function patchMatchingFile(pattern, func) { } Promise.resolve() + .then(() => patchMatchingFile('**/*.xcodeproj/project.pbxproj', unpatchXcodeProj)) .then(() => patchMatchingFile('**/AppDelegate.m', unpatchAppDelegate)) - .then(() => patchMatchingFile('index.*.js', unpatchIndexJs)) .then(() => patchMatchingFile('**/app/build.gradle', unpatchBuildGradle)) .catch(function(e) { console.log('Could not unlink react-native-sentry: ' + e); From a5fdebad9dd1a2f9feadc07aad2caff95784c9de Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 20 May 2017 10:04:50 +0200 Subject: [PATCH 03/13] Better unlink older versions of the generated code --- scripts/postunlink | 57 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/scripts/postunlink b/scripts/postunlink index 607962ba02..c0a164080d 100644 --- a/scripts/postunlink +++ b/scripts/postunlink @@ -5,6 +5,7 @@ let xcode = require('xcode'); function unpatchAppDelegate(contents) { return Promise.resolve(contents .replace(/^#if __has_include\(\)[^]*?\#endif\r?\n/m, '') + .replace(/^#import\s+(?:|"RNSentry.h")\s*?\r?\n/m '') .replace(/(\r?\n|^)\s*\[RNSentry\s+installWithRootView:.*?\];\s*?\r?\n/m, '')); } @@ -19,6 +20,52 @@ function unpatchXcodeBuildScripts(proj) { let firstTarget = proj.getFirstTarget().uuid; let nativeTargets = proj.hash.project.objects.PBXNativeTarget; + // scripts to patch partially. Run this first so that we don't + // accidentally delete some scripts later entirely that we only want to + // rewrite. + for (let key of Object.keys(scripts)) { + let script = scripts[key]; + + // ignore comments + if (typeof script === 'string') { + continue; + } + + // ignore scripts that do not invoke the react-native-xcode command. + if (!script.shellScript.match(/sentry-cli\s+react-native-xcode\b/)) { + continue; + } + + script.shellScript = script.shellScript + // "legacy" location for this. This is what happens if users followed + // the old documentation for where to add the bundle command + .replace( + /^..\/node_modules\/react-native-sentry\/bin\/bundle-frameworks\s*?\r\n?/m, + '') + // legacy location for dsym upload + .replace( + /^..\/node_modules\/sentry-cli-binary\/bin\/sentry-cli upload-dsym\s*?\r?\n/m, + '') + // remove sentry properties export + .replace( + /^export SENTRY_PROPERTIES=sentry.properties\r?\n/m, + '') + // unwrap react-native-xcode.sh command. In case someone replaced it + // entirely with the sentry-cli command we need to put the original + // version back in. + .replace( + /^(?:..\/node_modules\/sentry-cli-binary\/bin\/)?sentry-cli\s+react-native-xcode(\s+.*?)$/m, + function(match, m1) { + let rv = m1.trim(); + if (rv === '') { + return '../node_modules/react-native/packager/react-native-xcode.sh'; + } else { + return rv; + } + }); + } + + // scripts to kill entirely. for (let key of Object.keys(scripts)) { let script = scripts[key]; @@ -27,7 +74,6 @@ function unpatchXcodeBuildScripts(proj) { continue; } - // scripts to kill entirely. if (script.shellScript.match(/react-native-sentry\/bin\/bundle-frameworks\b/) || script.shellScript.match(/sentry-cli-binary\/bin\/sentry-cli\s+upload-dsym\b/)) { delete scripts[key]; @@ -43,15 +89,6 @@ function unpatchXcodeBuildScripts(proj) { } continue; } - - // scripts to patch partially - script.shellScript = script.shellScript - .replace( - /^export SENTRY_PROPERTIES=sentry.properties\r?\n/m, - '') - .replace( - /^..\/node_modules\/sentry-cli-binary\/bin\/sentry-cli\s+react-native-xcode\s+(.*?)$/m, - '$1'); } } From f6c2c62191612bf07f018719e24fbc2467918fb0 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 20 May 2017 15:28:38 +0200 Subject: [PATCH 04/13] Prompt for which platforms to configure --- scripts/postlink | 101 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 26 deletions(-) diff --git a/scripts/postlink b/scripts/postlink index 20c0d54ddb..936c2cb672 100644 --- a/scripts/postlink +++ b/scripts/postlink @@ -14,6 +14,7 @@ let OBJC_HEADER = '\ let cachedDsn = null; let cachedProps = {}; let patchedAny = false; +let configurePlatform = {}; function getPlatformName(platform) { return { @@ -22,6 +23,30 @@ function getPlatformName(platform) { }[platform] || platform; } +function shouldConfigurePlatform(platform) { + if (configurePlatform[platform] !== undefined) { + return Promise.resolve(configurePlatform[platform]); + } + return inquirer.prompt([{ + type: 'list', + name: 'configure', + message: `Do you want to configure Sentry for ${getPlatformName(platform)}?`, + choices: [ + { + name: 'Yes', + value: true + }, + { + name: 'No (or later)', + value: false + } + ] + }]).then(function(answers) { + configurePlatform[platform] = answers.configure; + return Promise.resolve(answers.configure); + }); +} + function getDsn(platform) { return inquirer.prompt([{ type: 'input', @@ -82,7 +107,7 @@ function dumpProperties(props) { rv.push(key + '=' + value); } } - return rv.join('\n'); + return rv.join('\n') + '\n'; } function patchAppDelegate(contents) { @@ -115,30 +140,42 @@ function patchIndexJs(contents, filename) { // since the init call could live in other places too, we really only // want to do this if we managed to patch any of the other files as well. if (contents.match(/Sentry.config\(/) || !patchedAny) { - return Promise.resolve(contents); + return Promise.resolve(null); } let platform = filename.match(/index\.([^.]+?)\.js/)[1]; - return getDsn(platform).then(function(dsn) { - return Promise.resolve(contents.replace(/^([^]*)(import\s+[^;]*?;$)/m, function(match) { - return match + '\n\nimport { Sentry } from \'react-native-sentry\';\n\n' + - 'Sentry.config(' + JSON.stringify(dsn) + ').install();\n'; - })); + return shouldConfigurePlatform(platform).then((shouldConfigure) => { + if (!shouldConfigure) { + return null; + } + + return getDsn(platform).then(function(dsn) { + return Promise.resolve(contents.replace(/^([^]*)(import\s+[^;]*?;$)/m, function(match) { + return match + '\n\nimport { Sentry } from \'react-native-sentry\';\n\n' + + 'Sentry.config(' + JSON.stringify(dsn) + ').install();\n'; + })); + }); }); } function patchBuildGradle(contents) { let applyFrom = 'apply from: "../../node_modules/react-native-sentry/sentry.gradle"'; if (contents.indexOf(applyFrom) >= 0) { - return Promise.resolve(contents); + return Promise.resolve(null); } - return Promise.resolve(contents.replace( - /^apply from: "..\/..\/node_modules\/react-native\/react.gradle"/m, - function(match) { - return match + '\n' + applyFrom; + return shouldConfigurePlatform('android').then((shouldConfigure) => { + if (!shouldConfigure) { + return null; } - )); + + return Promise.resolve(contents.replace( + /^apply from: "..\/..\/node_modules\/react-native\/react.gradle"/m, + function(match) { + return match + '\n' + applyFrom; + } + )); + }); } function patchAlwaysIncludeSwift(proj) { @@ -245,7 +282,18 @@ function patchXcodeProj(contents, filename) { addNewXcodeBuildPhaseForSymbols(buildScripts, proj); addNewXcodeBuildPhaseForBundleFw(buildScripts, proj); - resolve(proj.writeSync()); + // we always modify the xcode file in memory but we only want to save it + // in case the user wants configuration for ios. This is why we check + // here first if changes are made before we might prompt the platform + // continue prompt. + let newContents = proj.writeSync(); + if (newContents === contents) { + resolve(null); + } else { + return shouldConfigurePlatform('ios').then((shouldConfigure) => { + resolve(shouldConfigure ? newContents : null); + }); + } }); }); } @@ -260,7 +308,7 @@ function patchMatchingFile(pattern, func) { encoding: 'utf-8' }); rv = rv.then(() => func(contents, match)).then(function(newContents) { - if (contents != newContents) { + if (newContents !== null && contents !== undefined && contents != newContents) { patchedAny = true; fs.writeFileSync(match, newContents); } @@ -270,7 +318,7 @@ function patchMatchingFile(pattern, func) { } function addSentryProperties() { - let rv = null; + let rv = Promise.resolve(); for (let platform of PLATFORMS) { let fn = platform + '/sentry.properties'; @@ -278,24 +326,25 @@ function addSentryProperties() { continue; } - let p = () => getProperties(platform).then((props) => { - fs.writeFileSync(fn, dumpProperties(props)); - }); - if (rv === null) { - rv = p(); - } else { - rv = rv.then(p); - } + rv = rv.then(() => shouldConfigurePlatform(platform).then((shouldConfigure) => { + if (!shouldConfigure) { + return null; + } + return getProperties(platform).then((props) => { + fs.writeFileSync(fn, dumpProperties(props)); + }); + })); } return rv; } Promise.resolve() + .then(() => patchMatchingFile('**/app/build.gradle', patchBuildGradle)) .then(() => patchMatchingFile('**/*.xcodeproj/project.pbxproj', patchXcodeProj)) .then(() => patchMatchingFile('**/AppDelegate.m', patchAppDelegate)) - .then(() => patchMatchingFile('**/app/build.gradle', patchBuildGradle)) - .then(() => patchMatchingFile('index.*.js', patchIndexJs)) + .then(() => patchMatchingFile('index.android.js', patchIndexJs)) + .then(() => patchMatchingFile('index.ios.js', patchIndexJs)) .then(() => addSentryProperties()) .catch(function(e) { console.log('Could not link react-native-sentry: ' + e); From 3d9dd8f611528f31edefa2d635aaf9164faefc03 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 20 May 2017 15:49:42 +0200 Subject: [PATCH 05/13] Fixed a syntax error --- scripts/postunlink | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/postunlink b/scripts/postunlink index c0a164080d..3e096cc5ce 100644 --- a/scripts/postunlink +++ b/scripts/postunlink @@ -5,7 +5,7 @@ let xcode = require('xcode'); function unpatchAppDelegate(contents) { return Promise.resolve(contents .replace(/^#if __has_include\(\)[^]*?\#endif\r?\n/m, '') - .replace(/^#import\s+(?:|"RNSentry.h")\s*?\r?\n/m '') + .replace(/^#import\s+(?:|"RNSentry.h")\s*?\r?\n/m, '') .replace(/(\r?\n|^)\s*\[RNSentry\s+installWithRootView:.*?\];\s*?\r?\n/m, '')); } From 49e2d0120aa8ef2d12fc8c1df77b1720f5953056 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 20 May 2017 16:16:59 +0200 Subject: [PATCH 06/13] Detect old property files to indicate platform configuration --- scripts/postlink | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/postlink b/scripts/postlink index 936c2cb672..33d3633a7d 100644 --- a/scripts/postlink +++ b/scripts/postlink @@ -27,6 +27,13 @@ function shouldConfigurePlatform(platform) { if (configurePlatform[platform] !== undefined) { return Promise.resolve(configurePlatform[platform]); } + // if a sentry.properties file exists for the platform we want to configure + // without asking the user. This means that re-linking later will not + // bring up a useless dialog. + if (fs.existsSync(platform + '/sentry.properties')) { + configurePlatform[platform] = true; + return Promise.resolve(true); + } return inquirer.prompt([{ type: 'list', name: 'configure', From 56111c4cb224e0152d05e2ab5c59d90dd2fc45bf Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 21 May 2017 14:02:13 +0200 Subject: [PATCH 07/13] Validate DSN and link to /api/ --- package.json | 5 +++-- scripts/postlink | 51 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 27f839081c..e368c23b5f 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,11 @@ "react-native": ">=0.38.0" }, "dependencies": { + "chalk": "^1.1.1", + "glob": "7.1.1", + "inquirer": "3.0.6", "raven-js": "^3.15.0", "sentry-cli-binary": "^1.9.0", - "inquirer": "3.0.6", - "glob": "7.1.1", "xcode": "0.9.3" }, "rnpm": { diff --git a/scripts/postlink b/scripts/postlink index 33d3633a7d..310c608b86 100644 --- a/scripts/postlink +++ b/scripts/postlink @@ -1,10 +1,11 @@ -let glob = require('glob'); -let fs = require('fs'); -let inquirer = require('inquirer'); -let xcode = require('xcode'); - -let PLATFORMS = ['android', 'ios']; -let OBJC_HEADER = '\ +const glob = require('glob'); +const fs = require('fs'); +const inquirer = require('inquirer'); +const xcode = require('xcode'); +const chalk = require('chalk'); + +const PLATFORMS = ['android', 'ios']; +const OBJC_HEADER = '\ #if __has_include()\n\ #import // This is used for versions of react >= 0.40\n\ #else\n\ @@ -14,6 +15,7 @@ let OBJC_HEADER = '\ let cachedDsn = null; let cachedProps = {}; let patchedAny = false; +let didShowInfoHint = false; let configurePlatform = {}; function getPlatformName(platform) { @@ -23,6 +25,26 @@ function getPlatformName(platform) { }[platform] || platform; } +function considerShowingInfoHint() { + if (didShowInfoHint) { + return; + } + console.log(''); + console.log(chalk.green( + 'You are about to configure Sentry for React Native')); + console.log(chalk.dim( + 'We will ask you a bunch of questions to configure Sentry for you.')); + console.log(chalk.dim( + 'If you chose not to configure an integration you can run link again')); + console.log(chalk.dim( + 'later to configure that platform.')); + console.log(''); + console.log('You will need an API key and the DSN for the application'); + console.log('You can find these at https://sentry.io/api/'); + console.log(''); + didShowInfoHint = true; +} + function shouldConfigurePlatform(platform) { if (configurePlatform[platform] !== undefined) { return Promise.resolve(configurePlatform[platform]); @@ -34,6 +56,7 @@ function shouldConfigurePlatform(platform) { configurePlatform[platform] = true; return Promise.resolve(true); } + considerShowingInfoHint(); return inquirer.prompt([{ type: 'list', name: 'configure', @@ -55,11 +78,25 @@ function shouldConfigurePlatform(platform) { } function getDsn(platform) { + considerShowingInfoHint(); return inquirer.prompt([{ type: 'input', default: cachedDsn || process.env.SENTRY_DSN || 'YOUR_DSN_HERE', message: 'The DSN for ' + getPlatformName(platform), name: 'dsn', + validate: function(value) { + let m = value.match(/^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)$/); + if (!m) { + return 'invalid dsn format'; + } + if (m[1] !== 'http' && m[1] !== 'https') { + return 'unsupported protocol for dsn: ' + m[1]; + } + if (!m[3]) { + return 'missing secret in dsn'; + } + return true; + } }]).then(function(answers) { cachedDsn = answers.dsn; return Promise.resolve(answers.dsn); From 62659ea07d3e02a12b9cf000b3856b0ee7931ebc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 21 May 2017 21:24:38 +0200 Subject: [PATCH 08/13] Small text improvements --- scripts/postlink | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/postlink b/scripts/postlink index 310c608b86..847b003721 100644 --- a/scripts/postlink +++ b/scripts/postlink @@ -39,8 +39,9 @@ function considerShowingInfoHint() { console.log(chalk.dim( 'later to configure that platform.')); console.log(''); - console.log('You will need an API key and the DSN for the application'); - console.log('You can find these at https://sentry.io/api/'); + console.log('You will need the DSN and an API key for the application.'); + console.log('You can find these in the project settings and the API key'); + console.log('at https://sentry.io/api/'); console.log(''); didShowInfoHint = true; } From c01caea0031dbac2e65a2f0a96d584e3f804aedf Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 21 May 2017 21:31:12 +0200 Subject: [PATCH 09/13] Rewrap text a bit --- scripts/postlink | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/scripts/postlink b/scripts/postlink index 847b003721..06b06d6f07 100644 --- a/scripts/postlink +++ b/scripts/postlink @@ -29,20 +29,20 @@ function considerShowingInfoHint() { if (didShowInfoHint) { return; } - console.log(''); - console.log(chalk.green( - 'You are about to configure Sentry for React Native')); - console.log(chalk.dim( - 'We will ask you a bunch of questions to configure Sentry for you.')); - console.log(chalk.dim( - 'If you chose not to configure an integration you can run link again')); - console.log(chalk.dim( - 'later to configure that platform.')); - console.log(''); - console.log('You will need the DSN and an API key for the application.'); - console.log('You can find these in the project settings and the API key'); - console.log('at https://sentry.io/api/'); - console.log(''); + + let {green, dim} = chalk; + function l(msg) { console.log(msg); } + + + l(''); + l(green('You are about to configure Sentry for React Native')); + l(dim('We will ask you a bunch of questions to configure Sentry for you.')); + l(dim('If you chose not to configure an integration you can run link again')); + l(dim('later to configure that platform.')); + l(''); + l('You will need the DSN and an API key for the application to proceed.'); + l('The keys can be found the project settings and at sentry.io/api/'); + l(''); didShowInfoHint = true; } From bbd0efe3112f1c4fb603f50e2aac8f7df90fcae1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 21 May 2017 23:05:58 +0200 Subject: [PATCH 10/13] Restructured linking code to make what is happening clearer --- scripts/postlink | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/postlink b/scripts/postlink index 06b06d6f07..63c28ba7ba 100644 --- a/scripts/postlink +++ b/scripts/postlink @@ -362,6 +362,14 @@ function patchMatchingFile(pattern, func) { return rv; } +function addSentryInit() { + let rv = Promise.resolve(); + for (let platform of PLATFORMS) { + rv = rv.then(() => patchMatchingFile(`index.${platform}.js`, patchIndexJs)); + } + return rv; +} + function addSentryProperties() { let rv = Promise.resolve(); @@ -385,11 +393,14 @@ function addSentryProperties() { } Promise.resolve() + /* these steps patch the build files without user interactions */ .then(() => patchMatchingFile('**/app/build.gradle', patchBuildGradle)) .then(() => patchMatchingFile('**/*.xcodeproj/project.pbxproj', patchXcodeProj)) .then(() => patchMatchingFile('**/AppDelegate.m', patchAppDelegate)) - .then(() => patchMatchingFile('index.android.js', patchIndexJs)) - .then(() => patchMatchingFile('index.ios.js', patchIndexJs)) + /* if any of the previous steps did something, this will patch + the index.PLATFORM.js files with the necessary initialization code */ + .then(() => addSentryInit()) + /* writes sentry.properties files with the API key and other settings */ .then(() => addSentryProperties()) .catch(function(e) { console.log('Could not link react-native-sentry: ' + e); From ce1e6a72e02909af91192ef1768e195c6cbf7b80 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 21 May 2017 23:22:00 +0200 Subject: [PATCH 11/13] Fixed smaller issues on unlinking --- scripts/postlink | 6 +++--- scripts/postunlink | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/postlink b/scripts/postlink index 63c28ba7ba..04fc779ecd 100644 --- a/scripts/postlink +++ b/scripts/postlink @@ -281,8 +281,8 @@ function addNewXcodeBuildPhaseForSymbols(buildScripts, proj) { { shellPath: '/bin/sh', shellScript: ( - '"export SENTRY_PROPERTIES=sentry.properties\\n' + - '../node_modules/sentry-cli-binary/bin/sentry-cli upload-dsym"' + 'export SENTRY_PROPERTIES=sentry.properties\\n' + + '../node_modules/sentry-cli-binary/bin/sentry-cli upload-dsym' ) } ); @@ -303,7 +303,7 @@ function addNewXcodeBuildPhaseForBundleFw(buildScripts, proj) { { shellPath: '/bin/sh', shellScript: ( - '"../node_modules/react-native-sentry/bin/bundle-frameworks"' + '../node_modules/react-native-sentry/bin/bundle-frameworks' ) } ); diff --git a/scripts/postunlink b/scripts/postunlink index 3e096cc5ce..7b37183915 100644 --- a/scripts/postunlink +++ b/scripts/postunlink @@ -36,7 +36,7 @@ function unpatchXcodeBuildScripts(proj) { continue; } - script.shellScript = script.shellScript + script.shellScript = JSON.stringify(JSON.parse(script.shellScript) // "legacy" location for this. This is what happens if users followed // the old documentation for where to add the bundle command .replace( @@ -62,7 +62,7 @@ function unpatchXcodeBuildScripts(proj) { } else { return rv; } - }); + })); } // scripts to kill entirely. From d60b6f3bb9680419f89f4b36dd5cf8bf4792ff64 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 21 May 2017 23:39:29 +0200 Subject: [PATCH 12/13] Updated docs for latest link step --- docs/index.rst | 140 +++++++++++++++--------------------------- docs/manual-setup.rst | 71 +++++++++++++++++++++ 2 files changed, 119 insertions(+), 92 deletions(-) create mode 100644 docs/manual-setup.rst diff --git a/docs/index.rst b/docs/index.rst index 89c6b48c2d..c3a875793e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,133 +15,88 @@ We would love to get your feedback! Installation ------------ -Start with adding sentry and linking it:: +Start with adding Sentry and linking it:: $ npm install react-native-sentry --save $ react-native link react-native-sentry -The `link` step will pull in the native dependency. If you are using -expo you don't have to (or can't) run that step. In that case we fall -back automatically. +The `link` step will pull in the native dependency and patch your project +accordingly. If you are using expo you don't have to (or can't) run that +step. For more information about that see :doc:`expo`. + +On linking you will automatically be prompted for your DSN and other +information and we will configure your app automatically for react-native +and change files accordingly. You will need to provide the following +data: your DSN, the slug of your organization in Sentry, the slug of your +project in Sentry as well as the API key. + +You can find the slugs in the URL of your project +(``sentry.io/your-org-slug/your-project-slug``) If you don't have an auth +token yet you can `create an auth token here `_. -On linking you will usually be prompted for your DSN and we will configure -your app automatically for react-native and change files accordingly. Upon linking the following changes will be performed: -* added the raven-java package for native crash reporting on android -* added the sentry-swift package for native crash reporting on iOS -* enabled the sentry gradle build step for android +* add the raven-java package for native crash reporting on Android +* add the sentry-swift package for native crash reporting on iOS +* enable the sentry gradle build step for android * patch `AppDelegate.m` for iOS * patch `MainApplication.java` for Android -* configured Sentry for the supplied DSN in your `index.js` files +* configure Sentry for the supplied DSN in your `index.js` files +* store build credentials in `ios/sentry.properties` and + `android/sentry.properties`. Note that we only support ``react-native >= 0.38`` at the moment. -iOS Specifics -------------- +To see what is happening during linking you can refer to +:doc:`manual-setup` which will give you the details. -Since we use our `Swift Client -`_ in the background, your -project has to embed the swift standard libraries. +Upgrading +--------- -Search for ``ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES`` in your Xcode project -build settings and set it to ``YES``. +If you are upgrading from an earlier version of sentry-react-native you +should re-link the package to ensure the generated code is updated to the +latest version:: -You will get this error message if you forget to set it:: + $ react-native unlink react-native-sentry + $ react-native link react-native-sentry - dyld: Library not loaded: @rpath/libswiftCore.dylib - Referenced from: [Redacted]/Sentry.framework/Sentry - Reason: image not found +iOS Specifics +------------- -Also note that if you build the project without setting this, you have to -run clean in order to make the change work. +Since we use our `Swift Client +`_ in the background, your +project has to embed the swift standard libraries. The link step will do +this automatically for your project. When you use xcode you can hook directly into the build process to upload -debug symbols. Open up your xcode project in the iOS folder, go to your -project's target and change the "Bundle React Native code and images" -build script. The script that is currently there needs to be adjusted as -follows:: - - export SENTRY_ORG=___ORG_NAME___ - export SENTRY_PROJECT=___PROJECT_NAME___ - export SENTRY_AUTH_TOKEN=YOUR_AUTH_TOKEN - export NODE_BINARY=node - ../node_modules/react-native-sentry/bin/bundle-frameworks - ../node_modules/sentry-cli-binary/bin/sentry-cli react-native-xcode \ - ../node_modules/react-native/packager/react-native-xcode.sh - ../node_modules/sentry-cli-binary/bin/sentry-cli upload-dsym - -You can find the slugs in the URL of your project -(sentry.io/your-org-slug/your-project-slug) If you don't have an auth -token yet you can `create an auth token here `_. - -This also uploads debug symbols in the last line which however will not -work for bitcode enabled builds. If you are using bitcode you need to -remove that line (``../node_modules/sentry-cli-binary/bin/sentry-cli -upload-dsym``) and consult the documentation on dsym handling instead (see -:ref:`dsym-with-bitcode`). - -Note that uploading of debug simulator builds by default is disabled for -speed reasons. If you do want to also generate debug symbols for debug -builds you can pass `--allow-fetch` as a parameter to ``react-native-xcode``. +debug symbols and sourcemaps. If you however are using bitcode you will +need to disable the "Upload Debug Symbols to Sentry" build phase and then +separately upload debug symbols from iTunes Connect to Sentry. Android Specifics ----------------- For Android we hook into gradle for the sourcemap build process. When you -run ``react-native link`` the gradle files are automatically updated but -in case you are not using linked frameworks you might have to do it -manually. Whenever you run ``./gradlew assembleRelease`` sourcemaps are -automatically built and uploaded to Sentry. - -To enable the gradle integration you need to change your -``android/app/build.gradle`` file and add the following line after the -``react.gradle`` one:: - - apply from: "../../node_modules/react-native-sentry/sentry.gradle" - -Additionally you need to create an ``android/sentry.properties`` file with -the access credentials: - -.. sourcecode:: ini - - defaults.org=___ORG_NAME___ - defaults.project=___PROJECT_NAME___ - auth.token=YOUR_AUTH_TOKEN +run ``react-native link`` the gradle files are automatically updated. +When you run ``./gradlew assembleRelease`` sourcemaps are automatically +built and uploaded to Sentry. Client Configuration -------------------- -Note: When you run ``react-native link`` we will attempt to automatically -patch your code so you might notice that some of these changes were -already performed. - -Add Sentry to your `index.ios.js` and `index.android.js`: +Note: When you run ``react-native link`` we will automatically update your +`index.ios.js` / `index.android.js` with the following changes: .. sourcecode:: javascript import { Sentry } from 'react-native-sentry'; - Sentry.config('___DSN___').install(); -If you are using the binary version of the package (eg: you ran -``react-native link``) then you additionally need to register the native -crash handler in your `AppDelegate.m` after the root view was created for -iOS: - -.. sourcecode:: objc - - #if __has_include() - #import // This is used for versions of react >= 0.40 - #else - #import "RNSentry.h" // This is used for versions of react < 0.40 - #endif - - /* in your didFinishLaunchingWithOptions */ - [RNSentry installWithRootView:rootView]; +You can pass additional configuration options to the `config()` method if +you want to do so. -More ----- +Deep Dive +--------- .. toctree:: :maxdepth: 2 @@ -150,3 +105,4 @@ More expo sourcemaps cocoapods + manual-setup diff --git a/docs/manual-setup.rst b/docs/manual-setup.rst new file mode 100644 index 0000000000..4db71d14f7 --- /dev/null +++ b/docs/manual-setup.rst @@ -0,0 +1,71 @@ +Manual Setup +============ + +If you can't (or don't want) to run the linking step you can see here what +is happening on each platform. + +iOS +--- + +Since we use our `Swift Client +`_ in the background, your +project has to embed the swift standard libraries. + +Xcode Settings +`````````````` + +The link step sets ``ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES`` in your Xcode +project's build settings to ``YES``. + +You will get this error message if that setting is not set:: + + dyld: Library not loaded: @rpath/libswiftCore.dylib + Referenced from: [Redacted]/Sentry.framework/Sentry + Reason: image not found + +Build Steps +``````````` + +When you use Xcode you can hook directly into the build process to upload +debug symbols. When linking one build phase script is changed and two more +are added. + +We modify the react-native build phase ("Bundle React Native code and images") +slightly from this:: + + export NODE_BINARY=node + ../node_modules/react-native/packager/react-native-xcode.sh + +To this:: + + export NODE_BINARY=node + export SENTRY_PROPERTIES=sentry.properties + ../node_modules/sentry-cli-binary/bin/sentry-cli react-native-xcode \ + ../node_modules/react-native/packager/react-native-xcode.sh + +Additionally we add a build script called "Bundle react-native-sentry +Frameworks" which bundles necessary frameworks as well as a build +step called "Upload Debug Symbols to Sentry" which uploads debug symbols +to Sentry. The latter needs to be disabled if you use bitcode. + +This also uploads debug symbols in the last line which however will not +work for bitcode enabled builds. If you are using bitcode you need to +remove that line (``../node_modules/sentry-cli-binary/bin/sentry-cli +upload-dsym``) and consult the documentation on dsym handling instead (see +:ref:`dsym-with-bitcode`). + +Note that uploading of debug simulator builds by default is disabled for +speed reasons. If you do want to also generate debug symbols for debug +builds you can pass `--allow-fetch` as a parameter to ``react-native-xcode`` +in the above mentioned build phase. + +Android +------- + +For Android we hook into gradle for the sourcemap build process. When you +run ``react-native link`` the gradle files are automatically updated. + +We enable the gradle integration in your ``android/app/build.gradle`` file +by adding the following line after the ``react.gradle`` one:: + + apply from: "../../node_modules/react-native-sentry/sentry.gradle" From 68707f52ac0579238f4ea1dd3626995b41a08a74 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 23 May 2017 08:33:38 +0100 Subject: [PATCH 13/13] Small changes to the doc index --- docs/index.rst | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index c3a875793e..0be7887663 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,24 +5,21 @@ React Native ============ -This is the documentation for our beta clients for React-Native. This is -an early release with various different levels of support. iOS is best -supported if you are also using the native extension and if not we fall -back to pure JavaScript for basic support. - -We would love to get your feedback! +This is the documentation for our beta clients for React-Native. The +React-Native client uses a native extension for iOS and Android but can +fall back to a pure JavaScript version if needed. Installation ------------ -Start with adding Sentry and linking it:: +Start by adding Sentry and then linking it:: $ npm install react-native-sentry --save $ react-native link react-native-sentry The `link` step will pull in the native dependency and patch your project accordingly. If you are using expo you don't have to (or can't) run that -step. For more information about that see :doc:`expo`. +link step. For more information about that see :doc:`expo`. On linking you will automatically be prompted for your DSN and other information and we will configure your app automatically for react-native @@ -45,10 +42,10 @@ Upon linking the following changes will be performed: * store build credentials in `ios/sentry.properties` and `android/sentry.properties`. -Note that we only support ``react-native >= 0.38`` at the moment. - To see what is happening during linking you can refer to -:doc:`manual-setup` which will give you the details. +:doc:`manual-setup` which will give you all the details. + +Note that we only support ``react-native >= 0.38`` at the moment. Upgrading ---------