GT2/GT2-Android/node_modules/xdl/build/detach/IosIcons.js.flow

227 lines
7.5 KiB
Plaintext
Raw Normal View History

/**
* @flow
*/
import path from 'path';
import { saveImageToPathAsync, saveUrlToPathAsync, spawnAsyncThrowError } from './ExponentTools';
import StandaloneContext from './StandaloneContext';
function _getAppleIconQualifier(iconSize: number, iconResolution: number): string {
let iconQualifier;
if (iconResolution !== 1) {
// e.g. "29x29@3x"
iconQualifier = `${iconSize}x${iconSize}@${iconResolution}x`;
} else {
iconQualifier = `${iconSize}x${iconSize}`;
}
if (iconSize === 76 || iconSize === 83.5) {
// ipad sizes require ~ipad at the end
iconQualifier = `${iconQualifier}~ipad`;
}
return iconQualifier;
}
async function _saveDefaultIconToPathAsync(context: StandaloneContext, path: string) {
if (context.type === 'user') {
if (context.data.exp.icon) {
await saveImageToPathAsync(context.data.projectPath, context.data.exp.icon, path);
} else {
throw new Error('Cannot save icon because app.json has no exp.icon key.');
}
} else {
if (context.data.manifest.ios && context.data.manifest.ios.iconUrl) {
await saveUrlToPathAsync(context.data.manifest.ios.iconUrl, path);
} else if (context.data.manifest.iconUrl) {
await saveUrlToPathAsync(context.data.manifest.iconUrl, path);
} else {
throw new Error('Cannot save icon because manifest has no iconUrl or ios.iconUrl key.');
}
}
return;
}
/**
* Based on keys in the given context.config,
* ensure that the proper iOS icon images exist -- assuming Info.plist already
* points at them under CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles.
*
* This only works on MacOS (as far as I know) because it uses the sips utility.
*/
async function createAndWriteIconsToPathAsync(
context: StandaloneContext,
destinationIconPath: string
) {
if (process.platform !== 'darwin' && _resizeImageAsync === _resizeImageWithSipsAsync) {
console.warn('`sips` utility may or may not work outside of macOS');
}
let defaultIconFilename = 'exp-icon.png';
try {
await _saveDefaultIconToPathAsync(context, path.join(destinationIconPath, defaultIconFilename));
} catch (e) {
defaultIconFilename = null;
console.warn(e.message);
}
const iconSizes = [1024, 20, 29, 40, 60, 76, 83.5];
await Promise.all(
iconSizes.map(async iconSize => {
let iconResolutions;
if (iconSize === 76) {
// iPad has 1x and 2x icons for this size only
iconResolutions = [1, 2];
} else if (iconSize == 1024) {
// marketing icon is weird
iconResolutions = [1];
} else if (iconSize === 83.5) {
iconResolutions = [2];
} else {
iconResolutions = [2, 3];
}
// We need to wait for all of these to finish!
await Promise.all(
iconResolutions.map(async iconResolution => {
let iconQualifier = _getAppleIconQualifier(iconSize, iconResolution);
let iconKey = `iconUrl${iconQualifier}`;
let rawIconFilename;
let usesDefault = false;
if (context.type === 'service') {
// TODO(nikki): Support local paths for these icons
const manifest = context.data.manifest;
if (manifest.ios && manifest.ios.hasOwnProperty(iconKey)) {
// manifest specifies an image just for this size/resolution, use that
rawIconFilename = `exp-icon${iconQualifier}.png`;
await saveUrlToPathAsync(
manifest.ios[iconKey],
`${destinationIconPath}/${rawIconFilename}`
);
}
}
if (!rawIconFilename) {
// use default iconUrl
usesDefault = true;
if (defaultIconFilename) {
rawIconFilename = defaultIconFilename;
} else {
console.warn(
`Project does not specify ios.${iconKey} nor a default iconUrl. Bundle will use the Expo logo.`
);
return;
}
}
let iconFilename = `AppIcon${iconQualifier}.png`;
let iconSizePx = iconSize * iconResolution;
await spawnAsyncThrowError('/bin/cp', [rawIconFilename, iconFilename], {
stdio: 'inherit',
cwd: destinationIconPath,
});
try {
await _resizeImageAsync(iconSizePx, iconFilename, destinationIconPath);
} catch (e) {
throw new Error(`Failed to resize image: ${iconFilename}. (${e})`);
}
// reject non-square icons (because Apple will if we don't)
const dims = await getImageDimensionsMacOSAsync(destinationIconPath, iconFilename);
if (!dims || dims.length < 2 || dims[0] !== dims[1]) {
if (!dims) {
throw new Error(`Unable to read the dimensions of ${iconFilename}`);
} else {
throw new Error(
`iOS icons must be square, the dimensions of ${iconFilename} are ${dims}`
);
}
}
if (!usesDefault) {
// non-default icon used, clean up the downloaded version
await spawnAsyncThrowError('/bin/rm', [
path.join(destinationIconPath, rawIconFilename),
]);
}
})
);
})
);
// clean up default icon
if (defaultIconFilename) {
await spawnAsyncThrowError('/bin/rm', [path.join(destinationIconPath, defaultIconFilename)]);
}
return;
}
/**
* @return array [ width, height ] or null if that fails for some reason.
*/
async function getImageDimensionsMacOSAsync(
dirname: string,
basename: string
): Promise<?(number[])> {
if (process.platform !== 'darwin') {
console.warn('`sips` utility may or may not work outside of macOS');
}
let dimensions = null;
try {
dimensions = await _getImageDimensionsAsync(basename, dirname);
} catch (_) {}
return dimensions;
}
async function _resizeImageWithSipsAsync(
iconSizePx: number,
iconFilename: string,
destinationIconPath: string
) {
return spawnAsyncThrowError('sips', ['-Z', iconSizePx, iconFilename], {
stdio: ['ignore', 'ignore', 'inherit'], // only stderr
cwd: destinationIconPath,
});
}
async function _getImageDimensionsWithSipsAsync(
basename: string,
dirname: string
): Promise<number[]> {
let childProcess = await spawnAsyncThrowError(
'sips',
['-g', 'pixelWidth', '-g', 'pixelHeight', basename],
{
cwd: dirname,
}
);
// stdout looks something like 'pixelWidth: 1200\n pixelHeight: 800'
const components = childProcess.stdout.split(/(\s+)/);
return components.map(c => parseInt(c, 10)).filter(n => !isNaN(n));
}
// Allow us to swap out the default implementations of image functions
let _resizeImageAsync = _resizeImageWithSipsAsync;
let _getImageDimensionsAsync = _getImageDimensionsWithSipsAsync;
// Allow users to provide an alternate implementation for our image resize function.
// This is used internally in order to use sharp instead of sips in standalone builder.
function setResizeImageFunction(
fn: (iconSizePx: number, iconFilename: string, destinationIconPath: string) => Promise<any>
) {
_resizeImageAsync = fn;
}
// Allow users to provide an alternate implementation for our image dimensions function.
// This is used internally in order to use sharp instead of sips in standalone builder.
function setGetImageDimensionsFunction(
fn: (basename: string, dirname: string) => Promise<?(number[])>
) {
_getImageDimensionsAsync = fn;
}
export {
createAndWriteIconsToPathAsync,
getImageDimensionsMacOSAsync,
setResizeImageFunction,
setGetImageDimensionsFunction,
};