36
36
import java .nio .file .Path ;
37
37
import java .time .LocalDateTime ;
38
38
import java .time .format .DateTimeFormatter ;
39
+ import java .util .ArrayList ;
40
+ import java .util .Arrays ;
41
+ import java .util .HashMap ;
39
42
import java .util .List ;
43
+ import java .util .Map ;
40
44
import java .util .Objects ;
41
45
import java .util .stream .Collectors ;
42
46
45
49
public class Deploy {
46
50
47
51
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter .ofPattern ("yyyyMMddHHmmss" );
48
- private static MobileDeviceBridge bridge ;
52
+ private static final List <String > LIBIMOBILEDEVICE_DEPENDENCIES = Arrays .asList (
53
+ "libssl" , "libcrypto" , "libusbmuxd" , "libplist" );
54
+
55
+ private Path iosDeployPath ;
56
+
57
+ public Deploy () throws IOException , InterruptedException {
58
+ checkPrerequisites ();
59
+ }
60
+
61
+ public Path getIosDeployPath () {
62
+ return iosDeployPath ;
63
+ }
64
+
65
+ private void checkPrerequisites () throws IOException , InterruptedException {
66
+ iosDeployPath = null ;
49
67
50
- public static Path getIOSDeployPath () throws IOException , InterruptedException {
51
68
// Check for Homebrew installed
52
69
String response = ProcessRunner .runProcessForSingleOutput ("check brew" ,"which" , "brew" );
53
- if (response == null || response .isEmpty ()) {
70
+ if (response == null || response .isEmpty () || ! Files . exists ( Path . of ( response )) ) {
54
71
Logger .logSevere ("Homebrew not found" );
55
72
throw new RuntimeException ("Open a terminal and run the following command to install Homebrew: \n \n " +
56
73
"ruby -e \" $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\" " );
57
74
}
58
75
Logger .logDebug ("Brew found at " + response );
59
76
77
+ // Check if dependencies of libimobiledevice are installed and retrieve linked versions
78
+ Map <String , List <String >> map = new HashMap <>();
79
+ for (String nameLib : LIBIMOBILEDEVICE_DEPENDENCIES ) {
80
+ List <String > pathLibs = checkDependencyPaths (nameLib );
81
+ List <String > linkLibs = checkDependencyLinks (nameLib , pathLibs );
82
+ map .put (nameLib , linkLibs );
83
+ }
84
+
85
+ // Check for libimobiledevice installed
86
+ List <String > libiPath = checkDependencyPaths ("libimobiledevice" );
87
+ if (libiPath == null || libiPath .isEmpty () || !Files .exists (Path .of (libiPath .get (0 )))) {
88
+ Logger .logSevere ("Error finding libimobiledevice.dylib" );
89
+ return ;
90
+ }
91
+
92
+ ProcessRunner runner = new ProcessRunner ("otool" , "-L" , libiPath .get (0 ));
93
+ if (runner .runProcess ("otool" ) == 0 ) {
94
+ for (String key : map .keySet ()) {
95
+ if (runner .getResponses ().stream ()
96
+ .noneMatch (link -> map .get (key ).stream ().anyMatch (link ::contains ))) {
97
+ Logger .logSevere ("Error: there is a mismatch in the dependency (" + key + ") required by libimobiledevice.dylib: " + map .get (key ) + "is required but it wasn't found" );
98
+ throw new RuntimeException ("Open a terminal and run the following command to reinstall the required libraries: \n \n " +
99
+ "brew reinstall " + key );
100
+ }
101
+ }
102
+ }
103
+
60
104
// Check for ios-deploy installed
61
105
response = ProcessRunner .runProcessForSingleOutput ("check ios-deploy" ,"which" , "ios-deploy" );
62
- if (response == null || response .isEmpty ()) {
63
- Logger .logSevere ("ios-deploy not found. It will be installed now" );
64
- ProcessRunner runner = new ProcessRunner ("brew" , "install" , "ios-deploy" );
65
- if (runner .runProcess ("ios-deploy" ) == 0 ) {
66
- Logger .logDebug ("ios-deploy installed" );
67
- return getIOSDeployPath ();
68
- } else {
69
- Logger .logDebug ("Error installing ios-deploy" );
70
- return null ;
106
+ if (response == null || response .isEmpty () || !Files .exists (Path .of (response ))) {
107
+ if (installIOSDeploy ()) {
108
+ checkPrerequisites ();
71
109
}
72
110
} else {
73
- Logger .logDebug ("ios-deploy found at " + response );
111
+ // Check for ios-deploy version installed (it should be 1.10+)
112
+ String version = ProcessRunner .runProcessForSingleOutput ("ios-deploy version" ,"ios-deploy" , "-V" );
113
+ if (version != null && !version .isEmpty () && (version .startsWith ("1.8" ) || version .startsWith ("1.9" ))) {
114
+ Logger .logDebug ("ios-deploy version (" + version + ") is outdated" );
115
+ uninstallIOSDeploy ();
116
+ if (installIOSDeploy ()) {
117
+ checkPrerequisites ();
118
+ }
119
+ } else {
120
+ Logger .logDebug ("ios-deploy found at " + response );
121
+ iosDeployPath = Path .of (response );
122
+ }
74
123
}
75
- return Path .of (response );
76
124
}
77
125
78
- public static String [] connectedDevices () {
79
- if (bridge == null ) {
80
- bridge = MobileDeviceBridge . instance ;
126
+ private String [] connectedDevices () throws IOException , InterruptedException {
127
+ if (iosDeployPath == null ) {
128
+ return new String [] {} ;
81
129
}
82
-
83
- return bridge .getDeviceIds ();
130
+ ProcessRunner runner = new ProcessRunner ("ios-deploy" , "-c" );
131
+ if (!runner .runTimedProcess ("connected devices" , 10L )) {
132
+ Logger .logSevere ("Error finding connected devices" );
133
+ return new String [] {};
134
+ }
135
+ List <String > devices = runner .getResponses ();
136
+ return devices .stream ()
137
+ .filter (line -> line .startsWith ("[....] Found" ))
138
+ .map (line -> line .substring ("[....] Found " .length ()).split ("\\ s" )[0 ])
139
+ .peek (id -> Logger .logDebug ("ID found: " + id ))
140
+ .toArray (String []::new );
84
141
}
85
142
86
- public static boolean install (String app ) throws IOException , InterruptedException {
87
- Path deploy = getIOSDeployPath ();
88
- if (deploy != null ) {
89
- String [] devices = Deploy .connectedDevices ();
90
- if (devices == null || devices .length == 0 ) {
91
- Logger .logSevere ("No iOS devices connected to this system. Exit install procedure" );
92
- return false ;
93
- }
94
- if (devices .length > 1 ) {
95
- Logger .logSevere ("Multiple iOS devices connected to this system: " + String .join (", " , devices ) + ". We'll use the first one." );
96
- }
97
- String deviceId = devices [0 ];
143
+ public boolean install (String app ) throws IOException , InterruptedException {
144
+ if (iosDeployPath == null ) {
145
+ Logger .logSevere ("Error: ios-deploy was not found" );
146
+ return false ;
147
+ }
98
148
99
- ProcessRunner runner = new ProcessRunner (deploy .toString (),
100
- "--id" , deviceId , "--bundle" , app , "--no-wifi" , "--debug" , "--noninteractive" );
101
- runner .addToEnv ("PATH" , "/usr/bin/:$PATH" );
102
- runner .setInfo (true );
149
+ String [] devices = connectedDevices ();
150
+ if (devices == null || devices .length == 0 ) {
151
+ Logger .logInfo ("No iOS devices connected to this system. Exit install procedure" );
152
+ return false ;
153
+ }
154
+ if (devices .length > 1 ) {
155
+ Logger .logInfo ("Multiple iOS devices connected to this system: " + String .join (", " , devices ) + ". We'll use the first one." );
156
+ }
157
+ String deviceId = devices [0 ];
158
+
159
+ ProcessRunner runner = new ProcessRunner (iosDeployPath .toString (),
160
+ "--id" , deviceId , "--bundle" , app , "--no-wifi" , "--debug" , "--noninteractive" );
161
+ runner .addToEnv ("PATH" , "/usr/bin/:$PATH" );
162
+ runner .setInfo (true );
163
+ boolean keepTrying = true ;
164
+ while (keepTrying ) {
165
+ keepTrying = false ;
103
166
boolean result = runner .runTimedProcess ("run" , 60 );
104
167
Logger .logInfo ("result = " + result );
105
- return result ;
168
+ if (result ) {
169
+ if (runner .getResponses ().stream ().anyMatch (l -> "Error: The device is locked." .equals (l ))) {
170
+ Logger .logInfo ("Device locked! Please, unlock and press ENTER to try again" );
171
+ System .in .read ();
172
+ keepTrying = true ;
173
+ }
174
+ } else {
175
+ return false ;
176
+ }
106
177
}
107
- return false ;
178
+ return true ;
108
179
}
109
180
110
- public static void addDebugSymbolInfo (Path appPath , String appName ) throws IOException , InterruptedException {
181
+ public void addDebugSymbolInfo (Path appPath , String appName ) throws IOException , InterruptedException {
111
182
Path applicationPath = appPath .resolve (appName + ".app" );
112
183
Path debugSymbolsPath = Path .of (applicationPath .toString () + ".dSYM" );
113
184
if (Files .exists (debugSymbolsPath )) {
@@ -124,7 +195,7 @@ public static void addDebugSymbolInfo(Path appPath, String appName) throws IOExc
124
195
}
125
196
}
126
197
127
- private static void copyAppToProducts (Path debugSymbolsPath , Path executablePath , String appName ) throws IOException {
198
+ private void copyAppToProducts (Path debugSymbolsPath , Path executablePath , String appName ) throws IOException {
128
199
if (Files .exists (XCODE_PRODUCTS_PATH )) {
129
200
List <Path > oldAppsPaths = Files .walk (XCODE_PRODUCTS_PATH , 1 )
130
201
.filter (Objects ::nonNull )
@@ -148,4 +219,63 @@ private static void copyAppToProducts(Path debugSymbolsPath, Path executablePath
148
219
Files .createDirectories (productDebugSymbolsPath );
149
220
FileOps .copyDirectory (debugSymbolsPath , productDebugSymbolsPath );
150
221
}
222
+
223
+ private List <String > checkDependencyPaths (String nameLib ) throws IOException , InterruptedException {
224
+ ProcessRunner runner = new ProcessRunner ("/bin/sh" , "-c" , "find $(brew --cellar) -name " + nameLib + ".dylib" );
225
+ if (runner .runProcess (nameLib ) != 0 ) {
226
+ Logger .logDebug ("Error finding " + nameLib );
227
+ return new ArrayList <>();
228
+ }
229
+ return runner .getResponses ().stream ()
230
+ .map (libPath -> {
231
+ Logger .logDebug ("lib " + nameLib + " found at " + libPath );
232
+ if (libPath == null || libPath .isEmpty () || !Files .exists (Path .of (libPath ))) {
233
+ Logger .logSevere ("Error finding " + nameLib + ".dylib" );
234
+ throw new RuntimeException ("Open a terminal and run the following command to install " + nameLib + ".dylib: \n \n " +
235
+ "brew install --HEAD " + nameLib );
236
+ }
237
+ Logger .logDebug (nameLib + ".dylib found in: " + libPath );
238
+ return libPath ;
239
+ })
240
+ .collect (Collectors .toList ());
241
+ }
242
+
243
+ private List <String > checkDependencyLinks (String nameLib , List <String > libPaths ) throws IOException , InterruptedException {
244
+ List <String > libLinks = new ArrayList <>();
245
+ for (String libPath : libPaths ) {
246
+ // retrieve name of linked library
247
+ String linkedLib = ProcessRunner .runProcessForSingleOutput ("readlink " + nameLib , "readlink" , libPath );
248
+ Logger .logDebug (nameLib + ".dylib link of: " + linkedLib );
249
+ if (linkedLib == null || linkedLib .isEmpty ()) {
250
+ throw new RuntimeException ("Error finding " + nameLib + ".dylib version" );
251
+ }
252
+ libLinks .add (linkedLib );
253
+ }
254
+ return libLinks ;
255
+ }
256
+
257
+ private boolean uninstallIOSDeploy () throws IOException , InterruptedException {
258
+ ProcessRunner runner = new ProcessRunner ("brew" , "unlink" , "ios-deploy" );
259
+ if (runner .runProcess ("ios-deploy unlink" ) == 0 ) {
260
+ Logger .logDebug ("ios-deploy unlinked" );
261
+ }
262
+ Logger .logDebug ("Uninstalling ios-deploy" );
263
+ runner = new ProcessRunner ("brew" , "uninstall" , "ios-deploy" );
264
+ if (runner .runProcess ("ios-deploy uninstall" ) == 0 ) {
265
+ Logger .logDebug ("ios-deploy uninstalled" );
266
+ return true ;
267
+ }
268
+ return false ;
269
+ }
270
+
271
+ private boolean installIOSDeploy () throws IOException , InterruptedException {
272
+ Logger .logInfo ("ios-deploy not found. It will be installed now" );
273
+
274
+ ProcessRunner runner = new ProcessRunner ("brew" , "install" , "--HEAD" , "ios-deploy" );
275
+ if (runner .runProcess ("ios-deploy" ) == 0 ) {
276
+ Logger .logDebug ("ios-deploy installed" );
277
+ return true ;
278
+ }
279
+ throw new RuntimeException ("Error installing ios-deploy. See detailed message above on how to proceed. Then try to deploy again" );
280
+ }
151
281
}
0 commit comments