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

feat(ios)!: Move Cordova to Plugin for optional cordova #7805

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
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
333 changes: 20 additions & 313 deletions cli/src/ios/update.ts

Large diffs are not rendered by default.

318 changes: 318 additions & 0 deletions cli/src/util/cordova-ios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
import { copy, readFile, writeFile, remove } from 'fs-extra';
import { join } from 'path';

import { needsStaticPod } from '../cordova';
import type { Config } from '../definitions';
import { PluginType, getPlatformElement, getPluginType, getAllElements, getFilePath } from '../plugin';
import type { Plugin } from '../plugin';
import { extractTemplate } from '../util/template';

const platform = 'ios';

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

export async function generateCordovaPodspec(
cordovaPlugins: Plugin[],
config: Config,
isStatic: boolean,
): Promise<void> {
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' => 'hi@example.com' }
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 '';
}

export async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plugin[]): Promise<void> {
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));
}
}
}
}

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

export function filterARCFiles(plugin: Plugin): boolean {
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[]): boolean {
const libraries = sourceFrameworks.filter((framework) => framework.includes(library));
return libraries.length === 0;
}

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

function getFrameworkName(framework: any): string {
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');
}

export function cordovaPodfileLines(config: Config, plugins: Plugin[]): string[] {
const pods: string[] = [];

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`);
}

return pods;
}

function filterResources(plugin: Plugin) {
const resources = getPlatformElement(plugin, platform, 'resource-file');
return resources.length > 0;
}
1 change: 0 additions & 1 deletion ios/Capacitor.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,5 @@ Pod::Spec.new do |s|
s.source_files = "#{prefix}Capacitor/Capacitor/**/*.{swift,h,m}"
s.module_map = "#{prefix}Capacitor/Capacitor/Capacitor.modulemap"
s.resources = ["#{prefix}Capacitor/Capacitor/assets/native-bridge.js", "#{prefix}Capacitor/Capacitor/PrivacyInfo.xcprivacy"]
s.dependency 'CapacitorCordova'
s.swift_version = '5.1'
end
8 changes: 0 additions & 8 deletions ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
/* Begin PBXBuildFile section */
0F83E885285A332E006C43CB /* AppUUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F83E884285A332D006C43CB /* AppUUID.swift */; };
0F8F33B327DA980A003F49D6 /* PluginConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F8F33B127DA980A003F49D6 /* PluginConfig.swift */; };
2F81F5C926FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F81F5C726FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h */; };
2F81F5CA26FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F81F5C826FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m */; };
373A69C1255C9360000A6F44 /* NotificationHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373A69C0255C9360000A6F44 /* NotificationHandlerProtocol.swift */; };
373A69F2255C95D0000A6F44 /* NotificationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373A69F1255C95D0000A6F44 /* NotificationRouter.swift */; };
501CBAA71FC0A723009B0D4D /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 501CBAA61FC0A723009B0D4D /* WebKit.framework */; };
Expand Down Expand Up @@ -158,8 +156,6 @@
/* Begin PBXFileReference section */
0F83E884285A332D006C43CB /* AppUUID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUUID.swift; sourceTree = "<group>"; };
0F8F33B127DA980A003F49D6 /* PluginConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PluginConfig.swift; sourceTree = "<group>"; };
2F81F5C726FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CAPBridgeViewController+CDVScreenOrientationDelegate.h"; sourceTree = "<group>"; };
2F81F5C826FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CAPBridgeViewController+CDVScreenOrientationDelegate.m"; sourceTree = "<group>"; };
373A69C0255C9360000A6F44 /* NotificationHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandlerProtocol.swift; sourceTree = "<group>"; };
373A69F1255C95D0000A6F44 /* NotificationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRouter.swift; sourceTree = "<group>"; };
501CBAA61FC0A723009B0D4D /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -403,8 +399,6 @@
62FABD1925AE5C01007B3814 /* Array+Capacitor.swift */,
62D43AEF2581817500673C24 /* WKWebView+Capacitor.swift */,
62D43B642582A13D00673C24 /* WKWebView+Capacitor.m */,
2F81F5C726FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h */,
2F81F5C826FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m */,
62959AE92524DA7700A3D7F1 /* UIColor.swift */,
62959AFE2524DA7700A3D7F1 /* UIStatusBarManager+CAPHandleTapAction.m */,
62959B122524DA7700A3D7F1 /* Info.plist */,
Expand Down Expand Up @@ -515,7 +509,6 @@
623D6909254C6FDF002D01D1 /* CAPInstanceDescriptor.h in Headers */,
62959B192524DA7800A3D7F1 /* CAPBridgedPlugin.h in Headers */,
621ECCB82542045900D3D615 /* CAPBridgedJSTypes.h in Headers */,
2F81F5C926FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -728,7 +721,6 @@
62959B1D2524DA7800A3D7F1 /* UIColor.swift in Sources */,
62959B332524DA7800A3D7F1 /* CAPPlugin.m in Sources */,
62959B1C2524DA7800A3D7F1 /* CAPPluginMethod.m in Sources */,
2F81F5CA26FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m in Sources */,
62ADC0CA25CB678000E914DE /* PluginCallResult.swift in Sources */,
62959B472524DA7800A3D7F1 /* CAPNotifications.swift in Sources */,
62D43B652582A13D00673C24 /* WKWebView+Capacitor.m in Sources */,
Expand Down
1 change: 0 additions & 1 deletion ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public class ApplicationDelegateProxy: NSObject, UIApplicationDelegate {
"url": url,
"options": options
])
NotificationCenter.default.post(name: NSNotification.Name.CDVPluginHandleOpenURL, object: url)
lastURL = url
return true
}
Expand Down
3 changes: 3 additions & 0 deletions ios/Capacitor/Capacitor/CAPBridgeProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ import WebKit
func registerPluginType(_ pluginType: CAPPlugin.Type)
func registerPluginInstance(_ pluginInstance: CAPPlugin)

// MARK: - Interceptors
func registerCallInterceptor(_ name: String, handler: @escaping ([String: Any]) -> Void)

// MARK: - View Presentation
func showAlertWith(title: String, message: String, buttonTitle: String)
func presentVC(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?)
Expand Down

This file was deleted.

This file was deleted.

Loading
Loading