2015-10-21 07:15:57 +08:00
/ *
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 .
* /
2017-06-14 02:42:20 +08:00
var path = require ( 'path' ) ;
2020-01-29 09:12:55 +08:00
var fs = require ( 'fs-extra' ) ;
2021-07-13 17:01:50 +08:00
var utils = require ( './utils' ) ;
var check _reqs = require ( './check_reqs' ) ;
var ROOT = path . join ( _ _dirname , '..' ) ;
2021-07-06 14:38:28 +08:00
const { createEditor } = require ( 'properties-parser' ) ;
2015-10-21 07:15:57 +08:00
var CordovaError = require ( 'cordova-common' ) . CordovaError ;
2021-07-13 17:01:50 +08:00
var AndroidManifest = require ( './AndroidManifest' ) ;
2015-10-21 07:15:57 +08:00
2017-07-12 01:25:12 +08:00
// Export all helper functions, and make sure internally within this module, we
// reference these methods via the `exports` object - this helps with testing
// (since we can then mock and control behaviour of all of these functions)
exports . validatePackageName = validatePackageName ;
exports . validateProjectName = validateProjectName ;
exports . copyJsAndLibrary = copyJsAndLibrary ;
exports . copyScripts = copyScripts ;
exports . copyBuildRules = copyBuildRules ;
exports . writeProjectProperties = writeProjectProperties ;
exports . prepBuildFiles = prepBuildFiles ;
2021-03-27 21:06:26 +08:00
exports . writeNameForAndroidStudio = writeNameForAndroidStudio ;
2017-07-12 01:25:12 +08:00
2017-06-14 02:42:20 +08:00
function getFrameworkDir ( projectPath , shared ) {
2015-10-21 07:15:57 +08:00
return shared ? path . join ( ROOT , 'framework' ) : path . join ( projectPath , 'CordovaLib' ) ;
}
2021-07-06 14:38:28 +08:00
function copyJsAndLibrary ( projectPath , shared , projectName , targetAPI ) {
2015-10-21 07:15:57 +08:00
var nestedCordovaLibPath = getFrameworkDir ( projectPath , false ) ;
2021-07-13 17:01:50 +08:00
var srcCordovaJsPath = path . join ( ROOT , 'templates' , 'project' , 'assets' , 'www' , 'cordova.js' ) ;
2017-04-22 07:27:26 +08:00
var app _path = path . join ( projectPath , 'app' , 'src' , 'main' ) ;
2020-01-29 09:12:55 +08:00
const platform _www = path . join ( projectPath , 'platform_www' ) ;
2017-04-22 07:27:26 +08:00
2020-01-29 09:12:55 +08:00
fs . copySync ( srcCordovaJsPath , path . join ( app _path , 'assets' , 'www' , 'cordova.js' ) ) ;
2015-10-21 07:15:57 +08:00
// Copy the cordova.js file to platforms/<platform>/platform_www/
// The www dir is nuked on each prepare so we keep cordova.js in platform_www
2020-01-29 09:12:55 +08:00
fs . ensureDirSync ( platform _www ) ;
fs . copySync ( srcCordovaJsPath , path . join ( platform _www , 'cordova.js' ) ) ;
2015-10-21 07:15:57 +08:00
// Copy cordova-js-src directory into platform_www directory.
// We need these files to build cordova.js if using browserify method.
2020-01-29 09:12:55 +08:00
fs . copySync ( path . join ( ROOT , 'cordova-js-src' ) , path . join ( platform _www , 'cordova-js-src' ) ) ;
2015-10-21 07:15:57 +08:00
if ( shared ) {
var relativeFrameworkPath = path . relative ( projectPath , getFrameworkDir ( projectPath , true ) ) ;
fs . symlinkSync ( relativeFrameworkPath , nestedCordovaLibPath , 'dir' ) ;
} else {
2020-01-29 09:12:55 +08:00
fs . ensureDirSync ( nestedCordovaLibPath ) ;
fs . copySync ( path . join ( ROOT , 'framework' , 'AndroidManifest.xml' ) , path . join ( nestedCordovaLibPath , 'AndroidManifest.xml' ) ) ;
2021-07-06 14:38:28 +08:00
const propertiesEditor = createEditor ( path . join ( ROOT , 'framework' , 'project.properties' ) ) ;
propertiesEditor . set ( 'target' , targetAPI ) ;
propertiesEditor . save ( path . join ( nestedCordovaLibPath , 'project.properties' ) ) ;
2020-01-29 09:12:55 +08:00
fs . copySync ( path . join ( ROOT , 'framework' , 'build.gradle' ) , path . join ( nestedCordovaLibPath , 'build.gradle' ) ) ;
fs . copySync ( path . join ( ROOT , 'framework' , 'cordova.gradle' ) , path . join ( nestedCordovaLibPath , 'cordova.gradle' ) ) ;
2021-03-30 21:57:14 +08:00
fs . copySync ( path . join ( ROOT , 'framework' , 'repositories.gradle' ) , path . join ( nestedCordovaLibPath , 'repositories.gradle' ) ) ;
2020-01-29 09:12:55 +08:00
fs . copySync ( path . join ( ROOT , 'framework' , 'src' ) , path . join ( nestedCordovaLibPath , 'src' ) ) ;
2021-07-06 14:38:28 +08:00
fs . copySync ( path . join ( ROOT , 'framework' , 'cdv-gradle-config-defaults.json' ) , path . join ( projectPath , 'cdv-gradle-config.json' ) ) ;
2015-10-21 07:15:57 +08:00
}
}
2017-06-14 02:42:20 +08:00
function extractSubProjectPaths ( data ) {
2015-10-21 07:15:57 +08:00
var ret = { } ;
var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg ;
var m ;
while ( ( m = r . exec ( data ) ) ) {
ret [ m [ 1 ] ] = 1 ;
}
return Object . keys ( ret ) ;
}
2017-06-14 02:42:20 +08:00
function writeProjectProperties ( projectPath , target _api ) {
2015-10-21 07:15:57 +08:00
var dstPath = path . join ( projectPath , 'project.properties' ) ;
2021-07-13 17:01:50 +08:00
var templatePath = path . join ( ROOT , 'templates' , 'project' , 'project.properties' ) ;
2015-10-21 07:15:57 +08:00
var srcPath = fs . existsSync ( dstPath ) ? dstPath : templatePath ;
var data = fs . readFileSync ( srcPath , 'utf8' ) ;
data = data . replace ( /^target=.*/m , 'target=' + target _api ) ;
var subProjects = extractSubProjectPaths ( data ) ;
2017-06-14 02:42:20 +08:00
subProjects = subProjects . filter ( function ( p ) {
2015-10-21 07:15:57 +08:00
return ! ( /^CordovaLib$/m . exec ( p ) ||
2017-06-14 02:42:20 +08:00
/[\\/]cordova-android[\\/]framework$/m . exec ( p ) ||
/^(\.\.[\\/])+framework$/m . exec ( p ) ) ;
2015-10-21 07:15:57 +08:00
} ) ;
subProjects . unshift ( 'CordovaLib' ) ;
data = data . replace ( /^\s*android\.library\.reference\.\d+=.*\n/mg , '' ) ;
if ( ! /\n$/ . exec ( data ) ) {
data += '\n' ;
}
for ( var i = 0 ; i < subProjects . length ; ++ i ) {
2017-06-14 02:42:20 +08:00
data += 'android.library.reference.' + ( i + 1 ) + '=' + subProjects [ i ] + '\n' ;
2015-10-21 07:15:57 +08:00
}
fs . writeFileSync ( dstPath , data ) ;
}
2017-03-31 04:38:18 +08:00
// This makes no sense, what if you're building with a different build system?
2018-09-06 10:06:18 +08:00
function prepBuildFiles ( projectPath ) {
2021-07-13 17:01:50 +08:00
var buildModule = require ( './builders/builders' ) ;
2019-04-13 23:34:59 +08:00
buildModule . getBuilder ( projectPath ) . prepBuildFiles ( ) ;
2015-10-21 07:15:57 +08:00
}
2017-06-28 04:15:04 +08:00
function copyBuildRules ( projectPath , isLegacy ) {
2021-07-13 17:01:50 +08:00
var srcDir = path . join ( ROOT , 'templates' , 'project' ) ;
2015-10-21 07:15:57 +08:00
2017-06-28 04:15:04 +08:00
if ( isLegacy ) {
// The project's build.gradle is identical to the earlier build.gradle, so it should still work
2020-01-29 09:12:55 +08:00
fs . copySync ( path . join ( srcDir , 'legacy' , 'build.gradle' ) , path . join ( projectPath , 'legacy' , 'build.gradle' ) ) ;
fs . copySync ( path . join ( srcDir , 'wrapper.gradle' ) , path . join ( projectPath , 'wrapper.gradle' ) ) ;
2017-06-13 23:27:46 +08:00
} else {
2020-01-29 09:12:55 +08:00
fs . copySync ( path . join ( srcDir , 'build.gradle' ) , path . join ( projectPath , 'build.gradle' ) ) ;
fs . copySync ( path . join ( srcDir , 'app' , 'build.gradle' ) , path . join ( projectPath , 'app' , 'build.gradle' ) ) ;
2021-03-30 21:57:14 +08:00
fs . copySync ( path . join ( srcDir , 'app' , 'repositories.gradle' ) , path . join ( projectPath , 'app' , 'repositories.gradle' ) ) ;
fs . copySync ( path . join ( srcDir , 'repositories.gradle' ) , path . join ( projectPath , 'repositories.gradle' ) ) ;
2020-01-29 09:12:55 +08:00
fs . copySync ( path . join ( srcDir , 'wrapper.gradle' ) , path . join ( projectPath , 'wrapper.gradle' ) ) ;
2017-06-13 23:27:46 +08:00
}
2015-10-21 07:15:57 +08:00
}
2018-12-19 09:33:16 +08:00
function copyScripts ( projectPath ) {
2021-07-13 17:01:50 +08:00
var srcScriptsDir = path . join ( ROOT , 'templates' , 'cordova' ) ;
2015-10-21 07:15:57 +08:00
var destScriptsDir = path . join ( projectPath , 'cordova' ) ;
// Delete old scripts directory if this is an update.
2020-01-29 09:12:55 +08:00
fs . removeSync ( destScriptsDir ) ;
2015-10-21 07:15:57 +08:00
// Copy in the new ones.
2020-01-29 09:12:55 +08:00
fs . copySync ( srcScriptsDir , destScriptsDir ) ;
2015-10-21 07:15:57 +08:00
}
/ * *
* Test whether a package name is acceptable for use as an android project .
* Returns a promise , fulfilled if the package name is acceptable ; rejected
* otherwise .
* /
2017-06-14 02:42:20 +08:00
function validatePackageName ( package _name ) {
// Make the package conform to Java package types
// http://developer.android.com/guide/topics/manifest/manifest-element.html#package
// Enforce underscore limitation
2015-10-21 07:15:57 +08:00
var msg = 'Error validating package name. ' ;
2017-07-12 01:25:12 +08:00
2015-10-21 07:15:57 +08:00
if ( ! /^[a-zA-Z][a-zA-Z0-9_]+(\.[a-zA-Z][a-zA-Z0-9_]*)+$/ . test ( package _name ) ) {
2020-01-07 20:22:59 +08:00
return Promise . reject ( new CordovaError ( msg + 'Must look like: `com.company.Name`. Currently is: `' + package _name + '`' ) ) ;
2015-10-21 07:15:57 +08:00
}
2017-06-14 02:42:20 +08:00
// Class is a reserved word
if ( /\b[Cc]lass\b/ . test ( package _name ) ) {
2020-01-07 20:22:59 +08:00
return Promise . reject ( new CordovaError ( msg + '"class" is a reserved word' ) ) ;
2015-10-21 07:15:57 +08:00
}
2020-01-07 20:22:59 +08:00
return Promise . resolve ( ) ;
2015-10-21 07:15:57 +08:00
}
/ * *
2020-01-07 22:10:04 +08:00
* Test whether given string is acceptable for use as a project name
2015-10-21 07:15:57 +08:00
* Returns a promise , fulfilled if the project name is acceptable ; rejected
* otherwise .
* /
2017-06-14 02:42:20 +08:00
function validateProjectName ( project _name ) {
2015-10-21 07:15:57 +08:00
var msg = 'Error validating project name. ' ;
2017-06-14 02:42:20 +08:00
// Make sure there's something there
2015-10-21 07:15:57 +08:00
if ( project _name === '' ) {
2020-01-07 20:22:59 +08:00
return Promise . reject ( new CordovaError ( msg + 'Project name cannot be empty' ) ) ;
2015-10-21 07:15:57 +08:00
}
2020-01-07 20:22:59 +08:00
return Promise . resolve ( ) ;
2015-10-21 07:15:57 +08:00
}
2021-03-27 21:06:26 +08:00
/ * *
* Write the name of the app in "platforms/android/.idea/.name" so that Android Studio can show that name in the
* project listing . This is helpful to quickly look in the Android Studio listing if there are so many projects in
* Android Studio .
*
* https : //github.com/apache/cordova-android/issues/1172
* /
function writeNameForAndroidStudio ( project _path , project _name ) {
const ideaPath = path . join ( project _path , '.idea' ) ;
fs . ensureDirSync ( ideaPath ) ;
fs . writeFileSync ( path . join ( ideaPath , '.name' ) , project _name ) ;
}
2015-10-21 07:15:57 +08:00
/ * *
* Creates an android application with the given options .
*
* @ param { String } project _path Path to the new Cordova android project .
* @ param { ConfigParser } config Instance of ConfigParser to retrieve basic
* project properties .
* @ param { Object } [ options = { } ] Various options
* @ param { String } [ options . activityName = 'MainActivity' ] Name for the
* activity
* @ param { Boolean } [ options . link = false ] Specifies whether javascript files
* and CordovaLib framework will be symlinked to created application .
* @ param { String } [ options . customTemplate ] Path to project template
* ( override )
* @ param { EventEmitter } [ events ] An EventEmitter instance for logging
* events
*
* @ return { Promise < String > } Directory where application has been created
* /
2017-06-14 02:42:20 +08:00
exports . create = function ( project _path , config , options , events ) {
2015-10-21 07:15:57 +08:00
options = options || { } ;
// Set default values for path, package and name
project _path = path . relative ( process . cwd ( ) , ( project _path || 'CordovaExample' ) ) ;
// Check if project already exists
2017-06-14 02:42:20 +08:00
if ( fs . existsSync ( project _path ) ) {
2020-01-07 20:22:59 +08:00
return Promise . reject ( new CordovaError ( 'Project already exists! Delete and recreate' ) ) ;
2015-10-21 07:15:57 +08:00
}
2017-07-28 19:05:53 +08:00
var package _name = config . android _packageName ( ) || config . packageName ( ) || 'my.cordova.project' ;
2020-01-31 21:02:48 +08:00
var project _name = config . name ( )
? config . name ( ) . replace ( /[^\w.]/g , '_' ) : 'CordovaExample' ;
2015-10-21 07:15:57 +08:00
var safe _activity _name = config . android _activityName ( ) || options . activityName || 'MainActivity' ;
2021-07-13 14:51:20 +08:00
var target _api = check _reqs . get _target ( project _path ) ;
2015-10-21 07:15:57 +08:00
2017-06-14 02:42:20 +08:00
// Make the package conform to Java package types
2017-07-12 01:25:12 +08:00
return exports . validatePackageName ( package _name )
2017-06-14 02:42:20 +08:00
. then ( function ( ) {
2019-07-17 15:52:19 +08:00
return exports . validateProjectName ( project _name ) ;
2017-06-14 02:42:20 +08:00
} ) . then ( function ( ) {
2015-10-21 07:15:57 +08:00
// Log the given values for the project
2017-06-28 04:15:04 +08:00
events . emit ( 'log' , 'Creating Cordova project for the Android platform:' ) ;
events . emit ( 'log' , '\tPath: ' + project _path ) ;
events . emit ( 'log' , '\tPackage: ' + package _name ) ;
events . emit ( 'log' , '\tName: ' + project _name ) ;
events . emit ( 'log' , '\tActivity: ' + safe _activity _name ) ;
events . emit ( 'log' , '\tAndroid target: ' + target _api ) ;
events . emit ( 'verbose' , 'Copying android template project to ' + project _path ) ;
2021-07-13 17:01:50 +08:00
var project _template _dir = options . customTemplate || path . join ( ROOT , 'templates' , 'project' ) ;
2020-01-29 09:12:55 +08:00
var app _path = path . join ( project _path , 'app' , 'src' , 'main' ) ;
// copy project template
fs . ensureDirSync ( app _path ) ;
fs . copySync ( path . join ( project _template _dir , 'assets' ) , path . join ( app _path , 'assets' ) ) ;
fs . copySync ( path . join ( project _template _dir , 'res' ) , path . join ( app _path , 'res' ) ) ;
fs . copySync ( path . join ( project _template _dir , 'gitignore' ) , path . join ( project _path , '.gitignore' ) ) ;
// Manually create directories that would be empty within the template (since git doesn't track directories).
fs . ensureDirSync ( path . join ( app _path , 'libs' ) ) ;
// copy cordova.js, cordova.jar
2021-07-06 14:38:28 +08:00
exports . copyJsAndLibrary ( project _path , options . link , safe _activity _name , target _api ) ;
2020-01-29 09:12:55 +08:00
// Set up ther Android Studio paths
var java _path = path . join ( app _path , 'java' ) ;
var assets _path = path . join ( app _path , 'assets' ) ;
var resource _path = path . join ( app _path , 'res' ) ;
fs . ensureDirSync ( java _path ) ;
fs . ensureDirSync ( assets _path ) ;
fs . ensureDirSync ( resource _path ) ;
// interpolate the activity name and package
var packagePath = package _name . replace ( /\./g , path . sep ) ;
var activity _dir = path . join ( java _path , packagePath ) ;
var activity _path = path . join ( activity _dir , safe _activity _name + '.java' ) ;
fs . ensureDirSync ( activity _dir ) ;
fs . copySync ( path . join ( project _template _dir , 'Activity.java' ) , activity _path ) ;
utils . replaceFileContents ( activity _path , /__ACTIVITY__/ , safe _activity _name ) ;
utils . replaceFileContents ( path . join ( app _path , 'res' , 'values' , 'strings.xml' ) , /__NAME__/ , project _name ) ;
utils . replaceFileContents ( activity _path , /__ID__/ , package _name ) ;
var manifest = new AndroidManifest ( path . join ( project _template _dir , 'AndroidManifest.xml' ) ) ;
manifest . setPackageId ( package _name )
. getActivity ( ) . setName ( safe _activity _name ) ;
var manifest _path = path . join ( app _path , 'AndroidManifest.xml' ) ;
manifest . write ( manifest _path ) ;
exports . copyScripts ( project _path ) ;
exports . copyBuildRules ( project _path ) ;
2017-06-28 04:15:04 +08:00
// Link it to local android install.
2017-07-12 01:25:12 +08:00
exports . writeProjectProperties ( project _path , target _api ) ;
2018-09-06 10:06:18 +08:00
exports . prepBuildFiles ( project _path ) ;
2021-03-27 21:06:26 +08:00
exports . writeNameForAndroidStudio ( project _path , project _name ) ;
2017-06-28 04:15:04 +08:00
events . emit ( 'log' , generateDoneMessage ( 'create' , options . link ) ) ;
2020-01-07 20:22:59 +08:00
} ) . then ( ( ) => project _path ) ;
2015-10-21 07:15:57 +08:00
} ;
2017-06-14 02:42:20 +08:00
function generateDoneMessage ( type , link ) {
2021-07-13 17:01:50 +08:00
var pkg = require ( '../package' ) ;
2017-06-14 02:42:20 +08:00
var msg = 'Android project ' + ( type === 'update' ? 'updated ' : 'created ' ) + 'with ' + pkg . name + '@' + pkg . version ;
2015-10-21 07:15:57 +08:00
if ( link ) {
msg += ' and has a linked CordovaLib' ;
}
return msg ;
}
// Returns a promise.
2017-06-14 02:42:20 +08:00
exports . update = function ( projectPath , options , events ) {
2017-12-01 03:08:39 +08:00
var errorString =
'An in-place platform update is not supported. \n' +
'The `platforms` folder is always treated as a build artifact in the CLI workflow.\n' +
'To update your platform, you have to remove, then add your android platform again.\n' +
'Make sure you save your plugins beforehand using `cordova plugin save`, and save \n' + 'a copy of the platform first if you had manual changes in it.\n' +
'\tcordova plugin save\n' +
'\tcordova platform rm android\n' +
'\tcordova platform add android\n'
;
2017-12-01 02:40:07 +08:00
2020-01-07 20:22:59 +08:00
return Promise . reject ( errorString ) ;
2015-10-21 07:15:57 +08:00
} ;