-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ios: build native modules as frameworks
Changes the native modules build steps to integrate them as framework bundles containing a MH_DYLIB binary, so they're accepted in the App Store. Generates a list of the native modules and their framework equivalence at build time. Overrides dlopen to load the modules from their framework file using a preloaded module.
- Loading branch information
1 parent
39e8ee5
commit 6cd38b0
Showing
6 changed files
with
296 additions
and
17 deletions.
There are no files selected for viewing
134 changes: 134 additions & 0 deletions
134
install/helper-scripts/ios-create-plists-and-dlopen-override.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const crypto = require('crypto'); | ||
const { spawnSync } = require('child_process'); | ||
|
||
function visitEveryFramework(projectPath) { | ||
var foundFrameworks = []; | ||
var countInvalidFrameworks = 0; | ||
var countValidFrameworks = 0; | ||
function recursivelyFindFrameworks(currentPath) { | ||
let currentFiles = fs.readdirSync(currentPath); | ||
for (let i = 0; i < currentFiles.length; i++) { | ||
let currentFilename = path.normalize(path.join(currentPath,currentFiles[i])); | ||
if (fs.lstatSync(currentFilename).isDirectory()) { | ||
if (currentFilename.endsWith(".node")) { | ||
let frameworkContents = fs.readdirSync(currentFilename); | ||
// Frameworks output by nodejs-mobile-gyp are expected to have only one file inside, corresponding to the proper shared library. | ||
if (frameworkContents.length != 1) { | ||
console.log('Skipping a ".node". Expected to find only one file inside this path: ' + currentFilename); | ||
countInvalidFrameworks++; | ||
} else { | ||
let currentBinaryName = frameworkContents[0]; | ||
let checkFileType = spawnSync('file', [path.join(currentFilename,currentBinaryName)]); | ||
// File inside a .framework should be a dynamically linked shared library. | ||
if(checkFileType.stdout.toString().indexOf('dynamically linked shared library') > -1) | ||
{ | ||
let newFrameworkObject = { | ||
originalFileName: currentFilename, | ||
originalRelativePath: '', | ||
originalBinaryName: currentBinaryName, | ||
newFrameworkName: '', | ||
newFrameworkFileName: '' | ||
}; | ||
foundFrameworks.push(newFrameworkObject); | ||
countValidFrameworks++; | ||
} else { | ||
console.log('Skipping a ".node". Couldn\'t find a dynamically linked shared library inside ' + currentFilename); | ||
countInvalidFrameworks++; | ||
} | ||
} | ||
} | ||
recursivelyFindFrameworks(currentFilename); | ||
} | ||
} | ||
} | ||
recursivelyFindFrameworks(projectPath); | ||
|
||
console.log("Found " + countValidFrameworks + " valid frameworks and " + countInvalidFrameworks + " invalid frameworks after rebuilding the native modules for iOS."); | ||
if (foundFrameworks.length<1) { | ||
console.log("No valid framework native modules were found. Skipping integrating them into the App."); | ||
return; | ||
} | ||
|
||
for (let i = 0; i < foundFrameworks.length; i++) { | ||
// Fill the helper fields for each framework. | ||
let currentFramework = foundFrameworks[i]; | ||
currentFramework.originalRelativePath = path.relative(projectPath,currentFramework.originalFileName); | ||
|
||
// To make each framework name unique while embedding, use a digest of the relative path. | ||
let hash = crypto.createHash('sha1'); | ||
hash.update(currentFramework.originalRelativePath); | ||
currentFramework.newFrameworkName = 'node' + hash.digest('hex'); | ||
currentFramework.newFrameworkFileName = path.join(path.dirname(currentFramework.originalFileName),currentFramework.newFrameworkName+'.framework'); | ||
} | ||
|
||
for (let i = 0; i < foundFrameworks.length; i++) { | ||
// Rename the binaries to the new framework structure and add a .plist | ||
let currentFramework = foundFrameworks[i]; | ||
fs.renameSync(currentFramework.originalFileName, currentFramework.newFrameworkFileName); | ||
fs.renameSync( | ||
path.join(currentFramework.newFrameworkFileName,currentFramework.originalBinaryName), | ||
path.join(currentFramework.newFrameworkFileName,currentFramework.newFrameworkName) | ||
); | ||
|
||
// Read template Info.plist | ||
let plistXmlContents = fs.readFileSync(path.join(__dirname,'plisttemplate.xml')).toString(); | ||
|
||
// Replace values with the new bundle name and XCode environment variables. | ||
plistXmlContents = plistXmlContents | ||
.replace(/\{ENV_MAC_OS_X_PRODUCT_BUILD_VERSION\}/g, process.env.MAC_OS_X_PRODUCT_BUILD_VERSION) | ||
.replace(/\{VAR_BINARY_NAME\}/g, currentFramework.newFrameworkName) | ||
.replace(/\{ENV_DEFAULT_COMPILER\}/g, process.env.DEFAULT_COMPILER) | ||
.replace(/\{ENV_PLATFORM_PRODUCT_BUILD_VERSION\}/g, process.env.PLATFORM_PRODUCT_BUILD_VERSION) | ||
.replace(/\{ENV_SDK_VERSION\}/g, process.env.SDK_VERSION) | ||
.replace(/\{ENV_SDK_PRODUCT_BUILD_VERSION\}/g, process.env.SDK_PRODUCT_BUILD_VERSION) | ||
.replace(/\{ENV_SDK_NAME\}/g, process.env.SDK_NAME) | ||
.replace(/\{ENV_XCODE_VERSION_ACTUAL\}/g, process.env.XCODE_VERSION_ACTUAL) | ||
.replace(/\{ENV_XCODE_PRODUCT_BUILD_VERSION\}/g, process.env.XCODE_PRODUCT_BUILD_VERSION); | ||
|
||
// Use plutil to generate the plist in the binary format. | ||
let plistGeneration = spawnSync('plutil',[ | ||
'-convert', | ||
'binary1', // Will convert the xml plist to binary. | ||
'-o', | ||
path.join(currentFramework.newFrameworkFileName,'Info.plist'), // target Info.plist path. | ||
'-' // read the input from the process stdin. | ||
], { | ||
input: plistXmlContents | ||
}); | ||
} | ||
|
||
var frameworkOverrideContents = [] | ||
for (let i = 0; i < foundFrameworks.length; i++) { | ||
// Generate the contents of a JSON file for overriding dlopen calls at runtime. | ||
let currentFramework = foundFrameworks[i]; | ||
frameworkOverrideContents.push( | ||
{ | ||
originalpath: currentFramework.originalRelativePath.split(path.sep), | ||
newpath: ['..', '..', 'Frameworks', currentFramework.newFrameworkName+'.framework', currentFramework.newFrameworkName] | ||
} | ||
); | ||
} | ||
fs.writeFileSync(path.join(projectPath, 'override-dlopen-paths-data.json'), JSON.stringify(frameworkOverrideContents)); | ||
|
||
// Copy runtime script that will override dlopen paths. | ||
fs.copyFileSync(path.join(__dirname,'override-dlopen-paths-preload.js'),path.join(projectPath,'override-dlopen-paths-preload.js')); | ||
|
||
for (let i = 0; i < foundFrameworks.length; i++) { | ||
// Put an empty file in each of the .node original locations, since some modules check their existence. | ||
fs.closeSync(fs.openSync(foundFrameworks[i].originalFileName, 'w')); | ||
} | ||
|
||
} | ||
|
||
|
||
if (process.argv.length >=3) { | ||
if (fs.existsSync(process.argv[2])) { | ||
visitEveryFramework(path.normalize(process.argv[2])); | ||
} | ||
process.exit(0); | ||
} else { | ||
console.error("A path is expected as an argument."); | ||
process.exit(1); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
|
||
var substitutionDataFile = path.join(__dirname,'override-dlopen-paths-data.json'); | ||
// If the json file exists, override dlopen to load the specified framework paths instead. | ||
if (fs.existsSync(substitutionDataFile)) { | ||
var pathSubstitutionData = JSON.parse(fs.readFileSync(substitutionDataFile, 'utf8')); | ||
|
||
var pathSubstitutionDictionary = {}; | ||
// Build a dictionary to convert paths at runtime, taking current sandboxed paths into account. | ||
for (let i = 0; i < pathSubstitutionData.length; i++) { | ||
pathSubstitutionDictionary[ | ||
path.normalize(path.join.apply(null, [__dirname].concat(pathSubstitutionData[i].originalpath))) | ||
] = path.normalize(path.join.apply(null, [__dirname].concat(pathSubstitutionData[i].newpath))); | ||
} | ||
|
||
var old_dlopen = process.dlopen; | ||
// Override process.dlopen | ||
process.dlopen = function(_module, _filename) { | ||
if( pathSubstitutionDictionary[path.normalize(_filename)] ) { | ||
_filename = pathSubstitutionDictionary[path.normalize(_filename)]; | ||
} | ||
old_dlopen(_module,_filename); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>BuildMachineOSBuild</key> | ||
<string>{ENV_MAC_OS_X_PRODUCT_BUILD_VERSION}</string> | ||
<key>CFBundleDevelopmentRegion</key> | ||
<string>en</string> | ||
<key>CFBundleExecutable</key> | ||
<string>{VAR_BINARY_NAME}</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>com.janeasystems.{VAR_BINARY_NAME}</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>{VAR_BINARY_NAME}</string> | ||
<key>CFBundlePackageType</key> | ||
<string>FMWK</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>1.0</string> | ||
<key>CFBundleSupportedPlatforms</key> | ||
<array> | ||
<string>iPhoneOS</string> | ||
</array> | ||
<key>CFBundleVersion</key> | ||
<string>1</string> | ||
<key>DTCompiler</key> | ||
<string>{ENV_DEFAULT_COMPILER}</string> | ||
<key>DTPlatformBuild</key> | ||
<string>{ENV_PLATFORM_PRODUCT_BUILD_VERSION}</string> | ||
<key>DTPlatformName</key> | ||
<string>iphoneos</string> | ||
<key>DTPlatformVersion</key> | ||
<string>{ENV_SDK_VERSION}</string> | ||
<key>DTSDKBuild</key> | ||
<string>{ENV_SDK_PRODUCT_BUILD_VERSION}</string> | ||
<key>DTSDKName</key> | ||
<string>{ENV_SDK_NAME}</string> | ||
<key>DTXcode</key> | ||
<string>{ENV_XCODE_VERSION_ACTUAL}</string> | ||
<key>DTXcodeBuild</key> | ||
<string>{ENV_XCODE_PRODUCT_BUILD_VERSION}</string> | ||
<key>MinimumOSVersion</key> | ||
<string>9.0</string> | ||
<key>NSHumanReadableCopyright</key> | ||
<string></string> | ||
<key>UIDeviceFamily</key> | ||
<array> | ||
<integer>1</integer> | ||
<integer>2</integer> | ||
</array> | ||
<key>UIRequiredDeviceCapabilities</key> | ||
<array> | ||
<string>arm64</string> | ||
</array> | ||
</dict> | ||
</plist> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters