Skip to content

Commit

Permalink
feat(spm): Update Package.swift on plugin add/remove
Browse files Browse the repository at this point in the history
Co-Authored-By: jcesarmobile <jcesarmobile@gmail.com>
  • Loading branch information
dpogue and jcesarmobile committed Dec 17, 2024
1 parent 880f1e2 commit 5589c15
Show file tree
Hide file tree
Showing 13 changed files with 494 additions and 31 deletions.
24 changes: 20 additions & 4 deletions lib/Api.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ class Api {
*/
addPlugin (plugin, installOptions) {
const xcodeproj = projectFile.parse(this.locations);
const { SwiftPackage, isSwiftPackagePlugin } = require('./SwiftPackage');

installOptions = installOptions || {};
installOptions.variables = installOptions.variables || {};
Expand All @@ -235,7 +236,14 @@ class Api {
return PluginManager.get(this.platform, this.locations, xcodeproj)
.addPlugin(plugin, installOptions)
.then(() => {
if (plugin != null) {
if (plugin != null && isSwiftPackagePlugin(plugin)) {
const packagePath = path.join(this.locations.root, 'CordovaPlugins', 'Package.swift');
const spm = new SwiftPackage(packagePath);
spm.addPlugin(plugin);
}
})
.then(() => {
if (plugin != null && !isSwiftPackagePlugin(plugin)) {
const headerTags = plugin.getHeaderFiles(this.platform);
const bridgingHeaders = headerTags.filter(obj => obj.type === 'BridgingHeader');
if (bridgingHeaders.length > 0) {
Expand All @@ -253,7 +261,7 @@ class Api {
}
})
.then(() => {
if (plugin != null) {
if (plugin != null && !isSwiftPackagePlugin(plugin)) {
const podSpecs = plugin.getPodSpecs ? plugin.getPodSpecs(this.platform) : [];
return this.addPodSpecs(plugin, podSpecs, installOptions);
}
Expand All @@ -277,11 +285,19 @@ class Api {
*/
removePlugin (plugin, uninstallOptions) {
const xcodeproj = projectFile.parse(this.locations);
const { SwiftPackage, isSwiftPackagePlugin } = require('./SwiftPackage');

return PluginManager.get(this.platform, this.locations, xcodeproj)
.removePlugin(plugin, uninstallOptions)
.then(() => {
if (plugin != null) {
if (plugin != null && isSwiftPackagePlugin(plugin)) {
const packagePath = path.join(this.locations.root, 'CordovaPlugins', 'Package.swift');
const spm = new SwiftPackage(packagePath);
spm.removePlugin(plugin);
}
})
.then(() => {
if (plugin != null && !isSwiftPackagePlugin(plugin)) {
const headerTags = plugin.getHeaderFiles(this.platform);
const bridgingHeaders = headerTags.filter(obj => obj.type === 'BridgingHeader');
if (bridgingHeaders.length > 0) {
Expand All @@ -299,7 +315,7 @@ class Api {
}
})
.then(() => {
if (plugin != null) {
if (plugin != null && !isSwiftPackagePlugin(plugin)) {
const podSpecs = plugin.getPodSpecs ? plugin.getPodSpecs(this.platform) : [];
return this.removePodSpecs(plugin, podSpecs, uninstallOptions);
}
Expand Down
73 changes: 73 additions & 0 deletions lib/SwiftPackage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/

const fs = require('node:fs');
const CordovaError = require('cordova-common').CordovaError;

class SwiftPackage {
constructor (packagePath) {
this.path = packagePath;

if (!fs.existsSync(this.path)) {
throw new CordovaError('Package.swift is not found.');
}
}

_pluginReference (plugin) {
return `
package.dependencies.append(.package(name: "${plugin.id}", path: "../../../plugins/${plugin.id}"))
package.targets.first?.dependencies.append(.product(name: "${plugin.id}", package: "${plugin.id}"))
`;
}

addPlugin (plugin) {
const fd = fs.openSync(this.path, 'a');
fs.writeFileSync(fd, this._pluginReference(plugin), 'utf8');
fs.closeSync(fd);
}

removePlugin (plugin) {
const fd = fs.openSync(this.path, 'r+');

let packageContent = fs.readFileSync(fd, 'utf8');
packageContent = packageContent.replace(this._pluginReference(plugin), '');

fs.ftruncateSync(fd);
fs.writeFileSync(fd, packageContent, 'utf8');
fs.closeSync(fd);
}
}

// Use this as a hidden property on the plugin so that we can cache the result
// of the Swift Package check for the lifetime of that PluginInfo object,
// without caching it based on plugin name.
const _isSwiftPackage = Symbol('isSwiftPackage');

function isSwiftPackagePlugin (plugin) {
if (!plugin[_isSwiftPackage]) {
plugin[_isSwiftPackage] = plugin.getPlatforms().some((platform) => {
return platform.name === 'ios' && !!platform.package;
});
}

return plugin[_isSwiftPackage];
}

module.exports.SwiftPackage = SwiftPackage;
module.exports.isSwiftPackagePlugin = isSwiftPackagePlugin;
21 changes: 21 additions & 0 deletions lib/plugman/pluginHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const path = require('node:path');
const util = require('node:util');
const events = require('cordova-common').events;
const CordovaError = require('cordova-common').CordovaError;
const { isSwiftPackagePlugin } = require('../SwiftPackage');

// These frameworks are required by cordova-ios by default. We should never add/remove them.
const keep_these_frameworks = [
Expand All @@ -31,22 +32,32 @@ const keep_these_frameworks = [
const handlers = {
'source-file': {
install: function (obj, plugin, project, options) {
if (isSwiftPackagePlugin(plugin)) return;

installHelper('source-file', obj, plugin.dir, project.projectDir, plugin.id, options, project);
},
uninstall: function (obj, plugin, project, options) {
if (isSwiftPackagePlugin(plugin)) return;

uninstallHelper('source-file', obj, project.projectDir, plugin.id, options, project);
}
},
'header-file': {
install: function (obj, plugin, project, options) {
if (isSwiftPackagePlugin(plugin)) return;

installHelper('header-file', obj, plugin.dir, project.projectDir, plugin.id, options, project);
},
uninstall: function (obj, plugin, project, options) {
if (isSwiftPackagePlugin(plugin)) return;

uninstallHelper('header-file', obj, project.projectDir, plugin.id, options, project);
}
},
'resource-file': {
install: function (obj, plugin, project, options) {
if (isSwiftPackagePlugin(plugin)) return;

const src = obj.src;
let target = obj.target;
const srcFile = path.resolve(plugin.dir, src);
Expand All @@ -67,6 +78,8 @@ const handlers = {
copyFile(plugin.dir, src, project.projectDir, destFile, link);
},
uninstall: function (obj, plugin, project, options) {
if (isSwiftPackagePlugin(plugin)) return;

const src = obj.src;
let target = obj.target;

Expand All @@ -81,6 +94,8 @@ const handlers = {
},
framework: { // CB-5238 custom frameworks only
install: function (obj, plugin, project, options) {
if (isSwiftPackagePlugin(plugin)) return;

const src = obj.src;
const custom = !!(obj.custom); // convert to boolean (if truthy/falsy)
const embed = !!(obj.embed); // convert to boolean (if truthy/falsy)
Expand Down Expand Up @@ -129,6 +144,8 @@ const handlers = {
events.emit('verbose', util.format('Custom framework added to project. %s -> %s', src, JSON.stringify(opt)));
},
uninstall: function (obj, plugin, project, options) {
if (isSwiftPackagePlugin(plugin)) return;

const src = obj.src;

if (!obj.custom) { // CB-9825 cocoapod integration for plugins
Expand Down Expand Up @@ -167,6 +184,8 @@ const handlers = {
},
asset: {
install: function (obj, plugin, project, options) {
if (isSwiftPackagePlugin(plugin)) return;

if (!obj.src) {
throw new CordovaError(generateAttributeError('src', 'asset', plugin.id));
}
Expand All @@ -178,6 +197,8 @@ const handlers = {
if (options && options.usePlatformWww) copyFile(plugin.dir, obj.src, project.platformWww, obj.target);
},
uninstall: function (obj, plugin, project, options) {
if (isSwiftPackagePlugin(plugin)) return;

const target = obj.target;

if (!target) {
Expand Down
52 changes: 52 additions & 0 deletions tests/spec/unit/Api.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ if (process.platform === 'darwin') {

const projectFile = require('../../../lib/projectFile');
const BridgingHeader_mod = require('../../../lib/BridgingHeader.js');
const SwiftPackage_mod = require('../../../lib/SwiftPackage.js');
const Podfile_mod = require('../../../lib/Podfile');
const PodsJson_mod = require('../../../lib/PodsJson');
const FIXTURES = path.join(__dirname, 'fixtures');
Expand Down Expand Up @@ -108,6 +109,7 @@ describe('Platform Api', () => {

describe('addPlugin', () => {
const my_plugin = {
getPlatforms () { return [{ name: 'ios' }]; },
getHeaderFiles: function () { return []; },
getFrameworks: function () { return []; },
getPodSpecs: function () { return []; }
Expand All @@ -117,6 +119,7 @@ describe('Platform Api', () => {
addPlugin: function () { return Promise.resolve(); }
});
spyOn(BridgingHeader_mod, 'BridgingHeader');
spyOn(SwiftPackage_mod, 'SwiftPackage');
spyOn(Podfile_mod, 'Podfile');
spyOn(PodsJson_mod, 'PodsJson');
});
Expand Down Expand Up @@ -145,6 +148,30 @@ describe('Platform Api', () => {
});
});
});

describe('adding plugin with Swift package', () => {
let swiftPackage_mock;
const swift_plugin = {
id: 'swift_plugin',
getPlatforms () { return [{ name: 'ios', package: 'swift' }]; },
getHeaderFiles: function () { return []; },
getFrameworks: function () { return []; },
getPodSpecs: function () { return []; }
};

beforeEach(() => {
swiftPackage_mock = jasmine.createSpyObj('swiftpackage mock', ['addPlugin']);
SwiftPackage_mod.SwiftPackage.and.returnValue(swiftPackage_mock);
});

it('should add the plugin reference to Package.swift', () => {
return api.addPlugin(swift_plugin)
.then(() => {
expect(swiftPackage_mock.addPlugin).toHaveBeenCalledWith(swift_plugin);
});
});
});

describe('adding pods since the plugin contained <podspecs>', () => {
let podsjson_mock;
let podfile_mock;
Expand Down Expand Up @@ -301,6 +328,7 @@ describe('Platform Api', () => {
});
describe('removePlugin', () => {
const my_plugin = {
getPlatforms () { return [{ name: 'ios' }]; },
getHeaderFiles: function () { return []; },
getFrameworks: function () {},
getPodSpecs: function () { return []; }
Expand All @@ -309,9 +337,33 @@ describe('Platform Api', () => {
spyOn(PluginManager, 'get').and.returnValue({
removePlugin: function () { return Promise.resolve(); }
});
spyOn(SwiftPackage_mod, 'SwiftPackage');
spyOn(Podfile_mod, 'Podfile');
spyOn(PodsJson_mod, 'PodsJson');
});

describe('removing plugin with Swift package', () => {
let swiftPackage_mock;
const swift_plugin = {
id: 'swift_plugin',
getPlatforms () { return [{ name: 'ios', package: 'swift' }]; },
getHeaderFiles: function () { return []; },
getFrameworks: function () { return []; },
getPodSpecs: function () { return []; }
};

beforeEach(() => {
swiftPackage_mock = jasmine.createSpyObj('swiftpackage mock', ['removePlugin']);
SwiftPackage_mod.SwiftPackage.and.returnValue(swiftPackage_mock);
});

it('should remove the plugin reference from Package.swift', () => {
return api.removePlugin(swift_plugin)
.then(() => {
expect(swiftPackage_mock.removePlugin).toHaveBeenCalledWith(swift_plugin);
});
});
});
describe('removing pods since the plugin contained <podspecs>', () => {
let podsjson_mock;
let podfile_mock;
Expand Down
Loading

0 comments on commit 5589c15

Please sign in to comment.