Skip to content

fix: prepare of pods should work on case-sensitive systems #5166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/controllers/preview-app-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon
@performanceLog()
private async handlePrepareReadyEvent(data: IPreviewAppLiveSyncData, currentPrepareData: IFilesChangeEventData) {
const { hmrData, files, platform } = currentPrepareData;
const platformHmrData = _.cloneDeep(hmrData);
const platformHmrData = _.cloneDeep(hmrData) || <IPlatformHmrData>{};
const connectedDevices = this.$previewDevicesService.getDevicesForPlatform(platform);
if (!connectedDevices || !connectedDevices.length) {
this.$logger.warn(`Unable to find any connected devices for platform '${platform}'. In order to execute live sync, open your Preview app and optionally re-scan the QR code using the Playground app.`);
Expand Down
2 changes: 1 addition & 1 deletion lib/definitions/plugins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ interface IBasePluginData {
}

interface IPluginData extends INodeModuleData {
platformsDataService: IPluginPlatformsData;
platformsData: IPluginPlatformsData;
/* Gets all plugin variables from plugin */
pluginVariables: IDictionary<IPluginVariableData>;
pluginPlatformsFolderPath(platform: string): string;
Expand Down
13 changes: 6 additions & 7 deletions lib/services/plugins-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,8 @@ export class PluginsService implements IPluginsService {
}
}

public async preparePluginNativeCode({pluginData, platform, projectData}: IPreparePluginNativeCodeData): Promise<void> {
public async preparePluginNativeCode({ pluginData, platform, projectData }: IPreparePluginNativeCodeData): Promise<void> {
const platformData = this.$platformsDataService.getPlatformData(platform, projectData);
pluginData.pluginPlatformsFolderPath = (_platform: string) => path.join(pluginData.fullPath, "platforms", _platform.toLowerCase());

const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(platform);
if (this.$fs.exists(pluginPlatformsFolderPath)) {
Expand Down Expand Up @@ -213,18 +212,18 @@ export class PluginsService implements IPluginsService {
pluginData.version = cacheData.version;
pluginData.fullPath = cacheData.directory || path.dirname(this.getPackageJsonFilePathForModule(cacheData.name, projectDir));
pluginData.isPlugin = !!cacheData.nativescript || !!cacheData.moduleInfo;
pluginData.pluginPlatformsFolderPath = (platform: string) => path.join(pluginData.fullPath, "platforms", platform);
pluginData.pluginPlatformsFolderPath = (platform: string) => path.join(pluginData.fullPath, "platforms", platform.toLowerCase());
const data = cacheData.nativescript || cacheData.moduleInfo;

if (pluginData.isPlugin) {
pluginData.platformsDataService = data.platforms;
pluginData.platformsData = data.platforms;
pluginData.pluginVariables = data.variables;
}

return pluginData;
}

private removeDependencyFromPackageJsonContent(dependency: string, packageJsonContent: Object): {hasModifiedPackageJson: boolean, packageJsonContent: Object} {
private removeDependencyFromPackageJsonContent(dependency: string, packageJsonContent: Object): { hasModifiedPackageJson: boolean, packageJsonContent: Object } {
let hasModifiedPackageJson = false;

if (packageJsonContent.devDependencies && packageJsonContent.devDependencies[dependency]) {
Expand Down Expand Up @@ -260,7 +259,7 @@ export class PluginsService implements IPluginsService {

private getPackageJsonFilePathForModule(moduleName: string, projectDir: string): string {
const pathToJsonFile = require.resolve(`${moduleName}/package.json`, {
paths: [projectDir]
paths: [projectDir]
});
return pathToJsonFile;
}
Expand Down Expand Up @@ -333,7 +332,7 @@ export class PluginsService implements IPluginsService {
let isValid = true;

const installedFrameworkVersion = this.getInstalledFrameworkVersion(platform, projectData);
const pluginPlatformsData = pluginData.platformsDataService;
const pluginPlatformsData = pluginData.platformsData;
if (pluginPlatformsData) {
const versionRequiredByPlugin = (<any>pluginPlatformsData)[platform];
if (!versionRequiredByPlugin) {
Expand Down
3 changes: 2 additions & 1 deletion test/ios-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -879,8 +879,9 @@ describe("handleNativeDependenciesChange", () => {
it("ensure the correct order of pod install and merging pod's xcconfig file", async () => {
const executedCocoapodsMethods: string[] = [];
const projectPodfilePath = "my/test/project/platforms/ios/Podfile";
const dir = temp.mkdirSync("myTestProjectPath");

const testInjector = createTestInjector("myTestProjectPath", "myTestProjectName");
const testInjector = createTestInjector(dir, "myTestProjectName");
const iOSProjectService = testInjector.resolve("iOSProjectService");
const projectData = testInjector.resolve("projectData");

Expand Down
167 changes: 68 additions & 99 deletions test/plugins-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,31 +207,6 @@ async function addPluginWhenExpectingToFail(testInjector: IInjector, plugin: str
assert.isTrue(isErrorThrown);
}

function createAndroidManifestFile(projectFolder: string, fs: IFileSystem): void {
const manifest = `
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.basiccontactables" android:versionCode="1" android:versionName="1.0" >
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/Theme.Sample" >
<activity android:name="com.example.android.basiccontactables.MainActivity" android:label="@string/app_name" android:launchMode="singleTop">
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
</manifest>`;

fs.createDirectory(path.join(projectFolder, "platforms"));
fs.createDirectory(path.join(projectFolder, "platforms", "android"));
fs.writeFile(path.join(projectFolder, "platforms", "android", "AndroidManifest.xml"), manifest);
}

describe("Plugins service", () => {
let testInjector: IInjector;
const commands = ["add", "install"];
Expand Down Expand Up @@ -505,76 +480,6 @@ describe("Plugins service", () => {
});
});

describe("merge xmls tests", () => {
beforeEach(() => {
testInjector = createTestInjector();
testInjector.registerCommand("plugin|add", AddPluginCommand);
});
it("fails if the plugin contains incorrect xml", async () => {
const pluginName = "mySamplePlugin";
const projectFolder = createProjectFile(testInjector);
const pluginFolderPath = path.join(projectFolder, pluginName);
const pluginJsonData: IDependencyData = {
name: pluginName,
nativescript: {
platforms: {
android: "0.10.0"
}
},
depth: 0,
directory: "some dir"
};
const fs = testInjector.resolve("fs");
fs.writeJson(path.join(pluginFolderPath, "package.json"), pluginJsonData);

// Adds AndroidManifest.xml file in platforms/android folder
createAndroidManifestFile(projectFolder, fs);

// Mock plugins service
const pluginsService: IPluginsService = testInjector.resolve("pluginsService");
pluginsService.getAllInstalledPlugins = async (pData: IProjectData) => {
return <any[]>[{ name: "" }];
};

const appDestinationDirectoryPath = path.join(projectFolder, "platforms", "android");

// Mock platformsDataService
const platformsDataService = testInjector.resolve("platformsDataService");
platformsDataService.getPlatformData = (platform: string) => {
return {
appDestinationDirectoryPath: appDestinationDirectoryPath,
frameworkPackageName: "tns-android",
configurationFileName: "AndroidManifest.xml",
normalizedPlatformName: "Android",
platformProjectService: {
preparePluginNativeCode: (pluginData: IPluginData) => Promise.resolve()
}
};
};

// Ensure the pluginDestinationPath folder exists
const pluginPlatformsDirPath = path.join(projectFolder, "node_modules", pluginName, "platforms", "android");
const projectData: IProjectData = testInjector.resolve("projectData");
projectData.initializeProjectData();
fs.ensureDirectoryExists(pluginPlatformsDirPath);

// Creates invalid plugin's AndroidManifest.xml file
const xml = '<?xml version="1.0" encoding="UTF-8"?>' +
'<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.basiccontactables" android:versionCode="1" android:versionName="1.0" >' +
'<uses-permission android:name="android.permission.READ_CONTACTS"/>';
const pluginConfigurationFilePath = path.join(pluginPlatformsDirPath, "AndroidManifest.xml");
fs.writeFile(pluginConfigurationFilePath, xml);

// Expected error message. The assertion happens in mockBeginCommand
const expectedErrorMessage = `Exception: Invalid xml file ${pluginConfigurationFilePath}. Additional technical information: element parse error: Exception: Invalid xml file ` +
`${pluginConfigurationFilePath}. Additional technical information: unclosed xml attribute` +
`\n@#[line:1,col:39].` +
`\n@#[line:1,col:39].`;
mockBeginCommand(testInjector, expectedErrorMessage);
await pluginsService.preparePluginNativeCode({pluginData: pluginsService.convertToPluginData(pluginJsonData, projectData.projectDir), platform: "android", projectData});
});
});

describe("preparePluginNativeCode", () => {
const setupTest = (opts: { hasChangesInShasums?: boolean, newPluginHashes?: IStringDictionary, buildDataFileExists?: boolean, hasPluginPlatformsDir?: boolean }): any => {
const testData: any = {
Expand All @@ -599,7 +504,8 @@ describe("Plugins service", () => {
const pluginHashes = opts.newPluginHashes || { "file1": "hash1" };
const samplePluginData: IPluginData = <any>{
fullPath: "plugin_full_path",
name: "plugin_name"
name: "plugin_name",
pluginPlatformsFolderPath: (_platform: string) => path.join("plugin_dir", "platforms", _platform.toLowerCase())
};

unitTestsInjector.register("filesHashService", {
Expand Down Expand Up @@ -646,22 +552,85 @@ describe("Plugins service", () => {

it("does not prepare the files when plugin does not have platforms dir", async () => {
const testData = setupTest({ hasPluginPlatformsDir: false });
await testData.pluginsService.preparePluginNativeCode({pluginData: testData.pluginData, platform, projectData});
await testData.pluginsService.preparePluginNativeCode({ pluginData: testData.pluginData, platform, projectData });
assert.isFalse(testData.isPreparePluginNativeCodeCalled);
});

it("prepares the files when plugin has platforms dir and has not been built before", async () => {
const newPluginHashes = { "file": "hash" };
const testData = setupTest({ newPluginHashes, hasPluginPlatformsDir: true });
await testData.pluginsService.preparePluginNativeCode({pluginData: testData.pluginData, platform, projectData});
await testData.pluginsService.preparePluginNativeCode({ pluginData: testData.pluginData, platform, projectData });
assert.isTrue(testData.isPreparePluginNativeCodeCalled);
assert.deepEqual(testData.dataPassedToWriteJson, { [testData.pluginData.name]: newPluginHashes });
});

it("does not prepare the files when plugin has platforms dir and files have not changed since then", async () => {
const testData = setupTest({ hasChangesInShasums: false, buildDataFileExists: true, hasPluginPlatformsDir: true });
await testData.pluginsService.preparePluginNativeCode({pluginData: testData.pluginData, platform, projectData});
await testData.pluginsService.preparePluginNativeCode({ pluginData: testData.pluginData, platform, projectData });
assert.isFalse(testData.isPreparePluginNativeCodeCalled);
});
});

describe("convertToPluginData", () => {
const createUnitTestsInjector = () => {
const unitTestsInjector = new Yok();
unitTestsInjector.register("platformsDataService", {});
unitTestsInjector.register("filesHashService", {});
unitTestsInjector.register("fs", {});
unitTestsInjector.register("packageManager", {});
unitTestsInjector.register("options", {});
unitTestsInjector.register("logger", {});
unitTestsInjector.register("errors", {});
unitTestsInjector.register("injector", unitTestsInjector);
unitTestsInjector.register("mobileHelper", MobileHelper);
unitTestsInjector.register("devicePlatformsConstants", DevicePlatformsConstants);
unitTestsInjector.register("nodeModulesDependenciesBuilder", {});
return unitTestsInjector;
};

const pluginDir = "pluginDir";
const dataFromPluginPackageJson = {
name: "name",
version: "1.0.0",
directory: pluginDir,
nativescript: {
platforms: {
ios: "6.0.0",
android: "6.0.0"
}
}
};

it("returns correct pluginData", () => {
const unitTestsInjector = createUnitTestsInjector();
const pluginsService: PluginsService = unitTestsInjector.resolve(PluginsService);
const pluginData = pluginsService.convertToPluginData(dataFromPluginPackageJson, "my project dir");
// Remove the comparison of a function
delete pluginData["pluginPlatformsFolderPath"];
assert.deepEqual(pluginData, <any>{
name: "name",
version: "1.0.0",
fullPath: pluginDir,
isPlugin: true,
platformsData: { android: "6.0.0", ios: "6.0.0" },
pluginVariables: undefined
});
});

it("always returns lowercased platform in the path to plugins dir", () => {
const unitTestsInjector = createUnitTestsInjector();
const pluginsService: PluginsService = unitTestsInjector.resolve(PluginsService);
const pluginData = pluginsService.convertToPluginData(dataFromPluginPackageJson, "my project dir");

const expectediOSPath = path.join(pluginDir, "platforms", "ios");
const expectedAndroidPath = path.join(pluginDir, "platforms", "android");
assert.equal(pluginData.pluginPlatformsFolderPath("iOS"), expectediOSPath);
assert.equal(pluginData.pluginPlatformsFolderPath("ios"), expectediOSPath);
assert.equal(pluginData.pluginPlatformsFolderPath("IOS"), expectediOSPath);

assert.equal(pluginData.pluginPlatformsFolderPath("Android"), expectedAndroidPath);
assert.equal(pluginData.pluginPlatformsFolderPath("android"), expectedAndroidPath);
assert.equal(pluginData.pluginPlatformsFolderPath("ANDROID"), expectedAndroidPath);
});
});
});
3 changes: 2 additions & 1 deletion test/services/playground/preview-app-livesync-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ function createTestInjector(options?: {
mapFilePath: (filePath: string) => path.join(path.join(platformsDirPath, "app"), path.relative(path.join(projectDirPath, "app"), filePath))
});
injector.register("previewDevicesService", {
getConnectedDevices: () => [deviceMockData]
getConnectedDevices: () => [deviceMockData],
getDevicesForPlatform: (platform: string): Device[] => [deviceMockData]
});
injector.register("previewAppFilesService", PreviewAppFilesService);
injector.register("previewQrCodeService", {
Expand Down