Skip to content

Commit 07adce0

Browse files
authored
Merge branch 'master' into improve-test-stability-with-safer-access
2 parents 0120472 + 19d4276 commit 07adce0

2 files changed

Lines changed: 164 additions & 1 deletion

File tree

src/extension/android/packageNameResolver.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,19 @@ import { FileSystem } from "../../common/node/fileSystem";
66

77
export class PackageNameResolver {
88
private static PackageNameRegexp: RegExp = /package="(.+?)"/;
9+
private static ApplicationIdRegexp: RegExp = /applicationId\s+(=)?\s*["'](.+?)["']/;
910
private static ManifestName = "AndroidManifest.xml";
11+
private static GradleBuildName = "build.gradle";
1012
private static DefaultPackagePrefix = "com.";
1113
private static SourceRootRelPath: string[] = ["android", "app", "src", "main"];
1214
private static DefaultManifestLocation: string[] = PackageNameResolver.SourceRootRelPath.concat(
1315
PackageNameResolver.ManifestName,
1416
);
17+
private static DefaultGradleBuildLocation: string[] = [
18+
"android",
19+
"app",
20+
PackageNameResolver.GradleBuildName,
21+
];
1522
private applicationName: string;
1623

1724
constructor(applicationName: string) {
@@ -22,14 +29,35 @@ export class PackageNameResolver {
2229
* Tries to find the package name in AndroidManifest.xml. If not found, it returns the default package name,
2330
* which is the application name prefixed with the default prefix.
2431
*/
25-
public resolvePackageName(projectRoot: string): Promise<string> {
32+
public async resolvePackageName(projectRoot: string): Promise<string> {
33+
const expectedGradleBuildPath = path.join.apply(
34+
this,
35+
[projectRoot].concat(PackageNameResolver.DefaultGradleBuildLocation),
36+
);
37+
const gradlePackageName = await this.readApplicationId(expectedGradleBuildPath);
38+
if (gradlePackageName) {
39+
return gradlePackageName;
40+
}
41+
2642
const expectedAndroidManifestPath = path.join.apply(
2743
this,
2844
[projectRoot].concat(PackageNameResolver.DefaultManifestLocation),
2945
);
3046
return this.readPackageName(expectedAndroidManifestPath);
3147
}
3248

49+
private async readApplicationId(gradlePath: string): Promise<string | null> {
50+
if (gradlePath) {
51+
const fs = new FileSystem();
52+
if (await fs.exists(gradlePath)) {
53+
const content = await fs.readFile(gradlePath);
54+
const match = content.toString().match(PackageNameResolver.ApplicationIdRegexp);
55+
return match ? match[2] : null;
56+
}
57+
}
58+
return null;
59+
}
60+
3361
/**
3462
* Given a manifest file path, it parses the file and returns the package name.
3563
* If the package name cannot be parsed, the default packge name is returned.
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for details.
3+
4+
import * as path from "path";
5+
import * as assert from "assert";
6+
const sinon = require("sinon");
7+
import { PackageNameResolver } from "../../../src/extension/android/packageNameResolver";
8+
import { FileSystem } from "../../../src/common/node/fileSystem";
9+
10+
suite("PackageNameResolver", function () {
11+
const projectRoot = path.join(__dirname, "mockProject");
12+
const androidRoot = path.join(projectRoot, "android");
13+
const appRoot = path.join(androidRoot, "app");
14+
const manifestPath = path.join(appRoot, "src", "main", "AndroidManifest.xml");
15+
const buildGradlePath = path.join(appRoot, "build.gradle");
16+
17+
let readFileStub: any;
18+
let existsStub: any;
19+
20+
setup(() => {
21+
// Mock FileSystem methods
22+
existsStub = sinon.stub(FileSystem.prototype, "exists");
23+
readFileStub = sinon.stub(FileSystem.prototype, "readFile");
24+
});
25+
26+
teardown(() => {
27+
if (existsStub) existsStub.restore();
28+
if (readFileStub) readFileStub.restore();
29+
});
30+
31+
test("should resolve package name from AndroidManifest.xml if build.gradle is missing", async () => {
32+
const manifestContent = `
33+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
34+
package="com.example.manifest">
35+
</manifest>`;
36+
37+
existsStub.withArgs(manifestPath).returns(Promise.resolve(true));
38+
existsStub.withArgs(buildGradlePath).returns(Promise.resolve(false));
39+
readFileStub.withArgs(manifestPath).returns(Promise.resolve(manifestContent));
40+
41+
const resolver = new PackageNameResolver("ExampleApp");
42+
const packageName = await resolver.resolvePackageName(projectRoot);
43+
44+
assert.strictEqual(packageName, "com.example.manifest");
45+
});
46+
47+
test("should resolve application id from build.gradle", async () => {
48+
const buildGradleContent = `
49+
android {
50+
defaultConfig {
51+
applicationId "com.example.gradle"
52+
}
53+
}
54+
`;
55+
56+
existsStub.withArgs(buildGradlePath).returns(Promise.resolve(true));
57+
readFileStub.withArgs(buildGradlePath).returns(Promise.resolve(buildGradleContent));
58+
59+
const resolver = new PackageNameResolver("ExampleApp");
60+
const packageName = await resolver.resolvePackageName(projectRoot);
61+
62+
// This is expected to fail until the fix is implemented
63+
assert.strictEqual(packageName, "com.example.gradle");
64+
});
65+
66+
test("should prioritize build.gradle applicationId over AndroidManifest.xml package", async () => {
67+
const manifestContent = `
68+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
69+
package="com.example.manifest">
70+
</manifest>`;
71+
const buildGradleContent = `
72+
android {
73+
defaultConfig {
74+
applicationId "com.example.gradle"
75+
}
76+
}
77+
`;
78+
79+
existsStub.withArgs(manifestPath).returns(Promise.resolve(true));
80+
existsStub.withArgs(buildGradlePath).returns(Promise.resolve(true));
81+
readFileStub.withArgs(manifestPath).returns(Promise.resolve(manifestContent));
82+
readFileStub.withArgs(buildGradlePath).returns(Promise.resolve(buildGradleContent));
83+
84+
const resolver = new PackageNameResolver("ExampleApp");
85+
const packageName = await resolver.resolvePackageName(projectRoot);
86+
87+
// This is expected to fail until the fix is implemented
88+
assert.strictEqual(packageName, "com.example.gradle");
89+
});
90+
91+
test("should resolve application id from build.gradle using single quotes", async () => {
92+
const buildGradleContent = `
93+
android {
94+
defaultConfig {
95+
applicationId 'com.example.gradle.singlequote'
96+
}
97+
}
98+
`;
99+
100+
existsStub.withArgs(buildGradlePath).returns(Promise.resolve(true));
101+
readFileStub.withArgs(buildGradlePath).returns(Promise.resolve(buildGradleContent));
102+
103+
const resolver = new PackageNameResolver("ExampleApp");
104+
const packageName = await resolver.resolvePackageName(projectRoot);
105+
106+
assert.strictEqual(packageName, "com.example.gradle.singlequote");
107+
});
108+
109+
test("should resolve application id from build.gradle using assignment", async () => {
110+
const buildGradleContent = `
111+
android {
112+
defaultConfig {
113+
applicationId = "com.example.gradle.assignment"
114+
}
115+
}
116+
`;
117+
118+
existsStub.withArgs(buildGradlePath).returns(Promise.resolve(true));
119+
readFileStub.withArgs(buildGradlePath).returns(Promise.resolve(buildGradleContent));
120+
121+
const resolver = new PackageNameResolver("ExampleApp");
122+
const packageName = await resolver.resolvePackageName(projectRoot);
123+
124+
assert.strictEqual(packageName, "com.example.gradle.assignment");
125+
});
126+
127+
test("should fall back to default package name if neither file exists", async () => {
128+
existsStub.returns(Promise.resolve(false));
129+
130+
const resolver = new PackageNameResolver("ExampleApp");
131+
const packageName = await resolver.resolvePackageName(projectRoot);
132+
133+
assert.strictEqual(packageName, "com.exampleapp");
134+
});
135+
});

0 commit comments

Comments
 (0)