Skip to content

Commit

Permalink
Merge pull request #1022 from MSOpenTech/symlinks_restore
Browse files Browse the repository at this point in the history
Restore symlinks in FacebookSDK.Framework before build
  • Loading branch information
aogilvie committed Aug 5, 2015
2 parents 27f747d + f22aaaf commit 2220274
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 2 deletions.
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"
}
]
}

0 comments on commit 2220274

Please sign in to comment.