Skip to content

Commit e9d8979

Browse files
committed
livesync changes
1 parent faac853 commit e9d8979

File tree

8 files changed

+182
-32
lines changed

8 files changed

+182
-32
lines changed

lib/common

lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const APK_DIR = "apk";
3030
export const RESOURCES_DIR = "res";
3131
export const CONFIG_NS_FILE_NAME = "nsconfig.json";
3232
export const CONFIG_NS_APP_RESOURCES_ENTRY = "appResources";
33+
export const DEPENDENCIES_JSON_NAME = "dependencies.json"
3334

3435
export class PackageVersion {
3536
static NEXT = "next";

lib/definitions/project.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,16 @@ interface IPlatformProjectService extends NodeJS.EventEmitter {
279279
* If there are parts in the project that are inconsistent with the desired options, marks them in the changeset flags.
280280
*/
281281
checkForChanges(changeset: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): Promise<void>;
282+
283+
/**
284+
* Build native part of a nativescript plugins if necessary
285+
*/
286+
prebuildNativePlugin(pluginName: string, platformsAndroidDirPath: string, aarOutputDir: string, tmpBuildDir: string): Promise<void>;
287+
288+
/**
289+
* Traverse through the production dependencies and find plugins that need build/rebuild
290+
*/
291+
checkIfPluginsNeedBuild(projectData: IProjectData): Promise<Array<any>>;
282292
}
283293

284294
interface IAndroidProjectPropertiesManager {

lib/services/android-project-service.ts

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import * as constants from "../constants";
44
import * as semver from "semver";
55
import * as projectServiceBaseLib from "./platform-project-service-base";
66
import { DeviceAndroidDebugBridge } from "../common/mobile/android/device-android-debug-bridge";
7-
import { attachAwaitDetach } from "../common/helpers";
87
import { EOL } from "os";
8+
import { attachAwaitDetach, isBuildFromCLI } from "../common/helpers";
99
import { Configurations } from "../common/constants";
1010
import { SpawnOptions } from "child_process";
11+
import { buildAar, migrateIncludeGradle, BuildAarOptions } from "plugin-migrator";
1112

1213
export class AndroidProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService {
1314
private static VALUES_DIRNAME = "values";
@@ -413,14 +414,90 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
413414
}
414415

415416
public async preparePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise<void> {
416-
if (!this.shouldUseNewRuntimeGradleRoutine(projectData)) {
417+
if (!this.runtimeVersionIsGreaterThanOrEquals(projectData, "3.3.0")) {
417418
const pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, AndroidProjectService.ANDROID_PLATFORM_NAME);
418419
await this.processResourcesFromPlugin(pluginData, pluginPlatformsFolderPath, projectData);
420+
} else if (this.runtimeVersionIsGreaterThanOrEquals(projectData, "4.0.0")) {
421+
// build Android plugins which contain AndroidManifest.xml and/or resources
422+
const pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, AndroidProjectService.ANDROID_PLATFORM_NAME);
423+
if (this.$fs.exists(pluginPlatformsFolderPath)) {
424+
this.prebuildNativePlugin(pluginData.name, pluginPlatformsFolderPath, pluginPlatformsFolderPath, path.join(projectData.platformsDir, "tempPlugin"));
425+
}
419426
}
420427

421428
// Do nothing, the Android Gradle script will configure itself based on the input dependencies.json
422429
}
423430

431+
public async checkIfPluginsNeedBuild(projectData: IProjectData): Promise<Array<any>> {
432+
const detectedPlugins: Array<any> = [];
433+
434+
const platformsAndroid = path.join(constants.PLATFORMS_DIR_NAME, "android");
435+
const pathToPlatformsAndroid = path.join(projectData.projectDir, platformsAndroid);
436+
const dependenciesJson = await this.$fs.readJson(path.join(pathToPlatformsAndroid, constants.DEPENDENCIES_JSON_NAME));
437+
const productionDependencies = dependenciesJson.map((item: any) => {
438+
return path.resolve(pathToPlatformsAndroid, item.directory);
439+
});
440+
441+
for (const dependencyKey in productionDependencies) {
442+
const dependency = productionDependencies[dependencyKey];
443+
const jsonContent = this.$fs.readJson(path.join(dependency, constants.PACKAGE_JSON_FILE_NAME));
444+
const isPlugin = !!jsonContent.nativescript;
445+
const pluginName = jsonContent.name;
446+
if (isPlugin) {
447+
const platformsAndroidDirPath = path.join(dependency, platformsAndroid);
448+
if (this.$fs.exists(platformsAndroidDirPath)) {
449+
let hasGeneratedAar = false;
450+
let generatedAarPath = "";
451+
const nativeFiles = this.$fs.enumerateFilesInDirectorySync(platformsAndroidDirPath).filter((item) => {
452+
if (isBuildFromCLI(item)) {
453+
generatedAarPath = item;
454+
hasGeneratedAar = true;
455+
}
456+
return this.isAllowedFile(item);
457+
});
458+
459+
if (hasGeneratedAar) {
460+
const aarStat = this.$fs.getFsStats(generatedAarPath);
461+
nativeFiles.forEach((item) => {
462+
const currentItemStat = this.$fs.getFsStats(item);
463+
if (currentItemStat.mtime > aarStat.mtime) {
464+
detectedPlugins.push({
465+
platformsAndroidDirPath: platformsAndroidDirPath,
466+
pluginName: pluginName
467+
});
468+
}
469+
});
470+
} else if (nativeFiles.length > 0) {
471+
detectedPlugins.push({
472+
platformsAndroidDirPath: platformsAndroidDirPath,
473+
pluginName: pluginName
474+
});
475+
}
476+
}
477+
}
478+
}
479+
return detectedPlugins;
480+
}
481+
482+
private isAllowedFile(item: string) {
483+
return item.endsWith(constants.MANIFEST_FILE_NAME) || item.endsWith(constants.RESOURCES_DIR);
484+
}
485+
486+
public async prebuildNativePlugin(pluginName: string, platformsAndroidDirPath: string, aarOutputDir: string, tmpBuildDir: string): Promise<void> {
487+
const options: BuildAarOptions = {
488+
pluginName: pluginName,
489+
platformsAndroidDirPath: platformsAndroidDirPath,
490+
aarOutputDir: aarOutputDir,
491+
tempPluginDirPath: tmpBuildDir
492+
};
493+
494+
if (await buildAar(options)) {
495+
this.$logger.info(`Built aar for ${pluginName}`);
496+
}
497+
498+
migrateIncludeGradle(options);
499+
}
500+
424501
public async processConfigurationFilesFromAppResources(): Promise<void> {
425502
return;
426503
}
@@ -458,9 +535,9 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
458535
public async removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise<void> {
459536
try {
460537
// check whether the dependency that's being removed has native code
461-
// TODO: Remove prior to the 4.0 CLI release @Pip3r4o @PanayotCankov
538+
// TODO: Remove prior to the 4.1 CLI release @Pip3r4o @PanayotCankov
462539
// the updated gradle script will take care of cleaning the prepared android plugins
463-
if (!this.shouldUseNewRuntimeGradleRoutine(projectData)) {
540+
if (!this.runtimeVersionIsGreaterThanOrEquals(projectData, "3.3.0")) {
464541
const pluginConfigDir = path.join(this.getPlatformData(projectData).projectRoot, "configurations", pluginData.name);
465542
if (this.$fs.exists(pluginConfigDir)) {
466543
await this.cleanProject(this.getPlatformData(projectData).projectRoot, projectData);
@@ -480,14 +557,14 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
480557
}
481558

482559
public async beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise<void> {
483-
const shouldUseNewRoutine = this.shouldUseNewRuntimeGradleRoutine(projectData);
560+
const shouldUseNewRoutine = this.runtimeVersionIsGreaterThanOrEquals(projectData, "3.3.0");
484561

485562
if (dependencies) {
486563
dependencies = this.filterUniqueDependencies(dependencies);
487564
if (shouldUseNewRoutine) {
488565
this.provideDependenciesJson(projectData, dependencies);
489566
} else {
490-
// TODO: Remove prior to the 4.0 CLI release @Pip3r4o @PanayotCankov
567+
// TODO: Remove prior to the 4.1 CLI release @Pip3r4o @PanayotCankov
491568

492569
const platformDir = path.join(projectData.platformsDir, AndroidProjectService.ANDROID_PLATFORM_NAME);
493570
const buildDir = path.join(platformDir, "build-tools");
@@ -524,7 +601,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
524601

525602
private provideDependenciesJson(projectData: IProjectData, dependencies: IDependencyData[]): void {
526603
const platformDir = path.join(projectData.platformsDir, AndroidProjectService.ANDROID_PLATFORM_NAME);
527-
const dependenciesJsonPath = path.join(platformDir, "dependencies.json");
604+
const dependenciesJsonPath = path.join(platformDir, constants.DEPENDENCIES_JSON_NAME);
528605
const nativeDependencies = dependencies
529606
.filter(AndroidProjectService.isNativeAndroidDependency)
530607
.map(({ name, directory }) => ({ name, directory: path.relative(platformDir, directory) }));
@@ -642,15 +719,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
642719
}
643720
}
644721

645-
// TODO: Remove prior to the 4.0 CLI release @Pip3r4o @PanayotCankov
646-
private shouldUseNewRuntimeGradleRoutine(projectData: IProjectData): boolean {
647-
const platformVersion = this.getCurrentPlatformVersion(this.getPlatformData(projectData), projectData);
648-
const newRuntimeGradleRoutineVersion = "3.3.0";
649-
650-
const normalizedPlatformVersion = `${semver.major(platformVersion)}.${semver.minor(platformVersion)}.0`;
651-
return semver.gte(normalizedPlatformVersion, newRuntimeGradleRoutineVersion);
652-
}
653-
654722
private isAndroidStudioCompatibleTemplate(projectData: IProjectData): boolean {
655723
const currentPlatformData: IDictionary<any> = this.$projectDataService.getNSValue(projectData.projectDir, constants.TNS_ANDROID_RUNTIME_NAME);
656724
let platformVersion = currentPlatformData && currentPlatformData[constants.VERSION_STRING];
@@ -676,6 +744,17 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
676744

677745
return semver.gte(normalizedPlatformVersion, androidStudioCompatibleTemplate);
678746
}
747+
748+
private runtimeVersionIsGreaterThanOrEquals(projectData: IProjectData, versionString: string): boolean {
749+
const platformVersion = this.getCurrentPlatformVersion(this.getPlatformData(projectData), projectData);
750+
751+
if (platformVersion === constants.PackageVersion.NEXT) {
752+
return true;
753+
}
754+
755+
const normalizedPlatformVersion = `${semver.major(platformVersion)}.${semver.minor(platformVersion)}.0`;
756+
return semver.gte(normalizedPlatformVersion, versionString);
757+
}
679758
}
680759

681760
$injector.register("androidProjectService", AndroidProjectService);

lib/services/ios-project-service.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -281,14 +281,14 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
281281
<dict>
282282
<key>method</key>
283283
<string>${exportOptionsMethod}</string>`;
284-
if (options && options.provision) {
285-
plistTemplate += ` <key>provisioningProfiles</key>
284+
if (options && options.provision) {
285+
plistTemplate += ` <key>provisioningProfiles</key>
286286
<dict>
287287
<key>${projectData.projectId}</key>
288288
<string>${options.provision}</string>
289289
</dict>`;
290-
}
291-
plistTemplate += `
290+
}
291+
plistTemplate += `
292292
<key>uploadBitcode</key>
293293
<false/>
294294
</dict>
@@ -960,7 +960,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
960960
return Promise.resolve();
961961
}
962962

963-
public async checkForChanges(changesInfo: IProjectChangesInfo, {provision, teamId}: IProjectChangesOptions, projectData: IProjectData): Promise<void> {
963+
public async checkForChanges(changesInfo: IProjectChangesInfo, { provision, teamId }: IProjectChangesOptions, projectData: IProjectData): Promise<void> {
964964
const hasProvision = provision !== undefined;
965965
const hasTeamId = teamId !== undefined;
966966
if (hasProvision || hasTeamId) {
@@ -1007,6 +1007,14 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
10071007
}
10081008
}
10091009

1010+
public async prebuildNativePlugin(pluginName: string, platformsAndroidDirPath: string, aarOutputDir: string, tmpBuildDir: string): Promise<void> {
1011+
Promise.resolve();
1012+
}
1013+
1014+
public async checkIfPluginsNeedBuild(projectData: IProjectData): Promise<Array<any>> {
1015+
return [];
1016+
}
1017+
10101018
private getAllLibsForPluginWithFileExtension(pluginData: IPluginData, fileExtension: string): string[] {
10111019
const filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => path.extname(fileName) === fileExtension;
10121020
return this.getAllNativeLibrariesForPlugin(pluginData, IOSProjectService.IOS_PLATFORM_NAME, filterCallback);
@@ -1351,7 +1359,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
13511359

13521360
private validateApplicationIdentifier(projectData: IProjectData): void {
13531361
const infoPlistPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName);
1354-
const mergedPlistPath = this.getPlatformData(projectData).configurationFilePath;
1362+
const mergedPlistPath = this.getPlatformData(projectData).configurationFilePath;
13551363

13561364
if (!this.$fs.exists(infoPlistPath) || !this.$fs.exists(mergedPlistPath)) {
13571365
return;

lib/services/livesync/livesync-service.ts

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import * as path from "path";
22
import * as choki from "chokidar";
33
import { EOL } from "os";
44
import { EventEmitter } from "events";
5-
import { hook } from "../../common/helpers";
5+
import { hook, executeActionByChunks, isBuildFromCLI } from "../../common/helpers";
66
import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME, DEBUGGER_DETACHED_EVENT_NAME, TrackActionNames } from "../../constants";
7-
import { FileExtensions, DeviceTypes, DeviceDiscoveryEventNames } from "../../common/constants";
7+
import { DEFAULT_CHUNK_SIZE, FileExtensions, DeviceTypes, DeviceDiscoveryEventNames } from "../../common/constants";
88
import { cache } from "../../common/decorators";
9+
import { BuildAarOptions } from "plugin-migrator";
910

1011
const deviceDescriptorPrimaryKey = "identifier";
1112

@@ -37,7 +38,8 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
3738
private $debugDataService: IDebugDataService,
3839
private $analyticsService: IAnalyticsService,
3940
private $usbLiveSyncService: DeprecatedUsbLiveSyncService,
40-
private $injector: IInjector) {
41+
private $injector: IInjector,
42+
private $platformsData: IPlatformsData) {
4143
super();
4244
}
4345

@@ -452,6 +454,26 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
452454
const platform = device.deviceInfo.platform;
453455
const deviceBuildInfoDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier);
454456

457+
if (liveSyncData.watchAllFiles) {
458+
const platformData: IPlatformData = this.$platformsData.getPlatformData("android", projectData);
459+
const pluginsNeedingRebuild: Array<any> = await platformData.platformProjectService.checkIfPluginsNeedBuild(projectData);
460+
const action = async (buildAarOptions: any) => {
461+
this.$logger.warn(`Building ${buildAarOptions.pluginName}...`);
462+
await platformData.platformProjectService.prebuildNativePlugin(buildAarOptions.pluginName, buildAarOptions.platformsAndroidDirPath, buildAarOptions.platformsAndroidDirPath, buildAarOptions.tempPluginDirPath);
463+
};
464+
const pluginInfo: any = [];
465+
pluginsNeedingRebuild.forEach((item) => {
466+
const options: BuildAarOptions = {
467+
pluginName: item.pluginName,
468+
platformsAndroidDirPath: item.platformsAndroidDirPath,
469+
aarOutputDir: item.platformsAndroidDirPath,
470+
tempPluginDirPath: path.join(projectData.platformsDir, 'tempPlugin')
471+
};
472+
pluginInfo.push(options);
473+
});
474+
await executeActionByChunks<string>(pluginInfo, DEFAULT_CHUNK_SIZE, action);
475+
}
476+
455477
await this.ensureLatestAppPackageIsInstalledOnDevice({
456478
device,
457479
preparedPlatforms,
@@ -553,6 +575,24 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
553575
filesToRemove = [];
554576

555577
const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove);
578+
if (liveSyncData.watchAllFiles) {
579+
const platformData: IPlatformData = this.$platformsData.getPlatformData("android", projectData);
580+
581+
allModifiedFiles.forEach(async (item) => {
582+
const matchedItem = item.match(/(.*\/node_modules\/[\w-]+)\/platforms\/android\//);
583+
if (matchedItem) {
584+
const matchLength = matchedItem[0].length;
585+
const matchIndex = item.indexOf(matchedItem[0]);
586+
const pluginInputOutputPath = item.substr(0, matchIndex + matchLength);
587+
588+
const pluginPackageJason = require(path.resolve(matchedItem[1], PACKAGE_JSON_FILE_NAME));
589+
if (pluginPackageJason && pluginPackageJason.name) {
590+
this.$logger.warn(`Building ${pluginPackageJason.name}...`);
591+
await platformData.platformProjectService.prebuildNativePlugin(pluginPackageJason.name, pluginInputOutputPath, pluginInputOutputPath, path.join(projectData.platformsDir, "tempPlugin"));
592+
}
593+
}
594+
});
595+
}
556596
const preparedPlatforms: string[] = [];
557597
const rebuiltInformation: ILiveSyncBuildInfo[] = [];
558598

@@ -637,22 +677,26 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
637677

638678
const watcher = choki.watch(patterns, watcherOptions)
639679
.on("all", async (event: string, filePath: string) => {
680+
640681
clearTimeout(timeoutTimer);
641682

642683
filePath = path.join(liveSyncData.projectDir, filePath);
643684

644685
this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`);
645686

646-
if (event === "add" || event === "addDir" || event === "change" /* <--- what to do when change event is raised ? */) {
647-
filesToSync.push(filePath);
648-
} else if (event === "unlink" || event === "unlinkDir") {
649-
filesToRemove.push(filePath);
687+
if (!isBuildFromCLI(filePath)) {
688+
if (event === "add" || event === "addDir" || event === "change" /* <--- what to do when change event is raised ? */) {
689+
filesToSync.push(filePath);
690+
} else if (event === "unlink" || event === "unlinkDir") {
691+
filesToRemove.push(filePath);
692+
}
650693
}
651694

652695
// Do not sync typescript files directly - wait for javascript changes to occur in order to restart the app only once
653696
if (path.extname(filePath) !== FileExtensions.TYPESCRIPT_FILE) {
654697
startTimeout();
655698
}
699+
656700
});
657701

658702
this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo = { watcher, patterns };

test/services/livesync-service.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ class LiveSyncServiceInheritor extends LiveSyncService {
5050
$debugDataService: IDebugDataService,
5151
$analyticsService: IAnalyticsService,
5252
$usbLiveSyncService: DeprecatedUsbLiveSyncService,
53-
$injector: IInjector) {
53+
$injector: IInjector,
54+
$platformsData: IPlatformsData) {
5455

5556
super(
5657
$platformService,
@@ -69,7 +70,7 @@ class LiveSyncServiceInheritor extends LiveSyncService {
6970
$analyticsService,
7071
$usbLiveSyncService,
7172
$injector,
72-
73+
$platformsData
7374
);
7475
}
7576

test/stubs.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,13 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor
287287
fastLivesyncFileExtensions: []
288288
};
289289
}
290+
prebuildNativePlugin(pluginName: string, platformsAndroidDirPath: string, aarOutputDir: string, tmpBuildDir: string): Promise<void> {
291+
return Promise.resolve();
292+
}
293+
294+
checkIfPluginsNeedBuild(projectData: IProjectData): Promise<Array<any>> {
295+
return Promise.resolve([]);
296+
}
290297
getAppResourcesDestinationDirectoryPath(): string {
291298
return "";
292299
}

0 commit comments

Comments
 (0)