Skip to content
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

[Work in Progress]: Optional Cordova (currently iOS Only) #7479

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
316 changes: 14 additions & 302 deletions cli/src/ios/update.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import { copy, remove, pathExists, readFile, realpath, writeFile } from '@ionic/utils-fs';
import { pathExists, readFile, realpath, writeFile } from '@ionic/utils-fs';
import { basename, dirname, join, relative } from 'path';

import c from '../colors';
import { checkPlatformVersions, runTask } from '../common';
import { checkPluginDependencies, handleCordovaPluginsJS, logCordovaManualSteps, needsStaticPod } from '../cordova';

Check warning on line 6 in cli/src/ios/update.ts

View workflow job for this annotation

GitHub Actions / lint

'needsStaticPod' is defined but never used
import type { Config } from '../definitions';
import { fatal } from '../errors';
import { logger } from '../log';
import {
PluginType,
getAllElements,
getFilePath,
getPlatformElement,

Check warning on line 12 in cli/src/ios/update.ts

View workflow job for this annotation

GitHub Actions / lint

'getPlatformElement' is defined but never used
getPluginType,
getPlugins,
printPlugins,
} from '../plugin';
import type { Plugin } from '../plugin';
import { copy as copyTask } from '../tasks/copy';
import {
generateCordovaPodspecs,
copyPluginsNativeFiles,
removePluginsNativeFiles,
cordovaPodfileLines,
} from '../util/cordova-ios';
import { convertToUnixPath } from '../util/fs';
import { generateIOSPackageJSON } from '../util/iosplugin';
import { resolveNode } from '../util/node';
import { checkPackageManager, generatePackageFile } from '../util/spm';
import { runCommand, isInstalled } from '../util/subprocess';
import { extractTemplate } from '../util/template';

import { getIOSPlugins } from './common';

Expand All @@ -49,6 +52,9 @@
await removePluginsNativeFiles(config);
const cordovaPlugins = plugins.filter((p) => getPluginType(p, platform) === PluginType.Cordova);
if (cordovaPlugins.length > 0) {
// TODO: all the new logic should probably go here
// with an else that removes cordova plugins

await copyPluginsNativeFiles(config, cordovaPlugins);
}
if (!(await pathExists(await config.ios.webDirAbs))) {
Expand Down Expand Up @@ -138,297 +144,13 @@
)}'\n`;
}),
);
const cordovaPlugins = plugins.filter((p) => getPluginType(p, platform) === PluginType.Cordova);
cordovaPlugins.map(async (p) => {
const podspecs = getPlatformElement(p, platform, 'podspec');
podspecs.map((podspec: any) => {
podspec.pods.map((pPods: any) => {
pPods.pod.map((pod: any) => {
if (pod.$.git) {
let gitRef = '';
if (pod.$.tag) {
gitRef = `, :tag => '${pod.$.tag}'`;
} else if (pod.$.branch) {
gitRef = `, :branch => '${pod.$.branch}'`;
} else if (pod.$.commit) {
gitRef = `, :commit => '${pod.$.commit}'`;
}
pods.push(` pod '${pod.$.name}', :git => '${pod.$.git}'${gitRef}\n`);
}
});
});
});
});
const staticPlugins = cordovaPlugins.filter((p) => needsStaticPod(p));
const noStaticPlugins = cordovaPlugins.filter((el) => !staticPlugins.includes(el));
if (noStaticPlugins.length > 0) {
pods.push(` pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'\n`);
}
if (staticPlugins.length > 0) {
pods.push(` pod 'CordovaPluginsStatic', :path => '../capacitor-cordova-ios-plugins'\n`);
}
const resourcesPlugins = cordovaPlugins.filter(filterResources);
if (resourcesPlugins.length > 0) {
pods.push(` pod 'CordovaPluginsResources', :path => '../capacitor-cordova-ios-plugins'\n`);
}
const cordovaPodlines = cordovaPodfileLines(config, plugins);
pods.concat(cordovaPodlines);

return `
pod 'Capacitor', :path => '${relativeCapacitoriOSPath}'
pod 'CapacitorCordova', :path => '${relativeCapacitoriOSPath}'
${pods.join('').trimRight()}`;
}

function getFrameworkName(framework: any) {
if (isFramework(framework)) {
if (framework.$.custom && framework.$.custom === 'true') {
return framework.$.src;
}
return framework.$.src.substr(0, framework.$.src.indexOf('.'));
}
return framework.$.src.substr(0, framework.$.src.indexOf('.')).replace('lib', '');
}

function isFramework(framework: any) {
return framework.$.src.split('.').pop().includes('framework');
}

async function generateCordovaPodspecs(cordovaPlugins: Plugin[], config: Config) {
const staticPlugins = cordovaPlugins.filter((p) => needsStaticPod(p));
const noStaticPlugins = cordovaPlugins.filter((el) => !staticPlugins.includes(el));
generateCordovaPodspec(noStaticPlugins, config, false);
generateCordovaPodspec(staticPlugins, config, true);
}

async function generateCordovaPodspec(cordovaPlugins: Plugin[], config: Config, isStatic: boolean) {
const weakFrameworks: string[] = [];
const linkedFrameworks: string[] = [];
const customFrameworks: string[] = [];
const systemLibraries: string[] = [];
const sourceFrameworks: string[] = [];
const frameworkDeps: string[] = [];
const compilerFlags: string[] = [];
let prefsArray: any[] = [];
let name = 'CordovaPlugins';
let sourcesFolderName = 'sources';
if (isStatic) {
name += 'Static';
frameworkDeps.push('s.static_framework = true');
sourcesFolderName += 'static';
}
cordovaPlugins.map((plugin: any) => {
const frameworks = getPlatformElement(plugin, platform, 'framework');
frameworks.map((framework: any) => {
if (!framework.$.type) {
const name = getFrameworkName(framework);
if (isFramework(framework)) {
if (framework.$.weak && framework.$.weak === 'true') {
if (!weakFrameworks.includes(name)) {
weakFrameworks.push(name);
}
} else if (framework.$.custom && framework.$.custom === 'true') {
const frameworktPath = join(sourcesFolderName, plugin.name, name);
if (!customFrameworks.includes(frameworktPath)) {
customFrameworks.push(frameworktPath);
}
} else {
if (!linkedFrameworks.includes(name)) {
linkedFrameworks.push(name);
}
}
} else {
if (!systemLibraries.includes(name)) {
systemLibraries.push(name);
}
}
} else if (framework.$.type && framework.$.type === 'podspec') {
let depString = `s.dependency '${framework.$.src}'`;
if (framework.$.spec && framework.$.spec !== '') {
depString += `, '${framework.$.spec}'`;
}
if (!frameworkDeps.includes(depString)) {
frameworkDeps.push(depString);
}
}
});
prefsArray = prefsArray.concat(getAllElements(plugin, platform, 'preference'));
const podspecs = getPlatformElement(plugin, platform, 'podspec');
podspecs.map((podspec: any) => {
podspec.pods.map((pods: any) => {
pods.pod.map((pod: any) => {
let depString = `s.dependency '${pod.$.name}'`;
if (pod.$.spec && pod.$.spec !== '') {
depString += `, '${pod.$.spec}'`;
}
if (!frameworkDeps.includes(depString)) {
frameworkDeps.push(depString);
}
});
});
});
const sourceFiles = getPlatformElement(plugin, platform, 'source-file');
sourceFiles.map((sourceFile: any) => {
if (sourceFile.$.framework && sourceFile.$.framework === 'true') {
let fileName = sourceFile.$.src.split('/').pop();
if (!fileName.startsWith('lib')) {
fileName = 'lib' + fileName;
}
const frameworktPath = join(sourcesFolderName, plugin.name, fileName);
if (!sourceFrameworks.includes(frameworktPath)) {
sourceFrameworks.push(frameworktPath);
}
} else if (sourceFile.$['compiler-flags']) {
const cFlag = sourceFile.$['compiler-flags'];
if (!compilerFlags.includes(cFlag)) {
compilerFlags.push(cFlag);
}
}
});
});
const onlySystemLibraries = systemLibraries.filter((library) => removeNoSystem(library, sourceFrameworks));
if (weakFrameworks.length > 0) {
frameworkDeps.push(`s.weak_frameworks = '${weakFrameworks.join(`', '`)}'`);
}
if (linkedFrameworks.length > 0) {
frameworkDeps.push(`s.frameworks = '${linkedFrameworks.join(`', '`)}'`);
}
if (onlySystemLibraries.length > 0) {
frameworkDeps.push(`s.libraries = '${onlySystemLibraries.join(`', '`)}'`);
}
if (customFrameworks.length > 0) {
frameworkDeps.push(`s.vendored_frameworks = '${customFrameworks.join(`', '`)}'`);
frameworkDeps.push(
`s.exclude_files = 'sources/**/*.framework/Headers/*.h', 'sources/**/*.framework/PrivateHeaders/*.h'`,
);
}
if (sourceFrameworks.length > 0) {
frameworkDeps.push(`s.vendored_libraries = '${sourceFrameworks.join(`', '`)}'`);
}
if (compilerFlags.length > 0) {
frameworkDeps.push(`s.compiler_flags = '${compilerFlags.join(' ')}'`);
}
const arcPlugins = cordovaPlugins.filter(filterARCFiles);
if (arcPlugins.length > 0) {
frameworkDeps.push(`s.subspec 'noarc' do |sna|
sna.requires_arc = false
sna.source_files = 'noarc/**/*.{swift,h,m,c,cc,mm,cpp}'
end`);
}
let frameworksString = frameworkDeps.join('\n ');
frameworksString = await replaceFrameworkVariables(config, prefsArray, frameworksString);
const content = `
Pod::Spec.new do |s|
s.name = '${name}'
s.version = '${config.cli.package.version}'
s.summary = 'Autogenerated spec'
s.license = 'Unknown'
s.homepage = 'https://example.com'
s.authors = { 'Capacitor Generator' => '[email protected]' }
s.source = { :git => 'https://github.com/ionic-team/does-not-exist.git', :tag => '${config.cli.package.version}' }
s.source_files = '${sourcesFolderName}/**/*.{swift,h,m,c,cc,mm,cpp}'
s.ios.deployment_target = '${config.ios.minVersion}'
s.xcconfig = {'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) COCOAPODS=1 WK_WEB_VIEW_ONLY=1' }
s.dependency 'CapacitorCordova'${getLinkerFlags(config)}
s.swift_version = '5.1'
${frameworksString}
end`;
await writeFile(join(config.ios.cordovaPluginsDirAbs, `${name}.podspec`), content);
}

function getLinkerFlags(config: Config) {
if (config.app.extConfig.ios?.cordovaLinkerFlags) {
return `\n s.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '${config.app.extConfig.ios.cordovaLinkerFlags.join(
' ',
)}' }`;
}
return '';
}

async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plugin[]) {
for (const p of cordovaPlugins) {
const sourceFiles = getPlatformElement(p, platform, 'source-file');
const headerFiles = getPlatformElement(p, platform, 'header-file');
const codeFiles = sourceFiles.concat(headerFiles);
const frameworks = getPlatformElement(p, platform, 'framework');
let sourcesFolderName = 'sources';
if (needsStaticPod(p)) {
sourcesFolderName += 'static';
}
const sourcesFolder = join(config.ios.cordovaPluginsDirAbs, sourcesFolderName, p.name);
for (const codeFile of codeFiles) {
let fileName = codeFile.$.src.split('/').pop();
const fileExt = codeFile.$.src.split('.').pop();
if (fileExt === 'a' && !fileName.startsWith('lib')) {
fileName = 'lib' + fileName;
}
let destFolder = sourcesFolderName;
if (codeFile.$['compiler-flags'] && codeFile.$['compiler-flags'] === '-fno-objc-arc') {
destFolder = 'noarc';
}
const filePath = getFilePath(config, p, codeFile.$.src);
const fileDest = join(config.ios.cordovaPluginsDirAbs, destFolder, p.name, fileName);
await copy(filePath, fileDest);
if (!codeFile.$.framework) {
let fileContent = await readFile(fileDest, { encoding: 'utf-8' });
if (fileExt === 'swift') {
fileContent = 'import Cordova\n' + fileContent;
await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
} else {
if (fileContent.includes('@import Firebase;')) {
fileContent = fileContent.replace('@import Firebase;', '#import <Firebase/Firebase.h>');
await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
}
if (
fileContent.includes('[NSBundle bundleForClass:[self class]]') ||
fileContent.includes('[NSBundle bundleForClass:[CDVCapture class]]')
) {
fileContent = fileContent.replace('[NSBundle bundleForClass:[self class]]', '[NSBundle mainBundle]');
fileContent = fileContent.replace('[NSBundle bundleForClass:[CDVCapture class]]', '[NSBundle mainBundle]');
await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
}
if (fileContent.includes('[self.webView superview]') || fileContent.includes('self.webView.superview')) {
fileContent = fileContent.replace(/\[self.webView superview\]/g, 'self.viewController.view');
fileContent = fileContent.replace(/self.webView.superview/g, 'self.viewController.view');
await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
}
}
}
}
const resourceFiles = getPlatformElement(p, platform, 'resource-file');
for (const resourceFile of resourceFiles) {
const fileName = resourceFile.$.src.split('/').pop();
await copy(
getFilePath(config, p, resourceFile.$.src),
join(config.ios.cordovaPluginsDirAbs, 'resources', fileName),
);
}
for (const framework of frameworks) {
if (framework.$.custom && framework.$.custom === 'true') {
await copy(getFilePath(config, p, framework.$.src), join(sourcesFolder, framework.$.src));
}
}
}
}

async function removePluginsNativeFiles(config: Config) {
await remove(config.ios.cordovaPluginsDirAbs);
await extractTemplate(config.cli.assets.ios.cordovaPluginsTemplateArchiveAbs, config.ios.cordovaPluginsDirAbs);
}

function filterResources(plugin: Plugin) {
const resources = getPlatformElement(plugin, platform, 'resource-file');
return resources.length > 0;
}

function filterARCFiles(plugin: Plugin) {
const sources = getPlatformElement(plugin, platform, 'source-file');
const sourcesARC = sources.filter(
(sourceFile: any) => sourceFile.$['compiler-flags'] && sourceFile.$['compiler-flags'] === '-fno-objc-arc',
);
return sourcesARC.length > 0;
}

function removeNoSystem(library: string, sourceFrameworks: string[]) {
const libraries = sourceFrameworks.filter((framework) => framework.includes(library));
return libraries.length === 0;
${pods.join('').trimEnd()}`;
}

async function getPluginsTask(config: Config) {
Expand All @@ -438,13 +160,3 @@
return iosPlugins;
});
}

async function replaceFrameworkVariables(config: Config, prefsArray: any[], frameworkString: string) {
prefsArray.map((preference: any) => {
frameworkString = frameworkString.replace(
new RegExp(('$' + preference.$.name).replace('$', '\\$&'), 'g'),
preference.$.default,
);
});
return frameworkString;
}
Loading
Loading