This repository has been archived by the owner on Jan 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 478
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* [wip] expo ios -- local iOS build command * clean up * remove cocoapods stuff * Remove XCPretty JS bundle error logging optimization * write logs to file * wip * update * Update yarn.lock * fix launching * added code signing * remove old xcpretty * updated format * wip * refactor * fix commands * refactor formatter * Added support for catching metro bundle errors * Update ExpoLogFormatter.ts * updated xcpretty * refactor * Added cocoapods stuff back * Remove PlistBuddy and add WIP LLDB * Improve bplist * Added simulator log stream * silence more errors * refactor * Update ExpoLogFormatter.ts * cleanup ios build command * Update index.ts * added dynamic port resolver * Update index.ts * Delete code signing plugins * [wip] prevent opening bundler * refactor * move logs * Update Simulator.ts * added --bundler flag * revert changes * Update Podfile.ts * added unit test for logger
- Loading branch information
Showing
16 changed files
with
975 additions
and
39 deletions.
There are no files selected for viewing
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
121 changes: 121 additions & 0 deletions
121
packages/expo-cli/src/commands/run/ios/ExpoLogFormatter.ts
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,121 @@ | ||
import { Formatter, Parser } from '@expo/xcpretty'; | ||
import { switchRegex } from '@expo/xcpretty/build/switchRegex'; | ||
import chalk from 'chalk'; | ||
|
||
import Log from '../../../log'; | ||
|
||
const ERROR = '❌ '; | ||
|
||
function moduleNameFromPath(modulePath: string) { | ||
if (modulePath.startsWith('@')) { | ||
const [org, packageName] = modulePath.split('/'); | ||
if (org && packageName) { | ||
return [org, packageName].join('/'); | ||
} | ||
return modulePath; | ||
} | ||
const [packageName] = modulePath.split('/'); | ||
return packageName ? packageName : modulePath; | ||
} | ||
|
||
function getNodeModuleName(filePath: string): string | null { | ||
// '/Users/evanbacon/Documents/GitHub/lab/yolo5/node_modules/react-native-reanimated/ios/Nodes/REACallFuncNode.m' | ||
const [, modulePath] = filePath.split('/node_modules/'); | ||
if (modulePath) { | ||
return moduleNameFromPath(modulePath); | ||
} | ||
return null; | ||
} | ||
|
||
class CustomParser extends Parser { | ||
private isCollectingMetroError = false; | ||
private metroError: string[] = []; | ||
|
||
constructor(public formatter: ExpoLogFormatter) { | ||
super(formatter); | ||
} | ||
|
||
parse(text: string): void | string { | ||
const results = this.checkMetroError(text); | ||
if (results) { | ||
return results; | ||
} | ||
return super.parse(text); | ||
} | ||
|
||
// Error for the build script wrapper in expo-updates that catches metro bundler errors. | ||
// This can be repro'd by importing a file that doesn't exist, then building. | ||
// Metro will fail to generate the JS bundle, and throw an error that should be caught here. | ||
checkMetroError(text: string) { | ||
// In expo-updates, we wrap the bundler script and add regex around the error message so we can present it nicely to the user. | ||
return switchRegex(text, [ | ||
[ | ||
/@build-script-error-begin/m, | ||
() => { | ||
this.isCollectingMetroError = true; | ||
}, | ||
], | ||
[ | ||
/@build-script-error-end/m, | ||
() => { | ||
const results = this.metroError.join('\n'); | ||
// Reset the metro collection error array (should never need this). | ||
this.isCollectingMetroError = false; | ||
this.metroError = []; | ||
return this.formatter.formatMetroAssetCollectionError(results); | ||
}, | ||
], | ||
[ | ||
null, | ||
() => { | ||
// Collect all the lines in the metro build error | ||
if (this.isCollectingMetroError) { | ||
let results = text; | ||
if (!this.metroError.length) { | ||
const match = text.match( | ||
/Error loading assets JSON from Metro.*steps correctly.((.|\n)*)/m | ||
); | ||
if (match && match[1]) { | ||
results = match[1].trim(); | ||
} | ||
} | ||
this.metroError.push(results); | ||
} | ||
}, | ||
], | ||
]); | ||
} | ||
} | ||
|
||
export class ExpoLogFormatter extends Formatter { | ||
constructor(props: { projectRoot: string }) { | ||
super(props); | ||
this.parser = new CustomParser(this); | ||
} | ||
|
||
formatMetroAssetCollectionError(errorContents: string): string { | ||
const results = `\n${chalk.red( | ||
ERROR + | ||
// Provide proper attribution. | ||
'Metro encountered an error:\n' + | ||
errorContents | ||
)}\n`; | ||
this.errors.push(results); | ||
return results; | ||
} | ||
|
||
shouldShowCompileWarning(filePath: string, lineNumber?: string, columnNumber?: string): boolean { | ||
if (Log.isDebug) return true; | ||
return !filePath.match(/node_modules/) && !filePath.match(/\/ios\/Pods\//); | ||
} | ||
|
||
formatCompile(fileName: string, filePath: string): string { | ||
const moduleName = getNodeModuleName(filePath); | ||
const moduleNameTag = moduleName ? chalk.dim(`(${moduleName})`) : undefined; | ||
return ['\u203A', 'Compiling', fileName, moduleNameTag].filter(Boolean).join(' '); | ||
} | ||
|
||
finish() { | ||
Log.log(`\n\u203A ${this.errors.length} error(s), and ${this.warnings.length} warning(s)\n`); | ||
} | ||
} |
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,37 @@ | ||
import spawnAsync from '@expo/spawn-async'; | ||
import chalk from 'chalk'; | ||
import { spawnSync } from 'child_process'; | ||
import wrapAnsi from 'wrap-ansi'; | ||
|
||
import CommandError, { SilentError } from '../../../CommandError'; | ||
import log from '../../../log'; | ||
|
||
export async function isInstalledAsync() { | ||
try { | ||
await spawnAsync('ios-deploy', ['--version'], { stdio: 'ignore' }); | ||
return true; | ||
} catch { | ||
return false; | ||
} | ||
} | ||
|
||
export function installBinaryOnDevice({ bundle, udid }: { bundle: string; udid: string }) { | ||
const iosDeployInstallArgs = ['--bundle', bundle, '--id', udid, '--justlaunch', '--debug']; | ||
|
||
const output = spawnSync('ios-deploy', iosDeployInstallArgs, { encoding: 'utf8' }); | ||
|
||
if (output.error) { | ||
throw new CommandError( | ||
`Failed to install the app on device. Error in "ios-deploy" command: ${output.error.message}` | ||
); | ||
} | ||
} | ||
|
||
export async function assertInstalledAsync() { | ||
if (!(await isInstalledAsync())) { | ||
// Controlled error message. | ||
const error = `Cannot install iOS apps on devices without ${chalk.bold`ios-deploy`} installed globally. Please install it with ${chalk.bold`brew install ios-deploy`} and try again, or build the app with a simulator.`; | ||
log.warn(wrapAnsi(error, process.stdout.columns || 80)); | ||
throw new SilentError(error); | ||
} | ||
} |
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,125 @@ | ||
import { getPackageJson, PackageJSONConfig } from '@expo/config'; | ||
import JsonFile from '@expo/json-file'; | ||
import chalk from 'chalk'; | ||
import fs from 'fs-extra'; | ||
import { safeLoad } from 'js-yaml'; | ||
import * as path from 'path'; | ||
|
||
import Log from '../../../log'; | ||
import { hashForDependencyMap } from '../../eject/updatePackageJson'; | ||
import { installCocoaPodsAsync } from '../../utils/CreateApp'; | ||
|
||
const CHECKSUM_KEY = 'SPEC CHECKSUMS'; | ||
|
||
export function getDependenciesFromPodfileLock(podfileLockPath: string) { | ||
Log.debug(`Reading ${podfileLockPath}`); | ||
let fileContent; | ||
try { | ||
fileContent = fs.readFileSync(podfileLockPath, 'utf8'); | ||
} catch (err) { | ||
Log.error( | ||
`Could not find "Podfile.lock" at ${chalk.dim(podfileLockPath)}. Did you run "${chalk.bold( | ||
'npx pod-install' | ||
)}"?` | ||
); | ||
return []; | ||
} | ||
|
||
// Previous portions of the lock file could be invalid yaml. | ||
// Only parse parts that are valid | ||
const tail = fileContent.split(CHECKSUM_KEY).slice(1); | ||
const checksumTail = CHECKSUM_KEY + tail; | ||
|
||
return Object.keys(safeLoad(checksumTail)[CHECKSUM_KEY] || {}); | ||
} | ||
|
||
function getTempPrebuildFolder(projectRoot: string) { | ||
return path.join(projectRoot, '.expo', 'prebuild'); | ||
} | ||
|
||
type PackageChecksums = { | ||
dependencies: string; | ||
devDependencies: string; | ||
}; | ||
|
||
function hasNewDependenciesSinceLastBuild(projectRoot: string, packageChecksums: PackageChecksums) { | ||
// TODO: Maybe comparing lock files would be better... | ||
const tempDir = getTempPrebuildFolder(projectRoot); | ||
const tempPkgJsonPath = path.join(tempDir, 'cached-packages.json'); | ||
if (!fs.pathExistsSync(tempPkgJsonPath)) { | ||
return true; | ||
} | ||
const { dependencies, devDependencies } = JsonFile.read(tempPkgJsonPath); | ||
// Only change the dependencies if the normalized hash changes, this helps to reduce meaningless changes. | ||
const hasNewDependencies = packageChecksums.dependencies !== dependencies; | ||
const hasNewDevDependencies = packageChecksums.devDependencies !== devDependencies; | ||
|
||
return hasNewDependencies || hasNewDevDependencies; | ||
} | ||
|
||
function createPackageChecksums(pkg: PackageJSONConfig): PackageChecksums { | ||
return { | ||
dependencies: hashForDependencyMap(pkg.dependencies || {}), | ||
devDependencies: hashForDependencyMap(pkg.devDependencies || {}), | ||
}; | ||
} | ||
|
||
async function hasPackageJsonDependencyListChangedAsync(projectRoot: string) { | ||
const pkg = getPackageJson(projectRoot); | ||
|
||
const packages = createPackageChecksums(pkg); | ||
const hasNewDependencies = hasNewDependenciesSinceLastBuild(projectRoot, packages); | ||
|
||
// Cache package.json | ||
const tempDir = path.join(getTempPrebuildFolder(projectRoot), 'cached-packages.json'); | ||
await fs.ensureFile(tempDir); | ||
await JsonFile.writeAsync(tempDir, packages); | ||
|
||
return hasNewDependencies; | ||
} | ||
|
||
function doesProjectUseCocoaPods(projectRoot: string): boolean { | ||
return fs.existsSync(path.join(projectRoot, 'ios', 'Podfile')); | ||
} | ||
|
||
function isLockfileCreated(projectRoot: string): boolean { | ||
const podfileLockPath = path.join(projectRoot, 'ios', 'Podfile.lock'); | ||
return fs.existsSync(podfileLockPath); | ||
} | ||
|
||
// TODO: Same process but with app.config changes + default plugins. | ||
// This will ensure the user is prompted for extra setup. | ||
export default async function maybePromptToSyncPodsAsync(projectRoot: string) { | ||
if (!doesProjectUseCocoaPods(projectRoot)) { | ||
// Project does not use CocoaPods | ||
return; | ||
} | ||
if (!isLockfileCreated(projectRoot)) { | ||
await installCocoaPodsAsync(projectRoot); | ||
return; | ||
} | ||
|
||
// Getting autolinked packages can be heavy, optimize around checking every time. | ||
if (!(await hasPackageJsonDependencyListChangedAsync(projectRoot))) { | ||
return; | ||
} | ||
|
||
await promptToInstallPodsAsync(projectRoot, []); | ||
} | ||
|
||
async function promptToInstallPodsAsync(projectRoot: string, missingPods?: string[]) { | ||
if (missingPods?.length) { | ||
Log.log( | ||
`Could not find the following native modules: ${missingPods | ||
.map(pod => chalk.bold(pod)) | ||
.join(', ')}. Did you forget to run "${chalk.bold('pod install')}" ?` | ||
); | ||
} | ||
|
||
try { | ||
await installCocoaPodsAsync(projectRoot); | ||
} catch (error) { | ||
fs.removeSync(path.join(getTempPrebuildFolder(projectRoot), 'cached-packages.json')); | ||
throw error; | ||
} | ||
} |
Oops, something went wrong.