-
-
Notifications
You must be signed in to change notification settings - Fork 344
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
272 additions
and
2 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
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
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,97 @@ | ||
import { withAppBuildGradle, withProjectBuildGradle } from '@expo/config-plugins'; | ||
|
||
export interface SentryAndroidGradlePluginOptions { | ||
enableAndroidGradlePlugin?: boolean; | ||
includeProguardMapping?: boolean; | ||
dexguardEnabled?: boolean; | ||
autoUploadNativeSymbols?: boolean; | ||
autoUploadProguardMapping?: boolean; | ||
uploadNativeSymbols?: boolean; | ||
includeNativeSources?: boolean; | ||
includeSourceContext?: boolean; | ||
} | ||
|
||
/** | ||
* Adds the Sentry Android Gradle Plugin to the project. | ||
* https://docs.sentry.io/platforms/react-native/manual-setup/manual-setup/#enable-sentry-agp | ||
*/ | ||
export function withSentryAndroidGradlePlugin(config: any, options: SentryAndroidGradlePluginOptions = {}): any { | ||
const version = '4.14.1'; | ||
const includeProguardMapping = options.includeProguardMapping ?? true; | ||
const dexguardEnabled = options.dexguardEnabled ?? false; | ||
const autoUploadProguardMapping = options.autoUploadProguardMapping ?? true; | ||
const uploadNativeSymbols = options.uploadNativeSymbols ?? true; | ||
const autoUploadNativeSymbols = options.autoUploadNativeSymbols ?? true; | ||
const includeNativeSources = options.includeNativeSources ?? true; | ||
const includeSourceContext = options.includeSourceContext ?? false; | ||
|
||
// Modify android/build.gradle | ||
const withSentryProjectBuildGradle = (config: any): any => { | ||
return withProjectBuildGradle(config, (projectBuildGradle: any) => { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
if (!projectBuildGradle.modResults || !projectBuildGradle.modResults.contents) { | ||
throw new Error('android/build.gradle content is missing or undefined.'); | ||
} | ||
|
||
const dependency = `classpath("io.sentry:sentry-android-gradle-plugin:${version}")`; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
if (!projectBuildGradle.modResults.contents.includes(dependency)) { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
projectBuildGradle.modResults.contents = projectBuildGradle.modResults.contents.replace( | ||
/dependencies\s*{/, | ||
`dependencies {\n ${dependency}`, | ||
); | ||
} | ||
|
||
return projectBuildGradle; | ||
}); | ||
}; | ||
|
||
// Modify android/app/build.gradle | ||
const withSentryAppBuildGradle = (config: any): any => { | ||
return withAppBuildGradle(config, (config: any) => { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
if (config.modResults.language === 'groovy') { | ||
const sentryPlugin = `apply plugin: "io.sentry.android.gradle"`; | ||
const sentryConfig = ` | ||
sentry { | ||
autoUploadProguardMapping = ${autoUploadProguardMapping} | ||
includeProguardMapping = ${includeProguardMapping} | ||
dexguardEnabled = ${dexguardEnabled} | ||
uploadNativeSymbols = ${uploadNativeSymbols} | ||
autoUploadNativeSymbols = ${autoUploadNativeSymbols} | ||
includeNativeSources = ${includeNativeSources} | ||
includeSourceContext = ${includeSourceContext} | ||
tracingInstrumentation { | ||
enabled = false | ||
} | ||
autoInstallation { | ||
enabled = false | ||
} | ||
}`; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
let contents = config.modResults.contents; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
if (!contents.includes(sentryPlugin)) { | ||
contents = `${sentryPlugin}\n${contents}`; | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
if (!contents.includes('sentry {')) { | ||
contents = `${contents}\n${sentryConfig}`; | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
config.modResults.contents = contents; | ||
} else { | ||
throw new Error('Cannot configure Sentry in android/app/build.gradle because it is not in Groovy.'); | ||
} | ||
return config; | ||
}); | ||
}; | ||
|
||
return withSentryAppBuildGradle(withSentryProjectBuildGradle(config)); | ||
} |
137 changes: 137 additions & 0 deletions
137
packages/core/test/expo-plugin/withSentryAndroidGradlePlugin.test.ts
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,137 @@ | ||
import { withAppBuildGradle, withProjectBuildGradle } from '@expo/config-plugins'; | ||
|
||
import type { SentryAndroidGradlePluginOptions } from '../../plugin/src/withSentryAndroidGradlePlugin'; | ||
import { withSentryAndroidGradlePlugin } from '../../plugin/src/withSentryAndroidGradlePlugin'; | ||
|
||
jest.mock('@expo/config-plugins', () => ({ | ||
withProjectBuildGradle: jest.fn(), | ||
withAppBuildGradle: jest.fn(), | ||
})); | ||
|
||
const mockedBuildGradle = ` | ||
buildscript { | ||
dependencies { | ||
classpath('otherDependency') | ||
} | ||
} | ||
`; | ||
|
||
const mockedAppBuildGradle = ` | ||
apply plugin: "somePlugin" | ||
react { | ||
} | ||
android { | ||
} | ||
dependencies { | ||
} | ||
`; | ||
|
||
describe('withSentryAndroidGradlePlugin', () => { | ||
const mockConfig = { | ||
name: 'test-app', | ||
slug: 'test-app', | ||
modResults: { contents: '' }, | ||
}; | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('adds the Sentry plugin to build.gradle when enableAndroidGradlePlugin is enabled', () => { | ||
const version = '4.14.1'; | ||
const options: SentryAndroidGradlePluginOptions = { enableAndroidGradlePlugin: true }; | ||
|
||
(withProjectBuildGradle as jest.Mock).mockImplementation((config, callback) => { | ||
const projectBuildGradle = { | ||
modResults: { contents: mockedBuildGradle }, | ||
}; | ||
const modified = callback(projectBuildGradle); | ||
return modified; | ||
}); | ||
|
||
withSentryAndroidGradlePlugin(mockConfig, options); | ||
|
||
expect(withProjectBuildGradle).toHaveBeenCalled(); | ||
expect(withProjectBuildGradle).toHaveBeenCalledWith(expect.any(Object), expect.any(Function)); | ||
|
||
const calledCallback = (withProjectBuildGradle as jest.Mock).mock.calls[0][1]; | ||
const modifiedGradle = calledCallback({ | ||
modResults: { contents: mockedBuildGradle }, | ||
}); | ||
|
||
expect(modifiedGradle.modResults.contents).toContain( | ||
`classpath("io.sentry:sentry-android-gradle-plugin:${version}")`, | ||
); | ||
}); | ||
|
||
it('adds the Sentry plugin configuration to app/build.gradle', () => { | ||
const options: SentryAndroidGradlePluginOptions = { | ||
autoUploadProguardMapping: true, | ||
includeProguardMapping: true, | ||
dexguardEnabled: false, | ||
uploadNativeSymbols: true, | ||
autoUploadNativeSymbols: true, | ||
includeNativeSources: false, | ||
includeSourceContext: true, | ||
}; | ||
|
||
// Mock withAppBuildGradle | ||
(withAppBuildGradle as jest.Mock).mockImplementation((config, callback) => { | ||
const appBuildGradle = { | ||
modResults: { language: 'groovy', contents: mockedAppBuildGradle }, | ||
}; | ||
const modified = callback(appBuildGradle); | ||
return modified; | ||
}); | ||
|
||
withSentryAndroidGradlePlugin(mockConfig, options); | ||
|
||
expect(withAppBuildGradle).toHaveBeenCalled(); | ||
expect(withAppBuildGradle).toHaveBeenCalledWith(expect.any(Object), expect.any(Function)); | ||
|
||
const calledCallback = (withAppBuildGradle as jest.Mock).mock.calls[0][1]; | ||
const modifiedGradle = calledCallback({ | ||
modResults: { language: 'groovy', contents: mockedAppBuildGradle }, | ||
}); | ||
|
||
expect(modifiedGradle.modResults.contents).toContain('apply plugin: "io.sentry.android.gradle"'); | ||
expect(modifiedGradle.modResults.contents).toContain(` | ||
sentry { | ||
autoUploadProguardMapping = true | ||
includeProguardMapping = true | ||
dexguardEnabled = false | ||
uploadNativeSymbols = true | ||
autoUploadNativeSymbols = true | ||
includeNativeSources = false | ||
includeSourceContext = true | ||
tracingInstrumentation { | ||
enabled = false | ||
} | ||
autoInstallation { | ||
enabled = false | ||
} | ||
}`); | ||
}); | ||
|
||
it('throws an error if modResults is missing in build.gradle', () => { | ||
(withProjectBuildGradle as jest.Mock).mockImplementation((config, callback) => { | ||
expect(() => callback({})).toThrow('android/build.gradle content is missing or undefined.'); | ||
}); | ||
|
||
withSentryAndroidGradlePlugin(mockConfig, {}); | ||
|
||
expect(withProjectBuildGradle).toHaveBeenCalled(); | ||
}); | ||
|
||
it('throws an error if app/build.gradle is not Groovy', () => { | ||
(withAppBuildGradle as jest.Mock).mockImplementation((config, callback) => { | ||
expect(() => callback({ modResults: { language: 'kotlin', contents: mockedAppBuildGradle } })).toThrow( | ||
'Cannot configure Sentry in android/app/build.gradle because it is not in Groovy.', | ||
); | ||
}); | ||
|
||
withSentryAndroidGradlePlugin(mockConfig, {}); | ||
|
||
expect(withAppBuildGradle).toHaveBeenCalled(); | ||
}); | ||
}); |
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