GradleBuilder.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /*
  2. Licensed to the Apache Software Foundation (ASF) under one
  3. or more contributor license agreements. See the NOTICE file
  4. distributed with this work for additional information
  5. regarding copyright ownership. The ASF licenses this file
  6. to you under the Apache License, Version 2.0 (the
  7. "License"); you may not use this file except in compliance
  8. with the License. You may obtain a copy of the License at
  9. http://www.apache.org/licenses/LICENSE-2.0
  10. Unless required by applicable law or agreed to in writing,
  11. software distributed under the License is distributed on an
  12. "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  13. KIND, either express or implied. See the License for the
  14. specific language governing permissions and limitations
  15. under the License.
  16. */
  17. var Q = require('q');
  18. var fs = require('fs');
  19. var util = require('util');
  20. var path = require('path');
  21. var shell = require('shelljs');
  22. var superspawn = require('cordova-common').superspawn;
  23. var CordovaError = require('cordova-common').CordovaError;
  24. var check_reqs = require('../check_reqs');
  25. var GenericBuilder = require('./GenericBuilder');
  26. var MARKER = 'YOUR CHANGES WILL BE ERASED!';
  27. var SIGNING_PROPERTIES = '-signing.properties';
  28. var TEMPLATE =
  29. '# This file is automatically generated.\n' +
  30. '# Do not modify this file -- ' + MARKER + '\n';
  31. function GradleBuilder (projectRoot) {
  32. GenericBuilder.call(this, projectRoot);
  33. this.binDirs = { gradle: this.binDirs.gradle };
  34. }
  35. util.inherits(GradleBuilder, GenericBuilder);
  36. GradleBuilder.prototype.getArgs = function (cmd, opts) {
  37. if (cmd === 'release') {
  38. cmd = 'cdvBuildRelease';
  39. } else if (cmd === 'debug') {
  40. cmd = 'cdvBuildDebug';
  41. }
  42. var args = [cmd, '-b', path.join(this.root, 'build.gradle')];
  43. if (opts.arch) {
  44. args.push('-PcdvBuildArch=' + opts.arch);
  45. }
  46. // 10 seconds -> 6 seconds
  47. args.push('-Dorg.gradle.daemon=true');
  48. // to allow dex in process
  49. args.push('-Dorg.gradle.jvmargs=-Xmx2048m');
  50. // allow NDK to be used - required by Gradle 1.5 plugin
  51. args.push('-Pandroid.useDeprecatedNdk=true');
  52. args.push.apply(args, opts.extraArgs);
  53. // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
  54. // args.push('-Dorg.gradle.parallel=true');
  55. return args;
  56. };
  57. /*
  58. * This returns a promise
  59. */
  60. GradleBuilder.prototype.runGradleWrapper = function (gradle_cmd, gradle_file) {
  61. var gradlePath = path.join(this.root, 'gradlew');
  62. gradle_file = path.join(this.root, (gradle_file || 'wrapper.gradle'));
  63. if (fs.existsSync(gradlePath)) {
  64. // Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows
  65. } else {
  66. return superspawn.spawn(gradle_cmd, ['-p', this.root, 'wrapper', '-b', gradle_file], { stdio: 'pipe' })
  67. .progress(function (stdio) {
  68. suppressJavaOptionsInfo(stdio);
  69. });
  70. }
  71. };
  72. // Makes the project buildable, minus the gradle wrapper.
  73. GradleBuilder.prototype.prepBuildFiles = function () {
  74. // Update the version of build.gradle in each dependent library.
  75. var pluginBuildGradle = path.join(this.root, 'cordova', 'lib', 'plugin-build.gradle');
  76. var propertiesObj = this.readProjectProperties();
  77. var subProjects = propertiesObj.libs;
  78. var checkAndCopy = function (subProject, root) {
  79. var subProjectGradle = path.join(root, subProject, 'build.gradle');
  80. // This is the future-proof way of checking if a file exists
  81. // This must be synchronous to satisfy a Travis test
  82. try {
  83. fs.accessSync(subProjectGradle, fs.F_OK);
  84. } catch (e) {
  85. shell.cp('-f', pluginBuildGradle, subProjectGradle);
  86. }
  87. };
  88. for (var i = 0; i < subProjects.length; ++i) {
  89. if (subProjects[i] !== 'CordovaLib') {
  90. checkAndCopy(subProjects[i], this.root);
  91. }
  92. }
  93. var name = this.extractRealProjectNameFromManifest();
  94. // Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
  95. var settingsGradlePaths = subProjects.map(function (p) {
  96. var realDir = p.replace(/[/\\]/g, ':');
  97. var libName = realDir.replace(name + '-', '');
  98. var str = 'include ":' + libName + '"\n';
  99. if (realDir.indexOf(name + '-') !== -1) { str += 'project(":' + libName + '").projectDir = new File("' + p + '")\n'; }
  100. return str;
  101. });
  102. // Write the settings.gradle file.
  103. fs.writeFileSync(path.join(this.root, 'settings.gradle'),
  104. '// GENERATED FILE - DO NOT EDIT\n' +
  105. 'include ":"\n' + settingsGradlePaths.join(''));
  106. // Update dependencies within build.gradle.
  107. var buildGradle = fs.readFileSync(path.join(this.root, 'build.gradle'), 'utf8');
  108. var depsList = '';
  109. var root = this.root;
  110. var insertExclude = function (p) {
  111. var gradlePath = path.join(root, p, 'build.gradle');
  112. var projectGradleFile = fs.readFileSync(gradlePath, 'utf-8');
  113. if (projectGradleFile.indexOf('CordovaLib') !== -1) {
  114. depsList += '{\n exclude module:("CordovaLib")\n }\n';
  115. } else {
  116. depsList += '\n';
  117. }
  118. };
  119. subProjects.forEach(function (p) {
  120. console.log('Subproject Path: ' + p);
  121. var libName = p.replace(/[/\\]/g, ':').replace(name + '-', '');
  122. depsList += ' debugCompile(project(path: "' + libName + '", configuration: "debug"))';
  123. insertExclude(p);
  124. depsList += ' releaseCompile(project(path: "' + libName + '", configuration: "release"))';
  125. insertExclude(p);
  126. });
  127. // For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
  128. var SYSTEM_LIBRARY_MAPPINGS = [
  129. [/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
  130. [/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
  131. ];
  132. propertiesObj.systemLibs.forEach(function (p) {
  133. var mavenRef;
  134. // It's already in gradle form if it has two ':'s
  135. if (/:.*:/.exec(p)) {
  136. mavenRef = p;
  137. } else {
  138. for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
  139. var pair = SYSTEM_LIBRARY_MAPPINGS[i];
  140. if (pair[0].exec(p)) {
  141. mavenRef = p.replace(pair[0], pair[1]);
  142. break;
  143. }
  144. }
  145. if (!mavenRef) {
  146. throw new CordovaError('Unsupported system library (does not work with gradle): ' + p);
  147. }
  148. }
  149. depsList += ' compile "' + mavenRef + '"\n';
  150. });
  151. buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
  152. var includeList = '';
  153. propertiesObj.gradleIncludes.forEach(function (includePath) {
  154. includeList += 'apply from: "' + includePath + '"\n';
  155. });
  156. buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2');
  157. fs.writeFileSync(path.join(this.root, 'build.gradle'), buildGradle);
  158. };
  159. GradleBuilder.prototype.prepEnv = function (opts) {
  160. var self = this;
  161. return check_reqs.check_gradle().then(function (gradlePath) {
  162. return self.runGradleWrapper(gradlePath);
  163. }).then(function () {
  164. return self.prepBuildFiles();
  165. }).then(function () {
  166. // We now copy the gradle out of the framework
  167. // This is a dirty patch to get the build working
  168. /*
  169. var wrapperDir = path.join(self.root, 'CordovaLib');
  170. if (process.platform == 'win32') {
  171. shell.rm('-f', path.join(self.root, 'gradlew.bat'));
  172. shell.cp(path.join(wrapperDir, 'gradlew.bat'), self.root);
  173. } else {
  174. shell.rm('-f', path.join(self.root, 'gradlew'));
  175. shell.cp(path.join(wrapperDir, 'gradlew'), self.root);
  176. }
  177. shell.rm('-rf', path.join(self.root, 'gradle', 'wrapper'));
  178. shell.mkdir('-p', path.join(self.root, 'gradle'));
  179. shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(self.root, 'gradle'));
  180. */
  181. // If the gradle distribution URL is set, make sure it points to version we want.
  182. // If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
  183. // For some reason, using ^ and $ don't work. This does the job, though.
  184. var distributionUrlRegex = /distributionUrl.*zip/;
  185. /* jshint -W069 */
  186. var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-3.3-all.zip';
  187. /* jshint +W069 */
  188. var gradleWrapperPropertiesPath = path.join(self.root, 'gradle', 'wrapper', 'gradle-wrapper.properties');
  189. shell.chmod('u+w', gradleWrapperPropertiesPath);
  190. shell.sed('-i', distributionUrlRegex, 'distributionUrl=' + distributionUrl, gradleWrapperPropertiesPath);
  191. var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
  192. var propertiesFilePath = path.join(self.root, propertiesFile);
  193. if (opts.packageInfo) {
  194. fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
  195. } else if (isAutoGenerated(propertiesFilePath)) {
  196. shell.rm('-f', propertiesFilePath);
  197. }
  198. });
  199. };
  200. /*
  201. * Builds the project with gradle.
  202. * Returns a promise.
  203. */
  204. GradleBuilder.prototype.build = function (opts) {
  205. var wrapper = path.join(this.root, 'gradlew');
  206. var args = this.getArgs(opts.buildType === 'debug' ? 'debug' : 'release', opts);
  207. return superspawn.spawn(wrapper, args, { stdio: 'pipe' })
  208. .progress(function (stdio) {
  209. suppressJavaOptionsInfo(stdio);
  210. }).catch(function (error) {
  211. if (error.toString().indexOf('failed to find target with hash string') >= 0) {
  212. return check_reqs.check_android_target(error).then(function () {
  213. // If due to some odd reason - check_android_target succeeds
  214. // we should still fail here.
  215. return Q.reject(error);
  216. });
  217. }
  218. return Q.reject(error);
  219. });
  220. };
  221. GradleBuilder.prototype.clean = function (opts) {
  222. var builder = this;
  223. var wrapper = path.join(this.root, 'gradlew');
  224. var args = builder.getArgs('clean', opts);
  225. return Q().then(function () {
  226. return superspawn.spawn(wrapper, args, { stdio: 'inherit' });
  227. }).then(function () {
  228. shell.rm('-rf', path.join(builder.root, 'out'));
  229. ['debug', 'release'].forEach(function (config) {
  230. var propertiesFilePath = path.join(builder.root, config + SIGNING_PROPERTIES);
  231. if (isAutoGenerated(propertiesFilePath)) {
  232. shell.rm('-f', propertiesFilePath);
  233. }
  234. });
  235. });
  236. };
  237. module.exports = GradleBuilder;
  238. function suppressJavaOptionsInfo (stdio) {
  239. if (stdio.stderr) {
  240. /*
  241. * Workaround for the issue with Java printing some unwanted information to
  242. * stderr instead of stdout.
  243. * This function suppresses 'Picked up _JAVA_OPTIONS' message from being
  244. * printed to stderr. See https://issues.apache.org/jira/browse/CB-9971 for
  245. * explanation.
  246. */
  247. var suppressThisLine = /^Picked up _JAVA_OPTIONS: /i.test(stdio.stderr.toString());
  248. if (suppressThisLine) {
  249. return;
  250. }
  251. process.stderr.write(stdio.stderr);
  252. } else {
  253. process.stdout.write(stdio.stdout);
  254. }
  255. }
  256. function isAutoGenerated (file) {
  257. return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
  258. }