Skip to content

Commit b8359d9

Browse files
authored
[BIVS-2834] List devices (#252)
* Make destination.DeviceList exported * devicefinder list devices function * Fix lint issues * Fix device_finder tests
1 parent 834b0ff commit b8359d9

File tree

7 files changed

+197
-183
lines changed

7 files changed

+197
-183
lines changed

destination/device_finder.go

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package destination
22

33
import (
4+
"encoding/json"
45
"errors"
56
"fmt"
7+
"github.com/bitrise-io/go-utils/errorutil"
8+
"github.com/bitrise-io/go-utils/retry"
9+
"os"
610
"time"
711

812
"github.com/bitrise-io/go-utils/v2/command"
@@ -13,29 +17,18 @@ import (
1317
// Keep it in sync with https://github.com/bitrise-io/image-build-utils/blob/master/roles/simulators/defaults/main.yml#L14
1418
const defaultDeviceName = "Bitrise iOS default"
1519

16-
// Device is an available device
17-
type Device struct {
18-
ID string
19-
Status string
20-
Type string
21-
22-
Platform string
23-
Name string
24-
OS string
25-
Arch string
26-
}
27-
2820
// DeviceFinder is an interface that find a matching device for a given destination
2921
type DeviceFinder interface {
3022
FindDevice(destination Simulator) (Device, error)
23+
ListDevices() (*DeviceList, error)
3124
}
3225

3326
type deviceFinder struct {
3427
logger log.Logger
3528
commandFactory command.Factory
3629
xcodeVersion xcodeversion.Version
3730

38-
list *deviceList
31+
list *DeviceList
3932
}
4033

4134
// NewDeviceFinder retruns the default implementation of DeviceFinder
@@ -47,6 +40,54 @@ func NewDeviceFinder(log log.Logger, commandFactory command.Factory, xcodeVersio
4740
}
4841
}
4942

43+
// ListDevices ...
44+
func (d deviceFinder) ListDevices() (*DeviceList, error) {
45+
var list DeviceList
46+
47+
// Retry gathering device information since xcrun simctl list can fail to show the complete device list
48+
// Originally added in https://github.com/bitrise-steplib/steps-xcode-test/pull/155
49+
if err := retry.Times(3).Wait(10 * time.Second).Try(func(attempt uint) error {
50+
listCmd := d.commandFactory.Create("xcrun", []string{"simctl", "list", "--json"}, &command.Opts{
51+
Stderr: os.Stderr,
52+
})
53+
54+
d.logger.TDebugf("$ %s", listCmd.PrintableCommandArgs())
55+
output, err := listCmd.RunAndReturnTrimmedOutput()
56+
if err != nil {
57+
if errorutil.IsExitStatusError(err) {
58+
return fmt.Errorf("device list command failed: %w", err)
59+
}
60+
61+
return fmt.Errorf("failed to run device list command: %w", err)
62+
}
63+
64+
if err := json.Unmarshal([]byte(output), &list); err != nil {
65+
return fmt.Errorf("failed to unmarshal device list: %w, json: %s", err, output)
66+
}
67+
68+
hasAvailableDevice := false
69+
for _, deviceList := range list.Devices {
70+
for _, device := range deviceList {
71+
if !device.IsAvailable {
72+
d.logger.Warnf("device %s is unavailable: %s", device.Name, device.AvailabilityError)
73+
} else {
74+
hasAvailableDevice = true
75+
}
76+
}
77+
}
78+
79+
if hasAvailableDevice {
80+
return nil
81+
} else {
82+
return fmt.Errorf("no available device found")
83+
}
84+
}); err != nil {
85+
return &DeviceList{}, err
86+
}
87+
88+
return &list, nil
89+
}
90+
5091
// FindDevice returns a Simulator matching the destination
5192
func (d deviceFinder) FindDevice(destination Simulator) (Device, error) {
5293
var (
@@ -56,7 +97,7 @@ func (d deviceFinder) FindDevice(destination Simulator) (Device, error) {
5697

5798
start := time.Now()
5899
if d.list == nil {
59-
d.list, err = d.parseDeviceList()
100+
d.list, err = d.ListDevices()
60101
}
61102
if err == nil {
62103
device, err = d.deviceForDestination(destination)
@@ -83,21 +124,11 @@ func (d deviceFinder) FindDevice(destination Simulator) (Device, error) {
83124
d.logger.Debugf("Created device in %s", time.Since(start).Round(time.Second))
84125

85126
if err == nil {
86-
d.list, err = d.parseDeviceList()
127+
d.list, err = d.ListDevices()
87128
}
88129
if err == nil {
89130
device, err = d.deviceForDestination(destination)
90131
}
91132

92133
return device, err
93134
}
94-
95-
// XcodebuildDestination returns the required xcodebuild -destination flag value for a device
96-
func (d Device) XcodebuildDestination() string {
97-
// `arch` doesn't seem to work together with `id`
98-
if d.Arch == "" {
99-
return fmt.Sprintf("id=%s", d.ID)
100-
}
101-
102-
return fmt.Sprintf("platform=%s,name=%s,OS=%s,arch=%s", d.Platform, d.Name, d.OS, d.Arch)
103-
}

destination/device_finder_test.go

Lines changed: 74 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,14 @@ func Test_deviceFinder_FindDevice(t *testing.T) {
8686
Name: "iPhone 8",
8787
},
8888
want: Device{
89-
ID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
90-
Status: "Shutdown",
91-
Type: "iPhone 8",
92-
Platform: "iOS Simulator",
93-
Name: "iPhone 8",
94-
OS: "16.0",
89+
UDID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
90+
State: "Shutdown",
91+
TypeIdentifier: "com.apple.CoreSimulator.SimDeviceType.iPhone-8",
92+
Type: "iPhone 8",
93+
Platform: "iOS Simulator",
94+
Name: "iPhone 8",
95+
OS: "16.0",
96+
IsAvailable: true,
9597
},
9698
},
9799
{
@@ -103,12 +105,14 @@ func Test_deviceFinder_FindDevice(t *testing.T) {
103105
Name: "iPhone 8",
104106
},
105107
want: Device{
106-
ID: "3F9A1206-31E2-417B-BBC7-6330B52B8358",
107-
Status: "Shutdown",
108-
Type: "iPhone 8",
109-
Platform: "iOS Simulator",
110-
Name: "iPhone 8",
111-
OS: "13.7",
108+
UDID: "3F9A1206-31E2-417B-BBC7-6330B52B8358",
109+
State: "Shutdown",
110+
TypeIdentifier: "com.apple.CoreSimulator.SimDeviceType.iPhone-8",
111+
Type: "iPhone 8",
112+
Platform: "iOS Simulator",
113+
Name: "iPhone 8",
114+
OS: "13.7",
115+
IsAvailable: true,
112116
},
113117
},
114118
{
@@ -120,13 +124,15 @@ func Test_deviceFinder_FindDevice(t *testing.T) {
120124
Arch: "x86_64",
121125
},
122126
want: Device{
123-
ID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
124-
Status: "Shutdown",
125-
Type: "iPhone 8",
126-
Platform: "iOS Simulator",
127-
Name: "iPhone 8",
128-
OS: "16.0",
129-
Arch: "x86_64",
127+
UDID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
128+
State: "Shutdown",
129+
TypeIdentifier: "com.apple.CoreSimulator.SimDeviceType.iPhone-8",
130+
Type: "iPhone 8",
131+
Platform: "iOS Simulator",
132+
Name: "iPhone 8",
133+
OS: "16.0",
134+
Arch: "x86_64",
135+
IsAvailable: true,
130136
},
131137
},
132138
{
@@ -146,12 +152,14 @@ func Test_deviceFinder_FindDevice(t *testing.T) {
146152
Name: "Bitrise iOS default",
147153
},
148154
want: Device{
149-
ID: "20FDD0DF-0369-43FF-98E6-DBB8C820341E",
150-
Status: "Shutdown",
151-
Type: "iPhone 6s",
152-
Platform: "iOS Simulator",
153-
Name: "Bitrise iOS default",
154-
OS: "13.7",
155+
UDID: "20FDD0DF-0369-43FF-98E6-DBB8C820341E",
156+
State: "Shutdown",
157+
TypeIdentifier: "com.apple.CoreSimulator.SimDeviceType.iPhone-6s",
158+
Type: "iPhone 6s",
159+
Platform: "iOS Simulator",
160+
Name: "Bitrise iOS default",
161+
OS: "13.7",
162+
IsAvailable: true,
155163
},
156164
},
157165
{
@@ -162,12 +170,14 @@ func Test_deviceFinder_FindDevice(t *testing.T) {
162170
Name: "Bitrise iOS default",
163171
},
164172
want: Device{
165-
ID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
166-
Status: "Shutdown",
167-
Type: "iPhone 8",
168-
Platform: "iOS Simulator",
169-
Name: "iPhone 8",
170-
OS: "16.0",
173+
UDID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
174+
State: "Shutdown",
175+
TypeIdentifier: "com.apple.CoreSimulator.SimDeviceType.iPhone-8",
176+
Type: "iPhone 8",
177+
Platform: "iOS Simulator",
178+
Name: "iPhone 8",
179+
OS: "16.0",
180+
IsAvailable: true,
171181
},
172182
},
173183
{
@@ -196,12 +206,14 @@ func Test_deviceFinder_FindDevice(t *testing.T) {
196206
Name: "iPhone 8",
197207
},
198208
want: Device{
199-
ID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
200-
Status: "Shutdown",
201-
Type: "iPhone 8",
202-
Platform: "iOS Simulator",
203-
Name: "iPhone 8",
204-
OS: "16.0",
209+
UDID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
210+
State: "Shutdown",
211+
TypeIdentifier: "com.apple.CoreSimulator.SimDeviceType.iPhone-8",
212+
Type: "iPhone 8",
213+
Platform: "iOS Simulator",
214+
Name: "iPhone 8",
215+
OS: "16.0",
216+
IsAvailable: true,
205217
},
206218
},
207219
{
@@ -212,12 +224,14 @@ func Test_deviceFinder_FindDevice(t *testing.T) {
212224
Name: "iPhone 8",
213225
},
214226
want: Device{
215-
ID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
216-
Status: "Shutdown",
217-
Type: "iPhone 8",
218-
Platform: "iOS Simulator",
219-
Name: "iPhone 8",
220-
OS: "16.0",
227+
UDID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
228+
State: "Shutdown",
229+
TypeIdentifier: "com.apple.CoreSimulator.SimDeviceType.iPhone-8",
230+
Type: "iPhone 8",
231+
Platform: "iOS Simulator",
232+
Name: "iPhone 8",
233+
OS: "16.0",
234+
IsAvailable: true,
221235
},
222236
},
223237
{
@@ -228,12 +242,14 @@ func Test_deviceFinder_FindDevice(t *testing.T) {
228242
Name: "iPhone 8",
229243
},
230244
want: Device{
231-
ID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
232-
Status: "Shutdown",
233-
Type: "iPhone 8",
234-
Platform: "iOS Simulator",
235-
Name: "iPhone 8",
236-
OS: "16.0",
245+
UDID: "D64FA78C-5A25-4BF3-9EE8-855761042DEE",
246+
State: "Shutdown",
247+
TypeIdentifier: "com.apple.CoreSimulator.SimDeviceType.iPhone-8",
248+
Type: "iPhone 8",
249+
Platform: "iOS Simulator",
250+
Name: "iPhone 8",
251+
OS: "16.0",
252+
IsAvailable: true,
237253
},
238254
},
239255
{
@@ -244,12 +260,14 @@ func Test_deviceFinder_FindDevice(t *testing.T) {
244260
Name: "Apple Watch Series 7 - 45mm",
245261
},
246262
want: Device{
247-
ID: "4F40330B-622F-4B44-8918-0BBE62720CC4",
248-
Status: "Shutdown",
249-
Type: "Apple Watch Series 7 - 45mm",
250-
Platform: "watchOS Simulator",
251-
Name: "Apple Watch Series 7 - 45mm",
252-
OS: "9.0",
263+
UDID: "4F40330B-622F-4B44-8918-0BBE62720CC4",
264+
State: "Shutdown",
265+
TypeIdentifier: "com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-7-45mm",
266+
Type: "Apple Watch Series 7 - 45mm",
267+
Platform: "watchOS Simulator",
268+
Name: "Apple Watch Series 7 - 45mm",
269+
OS: "9.0",
270+
IsAvailable: true,
253271
},
254272
},
255273
}
@@ -291,7 +309,7 @@ func Test_deviceFinder_FindDevice_realXcrun(t *testing.T) {
291309
})
292310

293311
require.NoError(t, err)
294-
require.NotEmpty(t, got.ID)
312+
require.NotEmpty(t, got.UDID)
295313

296314
t.Logf("got: %+v", got)
297315
}

destination/errors.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ func (e *missingDeviceErr) Error() string {
2323
return fmt.Sprintf("device (%s) with runtime (%s) is not yet created", e.name, e.runtimeID)
2424
}
2525

26-
func newMissingRuntimeErr(platform, version string, availableRuntimes []deviceRuntime) error {
26+
func newMissingRuntimeErr(platform, version string, availableRuntimes []DeviceRuntime) error {
2727
runtimeList := prettyRuntimeList(availableRuntimes)
2828
return fmt.Errorf("%s %s is not installed. Choose one of the available %s runtimes: \n%s", platform, version, platform, runtimeList)
2929
}
3030

31-
func prettyRuntimeList(runtimes []deviceRuntime) string {
31+
func prettyRuntimeList(runtimes []DeviceRuntime) string {
3232
var items []string
3333
for _, runtime := range runtimes {
3434
items = append(items, fmt.Sprintf("- %s", runtime.Name))

0 commit comments

Comments
 (0)