diff --git a/packages/cli-platform-android/src/config/__tests__/getAndroidProject.test.ts b/packages/cli-platform-android/src/config/__tests__/getAndroidProject.test.ts index 25fed58ba..ed208f185 100644 --- a/packages/cli-platform-android/src/config/__tests__/getAndroidProject.test.ts +++ b/packages/cli-platform-android/src/config/__tests__/getAndroidProject.test.ts @@ -6,7 +6,11 @@ * */ -import {validatePackageName} from '../getAndroidProject'; +import { + validatePackageName, + parsePackageNameFromAndroidManifestFile, + parseNamespaceFromBuildGradleFile, +} from '../getAndroidProject'; describe('android::getAndroidProject', () => { const expectedResults = { @@ -34,3 +38,70 @@ describe('android::getAndroidProject', () => { }); }); }); + +describe('parsePackageNameFromAndroidManifestFile', () => { + it('should parse package name from AndroidManifest', () => { + const androidManifest = ` + +`; + + expect(parsePackageNameFromAndroidManifestFile(androidManifest)).toBe( + 'com.example.app', + ); + }); + + it('should return null if package name is missing from AndroidManifest', () => { + const androidManifest = ` + +`; + + expect(parsePackageNameFromAndroidManifestFile(androidManifest)).toBeNull(); + }); +}); + +describe('parseNamespaceFromBuildGradleFile', () => { + // Test that it can parse a namespace from a build.gradle file + it('should parse namespace from build.gradle', () => { + const buildGradle = `apply plugin: 'com.android.application' + +android { + compileSdkVersion 31 + namespace "com.example.app" +}`; + + expect(parseNamespaceFromBuildGradleFile(buildGradle)).toBe( + 'com.example.app', + ); + }); + + // Test that it can parse a namespace from a build.gradle.kts file + it('should parse namespace from build.gradle.kts', () => { + const buildGradle = `plugins { + id 'com.android.application' +} + +android { + compileSdk = 31 + namespace = "com.example.app" +}`; + + expect(parseNamespaceFromBuildGradleFile(buildGradle)).toBe( + 'com.example.app', + ); + }); + + // Test that it returns null if namespace is missing from build.gradle + it('should return null if namespace is missing from build.gradle', () => { + const buildGradle = `apply plugin: 'com.android.application' +android { + compileSdkVersion 31 +}`; + + expect(parseNamespaceFromBuildGradleFile(buildGradle)).toBeNull(); + }); +}); diff --git a/packages/cli-platform-android/src/config/findBuildGradle.ts b/packages/cli-platform-android/src/config/findBuildGradle.ts new file mode 100644 index 000000000..1372c4118 --- /dev/null +++ b/packages/cli-platform-android/src/config/findBuildGradle.ts @@ -0,0 +1,15 @@ +import fs from 'fs'; +import path from 'path'; + +export function findBuildGradle(sourceDir: string) { + const buildGradlePath = path.join(sourceDir, 'build.gradle'); + const buildGradleKtsPath = path.join(sourceDir, 'build.gradle.kts'); + + if (fs.existsSync(buildGradlePath)) { + return buildGradlePath; + } else if (fs.existsSync(buildGradleKtsPath)) { + return buildGradleKtsPath; + } else { + return null; + } +} diff --git a/packages/cli-platform-android/src/config/getAndroidProject.ts b/packages/cli-platform-android/src/config/getAndroidProject.ts index 15e023cad..f30596c1b 100644 --- a/packages/cli-platform-android/src/config/getAndroidProject.ts +++ b/packages/cli-platform-android/src/config/getAndroidProject.ts @@ -17,23 +17,54 @@ export function getAndroidProject(config: Config) { } /** - * Get the package name of the running React Native app - * @param config + * Get the package name/namespace of the running React Native app + * @param manifestPath The path to the AndroidManifest.xml + * @param buildGradlePath The path to the build.gradle[.kts] file. */ -export function getPackageName(manifestPath: string) { +export function getPackageName( + manifestPath: string, + buildGradlePath: string | null, +) { const androidManifest = fs.readFileSync(manifestPath, 'utf8'); - let packageNameMatchArray = androidManifest.match(/package="(.+?)"/); - if (!packageNameMatchArray || packageNameMatchArray.length === 0) { + const packageNameFromManifest = parsePackageNameFromAndroidManifestFile( + androidManifest, + ); + let packageName; + if (packageNameFromManifest) { + // We got the package from the AndroidManifest.xml + packageName = packageNameFromManifest; + } else if (buildGradlePath) { + // We didn't get the package from the AndroidManifest.xml, + // so we'll try to get it from the build.gradle[.kts] file + // via the namespace field. + const buildGradle = fs.readFileSync(buildGradlePath, 'utf8'); + const namespace = parseNamespaceFromBuildGradleFile(buildGradle); + if (namespace) { + packageName = namespace; + } else { + throw new CLIError( + `Failed to build the app: No package name found. + We couldn't parse the namespace from your build.gradle[.kts] file at ${chalk.underline.dim( + `${buildGradlePath}`, + )} + and nor your package in the AndroidManifest at ${chalk.underline.dim( + `${manifestPath}`, + )} + `, + ); + } + } else { throw new CLIError( - `Failed to build the app: No package name found. Found errors in ${chalk.underline.dim( + `Failed to build the app: No package name found. + We failed to parse your AndroidManifest at ${chalk.underline.dim( `${manifestPath}`, - )}`, + )} + and we couldn't find your build.gradle[.kts] file. + `, ); } - let packageName = packageNameMatchArray[1]; - if (!validatePackageName(packageName)) { logger.warn( `Invalid application's package name "${chalk.bgRed( @@ -46,6 +77,27 @@ export function getPackageName(manifestPath: string) { return packageName; } +export function parsePackageNameFromAndroidManifestFile( + androidManifest: string, +) { + const matchArray = androidManifest.match(/package="(.+?)"/); + if (matchArray && matchArray.length > 0) { + return matchArray[1]; + } else { + return null; + } +} + +export function parseNamespaceFromBuildGradleFile(buildGradle: string) { + // search for namespace = inside the build.gradle file via regex + const matchArray = buildGradle.match(/namespace\s*[=]*\s*"(.+?)"/); + if (matchArray && matchArray.length > 0) { + return matchArray[1]; + } else { + return null; + } +} + // Validates that the package name is correct export function validatePackageName(packageName: string) { return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/i.test(packageName); diff --git a/packages/cli-platform-android/src/config/index.ts b/packages/cli-platform-android/src/config/index.ts index be515f4c8..7dcc9a00d 100644 --- a/packages/cli-platform-android/src/config/index.ts +++ b/packages/cli-platform-android/src/config/index.ts @@ -20,6 +20,7 @@ import { import {getPackageName} from './getAndroidProject'; import {findLibraryName} from './findLibraryName'; import {findComponentDescriptors} from './findComponentDescriptors'; +import {findBuildGradle} from './findBuildGradle'; /** * Gets android project config by analyzing given folder and taking some @@ -42,12 +43,14 @@ export function projectConfig( const manifestPath = userConfig.manifestPath ? path.join(sourceDir, userConfig.manifestPath) : findManifest(path.join(sourceDir, appName)); + const buildGradlePath = findBuildGradle(sourceDir); if (!manifestPath) { return null; } - const packageName = userConfig.packageName || getPackageName(manifestPath); + const packageName = + userConfig.packageName || getPackageName(manifestPath, buildGradlePath); if (!packageName) { throw new Error(`Package name not found in ${manifestPath}`); @@ -96,12 +99,14 @@ export function dependencyConfig( const manifestPath = userConfig.manifestPath ? path.join(sourceDir, userConfig.manifestPath) : findManifest(sourceDir); + const buildGradlePath = path.join(sourceDir, 'build.gradle'); if (!manifestPath) { return null; } - const packageName = userConfig.packageName || getPackageName(manifestPath); + const packageName = + userConfig.packageName || getPackageName(manifestPath, buildGradlePath); const packageClassName = findPackageClassName(sourceDir); /**