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