Skip to content

Commit ff1009c

Browse files
authored
143 package namespace (#145)
* Starting some work for #143 * Starting shell for gradle version * Get gradle source and gradle plugin version * Gradle version compare * Set and get namespace ops * Tests
1 parent 691dd81 commit ff1009c

File tree

8 files changed

+208
-8
lines changed

8 files changed

+208
-8
lines changed

packages/common/test/fixtures/ios-and-android/android/app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
apply plugin: 'com.android.application'
22

33
android {
4+
namespace "io.ionic.starter"
45
compileSdkVersion rootProject.ext.compileSdkVersion
56
defaultConfig {
67
applicationId "io.ionic.starter"

packages/configure/test/ops/android.gradle.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const makeOp = (name: string, value: any): Operation => ({
1818
displayText: expect.anything(),
1919
});
2020

21-
describe('op: android.gralde', () => {
21+
describe('op: android.gradle', () => {
2222
let dir: string;
2323
let ctx: Context;
2424

@@ -55,6 +55,7 @@ describe('op: android.gralde', () => {
5555
apply plugin: 'com.android.application'
5656
5757
android {
58+
namespace "io.ionic.starter"
5859
compileSdkVersion rootProject.ext.compileSdkVersion
5960
defaultConfig {
6061
applicationId "io.ionic.starter"

packages/project/src/android/gradle-file.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class GradleFile extends VFSStorable {
104104
*/
105105
// This is a beast, sorry. Hey, at least there's tests
106106
// In the future, this could be moved to the Java `gradle-parse` package provided in this monorepo
107-
// along with modifying the AST to inject our script but this works fine forn ow
107+
// along with modifying the AST to inject our script but this works fine for now
108108
private async replaceInGradleFile(
109109
toInject: any,
110110
targetNode: { node: GradleASTNode; depth: number },
@@ -441,6 +441,27 @@ export class GradleFile extends VFSStorable {
441441
}
442442
}
443443

444+
getSource(node: GradleASTNode) {
445+
if (!this.parsed || !this.source) {
446+
throw new Error('Call parse() first to load Gradle file');
447+
}
448+
449+
const lines = this.source.split(/\r?\n/);
450+
451+
const sourceLines = lines.slice(node.source.line - 1, node.source.lastLine);
452+
453+
const firstLine = sourceLines[0].slice(Math.max(0, node.source.column - 1));
454+
const lastLine = sourceLines[sourceLines.length - 1].slice(0, node.source.lastColumn);
455+
456+
if (sourceLines.length > 2) {
457+
return [firstLine, ...sourceLines.slice(1, sourceLines.length - 1), lastLine].join('\n');
458+
} else if (sourceLines.length == 2) {
459+
return [firstLine, lastLine].join('\n');
460+
} else {
461+
return firstLine;
462+
}
463+
}
464+
444465
private getDepth(pathObject: any) {
445466
let depth = 0;
446467
let n = pathObject;
@@ -642,6 +663,38 @@ export class GradleFile extends VFSStorable {
642663
return null;
643664
}
644665

666+
async getNamespace(): Promise<string | null> {
667+
const source = await this.getGradleSource();
668+
669+
if (source) {
670+
const namespace = source.match(/namespace\s+["']([^"']+)["']/);
671+
672+
if (!namespace) {
673+
return null;
674+
}
675+
676+
return namespace[1];
677+
}
678+
return null;
679+
}
680+
681+
async setNamespace(namespace: string) {
682+
const source = await this.getGradleSource();
683+
684+
if (source) {
685+
Logger.v('gradle', 'setNamespace', `to ${namespace} in ${this.filename}`);
686+
687+
return this.replaceProperties({
688+
android: {
689+
namespace: {}
690+
}
691+
}, {
692+
namespace: `"${namespace}"`
693+
});
694+
}
695+
}
696+
697+
645698
/*
646699
Generate a fragment of Gradle/Groovy code given the inject object
647700

packages/project/src/android/project.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { PropertiesFile } from '../properties';
2020
import { PlatformProject } from '../platform-project';
2121
import { readSource } from '../read-src';
2222
import { Logger } from '../logger';
23+
import { compare } from '../util/gradle-versions';
2324

2425
export class AndroidProject extends PlatformProject {
2526
private manifest: XmlFile;
@@ -168,6 +169,7 @@ export class AndroidProject extends PlatformProject {
168169

169170
this.manifest.getDocumentElement()?.setAttribute('package', packageName);
170171
await this.appBuildGradle?.setApplicationId(packageName);
172+
await this.appBuildGradle?.setNamespace(packageName);
171173
Logger.v('android', 'setPackageName', `set manifest package attribute and applicationId to ${packageName}`);
172174
this.manifest.setAttrs('manifest/application/activity', {
173175
'android:name': `${packageName}.MainActivity`,
@@ -240,7 +242,31 @@ export class AndroidProject extends PlatformProject {
240242
return `${parts[parts.length - 1]}.java`;
241243
}
242244

243-
getPackageName() {
245+
async getGradlePluginVersion() {
246+
await this.buildGradle?.parse();
247+
248+
const found = this.buildGradle?.find({
249+
buildscript: {
250+
dependencies: {
251+
classpath: {}
252+
}
253+
}
254+
});
255+
256+
const sources = (found ?? []).map(f => this.buildGradle?.getSource(f.node) ?? '');
257+
258+
const gradleLine = sources.find(s => s.indexOf('com.android.tools.build:gradle:'));
259+
260+
return gradleLine?.match(/:([\d.]+)/)?.[1] ?? null;
261+
}
262+
263+
async getPackageName() {
264+
const namespace = await this.appBuildGradle?.getNamespace();
265+
266+
if (namespace) {
267+
return namespace;
268+
}
269+
244270
return this.manifest.getDocumentElement()?.getAttribute('package');
245271
}
246272

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export function compare(version1: string, version2: string) {
2+
// Split the version strings into arrays of individual version components
3+
const version1Components = version1.split('.');
4+
const version2Components = version2.split('.');
5+
6+
// Iterate through each component of the version strings
7+
for (let i = 0; i < version1Components.length || i < version2Components.length; i++) {
8+
// Convert the current component to a number (or NaN if it can't be converted)
9+
const version1Component = Number(version1Components[i]);
10+
const version2Component = Number(version2Components[i]);
11+
12+
// If one of the components is NaN (not a number), we need to handle it differently
13+
if (isNaN(version1Component) || isNaN(version2Component)) {
14+
// Compare the non-numeric components as strings
15+
const stringCompareResult = compareStringComponents(version1Components[i], version2Components[i]);
16+
if (stringCompareResult !== 0) {
17+
return stringCompareResult;
18+
}
19+
} else {
20+
// Compare the numeric components
21+
if (version1Component > version2Component) {
22+
return 1;
23+
} else if (version1Component < version2Component) {
24+
return -1;
25+
}
26+
}
27+
}
28+
29+
// If we've made it this far, the versions are either equal or one is a prefix of the other
30+
// In either case, we consider them equal
31+
return 0;
32+
}
33+
34+
function compareStringComponents(str1: string, str2: string) {
35+
if (str1 === str2) {
36+
return 0;
37+
} else if (str1 === undefined) {
38+
return -1;
39+
} else if (str2 === undefined) {
40+
return 1;
41+
} else {
42+
return str1.localeCompare(str2);
43+
}
44+
}

packages/project/test/gradle-file.android.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,45 @@ describe('project - android - gradle', () => {
6161
expect(nodes.length).not.toBe(0);
6262
});
6363

64+
it('Should get source code for found node', async () => {
65+
const gradle = new GradleFile(
66+
join(project.config.android!.path!, 'build.gradle'),
67+
vfs,
68+
);
69+
await gradle.parse();
70+
71+
let nodes = gradle.find({
72+
buildscript: {
73+
dependencies: {
74+
classpath: {}
75+
},
76+
},
77+
});
78+
79+
let source = gradle.getSource(nodes[0].node);
80+
81+
expect(source).toBe("classpath 'com.android.tools.build:gradle:4.2.1'");
82+
83+
nodes = gradle.find({
84+
buildscript: {
85+
dependencies: {
86+
},
87+
},
88+
});
89+
90+
source = gradle.getSource(nodes[0].node);
91+
92+
expect(source).toBe(`
93+
dependencies {
94+
classpath 'com.android.tools.build:gradle:4.2.1'
95+
classpath 'com.google.gms:google-services:4.3.5'
96+
97+
// NOTE: Do not place your application dependencies here; they belong
98+
// in the individual module build.gradle files
99+
}
100+
`.trim());
101+
});
102+
64103
it('Should find exact element in parsed Gradle', async () => {
65104
const gradle = new GradleFile(join(project.config.android!.path!, 'build.gradle'), vfs);
66105
await gradle.parse();
@@ -548,4 +587,19 @@ intunemam {
548587
cordovaAndroidVersion = '7.0.0'
549588
}`);
550589
});
590+
591+
it('Should get and set namespace', async () => {
592+
const gradle = new GradleFile(
593+
join(project.config.android!.path!, 'app', 'build.gradle'),
594+
vfs,
595+
);
596+
597+
let namespace = await gradle.getNamespace();
598+
expect(namespace).toBe('io.ionic.starter');
599+
600+
await gradle.setNamespace('io.ionic.test');
601+
602+
namespace = await gradle.getNamespace();
603+
expect(namespace).toBe('io.ionic.test');
604+
});
551605
});

packages/project/test/project.android.test.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,19 @@ describe('project - android', () => {
4343
expect(project.android?.getMainActivityFilename()).toBe('MainActivity.java');
4444
});
4545

46+
it('should get gradle plugin version', async () => {
47+
expect(await project.android?.getGradlePluginVersion()).toBe('4.2.1');
48+
});
49+
50+
it('should get package name', async () => {
51+
expect(await project.android?.getPackageName()).toBe('io.ionic.starter');
52+
});
53+
4654
it('should set package name', async () => {
4755
await project.android?.setPackageName('com.ionicframework.awesome');
48-
expect(project.android?.getPackageName()).toBe('com.ionicframework.awesome');
56+
expect(await project.android?.getPackageName()).toBe('com.ionicframework.awesome');
4957
expect(await project.android?.getAppBuildGradle()?.getApplicationId()).toBe('com.ionicframework.awesome');
58+
expect(await project.android?.getAppBuildGradle()?.getNamespace()).toBe('com.ionicframework.awesome');
5059
const newSource = await readFile(join(project.config.android?.path!, 'app/src/main/java/com/ionicframework/awesome/MainActivity.java'), { encoding: 'utf-8' });
5160
expect(newSource.indexOf('package com.ionicframework.awesome;')).toBe(0);
5261
expect(!(await pathExists(join(project.config.android?.path!, 'app/src/main/java/io')))).toBe(true);
@@ -55,14 +64,14 @@ describe('project - android', () => {
5564
});
5665

5766
it('should not error setting same package name', async () => {
58-
const packageName = project.android?.getPackageName();
67+
const packageName = await project.android?.getPackageName();
5968
await project.android?.setPackageName(packageName!);
60-
expect(project.android?.getPackageName()).toBe(packageName);
69+
expect(await project.android?.getPackageName()).toBe(packageName);
6170
});
6271

6372
it('should set package name longer than current package', async () => {
6473
await project.android?.setPackageName('com.ionicframework.awesome.long');
65-
expect(project.android?.getPackageName()).toBe('com.ionicframework.awesome.long');
74+
expect(await project.android?.getPackageName()).toBe('com.ionicframework.awesome.long');
6675
expect(await project.android?.getAppBuildGradle()?.getApplicationId()).toBe('com.ionicframework.awesome.long');
6776
const newSource = await readFile(join(project.config.android?.path!, 'app/src/main/java/com/ionicframework/awesome/long/MainActivity.java'), { encoding: 'utf-8' });
6877
expect(newSource.indexOf('package com.ionicframework.awesome.long;')).toBe(0);
@@ -73,7 +82,7 @@ describe('project - android', () => {
7382

7483
it('should set package name shorter than current package', async () => {
7584
await project.android?.setPackageName('com.super');
76-
expect(project.android?.getPackageName()).toBe('com.super');
85+
expect(await project.android?.getPackageName()).toBe('com.super');
7786
expect(await project.android?.getAppBuildGradle()?.getApplicationId()).toBe('com.super');
7887
const newSource = await readFile(join(project.config.android?.path!, 'app/src/main/java/com/super/MainActivity.java'), { encoding: 'utf-8' });
7988
expect(newSource.indexOf('package com.super;')).toBe(0);
@@ -226,6 +235,7 @@ describe('project - android', () => {
226235
expect(appBuildGradleSource!.getData()?.getDocument()).toBe(`apply plugin: 'com.android.application'
227236
228237
android {
238+
namespace "io.ionic.starter"
229239
compileSdkVersion rootProject.ext.compileSdkVersion
230240
defaultConfig {
231241
applicationId "io.ionic.starter"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { compare } from '../../src/util/gradle-versions';
2+
3+
describe('Gradle versions', () => {
4+
it('should compare two gradle versions', () => {
5+
expect(compare('1.5.2', '1.6.2')).toBe(-1);
6+
expect(compare('1.8.2', '1.6.2')).toBe(1);
7+
expect(compare('[1.0,)', '[1.2,)')).toBe(-1);
8+
expect(compare('1.8.2-beta1-SNAPSHOT', '1.8.2')).toBe(1);
9+
expect(compare('1.8.2-beta1', '1.8.2-beta1')).toBe(0);
10+
});
11+
});

0 commit comments

Comments
 (0)