From 908cef549df8016b90e3e96ed936b106cf3cac94 Mon Sep 17 00:00:00 2001 From: Harshvardhan Joshi Date: Thu, 11 Dec 2025 17:45:44 +0530 Subject: [PATCH 1/2] bugfix/package-name-resolver-for-android-outdated Package resolver should not depend on the package value from `AndroidManifest.xml` anymore, the latest android app configuration enforce adding the package name in app/build.gradle as `applicationId` Made following changes to deal with the issue: - Fixed package resolver code to detect applicationId in the app/build.gradle file before fallback to android manifest version - Added basic tests to verify the same changes --- src/extension/android/packageNameResolver.ts | 30 +++- .../android/packageNameResolver.test.ts | 135 ++++++++++++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 test/extension/android/packageNameResolver.test.ts diff --git a/src/extension/android/packageNameResolver.ts b/src/extension/android/packageNameResolver.ts index 80cf6861c..bfb458514 100644 --- a/src/extension/android/packageNameResolver.ts +++ b/src/extension/android/packageNameResolver.ts @@ -6,12 +6,19 @@ import { FileSystem } from "../../common/node/fileSystem"; export class PackageNameResolver { private static PackageNameRegexp: RegExp = /package="(.+?)"/; + private static ApplicationIdRegexp: RegExp = /applicationId\s+(=)?\s*["'](.+?)["']/; private static ManifestName = "AndroidManifest.xml"; + private static GradleBuildName = "build.gradle"; private static DefaultPackagePrefix = "com."; private static SourceRootRelPath: string[] = ["android", "app", "src", "main"]; private static DefaultManifestLocation: string[] = PackageNameResolver.SourceRootRelPath.concat( PackageNameResolver.ManifestName, ); + private static DefaultGradleBuildLocation: string[] = [ + "android", + "app", + PackageNameResolver.GradleBuildName, + ]; private applicationName: string; constructor(applicationName: string) { @@ -22,7 +29,16 @@ export class PackageNameResolver { * Tries to find the package name in AndroidManifest.xml. If not found, it returns the default package name, * which is the application name prefixed with the default prefix. */ - public resolvePackageName(projectRoot: string): Promise { + public async resolvePackageName(projectRoot: string): Promise { + const expectedGradleBuildPath = path.join.apply( + this, + [projectRoot].concat(PackageNameResolver.DefaultGradleBuildLocation), + ); + const gradlePackageName = await this.readApplicationId(expectedGradleBuildPath); + if (gradlePackageName) { + return gradlePackageName; + } + const expectedAndroidManifestPath = path.join.apply( this, [projectRoot].concat(PackageNameResolver.DefaultManifestLocation), @@ -30,6 +46,18 @@ export class PackageNameResolver { return this.readPackageName(expectedAndroidManifestPath); } + private async readApplicationId(gradlePath: string): Promise { + if (gradlePath) { + const fs = new FileSystem(); + if (await fs.exists(gradlePath)) { + const content = await fs.readFile(gradlePath); + const match = content.toString().match(PackageNameResolver.ApplicationIdRegexp); + return match ? match[2] : null; + } + } + return null; + } + /** * Given a manifest file path, it parses the file and returns the package name. * If the package name cannot be parsed, the default packge name is returned. diff --git a/test/extension/android/packageNameResolver.test.ts b/test/extension/android/packageNameResolver.test.ts new file mode 100644 index 000000000..e8ef6da39 --- /dev/null +++ b/test/extension/android/packageNameResolver.test.ts @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +import * as path from "path"; +import * as assert from "assert"; +const sinon = require("sinon"); +import { PackageNameResolver } from "../../../src/extension/android/packageNameResolver"; +import { FileSystem } from "../../../src/common/node/fileSystem"; + +describe("PackageNameResolver", function () { + const projectRoot = path.join(__dirname, "mockProject"); + const androidRoot = path.join(projectRoot, "android"); + const appRoot = path.join(androidRoot, "app"); + const manifestPath = path.join(appRoot, "src", "main", "AndroidManifest.xml"); + const buildGradlePath = path.join(appRoot, "build.gradle"); + + let readFileStub: any; + let existsStub: any; + + beforeEach(() => { + // Mock FileSystem methods + existsStub = sinon.stub(FileSystem.prototype, "exists"); + readFileStub = sinon.stub(FileSystem.prototype, "readFile"); + }); + + afterEach(() => { + if (existsStub) existsStub.restore(); + if (readFileStub) readFileStub.restore(); + }); + + it("should resolve package name from AndroidManifest.xml if build.gradle is missing", async () => { + const manifestContent = ` + + `; + + existsStub.withArgs(manifestPath).returns(Promise.resolve(true)); + existsStub.withArgs(buildGradlePath).returns(Promise.resolve(false)); + readFileStub.withArgs(manifestPath).returns(Promise.resolve(manifestContent)); + + const resolver = new PackageNameResolver("ExampleApp"); + const packageName = await resolver.resolvePackageName(projectRoot); + + assert.strictEqual(packageName, "com.example.manifest"); + }); + + it("should resolve application id from build.gradle", async () => { + const buildGradleContent = ` + android { + defaultConfig { + applicationId "com.example.gradle" + } + } + `; + + existsStub.withArgs(buildGradlePath).returns(Promise.resolve(true)); + readFileStub.withArgs(buildGradlePath).returns(Promise.resolve(buildGradleContent)); + + const resolver = new PackageNameResolver("ExampleApp"); + const packageName = await resolver.resolvePackageName(projectRoot); + + // This is expected to fail until the fix is implemented + assert.strictEqual(packageName, "com.example.gradle"); + }); + + it("should prioritize build.gradle applicationId over AndroidManifest.xml package", async () => { + const manifestContent = ` + + `; + const buildGradleContent = ` + android { + defaultConfig { + applicationId "com.example.gradle" + } + } + `; + + existsStub.withArgs(manifestPath).returns(Promise.resolve(true)); + existsStub.withArgs(buildGradlePath).returns(Promise.resolve(true)); + readFileStub.withArgs(manifestPath).returns(Promise.resolve(manifestContent)); + readFileStub.withArgs(buildGradlePath).returns(Promise.resolve(buildGradleContent)); + + const resolver = new PackageNameResolver("ExampleApp"); + const packageName = await resolver.resolvePackageName(projectRoot); + + // This is expected to fail until the fix is implemented + assert.strictEqual(packageName, "com.example.gradle"); + }); + + it("should resolve application id from build.gradle using single quotes", async () => { + const buildGradleContent = ` + android { + defaultConfig { + applicationId 'com.example.gradle.singlequote' + } + } + `; + + existsStub.withArgs(buildGradlePath).returns(Promise.resolve(true)); + readFileStub.withArgs(buildGradlePath).returns(Promise.resolve(buildGradleContent)); + + const resolver = new PackageNameResolver("ExampleApp"); + const packageName = await resolver.resolvePackageName(projectRoot); + + assert.strictEqual(packageName, "com.example.gradle.singlequote"); + }); + + it("should resolve application id from build.gradle using assignment", async () => { + const buildGradleContent = ` + android { + defaultConfig { + applicationId = "com.example.gradle.assignment" + } + } + `; + + existsStub.withArgs(buildGradlePath).returns(Promise.resolve(true)); + readFileStub.withArgs(buildGradlePath).returns(Promise.resolve(buildGradleContent)); + + const resolver = new PackageNameResolver("ExampleApp"); + const packageName = await resolver.resolvePackageName(projectRoot); + + assert.strictEqual(packageName, "com.example.gradle.assignment"); + }); + + it("should fall back to default package name if neither file exists", async () => { + existsStub.returns(Promise.resolve(false)); + + const resolver = new PackageNameResolver("ExampleApp"); + const packageName = await resolver.resolvePackageName(projectRoot); + + assert.strictEqual(packageName, "com.exampleapp"); + }); +}); From 5043e7ec9e55e31b637999752f2dc43745683070 Mon Sep 17 00:00:00 2001 From: Harshvardhan Joshi Date: Sun, 4 Jan 2026 13:58:54 +0530 Subject: [PATCH 2/2] Fix test by using the suite, before, after and test wrappers --- .../android/packageNameResolver.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/extension/android/packageNameResolver.test.ts b/test/extension/android/packageNameResolver.test.ts index e8ef6da39..54404bc4d 100644 --- a/test/extension/android/packageNameResolver.test.ts +++ b/test/extension/android/packageNameResolver.test.ts @@ -7,7 +7,7 @@ const sinon = require("sinon"); import { PackageNameResolver } from "../../../src/extension/android/packageNameResolver"; import { FileSystem } from "../../../src/common/node/fileSystem"; -describe("PackageNameResolver", function () { +suite("PackageNameResolver", function () { const projectRoot = path.join(__dirname, "mockProject"); const androidRoot = path.join(projectRoot, "android"); const appRoot = path.join(androidRoot, "app"); @@ -17,18 +17,18 @@ describe("PackageNameResolver", function () { let readFileStub: any; let existsStub: any; - beforeEach(() => { + setup(() => { // Mock FileSystem methods existsStub = sinon.stub(FileSystem.prototype, "exists"); readFileStub = sinon.stub(FileSystem.prototype, "readFile"); }); - afterEach(() => { + teardown(() => { if (existsStub) existsStub.restore(); if (readFileStub) readFileStub.restore(); }); - it("should resolve package name from AndroidManifest.xml if build.gradle is missing", async () => { + test("should resolve package name from AndroidManifest.xml if build.gradle is missing", async () => { const manifestContent = ` @@ -44,7 +44,7 @@ describe("PackageNameResolver", function () { assert.strictEqual(packageName, "com.example.manifest"); }); - it("should resolve application id from build.gradle", async () => { + test("should resolve application id from build.gradle", async () => { const buildGradleContent = ` android { defaultConfig { @@ -63,7 +63,7 @@ describe("PackageNameResolver", function () { assert.strictEqual(packageName, "com.example.gradle"); }); - it("should prioritize build.gradle applicationId over AndroidManifest.xml package", async () => { + test("should prioritize build.gradle applicationId over AndroidManifest.xml package", async () => { const manifestContent = ` @@ -88,7 +88,7 @@ describe("PackageNameResolver", function () { assert.strictEqual(packageName, "com.example.gradle"); }); - it("should resolve application id from build.gradle using single quotes", async () => { + test("should resolve application id from build.gradle using single quotes", async () => { const buildGradleContent = ` android { defaultConfig { @@ -106,7 +106,7 @@ describe("PackageNameResolver", function () { assert.strictEqual(packageName, "com.example.gradle.singlequote"); }); - it("should resolve application id from build.gradle using assignment", async () => { + test("should resolve application id from build.gradle using assignment", async () => { const buildGradleContent = ` android { defaultConfig { @@ -124,7 +124,7 @@ describe("PackageNameResolver", function () { assert.strictEqual(packageName, "com.example.gradle.assignment"); }); - it("should fall back to default package name if neither file exists", async () => { + test("should fall back to default package name if neither file exists", async () => { existsStub.returns(Promise.resolve(false)); const resolver = new PackageNameResolver("ExampleApp");