1+ ///<reference path="../.d.ts"/>
2+ import path = require( "path" ) ;
3+ import shell = require( "shelljs" ) ;
4+ import util = require( "util" ) ;
5+ import options = require( "./../options" ) ;
6+ import helpers = require( "./../common/helpers" ) ;
7+ import constants = require( "./../constants" ) ;
8+
9+ class AndroidProjectService implements IPlatformProjectService {
10+ constructor ( private $fs : IFileSystem ,
11+ private $errors : IErrors ,
12+ private $logger : ILogger ,
13+ private $childProcess : IChildProcess ,
14+ private $projectData : IProjectData ,
15+ private $propertiesParser : IPropertiesParser ) { }
16+
17+ public validate ( ) : IFuture < void > {
18+ return ( ( ) => {
19+ this . validatePackageName ( this . $projectData . projectId ) ;
20+ this . validateProjectName ( this . $projectData . projectName ) ;
21+
22+ this . checkAnt ( ) . wait ( ) && this . checkAndroid ( ) . wait ( ) && this . checkJava ( ) . wait ( ) ;
23+ } ) . future < void > ( ) ( ) ;
24+ }
25+
26+ public createProject ( projectRoot : string , frameworkDir : string ) : IFuture < void > {
27+ return ( ( ) => {
28+ this . validateAndroidTarget ( frameworkDir ) ; // We need framework to be installed to validate android target so we can't call this method in validate()
29+
30+ var paths = "assets gen libs res" . split ( ' ' ) . map ( p => path . join ( frameworkDir , p ) ) ;
31+ shell . cp ( "-r" , paths , projectRoot ) ;
32+
33+ paths = ".project AndroidManifest.xml project.properties" . split ( ' ' ) . map ( p => path . join ( frameworkDir , p ) ) ;
34+ shell . cp ( "-f" , paths , projectRoot ) ;
35+
36+ // Create src folder
37+ var packageName = this . $projectData . projectId ;
38+ var packageAsPath = packageName . replace ( / \. / g, path . sep ) ;
39+ var activityDir = path . join ( projectRoot , 'src' , packageAsPath ) ;
40+ this . $fs . createDirectory ( activityDir ) . wait ( ) ;
41+
42+ } ) . future < any > ( ) ( ) ;
43+ }
44+
45+ public interpolateData ( projectRoot : string ) : void {
46+ // Interpolate the activity name and package
47+ var stringsFilePath = path . join ( projectRoot , 'res' , 'values' , 'strings.xml' ) ;
48+ shell . sed ( '-i' , / _ _ N A M E _ _ / , this . $projectData . projectName , stringsFilePath ) ;
49+ shell . sed ( '-i' , / _ _ T I T L E _ A C T I V I T Y _ _ / , this . $projectData . projectName , stringsFilePath ) ;
50+ shell . sed ( '-i' , / _ _ N A M E _ _ / , this . $projectData . projectName , path . join ( projectRoot , '.project' ) ) ;
51+ shell . sed ( '-i' , / _ _ P A C K A G E _ _ / , this . $projectData . projectId , path . join ( projectRoot , "AndroidManifest.xml" ) ) ;
52+ }
53+
54+ public afterCreateProject ( projectRoot : string ) {
55+ var targetApi = this . getTarget ( projectRoot ) . wait ( ) ;
56+ this . $logger . trace ( "Android target: %s" , targetApi ) ;
57+ this . runAndroidUpdate ( projectRoot , targetApi ) . wait ( ) ;
58+ }
59+
60+ public prepareProject ( normalizedPlatformName : string , platforms : string [ ] ) : IFuture < void > {
61+ return ( ( ) => {
62+ var platform = normalizedPlatformName . toLowerCase ( ) ;
63+ var assetsDirectoryPath = path . join ( this . $projectData . platformsDir , platform , "assets" ) ;
64+ var appResourcesDirectoryPath = path . join ( assetsDirectoryPath , constants . APP_FOLDER_NAME , constants . APP_RESOURCES_FOLDER_NAME ) ;
65+ shell . cp ( "-r" , path . join ( this . $projectData . projectDir , constants . APP_FOLDER_NAME ) , assetsDirectoryPath ) ;
66+
67+ if ( this . $fs . exists ( appResourcesDirectoryPath ) . wait ( ) ) {
68+ shell . cp ( "-r" , path . join ( appResourcesDirectoryPath , normalizedPlatformName , "*" ) , path . join ( this . $projectData . platformsDir , platform , "res" ) ) ;
69+ this . $fs . deleteDirectory ( appResourcesDirectoryPath ) . wait ( ) ;
70+ }
71+
72+ var files = helpers . enumerateFilesInDirectorySync ( path . join ( assetsDirectoryPath , constants . APP_FOLDER_NAME ) ) ;
73+ var platformsAsString = platforms . join ( "|" ) ;
74+
75+ _ . each ( files , fileName => {
76+ var platformInfo = AndroidProjectService . parsePlatformSpecificFileName ( path . basename ( fileName ) , platformsAsString ) ;
77+ var shouldExcludeFile = platformInfo && platformInfo . platform !== platform ;
78+ if ( shouldExcludeFile ) {
79+ this . $fs . deleteFile ( fileName ) . wait ( ) ;
80+ } else if ( platformInfo && platformInfo . onDeviceName ) {
81+ this . $fs . rename ( fileName , path . join ( path . dirname ( fileName ) , platformInfo . onDeviceName ) ) . wait ( ) ;
82+ }
83+ } ) ;
84+ } ) . future < void > ( ) ( ) ;
85+ }
86+
87+ public buildProject ( projectRoot : string ) : IFuture < void > {
88+ return ( ( ) => {
89+ var buildConfiguration = options . release ? "release" : "debug" ;
90+ var args = this . getAntArgs ( buildConfiguration , projectRoot ) ;
91+ this . spawn ( 'ant' , args ) ;
92+ } ) . future < void > ( ) ( ) ;
93+ }
94+
95+ private spawn ( command : string , args : string [ ] , options ?: any ) : void {
96+ if ( helpers . isWindows ( ) ) {
97+ args . unshift ( '/s' , '/c' , command ) ;
98+ command = 'cmd' ;
99+ }
100+
101+ this . $childProcess . spawn ( command , args , { cwd : options , stdio : 'inherit' } ) ;
102+ }
103+
104+ private getAntArgs ( configuration : string , projectRoot : string ) : string [ ] {
105+ var args = [ configuration , "-f" , path . join ( projectRoot , "build.xml" ) ] ;
106+ return args ;
107+ }
108+
109+ private runAndroidUpdate ( projectPath : string , targetApi : string ) : IFuture < void > {
110+ return ( ( ) => {
111+ var args = [
112+ "--path" , projectPath ,
113+ "--target" , targetApi
114+ ] ;
115+
116+ this . spawn ( "android update project" , args ) ;
117+ } ) . future < void > ( ) ( ) ;
118+ }
119+
120+ private validatePackageName ( packageName : string ) : void {
121+ //Make the package conform to Java package types
122+ //Enforce underscore limitation
123+ if ( ! / ^ [ a - z A - Z ] + ( \. [ a - z A - Z 0 - 9 ] [ a - z A - Z 0 - 9 _ ] * ) + $ / . test ( packageName ) ) {
124+ this . $errors . fail ( "Package name must look like: com.company.Name" ) ;
125+ }
126+
127+ //Class is a reserved word
128+ if ( / \b [ C c ] l a s s \b / . test ( packageName ) ) {
129+ this . $errors . fail ( "class is a reserved word" ) ;
130+ }
131+ }
132+
133+ private validateProjectName ( projectName : string ) : void {
134+ if ( projectName === '' ) {
135+ this . $errors . fail ( "Project name cannot be empty" ) ;
136+ }
137+
138+ //Classes in Java don't begin with numbers
139+ if ( / ^ [ 0 - 9 ] / . test ( projectName ) ) {
140+ this . $errors . fail ( "Project name must not begin with a number" ) ;
141+ }
142+ }
143+
144+ private validateAndroidTarget ( frameworkDir : string ) {
145+ var validTarget = this . getTarget ( frameworkDir ) . wait ( ) ;
146+ var output = this . $childProcess . exec ( 'android list targets' ) . wait ( ) ;
147+ if ( ! output . match ( validTarget ) ) {
148+ this . $errors . fail ( "Please install Android target %s the Android newest SDK). Make sure you have the latest Android tools installed as well. Run \"android\" from your command-line to install/update any missing SDKs or tools." ,
149+ validTarget . split ( '-' ) [ 1 ] ) ;
150+ }
151+ }
152+
153+ private getTarget ( projectRoot : string ) : IFuture < string > {
154+ return ( ( ) => {
155+ var projectPropertiesFilePath = path . join ( projectRoot , "project.properties" ) ;
156+
157+ if ( this . $fs . exists ( projectPropertiesFilePath ) . wait ( ) ) {
158+ var properties = this . $propertiesParser . createEditor ( projectPropertiesFilePath ) . wait ( ) ;
159+ return properties . get ( "target" ) ;
160+ }
161+
162+ return "" ;
163+ } ) . future < string > ( ) ( ) ;
164+ }
165+
166+ private checkAnt ( ) : IFuture < void > {
167+ return ( ( ) => {
168+ try {
169+ this . $childProcess . exec ( "ant -version" ) . wait ( ) ;
170+ } catch ( error ) {
171+ this . $errors . fail ( "Error executing commands 'ant', make sure you have ant installed and added to your PATH." )
172+ }
173+ } ) . future < void > ( ) ( ) ;
174+ }
175+
176+ private checkJava ( ) : IFuture < void > {
177+ return ( ( ) => {
178+ try {
179+ this . $childProcess . exec ( "java -version" ) . wait ( ) ;
180+ } catch ( error ) {
181+ this . $errors . fail ( "%s\n Failed to run 'java', make sure your java environment is set up.\n Including JDK and JRE.\n Your JAVA_HOME variable is %s" , error , process . env . JAVA_HOME ) ;
182+ }
183+ } ) . future < void > ( ) ( ) ;
184+ }
185+
186+ private checkAndroid ( ) : IFuture < void > {
187+ return ( ( ) => {
188+ try {
189+ this . $childProcess . exec ( 'android list targets' ) . wait ( ) ;
190+ } catch ( error ) {
191+ if ( error . match ( / c o m m a n d \s n o t \s f o u n d / ) ) {
192+ this . $errors . fail ( "The command \"android\" failed. Make sure you have the latest Android SDK installed, and the \"android\" command (inside the tools/ folder) is added to your path." ) ;
193+ } else {
194+ this . $errors . fail ( "An error occurred while listing Android targets" ) ;
195+ }
196+ }
197+ } ) . future < void > ( ) ( ) ;
198+ }
199+
200+ private static parsePlatformSpecificFileName ( fileName : string , platforms : string ) : any {
201+ var regex = util . format ( "^(.+?)\.(%s)(\..+?)$" , platforms ) ;
202+ var parsed = fileName . toLowerCase ( ) . match ( new RegExp ( regex , "i" ) ) ;
203+ if ( parsed ) {
204+ return {
205+ platform : parsed [ 2 ] ,
206+ onDeviceName : parsed [ 1 ] + parsed [ 3 ]
207+ } ;
208+ }
209+ return undefined ;
210+ }
211+ }
212+ $injector . register ( "androidProjectService" , AndroidProjectService ) ;
0 commit comments