@@ -61,6 +61,11 @@ class CommandArguments {
61
61
62
62
this . xcodePath = this . validatedStringArgument ( '--xcode-path' , parsedArguments [ '--xcode-path' ] ) ;
63
63
this . projectPath = this . validatedStringArgument ( '--project-path' , parsedArguments [ '--project-path' ] ) ;
64
+ this . projectName = this . validatedStringArgument ( '--project-name' , parsedArguments [ '--project-name' ] ) ;
65
+ this . expectedConfigurationBuildDir = this . validatedStringArgument (
66
+ '--expected-configuration-build-dir' ,
67
+ parsedArguments [ '--expected-configuration-build-dir' ] ,
68
+ ) ;
64
69
this . workspacePath = this . validatedStringArgument ( '--workspace-path' , parsedArguments [ '--workspace-path' ] ) ;
65
70
this . targetDestinationId = this . validatedStringArgument ( '--device-id' , parsedArguments [ '--device-id' ] ) ;
66
71
this . targetSchemeName = this . validatedStringArgument ( '--scheme' , parsedArguments [ '--scheme' ] ) ;
@@ -92,42 +97,76 @@ class CommandArguments {
92
97
}
93
98
94
99
/**
95
- * Validates the flag is allowed for the current command.
100
+ * Returns map of commands to map of allowed arguments. For each command, if
101
+ * an argument flag is a key, than that flag is allowed for that command. If
102
+ * the value for the key is true, then it is required for the command.
96
103
*
97
- * @param {!string } flag
98
- * @param {?string } value
99
- * @returns {!bool }
100
- * @throws Will throw an error if the flag is not allowed for the current
101
- * command and the value is not null, undefined, or empty.
104
+ * @returns {!string } Map of commands to allowed and optionally required
105
+ * arguments.
102
106
*/
103
- isArgumentAllowed ( flag , value ) {
104
- const allowedArguments = {
105
- 'common ' : {
107
+ argumentSettings ( ) {
108
+ return {
109
+ 'check-workspace-opened ' : {
106
110
'--xcode-path' : true ,
107
111
'--project-path' : true ,
108
112
'--workspace-path' : true ,
109
- '--verbose' : true ,
113
+ '--verbose' : false ,
110
114
} ,
111
- 'check-workspace-opened' : { } ,
112
115
'debug' : {
116
+ '--xcode-path' : true ,
117
+ '--project-path' : true ,
118
+ '--workspace-path' : true ,
119
+ '--project-name' : true ,
120
+ '--expected-configuration-build-dir' : false ,
113
121
'--device-id' : true ,
114
122
'--scheme' : true ,
115
123
'--skip-building' : true ,
116
124
'--launch-args' : true ,
125
+ '--verbose' : false ,
117
126
} ,
118
127
'stop' : {
128
+ '--xcode-path' : true ,
129
+ '--project-path' : true ,
130
+ '--workspace-path' : true ,
119
131
'--close-window' : true ,
120
132
'--prompt-to-save' : true ,
133
+ '--verbose' : false ,
121
134
} ,
122
- }
135
+ } ;
136
+ }
123
137
124
- const isAllowed = allowedArguments [ 'common' ] [ flag ] === true || allowedArguments [ this . command ] [ flag ] === true ;
138
+ /**
139
+ * Validates the flag is allowed for the current command.
140
+ *
141
+ * @param {!string } flag
142
+ * @param {?string } value
143
+ * @returns {!bool }
144
+ * @throws Will throw an error if the flag is not allowed for the current
145
+ * command and the value is not null, undefined, or empty.
146
+ */
147
+ isArgumentAllowed ( flag , value ) {
148
+ const isAllowed = this . argumentSettings ( ) [ this . command ] . hasOwnProperty ( flag ) ;
125
149
if ( isAllowed === false && ( value != null && value !== '' ) ) {
126
150
throw `The flag ${ flag } is not allowed for the command ${ this . command } .` ;
127
151
}
128
152
return isAllowed ;
129
153
}
130
154
155
+ /**
156
+ * Validates required flag has a value.
157
+ *
158
+ * @param {!string } flag
159
+ * @param {?string } value
160
+ * @throws Will throw an error if the flag is required for the current
161
+ * command and the value is not null, undefined, or empty.
162
+ */
163
+ validateRequiredArgument ( flag , value ) {
164
+ const isRequired = this . argumentSettings ( ) [ this . command ] [ flag ] === true ;
165
+ if ( isRequired === true && ( value == null || value === '' ) ) {
166
+ throw `Missing value for ${ flag } ` ;
167
+ }
168
+ }
169
+
131
170
/**
132
171
* Parses the command line arguments into an object.
133
172
*
@@ -182,9 +221,7 @@ class CommandArguments {
182
221
if ( this . isArgumentAllowed ( flag , value ) === false ) {
183
222
return null ;
184
223
}
185
- if ( value == null || value === '' ) {
186
- throw `Missing value for ${ flag } ` ;
187
- }
224
+ this . validateRequiredArgument ( flag , value ) ;
188
225
return value ;
189
226
}
190
227
@@ -226,9 +263,7 @@ class CommandArguments {
226
263
if ( this . isArgumentAllowed ( flag , value ) === false ) {
227
264
return null ;
228
265
}
229
- if ( value == null || value === '' ) {
230
- throw `Missing value for ${ flag } ` ;
231
- }
266
+ this . validateRequiredArgument ( flag , value ) ;
232
267
try {
233
268
return JSON . parse ( value ) ;
234
269
} catch ( e ) {
@@ -347,6 +382,15 @@ function debugApp(xcode, args) {
347
382
return new FunctionResult ( null , destinationResult . error )
348
383
}
349
384
385
+ // If expectedConfigurationBuildDir is available, ensure that it matches the
386
+ // build settings.
387
+ if ( args . expectedConfigurationBuildDir != null && args . expectedConfigurationBuildDir !== '' ) {
388
+ const updateResult = waitForConfigurationBuildDirToUpdate ( targetWorkspace , args ) ;
389
+ if ( updateResult . error != null ) {
390
+ return new FunctionResult ( null , updateResult . error ) ;
391
+ }
392
+ }
393
+
350
394
try {
351
395
// Documentation from the Xcode Script Editor dictionary indicates that the
352
396
// `debug` function has a parameter called `runDestinationSpecifier` which
@@ -528,3 +572,92 @@ function stopApp(xcode, args) {
528
572
}
529
573
return new FunctionResult ( null , null ) ;
530
574
}
575
+
576
+ /**
577
+ * Gets resolved build setting for CONFIGURATION_BUILD_DIR and waits until its
578
+ * value matches the `--expected-configuration-build-dir` argument. Waits up to
579
+ * 2 minutes.
580
+ *
581
+ * @param {!WorkspaceDocument } targetWorkspace A `WorkspaceDocument` (Xcode Mac
582
+ * Scripting class).
583
+ * @param {!CommandArguments } args
584
+ * @returns {!FunctionResult } Always returns null as the `result`.
585
+ */
586
+ function waitForConfigurationBuildDirToUpdate ( targetWorkspace , args ) {
587
+ // Get the project
588
+ let project ;
589
+ try {
590
+ project = targetWorkspace . projects ( ) . find ( x => x . name ( ) == args . projectName ) ;
591
+ } catch ( e ) {
592
+ return new FunctionResult ( null , `Failed to find project ${ args . projectName } : ${ e } ` ) ;
593
+ }
594
+ if ( project == null ) {
595
+ return new FunctionResult ( null , `Failed to find project ${ args . projectName } .` ) ;
596
+ }
597
+
598
+ // Get the target
599
+ let target ;
600
+ try {
601
+ // The target is probably named the same as the project, but if not, just use the first.
602
+ const targets = project . targets ( ) ;
603
+ target = targets . find ( x => x . name ( ) == args . projectName ) ;
604
+ if ( target == null && targets . length > 0 ) {
605
+ target = targets [ 0 ] ;
606
+ if ( args . verbose ) {
607
+ console . log ( `Failed to find target named ${ args . projectName } , picking first target: ${ target . name ( ) } .` ) ;
608
+ }
609
+ }
610
+ } catch ( e ) {
611
+ return new FunctionResult ( null , `Failed to find target: ${ e } ` ) ;
612
+ }
613
+ if ( target == null ) {
614
+ return new FunctionResult ( null , `Failed to find target.` ) ;
615
+ }
616
+
617
+ try {
618
+ // Use the first build configuration (Debug). Any should do since they all
619
+ // include Generated.xcconfig.
620
+ const buildConfig = target . buildConfigurations ( ) [ 0 ] ;
621
+ const buildSettings = buildConfig . resolvedBuildSettings ( ) . reverse ( ) ;
622
+
623
+ // CONFIGURATION_BUILD_DIR is often at (reverse) index 225 for Xcode
624
+ // projects, so check there first. If it's not there, search the build
625
+ // settings (which can be a little slow).
626
+ const defaultIndex = 225 ;
627
+ let configurationBuildDirSettings ;
628
+ if ( buildSettings [ defaultIndex ] != null && buildSettings [ defaultIndex ] . name ( ) === 'CONFIGURATION_BUILD_DIR' ) {
629
+ configurationBuildDirSettings = buildSettings [ defaultIndex ] ;
630
+ } else {
631
+ configurationBuildDirSettings = buildSettings . find ( x => x . name ( ) === 'CONFIGURATION_BUILD_DIR' ) ;
632
+ }
633
+
634
+ if ( configurationBuildDirSettings == null ) {
635
+ // This should not happen, even if it's not set by Flutter, there should
636
+ // always be a resolved build setting for CONFIGURATION_BUILD_DIR.
637
+ return new FunctionResult ( null , `Unable to find CONFIGURATION_BUILD_DIR.` ) ;
638
+ }
639
+
640
+ // Wait up to 2 minutes for the CONFIGURATION_BUILD_DIR to update to the
641
+ // expected value.
642
+ const checkFrequencyInSeconds = 0.5 ;
643
+ const maxWaitInSeconds = 2 * 60 ; // 2 minutes
644
+ const verboseLogInterval = 10 * ( 1 / checkFrequencyInSeconds ) ;
645
+ const iterations = maxWaitInSeconds * ( 1 / checkFrequencyInSeconds ) ;
646
+ for ( let i = 0 ; i < iterations ; i ++ ) {
647
+ const verbose = args . verbose && i % verboseLogInterval === 0 ;
648
+
649
+ const configurationBuildDir = configurationBuildDirSettings . value ( ) ;
650
+ if ( configurationBuildDir === args . expectedConfigurationBuildDir ) {
651
+ console . log ( `CONFIGURATION_BUILD_DIR: ${ configurationBuildDir } ` ) ;
652
+ return new FunctionResult ( null , null ) ;
653
+ }
654
+ if ( verbose ) {
655
+ console . log ( `Current CONFIGURATION_BUILD_DIR: ${ configurationBuildDir } while expecting ${ args . expectedConfigurationBuildDir } ` ) ;
656
+ }
657
+ delay ( checkFrequencyInSeconds ) ;
658
+ }
659
+ return new FunctionResult ( null , 'Timed out waiting for CONFIGURATION_BUILD_DIR to update.' ) ;
660
+ } catch ( e ) {
661
+ return new FunctionResult ( null , `Failed to get CONFIGURATION_BUILD_DIR: ${ e } ` ) ;
662
+ }
663
+ }
0 commit comments