diff --git a/Libraries/RCTWebSocketDebugger/package.json b/Libraries/RCTWebSocketDebugger/package.json new file mode 100644 index 00000000000000..6356c20e2ed45f --- /dev/null +++ b/Libraries/RCTWebSocketDebugger/package.json @@ -0,0 +1,8 @@ +{ + "react-native": { + "xcodeproj": "RCTWebSocketDebugger", + "libraries": [ + "libicucore" + ] + } +} diff --git a/cli.js b/cli.js index 99a3b24e0e2e8c..814fe0f3ae6e1d 100644 --- a/cli.js +++ b/cli.js @@ -5,7 +5,9 @@ 'use strict'; var spawn = require('child_process').spawn; +var fs = require('fs'); var path = require('path'); +var uuid = require('node-uuid'); function printUsage() { console.log([ @@ -13,6 +15,7 @@ function printUsage() { '', 'Commands:', ' start: starts the webserver', + ' link: link the target library the current project', ].join('\n')); process.exit(1); } @@ -31,6 +34,13 @@ function run() { process.cwd(), ], {stdio: 'inherit'}); break; + case 'link': + if (!args[1]) { + console.error('Usage: react-native link '); + return; + } + link(args[1]); + break; default: console.error('Command `%s` unrecognized', args[0]); printUsage(); @@ -42,6 +52,265 @@ function init(root, projectName) { spawn(path.resolve(__dirname, 'init.sh'), [projectName], {stdio:'inherit'}); } +function link(libraryPath) { + libraryPath = path.relative(process.cwd(), path.resolve(process.cwd(), libraryPath)); + var packagePath = path.resolve(libraryPath, 'package.json'); + var pkg; + try { + pkg = require(packagePath); + } catch(err) { + pkg = { 'react-native': {} }; + } + var name = pkg['react-native'].xcodeproj || path.basename(libraryPath); + var targetName = pkg['react-native'].target || 'RCT' + name.replace(/^(?:RCT)?(.*?)(?:IOS)?$/, '$1'); + var xcodeprojName = targetName + '.xcodeproj'; + var staticLibraryPath = pkg['react-native'].staticLibrary || 'lib' + targetName + '.a'; + var pbxprojPath = path.resolve(process.cwd(), libraryPath, + xcodeprojName, 'project.pbxproj') + + readProject(pbxprojPath, function (err, pbxproj) { + if (err) throw err; + + var projectID = pbxproj.rootObject; + var project = pbxproj.objects[projectID]; + var targets = project.targets.map(function (id) { + return pbxproj.objects[id]; + }).filter(function (target) { + return target.name === targetName; + }); + + console.assert(targets.length === 1, + 'More than one target found', targets); + + var target = targets[0]; + + console.assert(target.productType === 'com.apple.product-type.library.static', + 'Target is not a static library, found:', target.productType); + + // Leave it on top since other sections depend on it. + var PBXReferenceProxyHash = getHash(); + var PBXFileReferenceHash = getHash(); + + var PBXBuildFileHash = getHash(); + var PBXBuildFile = { + isa: 'PBXBuildFile', + fileRef: PBXReferenceProxyHash, + }; + + var PBXContainerItemProxyHash = getHash(); + var PBXContainerItemProxy = { + isa: 'PBXContainerItemProxy', + containerPortal: PBXFileReferenceHash, + proxyType: 2, + remoteGlobalIDString: target.productReference, + remoteInfo: targetName, + }; + + var PBXFileReference = { + isa: 'PBXFileReference', + lastKnownFileType: 'wrapper.pb-project', + name: xcodeprojName, + path: path.join(libraryPath, xcodeprojName), + sourceTree: '', + }; + + var PBXFrameworksBuildPhase = PBXBuildFileHash; + + var PBXGroupHash = getHash(); + var PBXGroup = { + isa: 'PBXGroup', + children: [ + PBXReferenceProxyHash, + ], + name: 'Products', + sourceTree: '', + }; + + var LibrariesHash = PBXFileReferenceHash; + + var ProjectReferece = { + ProductGroup: PBXGroupHash, + ProjectRef: PBXFileReferenceHash + }; + + var PBXReferenceProxy = { + isa: 'PBXReferenceProxy', + fileType: 'archive.ar', + paht: staticLibraryPath, + remoteRef: PBXContainerItemProxyHash, + sourceTree: 'BUILT_PRODUCTS_DIR', + }; + + var HeadersPath = '$(SRCROOT)/' + libraryPath; + + var sourcePkg = require(path.resolve( + process.cwd(), + './package.json' + )); + sourcePkg = sourcePkg['react-native'] || {}; + var sourceXcodeprojName = sourcePkg.xcodeproj || + path.basename(process.cwd()); + var sourcePbxprojPath = path.resolve( + process.cwd(), + sourceXcodeprojName + '.xcodeproj', + 'project.pbxproj' + ); + + readProject(sourcePbxprojPath, function (err, sourcePbxproj) { + if (err) throw err; + + var sourceRootID = sourcePbxproj.rootObject; + var PBXProject = sourcePbxproj.objects[sourceRootID]; + + PBXProject.projectReferences.push( + ProjectReferece + ); + + addToFrameworksBuildPhase(sourcePbxproj, PBXBuildFileHash); + + var buildConfigurationList = sourcePbxproj.objects[PBXProject.buildConfigurationList]; + buildConfigurationList.buildConfigurations.forEach(function (buildConfigurationHash) { + var buildConfiguration = sourcePbxproj.objects[buildConfigurationHash]; + buildConfiguration.buildSettings.HEADER_SEARCH_PATHS.push( + HeadersPath + ); + }); + + addToLibraries(sourcePbxproj, PBXFileReferenceHash); + + sourcePbxproj.objects[PBXBuildFileHash] = PBXBuildFile; + sourcePbxproj.objects[PBXContainerItemProxyHash] = PBXContainerItemProxy; + sourcePbxproj.objects[PBXFileReferenceHash] = PBXFileReference; + sourcePbxproj.objects[PBXGroupHash] = PBXGroup; + sourcePbxproj.objects[PBXReferenceProxyHash] = PBXReferenceProxy; + + var libraries = pkg['react-native'].libraries; + if (libraries) { + libraries.forEach(function (library) { + var libraryHash = getHash(); + var libraryPBXBuildFileHash = getHash(); + sourcePbxproj.objects[libraryPBXBuildFileHash] = { + isa: 'PBXBuildFile', + fileRef: libraryHash, + }; + sourcePbxproj.objects[libraryHash] = { + isa: 'PBXFileReference', + lastKnownFileType: 'compiled.mach-o.dylib', + name: library + '.dylib', + path: 'usr/lib/' + library + '.dylib', + sourceTree: 'SDKROOT', + }; + addToLibraries(sourcePbxproj, libraryHash); + addToFrameworksBuildPhase(sourcePbxproj, libraryPBXBuildFileHash); + }); + } + + var output = OldPlist.stringify(sourcePbxproj); + fs.writeFileSync(sourcePbxprojPath, output); + }); + }); +} + +var OldPlist = { + stringify: function (object, level) { + var HEADER = '// !$*UTF8*$!\n'; + var INDENT_SIZE = 2; + + function indent(str, level) { + var _indent = ''; + level *= INDENT_SIZE; + while (level--) { + _indent += ' '; + } + return _indent + str; + } + + function _stringify(object, level) { + if (Array.isArray(object)) { + return '(\n' + _stringifyArray(object, level + 1) + '\n' + indent(')', level); + } else if (typeof object === 'object') { + return '{\n' + _stringifyObject(object, level + 1) + '\n' + indent('}', level); + } else { + return _stringifyString(''+object); + } + } + + function _stringifyObject(object, level) { + return Object.keys(object).map(function (key) { + return indent(_stringifyString(key) + ' = ' + _stringify(object[key], level) + ';', level); + }).join('\n'); + } + + function _stringifyString(str) { + return !str || /\W/.test(str) ? '"' + str + '"' : str; + } + + function _stringifyArray(array, level) { + return array.map(function (value) { + return indent(_stringify(value, level), level); + }).join(',\n'); + } + + return HEADER + _stringify(object, 0); + } +}; + +function addToFrameworksBuildPhase(pbxproj, hash) { + var sourceRootID = pbxproj.rootObject; + var PBXProject = pbxproj.objects[sourceRootID]; + PBXProject.targets.forEach(function (targetHash) { + var target = pbxproj.objects[targetHash]; + target.buildPhases.forEach(function (buildPhaseHash) { + var buildPhase = pbxproj.objects[buildPhaseHash]; + if (buildPhase.isa === 'PBXFrameworksBuildPhase') { + buildPhase.files.push(hash); + } + }) + }); +} +function addToLibraries(pbxproj, hash) { + var sourceRootID = pbxproj.rootObject; + var PBXProject = pbxproj.objects[sourceRootID]; + var mainGroup = pbxproj.objects[PBXProject.mainGroup]; + mainGroup.children.forEach(function (childHash) { + var child = pbxproj.objects[childHash]; + if (child.name === 'Libraries') { + child.children.push(hash); + } + }); +} + +function readProject(pbxprojPath, callback) { + var plutil = spawn('plutil', [ + '-convert', + 'json', + '-o', + '-', + pbxprojPath + ]); + + var data = ''; + var error = ''; + plutil.stdout.on('data', function (d) { + data += d.toString(); + }); + + plutil.stderr.on('data', function (d) { + error += d.toString(); + }); + + plutil.on('close', function (code) { + callback( + error && new Error(error), + data && JSON.parse(data) + ); + }); +} + +function getHash() { + return uuid.v4().split('-').slice(1).join('').toUpperCase(); +} + module.exports = { run: run, init: init, diff --git a/package.json b/package.json index ca60030f782fce..5e2f003bcb2192 100644 --- a/package.json +++ b/package.json @@ -41,25 +41,26 @@ "react-native-start": "packager/packager.sh" }, "dependencies": { - "connect": "2.8.3", - "jstransform": "10.0.1", - "react-timer-mixin": "^0.13.1", - "react-tools": "0.13.0-rc2", - "rebound": "^0.0.12", - "source-map": "0.1.31", - "stacktrace-parser": "0.1.1", "absolute-path": "0.0.0", + "connect": "2.8.3", "debug": "~2.1.0", "joi": "~5.1.0", + "jstransform": "10.0.1", "module-deps": "3.5.6", + "node-uuid": "^1.4.3", "optimist": "0.6.1", "q": "1.0.1", + "react-timer-mixin": "^0.13.1", + "react-tools": "0.13.0-rc2", + "rebound": "^0.0.12", "sane": "1.0.1", + "source-map": "0.1.31", + "stacktrace-parser": "0.1.1", "uglify-js": "~2.4.16", "underscore": "1.7.0", "worker-farm": "1.1.0", - "yargs": "1.3.2", - "ws": "0.4.31" + "ws": "0.4.31", + "yargs": "1.3.2" }, "devDependencies": { "jest-cli": "0.2.1",