Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TIMOB-26079] Continue build on invalid Android library ABI #10084

Merged
merged 10 commits into from
Jun 30, 2018
317 changes: 154 additions & 163 deletions android/cli/commands/_build.js
Original file line number Diff line number Diff line change
Expand Up @@ -4393,200 +4393,191 @@ AndroidBuilder.prototype.createUnsignedApk = function createUnsignedApk(next) {
soRegExp = /\.so$/,
trailingSlashRegExp = /\/$/,
nativeLibs = {},
origConsoleError = console.error,
entryNames = [];

// since the archiver library didn't set max listeners, we squelch all error output
console.error = function () {};
fs.existsSync(this.unsignedApkFile) && fs.unlinkSync(this.unsignedApkFile);
const apkStream = fs.createWriteStream(this.unsignedApkFile);
apkStream.on('close', function () {
next();
});
dest.catchEarlyExitAttached = true; // silence exceptions
dest.pipe(apkStream);

this.logger.info(__('Creating unsigned apk'));

// merge files from the app.ap_ file as well as all titanium and 3rd party jar files
const archives = [this.ap_File].concat(Object.keys(this.moduleJars)).concat(Object.keys(this.jarLibraries));

archives.forEach(function (file) {
const src = new AdmZip(file),
entries = src.getEntries();

this.logger.debug(__('Processing %s', file.cyan));

entries.forEach(function (entry) {
if (entry.entryName.indexOf('META-INF/') === -1
&& (entry.entryName.indexOf('org/appcelerator/titanium/bindings/') === -1 || !jsonRegExp.test(entry.name))
&& entry.name.charAt(0) !== '.'
&& !classRegExp.test(entry.name)
&& !trailingSlashRegExp.test(entry.entryName)
) {
// do not add duplicate entries
if (entryNames.indexOf(entry.entryName) > -1) {
this.logger.warn(__('Removing duplicate entry %s', entry.entryName.cyan));
return;
}

try {
fs.existsSync(this.unsignedApkFile) && fs.unlinkSync(this.unsignedApkFile);
const apkStream = fs.createWriteStream(this.unsignedApkFile);
apkStream.on('close', function () {
console.error = origConsoleError;
next();
});
dest.catchEarlyExitAttached = true; // silence exceptions
dest.pipe(apkStream);

this.logger.info(__('Creating unsigned apk'));

// merge files from the app.ap_ file as well as all titanium and 3rd party jar files
const archives = [ this.ap_File ].concat(Object.keys(this.moduleJars)).concat(Object.keys(this.jarLibraries));

archives.forEach(function (file) {
const src = new AdmZip(file),
entries = src.getEntries();

this.logger.debug(__('Processing %s', file.cyan));

entries.forEach(function (entry) {
if (entry.entryName.indexOf('META-INF/') === -1
&& (entry.entryName.indexOf('org/appcelerator/titanium/bindings/') === -1 || !jsonRegExp.test(entry.name))
&& entry.name.charAt(0) !== '.'
&& !classRegExp.test(entry.name)
&& !trailingSlashRegExp.test(entry.entryName)
) {
// do not add duplicate entries
if (entryNames.indexOf(entry.entryName) > -1) {
this.logger.warn(__('Removing duplicate entry %s', entry.entryName.cyan));
return;
}
const store = this.uncompressedTypes.indexOf(entry.entryName.split('.').pop()) !== -1;

const store = this.uncompressedTypes.indexOf(entry.entryName.split('.').pop()) !== -1;
this.logger.debug(store
? __('Adding %s', entry.entryName.cyan)
: __('Deflating %s', entry.entryName.cyan));

this.logger.debug(store
? __('Adding %s', entry.entryName.cyan)
: __('Deflating %s', entry.entryName.cyan));
dest.append(src.readFile(entry), {
name: entry.entryName,
store: store
});
entryNames.push(entry.entryName);
}
}, this);
}, this);

dest.append(src.readFile(entry), {
name: entry.entryName,
store: store
});
entryNames.push(entry.entryName);
this.logger.debug('test3');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this straggler debug log, please


// Add dex files
this.logger.info(__('Processing %s', this.buildBinClassesDex.cyan));
fs.readdirSync(this.buildBinClassesDex).forEach(function (name) {
var file = path.join(this.buildBinClassesDex, name);
if (dexRegExp.test(name)) {
this.logger.debug(__('Adding %s', name.cyan));
dest.append(fs.createReadStream(file), { name: name });
}
}, this);

this.logger.info(__('Processing %s', this.buildSrcDir.cyan));
(function copyDir(dir, base) {
base = base || dir;
fs.readdirSync(dir).forEach(function (name) {
var file = path.join(dir, name);
if (fs.existsSync(file)) {
if (fs.statSync(file).isDirectory()) {
copyDir(file, base);
} else if (!javaRegExp.test(name)) {
name = file.replace(base, '').replace(/^[/\\]/, '');
this.logger.debug(__('Adding %s', name.cyan));
dest.append(fs.createReadStream(file), { name: name });
}
}, this);
}
}, this);
}.call(this, this.buildSrcDir));

const addNativeLibs = function (dir) {
if (!fs.existsSync(dir)) {
return;
}

for (let i = 0; i < this.abis.length; i++) {
const abiDir = path.join(dir, this.abis[i]);

// Add dex files
this.logger.info(__('Processing %s', this.buildBinClassesDex.cyan));
fs.readdirSync(this.buildBinClassesDex).forEach(function (name) {
var file = path.join(this.buildBinClassesDex, name);
if (dexRegExp.test(name)) {
this.logger.debug(__('Adding %s', name.cyan));
dest.append(fs.createReadStream(file), { name: name });
// check that we found the desired abi, otherwise we abort the build
if (!fs.existsSync(abiDir) || !fs.statSync(abiDir).isDirectory()) {
throw this.abis[i];
}
}, this);

this.logger.info(__('Processing %s', this.buildSrcDir.cyan));
(function copyDir(dir, base) {
base = base || dir;
fs.readdirSync(dir).forEach(function (name) {
var file = path.join(dir, name);
if (fs.existsSync(file)) {
if (fs.statSync(file).isDirectory()) {
copyDir(file, base);
} else if (!javaRegExp.test(name)) {
name = file.replace(base, '').replace(/^[/\\]/, '');
this.logger.debug(__('Adding %s', name.cyan));
dest.append(fs.createReadStream(file), { name: name });
// copy all the .so files into the archive
fs.readdirSync(abiDir).forEach(function (name) {
if (name !== 'libtiprofiler.so' || (this.allowProfiling && this.profilerPort)) {
const file = path.join(abiDir, name),
rel = 'lib/' + this.abis[i] + '/' + name;
if (!nativeLibs[rel] && soRegExp.test(name) && fs.existsSync(file)) {
nativeLibs[rel] = 1;
this.logger.debug(__('Adding %s', rel.cyan));
dest.append(fs.createReadStream(file), { name: rel });
}
}
}, this);
}.call(this, this.buildSrcDir));
}
}.bind(this);

const addNativeLibs = function (dir) {
if (!fs.existsSync(dir)) {
return;
try {
// add Titanium native modules
addNativeLibs(path.join(this.platformPath, 'native', 'libs'));
} catch (abi) {
// this should never be called since we already validated this
const abis = [];
fs.readdirSync(path.join(this.platformPath, 'native', 'libs')).forEach(function (abi) {
var dir = path.join(this.platformPath, 'native', 'libs', abi);
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
abis.push(abi);
}
});
this.logger.error(__('Invalid native Titanium library ABI "%s"', abi));
this.logger.error(__('Supported ABIs: %s', abis.join(', ')) + '\n');
process.exit(1);
}

for (let i = 0; i < this.abis.length; i++) {
const abiDir = path.join(dir, this.abis[i]);

// check that we found the desired abi, otherwise we abort the build
if (!fs.existsSync(abiDir) || !fs.statSync(abiDir).isDirectory()) {
throw this.abis[i];
}
try {
// add native modules from the build dir's "libs" dir
addNativeLibs(path.join(this.buildDir, 'libs'));
} catch (e) {
// ignore
}

// copy all the .so files into the archive
fs.readdirSync(abiDir).forEach(function (name) {
if (name !== 'libtiprofiler.so' || (this.allowProfiling && this.profilerPort)) {
const file = path.join(abiDir, name),
rel = 'lib/' + this.abis[i] + '/' + name;
if (!nativeLibs[rel] && soRegExp.test(name) && fs.existsSync(file)) {
nativeLibs[rel] = 1;
this.logger.debug(__('Adding %s', rel.cyan));
dest.append(fs.createReadStream(file), { name: rel });
}
this.modules.forEach(function (m) {
if (m.native) {
try {
// add native modules for each module
addNativeLibs(path.join(m.modulePath, 'libs'));
} catch (abi) {
// this should never be called since we already validated this
const abis = [];
fs.readdirSync(path.join(m.modulePath, 'libs')).forEach(function (abi) {
var dir = path.join(m.modulePath, 'libs', abi);
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
abis.push(abi);
}
}, this);
});
/* commenting this out to preserve the old, incorrect behavior
this.logger.error(__('The module "%s" does not support the ABI "%s"', m.id, abi));
this.logger.error(__('Supported ABIs: %s', abis.join(', ')) + '\n');
process.exit(1);
*/
this.logger.warn(__('The module %s does not support the ABI: %s', m.id.cyan, abi.cyan));
this.logger.warn(__('It only supports the following ABIs: %s', abis.map(function (a) { return a.cyan; }).join(', '))); // eslint-disable-line max-statements-per-line
this.logger.warn(__('Your application will most likely encounter issues'));
}
}.bind(this);
}
}, this);

this.androidLibraries.forEach(function (libraryInfo) {
if (libraryInfo.nativeLibraries.length === 0) {
return;
}

const libraryJniPath = path.join(libraryInfo.explodedPath, 'jni');
try {
// add Titanium native modules
addNativeLibs(path.join(this.platformPath, 'native', 'libs'));
addNativeLibs(libraryJniPath);
} catch (abi) {
// this should never be called since we already validated this
const abis = [];
fs.readdirSync(path.join(this.platformPath, 'native', 'libs')).forEach(function (abi) {
var dir = path.join(this.platformPath, 'native', 'libs', abi);
fs.readdirSync(libraryJniPath).forEach(function (abi) {
const dir = path.join(libraryJniPath, abi);
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
abis.push(abi);
}
});
this.logger.error(__('Invalid native Titanium library ABI "%s"', abi));
this.logger.error(__('Supported ABIs: %s', abis.join(', ')) + '\n');
process.exit(1);
}

try {
// add native modules from the build dir's "libs" dir
addNativeLibs(path.join(this.buildDir, 'libs'));
} catch (e) {
// ignore
}

this.modules.forEach(function (m) {
if (m.native) {
try {
// add native modules for each module
addNativeLibs(path.join(m.modulePath, 'libs'));
} catch (abi) {
// this should never be called since we already validated this
const abis = [];
fs.readdirSync(path.join(m.modulePath, 'libs')).forEach(function (abi) {
var dir = path.join(m.modulePath, 'libs', abi);
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
abis.push(abi);
}
});
/* commenting this out to preserve the old, incorrect behavior
this.logger.error(__('The module "%s" does not support the ABI "%s"', m.id, abi));
this.logger.error(__('Supported ABIs: %s', abis.join(', ')) + '\n');
process.exit(1);
*/
this.logger.warn(__('The module %s does not support the ABI: %s', m.id.cyan, abi.cyan));
this.logger.warn(__('It only supports the following ABIs: %s', abis.map(function (a) { return a.cyan; }).join(', '))); // eslint-disable-line max-statements-per-line
this.logger.warn(__('Your application will most likely encounter issues'));
}
}
}, this);

this.androidLibraries.forEach(function (libraryInfo) {
if (libraryInfo.nativeLibraries.length === 0) {
return;
if (libraryInfo.task.originType === 'Module') {
this.logger.warn(__('The Android Library "%s" from module "%s" does not support the ABI: %s', libraryInfo.packageName.cyan, libraryInfo.task.moduleInfo.id.cyan, abi.cyan));
} else if (libraryInfo.task.originType === 'Project') {
this.logger.warn(__('The Android Library "%s" does not support the ABI: %s', libraryInfo.packageName.cyan, abi.cyan));
}
this.logger.warn(__('It only supports the following ABIs: %s', abis.map(function (a) { return a.cyan; }).join(', '))); // eslint-disable-line max-statements-per-line
this.logger.warn(__('Your application will most likely encounter issues'));
}
}, this);

const libraryJniPath = path.join(libraryInfo.explodedPath, 'jni');
try {
addNativeLibs(libraryJniPath);
} catch (e) {
const abis = [];
fs.readdirSync(libraryJniPath).forEach(function (abi) {
const dir = path.join(libraryJniPath, abi);
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
abis.push(abi);
}
});
// FIXME This validation code isn't right. Shoudl likley be in the function above!
// if (libraryInfo.task.originType === 'Module') {
// this.logger.error(__('The Android Library "%s" from module "%s" does not support the ABI: %s', libraryInfo.packageName, libraryInfo.task.moduleInfo.id, abi));
// } else if (libraryInfo.task.originType === 'Project') {
// this.logger.error(__('The Android Library "%s" does not support the ABI: %s', libraryInfo.packageName, abi));
// }
this.logger.error(__('Supported ABIs by the Android Library: %s', abis.join(', ')));
this.logger.error(__('Valid ABIs for this project: %s', this.abis(', ')));
process.exit(1);
}
}, this);

this.logger.info(__('Writing unsigned apk: %s', this.unsignedApkFile.cyan));
dest.finalize();
} catch (ex) {
console.error = origConsoleError;
throw ex;
}
this.logger.info(__('Writing unsigned apk: %s', this.unsignedApkFile.cyan));
dest.finalize();
};

AndroidBuilder.prototype.createSignedApk = function createSignedApk(next) {
Expand Down