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

Restore symlinks in FacebookSDK.Framework before build #1022

Merged
merged 1 commit into from
Aug 5, 2015
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ gen/
build/

local.properties
proguard/
proguard/

node_modules/
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ The Facebook plugin for [Apache Cordova](http://incubator.apache.org/cordova/) a

## << --- Cordova Registry Warning [iOS]

****Installing this plugin directly from Cordova Registry results in Xcode using a broken `FacebookSDK.framework`, this is because the current publish procedure to NPM breaks symlinks [CB-6092](https://issues.apache.org/jira/browse/CB-6092). Please install the plugin through a locally cloned copy or re-add the `FacebookSDK.framework` to Xcode after installation.****
****Installing this plugin directly from Cordova Registry results in Xcode using a broken `FacebookSDK.framework` if you're using cordova version lower than 4.0.0. This is because the current publish procedure to NPM breaks symlinks [CB-6092](https://issues.apache.org/jira/browse/CB-6092). Please install the plugin through a locally cloned copy or re-add the `FacebookSDK.framework` to Xcode after installation.****

****If you're using cordova@4.0.0 or greater, symlinks inside of FacebookSDK.framework will be restored automatically in `before_compile` hook. If you want to build project using Xcode, you will need to build it using CLI first to repair broken framework.****

## ------------------------------------------ >>

Expand Down
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"version": "0.11.0",
"name": "com.phonegap.plugins.facebookconnect",
"cordova_name": "Facebook Connect",
"description": "\r\n This is the official plugin for Facebook in Apache Cordova/PhoneGap!\r\n\r\n The Facebook plugin for Apache Cordova allows you to use the same JavaScript code in your\r\n Cordova application as you use in your web application.\r\n Docs: https://github.com/Wizcorp/phonegap-facebook-plugin.\r\n ",
"license": "Apache 2.0",
"platforms": [
"android",
"ios",
"browser"
],
"scripts": {
"prepublish": "node ./scripts/saveframeworksmetadata.js"
},
"engines": [
{
"name": "cordova",
"version": ">=3.5.0"
}
],
"devDependencies": {
"elementtree": "^0.1.6",
"glob": "^5.0.6"
}
}
3 changes: 3 additions & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@

<!-- ios -->
<platform name="ios">

<hook type="before_compile" src="scripts/restoreframeworksmetadata.js" />

<config-file target="config.xml" parent="/*">
<feature name="FacebookConnectPlugin">
<param name="ios-package" value="FacebookConnectPlugin"/>
Expand Down
42 changes: 42 additions & 0 deletions scripts/restoreframeworksmetadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env node
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.

/* jshint node: true */

var path = require('path');
var fs = require('fs');

var PLATFORM = 'ios';

module.exports = function (ctx) {
// We want to restore symlinks on ios build only
if (ctx.opts.platforms.indexOf(PLATFORM) < 0 || process.platform !== 'darwin') return;

var symlinkMetadata = path.join(__dirname, '..', 'symlinkmetadata.json');
// If there is no metadata for symlinks, just skip this step
if (!fs.existsSync(symlinkMetadata)) return;

console.log('Restoring symlinks for custom frameworks');
var frameworks = require(symlinkMetadata);
var iosProjectWrapper = ctx.requireCordovaModule('../plugman/platforms/ios');

var platformPluginsDir = iosProjectWrapper.parseProjectFile(path.join(ctx.opts.projectRoot, 'platforms', PLATFORM)).plugins_dir;
var installedPluginDir = path.join(platformPluginsDir, ctx.opts.plugin.id);

Object.keys(frameworks).forEach(function (framework) {
console.log('Processing ' + framework + ':');
var frameworkLocation = path.join(installedPluginDir, framework);
var symlinks = frameworks[framework];
symlinks.forEach(function (symlink) {
var link = path.join(frameworkLocation, symlink.link);
if (fs.existsSync(link)) {
fs.unlinkSync(link);
}

console.log('\tRestoring symlink ' + symlink.link);
fs.symlinkSync(symlink.target, link);
});
});
};
154 changes: 154 additions & 0 deletions scripts/saveframeworksmetadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#!/usr/bin/env node
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.

var path = require('path'),
glob = require('glob'),
et = require('elementtree'),
fs = require('fs');

var POSSIBLE_SURROGATE_SIZE = 512;
var CHECK_FOR_WINDOWS_SURROGATES = true;

/**
* Saves metadata for all symlinks inside of iOS custom frameworks if any present in plugin
* to be able to restore it when application with this plugin installed is being built.
* This is required for some plugins that uses custom frameworks on iOS (which are heavily
* depends on symlinks) due to fact that npm doesn't preserves symlinks and removes them
* at 'package'/'publish' step. See https://issues.apache.org/jira/browse/CB-6092 for details.
*
* @param {String} packageDirectory Plugin directory.
*/
function saveCustomFrameworksSymlinksMetadata(packageDirectory) {
// Find all symlinks within plugin source code and save metadata for them
// Convert package directory to absolute path to avoid common problems
packageDirectory = path.resolve(packageDirectory);

// We need to produce an object with following structure:
// { <framework_name> : [
// { link: <link_source>, target: <link_target> }
// <symlink2>
// ...
// ]
// }
var frameworksMetadata =getCustomFrameworks(packageDirectory)
.reduce(getSymlinksForFramework, {});

// If no symlinks found (this is probably impossible but still) then just return nothing
if (frameworksMetadata.length === 0) return;

var symlinkMetadata = path.join(packageDirectory, 'symlinkmetadata.json');
fs.writeFileSync(symlinkMetadata, JSON.stringify(frameworksMetadata, null, 4));

function getSymlinksForFramework (accumulator, framework) {
var frameworkSource = path.resolve(packageDirectory, framework);
var possibleLinks = glob.sync(path.join(frameworkSource, '**', '*'));

var realLinks = possibleLinks
// First try to get info for all possible symlinks in framework directory
.map(function (possibleLinkPath) {
return getSymlinkInfo(possibleLinkPath, frameworkSource);
})
// Filter out items that are not symlinks
.filter(function (symlinkInfo) {
return symlinkInfo;
});

if (realLinks.length > 0) {
// If there is any symlinks found, create an object, representing
// symlinks for framework and push it to accumulator.
accumulator[path.basename(framework)] = realLinks;
}

return accumulator;
}

function getSymlinkInfo (linkPath, basePath) {
var possibleSymlinkTarget = checkSymlink(linkPath, CHECK_FOR_WINDOWS_SURROGATES);
if (possibleSymlinkTarget) {
// convert link and link's target paths to relative again
// replace backslashes with forward slashes to prevent cross-platform path issues
var link = path.relative(basePath, linkPath).replace(/\\/g, '/');
var target = path.relative(path.dirname(linkPath), possibleSymlinkTarget).replace(/\\/g, '/');
var linkMetadata = {
link: link,
target: target
};
// events.emit('verbose', 'Saving restore metadata for link ' + possibleLink + ' ==> ' + target);
return linkMetadata;
}
}
}

/**
* Checks if provided path is a symlink and if true, returns it's destination.
* Method can also try to resolve symlink 'surrogates' on windows (text files
* without an extension, that contains path to linked file)
*
* @param {String} possibleSymlink Path to candidate to be a symlink.
* @param {Boolean} checkForWindowsSurrogates
* Flag that forces method to check windows 'surrogates' for symlinks
* @return {String} Absolute path to symlink target or undefined, if provided
* path is not a symlink
*/
function checkSymlink(possibleSymlink, checkForWindowsSurrogates) {
// To be sure that we're operating by absolute paths
possibleSymlink = path.resolve(possibleSymlink);

var stat,
result;
try {
stat = fs.lstatSync(possibleSymlink);
} catch (e) {
return;
}

function isWindowsSurrogate() {
return stat.isFile() &&
stat.size > 0 && stat.size < POSSIBLE_SURROGATE_SIZE &&
path.extname(possibleSymlink) === '';
}

if (stat.isSymbolicLink()) {
result = fs.realpathSync(possibleSymlink);
} else if (checkForWindowsSurrogates && isWindowsSurrogate()) {
var possibleSymlinkContent = fs.readFileSync(possibleSymlink, 'utf8');
// Need to add '..' path here since surrogate content
// references to file/folder relative to symlink's parent
var possibleSymlinkDestination = path.resolve(possibleSymlink, '..', possibleSymlinkContent);
if (fs.existsSync(possibleSymlinkDestination)) {
result = possibleSymlinkDestination;
}
}

return result;
}

/**
* Searches plugin.xml in package folder for ios custom frameworks (<framework custom="true">)
*
* @param {String} packageDirectory Path to directory that contains plugin.xml file
* @return {String[]} Array of custom frameworks sources (value of 'src' attribute)
*/
function getCustomFrameworks(packageDirectory) {
var pluginXml = path.join(packageDirectory, 'plugin.xml');
if (!fs.existsSync(pluginXml))
throw new Error('The provided directory doesn\'t contains plugin definition.');

var contents = fs.readFileSync(pluginXml, 'utf-8');
if(contents) {
//Windows is the BOM. Skip the Byte Order Mark.
contents = contents.substring(contents.indexOf('<'));
}

var pluginXmlTree = new et.ElementTree(et.XML(contents));

return pluginXmlTree
.findall('./platform[@name="ios"]/framework[@custom="true"]')
.map(function (frameworkElement) {
return frameworkElement.attrib.src;
});
}

saveCustomFrameworksSymlinksMetadata('.');
20 changes: 20 additions & 0 deletions symlinkmetadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"FacebookSDK.framework": [
{
"link": "FacebookSDK",
"target": "Versions/A/FacebookSDK"
},
{
"link": "Headers",
"target": "Versions/A/Headers"
},
{
"link": "Resources",
"target": "Versions/A/Resources"
},
{
"link": "Versions/Current",
"target": "A"
}
]
}