/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /* eslint no-self-assign: 0 */ /* eslint no-unused-vars: 0 */ var Q = require('q'); var fs = require('fs'); var path = require('path'); var shell = require('shelljs'); var spawn = require('cordova-common').superspawn.spawn; var events = require('cordova-common').events; var CordovaError = require('cordova-common').CordovaError; var check_reqs = require('../check_reqs'); var PackageType = require('../PackageType'); const compareFunc = require('compare-func'); const MARKER = 'YOUR CHANGES WILL BE ERASED!'; const SIGNING_PROPERTIES = '-signing.properties'; const TEMPLATE = '# This file is automatically generated.\n' + '# Do not modify this file -- ' + MARKER + '\n'; class ProjectBuilder { constructor (rootDirectory) { this.root = rootDirectory || path.resolve(__dirname, '../../..'); this.apkDir = path.join(this.root, 'app', 'build', 'outputs', 'apk'); this.aabDir = path.join(this.root, 'app', 'build', 'outputs', 'bundle'); } getArgs (cmd, opts) { let args; let buildCmd = cmd; if (opts.packageType === PackageType.BUNDLE) { if (cmd === 'release') { buildCmd = ':app:bundleRelease'; } else if (cmd === 'debug') { buildCmd = ':app:bundleDebug'; } args = [buildCmd, '-b', path.join(this.root, 'build.gradle')]; } else { if (cmd === 'release') { buildCmd = 'cdvBuildRelease'; } else if (cmd === 'debug') { buildCmd = 'cdvBuildDebug'; } args = [buildCmd, '-b', path.join(this.root, 'build.gradle')]; if (opts.arch) { args.push('-PcdvBuildArch=' + opts.arch); } args.push.apply(args, opts.extraArgs); } return args; } /* * This returns a promise */ runGradleWrapper (gradle_cmd) { var gradlePath = path.join(this.root, 'gradlew'); var wrapperGradle = path.join(this.root, 'wrapper.gradle'); if (fs.existsSync(gradlePath)) { // Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows } else { return spawn(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], { stdio: 'inherit' }); } } readProjectProperties () { function findAllUniq (data, r) { var s = {}; var m; while ((m = r.exec(data))) { s[m[1]] = 1; } return Object.keys(s); } var data = fs.readFileSync(path.join(this.root, 'project.properties'), 'utf8'); return { libs: findAllUniq(data, /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg), gradleIncludes: findAllUniq(data, /^\s*cordova\.gradle\.include\.\d+=(.*)(?:\s|$)/mg), systemLibs: findAllUniq(data, /^\s*cordova\.system\.library\.\d+=(.*)(?:\s|$)/mg) }; } extractRealProjectNameFromManifest () { var manifestPath = path.join(this.root, 'app', 'src', 'main', 'AndroidManifest.xml'); var manifestData = fs.readFileSync(manifestPath, 'utf8'); var m = /= 0) { return check_reqs.check_android_target(error).then(function () { // If due to some odd reason - check_android_target succeeds // we should still fail here. return Q.reject(error); }); } return Q.reject(error); }); } clean (opts) { var builder = this; var wrapper = path.join(this.root, 'gradlew'); var args = builder.getArgs('clean', opts); return Q().then(function () { return spawn(wrapper, args, { stdio: 'inherit' }); }) .then(function () { shell.rm('-rf', path.join(builder.root, 'out')); ['debug', 'release'].forEach(function (config) { var propertiesFilePath = path.join(builder.root, config + SIGNING_PROPERTIES); if (isAutoGenerated(propertiesFilePath)) { shell.rm('-f', propertiesFilePath); } }); }); } findOutputApks (build_type, arch) { return findOutputApksHelper(this.apkDir, build_type, arch).sort(apkSorter); } findOutputBundles (build_type) { return findOutputBundlesHelper(this.aabDir, build_type); } fetchBuildResults (build_type, arch) { return { apkPaths: this.findOutputApks(build_type, arch), buildType: build_type }; } } module.exports = ProjectBuilder; const apkSorter = compareFunc([ // Sort arch specific builds after generic ones apkPath => /-x86|-arm/.test(apkPath), // Sort unsigned builds after signed ones apkPath => /-unsigned/.test(apkPath), // Sort by file modification time, latest first apkPath => -fs.statSync(apkPath).mtime.getTime(), // Sort by file name length, ascending 'length' ]); function findOutputApksHelper (dir, build_type, arch) { var shellSilent = shell.config.silent; shell.config.silent = true; // list directory recursively var ret = shell.ls('-R', dir).map(function (file) { // ls does not include base directory return path.join(dir, file); }).filter(function (file) { // find all APKs return file.match(/\.apk?$/i); }).filter(function (candidate) { var apkName = path.basename(candidate); // Need to choose between release and debug .apk. if (build_type === 'debug') { return /-debug/.exec(apkName) && !/-unaligned|-unsigned/.exec(apkName); } if (build_type === 'release') { return /-release/.exec(apkName) && !/-unaligned/.exec(apkName); } return true; }).sort(apkSorter); shellSilent = shellSilent; if (ret.length === 0) { return ret; } // Assume arch-specific build if newest apk has -x86 or -arm. var archSpecific = !!/-x86|-arm/.exec(path.basename(ret[0])); // And show only arch-specific ones (or non-arch-specific) ret = ret.filter(function (p) { return !!/-x86|-arm/.exec(path.basename(p)) === archSpecific; }); if (archSpecific && ret.length > 1 && arch) { ret = ret.filter(function (p) { return path.basename(p).indexOf('-' + arch) !== -1; }); } return ret; } // This method was a copy of findOutputApksHelper and modified to look for bundles // While replacing shell with fs-extra, it might be a good idea to see if we can // generalise these findOutput methods. function findOutputBundlesHelper (dir, build_type) { // This is an unused variable that was copied from findOutputApksHelper // we are pretty sure it was meant to reset shell.config.silent back to // the original value. However shell is planned to be replaced, // it was left as is to avoid unintended consequences. const shellSilent = shell.config.silent; shell.config.silent = true; // list directory recursively const ret = shell.ls('-R', dir).map(function (file) { return path.join(dir, file); // ls does not include base directory }).filter(function (file) { return file.match(/\.aab?$/i); // find all bundles }).filter(function (candidate) { // Need to choose between release and debug bundle. if (build_type === 'debug') { return /debug/.exec(candidate); } if (build_type === 'release') { return /release/.exec(candidate); } return true; }); return ret; } function isAutoGenerated (file) { return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0; }